#433 introduce resultType in @IterableMapping and @MapMapping

This commit is contained in:
sjaakd 2015-01-27 21:23:58 +01:00
parent adcc89b184
commit 897c8fbb6d
10 changed files with 167 additions and 14 deletions

View File

@ -29,6 +29,9 @@ import java.util.Date;
/** /**
* Configures the mapping between two iterable types, e.g. {@code List<String>} and {@code List<Date>}. * Configures the mapping between two iterable types, e.g. {@code List<String>} and {@code List<Date>}.
* *
* <p>Note: either @IterableMapping#dateFormat, @IterableMapping#resultType or @IterableMapping#qualifiedBy
* must be specified</p>
*
* @author Gunnar Morling * @author Gunnar Morling
*/ */
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@ -52,4 +55,12 @@ public @interface IterableMapping {
* @return the qualifiers * @return the qualifiers
*/ */
Class<? extends Annotation>[] qualifiedBy() default { }; Class<? extends Annotation>[] 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;
} }

View File

@ -29,6 +29,8 @@ import java.util.Date;
/** /**
* Configures the mapping between two map types, e.g. {@code Map<String, String>} and {@code Map<Long, Date>}. * Configures the mapping between two map types, e.g. {@code Map<String, String>} and {@code Map<Long, Date>}.
* *
* <p>Note: at least one element needs to be specified</p>
*
* @author Gunnar Morling * @author Gunnar Morling
*/ */
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@ -73,4 +75,20 @@ public @interface MapMapping {
* @return the qualifiers * @return the qualifiers
*/ */
Class<? extends Annotation>[] valueQualifiedBy() default { }; Class<? extends Annotation>[] 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;
} }

View File

@ -54,6 +54,7 @@ public class IterableMappingMethod extends MappingMethod {
private MappingBuilderContext ctx; private MappingBuilderContext ctx;
private String dateFormat; private String dateFormat;
private List<TypeMirror> qualifiers; private List<TypeMirror> qualifiers;
private TypeMirror qualifyingElementTargetType;
public Builder mappingContext(MappingBuilderContext mappingContext) { public Builder mappingContext(MappingBuilderContext mappingContext) {
this.ctx = mappingContext; this.ctx = mappingContext;
@ -75,6 +76,11 @@ public class IterableMappingMethod extends MappingMethod {
return this; return this;
} }
public Builder qualifyingElementTargetType(TypeMirror qualifyingElementTargetType) {
this.qualifyingElementTargetType = qualifyingElementTargetType;
return this;
}
public IterableMappingMethod build() { public IterableMappingMethod build() {
Type sourceParameterType = method.getSourceParameters().iterator().next().getType(); Type sourceParameterType = method.getSourceParameters().iterator().next().getType();
Type resultType = method.getResultType(); Type resultType = method.getResultType();
@ -100,7 +106,7 @@ public class IterableMappingMethod extends MappingMethod {
null, // there is no targetPropertyName null, // there is no targetPropertyName
dateFormat, dateFormat,
qualifiers, qualifiers,
null, // resulttype does not seem to make sense qualifyingElementTargetType,
loopVariableName loopVariableName
); );

View File

@ -52,6 +52,8 @@ public class MapMappingMethod extends MappingMethod {
private String valueDateFormat; private String valueDateFormat;
private List<TypeMirror> keyQualifiers; private List<TypeMirror> keyQualifiers;
private List<TypeMirror> valueQualifiers; private List<TypeMirror> valueQualifiers;
private TypeMirror keyQualifyingTargetType;
private TypeMirror valueQualifyingTargetType;
private Method method; private Method method;
private MappingBuilderContext ctx; private MappingBuilderContext ctx;
@ -85,6 +87,16 @@ public class MapMappingMethod extends MappingMethod {
return this; 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() { public MapMappingMethod build() {
List<Type> sourceTypeParams = method.getSourceParameters().iterator().next().getType().getTypeParameters(); List<Type> sourceTypeParams = method.getSourceParameters().iterator().next().getType().getTypeParameters();
@ -102,7 +114,7 @@ public class MapMappingMethod extends MappingMethod {
null, // there is no targetPropertyName null, // there is no targetPropertyName
keyDateFormat, keyDateFormat,
keyQualifiers, keyQualifiers,
null, // resulttype does not seem to make sense keyQualifyingTargetType,
"entry.getKey()" "entry.getKey()"
); );
@ -126,7 +138,7 @@ public class MapMappingMethod extends MappingMethod {
null, // there is no targetPropertyName null, // there is no targetPropertyName
valueDateFormat, valueDateFormat,
valueQualifiers, valueQualifiers,
null, // resulttype does not seem to make sense valueQualifyingTargetType,
"entry.getValue()" "entry.getValue()"
); );

View File

@ -23,6 +23,7 @@ import javax.annotation.processing.Messager;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic; import javax.tools.Diagnostic;
@ -37,6 +38,7 @@ public class IterableMapping {
private final String dateFormat; private final String dateFormat;
private final List<TypeMirror> qualifiers; private final List<TypeMirror> qualifiers;
private final TypeMirror qualifyingElementTargetType;
private final AnnotationMirror mirror; private final AnnotationMirror mirror;
private final AnnotationValue dateFormatAnnotationValue; private final AnnotationValue dateFormatAnnotationValue;
@ -46,10 +48,13 @@ public class IterableMapping {
return null; 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( messager.printMessage(
Diagnostic.Kind.ERROR, Diagnostic.Kind.ERROR,
"'dateformat' and 'qualifiedBy' are undefined in @IterableMapping, " "'dateformat', 'qualifiedBy' and 'elementTargetType' are undefined in @IterableMapping, "
+ "define at least one of them.", + "define at least one of them.",
method method
); );
@ -58,6 +63,7 @@ public class IterableMapping {
return new IterableMapping( return new IterableMapping(
iterableMapping.dateFormat(), iterableMapping.dateFormat(),
iterableMapping.qualifiedBy(), iterableMapping.qualifiedBy(),
elementTargetTypeIsDefined ? iterableMapping.elementTargetType() : null,
iterableMapping.mirror, iterableMapping.mirror,
iterableMapping.values.dateFormat() iterableMapping.values.dateFormat()
); );
@ -66,10 +72,12 @@ public class IterableMapping {
private IterableMapping( private IterableMapping(
String dateFormat, String dateFormat,
List<TypeMirror> qualifiers, List<TypeMirror> qualifiers,
TypeMirror resultType,
AnnotationMirror mirror, AnnotationMirror mirror,
AnnotationValue dateFormatAnnotationValue) { AnnotationValue dateFormatAnnotationValue ) {
this.dateFormat = dateFormat; this.dateFormat = dateFormat;
this.qualifiers = qualifiers; this.qualifiers = qualifiers;
this.qualifyingElementTargetType = resultType;
this.mirror = mirror; this.mirror = mirror;
this.dateFormatAnnotationValue = dateFormatAnnotationValue; this.dateFormatAnnotationValue = dateFormatAnnotationValue;
} }
@ -82,6 +90,10 @@ public class IterableMapping {
return qualifiers; return qualifiers;
} }
public TypeMirror getQualifyingElementTargetType() {
return qualifyingElementTargetType;
}
public AnnotationMirror getMirror() { public AnnotationMirror getMirror() {
return mirror; return mirror;
} }

View File

@ -22,6 +22,7 @@ import java.util.List;
import javax.annotation.processing.Messager; import javax.annotation.processing.Messager;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic; import javax.tools.Diagnostic;
@ -39,20 +40,26 @@ public class MapMapping {
private final String valueFormat; private final String valueFormat;
private final List<TypeMirror> valueQualifiers; private final List<TypeMirror> valueQualifiers;
private final AnnotationMirror mirror; private final AnnotationMirror mirror;
private final TypeMirror keyQualifyingTargetType;
private final TypeMirror valueQualifyingTargetType;
public static MapMapping fromPrism(MapMappingPrism mapMapping, ExecutableElement method, Messager messager) { public static MapMapping fromPrism(MapMappingPrism mapMapping, ExecutableElement method, Messager messager) {
if ( mapMapping == null ) { if ( mapMapping == null ) {
return null; return null;
} }
boolean keyTargetTypeIsDefined = !TypeKind.VOID.equals( mapMapping.keyTargetType().getKind() );
boolean valueTargetTypeIsDefined = !TypeKind.VOID.equals( mapMapping.valueTargetType().getKind() );
if ( mapMapping.keyDateFormat().isEmpty() if ( mapMapping.keyDateFormat().isEmpty()
&& mapMapping.keyQualifiedBy().isEmpty() && mapMapping.keyQualifiedBy().isEmpty()
&& mapMapping.valueDateFormat().isEmpty() && mapMapping.valueDateFormat().isEmpty()
&& mapMapping.valueQualifiedBy().isEmpty() ) { && mapMapping.valueQualifiedBy().isEmpty()
&& !keyTargetTypeIsDefined
&& !valueTargetTypeIsDefined ) {
messager.printMessage( messager.printMessage(
Diagnostic.Kind.ERROR, Diagnostic.Kind.ERROR,
"'keyDateFormat', 'keyQualifiedBy', 'valueDateFormat' and 'valueQualfiedBy' are all undefined in " "'keyDateFormat', 'keyQualifiedBy', 'keyTargetType', 'valueDateFormat', 'valueQualfiedBy' and "
+ "@MapMapping, define at least one of them.", + "'valueTargetType' are all undefined in @MapMapping, define at least one of them.",
method method
); );
} }
@ -61,8 +68,10 @@ public class MapMapping {
return new MapMapping( return new MapMapping(
mapMapping.keyDateFormat(), mapMapping.keyDateFormat(),
mapMapping.keyQualifiedBy(), mapMapping.keyQualifiedBy(),
keyTargetTypeIsDefined ? mapMapping.keyTargetType() : null,
mapMapping.valueDateFormat(), mapMapping.valueDateFormat(),
mapMapping.valueQualifiedBy(), mapMapping.valueQualifiedBy(),
valueTargetTypeIsDefined ? mapMapping.valueTargetType() : null,
mapMapping.mirror mapMapping.mirror
); );
} }
@ -70,13 +79,17 @@ public class MapMapping {
private MapMapping( private MapMapping(
String keyFormat, String keyFormat,
List<TypeMirror> keyQualifiers, List<TypeMirror> keyQualifiers,
TypeMirror keyResultType,
String valueFormat, String valueFormat,
List<TypeMirror> valueQualifiers, List<TypeMirror> valueQualifiers,
AnnotationMirror mirror) { TypeMirror valueResultType,
AnnotationMirror mirror ) {
this.keyFormat = keyFormat; this.keyFormat = keyFormat;
this.keyQualifiers = keyQualifiers; this.keyQualifiers = keyQualifiers;
this.keyQualifyingTargetType = keyResultType;
this.valueFormat = valueFormat; this.valueFormat = valueFormat;
this.valueQualifiers = valueQualifiers; this.valueQualifiers = valueQualifiers;
this.valueQualifyingTargetType = valueResultType;
this.mirror = mirror; this.mirror = mirror;
} }
@ -88,6 +101,10 @@ public class MapMapping {
return keyQualifiers; return keyQualifiers;
} }
public TypeMirror getKeyQualifyingTargetType() {
return keyQualifyingTargetType;
}
public String getValueFormat() { public String getValueFormat() {
return valueFormat; return valueFormat;
} }
@ -96,6 +113,10 @@ public class MapMapping {
return valueQualifiers; return valueQualifiers;
} }
public TypeMirror getValueQualifyingTargetType() {
return valueQualifyingTargetType;
}
public AnnotationMirror getMirror() { public AnnotationMirror getMirror() {
return mirror; return mirror;
} }

View File

@ -271,9 +271,11 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
String dateFormat = null; String dateFormat = null;
List<TypeMirror> qualifiers = null; List<TypeMirror> qualifiers = null;
TypeMirror qualifyingElementTargetType = null;
if ( method.getIterableMapping() != null ) { if ( method.getIterableMapping() != null ) {
dateFormat = method.getIterableMapping().getDateFormat(); dateFormat = method.getIterableMapping().getDateFormat();
qualifiers = method.getIterableMapping().getQualifiers(); qualifiers = method.getIterableMapping().getQualifiers();
qualifyingElementTargetType = method.getIterableMapping().getQualifyingElementTargetType();
} }
IterableMappingMethod iterableMappingMethod = builder IterableMappingMethod iterableMappingMethod = builder
@ -281,6 +283,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
.method( method ) .method( method )
.dateFormat( dateFormat ) .dateFormat( dateFormat )
.qualifiers( qualifiers ) .qualifiers( qualifiers )
.qualifyingElementTargetType( qualifyingElementTargetType )
.build(); .build();
hasFactoryMethod = iterableMappingMethod.getFactoryMethod() != null; hasFactoryMethod = iterableMappingMethod.getFactoryMethod() != null;
@ -302,11 +305,15 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
String valueDateFormat = null; String valueDateFormat = null;
List<TypeMirror> keyQualifiers = null; List<TypeMirror> keyQualifiers = null;
List<TypeMirror> valueQualifiers = null; List<TypeMirror> valueQualifiers = null;
TypeMirror keyQualifyingTargetType = null;
TypeMirror valueQualifyingTargetType = null;
if ( method.getMapMapping() != null ) { if ( method.getMapMapping() != null ) {
keyDateFormat = method.getMapMapping().getKeyFormat(); keyDateFormat = method.getMapMapping().getKeyFormat();
valueDateFormat = method.getMapMapping().getValueFormat(); valueDateFormat = method.getMapMapping().getValueFormat();
keyQualifiers = method.getMapMapping().getKeyQualifiers(); keyQualifiers = method.getMapMapping().getKeyQualifiers();
valueQualifiers = method.getMapMapping().getValueQualifiers(); valueQualifiers = method.getMapMapping().getValueQualifiers();
keyQualifyingTargetType = method.getMapMapping().getKeyQualifyingTargetType();
valueQualifyingTargetType = method.getMapMapping().getValueQualifyingTargetType();
} }
MapMappingMethod mapMappingMethod = builder MapMappingMethod mapMappingMethod = builder
@ -316,6 +323,8 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
.valueDateFormat( valueDateFormat ) .valueDateFormat( valueDateFormat )
.keyQualifiers( keyQualifiers ) .keyQualifiers( keyQualifiers )
.valueQualifiers( valueQualifiers ) .valueQualifiers( valueQualifiers )
.keyQualifyingTargetType( keyQualifyingTargetType )
.valueQualifyingTargetType( valueQualifyingTargetType )
.build(); .build();
hasFactoryMethod = mapMappingMethod.getFactoryMethod() != null; hasFactoryMethod = mapMappingMethod.getFactoryMethod() != null;

View File

@ -65,8 +65,8 @@ public class ErroneousCollectionMappingTest {
@Diagnostic(type = EmptyItererableMappingMapper.class, @Diagnostic(type = EmptyItererableMappingMapper.class,
kind = Kind.ERROR, kind = Kind.ERROR,
line = 35, line = 35,
messageRegExp = "'dateformat' and 'qualifiedBy' are undefined in @IterableMapping, " messageRegExp = "'dateformat', 'qualifiedBy' and 'elementTargetType' are undefined in "
+ "define at least one of them.") + "@IterableMapping, define at least one of them.")
} }
) )
public void shouldFailOnEmptyIterableAnnotation() { public void shouldFailOnEmptyIterableAnnotation() {
@ -81,8 +81,9 @@ public class ErroneousCollectionMappingTest {
@Diagnostic(type = EmptyMapMappingMapper.class, @Diagnostic(type = EmptyMapMappingMapper.class,
kind = Kind.ERROR, kind = Kind.ERROR,
line = 34, line = 34,
messageRegExp = "'keyDateFormat', 'keyQualifiedBy', 'valueDateFormat' and 'valueQualfiedBy' are all " messageRegExp = "'keyDateFormat', 'keyQualifiedBy', 'keyTargetType', 'valueDateFormat', "
+ "undefined in @MapMapping, define at least one of them.") + "'valueQualfiedBy' and 'valueTargetType' are all undefined in @MapMapping, define at least "
+ "one of them.")
} }
) )
public void shouldFailOnEmptyMapAnnotation() { public void shouldFailOnEmptyMapAnnotation() {

View File

@ -18,7 +18,11 @@
*/ */
package org.mapstruct.ap.test.selection.resulttype; package org.mapstruct.ap.test.selection.resulttype;
import java.util.List;
import java.util.Map;
import org.mapstruct.BeanMapping; import org.mapstruct.BeanMapping;
import org.mapstruct.IterableMapping;
import org.mapstruct.MapMapping;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
@ -35,6 +39,12 @@ public interface FruitFamilyMapper {
@Mapping(target = "apple", resultType = GoldenDelicious.class) @Mapping(target = "apple", resultType = GoldenDelicious.class)
AppleFamily map(AppleFamilyDto source); AppleFamily map(AppleFamilyDto source);
@IterableMapping( elementTargetType = GoldenDelicious.class )
List<Apple> mapToGoldenDeliciousList(List<AppleDto> source);
@MapMapping( keyTargetType = GoldenDelicious.class, valueTargetType = Apple.class )
Map<Apple, Apple> mapToGoldenDeliciousMap(Map<AppleDto, AppleDto> source);
GoldenDelicious mapToGoldenDelicious(AppleDto source); GoldenDelicious mapToGoldenDelicious(AppleDto source);
@BeanMapping(resultType = Apple.class) @BeanMapping(resultType = Apple.class)

View File

@ -18,6 +18,10 @@
*/ */
package org.mapstruct.ap.test.selection.resulttype; 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 javax.tools.Diagnostic.Kind;
import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Assertions.assertThat;
import org.junit.Test; 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<AppleDto> source = Arrays.asList( new AppleDto( "AppleDto" ) );
List<Apple> 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<AppleDto, AppleDto> source = new HashMap<AppleDto, AppleDto>();
source.put( new AppleDto( "GoldenDelicious" ), new AppleDto( "AppleDto" ) );
Map<Apple, Apple> result = FruitFamilyMapper.INSTANCE.mapToGoldenDeliciousMap( source );
assertThat( result ).isNotNull();
assertThat( result ).isNotEmpty();
Map.Entry<Apple, Apple> 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" );
}
} }