From 1406c0b6db7740bfe663edeae936cc3399bc72bb Mon Sep 17 00:00:00 2001 From: sjaakd Date: Sat, 21 Jan 2017 11:14:11 +0100 Subject: [PATCH] #1011 Using ForgedMethods to forge nested target mappings --- .../conversion/CreateDecimalFormat.java | 5 + .../ap/internal/model/BeanMappingMethod.java | 533 ++++++++---------- .../ap/internal/model/LocalVariable.java | 88 --- .../model/NestedLocalVariableAssignment.java | 137 ----- .../ap/internal/model/PropertyMapping.java | 118 ++-- .../internal/model/source/ForgedMethod.java | 63 ++- .../ap/internal/model/source/Mapping.java | 47 +- .../internal/model/source/MappingOptions.java | 171 +++++- .../ap/internal/model/source/Method.java | 6 + .../internal/model/source/PropertyEntry.java | 43 +- .../internal/model/source/SourceMethod.java | 2 +- .../model/source/SourceReference.java | 23 + .../model/source/TargetReference.java | 24 +- .../model/source/builtin/BuiltInMethod.java | 6 + .../ap/internal/model/BeanMappingMethod.ftl | 9 - .../ap/internal/model/LocalVariable.ftl | 21 - .../model/NestedLocalVariableAssignment.ftl | 22 - .../ap/internal/model/PropertyMapping.ftl | 12 +- .../ChartEntryToArtist.java | 6 +- .../ChartEntryToArtistUpdate.java | 70 +++ .../FishTankMapper.java | 38 ++ .../NestedTargetPropertiesTest.java | 87 ++- .../_target/FishDto.java | 47 ++ .../_target/FishTankDto.java | 46 ++ .../_target/WaterPlantDto.java | 37 ++ .../nestedtargetproperties/source/Fish.java | 37 ++ .../source/FishTank.java | 55 ++ .../source/WaterPlant.java | 37 ++ 28 files changed, 1137 insertions(+), 653 deletions(-) delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/LocalVariable.java delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/NestedLocalVariableAssignment.java delete mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/LocalVariable.ftl delete mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/NestedLocalVariableAssignment.ftl create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistUpdate.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/FishTankMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/_target/FishDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/_target/FishTankDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/_target/WaterPlantDto.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/source/Fish.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/source/FishTank.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/source/WaterPlant.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java index 3ae9f27fb..d881a9fc1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java @@ -27,6 +27,7 @@ import org.mapstruct.ap.internal.model.HelperMethod; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.model.source.MappingOptions; /** * HelperMethod that creates a {@link java.text.DecimalFormat} @@ -63,4 +64,8 @@ public class CreateDecimalFormat extends HelperMethod { return returnType; } + @Override + public MappingOptions getMappingOptions() { + return MappingOptions.empty(); + } } 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 149e26d21..580d53dd6 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,12 +19,9 @@ package org.mapstruct.ap.internal.model; import static org.mapstruct.ap.internal.util.Collections.first; -import static org.mapstruct.ap.internal.util.Collections.last; -import static org.mapstruct.ap.internal.util.Strings.getSaveVariableName; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -42,7 +39,6 @@ import javax.tools.Diagnostic; import org.mapstruct.ap.internal.model.PropertyMapping.ConstantMappingBuilder; import org.mapstruct.ap.internal.model.PropertyMapping.JavaExpressionMappingBuilder; import org.mapstruct.ap.internal.model.PropertyMapping.PropertyMappingBuilder; -import org.mapstruct.ap.internal.model.assignment.Assignment; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer; @@ -50,6 +46,7 @@ import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBui import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.ForgedMethodHistory; import org.mapstruct.ap.internal.model.source.Mapping; +import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.PropertyEntry; import org.mapstruct.ap.internal.model.source.SelectionParameters; @@ -72,7 +69,7 @@ import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor; * * @author Gunnar Morling */ -public class BeanMappingMethod extends MappingMethod { +public class BeanMappingMethod extends ContainerMappingMethod { private final List propertyMappings; private final Map> mappingsByParameter; @@ -80,7 +77,6 @@ public class BeanMappingMethod extends MappingMethod { private final MethodReference factoryMethod; private final boolean mapNullToDefault; private final Type resultType; - private final NestedTargetObjects nestedTargetObjects; private final boolean overridden; public static class Builder { @@ -94,7 +90,6 @@ public class BeanMappingMethod extends MappingMethod { private NullValueMappingStrategyPrism nullValueMappingStrategy; private SelectionParameters selectionParameters; private final Set existingVariableNames = new HashSet(); - private NestedTargetObjects nestedTargetObjects; private Map> methodMappings; private SingleMappingByTargetPropertyNameFunction singleMapping; @@ -105,28 +100,21 @@ public class BeanMappingMethod extends MappingMethod { public Builder souceMethod(SourceMethod sourceMethod) { singleMapping = new SourceMethodSingleMapping( sourceMethod ); - return setupMethodWithMapping( sourceMethod, sourceMethod.getMappingOptions().getMappings() ); + return setupMethodWithMapping( sourceMethod ); } - public Builder forgedMethod(Method sourceMethod) { + public Builder forgedMethod(Method method ) { singleMapping = new EmptySingleMapping(); - return setupMethodWithMapping( sourceMethod, Collections.>emptyMap() ); + return setupMethodWithMapping( method ); } - private Builder setupMethodWithMapping(Method sourceMethod, Map> mappings) { + private Builder setupMethodWithMapping(Method sourceMethod) { this.method = sourceMethod; - this.methodMappings = mappings; + this.methodMappings = sourceMethod.getMappingOptions().getMappings(); CollectionMappingStrategyPrism cms = sourceMethod.getMapperConfiguration().getCollectionMappingStrategy(); Map accessors = method.getResultType().getPropertyWriteAccessors( cms ); this.targetProperties = accessors.keySet(); - this.nestedTargetObjects = new NestedTargetObjects.Builder() - .existingVariableNames( existingVariableNames ) - .mappings( mappings ) - .mappingBuilderContext( ctx ) - .sourceMethod( method ) - .build(); - this.unprocessedTargetProperties = new LinkedHashMap( accessors ); for ( Parameter sourceParameter : method.getSourceParameters() ) { unprocessedSourceParameters.add( sourceParameter ); @@ -147,16 +135,19 @@ public class BeanMappingMethod extends MappingMethod { public BeanMappingMethod build() { // map properties with mapping - boolean mappingErrorOccured = handleDefinedSourceMappings(); + boolean mappingErrorOccured = handleDefinedMappings(); if ( mappingErrorOccured ) { return null; } - // map properties without a mapping - applyPropertyNameBasedMapping(); + if ( !method.getMappingOptions().isRestrictToDefinedMappings() ) { - // map parameters without a mapping - applyParameterNameBasedMapping(); + // map properties without a mapping + applyPropertyNameBasedMapping(); + + // map parameters without a mapping + applyParameterNameBasedMapping(); + } // report errors on unmapped properties reportErrorForUnmappedTargetPropertiesIfRequired(); @@ -223,10 +214,8 @@ public class BeanMappingMethod extends MappingMethod { factoryMethod, mapNullToDefault, resultType, - existingVariableNames, beforeMappingMethods, - afterMappingMethods, - nestedTargetObjects + afterMappingMethods ); } @@ -277,146 +266,30 @@ public class BeanMappingMethod extends MappingMethod { * It is furthermore checked whether the given mappings are correct. When an error occurs, the method continues * in search of more problems. */ - private boolean handleDefinedSourceMappings() { + private boolean handleDefinedMappings() { boolean errorOccurred = false; Set handledTargets = new HashSet(); + if ( method.getMappingOptions().hasNestedTargetReferences() ) { + handleDefinedNestedTargetMapping( handledTargets ); + } + for ( Map.Entry> entry : methodMappings.entrySet() ) { for ( Mapping mapping : entry.getValue() ) { - - PropertyMapping propertyMapping = null; - - TargetReference targetRef = mapping.getTargetReference(); - String resultPropertyName = null; - if ( targetRef.isValid() ) { - resultPropertyName = first( targetRef.getPropertyEntries() ).getName(); - } - - if ( !unprocessedTargetProperties.containsKey( resultPropertyName ) ) { - boolean hasReadAccessor = - method.getResultType().getPropertyReadAccessors().containsKey( mapping.getTargetName() ); - - if ( hasReadAccessor ) { - if ( !mapping.isIgnored() ) { - ctx.getMessager().printMessage( - method.getExecutable(), - mapping.getMirror(), - mapping.getSourceAnnotationValue(), - Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE, - mapping.getTargetName() ); + TargetReference targetReference = mapping.getTargetReference(); + if ( targetReference.isValid() ) { + if ( !handledTargets.contains( first( targetReference.getPropertyEntries() ).getFullName() ) ) { + if ( handleDefinedMapping( mapping, handledTargets ) ) { errorOccurred = true; } } - else { - ctx.getMessager().printMessage( - method.getExecutable(), - mapping.getMirror(), - mapping.getSourceAnnotationValue(), - Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE, - mapping.getTargetName() ); - errorOccurred = true; - } - - continue; } - - PropertyEntry targetProperty = last( targetRef.getPropertyEntries() ); - - // unknown properties given via dependsOn()? - for ( String dependency : mapping.getDependsOn() ) { - if ( !targetProperties.contains( dependency ) ) { - ctx.getMessager().printMessage( - method.getExecutable(), - mapping.getMirror(), - mapping.getDependsOnAnnotationValue(), - Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON, - dependency - ); - errorOccurred = true; - } - } - - // check the mapping options - // its an ignored property mapping - if ( mapping.isIgnored() ) { - propertyMapping = null; - handledTargets.add( mapping.getTargetName() ); - } - - // its a plain-old property mapping - else if ( mapping.getSourceName() != null ) { - - // determine source parameter - SourceReference sourceRef = mapping.getSourceReference(); - if ( sourceRef.isValid() ) { - - // targetProperty == null can occur: we arrived here because we want as many errors - // as possible before we stop analysing - propertyMapping = new PropertyMappingBuilder() - .mappingContext( ctx ) - .sourceMethod( method ) - .targetProperty( targetProperty ) - .targetPropertyName( mapping.getTargetName() ) - .sourceReference( sourceRef ) - .selectionParameters( mapping.getSelectionParameters() ) - .formattingParameters( mapping.getFormattingParameters() ) - .existingVariableNames( existingVariableNames ) - .dependsOn( mapping.getDependsOn() ) - .defaultValue( mapping.getDefaultValue() ) - .localTargetVarName( nestedTargetObjects.getLocalVariableName( targetRef ) ) - .build(); - handledTargets.add( resultPropertyName ); - unprocessedSourceParameters.remove( sourceRef.getParameter() ); - } - else { - errorOccurred = true; - continue; - } - } - - // its a constant - else if ( mapping.getConstant() != null ) { - - propertyMapping = new ConstantMappingBuilder() - .mappingContext( ctx ) - .sourceMethod( method ) - .constantExpression( "\"" + mapping.getConstant() + "\"" ) - .targetProperty( targetProperty ) - .targetPropertyName( mapping.getTargetName() ) - .formattingParameters( mapping.getFormattingParameters() ) - .selectionParameters( mapping.getSelectionParameters() ) - .existingVariableNames( existingVariableNames ) - .dependsOn( mapping.getDependsOn() ) - .localTargetVarName( nestedTargetObjects.getLocalVariableName( targetRef ) ) - .build(); - handledTargets.add( mapping.getTargetName() ); - } - - // its an expression - else if ( mapping.getJavaExpression() != null ) { - - propertyMapping = new JavaExpressionMappingBuilder() - .mappingContext( ctx ) - .sourceMethod( method ) - .javaExpression( mapping.getJavaExpression() ) - .existingVariableNames( existingVariableNames ) - .targetProperty( targetProperty ) - .targetPropertyName( mapping.getTargetName() ) - .dependsOn( mapping.getDependsOn() ) - .localTargetVarName( nestedTargetObjects.getLocalVariableName( targetRef ) ) - .build(); - handledTargets.add( mapping.getTargetName() ); - } - - // remaining are the mappings without a 'source' so, 'only' a date format or qualifiers - - if ( propertyMapping != null ) { - propertyMappings.add( propertyMapping ); + else if ( reportErrorOnTargetObject( mapping ) ) { + errorOccurred = true; } } } - for ( String handledTarget : handledTargets ) { // In order to avoid: "Unknown property foo in return type" in case of duplicate // target mappings @@ -426,6 +299,170 @@ public class BeanMappingMethod extends MappingMethod { return errorOccurred; } + private void handleDefinedNestedTargetMapping(Set handledTargets) { + + Map optionsByNestedTarget = + method.getMappingOptions().groupByPoppedTargetReferences(); + for ( Entry entryByTP : optionsByNestedTarget.entrySet() ) { + + Map optionsBySourceParam = entryByTP.getValue().groupBySourceParameter(); + for ( Entry entryByParam : optionsBySourceParam.entrySet() ) { + + SourceReference sourceRef = new SourceReference.BuilderFromProperty() + .sourceParameter( entryByParam.getKey() ) + .name( entryByTP.getKey().getName() ) + .build(); + + PropertyMapping propertyMapping = new PropertyMappingBuilder() + .mappingContext( ctx ) + .sourceMethod( method ) + .targetProperty( entryByTP.getKey() ) + .targetPropertyName( entryByTP.getKey().getName() ) + .sourceReference( sourceRef ) + .existingVariableNames( existingVariableNames ) + .dependsOn( entryByParam.getValue().collectNestedDependsOn() ) + .forgeMethodWithMappingOptions( entryByParam.getValue() ) + .build(); + unprocessedSourceParameters.remove( sourceRef.getParameter() ); + + if ( propertyMapping != null ) { + propertyMappings.add( propertyMapping ); + } + } + handledTargets.add( entryByTP.getKey().getName() ); + } + + } + + private boolean handleDefinedMapping(Mapping mapping, Set handledTargets) { + + boolean errorOccured = false; + + PropertyMapping propertyMapping = null; + + TargetReference targetRef = mapping.getTargetReference(); + PropertyEntry targetProperty = first( targetRef.getPropertyEntries() ); + + // unknown properties given via dependsOn()? + for ( String dependency : mapping.getDependsOn() ) { + if ( !targetProperties.contains( dependency ) ) { + ctx.getMessager().printMessage( + method.getExecutable(), + mapping.getMirror(), + mapping.getDependsOnAnnotationValue(), + Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON, + dependency + ); + errorOccured = true; + } + } + + // check the mapping options + // its an ignored property mapping + if ( mapping.isIgnored() ) { + propertyMapping = null; + handledTargets.add( mapping.getTargetName() ); + } + + // its a plain-old property mapping + else if ( mapping.getSourceName() != null ) { + + // determine source parameter + SourceReference sourceRef = mapping.getSourceReference(); + if ( sourceRef.isValid() ) { + + // targetProperty == null can occur: we arrived here because we want as many errors + // as possible before we stop analysing + propertyMapping = new PropertyMappingBuilder() + .mappingContext( ctx ) + .sourceMethod( method ) + .targetProperty( targetProperty ) + .targetPropertyName( mapping.getTargetName() ) + .sourceReference( sourceRef ) + .selectionParameters( mapping.getSelectionParameters() ) + .formattingParameters( mapping.getFormattingParameters() ) + .existingVariableNames( existingVariableNames ) + .dependsOn( mapping.getDependsOn() ) + .defaultValue( mapping.getDefaultValue() ) + .build(); + handledTargets.add( targetProperty.getName() ); + unprocessedSourceParameters.remove( sourceRef.getParameter() ); + } + else { + errorOccured = true; + } + } + + // its a constant + else if ( mapping.getConstant() != null ) { + + propertyMapping = new ConstantMappingBuilder() + .mappingContext( ctx ) + .sourceMethod( method ) + .constantExpression( "\"" + mapping.getConstant() + "\"" ) + .targetProperty( targetProperty ) + .targetPropertyName( mapping.getTargetName() ) + .formattingParameters( mapping.getFormattingParameters() ) + .selectionParameters( mapping.getSelectionParameters() ) + .existingVariableNames( existingVariableNames ) + .dependsOn( mapping.getDependsOn() ) + .build(); + handledTargets.add( mapping.getTargetName() ); + } + + // its an expression + else if ( mapping.getJavaExpression() != null ) { + + propertyMapping = new JavaExpressionMappingBuilder() + .mappingContext( ctx ) + .sourceMethod( method ) + .javaExpression( mapping.getJavaExpression() ) + .existingVariableNames( existingVariableNames ) + .targetProperty( targetProperty ) + .targetPropertyName( mapping.getTargetName() ) + .dependsOn( mapping.getDependsOn() ) + .build(); + handledTargets.add( mapping.getTargetName() ); + } + + // remaining are the mappings without a 'source' so, 'only' a date format or qualifiers + if ( propertyMapping != null ) { + propertyMappings.add( propertyMapping ); + } + + return errorOccured; + } + + private boolean reportErrorOnTargetObject(Mapping mapping) { + + boolean errorOccurred = false; + + boolean hasReadAccessor + = method.getResultType().getPropertyReadAccessors().containsKey( mapping.getTargetName() ); + + if ( hasReadAccessor ) { + if ( !mapping.isIgnored() ) { + ctx.getMessager().printMessage( + method.getExecutable(), + mapping.getMirror(), + mapping.getSourceAnnotationValue(), + Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE, + mapping.getTargetName() ); + errorOccurred = true; + } + } + else { + ctx.getMessager().printMessage( + method.getExecutable(), + mapping.getMirror(), + mapping.getSourceAnnotationValue(), + Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE, + mapping.getTargetName() ); + errorOccurred = true; + } + return errorOccurred; + } + /** * Iterates over all target properties and all source parameters. *

@@ -574,9 +611,10 @@ public class BeanMappingMethod extends MappingMethod { //we handle forged methods differently than the usual source ones. in if ( method instanceof ForgedMethod ) { - if ( targetProperties.isEmpty() || !unprocessedTargetProperties.isEmpty() ) { - ForgedMethod forgedMethod = (ForgedMethod) this.method; + ForgedMethod forgedMethod = (ForgedMethod) this.method; + if ( forgedMethod.isAutoMapping() + && ( targetProperties.isEmpty() || !unprocessedTargetProperties.isEmpty() ) ) { if ( forgedMethod.getHistory() == null ) { Type sourceType = this.method.getParameters().get( 0 ).getType(); @@ -628,11 +666,19 @@ public class BeanMappingMethod extends MappingMethod { MethodReference factoryMethod, boolean mapNullToDefault, Type resultType, - Collection existingVariableNames, List beforeMappingReferences, - List afterMappingReferences, - NestedTargetObjects nestedTargetObjects) { - super( method, existingVariableNames, beforeMappingReferences, afterMappingReferences ); + List afterMappingReferences) { + super( + method, + null, + factoryMethod, + mapNullToDefault, + null, + beforeMappingReferences, + afterMappingReferences, + null + ); + this.propertyMappings = propertyMappings; // intialize constant mappings as all mappings, but take out the ones that can be contributed to a @@ -652,7 +698,6 @@ public class BeanMappingMethod extends MappingMethod { this.factoryMethod = factoryMethod; this.mapNullToDefault = mapNullToDefault; this.resultType = resultType; - this.nestedTargetObjects = nestedTargetObjects.init( this.getResultName() ); this.overridden = method.overridesMethod(); } @@ -668,18 +713,12 @@ public class BeanMappingMethod extends MappingMethod { return mappingsByParameter; } - public Set getLocalVariablesToCreate() { - return this.nestedTargetObjects.localVariables; - } - - public Set getNestedLocalVariableAssignments() { - return this.nestedTargetObjects.nestedAssignments; - } - + @Override public boolean isMapNullToDefault() { return mapNullToDefault; } + @Override public boolean isOverridden() { return overridden; } @@ -701,7 +740,6 @@ public class BeanMappingMethod extends MappingMethod { for ( PropertyMapping propertyMapping : propertyMappings ) { types.addAll( propertyMapping.getImportTypes() ); } - types.addAll( nestedTargetObjects.getImportTypes() ); return types; } @@ -727,148 +765,45 @@ public class BeanMappingMethod extends MappingMethod { return sourceParameters; } + @Override public MethodReference getFactoryMethod() { return this.factoryMethod; } - private static class NestedTargetObjects { + @Override + public Type getResultElementType() { + return null; + } - private final Set localVariables; - private final Set nestedAssignments; - // local variable names indexed by fullname - private final Map localVariableNames; + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ( ( getResultType() == null ) ? 0 : getResultType().hashCode() ); + return result; + } - private Set getImportTypes() { - Set importedTypes = new HashSet(); - for ( LocalVariable localVariableToCreate : localVariables ) { - importedTypes.add( localVariableToCreate.getType() ); - } - return importedTypes; + @Override + public boolean equals(Object obj) { + + if ( this == obj ) { + return true; + } + if ( obj == null || getClass() != obj.getClass() ) { + return false; } - private static class Builder { + BeanMappingMethod that = (BeanMappingMethod) obj; - private Map> mappings; - private Set existingVariableNames; - private MappingBuilderContext ctx; - private Method method; - - private Builder mappings(Map> mappings) { - this.mappings = mappings; - return this; - } - - private Builder existingVariableNames(Set existingVariableNames) { - this.existingVariableNames = existingVariableNames; - return this; - } - - private Builder mappingBuilderContext(MappingBuilderContext ctx) { - this.ctx = ctx; - return this; - } - - private Builder sourceMethod(Method method) { - this.method = method; - return this; - } - - private NestedTargetObjects build() { - - Map uniquePropertyEntries = new HashMap(); - Map localVariableNames = new HashMap(); - Set localVariables = new HashSet(); - - // colllect unique local variables - for ( Map.Entry> mapping : mappings.entrySet() ) { - - TargetReference targetRef = first( mapping.getValue() ).getTargetReference(); - List propertyEntries = targetRef.getPropertyEntries(); - - for ( int i = 0; i < propertyEntries.size() - 1; i++ ) { - PropertyEntry entry = propertyEntries.get( i ); - uniquePropertyEntries.put( entry.getFullName(), entry ); - } - - } - - // assign variable names and create local variables. - for ( PropertyEntry propertyEntry : uniquePropertyEntries.values() ) { - String name = getSaveVariableName( propertyEntry.getName(), existingVariableNames ); - existingVariableNames.add( name ); - Type type = propertyEntry.getType(); - Assignment factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, type, null ); - localVariables.add( new LocalVariable( name, type, factoryMethod ) ); - localVariableNames.put( propertyEntry.getFullName(), name ); - } - - - Set relations = new HashSet(); - - // create relations branches (getter / setter) -- need to go to postInit() - for ( Map.Entry> mapping : mappings.entrySet() ) { - - TargetReference targetRef = first( mapping.getValue() ).getTargetReference(); - List propertyEntries = targetRef.getPropertyEntries(); - - for ( int i = 0; i < propertyEntries.size() - 1; i++ ) { - // null means the targetBean is the methods targetBean. Needs to be set later. - String targetBean = null; - if ( i > 0 ) { - PropertyEntry targetPropertyEntry = propertyEntries.get( i - 1 ); - targetBean = localVariableNames.get( targetPropertyEntry.getFullName() ); - } - PropertyEntry sourcePropertyEntry = propertyEntries.get( i ); - String targetAccessor = sourcePropertyEntry.getWriteAccessor().getSimpleName().toString(); - String sourceRef = localVariableNames.get( sourcePropertyEntry.getFullName() ); - relations.add( new NestedLocalVariableAssignment( - targetBean, - targetAccessor, - sourceRef, - sourcePropertyEntry.getWriteAccessor().getExecutable() == null - ) ); - } - } - - return new NestedTargetObjects( localVariables, localVariableNames, relations ); - } + if ( !super.equals( obj ) ) { + return false; } - - private NestedTargetObjects(Set localVariables, Map localVariableNames, - Set relations) { - this.localVariables = localVariables; - this.localVariableNames = localVariableNames; - this.nestedAssignments = relations; - } - - /** - * returns a local vaRriable name when relevant (so when not the 'parameter' targetBean should be used) - * - * @param targetRef - * - * @return generated local variable name - */ - private String getLocalVariableName(TargetReference targetRef) { - String result = null; - List propertyEntries = targetRef.getPropertyEntries(); - if ( propertyEntries.size() > 1 ) { - result = localVariableNames.get( propertyEntries.get( propertyEntries.size() - 2 ).getFullName() ); - } - return result; - } - - private NestedTargetObjects init(String targetBeanName) { - for ( NestedLocalVariableAssignment nestedAssignment : nestedAssignments ) { - if ( nestedAssignment.getTargetBean() == null ) { - nestedAssignment.setTargetBean( targetBeanName ); - } - } - return this; - } - + return propertyMappings != null ? propertyMappings.equals( that.propertyMappings ) : + that.propertyMappings == null; } private interface SingleMappingByTargetPropertyNameFunction { + Mapping getSingleMappingByTargetPropertyName(String targetPropertyName); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LocalVariable.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LocalVariable.java deleted file mode 100644 index ebe29e1cc..000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LocalVariable.java +++ /dev/null @@ -1,88 +0,0 @@ -/** - * 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.model; - -import java.util.Set; -import org.mapstruct.ap.internal.model.assignment.Assignment; -import org.mapstruct.ap.internal.model.common.ModelElement; -import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.util.Collections; - -/** - * Local variable used in a mapping method. - * - * @author Sjaak Derksen - */ -public class LocalVariable extends ModelElement { - - private final String name; - private final Type type; - private final Assignment factoryMethod; - - public LocalVariable( String name, Type type, Assignment factoryMethod ) { - this.name = name; - this.type = type; - this.factoryMethod = factoryMethod; - } - - public String getName() { - return name; - } - - public Type getType() { - return type; - } - - public Assignment getFactoryMethod() { - return factoryMethod; - } - - @Override - public String toString() { - return type.toString() + " " + name; - } - - @Override - public Set getImportTypes() { - return Collections.asSet( type ); - } - - @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 LocalVariable other = (LocalVariable) 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/internal/model/NestedLocalVariableAssignment.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedLocalVariableAssignment.java deleted file mode 100644 index 90d7a0844..000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedLocalVariableAssignment.java +++ /dev/null @@ -1,137 +0,0 @@ -/** - * 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.model; - -import static java.util.Collections.emptySet; - -import java.util.Set; - -import org.mapstruct.ap.internal.model.common.ModelElement; -import org.mapstruct.ap.internal.model.common.Type; - -/** - * - * @author Sjaak Derksen - * - * In the process of creating target mappings, MapStruct creates local variables. - *

- * {@code Chart chart = new Chart();
- *
- *       Label label = new Label();
- *       Artist artist = new Artist();
- *       Song song = new Song();
- *       Studio studio = new Studio();
- *       artist.setLabel( label ); // NestedLocalVariableAssignment
- *       song.setArtist( artist ); // NestedLocalVariableAssignment
- *       label.setStudio( studio );// NestedLocalVariableAssignment
- *       chart.setSong( song ); // NestedLocalVariableAssignment
- * }
- *
- */ -public class NestedLocalVariableAssignment extends ModelElement { - - private String targetBean; - private final String setterName; - private final String sourceRef; - private final boolean fieldAssignment; - - public NestedLocalVariableAssignment(String targetBean, String setterName, String sourceRef, - boolean fieldAssignment) { - this.targetBean = targetBean; - this.setterName = setterName; - this.sourceRef = sourceRef; - this.fieldAssignment = fieldAssignment; - } - - /** - * - * @return the targetBean on which the property setter with {@link setterName} is called - */ - public String getTargetBean() { - return targetBean; - } - - /** - * - * @param targetBean the targetBean on which the property setter with {@link setterName} is called - */ - public void setTargetBean(String targetBean) { - this.targetBean = targetBean; - } - - /** - * - * @return the name of the setter (target accessor for the property) - */ - public String getSetterName() { - return setterName; - } - - /** - * - * @return source reference, to be used a argument in the setter. - */ - public String getSourceRef() { - return sourceRef; - } - - @Override - public Set getImportTypes() { - return emptySet(); - } - - /** - * @return {@code true}if field assignment should be used, {@code false} otherwise - */ - public boolean isFieldAssignment() { - return fieldAssignment; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 97 * hash + ( this.targetBean != null ? this.targetBean.hashCode() : 0 ); - hash = 97 * hash + ( this.sourceRef != null ? this.sourceRef.hashCode() : 0 ); - hash = 97 * hash + ( this.fieldAssignment ? 1 : 0 ); - return hash; - } - - @Override - public boolean equals(Object obj) { - if ( this == obj ) { - return true; - } - if ( obj == null ) { - return false; - } - if ( getClass() != obj.getClass() ) { - return false; - } - final NestedLocalVariableAssignment other = (NestedLocalVariableAssignment) obj; - if ( ( this.targetBean == null ) ? ( other.targetBean != null ) : - !this.targetBean.equals( other.targetBean ) ) { - return false; - } - if ( ( this.sourceRef == null ) ? ( other.sourceRef != null ) : !this.sourceRef.equals( other.sourceRef ) ) { - return false; - } - return this.fieldAssignment == other.fieldAssignment; - } - -} 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 080631ab6..3f1435395 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 @@ -40,6 +40,7 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.ForgedMethodHistory; import org.mapstruct.ap.internal.model.source.FormattingParameters; +import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.PropertyEntry; import org.mapstruct.ap.internal.model.source.SelectionParameters; @@ -70,7 +71,6 @@ public class PropertyMapping extends ModelElement { private final String sourceBeanName; private final String targetWriteAccessorName; private final ValueProvider targetReadAccessorProvider; - private final String localTargetVarName; private final Type targetType; private final Assignment assignment; private final List dependsOn; @@ -107,7 +107,6 @@ public class PropertyMapping extends ModelElement { protected Accessor targetReadAccessor; protected TargetWriteAccessorType targetReadAccessorType; protected String targetPropertyName; - protected String localTargetVarName; protected List dependsOn; protected Set existingVariableNames; @@ -147,11 +146,6 @@ public class PropertyMapping extends ModelElement { return (T) this; } - public T localTargetVarName(String localTargetVarName) { - this.localTargetVarName = localTargetVarName; - return (T) this; - } - private Type determineTargetType() { // This is a bean mapping method, so we know the result is a declared type DeclaredType resultType = (DeclaredType) method.getResultType().getTypeMirror(); @@ -198,6 +192,7 @@ public class PropertyMapping extends ModelElement { private SourceRHS rightHandSide; private FormattingParameters formattingParameters; private SelectionParameters selectionParameters; + private MappingOptions forgeMethodWithMappingOptions; PropertyMappingBuilder() { super( PropertyMappingBuilder.class ); @@ -223,6 +218,11 @@ public class PropertyMapping extends ModelElement { return this; } + public PropertyMappingBuilder forgeMethodWithMappingOptions(MappingOptions mappingOptions) { + this.forgeMethodWithMappingOptions = mappingOptions; + return this; + } + public PropertyMapping build() { // handle source this.rightHandSide = getSourceRHS( sourceReference ); @@ -238,15 +238,19 @@ public class PropertyMapping extends ModelElement { preferUpdateMethods = method.getMappingTargetParameter() != null; } - Assignment assignment = ctx.getMappingResolver().getTargetAssignment( - method, - targetType, - targetPropertyName, - formattingParameters, - selectionParameters, - rightHandSide, - preferUpdateMethods - ); + // forge a method instead of resolving one when there are mapping options. + Assignment assignment = null; + if ( forgeMethodWithMappingOptions == null ) { + assignment = ctx.getMappingResolver().getTargetAssignment( + method, + targetType, + targetPropertyName, + formattingParameters, + selectionParameters, + rightHandSide, + preferUpdateMethods + ); + } Type sourceType = rightHandSide.getSourceType(); // No mapping found. Try to forge a mapping @@ -296,7 +300,6 @@ public class PropertyMapping extends ModelElement { targetWriteAccessor.getSimpleName().toString(), ValueProvider.of( targetReadAccessor ), targetType, - localTargetVarName, assignment, dependsOn, getDefaultValueAssignment( assignment ) @@ -604,25 +607,41 @@ public class PropertyMapping extends ModelElement { String name = getName( sourceType, targetType ); name = Strings.getSaveVariableName( name, ctx.getNamesOfMappingsToGenerate() ); + List parameters = new ArrayList( method.getContextParameters() ); Type returnType; - if ( method.isUpdateMethod() ) { + // there's only one case for forging a method with mapping options: nested target properties. + // they should always forge an update method + if ( method.isUpdateMethod() || forgeMethodWithMappingOptions != null ) { parameters.add( Parameter.forForgedMappingTarget( targetType ) ); returnType = ctx.getTypeFactory().createVoidType(); } else { returnType = targetType; } - ForgedMethod forgedMethod = new ForgedMethod( - name, - sourceType, - returnType, - method.getMapperConfiguration(), - method.getExecutable(), - parameters, - getForgedMethodHistory( sourceRHS ) - ); - + ForgedMethod forgedMethod; + if ( forgeMethodWithMappingOptions != null ) { + forgedMethod = new ForgedMethod( + name, + sourceType, + returnType, + method.getMapperConfiguration(), + method.getExecutable(), + parameters, + forgeMethodWithMappingOptions + ); + } + else { + forgedMethod = new ForgedMethod( + name, + sourceType, + returnType, + method.getMapperConfiguration(), + method.getExecutable(), + parameters, + getForgedMethodHistory( sourceRHS ) + ); + } return createForgedBeanAssignment( sourceRHS, forgedMethod ); } @@ -768,7 +787,6 @@ public class PropertyMapping extends ModelElement { targetWriteAccessor.getSimpleName().toString(), ValueProvider.of( targetReadAccessor ), targetType, - localTargetVarName, assignment, dependsOn, null @@ -833,7 +851,6 @@ public class PropertyMapping extends ModelElement { targetWriteAccessor.getSimpleName().toString(), ValueProvider.of( targetReadAccessor ), targetType, - localTargetVarName, assignment, dependsOn, null @@ -845,15 +862,15 @@ public class PropertyMapping extends ModelElement { // Constructor for creating mappings of constant expressions. private PropertyMapping(String name, String targetWriteAccessorName, ValueProvider targetReadAccessorProvider, - Type targetType, String localTargetVarName, Assignment propertyAssignment, + Type targetType, Assignment propertyAssignment, List dependsOn, Assignment defaultValueAssignment ) { this( name, null, targetWriteAccessorName, targetReadAccessorProvider, - targetType, localTargetVarName, propertyAssignment, dependsOn, defaultValueAssignment + targetType, propertyAssignment, dependsOn, defaultValueAssignment ); } private PropertyMapping(String name, String sourceBeanName, String targetWriteAccessorName, - ValueProvider targetReadAccessorProvider, Type targetType, String localTargetVarName, + ValueProvider targetReadAccessorProvider, Type targetType, Assignment assignment, List dependsOn, Assignment defaultValueAssignment) { this.name = name; @@ -861,7 +878,6 @@ public class PropertyMapping extends ModelElement { this.targetWriteAccessorName = targetWriteAccessorName; this.targetReadAccessorProvider = targetReadAccessorProvider; this.targetType = targetType; - this.localTargetVarName = localTargetVarName; this.assignment = assignment; this.dependsOn = dependsOn != null ? dependsOn : Collections.emptyList(); @@ -891,10 +907,6 @@ public class PropertyMapping extends ModelElement { return targetType; } - public String getLocalTargetVarName() { - return localTargetVarName; - } - public Assignment getAssignment() { return assignment; } @@ -920,6 +932,36 @@ public class PropertyMapping extends ModelElement { return dependsOn; } + @Override + public int hashCode() { + int hash = 5; + hash = 67 * hash + (this.name != null ? this.name.hashCode() : 0); + hash = 67 * hash + (this.targetType != null ? this.targetType.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + final PropertyMapping other = (PropertyMapping) obj; + if ( (this.name == null) ? (other.name != null) : !this.name.equals( other.name ) ) { + return false; + } + if ( this.targetType != other.targetType && (this.targetType == null || + !this.targetType.equals( other.targetType )) ) { + return false; + } + return true; + } + @Override public String toString() { return "PropertyMapping {" diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java index 3c6c0377c..f555cfa46 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java @@ -48,6 +48,8 @@ public class ForgedMethod implements Method { private final List sourceParameters; private final List contextParameters; private final Parameter mappingTargetParameter; + private final MappingOptions mappingOptions; + private boolean autoMapping; /** * Creates a new forged method with the given name. @@ -61,7 +63,43 @@ public class ForgedMethod implements Method { */ public ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration, ExecutableElement positionHintElement, List additionalParameters) { - this( name, sourceType, returnType, mapperConfiguration, positionHintElement, additionalParameters, null ); + this( name, sourceType, returnType, mapperConfiguration, positionHintElement, additionalParameters, null, + false, MappingOptions.empty() ); + } + + /** + * Creates a new forged method with the given name with history + * + * @param name the (unique name) for this method + * @param sourceType the source type + * @param returnType the return type. + * @param mapperConfiguration the mapper configuration + * @param positionHintElement element used to for reference to the position in the source file. + * @param additionalParameters additional parameters to add to the forged method + * @param history a parent forged method if this is a forged method within a forged method + */ + public ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration, + ExecutableElement positionHintElement, List additionalParameters, ForgedMethodHistory history) { + this( name, sourceType, returnType, mapperConfiguration, positionHintElement, additionalParameters, history, + true, MappingOptions.empty() ); + } + + /** + * Creates a new forged method with the given name with mapping options + * + * @param name the (unique name) for this method + * @param sourceType the source type + * @param returnType the return type. + * @param mapperConfiguration the mapper configuration + * @param positionHintElement element used to for reference to the position in the source file. + * @param additionalParameters additional parameters to add to the forged method + * @param mappingOptions with mapping options + */ + public ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration, + ExecutableElement positionHintElement, List additionalParameters, + MappingOptions mappingOptions) { + this( name, sourceType, returnType, mapperConfiguration, positionHintElement, additionalParameters, null, + false, mappingOptions ); } /** @@ -74,15 +112,17 @@ public class ForgedMethod implements Method { * @param positionHintElement element used to for reference to the position in the source file. * @param additionalParameters additional parameters to add to the forged method * @param history a parent forged method if this is a forged method within a forged method + * @param mappingOptions the mapping options for this method */ - public ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration, + private ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration, ExecutableElement positionHintElement, List additionalParameters, - ForgedMethodHistory history) { + ForgedMethodHistory history, boolean autoMapping, MappingOptions mappingOptions) { String sourceParamName = Strings.decapitalize( sourceType.getName() ); String sourceParamSafeName = Strings.getSaveVariableName( sourceParamName ); this.parameters = new ArrayList( 1 + additionalParameters.size() ); - this.parameters.add( new Parameter( sourceParamSafeName, sourceType ) ); + Parameter sourceParameter = new Parameter( sourceParamSafeName, sourceType ); + this.parameters.add( sourceParameter ); this.parameters.addAll( additionalParameters ); this.sourceParameters = Parameter.getSourceParameters( parameters ); this.contextParameters = Parameter.getContextParameters( parameters ); @@ -94,6 +134,9 @@ public class ForgedMethod implements Method { this.mapperConfiguration = mapperConfiguration; this.positionHintElement = positionHintElement; this.history = history; + this.autoMapping = autoMapping; + this.mappingOptions = mappingOptions; + this.mappingOptions.initWithParameter( sourceParameter ); } /** @@ -108,10 +151,12 @@ public class ForgedMethod implements Method { this.mapperConfiguration = forgedMethod.mapperConfiguration; this.positionHintElement = forgedMethod.positionHintElement; this.history = forgedMethod.history; + this.autoMapping = forgedMethod.autoMapping; this.sourceParameters = Parameter.getSourceParameters( parameters ); this.contextParameters = Parameter.getContextParameters( parameters ); this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); + this.mappingOptions = forgedMethod.mappingOptions; this.name = name; } @@ -195,6 +240,10 @@ public class ForgedMethod implements Method { return history; } + public boolean isAutoMapping() { + return autoMapping; + } + public void addThrownTypes(List thrownTypesToAdd) { for ( Type thrownType : thrownTypesToAdd ) { // make sure there are no duplicates coming from the keyAssignment thrown types. @@ -278,6 +327,11 @@ public class ForgedMethod implements Method { return false; } + @Override + public MappingOptions getMappingOptions() { + return mappingOptions; + } + @Override public boolean equals(Object o) { if ( this == o ) { @@ -306,5 +360,4 @@ public class ForgedMethod implements Method { result = 31 * result + ( name != null ? name.hashCode() : 0 ); return result; } - } 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 bfcabfaca..e3517ea01 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 @@ -25,7 +25,6 @@ import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; - import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ElementKind; @@ -33,13 +32,14 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; - +import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; import org.mapstruct.ap.internal.prism.MappingPrism; import org.mapstruct.ap.internal.prism.MappingsPrism; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.Strings; /** * Represents a property mapping as configured via {@code @Mapping}. @@ -184,6 +184,24 @@ public class Mapping { this.dependsOn = dependsOn; } + private Mapping( Mapping mapping, TargetReference targetReference ) { + this.sourceName = mapping.sourceName; + this.constant = mapping.constant; + this.javaExpression = mapping.javaExpression; + this.targetName = Strings.join( targetReference.getElementNames(), "." ); + 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 = mapping.sourceReference; + this.targetReference = targetReference; + } + private static String getExpression(MappingPrism mappingPrism, ExecutableElement element, FormattingMessager messager) { if ( mappingPrism.expression().isEmpty() ) { @@ -228,6 +246,21 @@ public class Mapping { } } + /** + * Initializes the mapping with a new source parameter. + * + * @param sourceParameter + */ + public void init( Parameter sourceParameter ) { + if ( sourceReference != null ) { + SourceReference oldSourceReference = sourceReference; + sourceReference = new SourceReference.BuilderFromSourceReference() + .sourceParameter( sourceParameter ) + .sourceReference( oldSourceReference ) + .build(); + } + } + /** * Returns the complete source name of this mapping, either a qualified (e.g. {@code parameter1.foo}) or * unqualified (e.g. {@code foo}) property reference. @@ -290,6 +323,16 @@ public class Mapping { return targetReference; } + public Mapping popTargetReference() { + if ( targetReference != null ) { + TargetReference newTargetReference = targetReference.pop(); + if (newTargetReference != null ) { + return new Mapping(this, newTargetReference ); + } + } + 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 bf08ab517..91412342d 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 @@ -21,6 +21,7 @@ package org.mapstruct.ap.internal.model.source; import static org.mapstruct.ap.internal.util.Collections.first; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -28,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.util.FormattingMessager; @@ -43,14 +45,37 @@ public class MappingOptions { private BeanMapping beanMapping; private List valueMappings; private boolean fullyInitialized; + private final boolean restrictToDefinedMappings; public MappingOptions(Map> mappings, IterableMapping iterableMapping, MapMapping mapMapping, - BeanMapping beanMapping, List valueMappings ) { + BeanMapping beanMapping, List valueMappings, boolean restrictToDefinedMappings ) { this.mappings = mappings; this.iterableMapping = iterableMapping; this.mapMapping = mapMapping; this.beanMapping = beanMapping; this.valueMappings = valueMappings; + this.restrictToDefinedMappings = restrictToDefinedMappings; + } + + /** + * creates empty mapping options + * + * @return empty mapping options + */ + public static MappingOptions empty() { + return new MappingOptions( Collections.>emptyMap(), null, null, null, + Collections.emptyList(), false ); + } + + /** + * creates mapping options with only regular mappings + * + * @param mappings regular mappings to add + * @return MappingOptions with only regular mappings + */ + public static MappingOptions forMappingsOnly( Map> mappings ) { + return new MappingOptions( mappings, null, null, null, Collections.emptyList(), true ); + } /** @@ -61,6 +86,146 @@ public class MappingOptions { return mappings; } + /** + * The target references are popped. The MappingOptions are keyed on the unique first entries of the + * target references. + * + * So, take + * + * targetReference 1: propertyEntryX.propertyEntryX1.propertyEntryX1a + * targetReference 2: propertyEntryX.propertyEntryX2 + * targetReference 3: propertyEntryY.propertyY1 + * targetReference 4: propertyEntryZ + * + * will be popped and grouped into entries: + * + * propertyEntryX - MappingOptions ( targetReference1: propertyEntryX1.propertyEntryX1a, + * targetReference2: propertyEntryX2 ) + * propertyEntryY - MappingOptions ( targetReference1: propertyEntryY1 ) + * + * 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. + * + * @return See above + */ + public Map groupByPoppedTargetReferences() { + + // group all mappings based on the top level name before popping + Map> mappingsKeyedByProperty = new HashMap>(); + for ( List mapping : mappings.values() ) { + 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 ); + } + } + + // now group them into mapping options + Map result = new HashMap(); + for ( Map.Entry> mappingKeyedByProperty : mappingsKeyedByProperty.entrySet() ) { + Map> newEntries = new HashMap>(); + for ( Mapping newEntry : mappingKeyedByProperty.getValue() ) { + newEntries.put( newEntry.getTargetName(), Arrays.asList( newEntry ) ); + } + result.put( mappingKeyedByProperty.getKey(), forMappingsOnly( newEntries ) ); + } + return result; + } + + /** + * Check there are nested target references for this mapping options. + * + * @return boolean, true if there are nested target references + */ + public boolean hasNestedTargetReferences() { + for ( List mappingList : mappings.values() ) { + for ( Mapping mapping : mappingList ) { + TargetReference targetReference = mapping.getTargetReference(); + if ( targetReference.isValid() && targetReference.getPropertyEntries().size() > 1 ) { + return true; + } + } + } + return false; + } + + /** + * + * @return all dependencies to other properties the contained mappings are dependent on + */ + public List collectNestedDependsOn() { + + List nestedDependsOn = new ArrayList(); + for ( List mappingList : mappings.values() ) { + for ( Mapping mapping : mappingList ) { + nestedDependsOn.addAll( mapping.getDependsOn() ); + } + } + return nestedDependsOn; + } + + /** + * Splits the MappingOptions into possibly more MappingOptions based on each source method parameter type. + * + * 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 + * + * @return the split mapping options. + * + */ + public Map groupBySourceParameter() { + + Map> mappingsKeyedByParameterType = new HashMap>(); + for ( List mappingList : mappings.values() ) { + for ( Mapping mapping : mappingList ) { + if ( mapping.getSourceReference() != null && mapping.getSourceReference().isValid() ) { + Parameter parameter = mapping.getSourceReference().getParameter(); + if ( !mappingsKeyedByParameterType.containsKey( parameter ) ) { + mappingsKeyedByParameterType.put( parameter, new ArrayList() ); + } + mappingsKeyedByParameterType.get( parameter ).add( mapping ); + } + } + } + + Map result = new HashMap(); + for ( Map.Entry> entry : mappingsKeyedByParameterType.entrySet() ) { + result.put( entry.getKey(), MappingOptions.forMappingsOnly( groupByTargetName( entry.getValue() ) ) ); + } + return result; + } + + private Map> groupByTargetName( List mappingList ) { + Map> result = new HashMap>(); + for ( Mapping mapping : mappingList ) { + if ( !result.containsKey( mapping.getTargetName() ) ) { + result.put( mapping.getTargetName(), new ArrayList() ); + } + result.get( mapping.getTargetName() ).add( mapping ); + } + return result; + } + + /** + * Initializes the underlying mappings with a new property. Specifically used in in combination with forged methods + * where the new parameter name needs to be established at a later moment. + * + * @param sourceParameter the new source parameter + */ + public void initWithParameter( Parameter sourceParameter ) { + for ( List mappingList : mappings.values() ) { + for ( Mapping mapping : mappingList ) { + mapping.init( sourceParameter ); + } + } + } + public IterableMapping getIterableMapping() { return iterableMapping; } @@ -223,4 +388,8 @@ public class MappingOptions { } + public boolean isRestrictToDefinedMappings() { + return restrictToDefinedMappings; + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java index ab0159f87..2e77eabc8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java @@ -189,4 +189,10 @@ public interface Method { * {@code @MappingTarget}. */ boolean isUpdateMethod(); + + /** + * + * @return the mapping options for this method + */ + MappingOptions getMappingOptions(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/PropertyEntry.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/PropertyEntry.java index e86b836ac..b88b3cbe0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/PropertyEntry.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/PropertyEntry.java @@ -29,14 +29,6 @@ import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor; /** * A PropertyEntry contains information on the name, readAccessor (for source), readAccessor and writeAccessor * (for targets) and return type of a property. - * - * It can be shared between several nested properties. For example - * - * bean - * - * nestedMapping1 = "x.y1.z1" nestedMapping2 = "x.y1.z2" nestedMapping3 = "x.y2.z3" - * - * has property entries x, y1, y2, z1, z2, z3. */ public class PropertyEntry { @@ -115,4 +107,39 @@ public class PropertyEntry { return Strings.join( Arrays.asList( fullName ), "." ); } + public PropertyEntry pop() { + if ( fullName.length > 1 ) { + String[] newFullName = Arrays.copyOfRange( fullName, 1, fullName.length ); + return new PropertyEntry(newFullName, readAccessor, writeAccessor, presenceChecker, type ); + } + else { + return null; + } + } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + Arrays.deepHashCode( this.fullName ); + return hash; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + final PropertyEntry other = (PropertyEntry) obj; + if ( !Arrays.deepEquals( this.fullName, other.fullName ) ) { + return false; + } + return true; + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java index bd2da2b46..5f07249bf 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java @@ -185,7 +185,7 @@ public class SourceMethod implements Method { public SourceMethod build() { MappingOptions mappingOptions = - new MappingOptions( mappings, iterableMapping, mapMapping, beanMapping, valueMappings ); + new MappingOptions( mappings, iterableMapping, mapMapping, beanMapping, valueMappings, false ); SourceMethod sourceMethod = new SourceMethod( declaringMapper, 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 200355fa0..1cd4063dc 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 @@ -248,6 +248,29 @@ public class SourceReference { } } + /** + * Builds a {@link SourceReference} from a property. + */ + public static class BuilderFromSourceReference { + + private Parameter sourceParameter; + private SourceReference sourceReference; + + public BuilderFromSourceReference sourceReference(SourceReference sourceReference) { + this.sourceReference = sourceReference; + return this; + } + + public BuilderFromSourceReference sourceParameter(Parameter sourceParameter) { + this.sourceParameter = sourceParameter; + return this; + } + + public SourceReference build() { + return new SourceReference( sourceParameter, sourceReference.propertyEntries, true ); + } + } + private SourceReference(Parameter sourceParameter, List sourcePropertyEntries, boolean isValid) { this.parameter = sourceParameter; this.propertyEntries = sourcePropertyEntries; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/TargetReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/TargetReference.java index 8f52179e5..a6259ea9b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/TargetReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/TargetReference.java @@ -202,14 +202,30 @@ public class TargetReference { } public List getElementNames() { - List elementName = new ArrayList(); + List elementNames = new ArrayList(); if ( parameter != null ) { // only relevant for source properties - elementName.add( parameter.getName() ); + elementNames.add( parameter.getName() ); } for ( PropertyEntry propertyEntry : propertyEntries ) { - elementName.add( propertyEntry.getName() ); + elementNames.add( propertyEntry.getName() ); + } + return elementNames; + } + + public TargetReference pop() { + if ( propertyEntries.size() > 1 ) { + List newPropertyEntries = new ArrayList( propertyEntries.size() - 1 ); + for ( PropertyEntry propertyEntry : propertyEntries ) { + PropertyEntry newPropertyEntry = propertyEntry.pop(); + if ( newPropertyEntry != null ) { + newPropertyEntries.add( newPropertyEntry ); + } + } + return new TargetReference( null, newPropertyEntries, isValid ); + } + else { + return null; } - return elementName; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java index 9be91a3f9..4ba6e3c3a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java @@ -33,6 +33,7 @@ import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.util.MapperConfiguration; import org.mapstruct.ap.internal.util.Strings; @@ -276,4 +277,9 @@ public abstract class BuiltInMethod implements Method { public boolean isUpdateMethod() { return getMappingTargetParameter() != null; } + + @Override + public MappingOptions getMappingOptions() { + return MappingOptions.empty(); + } } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 86c8a6f65..2d9a738f5 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -42,7 +42,6 @@ - <@nestedTargetObjects/> <#if (sourceParameters?size > 1)> <#list sourceParametersExcludingPrimitives as sourceParam> <#if (propertyMappingsByParameter[sourceParam.name]?size > 0)> @@ -88,12 +87,4 @@ <#if exceptionType_has_next>, <#t> - -<#macro nestedTargetObjects> - <#list localVariablesToCreate as localVariable> - <@includeModel object=localVariable/> = <#if localVariable.factoryMethod??><@includeModel object=localVariable.factoryMethod targetType=localVariable.type/><#else>new <@includeModel object=localVariable.type/>(); - - <#list nestedLocalVariableAssignments as nestedLocalVariableAssignment> - <@includeModel object=nestedLocalVariableAssignment/> - \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/LocalVariable.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/LocalVariable.ftl deleted file mode 100644 index 45ed5e134..000000000 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/LocalVariable.ftl +++ /dev/null @@ -1,21 +0,0 @@ -<#-- - - 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. - ---> -<@includeModel object=type/> ${name} \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedLocalVariableAssignment.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedLocalVariableAssignment.ftl deleted file mode 100644 index d8fd9783b..000000000 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedLocalVariableAssignment.ftl +++ /dev/null @@ -1,22 +0,0 @@ -<#-- - - 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. - ---> -<#import "macro/CommonMacros.ftl" as lib> -${targetBean}.${setterName}<@lib.handleWrite>${sourceRef}; diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl index 477f7d7a6..a62300877 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl @@ -18,20 +18,10 @@ limitations under the License. --> -<#if localTargetVarName??> -<@includeModel object=assignment - targetBeanName=localTargetVarName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=targetReadAccessorName - targetWriteAccessorName=targetWriteAccessorName - targetType=targetType - defaultValueAssignment=defaultValueAssignment /> -<#else> <@includeModel object=assignment targetBeanName=ext.targetBeanName existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=targetReadAccessorName targetWriteAccessorName=targetWriteAccessorName targetType=targetType - defaultValueAssignment=defaultValueAssignment /> - + defaultValueAssignment=defaultValueAssignment /> \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtist.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtist.java index 7e81a10e9..966deb68c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtist.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtist.java @@ -18,8 +18,8 @@ */ package org.mapstruct.ap.test.nestedtargetproperties; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import org.mapstruct.InheritInverseConfiguration; @@ -65,10 +65,10 @@ public abstract class ChartEntryToArtist { protected List mapPosition(Integer in) { if ( in != null ) { - return Arrays.asList( in ); + return new ArrayList( Arrays.asList( in ) ); } else { - return Collections.emptyList(); + return new ArrayList(); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistUpdate.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistUpdate.java new file mode 100644 index 000000000..3d5859ffb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistUpdate.java @@ -0,0 +1,70 @@ +/** + * 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.test.nestedtargetproperties; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntry; +import org.mapstruct.ap.test.nestedsourceproperties.source.Chart; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper( nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS ) +public abstract class ChartEntryToArtistUpdate { + + public static final ChartEntryToArtistUpdate MAPPER = Mappers.getMapper( ChartEntryToArtistUpdate.class ); + + @Mappings({ + @Mapping(target = "type", ignore = true), + @Mapping(target = "name", source = "chartName"), + @Mapping(target = "song.title", source = "songTitle" ), + @Mapping(target = "song.artist.name", source = "artistName" ), + @Mapping(target = "song.artist.label.studio.name", source = "recordedAt"), + @Mapping(target = "song.artist.label.studio.city", source = "city" ), + @Mapping(target = "song.positions", source = "position" ) + }) + public abstract void map(ChartEntry chartEntry, @MappingTarget Chart chart ); + + protected List mapPosition(Integer in) { + if ( in != null ) { + return Arrays.asList( in ); + } + else { + return Collections.emptyList(); + } + } + + protected Integer mapPosition(List in) { + if ( in != null && !in.isEmpty() ) { + return in.get( 0 ); + } + else { + return null; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/FishTankMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/FishTankMapper.java new file mode 100644 index 000000000..6e434804f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/FishTankMapper.java @@ -0,0 +1,38 @@ +/** + * 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.test.nestedtargetproperties; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.nestedtargetproperties._target.FishTankDto; +import org.mapstruct.ap.test.nestedtargetproperties.source.FishTank; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface FishTankMapper { + + FishTankMapper INSTANCE = Mappers.getMapper( FishTankMapper.class ); + + @Mapping(target = "fish.kind", source = "fish.type") + FishTankDto map( FishTank source ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedTargetPropertiesTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedTargetPropertiesTest.java index 4f4666350..8d05cc652 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedTargetPropertiesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedTargetPropertiesTest.java @@ -27,6 +27,12 @@ import org.mapstruct.ap.test.nestedsourceproperties.source.Chart; import org.mapstruct.ap.test.nestedsourceproperties.source.Label; import org.mapstruct.ap.test.nestedsourceproperties.source.Song; import org.mapstruct.ap.test.nestedsourceproperties.source.Studio; +import org.mapstruct.ap.test.nestedtargetproperties._target.FishDto; +import org.mapstruct.ap.test.nestedtargetproperties._target.FishTankDto; +import org.mapstruct.ap.test.nestedtargetproperties._target.WaterPlantDto; +import org.mapstruct.ap.test.nestedtargetproperties.source.Fish; +import org.mapstruct.ap.test.nestedtargetproperties.source.FishTank; +import org.mapstruct.ap.test.nestedtargetproperties.source.WaterPlant; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; @@ -35,13 +41,28 @@ import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; * * @author Sjaak Derksen */ -@WithClasses({Song.class, Artist.class, Chart.class, Label.class, Studio.class, ChartEntry.class}) +@WithClasses({ + Song.class, + Artist.class, + Chart.class, + Label.class, + Studio.class, + ChartEntry.class, + FishDto.class, + FishTankDto.class, + WaterPlantDto.class, + Fish.class, + FishTank.class, + WaterPlant.class, + ChartEntryToArtist.class, + ChartEntryToArtistUpdate.class, + FishTankMapper.class +}) @IssueKey("389") @RunWith(AnnotationProcessorTestRunner.class) public class NestedTargetPropertiesTest { @Test - @WithClasses({ChartEntryToArtist.class}) public void shouldMapNestedTarget() { ChartEntry chartEntry = new ChartEntry(); @@ -71,7 +92,6 @@ public class NestedTargetPropertiesTest { } @Test - @WithClasses({ChartEntryToArtist.class}) public void shouldMapNestedComposedTarget() { ChartEntry chartEntry1 = new ChartEntry(); @@ -103,7 +123,6 @@ public class NestedTargetPropertiesTest { } @Test - @WithClasses({ChartEntryToArtist.class}) public void shouldReverseNestedTarget() { ChartEntry chartEntry = new ChartEntry(); @@ -125,4 +144,64 @@ public class NestedTargetPropertiesTest { assertThat( result.getRecordedAt() ).isEqualTo( "Live, First Avenue, Minneapolis" ); assertThat( result.getSongTitle() ).isEqualTo( "Purple Rain" ); } + + @Test + public void shouldMapNestedTargetWitUpdate() { + + ChartEntry chartEntry = new ChartEntry(); + chartEntry.setArtistName( "Prince" ); + chartEntry.setChartName( "US Billboard Hot Rock Songs" ); + chartEntry.setCity( "Minneapolis" ); + chartEntry.setPosition( 1 ); + chartEntry.setRecordedAt( "Live, First Avenue, Minneapolis" ); + chartEntry.setSongTitle( null ); + + Chart result = new Chart(); + result.setSong( new Song() ); + result.getSong().setTitle( "Raspberry Beret" ); + + ChartEntryToArtistUpdate.MAPPER.map( chartEntry, result ); + + assertThat( result.getName() ).isEqualTo( "US Billboard Hot Rock Songs" ); + assertThat( result.getSong() ).isNotNull(); + assertThat( result.getSong().getArtist() ).isNotNull(); + assertThat( result.getSong().getTitle() ).isEqualTo( "Raspberry Beret" ); + assertThat( result.getSong().getArtist().getName() ).isEqualTo( "Prince" ); + assertThat( result.getSong().getArtist().getLabel() ).isNotNull(); + assertThat( result.getSong().getArtist().getLabel().getStudio() ).isNotNull(); + assertThat( result.getSong().getArtist().getLabel().getStudio().getName() ) + .isEqualTo( "Live, First Avenue, Minneapolis" ); + assertThat( result.getSong().getArtist().getLabel().getStudio().getCity() ) + .isEqualTo( "Minneapolis" ); + assertThat( result.getSong().getPositions() ).hasSize( 1 ); + assertThat( result.getSong().getPositions().get( 0 ) ).isEqualTo( 1 ); + + } + + @Test + public void automappingAndTargetNestingDemonstrator() { + + FishTank source = new FishTank(); + source.setName( "MyLittleFishTank" ); + Fish fish = new Fish(); + fish.setType( "Carp" ); + WaterPlant waterplant = new WaterPlant(); + waterplant.setKind( "Water Hyacinth" ); + source.setFish( fish ); + source.setPlant( waterplant ); + + FishTankDto target = FishTankMapper.INSTANCE.map( source ); + + // the nested property generates a method fishTankToFishDto(FishTank fishTank, FishDto mappingTarget) + // when name based mapping continues MapStruct searches for a property called `name` in fishTank (type + // 'FishTank'. If it is there, it should most cerntainly not be mapped to a mappingTarget of type 'FishDto' + assertThat( target.getFish() ).isNotNull(); + assertThat( target.getFish().getKind() ).isEqualTo( "Carp" ); + assertThat( target.getFish().getName() ).isNull(); + + // automapping takes care of mapping property "waterPlant". + assertThat( target.getPlant() ).isNotNull(); + assertThat( target.getPlant().getKind() ).isEqualTo( "Water Hyacinth" ); + } + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/_target/FishDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/_target/FishDto.java new file mode 100644 index 000000000..c4dee0269 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/_target/FishDto.java @@ -0,0 +1,47 @@ +/** + * 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.test.nestedtargetproperties._target; + +/** + * + * @author Sjaak Derksen + */ +public class FishDto { + + private String kind; + + // make sure that mapping on name does not happen based on name mapping + private String name; + + public String getKind() { + return kind; + } + + public void setKind(String kind) { + this.kind = kind; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/_target/FishTankDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/_target/FishTankDto.java new file mode 100644 index 000000000..081b567bb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/_target/FishTankDto.java @@ -0,0 +1,46 @@ +/** + * 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.test.nestedtargetproperties._target; + +/** + * + * @author Sjaak Derksen + */ +public class FishTankDto { + + private FishDto fish; + private WaterPlantDto plant; + + public FishDto getFish() { + return fish; + } + + public void setFish(FishDto fish) { + this.fish = fish; + } + + public WaterPlantDto getPlant() { + return plant; + } + + public void setPlant(WaterPlantDto plant) { + this.plant = plant; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/_target/WaterPlantDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/_target/WaterPlantDto.java new file mode 100644 index 000000000..1118fd7fa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/_target/WaterPlantDto.java @@ -0,0 +1,37 @@ +/** + * 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.test.nestedtargetproperties._target; + +/** + * + * @author Sjaak Derksen + */ +public class WaterPlantDto { + + private String kind; + + public String getKind() { + return kind; + } + + public void setKind(String kind) { + this.kind = kind; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/source/Fish.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/source/Fish.java new file mode 100644 index 000000000..97b29c6e7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/source/Fish.java @@ -0,0 +1,37 @@ +/** + * 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.test.nestedtargetproperties.source; + +/** + * + * @author Sjaak Derksen + */ +public class Fish { + + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/source/FishTank.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/source/FishTank.java new file mode 100644 index 000000000..d250898c4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/source/FishTank.java @@ -0,0 +1,55 @@ +/** + * 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.test.nestedtargetproperties.source; + +/** + * + * @author Sjaak Derksen + */ +public class FishTank { + + private Fish fish; + private WaterPlant plant; + private String name; + + public Fish getFish() { + return fish; + } + + public void setFish(Fish fish) { + this.fish = fish; + } + + public WaterPlant getPlant() { + return plant; + } + + public void setPlant(WaterPlant plant) { + this.plant = plant; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/source/WaterPlant.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/source/WaterPlant.java new file mode 100644 index 000000000..138686c9c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/source/WaterPlant.java @@ -0,0 +1,37 @@ +/** + * 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.test.nestedtargetproperties.source; + +/** + * + * @author Sjaak Derksen + */ +public class WaterPlant { + + private String kind; + + public String getKind() { + return kind; + } + + public void setKind(String kind) { + this.kind = kind; + } + +}