diff --git a/core-jdk8/src/main/java/org/mapstruct/Mapping.java b/core-jdk8/src/main/java/org/mapstruct/Mapping.java index f329b3a56..53b8799e2 100644 --- a/core-jdk8/src/main/java/org/mapstruct/Mapping.java +++ b/core-jdk8/src/main/java/org/mapstruct/Mapping.java @@ -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". + *
  • When no matching property is found, MapStruct looks for a matching parameter name instead.
  • *
  • When used to map an enum constant, the name of the constant member is to be given.
  • * * Either this attribute or {@link #constant()} or {@link #expression()} may be specified for a given mapping. diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index 073a330da..8d09bf30d 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -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". + *
  • When no matching property is found, MapStruct looks for a matching parameter name instead.
  • *
  • When used to map an enum constant, the name of the constant member is to be given.
  • * * Either this attribute or {@link #constant()} or {@link #expression()} may be specified for a given mapping. diff --git a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java index 59d816d25..401fd9e40 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java @@ -65,6 +65,7 @@ public class BeanMappingMethod extends MappingMethod { private SourceMethod method; private Map unprocessedTargetProperties; private final List propertyMappings = new ArrayList(); + private final Set unprocessedSourceParameters = new HashSet(); 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> targetProperties = + unprocessedTargetProperties.entrySet().iterator(); + + while ( targetProperties.hasNext() ) { + + Entry targetProperty = targetProperties.next(); + + Iterator 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 candidates) { if ( candidates.isEmpty() ) { return null; @@ -462,6 +516,28 @@ public class BeanMappingMethod extends MappingMethod { return types; } + public List getSourceParametersExcludingPrimitives() { + List sourceParameters = new ArrayList(); + for ( Parameter sourceParam : getSourceParameters() ) { + if (!sourceParam.getType().isPrimitive()) { + sourceParameters.add( sourceParam ); + } + } + + return sourceParameters; + } + + public List getSourcePrimitiveParameters() { + List sourceParameters = new ArrayList(); + for ( Parameter sourceParam : getSourceParameters() ) { + if (sourceParam.getType().isPrimitive()) { + sourceParameters.add( sourceParam ); + } + } + return sourceParameters; + } + + public MethodReference getFactoryMethod() { return this.factoryMethod; } diff --git a/processor/src/main/java/org/mapstruct/ap/model/common/Parameter.java b/processor/src/main/java/org/mapstruct/ap/model/common/Parameter.java index 8079d6449..536852dde 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/common/Parameter.java +++ b/processor/src/main/java/org/mapstruct/ap/model/common/Parameter.java @@ -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; + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/model/source/SourceReference.java index fe4dace1d..8e6ee2bda 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/SourceReference.java @@ -234,7 +234,10 @@ public class SourceReference { } public SourceReference build() { - List sourcePropertyEntries = Arrays.asList( new PropertyEntry( name, accessor, type ) ); + List sourcePropertyEntries = new ArrayList(); + if ( accessor != null ) { + sourcePropertyEntries.add( new PropertyEntry( name, accessor, type ) ); + } return new SourceReference( sourceParameter, sourcePropertyEntries, true ); } } diff --git a/processor/src/main/resources/org.mapstruct.ap.model.BeanMappingMethod.ftl b/processor/src/main/resources/org.mapstruct.ap.model.BeanMappingMethod.ftl index eb98b7ba6..2db9a52e9 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.BeanMappingMethod.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.BeanMappingMethod.ftl @@ -20,13 +20,13 @@ --> @Override <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, ) <@throws/> { - if ( <#list sourceParameters as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && ) { + if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && ) { return<#if returnType.name != "void"> null; } <#if !existingInstanceMapping><@includeModel object=resultType/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType=resultType raw=true/><#else>new <@includeModel object=resultType/>(); <#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 @@ } + <#list sourcePrimitiveParameters as sourceParam> + <#if (propertyMappingsByParameter[sourceParam.name]?size > 0)> + <#list propertyMappingsByParameter[sourceParam.name] as propertyMapping> + <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/> + + + <#else> <#list propertyMappingsByParameter[sourceParameters[0].name] as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/> diff --git a/processor/src/test/java/org/mapstruct/ap/test/severalsources/SeveralSourceParametersTest.java b/processor/src/test/java/org/mapstruct/ap/test/severalsources/SeveralSourceParametersTest.java index 8b603207b..14ee7cd53 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/severalsources/SeveralSourceParametersTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/severalsources/SeveralSourceParametersTest.java @@ -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") diff --git a/processor/src/test/java/org/mapstruct/ap/test/severalsources/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/severalsources/SourceTargetMapper.java index 4d9622eae..9b9e28200 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/severalsources/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/severalsources/SourceTargetMapper.java @@ -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); + }