#112 Making sure mapper references have unique variable names and referenced methods are invoked on the right variable

This commit is contained in:
Gunnar Morling 2014-01-29 00:35:26 +01:00
parent 05b2e49364
commit 78b0943953
13 changed files with 374 additions and 68 deletions

View File

@ -18,12 +18,10 @@
*/
package org.mapstruct.ap.model;
import java.beans.Introspector;
import java.util.Set;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.util.Collections;
import org.mapstruct.ap.util.Strings;
/**
* Mapper reference which is retrieved via Annotation-based dependency injection.
@ -34,16 +32,10 @@ import org.mapstruct.ap.util.Strings;
public class AnnotationMapperReference extends MapperReference {
private final Annotation annotation;
private final Type type;
public AnnotationMapperReference(Annotation annotation, Type type) {
public AnnotationMapperReference(Type type, String variableName, Annotation annotation) {
super( type, variableName );
this.annotation = annotation;
this.type = type;
}
@Override
public Type getMapperType() {
return type;
}
public Annotation getAnnotation() {
@ -52,10 +44,6 @@ public class AnnotationMapperReference extends MapperReference {
@Override
public Set<Type> getImportTypes() {
return Collections.asSet( annotation.getImportTypes(), type );
}
public String getVariableName() {
return Strings.getSaveVariableName( Introspector.decapitalize( type.getName() ) );
return Collections.asSet( annotation.getImportTypes(), super.getMapperType() );
}
}

View File

@ -19,6 +19,7 @@
package org.mapstruct.ap.model;
import java.beans.Introspector;
import java.util.List;
import java.util.Set;
import org.mapstruct.ap.model.common.Type;
@ -34,24 +35,28 @@ import org.mapstruct.ap.util.Strings;
*/
public class DefaultMapperReference extends MapperReference {
private final Type type;
private final boolean isAnnotatedMapper;
private final Set<Type> importTypes;
public DefaultMapperReference(Type type, boolean isAnnotatedMapper, TypeFactory typeFactory) {
this.type = type;
private DefaultMapperReference(Type type, boolean isAnnotatedMapper, Set<Type> importTypes, String variableName) {
super( type, variableName );
this.isAnnotatedMapper = isAnnotatedMapper;
importTypes = Collections.asSet( type );
this.importTypes = importTypes;
}
if ( isAnnotatedMapper() ) {
public static DefaultMapperReference getInstance(Type type, boolean isAnnotatedMapper, TypeFactory typeFactory,
List<String> otherMapperReferences) {
Set<Type> importTypes = Collections.asSet( type );
if ( isAnnotatedMapper ) {
importTypes.add( typeFactory.getType( "org.mapstruct.factory.Mappers" ) );
}
}
@Override
public Type getMapperType() {
return type;
String variableName = Strings.getSaveVariableName(
Introspector.decapitalize( type.getName() ),
otherMapperReferences
);
return new DefaultMapperReference( type, isAnnotatedMapper, importTypes, variableName );
}
@Override
@ -59,10 +64,6 @@ public class DefaultMapperReference extends MapperReference {
return importTypes;
}
public String getVariableName() {
return Strings.getSaveVariableName( Introspector.decapitalize( type.getName() ) );
}
public boolean isAnnotatedMapper() {
return isAnnotatedMapper;
}

View File

@ -28,10 +28,29 @@ import org.mapstruct.ap.model.common.Type;
*/
public abstract class MapperReference extends ModelElement {
private final Type type;
private final String variableName;
public MapperReference(Type type, String variableName) {
this.type = type;
this.variableName = variableName;
}
/**
* Returns the type of the referenced mapper
*
* @return the type of the referenced mapper
*/
public abstract Type getMapperType();
public Type getMapperType() {
return type;
}
/**
* Returns the variable name of this reference.
*
* @return the variable name of this reference
*/
public String getVariableName() {
return variableName;
}
}

View File

@ -18,13 +18,11 @@
*/
package org.mapstruct.ap.model;
import java.beans.Introspector;
import java.util.HashSet;
import java.util.Set;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.util.Strings;
/**
* Represents a reference to {@link MappingMethod}.
@ -33,19 +31,19 @@ import org.mapstruct.ap.util.Strings;
*/
public class MappingMethodReference extends MappingMethod {
private final Type declaringMapper;
private final MapperReference declaringMapper;
public MappingMethodReference(Method method) {
public MappingMethodReference(Method method, MapperReference declaringMapper) {
super( method );
this.declaringMapper = method.getDeclaringMapper();
this.declaringMapper = declaringMapper;
}
public Type getDeclaringMapper() {
public MapperReference getDeclaringMapper() {
return declaringMapper;
}
public String getMapperVariableName() {
return Strings.getSaveVariableName( Introspector.decapitalize( declaringMapper.getName() ) );
return declaringMapper.getVariableName();
}
public Set<Type> getReferencedTypes() {

View File

@ -75,8 +75,9 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle
*/
protected MapperReference replacementMapperReference(MapperReference originalReference) {
return new AnnotationMapperReference(
getMapperReferenceAnnotation(),
originalReference.getMapperType()
originalReference.getMapperType(),
originalReference.getVariableName(),
getMapperReferenceAnnotation()
);
}

View File

@ -27,7 +27,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.Messager;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
@ -103,8 +102,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
private Mapper getMapper(TypeElement element, List<Method> methods) {
ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy( element );
List<MappingMethod> mappingMethods = getMappingMethods( methods, unmappedTargetPolicy );
List<MapperReference> mapperReferences = getReferencedMappers( element );
List<MappingMethod> mappingMethods = getMappingMethods( mapperReferences, methods, unmappedTargetPolicy );
return new Mapper.Builder()
.element( element )
@ -143,22 +142,27 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
private List<MapperReference> getReferencedMappers(TypeElement element) {
List<MapperReference> mapperReferences = new LinkedList<MapperReference>();
List<String> variableNames = new LinkedList<String>();
MapperPrism mapperPrism = MapperPrism.getInstanceOn( element );
for ( TypeMirror usedMapper : mapperPrism.uses() ) {
mapperReferences.add(
new DefaultMapperReference(
DefaultMapperReference mapperReference = DefaultMapperReference.getInstance(
typeFactory.getType( usedMapper ),
MapperPrism.getInstanceOn( typeUtils.asElement( usedMapper ) ) != null,
typeFactory
)
typeFactory,
variableNames
);
mapperReferences.add( mapperReference );
variableNames.add( mapperReference.getVariableName() );
}
return mapperReferences;
}
private List<MappingMethod> getMappingMethods(List<Method> methods, ReportingPolicy unmappedTargetPolicy) {
private List<MappingMethod> getMappingMethods(List<MapperReference> mapperReferences, List<Method> methods,
ReportingPolicy unmappedTargetPolicy) {
List<MappingMethod> mappingMethods = new ArrayList<MappingMethod>();
for ( Method method : methods ) {
@ -175,14 +179,14 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
reverseMappingMethod.getIterableMapping() != null ) {
method.setIterableMapping( reverseMappingMethod.getIterableMapping() );
}
mappingMethods.add( getIterableMappingMethod( methods, method ) );
mappingMethods.add( getIterableMappingMethod( mapperReferences, methods, method ) );
}
else if ( method.isMapMapping() ) {
if ( method.getMapMapping() == null && reverseMappingMethod != null &&
reverseMappingMethod.getMapMapping() != null ) {
method.setMapMapping( reverseMappingMethod.getMapMapping() );
}
mappingMethods.add( getMapMappingMethod( methods, method ) );
mappingMethods.add( getMapMappingMethod( mapperReferences, methods, method ) );
}
else {
if ( method.getMappings().isEmpty() ) {
@ -190,7 +194,12 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
method.setMappings( reverse( reverseMappingMethod.getMappings() ) );
}
}
MappingMethod beanMappingMethod = getBeanMappingMethod( methods, method, unmappedTargetPolicy );
MappingMethod beanMappingMethod = getBeanMappingMethod(
mapperReferences,
methods,
method,
unmappedTargetPolicy
);
if ( beanMappingMethod != null ) {
mappingMethods.add( beanMappingMethod );
}
@ -228,8 +237,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
return reversed;
}
private PropertyMapping getPropertyMapping(List<Method> methods, Method method, ExecutableElement targetAcessor,
Parameter parameter) {
private PropertyMapping getPropertyMapping(List<MapperReference> mapperReferences, List<Method> methods,
Method method, ExecutableElement targetAcessor, Parameter parameter) {
String targetPropertyName = Executables.getPropertyName( targetAcessor );
Mapping mapping = method.getMapping( targetPropertyName );
String dateFormat = mapping != null ? mapping.getDateFormat() : null;
@ -248,6 +257,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
if ( Executables.getPropertyName( sourceAccessor ).equals( sourcePropertyName ) &&
!mapsToOtherTarget ) {
return getPropertyMapping(
mapperReferences,
methods,
method,
parameter,
@ -260,6 +270,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
}
else if ( Executables.getPropertyName( sourceAccessor ).equals( sourcePropertyName ) ) {
return getPropertyMapping(
mapperReferences,
methods,
method,
parameter,
@ -273,8 +284,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
return null;
}
private MappingMethod getBeanMappingMethod(List<Method> methods, Method method,
ReportingPolicy unmappedTargetPolicy) {
private MappingMethod getBeanMappingMethod(List<MapperReference> mapperReferences, List<Method> methods,
Method method, ReportingPolicy unmappedTargetPolicy) {
List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>();
Set<String> mappedTargetProperties = new HashSet<String>();
@ -300,12 +311,13 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
PropertyMapping propertyMapping = null;
if ( mapping != null && mapping.getSourceParameterName() != null ) {
Parameter parameter = method.getSourceParameter( mapping.getSourceParameterName() );
propertyMapping = getPropertyMapping( methods, method, targetAccessor, parameter );
propertyMapping = getPropertyMapping( mapperReferences, methods, method, targetAccessor, parameter );
}
if ( propertyMapping == null ) {
for ( Parameter sourceParameter : method.getSourceParameters() ) {
PropertyMapping newPropertyMapping = getPropertyMapping(
mapperReferences,
methods,
method,
targetAccessor,
@ -481,9 +493,9 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
return !foundUnmappedProperty;
}
private PropertyMapping getPropertyMapping(List<Method> methods, Method method, Parameter parameter,
ExecutableElement sourceAccessor, ExecutableElement targetAcessor,
String dateFormat) {
private PropertyMapping getPropertyMapping(List<MapperReference> mapperReferences, List<Method> methods,
Method method, Parameter parameter, ExecutableElement sourceAccessor,
ExecutableElement targetAcessor, String dateFormat) {
Type sourceType = typeFactory.getReturnType( sourceAccessor );
Type targetType = null;
String conversionString = null;
@ -496,7 +508,12 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
targetType = typeFactory.getReturnType( targetAcessor );
}
MappingMethodReference propertyMappingMethod = getMappingMethodReference( methods, sourceType, targetType );
MappingMethodReference propertyMappingMethod = getMappingMethodReference(
mapperReferences,
methods,
sourceType,
targetType
);
TypeConversion conversion = getConversion(
sourceType,
targetType,
@ -524,7 +541,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
return property;
}
private MappingMethod getIterableMappingMethod(List<Method> methods, Method method) {
private MappingMethod getIterableMappingMethod(List<MapperReference> mapperReferences, List<Method> methods,
Method method) {
Type sourceElementType = method.getSourceParameters().iterator().next().getType().getTypeParameters().get( 0 );
Type targetElementType = method.getResultType().getTypeParameters().get( 0 );
@ -539,7 +557,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
);
MappingMethodReference elementMappingMethod =
getMappingMethodReference( methods, sourceElementType, targetElementType );
getMappingMethodReference( mapperReferences, methods, sourceElementType, targetElementType );
if ( !sourceElementType.isAssignableTo( targetElementType ) && conversion == null &&
elementMappingMethod == null ) {
@ -561,7 +579,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
);
}
private MappingMethod getMapMappingMethod(List<Method> methods, Method method) {
private MappingMethod getMapMappingMethod(List<MapperReference> mapperReferences, List<Method> methods,
Method method) {
List<Type> sourceTypeParams = method.getSourceParameters().iterator().next().getType().getTypeParameters();
Type sourceKeyType = sourceTypeParams.get( 0 );
Type sourceValueType = sourceTypeParams.get( 1 );
@ -581,8 +600,14 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
"entry.getValue()"
);
MappingMethodReference keyMappingMethod = getMappingMethodReference( methods, sourceKeyType, targetKeyType );
MappingMethodReference keyMappingMethod = getMappingMethodReference(
mapperReferences,
methods,
sourceKeyType,
targetKeyType
);
MappingMethodReference valueMappingMethod = getMappingMethodReference(
mapperReferences,
methods,
sourceValueType,
targetValueType
@ -629,7 +654,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
);
}
private MappingMethodReference getMappingMethodReference(Iterable<Method> methods, Type parameterType,
private MappingMethodReference getMappingMethodReference(List<MapperReference> mapperReferences,
Iterable<Method> methods, Type parameterType,
Type returnType) {
List<Method> candidatesWithMathingTargetType = new ArrayList<Method>();
@ -678,7 +704,17 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
);
}
return new MappingMethodReference( candidatesWithBestMatchingSourceType.get( 0 ) );
Method method = candidatesWithBestMatchingSourceType.get( 0 );
MapperReference mapperReference = null;
for ( MapperReference ref : mapperReferences ) {
if ( ref.getMapperType().equals( method.getDeclaringMapper() ) ) {
mapperReference = ref;
break;
}
}
return new MappingMethodReference( method, mapperReference );
}
private int addToCandidateListIfMinimal(List<Method> candidatesWithBestMathingType, int bestMatchingTypeDistance,

View File

@ -0,0 +1,39 @@
/**
* 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.references.samename;
import org.mapstruct.Mapper;
import org.mapstruct.ap.test.references.samename.a.CustomMapper;
import org.mapstruct.factory.Mappers;
/**
* @author Gunnar Morling
*/
@Mapper(
componentModel = "jsr330",
uses = {
CustomMapper.class,
org.mapstruct.ap.test.references.samename.b.CustomMapper.class
})
public interface Jsr330SourceTargetMapper {
Jsr330SourceTargetMapper INSTANCE = Mappers.getMapper( Jsr330SourceTargetMapper.class );
Target sourceToTarget(Source source);
}

View File

@ -0,0 +1,53 @@
/**
* 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.references.samename;
import org.mapstruct.ap.test.references.samename.a.CustomMapper;
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 referring several mappers with the same simple name.
*
* @author Gunnar Morling
*/
@IssueKey("112")
@WithClasses({
Source.class, Target.class, SourceTargetMapper.class, CustomMapper.class,
org.mapstruct.ap.test.references.samename.b.CustomMapper.class, Jsr330SourceTargetMapper.class
})
public class SeveralReferencedMappersWithSameSimpleNameTest extends MapperTestBase {
@Test
public void severalMappersWithSameSimpleNameCanBeReferenced() {
Source source = new Source();
source.setFoo( 123 );
source.setBar( 456L );
Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source );
assertThat( target ).isNotNull();
assertThat( target.getFoo() ).isEqualTo( "246" );
assertThat( target.getBar() ).isEqualTo( "912" );
}
}

View File

@ -0,0 +1,41 @@
/**
* 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.references.samename;
public class Source {
private int foo;
private long bar;
public int getFoo() {
return foo;
}
public void setFoo(int foo) {
this.foo = foo;
}
public long getBar() {
return bar;
}
public void setBar(long bar) {
this.bar = bar;
}
}

View File

@ -0,0 +1,37 @@
/**
* 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.references.samename;
import org.mapstruct.Mapper;
import org.mapstruct.ap.test.references.samename.a.CustomMapper;
import org.mapstruct.factory.Mappers;
/**
* @author Gunnar Morling
*/
@Mapper(uses = {
CustomMapper.class,
org.mapstruct.ap.test.references.samename.b.CustomMapper.class
})
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
Target sourceToTarget(Source source);
}

View File

@ -0,0 +1,41 @@
/**
* 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.references.samename;
public class Target {
private String foo;
private String bar;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public String getBar() {
return bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}

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.references.samename.a;
public class CustomMapper {
public String intToString(int i) {
return String.valueOf( i * 2 );
}
}

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.references.samename.b;
public class CustomMapper {
public String longToString(long l) {
return String.valueOf( l * 2 );
}
}