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"
|
||||
* 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>
|
||||
* <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>
|
||||
* </ol>
|
||||
* 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"
|
||||
* 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>
|
||||
* <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>
|
||||
* </ol>
|
||||
* 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 Map<String, ExecutableElement> unprocessedTargetProperties;
|
||||
private final List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>();
|
||||
private final Set<Parameter> unprocessedSourceParameters = new HashSet<Parameter>();
|
||||
|
||||
public Builder mappingContext(MappingBuilderContext mappingContext) {
|
||||
this.ctx = mappingContext;
|
||||
@ -74,6 +75,9 @@ public class BeanMappingMethod extends MappingMethod {
|
||||
public Builder souceMethod(SourceMethod sourceMethod) {
|
||||
this.method = sourceMethod;
|
||||
this.unprocessedTargetProperties = initTargetPropertyAccessors();
|
||||
for ( Parameter sourceParameter : method.getSourceParameters() ) {
|
||||
unprocessedSourceParameters.add( sourceParameter );
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -87,6 +91,9 @@ public class BeanMappingMethod extends MappingMethod {
|
||||
// map properties without a mapping
|
||||
applyPropertyNameBasedMapping();
|
||||
|
||||
// map parameters without a mapping
|
||||
applyParameterNameBasedMapping();
|
||||
|
||||
// report errors on unmapped properties
|
||||
reportErrorForUnmappedTargetPropertiesIfRequired();
|
||||
|
||||
@ -207,6 +214,7 @@ public class BeanMappingMethod extends MappingMethod {
|
||||
.dateFormat( mapping.getDateFormat() )
|
||||
.build();
|
||||
handledTargets.add( mapping.getTargetName() );
|
||||
unprocessedSourceParameters.remove( sourceRef.getParameter() );
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -279,6 +287,10 @@ public class BeanMappingMethod extends MappingMethod {
|
||||
|
||||
for ( Parameter sourceParameter : method.getSourceParameters() ) {
|
||||
|
||||
if ( sourceParameter.getType().isPrimitive() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for ( ExecutableElement sourceAccessor : sourceParameter.getType().getGetters() ) {
|
||||
String sourcePropertyName = Executables.getPropertyName( sourceAccessor );
|
||||
|
||||
@ -309,6 +321,8 @@ public class BeanMappingMethod extends MappingMethod {
|
||||
.dateFormat( mapping != null ? mapping.getDateFormat() : null )
|
||||
.build();
|
||||
|
||||
unprocessedSourceParameters.remove( sourceParameter );
|
||||
|
||||
}
|
||||
// candidates are handled
|
||||
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) {
|
||||
if ( candidates.isEmpty() ) {
|
||||
return null;
|
||||
@ -462,6 +516,28 @@ public class BeanMappingMethod extends MappingMethod {
|
||||
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() {
|
||||
return this.factoryMethod;
|
||||
}
|
||||
|
@ -71,4 +71,27 @@ public class Parameter extends ModelElement {
|
||||
public boolean isTargetType() {
|
||||
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() {
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
@ -20,13 +20,13 @@
|
||||
-->
|
||||
@Override
|
||||
<#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>;
|
||||
}
|
||||
|
||||
<#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)>
|
||||
<#list sourceParameters as sourceParam>
|
||||
<#list sourceParametersExcludingPrimitives as sourceParam>
|
||||
<#if (propertyMappingsByParameter[sourceParam.name]?size > 0)>
|
||||
if ( ${sourceParam.name} != null ) {
|
||||
<#list propertyMappingsByParameter[sourceParam.name] as propertyMapping>
|
||||
@ -35,6 +35,13 @@
|
||||
}
|
||||
</#if>
|
||||
</#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>
|
||||
<#list propertyMappingsByParameter[sourceParameters[0].name] as propertyMapping>
|
||||
<@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/>
|
||||
|
@ -98,6 +98,21 @@ public class SeveralSourceParametersTest {
|
||||
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
|
||||
@WithClasses({ ErroneousSourceTargetMapper.class, Address.class, DeliveryAddress.class })
|
||||
@ProcessorOption(name = "mapstruct.unmappedTargetPolicy", value = "IGNORE")
|
||||
|
@ -41,4 +41,9 @@ public interface SourceTargetMapper {
|
||||
})
|
||||
void personAndAddressToDeliveryAddress(Person person, Address address,
|
||||
@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