diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 8c55047ed..f5ddf3867 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -19,7 +19,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import javax.lang.model.type.DeclaredType; import javax.tools.Diagnostic; @@ -52,7 +51,6 @@ import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.Accessor; -import static org.mapstruct.ap.internal.model.source.Mapping.getMappingByTargetName; import static org.mapstruct.ap.internal.model.beanmapping.MappingReferences.forSourceMethod; import static org.mapstruct.ap.internal.util.Collections.first; import static org.mapstruct.ap.internal.util.Message.BEANMAPPING_ABSTRACT; @@ -88,7 +86,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { private final Set unprocessedSourceParameters = new HashSet<>(); private final Set existingVariableNames = new HashSet<>(); private final Map> unprocessedDefinedTargets = new LinkedHashMap<>(); - private Function singleMapping; private MappingReferences mappingReferences; @@ -103,15 +100,12 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } public Builder sourceMethod(SourceMethod sourceMethod) { - singleMapping = - targetName -> getMappingByTargetName( targetName, sourceMethod.getMappingOptions().getMappings() ); this.method = sourceMethod; this.mappingReferences = forSourceMethod( sourceMethod, ctx.getMessager(), ctx.getTypeFactory() ); return this; } public Builder forgedMethod(ForgedMethod forgedMethod) { - singleMapping = targetPropertyName -> null; this.method = forgedMethod; mappingReferences = forgedMethod.getMappingReferences(); Parameter sourceParameter = first( Parameter.getSourceParameters( forgedMethod.getParameters() ) ); @@ -320,12 +314,14 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { .name( propertyName ) .build(); + Accessor targetPropertyReadAccessor = + method.getResultType().getPropertyReadAccessors().get( propertyName ); MappingReferences mappingRefs = extractMappingReferences( propertyName, true ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .targetWriteAccessor( unprocessedTargetProperties.get( propertyName ) ) - .targetReadAccessor( getTargetPropertyReadAccessor( propertyName ) ) + .targetReadAccessor( targetPropertyReadAccessor ) .targetPropertyName( propertyName ) .sourceReference( reference ) .existingVariableNames( existingVariableNames ) @@ -480,18 +476,16 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } for ( MappingReference mapping : mappingReferences.getMappingReferences() ) { - TargetReference targetReference = mapping.getTargetReference(); - if ( targetReference.isValid() ) { - String target = first( targetReference.getPropertyEntries() ).getFullName(); + if ( mapping.isValid() ) { + String target = mapping.getTargetReference().getShallowestPropertyName(); if ( !handledTargets.contains( target ) ) { if ( handleDefinedMapping( mapping, handledTargets ) ) { errorOccurred = true; } } - if ( mapping.getSourceReference() != null && mapping.getSourceReference().isValid() ) { - List sourceEntries = mapping.getSourceReference().getPropertyEntries(); - if ( !sourceEntries.isEmpty() ) { - String source = first( sourceEntries ).getFullName(); + if ( mapping.getSourceReference() != null ) { + String source = mapping.getSourceReference().getShallowestPropertyName(); + if ( source != null ) { unprocessedSourceProperties.remove( source ); } } @@ -558,6 +552,11 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } } + // check if source / expression / constant are not somehow handled already + if ( unprocessedDefinedTargets.containsKey( targetPropertyName ) ) { + return false; + } + // check the mapping options // its an ignored property mapping if ( mapping.isIgnored() ) { @@ -565,11 +564,52 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { handledTargets.add( targetProperty.getName() ); } + // its a constant + // if we have an unprocessed target that means that it most probably is nested and we should + // not generated any mapping for it now. Eventually it will be done though + else if ( mapping.getConstant() != null ) { + + propertyMapping = new ConstantMappingBuilder() + .mappingContext( ctx ) + .sourceMethod( method ) + .constantExpression( mapping.getConstant() ) + .targetProperty( targetProperty ) + .targetPropertyName( targetPropertyName ) + .formattingParameters( mapping.getFormattingParameters() ) + .selectionParameters( mapping.getSelectionParameters() ) + .existingVariableNames( existingVariableNames ) + .dependsOn( mapping.getDependsOn() ) + .mirror( mapping.getMirror() ) + .build(); + handledTargets.add( targetPropertyName ); + } + + // its an expression + // if we have an unprocessed target that means that it most probably is nested and we should + // not generated any mapping for it now. Eventually it will be done though + else if ( mapping.getJavaExpression() != null ) { + + propertyMapping = new JavaExpressionMappingBuilder() + .mappingContext( ctx ) + .sourceMethod( method ) + .javaExpression( mapping.getJavaExpression() ) + .existingVariableNames( existingVariableNames ) + .targetProperty( targetProperty ) + .targetPropertyName( targetPropertyName ) + .dependsOn( mapping.getDependsOn() ) + .mirror( mapping.getMirror() ) + .build(); + handledTargets.add( targetPropertyName ); + } // its a plain-old property mapping - else if ( mapping.getSourceName() != null ) { + else { // determine source parameter SourceReference sourceRef = mappingRef.getSourceReference(); + if ( sourceRef == null && method.getSourceParameters().size() == 1 ) { + sourceRef = getSourceRef( method.getSourceParameters().get( 0 ), targetPropertyName ); + } + if ( sourceRef.isValid() ) { // targetProperty == null can occur: we arrived here because we want as many errors @@ -598,46 +638,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { errorOccured = true; } } - - // its a constant - // if we have an unprocessed target that means that it most probably is nested and we should - // not generated any mapping for it now. Eventually it will be done though - else if ( mapping.getConstant() != null && !unprocessedDefinedTargets.containsKey( targetPropertyName ) ) { - - propertyMapping = new ConstantMappingBuilder() - .mappingContext( ctx ) - .sourceMethod( method ) - .constantExpression( mapping.getConstant() ) - .targetProperty( targetProperty ) - .targetPropertyName( targetPropertyName ) - .formattingParameters( mapping.getFormattingParameters() ) - .selectionParameters( mapping.getSelectionParameters() ) - .existingVariableNames( existingVariableNames ) - .dependsOn( mapping.getDependsOn() ) - .mirror( mapping.getMirror() ) - .build(); - handledTargets.add( targetPropertyName ); - } - - // its an expression - // if we have an unprocessed target that means that it most probably is nested and we should - // not generated any mapping for it now. Eventually it will be done though - else if ( mapping.getJavaExpression() != null - && !unprocessedDefinedTargets.containsKey( targetPropertyName ) ) { - - propertyMapping = new JavaExpressionMappingBuilder() - .mappingContext( ctx ) - .sourceMethod( method ) - .javaExpression( mapping.getJavaExpression() ) - .existingVariableNames( existingVariableNames ) - .targetProperty( targetProperty ) - .targetPropertyName( targetPropertyName ) - .dependsOn( mapping.getDependsOn() ) - .mirror( mapping.getMirror() ) - .build(); - handledTargets.add( targetPropertyName ); - } - // remaining are the mappings without a 'source' so, 'only' a date format or qualifiers if ( propertyMapping != null ) { propertyMappings.add( propertyMapping ); @@ -653,91 +653,60 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { * the set of remaining target properties. */ private void applyPropertyNameBasedMapping() { - - Iterator> targetPropertyEntriesIterator = - unprocessedTargetProperties.entrySet().iterator(); - - while ( targetPropertyEntriesIterator.hasNext() ) { - - Entry targetProperty = targetPropertyEntriesIterator.next(); - String targetPropertyName = targetProperty.getKey(); - - PropertyMapping propertyMapping = null; - - if ( propertyMapping == null ) { - - for ( Parameter sourceParameter : method.getSourceParameters() ) { - - Type sourceType = sourceParameter.getType(); - - if ( sourceType.isPrimitive() || sourceType.isArrayType() ) { - continue; - } - - PropertyMapping newPropertyMapping = null; - - Accessor sourceReadAccessor = - sourceParameter.getType().getPropertyReadAccessors().get( targetPropertyName ); - - Accessor sourcePresenceChecker = - sourceParameter.getType().getPropertyPresenceCheckers().get( targetPropertyName ); - - if ( sourceReadAccessor != null ) { - Mapping mapping = singleMapping.apply( targetProperty.getKey() ); - DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror(); - - SourceReference sourceRef = new SourceReference.BuilderFromProperty() - .sourceParameter( sourceParameter ) - .type( ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor ) ) - .readAccessor( sourceReadAccessor ) - .presenceChecker( sourcePresenceChecker ) - .name( targetProperty.getKey() ) - .build(); - - MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); - newPropertyMapping = new PropertyMappingBuilder() - .mappingContext( ctx ) - .sourceMethod( method ) - .targetWriteAccessor( targetProperty.getValue() ) - .targetReadAccessor( getTargetPropertyReadAccessor( targetPropertyName ) ) - .targetPropertyName( targetPropertyName ) - .sourceReference( sourceRef ) - .formattingParameters( mapping != null ? mapping.getFormattingParameters() : null ) - .selectionParameters( mapping != null ? mapping.getSelectionParameters() : null ) - .defaultValue( mapping != null ? mapping.getDefaultValue() : null ) - .existingVariableNames( existingVariableNames ) - .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.emptySet() ) - .forgeMethodWithMappingReferences( mappingRefs ) - .nullValueCheckStrategy( mapping != null ? mapping.getNullValueCheckStrategy() : null ) - .nullValuePropertyMappingStrategy( mapping != null ? - mapping.getNullValuePropertyMappingStrategy() : null ) - .mirror( mapping != null ? mapping.getMirror() : null ) - .build(); - - unprocessedSourceParameters.remove( sourceParameter ); - } - - if ( propertyMapping != null && newPropertyMapping != null ) { - // TODO improve error message - ctx.getMessager().printMessage( - method.getExecutable(), - Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES, - targetPropertyName - ); - break; - } - else if ( newPropertyMapping != null ) { - propertyMapping = newPropertyMapping; - } + List sourceReferences = new ArrayList<>(); + for ( String targetPropertyName : unprocessedTargetProperties.keySet() ) { + for ( Parameter sourceParameter : method.getSourceParameters() ) { + SourceReference sourceRef = getSourceRef( sourceParameter, targetPropertyName ); + if ( sourceRef != null ) { + sourceReferences.add( sourceRef ); } } + } + applyPropertyNameBasedMapping( sourceReferences ); + } + + /** + * Iterates over all target properties and all source parameters. + *

+ * When a property name match occurs, the remainder will be checked for duplicates. Matches will be removed from + * the set of remaining target properties. + */ + private void applyPropertyNameBasedMapping(List sourceReferences) { + + for ( SourceReference sourceRef : sourceReferences ) { + + String targetPropertyName = sourceRef.getDeepestPropertyName(); + Accessor targetPropertyWriteAccessor = unprocessedTargetProperties.remove( targetPropertyName ); + if ( targetPropertyWriteAccessor == null ) { + // TODO improve error message + ctx.getMessager() + .printMessage( method.getExecutable(), + Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES, + targetPropertyName + ); + continue; + } + + Accessor targetPropertyReadAccessor = + method.getResultType().getPropertyReadAccessors().get( targetPropertyName ); + MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); + PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) + .sourceMethod( method ) + .targetWriteAccessor( targetPropertyWriteAccessor ) + .targetReadAccessor( targetPropertyReadAccessor ) + .targetPropertyName( targetPropertyName ) + .sourceReference( sourceRef ) + .existingVariableNames( existingVariableNames ) + .forgeMethodWithMappingReferences( mappingRefs ) + .build(); + + unprocessedSourceParameters.remove( sourceRef.getParameter() ); if ( propertyMapping != null ) { propertyMappings.add( propertyMapping ); - targetPropertyEntriesIterator.remove(); - unprocessedDefinedTargets.remove( targetPropertyName ); - unprocessedSourceProperties.remove( targetPropertyName ); } + unprocessedDefinedTargets.remove( targetPropertyName ); + unprocessedSourceProperties.remove( targetPropertyName ); } } @@ -756,30 +725,24 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { Parameter sourceParameter = sourceParameters.next(); if ( sourceParameter.getName().equals( targetProperty.getKey() ) ) { - Mapping mapping = singleMapping.apply( targetProperty.getKey() ); SourceReference sourceRef = new SourceReference.BuilderFromProperty() .sourceParameter( sourceParameter ) .name( targetProperty.getKey() ) .build(); + Accessor targetPropertyReadAccessor = + method.getResultType().getPropertyReadAccessors().get( targetProperty.getKey() ); MappingReferences mappingRefs = extractMappingReferences( targetProperty.getKey(), false ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .targetWriteAccessor( targetProperty.getValue() ) - .targetReadAccessor( getTargetPropertyReadAccessor( targetProperty.getKey() ) ) + .targetReadAccessor( targetPropertyReadAccessor ) .targetPropertyName( targetProperty.getKey() ) .sourceReference( sourceRef ) - .formattingParameters( mapping != null ? mapping.getFormattingParameters() : null ) - .selectionParameters( mapping != null ? mapping.getSelectionParameters() : null ) .existingVariableNames( existingVariableNames ) - .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.emptySet() ) .forgeMethodWithMappingReferences( mappingRefs ) - .nullValueCheckStrategy( mapping != null ? mapping.getNullValueCheckStrategy() : null ) - .nullValuePropertyMappingStrategy( mapping != null ? - mapping.getNullValuePropertyMappingStrategy() : null ) - .mirror( mapping != null ? mapping.getMirror() : null ) .build(); propertyMappings.add( propertyMapping ); @@ -792,6 +755,33 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } } + private SourceReference getSourceRef(Parameter sourceParameter, String targetPropertyName) { + + SourceReference sourceRef = null; + + if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() ) { + return sourceRef; + } + + Accessor sourceReadAccessor = + sourceParameter.getType().getPropertyReadAccessors().get( targetPropertyName ); + + Accessor sourcePresenceChecker = + sourceParameter.getType().getPropertyPresenceCheckers().get( targetPropertyName ); + + if ( sourceReadAccessor != null ) { + DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror(); + Type returnType = ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor ); + sourceRef = new SourceReference.BuilderFromProperty().sourceParameter( sourceParameter ) + .type( returnType ) + .readAccessor( sourceReadAccessor ) + .presenceChecker( sourcePresenceChecker ) + .name( targetPropertyName ) + .build(); + } + return sourceRef; + } + private MappingReferences extractMappingReferences(String targetProperty, boolean restrictToDefinedMappings) { if ( unprocessedDefinedTargets.containsKey( targetProperty ) ) { Set mappings = unprocessedDefinedTargets.get( targetProperty ); @@ -800,10 +790,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { return null; } - private Accessor getTargetPropertyReadAccessor(String propertyName) { - return method.getResultType().getPropertyReadAccessors().get( propertyName ); - } - private ReportingPolicyPrism getUnmappedTargetPolicy() { if ( mappingReferences.isForForgedMethods() ) { return ReportingPolicyPrism.IGNORE; @@ -1034,10 +1020,5 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { return Objects.equals( propertyMappings, that.propertyMappings ); } - private interface SingleMappingByTargetPropertyNameFunction { - - Mapping getSingleMappingByTargetPropertyName(String targetPropertyName); - } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 0a11473b7..628d00cc4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -51,8 +51,6 @@ import static org.mapstruct.ap.internal.model.ForgedMethod.forElementMapping; import static org.mapstruct.ap.internal.model.ForgedMethod.forPropertyMapping; import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_DEFAULT; import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_NULL; -import static org.mapstruct.ap.internal.util.Collections.first; -import static org.mapstruct.ap.internal.util.Collections.last; /** * Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to @@ -519,10 +517,10 @@ public class PropertyMapping extends ModelElement { private SourceRHS getSourceRHS( SourceReference sourceReference ) { Parameter sourceParam = sourceReference.getParameter(); - List propertyEntries = sourceReference.getPropertyEntries(); + PropertyEntry propertyEntry = sourceReference.getDeepestProperty(); // parameter reference - if ( propertyEntries.isEmpty() ) { + if ( propertyEntry == null ) { return new SourceRHS( sourceParam.getName(), sourceParam.getType(), existingVariableNames, @@ -530,8 +528,7 @@ public class PropertyMapping extends ModelElement { ); } // simple property - else if ( propertyEntries.size() == 1 ) { - PropertyEntry propertyEntry = propertyEntries.get( 0 ); + else if ( !sourceReference.isNested() ) { String sourceRef = sourceParam.getName() + "." + ValueProvider.of( propertyEntry.getReadAccessor() ); return new SourceRHS( sourceParam.getName(), sourceRef, @@ -543,7 +540,7 @@ public class PropertyMapping extends ModelElement { } // nested property given as dot path else { - Type sourceType = last( propertyEntries ).getType(); + Type sourceType = propertyEntry.getType(); if ( sourceType.isPrimitive() && !targetType.isPrimitive() ) { // Handle null's. If the forged method needs to be mapped to an object, the forged method must be // able to return null. So in that case primitive types are mapped to their corresponding wrapped @@ -581,7 +578,7 @@ public class PropertyMapping extends ModelElement { ); // create a local variable to which forged method can be assigned. - String desiredName = last( sourceReference.getPropertyEntries() ).getName(); + String desiredName = propertyEntry.getName(); sourceRhs.setSourceLocalVarName( sourceRhs.createUniqueVarName( desiredName ) ); return sourceRhs; @@ -595,7 +592,7 @@ public class PropertyMapping extends ModelElement { Parameter sourceParam = sourceReference.getParameter(); // TODO is first correct here?? shouldn't it be last since the remainer is checked // in the forged method? - PropertyEntry propertyEntry = first( sourceReference.getPropertyEntries() ); + PropertyEntry propertyEntry = sourceReference.getShallowestProperty(); if ( propertyEntry.getPresenceChecker() != null ) { sourcePresenceChecker = sourceParam.getName() + "." + propertyEntry.getPresenceChecker().getSimpleName() + "()"; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/AbstractReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/AbstractReference.java new file mode 100644 index 000000000..dcb67c903 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/AbstractReference.java @@ -0,0 +1,132 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.model.beanmapping; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.util.Strings; + +import static org.mapstruct.ap.internal.util.Collections.first; +import static org.mapstruct.ap.internal.util.Collections.last; + +/** + * Class acts as a common base class for {@link TargetReference} and {@link SourceReference}. + * + * @author sjaak + */ +public abstract class AbstractReference { + + private final Parameter parameter; + private final List propertyEntries; + private final boolean isValid; + + protected AbstractReference(Parameter sourceParameter, List sourcePropertyEntries, boolean isValid) { + this.parameter = sourceParameter; + this.propertyEntries = sourcePropertyEntries; + this.isValid = isValid; + } + + public Parameter getParameter() { + return parameter; + } + + public List getPropertyEntries() { + return propertyEntries; + } + + public boolean isValid() { + return isValid; + } + + public List getElementNames() { + List elementNames = new ArrayList<>(); + if ( parameter != null ) { + // only relevant for source properties + elementNames.add( parameter.getName() ); + } + for ( PropertyEntry propertyEntry : propertyEntries ) { + elementNames.add( propertyEntry.getName() ); + } + return elementNames; + } + + /** + * returns the property name on the shallowest nesting level + * @return + */ + public PropertyEntry getShallowestProperty() { + if ( propertyEntries.isEmpty() ) { + return null; + } + return first( propertyEntries ); + } + + /** + * returns the property name on the shallowest nesting level + * @return + */ + public String getShallowestPropertyName() { + if ( propertyEntries.isEmpty() ) { + return null; + } + return first( propertyEntries ).getName(); + } + + /** + * returns the property name on the deepest nesting level + * @return + */ + public PropertyEntry getDeepestProperty() { + if ( propertyEntries.isEmpty() ) { + return null; + } + return last( propertyEntries ); + } + + /** + * returns the property name on the deepest nesting level + * @return + */ + public String getDeepestPropertyName() { + if ( propertyEntries.isEmpty() ) { + return null; + } + return last( propertyEntries ).getName(); + } + + public boolean isNested() { + return propertyEntries.size() > 1; + } + + @Override + public String toString() { + + String result = ""; + if ( !isValid ) { + result = "invalid"; + } + else if ( propertyEntries.isEmpty() ) { + if ( parameter != null ) { + result = String.format( "parameter \"%s %s\"", parameter.getType(), parameter.getName() ); + } + } + else if ( propertyEntries.size() == 1 ) { + PropertyEntry propertyEntry = propertyEntries.get( 0 ); + result = String.format( "property \"%s %s\"", propertyEntry.getType(), propertyEntry.getName() ); + } + else { + PropertyEntry lastPropertyEntry = propertyEntries.get( propertyEntries.size() - 1 ); + result = String.format( + "property \"%s %s\"", + lastPropertyEntry.getType(), + Strings.join( getElementNames(), "." ) + ); + } + return result; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java index 3e02617b1..1ea5ff500 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java @@ -8,7 +8,6 @@ package org.mapstruct.ap.internal.model.beanmapping; import java.util.Objects; import org.mapstruct.ap.internal.model.source.Mapping; -import org.mapstruct.ap.internal.util.Strings; /** * Represents the intermediate (nesting) state of the {@link Mapping} in this class. @@ -80,16 +79,24 @@ public class MappingReference { return Objects.hash( mapping ); } + public boolean isValid( ) { + boolean result = false; + if ( targetReference.isValid() ) { + result = sourceReference != null ? sourceReference.isValid() : true; + } + return result; + } + @Override public String toString() { - String targetRefName = Strings.join( targetReference.getElementNames(), "." ); - String sourceRefName = "null"; + String targetRefStr = targetReference.toString(); + String sourceRefStr = "null"; if ( sourceReference != null ) { - sourceRefName = Strings.join( sourceReference.getElementNames(), "." ); + sourceRefStr = sourceReference.toString(); } return "MappingReference {" - + "\n sourceRefName='" + sourceRefName + "\'," - + "\n targetRefName='" + targetRefName + "\'," + + "\n sourceReference='" + sourceRefStr + "\'," + + "\n targetReference='" + targetRefStr + "\'," + "\n}"; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java index ee5e9f367..7cb9a142b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java @@ -29,10 +29,9 @@ public class MappingReferences { public static MappingReferences forSourceMethod(SourceMethod sourceMethod, FormattingMessager messager, TypeFactory typeFactory) { - Set mappings = sourceMethod.getMappingOptions().getMappings(); - - Set references = new LinkedHashSet<>(); + Set targetThisReferences = new LinkedHashSet<>(); + for ( Mapping mapping : sourceMethod.getMappingOptions().getMappings() ) { // handle source reference @@ -52,9 +51,15 @@ public class MappingReferences { // add when inverse is also valid MappingReference mappingReference = new MappingReference( mapping, targetReference, sourceReference ); if ( isValidWhenInversed( mappingReference ) ) { - references.add( mappingReference ); + if ( targetReference.isTargetThis() ) { + targetThisReferences.add( mappingReference ); + } + else { + references.add( mappingReference ); + } } } + references.addAll( targetThisReferences ); return new MappingReferences( references, false ); } @@ -104,7 +109,7 @@ public class MappingReferences { for ( MappingReference mappingRef : mappingReferences ) { TargetReference targetReference = mappingRef.getTargetReference(); - if ( targetReference.isValid() && targetReference.getPropertyEntries().size() > 1 ) { + if ( targetReference.isValid() && targetReference.isNested()) { return true; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 97bbb228e..5176f9b2e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -6,7 +6,6 @@ package org.mapstruct.ap.internal.model.beanmapping; import static org.mapstruct.ap.internal.model.beanmapping.PropertyEntry.forSourceReference; -import static org.mapstruct.ap.internal.util.Collections.first; import static org.mapstruct.ap.internal.util.Collections.last; import java.util.ArrayList; @@ -22,7 +21,6 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.Mapping; import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.model.source.SourceMethod; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; @@ -50,11 +48,7 @@ import org.mapstruct.ap.internal.util.accessor.Accessor; * * @author Sjaak Derksen */ -public class SourceReference { - - private final Parameter parameter; - private final List propertyEntries; - private final boolean isValid; +public class SourceReference extends AbstractReference { /** * Builds a {@link SourceReference} from an {@code @Mappping}. @@ -371,88 +365,23 @@ public class SourceReference { } public SourceReference build() { - return new SourceReference( sourceParameter, sourceReference.propertyEntries, true ); + return new SourceReference( sourceParameter, sourceReference.getPropertyEntries(), true ); } } private SourceReference(Parameter sourceParameter, List sourcePropertyEntries, boolean isValid) { - this.parameter = sourceParameter; - this.propertyEntries = sourcePropertyEntries; - this.isValid = isValid; - } - - public Parameter getParameter() { - return parameter; - } - - public List getPropertyEntries() { - return propertyEntries; - } - - public boolean isValid() { - return isValid; - } - - public List getElementNames() { - List sourceName = new ArrayList<>(); - sourceName.add( parameter.getName() ); - for ( PropertyEntry propertyEntry : propertyEntries ) { - sourceName.add( propertyEntry.getName() ); - } - return sourceName; - } - - /** - * Creates a copy of this reference, which is adapted to the given method - * - * @param method the method to create the copy for - * @return the copy - */ - public SourceReference copyForInheritanceTo(SourceMethod method) { - List replacementParamCandidates = new ArrayList<>(); - for ( Parameter sourceParam : method.getSourceParameters() ) { - if ( parameter != null && sourceParam.getType().isAssignableTo( parameter.getType() ) ) { - replacementParamCandidates.add( sourceParam ); - } - } - - Parameter replacement = parameter; - if ( replacementParamCandidates.size() == 1 ) { - replacement = first( replacementParamCandidates ); - } - - return new SourceReference( replacement, propertyEntries, isValid ); + super( sourceParameter, sourcePropertyEntries, isValid ); } public SourceReference pop() { - if ( propertyEntries.size() > 1 ) { + if ( getPropertyEntries().size() > 1 ) { List newPropertyEntries = - new ArrayList<>( propertyEntries.subList( 1, propertyEntries.size() ) ); - return new SourceReference( parameter, newPropertyEntries, isValid ); + new ArrayList<>( getPropertyEntries().subList( 1, getPropertyEntries().size() ) ); + return new SourceReference( getParameter(), newPropertyEntries, isValid() ); } else { return null; } } - @Override - public String toString() { - - if ( propertyEntries.isEmpty() ) { - return String.format( "parameter \"%s %s\"", getParameter().getType(), getParameter().getName() ); - } - else if ( propertyEntries.size() == 1 ) { - PropertyEntry propertyEntry = propertyEntries.get( 0 ); - return String.format( "property \"%s %s\"", propertyEntry.getType(), propertyEntry.getName() ); - } - else { - PropertyEntry lastPropertyEntry = propertyEntries.get( propertyEntries.size() - 1 ); - return String.format( - "property \"%s %s\"", - lastPropertyEntry.getType(), - Strings.join( getElementNames(), "." ) - ); - } - } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/TargetReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/TargetReference.java index bce6d43ed..b86c09dc4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/TargetReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/TargetReference.java @@ -51,12 +51,7 @@ import static org.mapstruct.ap.internal.util.Collections.first; * * @author Sjaak Derksen */ -public class TargetReference { - - private final Parameter parameter; - private final List propertyEntries; - private final boolean isValid; - +public class TargetReference extends AbstractReference { /** * Builds a {@link TargetReference} from an {@code @Mappping}. @@ -348,46 +343,24 @@ public class TargetReference { } } - private TargetReference(Parameter sourceParameter, List sourcePropertyEntries, boolean isValid) { - this.parameter = sourceParameter; - this.propertyEntries = sourcePropertyEntries; - this.isValid = isValid; + private TargetReference(Parameter sourceParameter, List targetPropertyEntries, boolean isValid) { + super( sourceParameter, targetPropertyEntries, isValid ); } - public Parameter getParameter() { - return parameter; - } - - public List getPropertyEntries() { - return propertyEntries; - } - - public boolean isValid() { - return isValid; - } - - public List getElementNames() { - List elementNames = new ArrayList<>(); - if ( parameter != null ) { - // only relevant for source properties - elementNames.add( parameter.getName() ); - } - for ( PropertyEntry propertyEntry : propertyEntries ) { - elementNames.add( propertyEntry.getName() ); - } - return elementNames; + public boolean isTargetThis() { + return getPropertyEntries().isEmpty(); } public TargetReference pop() { - if ( propertyEntries.size() > 1 ) { - List newPropertyEntries = new ArrayList<>( propertyEntries.size() - 1 ); - for ( PropertyEntry propertyEntry : propertyEntries ) { + if ( getPropertyEntries().size() > 1 ) { + List newPropertyEntries = new ArrayList<>( getPropertyEntries().size() - 1 ); + for ( PropertyEntry propertyEntry : getPropertyEntries() ) { PropertyEntry newPropertyEntry = propertyEntry.pop(); if ( newPropertyEntry != null ) { newPropertyEntries.add( newPropertyEntry ); } } - return new TargetReference( null, newPropertyEntries, isValid ); + return new TargetReference( null, newPropertyEntries, isValid() ); } else { return null; diff --git a/processor/src/test/java/org/mapstruct/ap/test/dependency/OrderingTest.java b/processor/src/test/java/org/mapstruct/ap/test/dependency/OrderingTest.java index 3faa85f78..ede43320d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/dependency/OrderingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/dependency/OrderingTest.java @@ -63,8 +63,8 @@ public class OrderingTest { @Diagnostic(type = ErroneousAddressMapperWithCyclicDependency.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 24, - messageRegExp = "Cycle\\(s\\) between properties given via dependsOn\\(\\): firstName -> lastName -> " - + "middleName -> firstName" + messageRegExp = "Cycle\\(s\\) between properties given via dependsOn\\(\\): lastName -> middleName -> " + + "firstName -> lastName" ) } ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/SuggestMostSimilarNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/SuggestMostSimilarNameTest.java index 1c4c23bb9..dcf945130 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/SuggestMostSimilarNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/SuggestMostSimilarNameTest.java @@ -84,11 +84,11 @@ public class SuggestMostSimilarNameTest { diagnostics = { @Diagnostic(type = PersonGarageWrongSourceMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 19, + line = 21, messageRegExp = "No property named \"garage\\.colour\\.rgb\".*Did you mean \"garage\\.color\"\\?"), @Diagnostic(type = PersonGarageWrongSourceMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 22, + line = 28, messageRegExp = "No property named \"garage\\.colour\".*Did you mean \"garage\\.color\"\\?") } ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonGarageWrongSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonGarageWrongSourceMapper.java index ed8e6ec90..0fd3a06b0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonGarageWrongSourceMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonGarageWrongSourceMapper.java @@ -7,6 +7,7 @@ package org.mapstruct.ap.test.namesuggestion.erroneous; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Mappings; import org.mapstruct.ap.test.namesuggestion.Person; import org.mapstruct.ap.test.namesuggestion.PersonDto; import org.mapstruct.factory.Mappers; @@ -16,10 +17,18 @@ public interface PersonGarageWrongSourceMapper { PersonGarageWrongSourceMapper MAPPER = Mappers.getMapper( PersonGarageWrongSourceMapper.class ); - @Mapping(source = "garage.colour.rgb", target = "garage.color.rgb") + @Mappings( { + @Mapping( target = "garage.color.rgb", source = "garage.colour.rgb" ), + @Mapping( target = "fullAge", source = "age" ), + @Mapping( target = "fullName", source = "name" ) + } ) Person mapPerson(PersonDto dto); - @Mapping(source = "garage.colour", target = "garage.color") + @Mappings( { + @Mapping( target = "garage.color", source = "garage.colour" ), + @Mapping( target = "fullAge", source = "age" ), + @Mapping( target = "fullName", source = "name" ) + } ) Person mapPersonGarage(PersonDto dto); }