From 897c8fbb6dfae68aae97c48297d7be2955a263d8 Mon Sep 17 00:00:00 2001 From: sjaakd Date: Tue, 27 Jan 2015 21:23:58 +0100 Subject: [PATCH] #433 introduce resultType in @IterableMapping and @MapMapping --- .../java/org/mapstruct/IterableMapping.java | 11 ++++ .../main/java/org/mapstruct/MapMapping.java | 18 +++++++ .../ap/model/IterableMappingMethod.java | 8 ++- .../mapstruct/ap/model/MapMappingMethod.java | 16 +++++- .../ap/model/source/IterableMapping.java | 18 +++++-- .../mapstruct/ap/model/source/MapMapping.java | 29 ++++++++-- .../ap/processor/MapperCreationProcessor.java | 9 ++++ .../ErroneousCollectionMappingTest.java | 9 ++-- .../resulttype/FruitFamilyMapper.java | 10 ++++ .../resulttype/InheritanceSelectionTest.java | 53 +++++++++++++++++++ 10 files changed, 167 insertions(+), 14 deletions(-) diff --git a/core-common/src/main/java/org/mapstruct/IterableMapping.java b/core-common/src/main/java/org/mapstruct/IterableMapping.java index 197064ee3..522a4ff87 100644 --- a/core-common/src/main/java/org/mapstruct/IterableMapping.java +++ b/core-common/src/main/java/org/mapstruct/IterableMapping.java @@ -29,6 +29,9 @@ import java.util.Date; /** * Configures the mapping between two iterable types, e.g. {@code List} and {@code List}. * + *

Note: either @IterableMapping#dateFormat, @IterableMapping#resultType or @IterableMapping#qualifiedBy + * must be specified

+ * * @author Gunnar Morling */ @Target(ElementType.METHOD) @@ -52,4 +55,12 @@ public @interface IterableMapping { * @return the qualifiers */ Class[] qualifiedBy() default { }; + + /** + * Specifies the type of the element to be used in the result of the mapping method in case multiple mapping + * methods qualify. + * + * @return the elementTargetType to select + */ + Class elementTargetType() default void.class; } diff --git a/core-common/src/main/java/org/mapstruct/MapMapping.java b/core-common/src/main/java/org/mapstruct/MapMapping.java index 4b1874147..0296d6de9 100644 --- a/core-common/src/main/java/org/mapstruct/MapMapping.java +++ b/core-common/src/main/java/org/mapstruct/MapMapping.java @@ -29,6 +29,8 @@ import java.util.Date; /** * Configures the mapping between two map types, e.g. {@code Map} and {@code Map}. * + *

Note: at least one element needs to be specified

+ * * @author Gunnar Morling */ @Target(ElementType.METHOD) @@ -73,4 +75,20 @@ public @interface MapMapping { * @return the qualifiers */ Class[] valueQualifiedBy() default { }; + + /** + * Specifies the type of the key to be used in the result of the mapping method in case multiple mapping + * methods qualify. + * * + * @return the resultType to select + */ + Class keyTargetType() default void.class; + + /** + * Specifies the type of the value to be used in the result of the mapping method in case multiple mapping + * methods qualify. + * * + * @return the resultType to select + */ + Class valueTargetType() default void.class; } diff --git a/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java index 7022af4ea..8ccb4a02b 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java @@ -54,6 +54,7 @@ public class IterableMappingMethod extends MappingMethod { private MappingBuilderContext ctx; private String dateFormat; private List qualifiers; + private TypeMirror qualifyingElementTargetType; public Builder mappingContext(MappingBuilderContext mappingContext) { this.ctx = mappingContext; @@ -75,6 +76,11 @@ public class IterableMappingMethod extends MappingMethod { return this; } + public Builder qualifyingElementTargetType(TypeMirror qualifyingElementTargetType) { + this.qualifyingElementTargetType = qualifyingElementTargetType; + return this; + } + public IterableMappingMethod build() { Type sourceParameterType = method.getSourceParameters().iterator().next().getType(); Type resultType = method.getResultType(); @@ -100,7 +106,7 @@ public class IterableMappingMethod extends MappingMethod { null, // there is no targetPropertyName dateFormat, qualifiers, - null, // resulttype does not seem to make sense + qualifyingElementTargetType, loopVariableName ); diff --git a/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java index 4a1bf9ab5..36f0f731a 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java @@ -52,6 +52,8 @@ public class MapMappingMethod extends MappingMethod { private String valueDateFormat; private List keyQualifiers; private List valueQualifiers; + private TypeMirror keyQualifyingTargetType; + private TypeMirror valueQualifyingTargetType; private Method method; private MappingBuilderContext ctx; @@ -85,6 +87,16 @@ public class MapMappingMethod extends MappingMethod { return this; } + public Builder keyQualifyingTargetType(TypeMirror keyQualifyingTargetType) { + this.keyQualifyingTargetType = keyQualifyingTargetType; + return this; + } + + public Builder valueQualifyingTargetType(TypeMirror valueQualifyingTargetType) { + this.valueQualifyingTargetType = valueQualifyingTargetType; + return this; + } + public MapMappingMethod build() { List sourceTypeParams = method.getSourceParameters().iterator().next().getType().getTypeParameters(); @@ -102,7 +114,7 @@ public class MapMappingMethod extends MappingMethod { null, // there is no targetPropertyName keyDateFormat, keyQualifiers, - null, // resulttype does not seem to make sense + keyQualifyingTargetType, "entry.getKey()" ); @@ -126,7 +138,7 @@ public class MapMappingMethod extends MappingMethod { null, // there is no targetPropertyName valueDateFormat, valueQualifiers, - null, // resulttype does not seem to make sense + valueQualifyingTargetType, "entry.getValue()" ); diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/IterableMapping.java b/processor/src/main/java/org/mapstruct/ap/model/source/IterableMapping.java index 55cc48e97..3b73958ea 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/IterableMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/IterableMapping.java @@ -23,6 +23,7 @@ import javax.annotation.processing.Messager; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; @@ -37,6 +38,7 @@ public class IterableMapping { private final String dateFormat; private final List qualifiers; + private final TypeMirror qualifyingElementTargetType; private final AnnotationMirror mirror; private final AnnotationValue dateFormatAnnotationValue; @@ -46,10 +48,13 @@ public class IterableMapping { return null; } - if ( iterableMapping.dateFormat().isEmpty() && iterableMapping.qualifiedBy().isEmpty() ) { + boolean elementTargetTypeIsDefined = !TypeKind.VOID.equals( iterableMapping.elementTargetType().getKind() ); + if ( !elementTargetTypeIsDefined + && iterableMapping.dateFormat().isEmpty() + && iterableMapping.qualifiedBy().isEmpty() ) { messager.printMessage( Diagnostic.Kind.ERROR, - "'dateformat' and 'qualifiedBy' are undefined in @IterableMapping, " + "'dateformat', 'qualifiedBy' and 'elementTargetType' are undefined in @IterableMapping, " + "define at least one of them.", method ); @@ -58,6 +63,7 @@ public class IterableMapping { return new IterableMapping( iterableMapping.dateFormat(), iterableMapping.qualifiedBy(), + elementTargetTypeIsDefined ? iterableMapping.elementTargetType() : null, iterableMapping.mirror, iterableMapping.values.dateFormat() ); @@ -66,10 +72,12 @@ public class IterableMapping { private IterableMapping( String dateFormat, List qualifiers, + TypeMirror resultType, AnnotationMirror mirror, - AnnotationValue dateFormatAnnotationValue) { + AnnotationValue dateFormatAnnotationValue ) { this.dateFormat = dateFormat; this.qualifiers = qualifiers; + this.qualifyingElementTargetType = resultType; this.mirror = mirror; this.dateFormatAnnotationValue = dateFormatAnnotationValue; } @@ -82,6 +90,10 @@ public class IterableMapping { return qualifiers; } + public TypeMirror getQualifyingElementTargetType() { + return qualifyingElementTargetType; + } + public AnnotationMirror getMirror() { return mirror; } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/MapMapping.java b/processor/src/main/java/org/mapstruct/ap/model/source/MapMapping.java index 765a12e23..e5740cd0d 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/MapMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/MapMapping.java @@ -22,6 +22,7 @@ import java.util.List; import javax.annotation.processing.Messager; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; @@ -39,20 +40,26 @@ public class MapMapping { private final String valueFormat; private final List valueQualifiers; private final AnnotationMirror mirror; + private final TypeMirror keyQualifyingTargetType; + private final TypeMirror valueQualifyingTargetType; public static MapMapping fromPrism(MapMappingPrism mapMapping, ExecutableElement method, Messager messager) { if ( mapMapping == null ) { return null; } + boolean keyTargetTypeIsDefined = !TypeKind.VOID.equals( mapMapping.keyTargetType().getKind() ); + boolean valueTargetTypeIsDefined = !TypeKind.VOID.equals( mapMapping.valueTargetType().getKind() ); if ( mapMapping.keyDateFormat().isEmpty() && mapMapping.keyQualifiedBy().isEmpty() && mapMapping.valueDateFormat().isEmpty() - && mapMapping.valueQualifiedBy().isEmpty() ) { + && mapMapping.valueQualifiedBy().isEmpty() + && !keyTargetTypeIsDefined + && !valueTargetTypeIsDefined ) { messager.printMessage( Diagnostic.Kind.ERROR, - "'keyDateFormat', 'keyQualifiedBy', 'valueDateFormat' and 'valueQualfiedBy' are all undefined in " - + "@MapMapping, define at least one of them.", + "'keyDateFormat', 'keyQualifiedBy', 'keyTargetType', 'valueDateFormat', 'valueQualfiedBy' and " + + "'valueTargetType' are all undefined in @MapMapping, define at least one of them.", method ); } @@ -61,8 +68,10 @@ public class MapMapping { return new MapMapping( mapMapping.keyDateFormat(), mapMapping.keyQualifiedBy(), + keyTargetTypeIsDefined ? mapMapping.keyTargetType() : null, mapMapping.valueDateFormat(), mapMapping.valueQualifiedBy(), + valueTargetTypeIsDefined ? mapMapping.valueTargetType() : null, mapMapping.mirror ); } @@ -70,13 +79,17 @@ public class MapMapping { private MapMapping( String keyFormat, List keyQualifiers, + TypeMirror keyResultType, String valueFormat, List valueQualifiers, - AnnotationMirror mirror) { + TypeMirror valueResultType, + AnnotationMirror mirror ) { this.keyFormat = keyFormat; this.keyQualifiers = keyQualifiers; + this.keyQualifyingTargetType = keyResultType; this.valueFormat = valueFormat; this.valueQualifiers = valueQualifiers; + this.valueQualifyingTargetType = valueResultType; this.mirror = mirror; } @@ -88,6 +101,10 @@ public class MapMapping { return keyQualifiers; } + public TypeMirror getKeyQualifyingTargetType() { + return keyQualifyingTargetType; + } + public String getValueFormat() { return valueFormat; } @@ -96,6 +113,10 @@ public class MapMapping { return valueQualifiers; } + public TypeMirror getValueQualifyingTargetType() { + return valueQualifyingTargetType; + } + public AnnotationMirror getMirror() { return mirror; } diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java index faad6ad4b..f3859e7a3 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java @@ -271,9 +271,11 @@ public class MapperCreationProcessor implements ModelElementProcessor qualifiers = null; + TypeMirror qualifyingElementTargetType = null; if ( method.getIterableMapping() != null ) { dateFormat = method.getIterableMapping().getDateFormat(); qualifiers = method.getIterableMapping().getQualifiers(); + qualifyingElementTargetType = method.getIterableMapping().getQualifyingElementTargetType(); } IterableMappingMethod iterableMappingMethod = builder @@ -281,6 +283,7 @@ public class MapperCreationProcessor implements ModelElementProcessor keyQualifiers = null; List valueQualifiers = null; + TypeMirror keyQualifyingTargetType = null; + TypeMirror valueQualifyingTargetType = null; if ( method.getMapMapping() != null ) { keyDateFormat = method.getMapMapping().getKeyFormat(); valueDateFormat = method.getMapMapping().getValueFormat(); keyQualifiers = method.getMapMapping().getKeyQualifiers(); valueQualifiers = method.getMapMapping().getValueQualifiers(); + keyQualifyingTargetType = method.getMapMapping().getKeyQualifyingTargetType(); + valueQualifyingTargetType = method.getMapMapping().getValueQualifyingTargetType(); } MapMappingMethod mapMappingMethod = builder @@ -316,6 +323,8 @@ public class MapperCreationProcessor implements ModelElementProcessor mapToGoldenDeliciousList(List source); + + @MapMapping( keyTargetType = GoldenDelicious.class, valueTargetType = Apple.class ) + Map mapToGoldenDeliciousMap(Map source); + GoldenDelicious mapToGoldenDelicious(AppleDto source); @BeanMapping(resultType = Apple.class) diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java index 5b3435e15..8d5d72339 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/resulttype/InheritanceSelectionTest.java @@ -18,6 +18,10 @@ */ package org.mapstruct.ap.test.selection.resulttype; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.tools.Diagnostic.Kind; import static org.fest.assertions.Assertions.assertThat; import org.junit.Test; @@ -95,4 +99,53 @@ public class InheritanceSelectionTest { } + @Test + @IssueKey("433") + @WithClasses( { + FruitFamilyMapper.class, + GoldenDeliciousDto.class, + GoldenDelicious.class, + AppleFamily.class, + AppleFamilyDto.class, + AppleFactory.class, + Banana.class + } ) + public void testShouldSelectResultTypeInCaseOfAmbiguityForIterable() { + + List source = Arrays.asList( new AppleDto( "AppleDto" ) ); + + List result = FruitFamilyMapper.INSTANCE.mapToGoldenDeliciousList( source ); + assertThat( result ).isNotNull(); + assertThat( result ).isNotEmpty(); + assertThat( result.get( 0 ) ).isInstanceOf( GoldenDelicious.class ); + assertThat( result.get( 0 ).getType() ).isEqualTo( "AppleDto" ); + } + + @Test + @IssueKey("433") + @WithClasses( { + FruitFamilyMapper.class, + GoldenDeliciousDto.class, + GoldenDelicious.class, + AppleFamily.class, + AppleFamilyDto.class, + AppleFactory.class, + Banana.class + } ) + public void testShouldSelectResultTypeInCaseOfAmbiguityForMap() { + + Map source = new HashMap(); + source.put( new AppleDto( "GoldenDelicious" ), new AppleDto( "AppleDto" ) ); + + Map result = FruitFamilyMapper.INSTANCE.mapToGoldenDeliciousMap( source ); + assertThat( result ).isNotNull(); + assertThat( result ).isNotEmpty(); + Map.Entry entry = result.entrySet().iterator().next(); + + assertThat( entry.getKey() ).isInstanceOf( GoldenDelicious.class ); + assertThat( entry.getKey().getType() ).isEqualTo( "GoldenDelicious" ); + assertThat( entry.getValue() ).isInstanceOf( Apple.class ); + assertThat( entry.getValue().getType() ).isEqualTo( "AppleDto" ); + + } }