#634 Using DeclaredType instead of TypeElement when resolving property accessors of mapped bean types; That way accessor parameter/return types referring to type parameters of the bean will be resolved correctly

This commit is contained in:
Gunnar Morling 2016-02-03 23:19:25 +01:00
parent 4997f3f292
commit 8a28f12999
13 changed files with 287 additions and 40 deletions

View File

@ -33,7 +33,7 @@ import java.util.Map.Entry;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
@ -407,8 +407,7 @@ public class BeanMappingMethod extends MappingMethod {
ExecutableElement sourceAccessor = getSourceAccessor( targetProperty.getKey(), candidates );
if ( sourceAccessor != null ) {
Mapping mapping = method.getSingleMappingByTargetPropertyName( targetProperty.getKey() );
TypeElement sourceType = sourceParameter.getType().getTypeElement();
DeclaredType sourceType = (DeclaredType) sourceParameter.getType().getTypeMirror();
SourceReference sourceRef = new SourceReference.BuilderFromProperty()
.sourceParameter( sourceParameter )

View File

@ -30,6 +30,7 @@ import java.util.List;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.internal.model.assignment.AdderWrapper;
@ -130,16 +131,19 @@ public class PropertyMapping extends ModelElement {
}
private Type determineTargetType() {
// This is a bean mapping method, so we know the result is a declared type
DeclaredType resultType = (DeclaredType) method.getResultType().getTypeMirror();
switch ( targetWriteAccessorType ) {
case ADDER:
case SETTER:
return ctx.getTypeFactory()
.getSingleParameter( method.getResultType().getTypeElement(), targetWriteAccessor )
.getSingleParameter( resultType, targetWriteAccessor )
.getType();
case GETTER:
default:
return ctx.getTypeFactory()
.getReturnType( method.getResultType().getTypeElement(), targetWriteAccessor );
.getReturnType( resultType, targetWriteAccessor );
}
}

View File

@ -35,6 +35,7 @@ import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.WildcardType;
@ -396,7 +397,7 @@ public class Type extends ModelElement implements Comparable<Type> {
// first check if there's a setter method.
ExecutableElement adderMethod = null;
if ( Executables.isSetterMethod( candidate ) ) {
Type targetType = typeFactory.getSingleParameter( typeElement, candidate ).getType();
Type targetType = typeFactory.getSingleParameter( (DeclaredType) typeMirror, candidate ).getType();
// ok, the current accessor is a setter. So now the strategy determines what to use
if ( cmStrategy == CollectionMappingStrategyPrism.ADDER_PREFERRED ) {
adderMethod = getAdderForType( targetType, targetPropertyName );
@ -405,7 +406,9 @@ public class Type extends ModelElement implements Comparable<Type> {
else if ( Executables.isGetterMethod( candidate ) ) {
// 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.
Type targetType = typeFactory.getReturnType( typeFactory.getMethodType( typeElement, candidate ) );
Type targetType = typeFactory.getReturnType(
typeFactory.getMethodType( (DeclaredType) typeMirror, candidate )
);
adderMethod = getAdderForType( targetType, targetPropertyName );
}
if ( adderMethod != null ) {

View File

@ -18,8 +18,6 @@
*/
package org.mapstruct.ap.internal.model.common;
import static org.mapstruct.ap.internal.util.workarounds.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@ -256,13 +254,12 @@ public class TypeFactory {
* @param method the method
* @return the ExecutableType representing the method as part of usedMapper
*/
public ExecutableType getMethodType(TypeElement includingType, ExecutableElement method) {
DeclaredType asType = (DeclaredType) replaceTypeElementIfNecessary( elementUtils, includingType ).asType();
TypeMirror asMemberOf = typeUtils.asMemberOf( asType, method );
public ExecutableType getMethodType(DeclaredType includingType, ExecutableElement method) {
TypeMirror asMemberOf = typeUtils.asMemberOf( includingType, method );
return (ExecutableType) asMemberOf;
}
public Parameter getSingleParameter(TypeElement includingType, ExecutableElement method) {
public Parameter getSingleParameter(DeclaredType includingType, ExecutableElement method) {
List<? extends VariableElement> parameters = method.getParameters();
if ( parameters.size() != 1 ) {
@ -273,7 +270,7 @@ public class TypeFactory {
return Collections.first( getParameters( includingType, method ) );
}
public List<Parameter> getParameters(TypeElement includingType, ExecutableElement method) {
public List<Parameter> getParameters(DeclaredType includingType, ExecutableElement method) {
return getParameters( getMethodType( includingType, method ), method );
}
@ -299,7 +296,7 @@ public class TypeFactory {
return result;
}
public Type getReturnType(TypeElement includingType, ExecutableElement method) {
public Type getReturnType(DeclaredType includingType, ExecutableElement method) {
return getReturnType( getMethodType( includingType, method ) );
}
@ -307,7 +304,7 @@ public class TypeFactory {
return getType( method.getReturnType() );
}
public List<Type> getThrownTypes(TypeElement includingType, ExecutableElement method) {
public List<Type> getThrownTypes(DeclaredType includingType, ExecutableElement method) {
return getThrownTypes( getMethodType( includingType, method ) );
}

View File

@ -26,6 +26,7 @@ import java.util.List;
import java.util.Map;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
@ -176,7 +177,9 @@ public class SourceReference {
Map<String, ExecutableElement> sourceReadAccessors = newType.getPropertyReadAccessors();
for ( Map.Entry<String, ExecutableElement> getter : sourceReadAccessors.entrySet() ) {
if ( getter.getKey().equals( entryName ) ) {
newType = typeFactory.getReturnType( newType.getTypeElement(), getter.getValue() );
newType = typeFactory.getReturnType(
(DeclaredType) newType.getTypeMirror(), getter.getValue()
);
sourceEntries.add( new PropertyEntry( entryName, getter.getValue(), newType ) );
matchFound = true;
break;

View File

@ -32,7 +32,6 @@ 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;
import javax.lang.model.util.Types;
@ -86,8 +85,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
mapperConfig.getAnnotationMirror() );
}
List<SourceMethod> prototypeMethods =
retrievePrototypeMethods( mapperConfig.getMapperConfigMirror(), mapperConfig );
List<SourceMethod> prototypeMethods = retrievePrototypeMethods( mapperConfig );
return retrieveMethods( mapperTypeElement, mapperTypeElement, mapperConfig, prototypeMethods );
}
@ -96,16 +94,16 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
return 1;
}
private List<SourceMethod> retrievePrototypeMethods(TypeMirror typeMirror, MapperConfiguration mapperConfig ) {
if ( typeMirror == null || typeMirror.getKind() == TypeKind.VOID ) {
private List<SourceMethod> retrievePrototypeMethods(MapperConfiguration mapperConfig ) {
if ( mapperConfig.config() == null ) {
return Collections.emptyList();
}
TypeElement typeElement = asTypeElement( typeMirror );
TypeElement typeElement = asTypeElement( mapperConfig.config() );
List<SourceMethod> methods = new ArrayList<SourceMethod>();
for ( ExecutableElement executable : getAllEnclosedExecutableElements( elementUtils, typeElement ) ) {
ExecutableType methodType = typeFactory.getMethodType( typeElement, executable );
ExecutableType methodType = typeFactory.getMethodType( mapperConfig.config(), executable );
List<Parameter> parameters = typeFactory.getParameters( methodType, executable );
boolean containsTargetTypeParameter = SourceMethod.containsTargetTypeParameter( parameters );
@ -158,7 +156,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
//Add all methods of used mappers in order to reference them in the aggregated model
if ( usedMapper.equals( mapperToImplement ) ) {
for ( TypeMirror mapper : mapperConfig.uses() ) {
for ( DeclaredType mapper : mapperConfig.uses() ) {
methods.addAll( retrieveMethods(
asTypeElement( mapper ),
mapperToImplement,
@ -170,8 +168,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
return methods;
}
private TypeElement asTypeElement(TypeMirror usedMapper) {
return (TypeElement) ( (DeclaredType) usedMapper ).asElement();
private TypeElement asTypeElement(DeclaredType type) {
return (TypeElement) type.asElement();
}
private SourceMethod getMethod(TypeElement usedMapper,
@ -180,7 +178,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
MapperConfiguration mapperConfig,
List<SourceMethod> prototypeMethods) {
ExecutableType methodType = typeFactory.getMethodType( usedMapper, method );
ExecutableType methodType = typeFactory.getMethodType( (DeclaredType) usedMapper.asType(), method );
List<Parameter> parameters = typeFactory.getParameters( methodType, method );
Type returnType = typeFactory.getReturnType( methodType );

View File

@ -18,7 +18,6 @@
*/
package org.mapstruct.ap.internal.util;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@ -26,7 +25,6 @@ import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism;
@ -49,6 +47,7 @@ public class MapperConfiguration {
private final MapperPrism mapperPrism;
private final MapperConfigPrism mapperConfigPrism;
private final DeclaredType config;
public static MapperConfiguration getInstanceOn(Element e) {
return new MapperConfiguration( MapperPrism.getInstanceOn( e ) );
@ -56,11 +55,15 @@ public class MapperConfiguration {
private MapperConfiguration(MapperPrism mapperPrism) {
this.mapperPrism = mapperPrism;
TypeMirror typeMirror = mapperPrism.config();
if ( typeMirror.getKind().equals( TypeKind.DECLARED ) ) {
this.mapperConfigPrism = MapperConfigPrism.getInstanceOn( ( (DeclaredType) typeMirror ).asElement() );
if ( mapperPrism.values.config() != null ) {
// TODO #737 Only a declared type makes sense here; Validate and raise graceful error;
// Also validate that @MapperConfig is present
this.config = (DeclaredType) mapperPrism.config();
this.mapperConfigPrism = MapperConfigPrism.getInstanceOn( config.asElement() );
}
else {
this.config = null;
this.mapperConfigPrism = null;
}
}
@ -83,12 +86,22 @@ public class MapperConfiguration {
}
}
public List<TypeMirror> uses() {
Set<TypeMirror> uses = new LinkedHashSet<TypeMirror>( mapperPrism.uses() );
if ( mapperConfigPrism != null ) {
uses.addAll( mapperConfigPrism.uses() );
public Set<DeclaredType> uses() {
Set<DeclaredType> uses = new LinkedHashSet<DeclaredType>();
for ( TypeMirror usedMapperType : mapperPrism.uses() ) {
// TODO #737 Only declared type make sense here; Validate and raise graceful error;
uses.add( (DeclaredType) usedMapperType );
}
return new ArrayList<TypeMirror>( uses );
if ( mapperConfigPrism != null ) {
for ( TypeMirror usedMapperType : mapperConfigPrism.uses() ) {
// TODO #737 Only declared type make sense here; Validate and raise graceful error;
uses.add( (DeclaredType) usedMapperType );
}
}
return uses;
}
public List<TypeMirror> imports() {
@ -155,8 +168,8 @@ public class MapperConfiguration {
}
}
public TypeMirror getMapperConfigMirror() {
return mapperPrism.config();
public DeclaredType config() {
return config;
}
public boolean isValid() {

View File

@ -0,0 +1,39 @@
/**
* Copyright 2012-2016 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.container;
public class Bar {
private long value;
public Bar() {
}
public Bar(long value) {
this.value = value;
}
public long getValue() {
return value;
}
public void setValue(long value) {
this.value = value;
}
}

View File

@ -0,0 +1,36 @@
/**
* Copyright 2012-2016 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.container;
public class Foo {
private String value;
public Foo(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@ -0,0 +1,55 @@
/**
* Copyright 2012-2016 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.container;
import static org.fest.assertions.Assertions.assertThat;
import java.util.Arrays;
import java.util.List;
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;
/**
* @author Gunnar Morling
*/
@WithClasses({
Bar.class,
Foo.class,
Source.class,
Target.class,
SourceTargetMapper.class,
})
@RunWith(AnnotationProcessorTestRunner.class)
public class GenericContainerTest {
@Test
@IssueKey("634")
public void canMapGenericSourceTypeToGenericTargetType() {
List<Foo> items = Arrays.asList( new Foo( "42" ), new Foo( "84" ) );
Source<Foo> source = new Source<Foo>( items );
Target<Bar> target = SourceTargetMapper.INSTANCE.mapSourceToTarget( source );
assertThat( target.getContent() ).onProperty( "value" ).containsExactly( 42L, 84L );
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright 2012-2016 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.container;
import java.util.List;
public class Source<T> {
private final List<T> items;
public Source(List<T> items) {
this.items = items;
}
public List<T> getContent() {
return items;
}
}

View File

@ -0,0 +1,32 @@
/**
* Copyright 2012-2016 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.container;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
Bar mapFooToBar(Foo foo);
Target<Bar> mapSourceToTarget(Source<Foo> source);
}

View File

@ -0,0 +1,34 @@
/**
* Copyright 2012-2016 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.container;
import java.util.List;
public class Target<T> {
private List<T> content;
public List<T> getContent() {
return content;
}
public void setContent(List<T> content) {
this.content = content;
}
}