From 0b9ca3548d4c7de7b77c1e33626f890f7f907641 Mon Sep 17 00:00:00 2001 From: Ewald Volkert Date: Fri, 21 Nov 2014 17:43:12 +0100 Subject: [PATCH] #274 Evaluate explicit types of method parameters and return types using Types#asMemberOf --- .../ap/model/common/TypeFactory.java | 62 +++++++++++++- .../processor/MethodRetrievalProcessor.java | 29 ++++--- .../inheritedmappingmethod/BoundMappable.java | 29 +++++++ .../inheritedmappingmethod/CarMapper.java | 31 +++++++ .../inheritedmappingmethod/FastCarMapper.java | 31 +++++++ .../InheritedMappingMethodTest.java | 84 +++++++++++++++++++ .../UnboundMappable.java | 26 ++++++ .../inheritedmappingmethod/source/Car.java | 33 ++++++++ .../source/FastCar.java | 32 +++++++ .../inheritedmappingmethod/target/CarDto.java | 15 ++++ .../target/FastCarDto.java | 14 ++++ 11 files changed, 372 insertions(+), 14 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/BoundMappable.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/CarMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/FastCarMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/InheritedMappingMethodTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/UnboundMappable.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/source/Car.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/source/FastCar.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/target/CarDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/target/FastCarDto.java diff --git a/processor/src/main/java/org/mapstruct/ap/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/model/common/TypeFactory.java index d0b2ad95a..d1405ad83 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/model/common/TypeFactory.java @@ -18,10 +18,13 @@ */ package org.mapstruct.ap.model.common; +import static org.mapstruct.ap.util.SpecificCompilerWorkarounds.erasure; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NavigableMap; @@ -41,11 +44,13 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleElementVisitor6; +import javax.lang.model.util.SimpleTypeVisitor6; import javax.lang.model.util.Types; import org.mapstruct.ap.prism.MappingTargetPrism; @@ -53,8 +58,6 @@ import org.mapstruct.ap.prism.TargetTypePrism; import org.mapstruct.ap.util.AnnotationProcessingException; import org.mapstruct.ap.util.SpecificCompilerWorkarounds; -import static org.mapstruct.ap.util.SpecificCompilerWorkarounds.erasure; - /** * Factory creating {@link Type} instances. * @@ -206,6 +209,20 @@ public class TypeFactory { return getType( typeUtils.getDeclaredType( elementUtils.getTypeElement( "java.lang.Class" ), typeToUse ) ); } + /** + * Get the ExecutableType for given method as part of usedMapper. Possibly parameterized types in method declaration + * will be evaluated to concrete types then. + * + * @param usedMapper + * @param method + * @return the ExecutableType representing the method as part of usedMapper + */ + public ExecutableType getMethodType(TypeElement usedMapper, ExecutableElement method) { + TypeMirror asMemberOf = typeUtils.asMemberOf( (DeclaredType) usedMapper.asType(), method ); + ExecutableType methodType = asMemberOf.accept( new ExecutableTypeRetrievalVisitor(), null ); + return methodType; + } + public Parameter getSingleParameter(ExecutableElement method) { List parameters = method.getParameters(); @@ -241,10 +258,36 @@ public class TypeFactory { return result; } + public List getParameters(ExecutableType methodType, ExecutableElement method) { + List parameterTypes = methodType.getParameterTypes(); + List parameters = method.getParameters(); + List result = new ArrayList( parameters.size() ); + + Iterator varIt = parameters.iterator(); + Iterator typesIt = parameterTypes.iterator(); + + for ( ; varIt.hasNext(); ) { + VariableElement parameter = varIt.next(); + TypeMirror parameterType = typesIt.next(); + + result.add( new Parameter( + parameter.getSimpleName().toString(), + getType( parameterType ), + MappingTargetPrism.getInstanceOn( parameter ) != null, + TargetTypePrism.getInstanceOn( parameter ) != null ) ); + } + + return result; + } + public Type getReturnType(ExecutableElement method) { return getType( method.getReturnType() ); } + public Type getReturnType(ExecutableType method) { + return getType( method.getReturnType() ); + } + public List getThrownTypes(ExecutableElement method) { List thrownTypes = new ArrayList(); for (TypeMirror exceptionType : method.getThrownTypes() ) { @@ -253,6 +296,14 @@ public class TypeFactory { return thrownTypes; } + public List getThrownTypes(ExecutableType method) { + List thrownTypes = new ArrayList(); + for ( TypeMirror exceptionType : method.getThrownTypes() ) { + thrownTypes.add( getType( exceptionType ) ); + } + return thrownTypes; + } + private List getTypeParameters(TypeMirror mirror) { if ( mirror.getKind() != TypeKind.DECLARED ) { return java.util.Collections.emptyList(); @@ -340,4 +391,11 @@ public class TypeFactory { return e; } } + + private static class ExecutableTypeRetrievalVisitor extends SimpleTypeVisitor6 { + @Override + public ExecutableType visitExecutable(ExecutableType t, Void p) { + return t; + } + } } diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java index f4d4468e7..3570d18a1 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java @@ -18,15 +18,19 @@ */ package org.mapstruct.ap.processor; +import static org.mapstruct.ap.util.Executables.getAllEnclosedExecutableElements; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; + import javax.annotation.processing.Messager; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; @@ -47,8 +51,6 @@ import org.mapstruct.ap.prism.MappingsPrism; import org.mapstruct.ap.util.AnnotationProcessingException; import org.mapstruct.ap.util.MapperConfig; -import static org.mapstruct.ap.util.Executables.getAllEnclosedExecutableElements; - /** * A {@link ModelElementProcessor} which retrieves a list of {@link SourceMethod}s * representing all the mapping methods of the given bean mapper type as well as @@ -120,28 +122,30 @@ public class MethodRetrievalProcessor implements ModelElementProcessor parameters = typeFactory.getParameters( method ); + ExecutableType methodType = typeFactory.getMethodType( usedMapper, method ); + List parameters = typeFactory.getParameters( methodType, method ); boolean methodRequiresImplementation = method.getModifiers().contains( Modifier.ABSTRACT ); boolean containsTargetTypeParameter = SourceMethod.containsTargetTypeParameter( parameters ); //add method with property mappings if an implementation needs to be generated if ( ( usedMapper.equals( mapperToImplement ) ) && methodRequiresImplementation ) { - return getMethodRequiringImplementation( method, parameters, containsTargetTypeParameter ); + return getMethodRequiringImplementation( methodType, method, parameters, containsTargetTypeParameter ); } //otherwise add reference to existing mapper method else if ( isValidReferencedMethod( parameters ) || isValidFactoryMethod( parameters ) ) { - return getReferencedMethod( usedMapper, method, mapperToImplement, parameters ); + return getReferencedMethod( usedMapper, methodType, method, mapperToImplement, parameters ); } else { return null; } } - private SourceMethod getMethodRequiringImplementation(ExecutableElement method, List parameters, + private SourceMethod getMethodRequiringImplementation(ExecutableType methodType, ExecutableElement method, + List parameters, boolean containsTargetTypeParameter) { - Type returnType = typeFactory.getReturnType( method ); - List exceptionTypes = typeFactory.getThrownTypes( method ); + Type returnType = typeFactory.getReturnType( methodType ); + List exceptionTypes = typeFactory.getThrownTypes( methodType ); List sourceParameters = extractSourceParameters( parameters ); Parameter targetParameter = extractTargetParameter( parameters ); Type resultType = selectResultType( returnType, targetParameter ); @@ -173,10 +177,11 @@ public class MethodRetrievalProcessor implements ModelElementProcessor parameters) { - Type returnType = typeFactory.getReturnType( method ); - List exceptionTypes = typeFactory.getThrownTypes( method ); + private SourceMethod getReferencedMethod(TypeElement usedMapper, ExecutableType methodType, + ExecutableElement method, TypeElement mapperToImplement, + List parameters) { + Type returnType = typeFactory.getReturnType( methodType ); + List exceptionTypes = typeFactory.getThrownTypes( methodType ); Type usedMapperAsType = typeFactory.getType( usedMapper ); Type mapperToImplementAsType = typeFactory.getType( mapperToImplement ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/BoundMappable.java b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/BoundMappable.java new file mode 100644 index 000000000..25a720050 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/BoundMappable.java @@ -0,0 +1,29 @@ +/** + * Copyright 2012-2014 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.inheritedmappingmethod; + +import org.mapstruct.ap.test.inheritedmappingmethod.source.Car; +import org.mapstruct.ap.test.inheritedmappingmethod.target.CarDto; + +public interface BoundMappable { + ENTITY from(DTO dto); + + DTO to(ENTITY entity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/CarMapper.java new file mode 100644 index 000000000..2e2ab022e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/CarMapper.java @@ -0,0 +1,31 @@ +/** + * Copyright 2012-2014 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.inheritedmappingmethod; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.inheritedmappingmethod.source.Car; +import org.mapstruct.ap.test.inheritedmappingmethod.target.CarDto; +import org.mapstruct.factory.Mappers; + +//CHECKSTYLE:OFF +@Mapper +public interface CarMapper extends UnboundMappable { + CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); +} +// CHECKSTYLE:ON diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/FastCarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/FastCarMapper.java new file mode 100644 index 000000000..4ee4617ed --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/FastCarMapper.java @@ -0,0 +1,31 @@ +/** + * Copyright 2012-2014 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.inheritedmappingmethod; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.inheritedmappingmethod.source.FastCar; +import org.mapstruct.ap.test.inheritedmappingmethod.target.FastCarDto; +import org.mapstruct.factory.Mappers; + +//CHECKSTYLE:OFF +@Mapper +public interface FastCarMapper extends BoundMappable { + FastCarMapper INSTANCE = Mappers.getMapper( FastCarMapper.class ); +} +// CHECKSTYLE:ON diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/InheritedMappingMethodTest.java b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/InheritedMappingMethodTest.java new file mode 100644 index 000000000..6f67b1239 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/InheritedMappingMethodTest.java @@ -0,0 +1,84 @@ +/** + * Copyright 2012-2014 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.inheritedmappingmethod; + +import static org.fest.assertions.Assertions.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.test.inheritedmappingmethod.source.Car; +import org.mapstruct.ap.test.inheritedmappingmethod.source.FastCar; +import org.mapstruct.ap.test.inheritedmappingmethod.target.CarDto; +import org.mapstruct.ap.test.inheritedmappingmethod.target.FastCarDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +@IssueKey( "274" ) +@WithClasses({ + Car.class, CarDto.class, UnboundMappable.class, CarMapper.class, // + FastCar.class, FastCarDto.class, BoundMappable.class, FastCarMapper.class +}) +@RunWith(AnnotationProcessorTestRunner.class) +public class InheritedMappingMethodTest { + + @Test + public void shouldProvideUnboundedMapperInstance() throws Exception { + UnboundMappable instance = CarMapper.INSTANCE; + assertThat( instance ).isNotNull(); + } + + @Test + public void shouldMapUsingUnboundedInheretedMappingMethod() { + // given + CarDto bikeDto = new CarDto(); + bikeDto.setHorsepower( 130 ); + // when + UnboundMappable instance = CarMapper.INSTANCE; + Car bike = instance.from( bikeDto ); + + // then + assertThat( bike ).isNotNull(); + assertThat( bike.getHorsepower() ).isEqualTo( 130 ); + } + + @Test + public void shouldProvideBoundedMapperInstance() throws Exception { + BoundMappable instance = FastCarMapper.INSTANCE; + assertThat( instance ).isNotNull(); + } + + @Test + public void shouldMapUsingBoundedInheretedMappingMethod() { + // given + FastCarDto bikeDto = new FastCarDto(); + bikeDto.setHorsepower( 130 ); + bikeDto.setCoolnessFactor( 243 ); + + // when + BoundMappable instance = FastCarMapper.INSTANCE; + FastCar bike = instance.from( bikeDto ); + + // then + assertThat( bike ).isNotNull(); + assertThat( bike.getHorsepower() ).isEqualTo( 130 ); + assertThat( bike.getCoolnessFactor() ).isEqualTo( 243 ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/UnboundMappable.java b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/UnboundMappable.java new file mode 100644 index 000000000..f2a685560 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/UnboundMappable.java @@ -0,0 +1,26 @@ +/** + * Copyright 2012-2014 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.inheritedmappingmethod; + +public interface UnboundMappable { + ENTITY from(DTO dto); + + DTO to(ENTITY entity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/source/Car.java b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/source/Car.java new file mode 100644 index 000000000..19de6ff6c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/source/Car.java @@ -0,0 +1,33 @@ +/** + * Copyright 2012-2014 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.inheritedmappingmethod.source; + +public class Car { + private int horsepower; + + public int getHorsepower() { + return horsepower; + } + + public void setHorsepower(int horsepower) { + this.horsepower = horsepower; + } + + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/source/FastCar.java b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/source/FastCar.java new file mode 100644 index 000000000..5ab165486 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/source/FastCar.java @@ -0,0 +1,32 @@ +/** + * Copyright 2012-2014 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.inheritedmappingmethod.source; + +public class FastCar extends Car { + private int coolnessFactor; + + public int getCoolnessFactor() { + return coolnessFactor; + } + + public void setCoolnessFactor(int coolnessFactor) { + this.coolnessFactor = coolnessFactor; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/target/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/target/CarDto.java new file mode 100644 index 000000000..f3c823382 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/target/CarDto.java @@ -0,0 +1,15 @@ +package org.mapstruct.ap.test.inheritedmappingmethod.target; + +public class CarDto { + private int horsepower; + + public int getHorsepower() { + return horsepower; + } + + public void setHorsepower(int horsepower) { + this.horsepower = horsepower; + } + + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/target/FastCarDto.java b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/target/FastCarDto.java new file mode 100644 index 000000000..aaf7bcc05 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/target/FastCarDto.java @@ -0,0 +1,14 @@ +package org.mapstruct.ap.test.inheritedmappingmethod.target; + +public class FastCarDto extends CarDto { + private int coolnessFactor; + + public int getCoolnessFactor() { + return coolnessFactor; + } + + public void setCoolnessFactor(int coolnessFactor) { + this.coolnessFactor = coolnessFactor; + } + +}