#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>}.
*
* <p>Note: either @IterableMapping#dateFormat, @IterableMapping#resultType or @IterableMapping#qualifiedBy
* must be specified</p>
*
* @author Gunnar Morling
*/
@Target(ElementType.METHOD)
@ -52,4 +55,12 @@ public @interface IterableMapping {
* @return the qualifiers
*/
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>}.
*
* <p>Note: at least one element needs to be specified</p>
*
* @author Gunnar Morling
*/
@Target(ElementType.METHOD)
@ -73,4 +75,20 @@ public @interface MapMapping {
* @return the qualifiers
*/
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 String dateFormat;
private List<TypeMirror> 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
);

View File

@ -52,6 +52,8 @@ public class MapMappingMethod extends MappingMethod {
private String valueDateFormat;
private List<TypeMirror> keyQualifiers;
private List<TypeMirror> 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<Type> 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()"
);

View File

@ -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<TypeMirror> 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<TypeMirror> 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;
}

View File

@ -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<TypeMirror> 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<TypeMirror> keyQualifiers,
TypeMirror keyResultType,
String valueFormat,
List<TypeMirror> 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;
}

View File

@ -271,9 +271,11 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
String dateFormat = null;
List<TypeMirror> 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<List<Sourc
.method( method )
.dateFormat( dateFormat )
.qualifiers( qualifiers )
.qualifyingElementTargetType( qualifyingElementTargetType )
.build();
hasFactoryMethod = iterableMappingMethod.getFactoryMethod() != null;
@ -302,11 +305,15 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
String valueDateFormat = null;
List<TypeMirror> keyQualifiers = null;
List<TypeMirror> 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<List<Sourc
.valueDateFormat( valueDateFormat )
.keyQualifiers( keyQualifiers )
.valueQualifiers( valueQualifiers )
.keyQualifyingTargetType( keyQualifyingTargetType )
.valueQualifyingTargetType( valueQualifyingTargetType )
.build();
hasFactoryMethod = mapMappingMethod.getFactoryMethod() != null;

View File

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

View File

@ -18,7 +18,11 @@
*/
package org.mapstruct.ap.test.selection.resulttype;
import java.util.List;
import java.util.Map;
import org.mapstruct.BeanMapping;
import org.mapstruct.IterableMapping;
import org.mapstruct.MapMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@ -35,6 +39,12 @@ public interface FruitFamilyMapper {
@Mapping(target = "apple", resultType = GoldenDelicious.class)
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);
@BeanMapping(resultType = Apple.class)

View File

@ -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<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" );
}
}