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 0a22215037..314659d526 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 @@ -89,6 +89,7 @@ public static class Builder { private final Set existingVariableNames = new HashSet(); private Map> methodMappings; private SingleMappingByTargetPropertyNameFunction singleMapping; + private final Map> unprocessedDefinedTargets = new HashMap>(); public Builder mappingContext(MappingBuilderContext mappingContext) { this.ctx = mappingContext; @@ -146,6 +147,8 @@ public BeanMappingMethod build() { applyParameterNameBasedMapping(); } + handleUnprocessedDefinedTargets(); + // report errors on unmapped properties reportErrorForUnmappedTargetPropertiesIfRequired(); @@ -216,6 +219,50 @@ else if ( !method.isUpdateMethod() && method.getReturnType().isAbstract() ) { ); } + /** + * If there were nested defined targets that have not been handled. The we need to process them at he end. + */ + private void handleUnprocessedDefinedTargets() { + Iterator>> iterator = unprocessedDefinedTargets.entrySet().iterator(); + + while ( iterator.hasNext() ) { + Entry> entry = iterator.next(); + String propertyName = entry.getKey(); + if ( !unprocessedTargetProperties.containsKey( propertyName ) ) { + continue; + } + List sourceParameters = method.getSourceParameters(); + boolean forceUpdateMethod = sourceParameters.size() > 1; + for ( Parameter sourceParameter : sourceParameters ) { + SourceReference reference = new SourceReference.BuilderFromProperty() + .sourceParameter( sourceParameter ) + .name( propertyName ) + .build(); + + MappingOptions mappingOptions = extractAdditionalOptions( propertyName, true ); + PropertyMapping propertyMapping = new PropertyMappingBuilder() + .mappingContext( ctx ) + .sourceMethod( method ) + .targetWriteAccessor( unprocessedTargetProperties.get( propertyName ) ) + .targetReadAccessor( getTargetPropertyReadAccessor( propertyName ) ) + .targetPropertyName( propertyName ) + .sourceReference( reference ) + .existingVariableNames( existingVariableNames ) + .dependsOn( mappingOptions.collectNestedDependsOn() ) + .forgeMethodWithMappingOptions( mappingOptions ) + .forceUpdateMethod( forceUpdateMethod ) + .forgedNamedBased( false ) + .build(); + + if ( propertyMapping != null ) { + unprocessedTargetProperties.remove( propertyName ); + iterator.remove(); + propertyMappings.add( propertyMapping ); + } + } + } + } + /** * Sources the given mappings as per the dependency relationships given via {@code dependsOn()}. If a cycle is * detected, an error is reported. @@ -291,6 +338,7 @@ else if ( reportErrorOnTargetObject( mapping ) ) { // In order to avoid: "Unknown property foo in return type" in case of duplicate // target mappings unprocessedTargetProperties.remove( handledTarget ); + unprocessedDefinedTargets.remove( handledTarget ); } return errorOccurred; @@ -307,7 +355,12 @@ private void handleDefinedNestedTargetMapping(Set handledTargets) { unprocessedSourceParameters.removeAll( holder.getProcessedSourceParameters() ); propertyMappings.addAll( holder.getPropertyMappings() ); handledTargets.addAll( holder.getHandledTargets() ); - + for ( Entry> entry : holder.getUnprocessedDefinedTarget().entrySet() ) { + if ( entry.getValue().isEmpty() ) { + continue; + } + unprocessedDefinedTargets.put( entry.getKey().getName(), entry.getValue() ); + } } private boolean handleDefinedMapping(Mapping mapping, Set handledTargets) { @@ -318,6 +371,7 @@ private boolean handleDefinedMapping(Mapping mapping, Set handledTargets TargetReference targetRef = mapping.getTargetReference(); PropertyEntry targetProperty = first( targetRef.getPropertyEntries() ); + String propertyName = targetProperty.getName(); // unknown properties given via dependsOn()? for ( String dependency : mapping.getDependsOn() ) { @@ -361,7 +415,7 @@ else if ( mapping.getSourceName() != null ) { .dependsOn( mapping.getDependsOn() ) .defaultValue( mapping.getDefaultValue() ) .build(); - handledTargets.add( targetProperty.getName() ); + handledTargets.add( propertyName ); unprocessedSourceParameters.remove( sourceRef.getParameter() ); } else { @@ -370,7 +424,7 @@ else if ( mapping.getSourceName() != null ) { } // its a constant - else if ( mapping.getConstant() != null ) { + else if ( mapping.getConstant() != null && !unprocessedDefinedTargets.containsKey( propertyName ) ) { propertyMapping = new ConstantMappingBuilder() .mappingContext( ctx ) @@ -387,7 +441,7 @@ else if ( mapping.getConstant() != null ) { } // its an expression - else if ( mapping.getJavaExpression() != null ) { + else if ( mapping.getJavaExpression() != null && !unprocessedDefinedTargets.containsKey( propertyName ) ) { propertyMapping = new JavaExpressionMappingBuilder() .mappingContext( ctx ) @@ -500,6 +554,7 @@ private void applyPropertyNameBasedMapping() { .defaultValue( mapping != null ? mapping.getDefaultValue() : null ) .existingVariableNames( existingVariableNames ) .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.emptyList() ) + .forgeMethodWithMappingOptions( extractAdditionalOptions( targetPropertyName, false ) ) .build(); unprocessedSourceParameters.remove( sourceParameter ); @@ -523,6 +578,7 @@ else if ( newPropertyMapping != null ) { if ( propertyMapping != null ) { propertyMappings.add( propertyMapping ); targetPropertyEntriesIterator.remove(); + unprocessedDefinedTargets.remove( targetPropertyName ); } } } @@ -560,16 +616,32 @@ private void applyParameterNameBasedMapping() { .selectionParameters( mapping != null ? mapping.getSelectionParameters() : null ) .existingVariableNames( existingVariableNames ) .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.emptyList() ) + .forgeMethodWithMappingOptions( extractAdditionalOptions( targetProperty.getKey(), false ) ) .build(); propertyMappings.add( propertyMapping ); targetPropertyEntriesIterator.remove(); sourceParameters.remove(); + unprocessedDefinedTargets.remove( targetProperty.getKey() ); } } } } + private MappingOptions extractAdditionalOptions(String targetProperty, boolean restrictToDefinedMappings) { + MappingOptions additionalOptions = null; + if ( unprocessedDefinedTargets.containsKey( targetProperty ) ) { + + Map> mappings = new HashMap>(); + mappings.put( + targetProperty, + unprocessedDefinedTargets.get( targetProperty ) + ); + additionalOptions = MappingOptions.forMappingsOnly( mappings, restrictToDefinedMappings ); + } + return additionalOptions; + } + private Accessor getTargetPropertyReadAccessor(String propertyName) { return method.getResultType().getPropertyReadAccessors().get( propertyName ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index 0ae6e9afe8..3788cc6034 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -31,6 +31,7 @@ import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.PropertyEntry; import org.mapstruct.ap.internal.model.source.SourceReference; +import org.mapstruct.ap.internal.util.Extractor; import static org.mapstruct.ap.internal.util.Collections.first; @@ -42,16 +43,35 @@ */ public class NestedTargetPropertyMappingHolder { + private static final Extractor SOURCE_PARAM_EXTRACTOR = new + Extractor() { + @Override + public Parameter apply(SourceReference sourceReference) { + return sourceReference.getParameter(); + } + }; + + private static final Extractor PROPERTY_EXTRACTOR = new + Extractor() { + @Override + public PropertyEntry apply(SourceReference sourceReference) { + return first( sourceReference.getPropertyEntries() ); + } + }; + private final List processedSourceParameters; private final Set handledTargets; private final List propertyMappings; + private final Map> unprocessedDefinedTarget; public NestedTargetPropertyMappingHolder( List processedSourceParameters, Set handledTargets, - List propertyMappings) { + List propertyMappings, + Map> unprocessedDefinedTarget) { this.processedSourceParameters = processedSourceParameters; this.handledTargets = handledTargets; this.propertyMappings = propertyMappings; + this.unprocessedDefinedTarget = unprocessedDefinedTarget; } /** @@ -62,7 +82,6 @@ public List getProcessedSourceParameters() { } /** - * * @return all the targets that were hanled */ public Set getHandledTargets() { @@ -70,13 +89,19 @@ public Set getHandledTargets() { } /** - * * @return the generated property mappings */ public List getPropertyMappings() { return propertyMappings; } + /** + * @return a map of all the unprocessed defined targets that can be applied to name forged base methods + */ + public Map> getUnprocessedDefinedTarget() { + return unprocessedDefinedTarget; + } + public static class Builder { private Method method; @@ -103,42 +128,92 @@ public NestedTargetPropertyMappingHolder build() { Set handledTargets = new HashSet(); List propertyMappings = new ArrayList(); - Map> groupedByTP = groupByPoppedTargetReferences( method.getMappingOptions() ); + GroupedTargetReferences groupedByTP = groupByTargetReferences( method.getMappingOptions() ); + Map> unprocessedDefinedTarget = new HashMap>(); + + for ( Map.Entry> entryByTP : groupedByTP.poppedTargetReferences.entrySet() ) { + PropertyEntry targetProperty = entryByTP.getKey(); + GroupedBySourceParameters groupedBySourceParam = groupBySourceParameter( + entryByTP.getValue(), + groupedByTP.singleTargetReferences.get( targetProperty ) + ); + boolean forceUpdateMethod = groupedBySourceParam.groupedBySourceParameter.keySet().size() > 1; + + unprocessedDefinedTarget.put( targetProperty, groupedBySourceParam.notProcessedAppliesToAll ); + for ( Map.Entry> entryByParam : groupedBySourceParam + .groupedBySourceParameter.entrySet() ) { - for ( Map.Entry> entryByTP : groupedByTP.entrySet() ) { - Map> groupedBySourceParam = groupBySourceParameter( entryByTP.getValue() ); - boolean forceUpdateMethod = groupedBySourceParam.keySet().size() > 1; - for ( Map.Entry> entryByParam : groupedBySourceParam.entrySet() ) { + Parameter sourceParameter = entryByParam.getKey(); - SourceReference sourceRef = new SourceReference.BuilderFromProperty() - .sourceParameter( entryByParam.getKey() ) - .name( entryByTP.getKey().getName() ) - .build(); - MappingOptions mappingOptions = MappingOptions.forMappingsOnly( - groupByTargetName( entryByParam.getValue() ) + GroupedSourceReferences groupedSourceReferences = groupByPoppedSourceReferences( + entryByParam.getValue(), + groupedByTP.singleTargetReferences.get( targetProperty ) ); - PropertyMapping propertyMapping = new PropertyMapping.PropertyMappingBuilder() - .mappingContext( mappingContext ) - .sourceMethod( method ) - .targetProperty( entryByTP.getKey() ) - .targetPropertyName( entryByTP.getKey().getName() ) - .sourceReference( sourceRef ) - .existingVariableNames( existingVariableNames ) - .dependsOn( mappingOptions.collectNestedDependsOn() ) - .forgeMethodWithMappingOptions( mappingOptions ) - .forceUpdateMethod( forceUpdateMethod ) - .build(); - processedSourceParameters.add( sourceRef.getParameter() ); - - if ( propertyMapping != null ) { - propertyMappings.add( propertyMapping ); + for ( Map.Entry> entryBySP : groupedSourceReferences + .groupedBySourceReferences + .entrySet() ) { + PropertyEntry sourceEntry = entryBySP.getKey(); + MappingOptions sourceMappingOptions = MappingOptions.forMappingsOnly( + groupByTargetName( entryBySP.getValue() ), + forceUpdateMethod + ); + SourceReference sourceRef = new SourceReference.BuilderFromProperty() + .sourceParameter( sourceParameter ) + .type( sourceEntry.getType() ) + .readAccessor( sourceEntry.getReadAccessor() ) + .presenceChecker( sourceEntry.getPresenceChecker() ) + .name( targetProperty.getName() ) + .build(); + + PropertyMapping propertyMapping = createPropertyMappingForNestedTarget( + sourceMappingOptions, + targetProperty, + sourceRef, + forceUpdateMethod + ); + + if ( propertyMapping != null ) { + propertyMappings.add( propertyMapping ); + } + + handledTargets.add( entryByTP.getKey().getName() ); + } + + if ( !groupedSourceReferences.nonNested.isEmpty() ) { + MappingOptions nonNestedOptions = MappingOptions.forMappingsOnly( + groupByTargetName( groupedSourceReferences.nonNested ), + true + ); + SourceReference reference = new SourceReference.BuilderFromProperty() + .sourceParameter( sourceParameter ) + .name( targetProperty.getName() ) + .build(); + + PropertyMapping propertyMapping = createPropertyMappingForNestedTarget( + nonNestedOptions, + targetProperty, + reference, + forceUpdateMethod + ); + + if ( propertyMapping != null ) { + propertyMappings.add( propertyMapping ); + } + + handledTargets.add( entryByTP.getKey().getName() ); } + + unprocessedDefinedTarget.put( targetProperty, groupedSourceReferences.notProcessedAppliesToAll ); } - handledTargets.add( entryByTP.getKey().getName() ); } - return new NestedTargetPropertyMappingHolder( processedSourceParameters, handledTargets, propertyMappings ); + return new NestedTargetPropertyMappingHolder( + processedSourceParameters, + handledTargets, + propertyMappings, + unprocessedDefinedTarget + ); } /** @@ -160,28 +235,38 @@ public NestedTargetPropertyMappingHolder build() { * * The key will be the former top level property, the MappingOptions will contain the remainders. * - * So, 2 cloned new MappingOptions with popped targetReferences. Also Note that the not nested targetReference4 - * disappeared. + * If the target reference cannot be popped it is stored in a different map. That looks like: + * + * propertyEntryZ - List ( targetReference4: propertyEntryZ ) + * + * @param mappingOptions that need to be used to create the {@link GroupedTargetReferences} * * @return See above */ - public Map> groupByPoppedTargetReferences(MappingOptions mappingOptions) { + private GroupedTargetReferences groupByTargetReferences(MappingOptions mappingOptions) { Map> mappings = mappingOptions.getMappings(); // group all mappings based on the top level name before popping Map> mappingsKeyedByProperty = new HashMap>(); + Map> singleTargetReferences = new HashMap>(); for ( List mapping : mappings.values() ) { + PropertyEntry property = first( first( mapping ).getTargetReference().getPropertyEntries() ); Mapping newMapping = first( mapping ).popTargetReference(); if ( newMapping != null ) { // group properties on current name. - PropertyEntry property = first( first( mapping ).getTargetReference().getPropertyEntries() ); if ( !mappingsKeyedByProperty.containsKey( property ) ) { mappingsKeyedByProperty.put( property, new ArrayList() ); } mappingsKeyedByProperty.get( property ).add( newMapping ); } + else { + if ( !singleTargetReferences.containsKey( property ) ) { + singleTargetReferences.put( property, new ArrayList() ); + } + singleTargetReferences.get( property ).add( first( mapping ) ); + } } - return mappingsKeyedByProperty; + return new GroupedTargetReferences( mappingsKeyedByProperty, singleTargetReferences ); } /** @@ -190,11 +275,16 @@ public Map> groupByPoppedTargetReferences(MappingOp * Note: this method is used for forging nested update methods. For that purpose, the same method with all * joined mappings should be generated. See also: NestedTargetPropertiesTest#shouldMapNestedComposedTarget * + * @param mappings that mappings that need to be used for the grouping + * @param singleTargetReferences a List containing all non-nested mappings for the same grouped target + * property as the {@code mappings} * @return the split mapping options. */ - public Map> groupBySourceParameter(List mappings) { + private GroupedBySourceParameters groupBySourceParameter(List mappings, + List singleTargetReferences) { Map> mappingsKeyedByParameter = new HashMap>(); + List appliesToAll = new ArrayList(); for ( Mapping mapping : mappings ) { if ( mapping.getSourceReference() != null && mapping.getSourceReference().isValid() ) { Parameter parameter = mapping.getSourceReference().getParameter(); @@ -203,9 +293,84 @@ public Map> groupBySourceParameter(List mappin } mappingsKeyedByParameter.get( parameter ).add( mapping ); } + else { + appliesToAll.add( mapping ); + } + } + + populateWithSingleTargetReferences( + mappingsKeyedByParameter, + singleTargetReferences, + SOURCE_PARAM_EXTRACTOR + ); + + for ( Map.Entry> entry : mappingsKeyedByParameter.entrySet() ) { + entry.getValue().addAll( appliesToAll ); } - return mappingsKeyedByParameter; + List notProcessAppliesToAll = + mappingsKeyedByParameter.isEmpty() ? appliesToAll : new ArrayList(); + + return new GroupedBySourceParameters( mappingsKeyedByParameter, notProcessAppliesToAll ); + } + + /** + * Creates a nested grouping by popping the source mappings. See the description of the class to see what is + * generated. + * + * @param mappings the list of {@link Mapping} that needs to be used for grouping on popped source references + * @param singleTargetReferences the single target references that match the source mappings + * + * @return the Grouped Source References + */ + private GroupedSourceReferences groupByPoppedSourceReferences(List mappings, + List singleTargetReferences) { + List nonNested = new ArrayList(); + List appliesToAll = new ArrayList(); + // group all mappings based on the top level name before popping + Map> mappingsKeyedByProperty = new HashMap>(); + for ( Mapping mapping : mappings ) { + + Mapping newMapping = mapping.popSourceReference(); + if ( newMapping != null ) { + // group properties on current name. + PropertyEntry property = first( mapping.getSourceReference().getPropertyEntries() ); + if ( !mappingsKeyedByProperty.containsKey( property ) ) { + mappingsKeyedByProperty.put( property, new ArrayList() ); + } + mappingsKeyedByProperty.get( property ).add( newMapping ); + } + //This is an ignore, or some expression, or a default. We apply these to all + else if ( mapping.getSourceReference() == null ) { + appliesToAll.add( mapping ); + } + else { + nonNested.add( mapping ); + } + } + + populateWithSingleTargetReferences( mappingsKeyedByProperty, singleTargetReferences, PROPERTY_EXTRACTOR ); + + for ( Map.Entry> entry : mappingsKeyedByProperty.entrySet() ) { + entry.getValue().addAll( appliesToAll ); + } + + List notProcessedAppliesToAll = new ArrayList(); + // If the applied to all were not added to all properties because they were empty, and the non-nested + // one are not empty, add them to the non-nested ones + if ( mappingsKeyedByProperty.isEmpty() && !nonNested.isEmpty() ) { + nonNested.addAll( appliesToAll ); + } + // Otherwise if the non-nested are empty, just pass them along so they can be processed later on + else if ( mappingsKeyedByProperty.isEmpty() && nonNested.isEmpty() ) { + notProcessedAppliesToAll.addAll( appliesToAll ); + } + + return new GroupedSourceReferences( + mappingsKeyedByProperty, + nonNested, + notProcessedAppliesToAll + ); } private Map> groupByTargetName(List mappingList) { @@ -218,5 +383,123 @@ private Map> groupByTargetName(List mappingList) } return result; } + + private PropertyMapping createPropertyMappingForNestedTarget(MappingOptions mappingOptions, + PropertyEntry targetProperty, SourceReference sourceReference, boolean forceUpdateMethod) { + PropertyMapping propertyMapping = new PropertyMapping.PropertyMappingBuilder() + .mappingContext( mappingContext ) + .sourceMethod( method ) + .targetProperty( targetProperty ) + .targetPropertyName( targetProperty.getName() ) + .sourceReference( sourceReference ) + .existingVariableNames( existingVariableNames ) + .dependsOn( mappingOptions.collectNestedDependsOn() ) + .forgeMethodWithMappingOptions( mappingOptions ) + .forceUpdateMethod( forceUpdateMethod ) + .forgedNamedBased( false ) + .build(); + return propertyMapping; + } + + /** + * If a single target mapping has a valid {@link SourceReference} and the {@link SourceReference} has more + * then 0 {@link PropertyEntry} and if the {@code map} does not contain an entry with the extracted key then + * an entry with the extracted key and an empty list is added. + * + * @param map that needs to be populated + * @param singleTargetReferences to use + * @param keyExtractor to be used to extract a key + */ + private void populateWithSingleTargetReferences(Map> map, + List singleTargetReferences, Extractor keyExtractor) { + if ( singleTargetReferences != null ) { + //This are non nested target references only their property needs to be added as they most probably + // define it + for ( Mapping mapping : singleTargetReferences ) { + if ( mapping.getSourceReference() != null && mapping.getSourceReference().isValid() + && !mapping.getSourceReference().getPropertyEntries().isEmpty() ) { + //TODO is this OK? Why there are no propertyEntries? For the Inverse LetterMapper for example + K key = keyExtractor.apply( mapping.getSourceReference() ); + if ( !map.containsKey( key ) ) { + map.put( key, new ArrayList() ); + } + } + } + } + } + } + + private static class GroupedBySourceParameters { + private final Map> groupedBySourceParameter; + private final List notProcessedAppliesToAll; + + private GroupedBySourceParameters(Map> groupedBySourceParameter, + List notProcessedAppliesToAll) { + this.groupedBySourceParameter = groupedBySourceParameter; + this.notProcessedAppliesToAll = notProcessedAppliesToAll; + } + } + + /** + * The grouped target references. This class contains the poppedTarget references and the single target + * references (target references that were not nested). + */ + private static class GroupedTargetReferences { + private final Map> poppedTargetReferences; + private final Map> singleTargetReferences; + + private GroupedTargetReferences(Map> poppedTargetReferences, + Map> singleTargetReferences) { + this.poppedTargetReferences = poppedTargetReferences; + this.singleTargetReferences = singleTargetReferences; + } + } + + /** + * This class is used to group Source references in respected to the nestings that they have. + * + * This class contains all groupings by Property Entries if they are nested, or a list of all the other options + * that could not have been popped. + * + * So, take + * + * sourceReference 1: propertyEntryX.propertyEntryX1.propertyEntryX1a + * sourceReference 2: propertyEntryX.propertyEntryX2 + * sourceReference 3: propertyEntryY.propertyY1 + * sourceReference 4: propertyEntryZ + * sourceReference 5: propertyEntryZ2 + * + * We will have a map with entries: + *
+     *
+     * propertyEntryX - ( sourceReference1: propertyEntryX1.propertyEntryX1a,
+     *                    sourceReference2 propertyEntryX2 )
+     * propertyEntryY - ( sourceReference1: propertyEntryY1 )
+     *
+     * 
+ * + * If non-nested mappings were found they will be located in a List. + *
+     * sourceReference 4: propertyEntryZ
+     * sourceReference 5: propertyEntryZ2
+     * 
+ * + *
+ * If Mappings that should apply to all were found, but no grouping was found, they will be located in a + * different list: + */ + private static class GroupedSourceReferences { + + private final Map> groupedBySourceReferences; + private final List nonNested; + private final List notProcessedAppliesToAll; + + private GroupedSourceReferences(Map> groupedBySourceReferences, + List nonNested, List notProcessedAppliesToAll) { + this.groupedBySourceReferences = groupedBySourceReferences; + this.nonNested = nonNested; + this.notProcessedAppliesToAll = notProcessedAppliesToAll; + + } } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java index b11f9d3e42..dbe0eee278 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java @@ -73,7 +73,7 @@ public static BeanMapping fromPrism(BeanMappingPrism beanMapping, ExecutableElem * * @return bean mapping that needs to be used for Mappings */ - public static BeanMapping forMappingsOnly() { + public static BeanMapping forForgedMethods() { return new BeanMapping( null, null, ReportingPolicyPrism.IGNORE ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java index 9eac9f92da..137628f71c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java @@ -202,6 +202,24 @@ private Mapping( Mapping mapping, TargetReference targetReference ) { this.targetReference = targetReference; } + private Mapping( Mapping mapping, SourceReference sourceReference ) { + this.sourceName = Strings.join( sourceReference.getElementNames(), "." ); + this.constant = mapping.constant; + this.javaExpression = mapping.javaExpression; + this.targetName = mapping.targetName; + this.defaultValue = mapping.defaultValue; + this.isIgnored = mapping.isIgnored; + this.mirror = mapping.mirror; + this.sourceAnnotationValue = mapping.sourceAnnotationValue; + this.targetAnnotationValue = mapping.targetAnnotationValue; + this.formattingParameters = mapping.formattingParameters; + this.selectionParameters = mapping.selectionParameters; + this.dependsOnAnnotationValue = mapping.dependsOnAnnotationValue; + this.dependsOn = mapping.dependsOn; + this.sourceReference = sourceReference; + this.targetReference = mapping.targetReference; + } + private static String getExpression(MappingPrism mappingPrism, ExecutableElement element, FormattingMessager messager) { if ( mappingPrism.expression().isEmpty() ) { @@ -333,6 +351,16 @@ public Mapping popTargetReference() { return null; } + public Mapping popSourceReference() { + if ( sourceReference != null ) { + SourceReference newSourceReference = sourceReference.pop(); + if (newSourceReference != null ) { + return new Mapping(this, newSourceReference ); + } + } + return null; + } + public List getDependsOn() { return dependsOn; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index aabb1116c4..aa2d0cbea5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -76,16 +76,18 @@ public static MappingOptions empty() { * creates mapping options with only regular mappings * * @param mappings regular mappings to add + * @param restrictToDefinedMappings whether to restrict the mappings only to the defined mappings * @return MappingOptions with only regular mappings */ - public static MappingOptions forMappingsOnly( Map> mappings ) { + public static MappingOptions forMappingsOnly(Map> mappings, + boolean restrictToDefinedMappings) { return new MappingOptions( mappings, null, null, - BeanMapping.forMappingsOnly(), + restrictToDefinedMappings ? BeanMapping.forForgedMethods() : null, Collections.emptyList(), - true + restrictToDefinedMappings ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceReference.java index 1cd4063dce..d81c265408 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceReference.java @@ -320,6 +320,17 @@ public SourceReference copyForInheritanceTo(SourceMethod method) { return new SourceReference( replacement, propertyEntries, isValid ); } + public SourceReference pop() { + if ( propertyEntries.size() > 1 ) { + List newPropertyEntries = + new ArrayList( propertyEntries.subList( 1, propertyEntries.size() ) ); + return new SourceReference( parameter, newPropertyEntries, isValid ); + } + else { + return null; + } + } + @Override public String toString() { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Extractor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Extractor.java new file mode 100644 index 0000000000..ec70657afd --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Extractor.java @@ -0,0 +1,39 @@ +/** + * Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.internal.util; + +/** + * This is a helper interface until we migrate to Java 8. It allows us to abstract our code easier. + * + * @param the type of the input to the function + * @param the type of the result of the function + * + * @author Filip Hrisafov + */ +public interface Extractor { + + /** + * Extract a value from the passed parameter. + * + * @param t the value that we need to extract from + * + * @return the result from the extraction + */ + R apply(T t); +}