#574 determine property type from getter/setter methods only in combination with ExecutableType (i.e. using asMemberOf to determine the type with any generics being resolved)

This commit is contained in:
Andreas Gudian 2015-06-25 19:55:47 +02:00 committed by Gunnar Morling
parent a35cf3c16d
commit 264a8f65af
11 changed files with 248 additions and 45 deletions

View File

@ -33,6 +33,7 @@ import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic; import javax.tools.Diagnostic;
@ -404,9 +405,11 @@ public class BeanMappingMethod extends MappingMethod {
if ( sourceAccessor != null ) { if ( sourceAccessor != null ) {
Mapping mapping = method.getSingleMappingByTargetPropertyName( targetProperty.getKey() ); Mapping mapping = method.getSingleMappingByTargetPropertyName( targetProperty.getKey() );
TypeElement sourceType = sourceParameter.getType().getTypeElement();
SourceReference sourceRef = new SourceReference.BuilderFromProperty() SourceReference sourceRef = new SourceReference.BuilderFromProperty()
.sourceParameter( sourceParameter ) .sourceParameter( sourceParameter )
.type( ctx.getTypeFactory().getReturnType( sourceAccessor ) ) .type( ctx.getTypeFactory().getReturnType( sourceType, sourceAccessor ) )
.accessor( sourceAccessor ) .accessor( sourceAccessor )
.name( targetProperty.getKey() ) .name( targetProperty.getKey() )
.build(); .build();

View File

@ -454,10 +454,14 @@ public class PropertyMapping extends ModelElement {
switch ( targetAccessorType ) { switch ( targetAccessorType ) {
case ADDER: case ADDER:
case SETTER: case SETTER:
return ctx.getTypeFactory().getSingleParameter( targetWriteAccessor ).getType(); return ctx.getTypeFactory().getSingleParameter(
method.getResultType().getTypeElement(),
targetWriteAccessor ).getType();
case GETTER: case GETTER:
default: default:
return ctx.getTypeFactory().getReturnType( targetWriteAccessor ); return ctx.getTypeFactory().getReturnType(
method.getResultType().getTypeElement(),
targetWriteAccessor );
} }
} }
@ -570,10 +574,14 @@ public class PropertyMapping extends ModelElement {
// target // target
Type targetType; Type targetType;
if ( Executables.isSetterMethod( targetWriteAccessor ) ) { if ( Executables.isSetterMethod( targetWriteAccessor ) ) {
targetType = ctx.getTypeFactory().getSingleParameter( targetWriteAccessor ).getType(); targetType = ctx.getTypeFactory().getSingleParameter(
method.getResultType().getTypeElement(),
targetWriteAccessor ).getType();
} }
else { else {
targetType = ctx.getTypeFactory().getReturnType( targetWriteAccessor ); targetType = ctx.getTypeFactory().getReturnType(
method.getResultType().getTypeElement(),
targetWriteAccessor );
} }
Assignment assignment = ctx.getMappingResolver().getTargetAssignment( Assignment assignment = ctx.getMappingResolver().getTargetAssignment(
@ -659,10 +667,14 @@ public class PropertyMapping extends ModelElement {
if ( Executables.isSetterMethod( targetWriteAccessor ) ) { if ( Executables.isSetterMethod( targetWriteAccessor ) ) {
// setter, so wrap in setter // setter, so wrap in setter
assignment = new SetterWrapper( assignment, method.getThrownTypes() ); assignment = new SetterWrapper( assignment, method.getThrownTypes() );
targetType = ctx.getTypeFactory().getSingleParameter( targetWriteAccessor ).getType(); targetType = ctx.getTypeFactory().getSingleParameter(
method.getResultType().getTypeElement(),
targetWriteAccessor ).getType();
} }
else { else {
targetType = ctx.getTypeFactory().getReturnType( targetWriteAccessor ); targetType = ctx.getTypeFactory().getReturnType(
method.getResultType().getTypeElement(),
targetWriteAccessor );
// target accessor is getter, so wrap the setter in getter map/ collection handling // target accessor is getter, so wrap the setter in getter map/ collection handling
assignment = new GetterWrapperForCollectionsAndMaps( assignment = new GetterWrapperForCollectionsAndMaps(
assignment, assignment,

View File

@ -398,7 +398,7 @@ public class Type extends ModelElement implements Comparable<Type> {
// first check if there's a setter method. // first check if there's a setter method.
ExecutableElement adderMethod = null; ExecutableElement adderMethod = null;
if ( Executables.isSetterMethod( candidate ) ) { if ( Executables.isSetterMethod( candidate ) ) {
Type targetType = typeFactory.getSingleParameter( candidate ).getType(); Type targetType = typeFactory.getSingleParameter( typeElement, candidate ).getType();
// ok, the current accessor is a setter. So now the strategy determines what to use // ok, the current accessor is a setter. So now the strategy determines what to use
if ( cmStrategy == CollectionMappingStrategyPrism.ADDER_PREFERRED ) { if ( cmStrategy == CollectionMappingStrategyPrism.ADDER_PREFERRED ) {
adderMethod = getAdderForType( targetType, targetPropertyName ); adderMethod = getAdderForType( targetType, targetPropertyName );
@ -407,7 +407,7 @@ public class Type extends ModelElement implements Comparable<Type> {
else if ( Executables.isGetterMethod( candidate ) ) { else if ( Executables.isGetterMethod( candidate ) ) {
// the current accessor is a getter (no setter available). But still, an add method is according // the current accessor is a getter (no setter available). But still, an add method is according
// to the above strategy (SETTER_PREFERRED || ADDER_PREFERRED) preferred over the getter. // to the above strategy (SETTER_PREFERRED || ADDER_PREFERRED) preferred over the getter.
Type targetType = typeFactory.getReturnType( candidate ); Type targetType = typeFactory.getReturnType( typeFactory.getMethodType( typeElement, candidate ) );
adderMethod = getAdderForType( targetType, targetPropertyName ); adderMethod = getAdderForType( targetType, targetPropertyName );
} }
if ( adderMethod != null ) { if ( adderMethod != null ) {

View File

@ -55,6 +55,7 @@ import javax.lang.model.util.Types;
import org.mapstruct.ap.internal.prism.MappingTargetPrism; import org.mapstruct.ap.internal.prism.MappingTargetPrism;
import org.mapstruct.ap.internal.prism.TargetTypePrism; import org.mapstruct.ap.internal.prism.TargetTypePrism;
import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.AnnotationProcessingException;
import org.mapstruct.ap.internal.util.Collections;
import org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds; import org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds;
import static org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds.erasure; import static org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds.erasure;
@ -254,18 +255,18 @@ public class TypeFactory {
* Get the ExecutableType for given method as part of usedMapper. Possibly parameterized types in method declaration * Get the ExecutableType for given method as part of usedMapper. Possibly parameterized types in method declaration
* will be evaluated to concrete types then. * will be evaluated to concrete types then.
* *
* @param usedMapper the type declaring the method * @param includingType the type on which's scope the method type shall be evaluated
* @param method the method * @param method the method
* @return the ExecutableType representing the method as part of usedMapper * @return the ExecutableType representing the method as part of usedMapper
*/ */
public ExecutableType getMethodType(TypeElement usedMapper, ExecutableElement method) { public ExecutableType getMethodType(TypeElement includingType, ExecutableElement method) {
DeclaredType asType = (DeclaredType) replaceTypeElementIfNecessary( elementUtils, usedMapper ).asType(); DeclaredType asType = (DeclaredType) replaceTypeElementIfNecessary( elementUtils, includingType ).asType();
TypeMirror asMemberOf = typeUtils.asMemberOf( asType, method ); TypeMirror asMemberOf = typeUtils.asMemberOf( asType, method );
ExecutableType methodType = asMemberOf.accept( new ExecutableTypeRetrievalVisitor(), null ); ExecutableType methodType = asMemberOf.accept( new ExecutableTypeRetrievalVisitor(), null );
return methodType; return methodType;
} }
public Parameter getSingleParameter(ExecutableElement method) { public Parameter getSingleParameter(TypeElement includingType, ExecutableElement method) {
List<? extends VariableElement> parameters = method.getParameters(); List<? extends VariableElement> parameters = method.getParameters();
if ( parameters.size() != 1 ) { if ( parameters.size() != 1 ) {
@ -273,31 +274,11 @@ public class TypeFactory {
return null; return null;
} }
VariableElement parameter = parameters.get( 0 ); return Collections.first( getParameters( includingType, method ) );
return new Parameter(
parameter.getSimpleName().toString(),
getType( parameter.asType() )
);
} }
public List<Parameter> getParameters(ExecutableElement method) { public List<Parameter> getParameters(TypeElement includingType, ExecutableElement method) {
List<? extends VariableElement> parameters = method.getParameters(); return getParameters( getMethodType( includingType, method ), method );
List<Parameter> result = new ArrayList<Parameter>( parameters.size() );
for ( VariableElement parameter : parameters ) {
result
.add(
new Parameter(
parameter.getSimpleName().toString(),
getType( parameter.asType() ),
MappingTargetPrism.getInstanceOn( parameter ) != null,
TargetTypePrism.getInstanceOn( parameter ) != null
)
);
}
return result;
} }
public List<Parameter> getParameters(ExecutableType methodType, ExecutableElement method) { public List<Parameter> getParameters(ExecutableType methodType, ExecutableElement method) {
@ -322,20 +303,16 @@ public class TypeFactory {
return result; return result;
} }
public Type getReturnType(ExecutableElement method) { public Type getReturnType(TypeElement includingType, ExecutableElement method) {
return getType( method.getReturnType() ); return getReturnType( getMethodType( includingType, method ) );
} }
public Type getReturnType(ExecutableType method) { public Type getReturnType(ExecutableType method) {
return getType( method.getReturnType() ); return getType( method.getReturnType() );
} }
public List<Type> getThrownTypes(ExecutableElement method) { public List<Type> getThrownTypes(TypeElement includingType, ExecutableElement method) {
List<Type> thrownTypes = new ArrayList<Type>(); return getThrownTypes( getMethodType( includingType, method ) );
for (TypeMirror exceptionType : method.getThrownTypes() ) {
thrownTypes.add( getType( exceptionType ) );
}
return thrownTypes;
} }
public List<Type> getThrownTypes(ExecutableType method) { public List<Type> getThrownTypes(ExecutableType method) {
@ -488,4 +465,5 @@ public class TypeFactory {
return collectionOrMap; return collectionOrMap;
} }
} }

View File

@ -181,7 +181,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
ExecutableType methodType = typeFactory.getMethodType( usedMapper, method ); ExecutableType methodType = typeFactory.getMethodType( usedMapper, method );
List<Parameter> parameters = typeFactory.getParameters( methodType, method ); List<Parameter> parameters = typeFactory.getParameters( methodType, method );
Type returnType = typeFactory.getReturnType( method ); Type returnType = typeFactory.getReturnType( methodType );
boolean methodRequiresImplementation = method.getModifiers().contains( Modifier.ABSTRACT ); boolean methodRequiresImplementation = method.getModifiers().contains( Modifier.ABSTRACT );
boolean containsTargetTypeParameter = SourceMethod.containsTargetTypeParameter( parameters ); boolean containsTargetTypeParameter = SourceMethod.containsTargetTypeParameter( parameters );

View File

@ -0,0 +1,34 @@
/**
* Copyright 2012-2015 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.generics;
/**
* @author Andreas Gudian
*/
public abstract class AbstractIdHoldingTo<ID> {
private ID id;
public ID getId() {
return this.id;
}
public void setId(ID id) {
this.id = id;
}
}

View File

@ -0,0 +1,26 @@
/**
* Copyright 2012-2015 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.generics;
/**
* @author Andreas Gudian
*/
public abstract class AbstractTo extends AbstractIdHoldingTo<Long> {
}

View File

@ -0,0 +1,50 @@
/**
* Copyright 2012-2015 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.generics;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.fest.assertions.Assertions.assertThat;
/**
* @author Andreas Gudian
*
*/
@WithClasses({
AbstractTo.class,
AbstractIdHoldingTo.class,
Source.class,
SourceTargetMapper.class,
TargetTo.class
})
@RunWith(AnnotationProcessorTestRunner.class)
public class GenericsTest {
@Test
@IssueKey("574")
public void mapsIdCorrectly() {
TargetTo target = new TargetTo();
target.setId( 41L );
assertThat( SourceTargetMapper.INSTANCE.toSource( target ).getId() ).isEqualTo( 41L );
}
}

View File

@ -0,0 +1,35 @@
/**
* Copyright 2012-2015 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.generics;
/**
* @author Andreas Gudian
*
*/
public class Source {
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}

View File

@ -0,0 +1,36 @@
/**
* Copyright 2012-2015 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.generics;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* @author Andreas Gudian
*
*/
@Mapper
public abstract class SourceTargetMapper {
public static final SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
public abstract Source toSource(TargetTo target);
public abstract TargetTo toTarget(Source source);
}

View File

@ -0,0 +1,29 @@
/**
* Copyright 2012-2015 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.generics;
/**
* @author Andreas Gudian
*
*/
public class TargetTo extends AbstractTo {
}