From d38a72c5347a3cfcfd17ff479bc7c8e6f4273b2d Mon Sep 17 00:00:00 2001 From: Gunnar Morling Date: Sat, 30 Nov 2013 23:05:01 +0100 Subject: [PATCH] #74 Adding support for generating methods from abstract classes --- .../org/mapstruct/ap/MappingProcessor.java | 5 ++ .../java/org/mapstruct/ap/model/Mapper.java | 75 ++++++++++++++++++- .../org/mapstruct/ap/model/source/Method.java | 8 ++ .../ap/processor/MapperCreationProcessor.java | 22 +++--- .../processor/MethodRetrievalProcessor.java | 22 +++--- .../org.mapstruct.ap.model.Mapper.ftl | 2 +- .../test/abstractclass/AbstractClassTest.java | 47 ++++++++++++ .../ap/test/abstractclass/Source.java | 41 ++++++++++ .../abstractclass/SourceTargetMapper.java | 39 ++++++++++ .../ap/test/abstractclass/Target.java | 41 ++++++++++ 10 files changed, 277 insertions(+), 25 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractClassTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/abstractclass/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/abstractclass/SourceTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/abstractclass/Target.java diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index dc26d5f79..f3e4124c2 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -224,6 +224,11 @@ public class MappingProcessor extends AbstractProcessor { return e; } + @Override + public TypeElement visitTypeAsClass(TypeElement e, Void p) { + return e; + } + }, null ); } diff --git a/processor/src/main/java/org/mapstruct/ap/model/Mapper.java b/processor/src/main/java/org/mapstruct/ap/model/Mapper.java index c6f59e2cd..7a06289e9 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/Mapper.java +++ b/processor/src/main/java/org/mapstruct/ap/model/Mapper.java @@ -24,6 +24,9 @@ import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import javax.annotation.Generated; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; import org.mapstruct.ap.util.TypeFactory; @@ -35,8 +38,11 @@ import org.mapstruct.ap.util.TypeFactory; */ public class Mapper extends AbstractModelElement { + private static final String IMPLEMENTATION_SUFFIX = "Impl"; + private final TypeFactory typeFactory; private final String packageName; + private final boolean superTypeIsInterface; private final String interfaceName; private final String implementationName; private final List annotations; @@ -44,9 +50,11 @@ public class Mapper extends AbstractModelElement { private final List referencedMappers; private final Options options; - public Mapper(TypeFactory typeFactory, String packageName, String interfaceName, String implementationName, - List mappingMethods, List referencedMappers, Options options) { + private Mapper(TypeFactory typeFactory, String packageName, boolean superTypeIsInterface, String interfaceName, + String implementationName, List mappingMethods, + List referencedMappers, Options options) { this.packageName = packageName; + this.superTypeIsInterface = superTypeIsInterface; this.interfaceName = interfaceName; this.implementationName = implementationName; this.annotations = new ArrayList(); @@ -56,6 +64,59 @@ public class Mapper extends AbstractModelElement { this.typeFactory = typeFactory; } + public static class Builder { + + private TypeFactory typeFactory; + private TypeElement element; + private List mappingMethods; + private List mapperReferences; + private Options options; + private Elements elementUtils; + + public Builder element(TypeElement element) { + this.element = element; + return this; + } + + public Builder mappingMethods(List mappingMethods) { + this.mappingMethods = mappingMethods; + return this; + } + + public Builder mapperReferences(List mapperReferences) { + this.mapperReferences = mapperReferences; + return this; + } + + public Builder options(Options options) { + this.options = options; + return this; + } + + public Builder typeFactory(TypeFactory typeFactory) { + this.typeFactory = typeFactory; + return this; + } + + public Builder elementUtils(Elements elementUtils) { + this.elementUtils = elementUtils; + return this; + } + + public Mapper build() { + return new Mapper( + typeFactory, + elementUtils.getPackageOf( element ).getQualifiedName().toString(), + element.getKind() == ElementKind.INTERFACE ? true : false, + element.getSimpleName().toString(), + element.getSimpleName() + IMPLEMENTATION_SUFFIX, + mappingMethods, + mapperReferences, + options + ); + } + } + @Override public SortedSet getImportTypes() { SortedSet importedTypes = new TreeSet(); @@ -121,6 +182,16 @@ public class Mapper extends AbstractModelElement { return packageName; } + /** + * Whether the mapper super-type is an interface or not. + * + * @return {@code true} if the mapper is generated from an interface, {@code false} when generated from an abstract + * class. + */ + public boolean isSuperTypeInterface() { + return superTypeIsInterface; + } + public String getInterfaceName() { return interfaceName; } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/model/source/Method.java index c4df38a9f..ee207c3eb 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/Method.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; import org.mapstruct.ap.model.Parameter; import org.mapstruct.ap.model.Type; @@ -226,4 +227,11 @@ public class Method { return null; } + + /** + * Whether an implementation of this method must be generated or not. + */ + public boolean requiresImplementation() { + return declaringMapper == null && executable.getModifiers().contains( Modifier.ABSTRACT ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java index 7ba07225f..0be79e8bc 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java @@ -72,8 +72,6 @@ import org.mapstruct.ap.util.TypeFactory; */ public class MapperCreationProcessor implements ModelElementProcessor, Mapper> { - private static final String IMPLEMENTATION_SUFFIX = "Impl"; - private Elements elementUtils; private Types typeUtils; private Messager messager; @@ -109,15 +107,14 @@ public class MapperCreationProcessor implements ModelElementProcessor mappingMethods = getMappingMethods( methods, unmappedTargetPolicy ); List mapperReferences = getReferencedMappers( element ); - return new Mapper( - typeFactory, - elementUtils.getPackageOf( element ).getQualifiedName().toString(), - element.getSimpleName().toString(), - element.getSimpleName() + IMPLEMENTATION_SUFFIX, - mappingMethods, - mapperReferences, - options - ); + return new Mapper.Builder() + .element( element ) + .mappingMethods( mappingMethods ) + .mapperReferences( mapperReferences ) + .options( options ) + .typeFactory( typeFactory ) + .elementUtils( elementUtils ) + .build(); } /** @@ -160,7 +157,7 @@ public class MapperCreationProcessor implements ModelElementProcessor mappingMethods = new ArrayList(); for ( Method method : methods ) { - if ( method.getDeclaringMapper() != null ) { + if ( !method.requiresImplementation() ) { continue; } @@ -617,6 +614,7 @@ public class MapperCreationProcessor implements ModelElementProcessor retrieveMethods(TypeElement element, boolean implementationRequired) { + private List retrieveMethods(TypeElement element, boolean mapperRequiresImplementation) { List methods = new ArrayList(); - MapperPrism mapperPrism = implementationRequired ? MapperPrism.getInstanceOn( element ) : null; + MapperPrism mapperPrism = mapperRequiresImplementation ? MapperPrism.getInstanceOn( element ) : null; for ( ExecutableElement executable : methodsIn( element.getEnclosedElements() ) ) { - Method method = getMethod( element, executable, implementationRequired ); + Method method = getMethod( element, executable, mapperRequiresImplementation ); if ( method != null ) { methods.add( method ); } } //Add all methods of used mappers in order to reference them in the aggregated model - if ( implementationRequired ) { + if ( mapperRequiresImplementation ) { for ( TypeMirror usedMapper : mapperPrism.uses() ) { methods.addAll( retrieveMethods( @@ -112,12 +112,14 @@ public class MethodRetrievalProcessor implements ModelElementProcessor parameters = executables.retrieveParameters( method ); Type returnType = executables.retrieveReturnType( method ); //add method with property mappings if an implementation needs to be generated - if ( implementationRequired ) { + boolean methodRequiresImplementation = method.getModifiers().contains( Modifier.ABSTRACT ); + + if ( mapperRequiresImplementation && methodRequiresImplementation ) { List sourceParameters = extractSourceParameters( parameters ); Parameter targetParameter = extractTargetParameter( parameters ); Type resultType = selectResultType( returnType, targetParameter ); @@ -144,7 +146,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor <#nt><@includeModel object=annotation/> -public class ${implementationName} implements ${interfaceName} { +public class ${implementationName} <#if superTypeInterface>implements<#else>extends ${interfaceName} { <#list referencedMappers as mapper> <@includeModel object=mapper/> diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractClassTest.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractClassTest.java new file mode 100644 index 000000000..3656ca823 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractClassTest.java @@ -0,0 +1,47 @@ +/** + * Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.abstractclass; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.MapperTestBase; +import org.mapstruct.ap.testutil.WithClasses; +import org.testng.annotations.Test; + +import static org.fest.assertions.Assertions.assertThat; + +/** + * Test for the generation of implementation of abstract base classes. + * + * @author Gunnar Morling + */ +@WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) +public class AbstractClassTest extends MapperTestBase { + + @Test + @IssueKey("64") + public void shouldCreateImplementationOfAbstractMethod() { + Source source = new Source(); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getSize() ).isEqualTo( Long.valueOf( 181 ) ); + assertThat( target.getBirthday() ).isEqualTo( "26.04.1948" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Source.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Source.java new file mode 100644 index 000000000..9125ef834 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Source.java @@ -0,0 +1,41 @@ +/** + * Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.abstractclass; + +import java.util.Calendar; + +public class Source { + + private final int size; + private final Calendar birthday; + + public Source() { + size = 181; + birthday = Calendar.getInstance(); + birthday.set( 1948, 3, 26 ); + } + + public int getSize() { + return size; + } + + public Calendar getBirthday() { + return birthday; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/SourceTargetMapper.java new file mode 100644 index 000000000..a23db2850 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/SourceTargetMapper.java @@ -0,0 +1,39 @@ +/** + * Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.abstractclass; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public abstract class SourceTargetMapper { + + public static final SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + public abstract Target sourceToTarget(Source source); + + protected String calendarToString(Calendar calendar) { + DateFormat format = new SimpleDateFormat( "dd.MM.yyyy" ); + return format.format( calendar.getTime() ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Target.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Target.java new file mode 100644 index 000000000..c982f00d7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Target.java @@ -0,0 +1,41 @@ +/** + * Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.abstractclass; + +public class Target { + + private Long size; + private String birthday; + + public Long getSize() { + return size; + } + + public void setSize(Long size) { + this.size = size; + } + + public String getBirthday() { + return birthday; + } + + public void setBirthday(String birthday) { + this.birthday = birthday; + } +}