mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#348 Mapping parameters based on name when other mappings fail
This commit is contained in:
parent
7d1c5c2f24
commit
7948acf630
@ -63,6 +63,7 @@ public @interface Mapping {
|
|||||||
* This may either be a simple property name (e.g. "address") or a dot-separated property path (e.g. "address.city"
|
* This may either be a simple property name (e.g. "address") or a dot-separated property path (e.g. "address.city"
|
||||||
* or "address.city.name"). In case the annotated method has several source parameters, the property name must
|
* or "address.city.name"). In case the annotated method has several source parameters, the property name must
|
||||||
* qualified with the parameter name, e.g. "addressParam.city".</li>
|
* qualified with the parameter name, e.g. "addressParam.city".</li>
|
||||||
|
* <li>When no matching property is found, MapStruct looks for a matching parameter name instead.</li>
|
||||||
* <li>When used to map an enum constant, the name of the constant member is to be given.</li>
|
* <li>When used to map an enum constant, the name of the constant member is to be given.</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* Either this attribute or {@link #constant()} or {@link #expression()} may be specified for a given mapping.
|
* Either this attribute or {@link #constant()} or {@link #expression()} may be specified for a given mapping.
|
||||||
|
@ -61,6 +61,7 @@ public @interface Mapping {
|
|||||||
* This may either be a simple property name (e.g. "address") or a dot-separated property path (e.g. "address.city"
|
* This may either be a simple property name (e.g. "address") or a dot-separated property path (e.g. "address.city"
|
||||||
* or "address.city.name"). In case the annotated method has several source parameters, the property name must
|
* or "address.city.name"). In case the annotated method has several source parameters, the property name must
|
||||||
* qualified with the parameter name, e.g. "addressParam.city".</li>
|
* qualified with the parameter name, e.g. "addressParam.city".</li>
|
||||||
|
* <li>When no matching property is found, MapStruct looks for a matching parameter name instead.</li>
|
||||||
* <li>When used to map an enum constant, the name of the constant member is to be given.</li>
|
* <li>When used to map an enum constant, the name of the constant member is to be given.</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* Either this attribute or {@link #constant()} or {@link #expression()} may be specified for a given mapping.
|
* Either this attribute or {@link #constant()} or {@link #expression()} may be specified for a given mapping.
|
||||||
|
@ -65,6 +65,7 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
private SourceMethod method;
|
private SourceMethod method;
|
||||||
private Map<String, ExecutableElement> unprocessedTargetProperties;
|
private Map<String, ExecutableElement> unprocessedTargetProperties;
|
||||||
private final List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>();
|
private final List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>();
|
||||||
|
private final Set<Parameter> unprocessedSourceParameters = new HashSet<Parameter>();
|
||||||
|
|
||||||
public Builder mappingContext(MappingBuilderContext mappingContext) {
|
public Builder mappingContext(MappingBuilderContext mappingContext) {
|
||||||
this.ctx = mappingContext;
|
this.ctx = mappingContext;
|
||||||
@ -74,6 +75,9 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
public Builder souceMethod(SourceMethod sourceMethod) {
|
public Builder souceMethod(SourceMethod sourceMethod) {
|
||||||
this.method = sourceMethod;
|
this.method = sourceMethod;
|
||||||
this.unprocessedTargetProperties = initTargetPropertyAccessors();
|
this.unprocessedTargetProperties = initTargetPropertyAccessors();
|
||||||
|
for ( Parameter sourceParameter : method.getSourceParameters() ) {
|
||||||
|
unprocessedSourceParameters.add( sourceParameter );
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +91,9 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
// map properties without a mapping
|
// map properties without a mapping
|
||||||
applyPropertyNameBasedMapping();
|
applyPropertyNameBasedMapping();
|
||||||
|
|
||||||
|
// map parameters without a mapping
|
||||||
|
applyParameterNameBasedMapping();
|
||||||
|
|
||||||
// report errors on unmapped properties
|
// report errors on unmapped properties
|
||||||
reportErrorForUnmappedTargetPropertiesIfRequired();
|
reportErrorForUnmappedTargetPropertiesIfRequired();
|
||||||
|
|
||||||
@ -207,6 +214,7 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
.dateFormat( mapping.getDateFormat() )
|
.dateFormat( mapping.getDateFormat() )
|
||||||
.build();
|
.build();
|
||||||
handledTargets.add( mapping.getTargetName() );
|
handledTargets.add( mapping.getTargetName() );
|
||||||
|
unprocessedSourceParameters.remove( sourceRef.getParameter() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -279,6 +287,10 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
|
|
||||||
for ( Parameter sourceParameter : method.getSourceParameters() ) {
|
for ( Parameter sourceParameter : method.getSourceParameters() ) {
|
||||||
|
|
||||||
|
if ( sourceParameter.getType().isPrimitive() ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for ( ExecutableElement sourceAccessor : sourceParameter.getType().getGetters() ) {
|
for ( ExecutableElement sourceAccessor : sourceParameter.getType().getGetters() ) {
|
||||||
String sourcePropertyName = Executables.getPropertyName( sourceAccessor );
|
String sourcePropertyName = Executables.getPropertyName( sourceAccessor );
|
||||||
|
|
||||||
@ -309,6 +321,8 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
.dateFormat( mapping != null ? mapping.getDateFormat() : null )
|
.dateFormat( mapping != null ? mapping.getDateFormat() : null )
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
unprocessedSourceParameters.remove( sourceParameter );
|
||||||
|
|
||||||
}
|
}
|
||||||
// candidates are handled
|
// candidates are handled
|
||||||
candidates.clear();
|
candidates.clear();
|
||||||
@ -337,6 +351,46 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyParameterNameBasedMapping() {
|
||||||
|
|
||||||
|
Iterator<Entry<String, ExecutableElement>> targetProperties =
|
||||||
|
unprocessedTargetProperties.entrySet().iterator();
|
||||||
|
|
||||||
|
while ( targetProperties.hasNext() ) {
|
||||||
|
|
||||||
|
Entry<String, ExecutableElement> targetProperty = targetProperties.next();
|
||||||
|
|
||||||
|
Iterator<Parameter> sourceParameters = unprocessedSourceParameters.iterator();
|
||||||
|
|
||||||
|
while ( sourceParameters.hasNext() ) {
|
||||||
|
|
||||||
|
Parameter sourceParameter = sourceParameters.next();
|
||||||
|
if ( sourceParameter.getName().equals( targetProperty.getKey() ) ) {
|
||||||
|
Mapping mapping = method.getSingleMappingByTargetPropertyName( targetProperty.getKey() );
|
||||||
|
|
||||||
|
SourceReference sourceRef = new SourceReference.BuilderFromProperty()
|
||||||
|
.sourceParameter( sourceParameter )
|
||||||
|
.name( targetProperty.getKey() )
|
||||||
|
.build();
|
||||||
|
|
||||||
|
PropertyMapping propertyMapping = new PropertyMappingBuilder()
|
||||||
|
.mappingContext( ctx )
|
||||||
|
.souceMethod( method )
|
||||||
|
.targetAccessor( targetProperty.getValue() )
|
||||||
|
.targetPropertyName( targetProperty.getKey() )
|
||||||
|
.sourceReference( sourceRef )
|
||||||
|
.qualifiers( mapping != null ? mapping.getQualifiers() : null )
|
||||||
|
.dateFormat( mapping != null ? mapping.getDateFormat() : null )
|
||||||
|
.build();
|
||||||
|
|
||||||
|
propertyMappings.add( propertyMapping );
|
||||||
|
targetProperties.remove();
|
||||||
|
sourceParameters.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ExecutableElement getSourceAccessor(String sourcePropertyName, List<ExecutableElement> candidates) {
|
private ExecutableElement getSourceAccessor(String sourcePropertyName, List<ExecutableElement> candidates) {
|
||||||
if ( candidates.isEmpty() ) {
|
if ( candidates.isEmpty() ) {
|
||||||
return null;
|
return null;
|
||||||
@ -462,6 +516,28 @@ public class BeanMappingMethod extends MappingMethod {
|
|||||||
return types;
|
return types;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Parameter> getSourceParametersExcludingPrimitives() {
|
||||||
|
List<Parameter> sourceParameters = new ArrayList<Parameter>();
|
||||||
|
for ( Parameter sourceParam : getSourceParameters() ) {
|
||||||
|
if (!sourceParam.getType().isPrimitive()) {
|
||||||
|
sourceParameters.add( sourceParam );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Parameter> getSourcePrimitiveParameters() {
|
||||||
|
List<Parameter> sourceParameters = new ArrayList<Parameter>();
|
||||||
|
for ( Parameter sourceParam : getSourceParameters() ) {
|
||||||
|
if (sourceParam.getType().isPrimitive()) {
|
||||||
|
sourceParameters.add( sourceParam );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sourceParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public MethodReference getFactoryMethod() {
|
public MethodReference getFactoryMethod() {
|
||||||
return this.factoryMethod;
|
return this.factoryMethod;
|
||||||
}
|
}
|
||||||
|
@ -71,4 +71,27 @@ public class Parameter extends ModelElement {
|
|||||||
public boolean isTargetType() {
|
public boolean isTargetType() {
|
||||||
return targetType;
|
return targetType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int hash = 5;
|
||||||
|
hash = 23 * hash + (this.name != null ? this.name.hashCode() : 0);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if ( obj == null ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( getClass() != obj.getClass() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final Parameter other = (Parameter) obj;
|
||||||
|
if ( (this.name == null) ? (other.name != null) : !this.name.equals( other.name ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,10 @@ public class SourceReference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SourceReference build() {
|
public SourceReference build() {
|
||||||
List<PropertyEntry> sourcePropertyEntries = Arrays.asList( new PropertyEntry( name, accessor, type ) );
|
List<PropertyEntry> sourcePropertyEntries = new ArrayList<PropertyEntry>();
|
||||||
|
if ( accessor != null ) {
|
||||||
|
sourcePropertyEntries.add( new PropertyEntry( name, accessor, type ) );
|
||||||
|
}
|
||||||
return new SourceReference( sourceParameter, sourcePropertyEntries, true );
|
return new SourceReference( sourceParameter, sourcePropertyEntries, true );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,13 @@
|
|||||||
-->
|
-->
|
||||||
@Override
|
@Override
|
||||||
<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>) <@throws/> {
|
<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>) <@throws/> {
|
||||||
if ( <#list sourceParameters as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && </#if></#list> ) {
|
if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && </#if></#list> ) {
|
||||||
return<#if returnType.name != "void"> null</#if>;
|
return<#if returnType.name != "void"> null</#if>;
|
||||||
}
|
}
|
||||||
|
|
||||||
<#if !existingInstanceMapping><@includeModel object=resultType/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType=resultType raw=true/><#else>new <@includeModel object=resultType/>()</#if>;</#if>
|
<#if !existingInstanceMapping><@includeModel object=resultType/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType=resultType raw=true/><#else>new <@includeModel object=resultType/>()</#if>;</#if>
|
||||||
<#if (sourceParameters?size > 1)>
|
<#if (sourceParameters?size > 1)>
|
||||||
<#list sourceParameters as sourceParam>
|
<#list sourceParametersExcludingPrimitives as sourceParam>
|
||||||
<#if (propertyMappingsByParameter[sourceParam.name]?size > 0)>
|
<#if (propertyMappingsByParameter[sourceParam.name]?size > 0)>
|
||||||
if ( ${sourceParam.name} != null ) {
|
if ( ${sourceParam.name} != null ) {
|
||||||
<#list propertyMappingsByParameter[sourceParam.name] as propertyMapping>
|
<#list propertyMappingsByParameter[sourceParam.name] as propertyMapping>
|
||||||
@ -35,6 +35,13 @@
|
|||||||
}
|
}
|
||||||
</#if>
|
</#if>
|
||||||
</#list>
|
</#list>
|
||||||
|
<#list sourcePrimitiveParameters as sourceParam>
|
||||||
|
<#if (propertyMappingsByParameter[sourceParam.name]?size > 0)>
|
||||||
|
<#list propertyMappingsByParameter[sourceParam.name] as propertyMapping>
|
||||||
|
<@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/>
|
||||||
|
</#list>
|
||||||
|
</#if>
|
||||||
|
</#list>
|
||||||
<#else>
|
<#else>
|
||||||
<#list propertyMappingsByParameter[sourceParameters[0].name] as propertyMapping>
|
<#list propertyMappingsByParameter[sourceParameters[0].name] as propertyMapping>
|
||||||
<@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/>
|
<@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/>
|
||||||
|
@ -98,6 +98,21 @@ public class SeveralSourceParametersTest {
|
|||||||
assertThat( deliveryAddress ).isNull();
|
assertThat( deliveryAddress ).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithClasses({ Person.class, Address.class, DeliveryAddress.class, SourceTargetMapper.class })
|
||||||
|
public void shouldMapSeveralSourceAttributesAndParameters() {
|
||||||
|
Person person = new Person( "Bob", "Garner", 181, "An actor" );
|
||||||
|
|
||||||
|
DeliveryAddress deliveryAddress =
|
||||||
|
SourceTargetMapper.INSTANCE.personAndAddressToDeliveryAddress( person, 42, 12345, "Main street" );
|
||||||
|
|
||||||
|
assertThat( deliveryAddress.getLastName() ).isEqualTo( "Garner" );
|
||||||
|
assertThat( deliveryAddress.getZipCode() ).isEqualTo( 12345 );
|
||||||
|
assertThat( deliveryAddress.getHouseNumber() ).isEqualTo( 42 );
|
||||||
|
assertThat( deliveryAddress.getDescription() ).isEqualTo( "An actor" );
|
||||||
|
assertThat( deliveryAddress.getStreet()).isEqualTo( "Main street" );
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@WithClasses({ ErroneousSourceTargetMapper.class, Address.class, DeliveryAddress.class })
|
@WithClasses({ ErroneousSourceTargetMapper.class, Address.class, DeliveryAddress.class })
|
||||||
@ProcessorOption(name = "mapstruct.unmappedTargetPolicy", value = "IGNORE")
|
@ProcessorOption(name = "mapstruct.unmappedTargetPolicy", value = "IGNORE")
|
||||||
|
@ -41,4 +41,9 @@ public interface SourceTargetMapper {
|
|||||||
})
|
})
|
||||||
void personAndAddressToDeliveryAddress(Person person, Address address,
|
void personAndAddressToDeliveryAddress(Person person, Address address,
|
||||||
@MappingTarget DeliveryAddress deliveryAddress);
|
@MappingTarget DeliveryAddress deliveryAddress);
|
||||||
|
|
||||||
|
@Mapping( target = "description", source = "person.description")
|
||||||
|
DeliveryAddress personAndAddressToDeliveryAddress(Person person, Integer houseNumber, int zipCode,
|
||||||
|
String street);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user