#274 Evaluate explicit types of method parameters and return types using Types#asMemberOf

This commit is contained in:
Ewald Volkert 2014-11-21 17:43:12 +01:00 committed by Andreas Gudian
parent 677952c2fe
commit 0b9ca3548d
11 changed files with 372 additions and 14 deletions

View File

@ -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<? extends VariableElement> parameters = method.getParameters();
@ -241,10 +258,36 @@ public class TypeFactory {
return result;
}
public List<Parameter> getParameters(ExecutableType methodType, ExecutableElement method) {
List<? extends TypeMirror> parameterTypes = methodType.getParameterTypes();
List<? extends VariableElement> parameters = method.getParameters();
List<Parameter> result = new ArrayList<Parameter>( parameters.size() );
Iterator<? extends VariableElement> varIt = parameters.iterator();
Iterator<? extends TypeMirror> 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<Type> getThrownTypes(ExecutableElement method) {
List<Type> thrownTypes = new ArrayList<Type>();
for (TypeMirror exceptionType : method.getThrownTypes() ) {
@ -253,6 +296,14 @@ public class TypeFactory {
return thrownTypes;
}
public List<Type> getThrownTypes(ExecutableType method) {
List<Type> thrownTypes = new ArrayList<Type>();
for ( TypeMirror exceptionType : method.getThrownTypes() ) {
thrownTypes.add( getType( exceptionType ) );
}
return thrownTypes;
}
private List<Type> 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<ExecutableType, Void> {
@Override
public ExecutableType visitExecutable(ExecutableType t, Void p) {
return t;
}
}
}

View File

@ -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<Void, Lis
private SourceMethod getMethod(TypeElement usedMapper,
ExecutableElement method,
TypeElement mapperToImplement) {
List<Parameter> parameters = typeFactory.getParameters( method );
ExecutableType methodType = typeFactory.getMethodType( usedMapper, method );
List<Parameter> 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<Parameter> parameters,
private SourceMethod getMethodRequiringImplementation(ExecutableType methodType, ExecutableElement method,
List<Parameter> parameters,
boolean containsTargetTypeParameter) {
Type returnType = typeFactory.getReturnType( method );
List<Type> exceptionTypes = typeFactory.getThrownTypes( method );
Type returnType = typeFactory.getReturnType( methodType );
List<Type> exceptionTypes = typeFactory.getThrownTypes( methodType );
List<Parameter> sourceParameters = extractSourceParameters( parameters );
Parameter targetParameter = extractTargetParameter( parameters );
Type resultType = selectResultType( returnType, targetParameter );
@ -173,10 +177,11 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
);
}
private SourceMethod getReferencedMethod(TypeElement usedMapper, ExecutableElement method,
TypeElement mapperToImplement, List<Parameter> parameters) {
Type returnType = typeFactory.getReturnType( method );
List<Type> exceptionTypes = typeFactory.getThrownTypes( method );
private SourceMethod getReferencedMethod(TypeElement usedMapper, ExecutableType methodType,
ExecutableElement method, TypeElement mapperToImplement,
List<Parameter> parameters) {
Type returnType = typeFactory.getReturnType( methodType );
List<Type> exceptionTypes = typeFactory.getThrownTypes( methodType );
Type usedMapperAsType = typeFactory.getType( usedMapper );
Type mapperToImplementAsType = typeFactory.getType( mapperToImplement );

View File

@ -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<DTO extends CarDto, ENTITY extends Car> {
ENTITY from(DTO dto);
DTO to(ENTITY entity);
}

View File

@ -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<CarDto, Car> {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
}
// CHECKSTYLE:ON

View File

@ -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<FastCarDto, FastCar> {
FastCarMapper INSTANCE = Mappers.getMapper( FastCarMapper.class );
}
// CHECKSTYLE:ON

View File

@ -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<CarDto, Car> instance = CarMapper.INSTANCE;
assertThat( instance ).isNotNull();
}
@Test
public void shouldMapUsingUnboundedInheretedMappingMethod() {
// given
CarDto bikeDto = new CarDto();
bikeDto.setHorsepower( 130 );
// when
UnboundMappable<CarDto, Car> instance = CarMapper.INSTANCE;
Car bike = instance.from( bikeDto );
// then
assertThat( bike ).isNotNull();
assertThat( bike.getHorsepower() ).isEqualTo( 130 );
}
@Test
public void shouldProvideBoundedMapperInstance() throws Exception {
BoundMappable<? extends CarDto, ? extends Car> instance = FastCarMapper.INSTANCE;
assertThat( instance ).isNotNull();
}
@Test
public void shouldMapUsingBoundedInheretedMappingMethod() {
// given
FastCarDto bikeDto = new FastCarDto();
bikeDto.setHorsepower( 130 );
bikeDto.setCoolnessFactor( 243 );
// when
BoundMappable<FastCarDto, FastCar> instance = FastCarMapper.INSTANCE;
FastCar bike = instance.from( bikeDto );
// then
assertThat( bike ).isNotNull();
assertThat( bike.getHorsepower() ).isEqualTo( 130 );
assertThat( bike.getCoolnessFactor() ).isEqualTo( 243 );
}
}

View File

@ -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<DTO, ENTITY> {
ENTITY from(DTO dto);
DTO to(ENTITY entity);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}