mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#433 introduce resultType in @IterableMapping and @MapMapping
This commit is contained in:
parent
adcc89b184
commit
897c8fbb6d
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
@ -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()"
|
||||
);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
|
@ -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" );
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user