From 70ba92b229415df9e2b3585e72f7ba8645a9a69c Mon Sep 17 00:00:00 2001 From: sjaakd Date: Mon, 1 Jun 2015 19:06:06 +0200 Subject: [PATCH] #389 Nested target properties --- .../mapstruct-reference-guide.asciidoc | 4 +- .../org/mapstruct/itest/simple/Target.java | 2 +- .../ap/internal/model/BeanMappingMethod.java | 258 +++++++++++++--- .../ap/internal/model/LocalVariable.java | 88 ++++++ .../mapstruct/ap/internal/model/Mapper.java | 3 - .../ap/internal/model/MappingMethod.java | 5 + .../model/NestedLocalVariableAssignment.java | 123 ++++++++ .../model/NestedPropertyMappingMethod.java | 32 +- .../ap/internal/model/PropertyMapping.java | 47 ++- .../ap/internal/model/source/Mapping.java | 25 +- .../internal/model/source/MappingOptions.java | 39 ++- .../internal/model/source/PropertyEntry.java | 117 ++++++++ .../internal/model/source/SourceMethod.java | 3 +- .../model/source/SourceReference.java | 50 +--- .../model/source/TargetReference.java | 215 ++++++++++++++ .../ap/internal/util/Collections.java | 4 + .../mapstruct/ap/internal/util/Message.java | 4 +- .../internal/writer/FreeMarkerWritable.java | 2 + .../ap/internal/model/BeanMappingMethod.ftl | 9 + .../ap/internal/model/LocalVariable.ftl | 21 ++ .../model/NestedLocalVariableAssignment.ftl | 21 ++ .../model/NestedPropertyMappingMethod.ftl | 6 +- .../ap/internal/model/PropertyMapping.ftl | 10 + .../ArtistToChartEntry.java | 9 - .../ArtistToChartEntryComposedReverse.java | 62 ++++ .../ArtistToChartEntryConfig.java | 41 +++ .../ArtistToChartEntryReverse.java | 51 ++++ .../ArtistToChartEntryUpdateReverse.java | 54 ++++ .../ArtistToChartEntryWithConfigReverse.java | 50 ++++ .../ArtistToChartEntryWithFactoryReverse.java | 53 ++++ .../ArtistToChartEntryWithIgnoresReverse.java | 54 ++++ .../ArtistToChartEntryWithMappingReverse.java | 72 +++++ .../NestedSourcePropertiesTest.java | 14 - .../ReversingNestedSourcePropertiesTest.java | 281 ++++++++++++++++++ .../_target/BaseChartEntry.java | 53 ++++ .../_target/ChartEntryComposed.java | 71 +++++ .../_target/ChartEntryLabel.java | 54 ++++ .../_target/ChartEntryWithBase.java | 53 ++++ .../_target/ChartEntryWithMapping.java | 80 +++++ .../source/SourceDtoFactory.java | 70 +++++ .../ChartEntryToArtist.java | 83 ++++++ .../NestedTargetPropertiesTest.java | 128 ++++++++ 42 files changed, 2271 insertions(+), 150 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/LocalVariable.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/NestedLocalVariableAssignment.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/PropertyEntry.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/source/TargetReference.java create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/LocalVariable.ftl create mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/NestedLocalVariableAssignment.ftl create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryComposedReverse.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryConfig.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryReverse.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryUpdateReverse.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithConfigReverse.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithFactoryReverse.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithIgnoresReverse.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithMappingReverse.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ReversingNestedSourcePropertiesTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/BaseChartEntry.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryComposed.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryLabel.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryWithBase.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryWithMapping.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/source/SourceDtoFactory.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtist.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedTargetPropertiesTest.java diff --git a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc index b2c91d712..ba7e4d902 100644 --- a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc +++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc @@ -346,7 +346,7 @@ Mapping methods with several source parameters will return `null` in case all th [[nested-mappings]] === Nested mappings -MapStruct will handle nested mappings, by means of the `.` notation: +MapStruct will handle nested mappings (in source), by means of the `.` notation: .Mapping method with several source parameters ==== @@ -1681,7 +1681,7 @@ Methods that are considered for inverse inheritance need to be defined in the cu If multiple methods qualify, the method from which to inherit the configuration from needs to be specified using the `name` property like this: `@InheritInverseConfiguration(name = "carToDto")`. -Nested properties are excluded (silently ignored) from reverse mapping. The same holds true for expressions and constants. Reverse mapping will take place automatically when the source property name and target property name are identical. Otherwise, `@Mapping` should specify both the target name and source name. In all cases, a suitable mapping method needs to be in place for the reverse mapping. +Expressions and constants are excluded (silently ignored). Reverse mapping of nested source properties is experimental as of the 1.1.0.Beta2. Reverse mapping will take place automatically when the source property name and target property name are identical. Otherwise, `@Mapping` should specify both the target name and source name. In all cases, a suitable mapping method needs to be in place for the reverse mapping. [[shared-configurations]] === Shared configurations diff --git a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/Target.java b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/Target.java index ecb9c68e6..6a811c20c 100644 --- a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/Target.java +++ b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/Target.java @@ -80,7 +80,7 @@ public class Target { this.someType = someType; } - public String getFormNested() { + public String getFromNested() { return fromNested; } 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 5c535b29b..67cca6fbc 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 @@ -39,21 +39,27 @@ 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; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder; import org.mapstruct.ap.internal.model.source.Mapping; +import org.mapstruct.ap.internal.model.source.PropertyEntry; import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SourceMethod; +import org.mapstruct.ap.internal.model.source.TargetReference; import org.mapstruct.ap.internal.model.source.SourceReference; import org.mapstruct.ap.internal.option.ReportingPolicy; import org.mapstruct.ap.internal.prism.BeanMappingPrism; import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; +import static org.mapstruct.ap.internal.util.Collections.first; +import static org.mapstruct.ap.internal.util.Collections.last; import org.mapstruct.ap.internal.util.MapperConfiguration; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; +import static org.mapstruct.ap.internal.util.Strings.getSaveVariableName; /** * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one bean type to another, optionally @@ -69,6 +75,7 @@ public class BeanMappingMethod extends MappingMethod { private final MethodReference factoryMethod; private final boolean mapNullToDefault; private final Type resultType; + private final NestedTargetObjects nestedTargetObjects; public static class Builder { @@ -81,6 +88,7 @@ public class BeanMappingMethod extends MappingMethod { private NullValueMappingStrategyPrism nullValueMappingStrategy; private SelectionParameters selectionParameters; private final Set existingVariableNames = new HashSet(); + private NestedTargetObjects nestedTargetObjects; public Builder mappingContext(MappingBuilderContext mappingContext) { this.ctx = mappingContext; @@ -92,6 +100,14 @@ public class BeanMappingMethod extends MappingMethod { CollectionMappingStrategyPrism cms = sourceMethod.getMapperConfiguration().getCollectionMappingStrategy(); Map accessors = method.getResultType().getPropertyWriteAccessors( cms ); this.targetProperties = accessors.keySet(); + + this.nestedTargetObjects = new NestedTargetObjects.Builder() + .existingVariableNames( existingVariableNames ) + .mappings( method.getMappingOptions().getMappings() ) + .mappingBuilderContext( ctx ) + .sourceMethod( method ) + .build(); + this.unprocessedTargetProperties = new LinkedHashMap( accessors ); for ( Parameter sourceParameter : method.getSourceParameters() ) { unprocessedSourceParameters.add( sourceParameter ); @@ -170,7 +186,8 @@ public class BeanMappingMethod extends MappingMethod { resultType, existingVariableNames, beforeMappingMethods, - afterMappingMethods + afterMappingMethods, + nestedTargetObjects ); } @@ -230,8 +247,8 @@ public class BeanMappingMethod extends MappingMethod { * in search of more problems. */ private boolean handleDefinedSourceMappings() { - boolean errorOccurred = false; + boolean errorOccurred = false; Set handledTargets = new HashSet(); for ( Map.Entry> entry : method.getMappingOptions().getMappings().entrySet() ) { @@ -239,22 +256,28 @@ public class BeanMappingMethod extends MappingMethod { PropertyMapping propertyMapping = null; - // fetch the target property - ExecutableElement targetWriteAccessor = unprocessedTargetProperties.get( mapping.getTargetName() ); - if ( targetWriteAccessor == null ) { - boolean hasReadAccessor = + 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() ); - ctx.getMessager().printMessage( - method.getExecutable(), + ctx.getMessager().printMessage( method.getExecutable(), mapping.getMirror(), mapping.getSourceAnnotationValue(), - hasReadAccessor ? Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RETURNTYPE : - Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RETURNTYPE, + hasReadAccessor ? Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE : + 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 ) ) { @@ -283,62 +306,60 @@ public class BeanMappingMethod extends MappingMethod { SourceReference sourceRef = mapping.getSourceReference(); if ( sourceRef.isValid() ) { - if ( targetWriteAccessor != null ) { - - // 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 ) - .targetWriteAccessor( targetWriteAccessor ) - .targetReadAccessor( getTargetPropertyReadAccessor( mapping.getTargetName() ) ) - .targetPropertyName( mapping.getTargetName() ) - .sourceReference( sourceRef ) - .selectionParameters( mapping.getSelectionParameters() ) - .formattingParameters( mapping.getFormattingParameters() ) - .existingVariableNames( existingVariableNames ) - .dependsOn( mapping.getDependsOn() ) - .defaultValue( mapping.getDefaultValue() ) - .build(); - handledTargets.add( mapping.getTargetName() ); - unprocessedSourceParameters.remove( sourceRef.getParameter() ); - } + // 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 && targetWriteAccessor != null ) { + else if ( mapping.getConstant() != null ) { propertyMapping = new ConstantMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .constantExpression( "\"" + mapping.getConstant() + "\"" ) - .targetWriteAccessor( targetWriteAccessor ) - .targetReadAccessor( getTargetPropertyReadAccessor( mapping.getTargetName() ) ) + .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 && targetWriteAccessor != null ) { + else if ( mapping.getJavaExpression() != null ) { propertyMapping = new JavaExpressionMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .javaExpression( mapping.getJavaExpression() ) .existingVariableNames( existingVariableNames ) - .targetWriteAccessor( targetWriteAccessor ) - .targetReadAccessor( targetWriteAccessor ) + .targetProperty( targetProperty ) .targetPropertyName( mapping.getTargetName() ) .dependsOn( mapping.getDependsOn() ) + .localTargetVarName( nestedTargetObjects.getLocalVariableName( targetRef ) ) .build(); handledTargets.add( mapping.getTargetName() ); } @@ -367,12 +388,13 @@ public class BeanMappingMethod extends MappingMethod { * the set of remaining target properties. */ private void applyPropertyNameBasedMapping() { - Iterator> targetPropertiesIterator = + + Iterator> targetPropertyEntriesIterator = unprocessedTargetProperties.entrySet().iterator(); - while ( targetPropertiesIterator.hasNext() ) { + while ( targetPropertyEntriesIterator.hasNext() ) { - Entry targetProperty = targetPropertiesIterator.next(); + Entry targetProperty = targetPropertyEntriesIterator.next(); String targetPropertyName = targetProperty.getKey(); PropertyMapping propertyMapping = null; @@ -441,19 +463,19 @@ public class BeanMappingMethod extends MappingMethod { if ( propertyMapping != null ) { propertyMappings.add( propertyMapping ); - targetPropertiesIterator.remove(); + targetPropertyEntriesIterator.remove(); } } } private void applyParameterNameBasedMapping() { - Iterator> targetProperties = + Iterator> targetPropertyEntriesIterator = unprocessedTargetProperties.entrySet().iterator(); - while ( targetProperties.hasNext() ) { + while ( targetPropertyEntriesIterator.hasNext() ) { - Entry targetProperty = targetProperties.next(); + Entry targetProperty = targetPropertyEntriesIterator.next(); Iterator sourceParameters = unprocessedSourceParameters.iterator(); @@ -482,7 +504,7 @@ public class BeanMappingMethod extends MappingMethod { .build(); propertyMappings.add( propertyMapping ); - targetProperties.remove(); + targetPropertyEntriesIterator.remove(); sourceParameters.remove(); } } @@ -545,7 +567,8 @@ public class BeanMappingMethod extends MappingMethod { Type resultType, Collection existingVariableNames, List beforeMappingReferences, - List afterMappingReferences) { + List afterMappingReferences, + NestedTargetObjects nestedTargetObjects ) { super( method, existingVariableNames, beforeMappingReferences, afterMappingReferences ); this.propertyMappings = propertyMappings; @@ -566,6 +589,7 @@ public class BeanMappingMethod extends MappingMethod { this.factoryMethod = factoryMethod; this.mapNullToDefault = mapNullToDefault; this.resultType = resultType; + this.nestedTargetObjects = nestedTargetObjects.init( this.getResultName() ); } public List getPropertyMappings() { @@ -580,6 +604,14 @@ public class BeanMappingMethod extends MappingMethod { return mappingsByParameter; } + public Set getLocalVariablesToCreate() { + return this.nestedTargetObjects.localVariables; + } + + public Set getNestedLocalVariableAssignments() { + return this.nestedTargetObjects.nestedAssignments; + } + public boolean isMapNullToDefault() { return mapNullToDefault; } @@ -601,6 +633,7 @@ public class BeanMappingMethod extends MappingMethod { for ( PropertyMapping propertyMapping : propertyMappings ) { types.addAll( propertyMapping.getImportTypes() ); } + types.addAll( nestedTargetObjects.getImportTypes() ); return types; } @@ -630,4 +663,139 @@ public class BeanMappingMethod extends MappingMethod { public MethodReference getFactoryMethod() { return this.factoryMethod; } + + + private static class NestedTargetObjects { + + private final Set localVariables; + private final Set nestedAssignments; + // local variable names indexed by fullname + private final Map localVariableNames; + + + private Set getImportTypes() { + Set importedTypes = new HashSet(); + for ( LocalVariable localVariableToCreate : localVariables ) { + importedTypes.add( localVariableToCreate.getType() ); + } + return importedTypes; + } + + private static class Builder { + + private Map> mappings; + private Set existingVariableNames; + private MappingBuilderContext ctx; + private SourceMethod 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(SourceMethod 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 ) ); + } + } + + return new NestedTargetObjects( localVariables, localVariableNames, relations ); + } + } + + 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 targefetRef + * @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; + } + + } + } + 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 new file mode 100644 index 000000000..ffaa92f1f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LocalVariable.java @@ -0,0 +1,88 @@ +/** + * Copyright 2012-2016 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/Mapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java index 7c270c1bf..23bd82a73 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java @@ -191,9 +191,6 @@ public class Mapper extends GeneratedType { this.decorator = null; } - /** - * Checks if the mapper has a custom implementation that is a custom suffix of an explicit destination package. - */ public boolean hasCustomImplementation() { return customImplName || customPackage; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java index a8fa9381d..055698105 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java @@ -55,6 +55,11 @@ public abstract class MappingMethod extends ModelElement { /** * constructor to be overloaded when local variable names are required prior to calling this constructor. (e.g. for * property mappings). It is supposed to be initialized with at least the parameter names. + * + * @param method the method for which this mapping is applicable + * @param existingVariableNames set of already assigned variable names + * @param beforeMappingReferences all life cycle methods to be called prior to carrying out mapping + * @param afterMappingReferences all life cycle methods to be called after carrying out mapping */ protected MappingMethod(Method method, Collection existingVariableNames, List beforeMappingReferences, 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 new file mode 100644 index 000000000..aceea84a8 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedLocalVariableAssignment.java @@ -0,0 +1,123 @@ +/** + * Copyright 2012-2016 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; + + public NestedLocalVariableAssignment(String targetBean, String setterName, String sourceRef) { + this.targetBean = targetBean; + this.setterName = setterName; + this.sourceRef = sourceRef; + } + + /** + * + * @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(); + } + + @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); + 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 true; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java index 9e4f3c172..f946d51ca 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java @@ -25,7 +25,7 @@ import java.util.Set; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.model.source.SourceReference.PropertyEntry; +import org.mapstruct.ap.internal.model.source.PropertyEntry; import org.mapstruct.ap.internal.util.Strings; /** @@ -140,23 +140,41 @@ public class NestedPropertyMappingMethod extends MappingMethod { return true; } - public static class SafePropertyEntry extends PropertyEntry { + public static class SafePropertyEntry { private final String safeName; + private final String readAccessorName; + private final String presenceCheckerName; + private final Type type; - public SafePropertyEntry( PropertyEntry entry, String safeName ) { - super( entry.getName(), entry.getAccessor(), entry.getPresenceChecker(), entry.getType() ); + public SafePropertyEntry(PropertyEntry entry, String safeName) { this.safeName = safeName; + this.readAccessorName = entry.getReadAccessor().getSimpleName().toString(); + if ( entry.getPresenceChecker() != null ) { + this.presenceCheckerName = entry.getPresenceChecker().getSimpleName().toString(); + } + else { + this.presenceCheckerName = null; + } + this.type = entry.getType(); } - @Override public String getName() { return safeName; } public String getAccessorName() { - return getAccessor().getSimpleName().toString(); + return readAccessorName; } - } + public String getPresenceCheckerName() { + return presenceCheckerName; + } + + public Type getType() { + return type; + } + + } } + 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 42f2ac0d3..1f2446045 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 @@ -50,9 +50,10 @@ import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.FormattingParameters; import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SourceMethod; +import org.mapstruct.ap.internal.model.source.PropertyEntry; import org.mapstruct.ap.internal.model.source.SourceReference; -import org.mapstruct.ap.internal.model.source.SourceReference.PropertyEntry; import static org.mapstruct.ap.internal.util.Collections.first; +import static org.mapstruct.ap.internal.util.Collections.last; import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.MapperConfiguration; import org.mapstruct.ap.internal.util.Message; @@ -72,6 +73,7 @@ public class PropertyMapping extends ModelElement { private final String sourceBeanName; private final String targetWriteAccessorName; private final String targetReadAccessorName; + private final String localTargetVarName; private final Type targetType; private final Assignment assignment; private final List dependsOn; @@ -106,6 +108,7 @@ public class PropertyMapping extends ModelElement { protected Type targetType; protected ExecutableElement targetReadAccessor; protected String targetPropertyName; + protected String localTargetVarName; protected List dependsOn; protected Set existingVariableNames; @@ -120,6 +123,14 @@ public class PropertyMapping extends ModelElement { return (T) this; } + public T targetProperty( PropertyEntry targetProp ) { + this.targetReadAccessor = targetProp.getReadAccessor(); + this.targetWriteAccessor = targetProp.getWriteAccessor(); + this.targetType = targetProp.getType(); + this.targetWriteAccessorType = TargetWriteAccessorType.of( targetWriteAccessor ); + return (T) this; + } + public T targetReadAccessor(ExecutableElement targetReadAccessor) { this.targetReadAccessor = targetReadAccessor; return (T) this; @@ -133,6 +144,11 @@ 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(); @@ -264,6 +280,7 @@ public class PropertyMapping extends ModelElement { targetWriteAccessor.getSimpleName().toString(), targetReadAccessor != null ? targetReadAccessor.getSimpleName().toString() : null, targetType, + localTargetVarName, assignment, dependsOn, getDefaultValueAssignment() @@ -478,12 +495,8 @@ public class PropertyMapping extends ModelElement { if ( propertyEntries.isEmpty() ) { return sourceParam.getType(); } - else if ( propertyEntries.size() == 1 ) { - PropertyEntry propertyEntry = propertyEntries.get( 0 ); - return propertyEntry.getType(); - } else { - PropertyEntry lastPropertyEntry = propertyEntries.get( propertyEntries.size() - 1 ); + PropertyEntry lastPropertyEntry = last( propertyEntries ); return lastPropertyEntry.getType(); } } @@ -499,11 +512,11 @@ public class PropertyMapping extends ModelElement { // simple property else if ( propertyEntries.size() == 1 ) { PropertyEntry propertyEntry = propertyEntries.get( 0 ); - return sourceParam.getName() + "." + propertyEntry.getAccessor().getSimpleName() + "()"; + return sourceParam.getName() + "." + propertyEntry.getReadAccessor().getSimpleName() + "()"; } // nested property given as dot path else { - PropertyEntry lastPropertyEntry = propertyEntries.get( propertyEntries.size() - 1 ); + PropertyEntry lastPropertyEntry = last( propertyEntries ); // copy mapper configuration from the source method, its the same mapper MapperConfiguration config = method.getMapperConfiguration(); @@ -732,6 +745,7 @@ public class PropertyMapping extends ModelElement { targetWriteAccessor.getSimpleName().toString(), targetReadAccessor != null ? targetReadAccessor.getSimpleName().toString() : null, targetType, + localTargetVarName, assignment, dependsOn, null @@ -770,6 +784,7 @@ public class PropertyMapping extends ModelElement { targetWriteAccessor.getSimpleName().toString(), targetReadAccessor != null ? targetReadAccessor.getSimpleName().toString() : null, targetType, + localTargetVarName, assignment, dependsOn, null @@ -778,20 +793,24 @@ public class PropertyMapping extends ModelElement { } // Constructor for creating mappings of constant expressions. - private PropertyMapping(String name, String targetWriteAccessorName, String targetReadAccessorName, Type targetType, - Assignment propertyAssignment, List dependsOn, Assignment defaultValueAssignment ) { + private PropertyMapping(String name, String targetWriteAccessorName, String targetReadAccessorName, + Type targetType, String localTargetVarName, Assignment propertyAssignment, + List dependsOn, Assignment defaultValueAssignment ) { this( name, null, targetWriteAccessorName, targetReadAccessorName, - targetType, propertyAssignment, dependsOn, defaultValueAssignment ); + targetType, localTargetVarName, propertyAssignment, dependsOn, defaultValueAssignment ); } private PropertyMapping(String name, String sourceBeanName, String targetWriteAccessorName, - String targetReadAccessorName, Type targetType, Assignment assignment, + String targetReadAccessorName, Type targetType, String localTargetVarName, + Assignment assignment, List dependsOn, Assignment defaultValueAssignment ) { this.name = name; this.sourceBeanName = sourceBeanName; this.targetWriteAccessorName = targetWriteAccessorName; this.targetReadAccessorName = targetReadAccessorName; this.targetType = targetType; + this.localTargetVarName = localTargetVarName; + this.assignment = assignment; this.dependsOn = dependsOn != null ? dependsOn : Collections.emptyList(); this.defaultValueAssignment = defaultValueAssignment; @@ -820,6 +839,10 @@ public class PropertyMapping extends ModelElement { return targetType; } + public String getLocalTargetVarName() { + return localTargetVarName; + } + public Assignment getAssignment() { return assignment; } 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 471f04346..7c4cf47fa 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 @@ -67,6 +67,7 @@ public class Mapping { private final AnnotationValue dependsOnAnnotationValue; private SourceReference sourceReference; + private TargetReference targetReference; public static Map> fromMappingsPrism(MappingsPrism mappingsAnnotation, ExecutableElement method, @@ -207,7 +208,7 @@ public class Mapping { ( (DeclaredType) mirror ).asElement().getKind() == ElementKind.ENUM; } - public void init(SourceMethod method, FormattingMessager messager, TypeFactory typeFactory) { + public void init(SourceMethod method, FormattingMessager messager, TypeFactory typeFactory, boolean isReverse) { if ( !method.isEnumMapping() ) { sourceReference = new SourceReference.BuilderFromMapping() @@ -216,6 +217,14 @@ public class Mapping { .messager( messager ) .typeFactory( typeFactory ) .build(); + + targetReference = new TargetReference.BuilderFromTargetMapping() + .mapping( this ) + .isReverse( isReverse ) + .method( method ) + .messager( messager ) + .typeFactory( typeFactory ) + .build(); } } @@ -277,6 +286,10 @@ public class Mapping { return sourceReference; } + public TargetReference getTargetReference() { + return targetReference; + } + public List getDependsOn() { return dependsOn; } @@ -300,11 +313,6 @@ public class Mapping { } } - // should not reverse a nested property - if ( sourceReference != null && sourceReference.getPropertyEntries().size() > 1 ) { - return null; - } - // should generate error when parameter if ( sourceReference != null && sourceReference.getPropertyEntries().isEmpty() ) { // parameter mapping only, apparently the @InheritReverseConfiguration is intentional @@ -332,7 +340,7 @@ public class Mapping { Collections.emptyList() ); - reverse.init( method, messager, typeFactory ); + reverse.init( method, messager, typeFactory, true ); return reverse; } @@ -363,6 +371,9 @@ public class Mapping { mapping.sourceReference = sourceReference.copyForInheritanceTo( method ); } + // TODO... must something be done here? Andreas? + mapping.targetReference = targetReference; + return mapping; } 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 a81a6527a..43b3a087b 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,10 +21,13 @@ package org.mapstruct.ap.internal.model.source; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.mapstruct.ap.internal.model.common.TypeFactory; +import static org.mapstruct.ap.internal.util.Collections.first; import org.mapstruct.ap.internal.util.FormattingMessager; /** @@ -51,7 +54,7 @@ public class MappingOptions { /** * @return the {@link Mapping}s configured for this method, keyed by target property name. Only for enum mapping - * methods a target will be mapped by several sources. + * methods a target will be mapped by several sources. TODO. Remove the value list when 2.0 */ public Map> getMappings() { return mappings; @@ -178,11 +181,45 @@ public class MappingOptions { } } + // now add all of its own mappings newMappings.putAll( getMappings() ); + + // filter new mappings + filterNestedTargetIgnores( newMappings ); + setMappings( newMappings ); } markAsFullyInitialized(); } + + private void filterNestedTargetIgnores( Map> mappings) { + + // collect all properties to ignore, and safe their target name ( == same name as first ref target property) + Set ignored = new HashSet(); + for ( Map.Entry> mappingEntry : mappings.entrySet() ) { + Mapping mapping = first( mappingEntry.getValue() ); // list only used for deprecated enums mapping + if ( mapping.isIgnored() && mapping.getTargetReference().isValid() ) { + ignored.add( mapping.getTargetName() ); + } + } + + // collect all entries to remove (avoid concurrent modification) + Set toBeRemoved = new HashSet(); + for ( Map.Entry> mappingEntry : mappings.entrySet() ) { + Mapping mapping = first( mappingEntry.getValue() ); // list only used for deprecated enums mapping + TargetReference targetReference = mapping.getTargetReference(); + if ( targetReference.isValid() + && targetReference.getPropertyEntries().size() > 1 + && ignored.contains( first( targetReference.getPropertyEntries() ).getName() ) ) { + toBeRemoved.add( mappingEntry.getKey() ); + } + } + + // finall remove all duplicates + mappings.keySet().removeAll( toBeRemoved ); + + } + } 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 new file mode 100644 index 000000000..66bcc7179 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/PropertyEntry.java @@ -0,0 +1,117 @@ +/** + * Copyright 2012-2016 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.source; + + +import java.util.Arrays; +import javax.lang.model.element.ExecutableElement; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Strings; + + + /** + * 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 { + + private final String[] fullName; + private final ExecutableElement readAccessor; + private final ExecutableElement writeAccessor; + private final ExecutableElement presenceChecker; + private final Type type; + + /** + * Constructor used to create {@link TargetReference} property entries from a mapping + * + * @param fullName + * @param readAccessor + * @param writeAccessor + * @param type + */ + private PropertyEntry(String[] fullName, ExecutableElement readAccessor, ExecutableElement writeAccessor, + ExecutableElement presenceChecker, Type type) { + this.fullName = fullName; + this.readAccessor = readAccessor; + this.writeAccessor = writeAccessor; + this.presenceChecker = presenceChecker; + this.type = type; + } + + /** + * Constructor used to create {@link TargetReference} property entries + * + * @param fullName name of the property (dot separated) + * @param readAccessor its read accessor + * @param writeAccessor its write accessor + * @param type type of the property + * @return the property entry for given parameters. + */ + public static PropertyEntry forTargetReference( String[] fullName, ExecutableElement readAccessor, + ExecutableElement writeAccessor, Type type) { + return new PropertyEntry( fullName, readAccessor, writeAccessor, null, type ); + } + + /** + * Constructor used to create {@link SourceReference} property entries from a mapping + * + * @param name name of the property (dot separated) + * @param readAccessor its read accessor + * @param presenceChecker its presence Checker + * @param type type of the property + * @return the property entry for given parameters. + */ + public static PropertyEntry forSourceReference(String name, ExecutableElement readAccessor, + ExecutableElement presenceChecker, Type type) { + return new PropertyEntry( new String[]{name}, readAccessor, null, presenceChecker, type ); + } + + public String getName() { + return fullName[fullName.length - 1]; + } + + public ExecutableElement getReadAccessor() { + return readAccessor; + } + + public ExecutableElement getWriteAccessor() { + return writeAccessor; + } + + public ExecutableElement getPresenceChecker() { + return presenceChecker; + } + + public Type getType() { + return type; + } + + public String getFullName() { + return Strings.join( Arrays.asList( fullName ), "." ); + } + +} 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 e59425e6f..de219f3a4 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 @@ -35,7 +35,6 @@ import org.mapstruct.ap.internal.model.common.Accessibility; 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.SourceReference.PropertyEntry; import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.MapperConfiguration; @@ -199,7 +198,7 @@ public class SourceMethod implements Method { if ( mappings != null ) { for ( Map.Entry> entry : mappings.entrySet() ) { for ( Mapping mapping : entry.getValue() ) { - mapping.init( sourceMethod, messager, typeFactory ); + mapping.init( sourceMethod, messager, typeFactory, false ); } } } 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 74aa42f4d..f9699f0a8 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 @@ -31,6 +31,7 @@ import javax.lang.model.type.DeclaredType; 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 static org.mapstruct.ap.internal.model.source.PropertyEntry.forSourceReference; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; @@ -95,6 +96,7 @@ public class SourceReference { public SourceReference build() { + String sourceName = mapping.getSourceName(); if ( sourceName == null ) { @@ -124,7 +126,7 @@ public class SourceReference { if ( segments.length > 1 && parameter != null ) { sourcePropertyNames = Arrays.copyOfRange( segments, 1, segments.length ); entries = getSourceEntries( parameter.getType(), sourcePropertyNames ); - foundEntryMatch = ( entries.size() == sourcePropertyNames.length ); + foundEntryMatch = (entries.size() == sourcePropertyNames.length); } else { // its only a parameter, no property @@ -138,14 +140,14 @@ public class SourceReference { sourcePropertyNames = segments; parameter = method.getSourceParameters().get( 0 ); entries = getSourceEntries( parameter.getType(), sourcePropertyNames ); - foundEntryMatch = ( entries.size() == sourcePropertyNames.length ); + foundEntryMatch = (entries.size() == sourcePropertyNames.length); if ( !foundEntryMatch ) { //Lets see if the expression contains the parameterName, so parameterName.propName1.propName2 if ( parameter.getName().equals( segments[0] ) ) { sourcePropertyNames = Arrays.copyOfRange( segments, 1, segments.length ); entries = getSourceEntries( parameter.getType(), sourcePropertyNames ); - foundEntryMatch = ( entries.size() == sourcePropertyNames.length ); + foundEntryMatch = (entries.size() == sourcePropertyNames.length); } else { // segment[0] cannot be attributed to the parameter name. @@ -158,7 +160,7 @@ public class SourceReference { if ( parameter != null ) { reportMappingError( Message.PROPERTYMAPPING_NO_PROPERTY_IN_PARAMETER, parameter.getName(), - Strings.join( Arrays.asList( sourcePropertyNames ), "." ) ); + Strings.join( Arrays.asList( sourcePropertyNames ), "." ) ); } else { reportMappingError( Message.PROPERTYMAPPING_INVALID_PROPERTY_NAME, mapping.getSourceName() ); @@ -181,7 +183,7 @@ public class SourceReference { if ( getter.getKey().equals( entryName ) ) { newType = typeFactory.getReturnType( (DeclaredType) newType.getTypeMirror(), getter.getValue() ); - sourceEntries.add( new PropertyEntry( entryName, getter.getValue(), + sourceEntries.add( forSourceReference( entryName, getter.getValue(), sourcePresenceCheckers.get( entryName ), newType ) ); matchFound = true; break; @@ -239,7 +241,7 @@ public class SourceReference { public SourceReference build() { List sourcePropertyEntries = new ArrayList(); if ( readAccessor != null ) { - sourcePropertyEntries.add( new PropertyEntry( name, readAccessor, presenceChecker, type ) ); + sourcePropertyEntries.add( forSourceReference( name, readAccessor, presenceChecker, type ) ); } return new SourceReference( sourceParameter, sourcePropertyEntries, true ); } @@ -272,42 +274,6 @@ public class SourceReference { return sourceName; } - /** - * A PropertyEntry contains information on the name, accessor and return type of a property. - */ - public static class PropertyEntry { - - private final String name; - private final ExecutableElement accessor; - private final ExecutableElement presenceChecker; - private final Type type; - - public PropertyEntry(String name, ExecutableElement readAccessor, - ExecutableElement presenceChecker, Type type) { - this.name = name; - this.accessor = readAccessor; - this.presenceChecker = presenceChecker; - this.type = type; - } - - public String getName() { - return name; - } - - public ExecutableElement getAccessor() { - return accessor; - } - - public ExecutableElement getPresenceChecker() { - return presenceChecker; - } - - public Type getType() { - return type; - } - - } - /** * Creates a copy of this reference, which is adapted to the given method * 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 new file mode 100644 index 000000000..84cf796c2 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/TargetReference.java @@ -0,0 +1,215 @@ +/** + * Copyright 2012-2016 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.source; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.DeclaredType; + +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.prism.CollectionMappingStrategyPrism; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; + +import org.mapstruct.ap.internal.util.Executables; + +/** + * This class describes the target side of a property mapping. + *

+ * It contains the target parameter, and all individual (nested) property entries. So consider the following mapping + * method: + * + *

+ * @Mapping(source = "in.propA.propB" target = "propC")
+ * TypeB mappingMethod(TypeA in);
+ * 
+ * + * Then: + *
    + *
  • {@code parameter} will describe {@code in}
  • + *
  • {@code propertyEntries[0]} will describe {@code propA}
  • + *
  • {@code propertyEntries[1]} will describe {@code propB}
  • + *
+ * + * After building, {@link #isValid()} will return true when when no problems are detected during building. + * + * @author Sjaak Derksen + */ +public class TargetReference { + + private final Parameter parameter; + private final List propertyEntries; + private final boolean isValid; + + + /** + * Builds a {@link TargetReference} from an {@code @Mappping}. + */ + public static class BuilderFromTargetMapping { + + private Mapping mapping; + private SourceMethod method; + private FormattingMessager messager; + private TypeFactory typeFactory; + + public BuilderFromTargetMapping messager(FormattingMessager messager) { + this.messager = messager; + return this; + } + + public BuilderFromTargetMapping mapping(Mapping mapping) { + this.mapping = mapping; + return this; + } + + public BuilderFromTargetMapping method(SourceMethod method) { + this.method = method; + return this; + } + + public BuilderFromTargetMapping typeFactory(TypeFactory typeFactory) { + this.typeFactory = typeFactory; + return this; + } + + public BuilderFromTargetMapping isReverse(boolean isReverse) { + return this; + } + + public TargetReference build() { + + String targetName = mapping.getTargetName(); + + if ( targetName == null ) { + return null; + } + + + String[] segments = targetName.split( "\\." ); + Parameter parameter = method.getMappingTargetParameter(); + + boolean foundEntryMatch; + Type resultType = method.getResultType(); + + // there can be 3 situations + // 1. Return type + // 2. @MappingTarget, with + // 3. or without parameter name. + String[] targetPropertyNames = segments; + List entries = getTargetEntries( resultType, targetPropertyNames ); + foundEntryMatch = (entries.size() == targetPropertyNames.length); + if ( !foundEntryMatch && segments.length > 1 ) { + targetPropertyNames = Arrays.copyOfRange( segments, 1, segments.length ); + entries = getTargetEntries( resultType, targetPropertyNames ); + foundEntryMatch = (entries.size() == targetPropertyNames.length); + } + + // foundEntryMatch = isValid, errors are handled in the BeanMapping, where the error context is known + return new TargetReference( parameter, entries, foundEntryMatch ); + } + + private List getTargetEntries(Type type, String[] entryNames) { + + // initialize + CollectionMappingStrategyPrism cms = method.getMapperConfiguration().getCollectionMappingStrategy(); + List targetEntries = new ArrayList(); + Type nextType = type; + + // iterate, establish for each entry the target write accessors. Other than setter is only allowed for + // last entry + for ( int i = 0; i < entryNames.length; i++ ) { + + ExecutableElement targetReadAccessor = nextType.getPropertyReadAccessors().get( entryNames[i] ); + ExecutableElement targetWriteAccessor = nextType.getPropertyWriteAccessors( cms ).get( entryNames[i] ); + if ( targetWriteAccessor == null || ( i < entryNames.length - 1 && targetReadAccessor == null) ) { + // there should always be a write accessor and there should be read accessor mandatory for all + // but the last + // TODO error handling when full fledged target mapping is in place. + break; + } + + if ( (i == entryNames.length - 1) || (Executables.isSetterMethod( targetWriteAccessor ) ) ) { + // only intermediate nested properties when they are a true setter + // the last may be other readAccessor (setter / getter / adder). + + if ( Executables.isGetterMethod( targetWriteAccessor ) ) { + nextType = typeFactory.getReturnType( + (DeclaredType) nextType.getTypeMirror(), + targetWriteAccessor ); + } + else { + nextType = typeFactory.getSingleParameter( + (DeclaredType) nextType.getTypeMirror(), + targetWriteAccessor ).getType(); + } + + // check if an entry alread exists, otherwise create + String[] fullName = Arrays.copyOfRange( entryNames, 0, i + 1 ); + PropertyEntry propertyEntry = PropertyEntry.forTargetReference( fullName, targetReadAccessor, + targetWriteAccessor, nextType ); + targetEntries.add( propertyEntry ); + } + + } + + return targetEntries; + } + + private void reportMappingError(Message msg, Object... objects) { + messager.printMessage( method.getExecutable(), mapping.getMirror(), mapping.getSourceAnnotationValue(), + msg, objects ); + } + } + + + private TargetReference(Parameter sourceParameter, List sourcePropertyEntries, boolean isValid) { + this.parameter = sourceParameter; + this.propertyEntries = sourcePropertyEntries; + this.isValid = isValid; + } + + public Parameter getParameter() { + return parameter; + } + + public List getPropertyEntries() { + return propertyEntries; + } + + public boolean isValid() { + return isValid; + } + + public List getElementNames() { + List elementName = new ArrayList(); + if ( parameter != null ) { + // only relevant for source properties + elementName.add( parameter.getName() ); + } + for ( PropertyEntry propertyEntry : propertyEntries ) { + elementName.add( propertyEntry.getName() ); + } + return elementName; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Collections.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Collections.java index 58fb9d1dd..c2eaf1419 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Collections.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Collections.java @@ -77,6 +77,10 @@ public class Collections { return collection.iterator().next(); } + public static T last(List list) { + return list.get( list.size() - 1 ); + } + public static List join(List a, List b) { List result = new ArrayList( a.size() + b.size() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index e459c43bf..dc5646f7d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -30,8 +30,8 @@ public enum Message { // CHECKSTYLE:OFF BEANMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy', 'resultType' and 'qualifiedBy' are undefined in @BeanMapping, define at least one of them." ), BEANMAPPING_NOT_ASSIGNABLE( "%s not assignable to: %s." ), - BEANMAPPING_UNKNOWN_PROPERTY_IN_RETURNTYPE( "Unknown property \"%s\" in return type." ), - BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RETURNTYPE( "Property \"%s\" has no write accessor." ), + BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE( "Unknown property \"%s\" in return type." ), + BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE( "Property \"%s\" has no write accessor." ), BEANMAPPING_SEVERAL_POSSIBLE_SOURCES( "Several possible source properties for target property \"%s\"." ), BEANMAPPING_SEVERAL_POSSIBLE_TARGET_ACCESSORS( "Found several matching getters for property \"%s\"." ), BEANMAPPING_UNMAPPED_TARGETS_WARNING( "Unmapped target %s.", Diagnostic.Kind.WARNING ), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/writer/FreeMarkerWritable.java b/processor/src/main/java/org/mapstruct/ap/internal/writer/FreeMarkerWritable.java index 98b7de446..fcf3952cc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/writer/FreeMarkerWritable.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/writer/FreeMarkerWritable.java @@ -48,6 +48,8 @@ public abstract class FreeMarkerWritable implements Writable { * the class name of the given model element type, appended with the extension {@code *.ftl} is used as template * file name. * + * @param clazz class to obtain a template for + * * @return the name of the template. Must not be {@code null}. */ protected String getTemplateNameForClass(Class clazz) { 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 a7ec5a0b2..40d565700 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,6 +42,7 @@ + <@nestedTargetObjects/> <#if (sourceParameters?size > 1)> <#list sourceParametersExcludingPrimitives as sourceParam> <#if (propertyMappingsByParameter[sourceParam.name]?size > 0)> @@ -87,4 +88,12 @@ <#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 new file mode 100644 index 000000000..bc7675b5d --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/LocalVariable.ftl @@ -0,0 +1,21 @@ +<#-- + + Copyright 2012-2016 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 new file mode 100644 index 000000000..2aa435e00 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedLocalVariableAssignment.ftl @@ -0,0 +1,21 @@ +<#-- + + Copyright 2012-2016 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. + +--> +${targetBean}.${setterName}( ${sourceRef} ); diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl index 675b5533f..7783507ab 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl @@ -24,13 +24,13 @@ return ${returnType.null}; } <#list propertyEntries as entry> - <#if entry.presenceChecker?? > - if ( !<@localVarName index=entry_index/>.${entry.presenceChecker.simpleName}() ) { + <#if entry.presenceCheckerName?? > + if ( !<@localVarName index=entry_index/>.${entry.presenceCheckerName}() ) { return ${returnType.null}; } <@includeModel object=entry.type/> ${entry.name} = <@localVarName index=entry_index/>.${entry.accessorName}(); - <#if !entry.presenceChecker?? > + <#if !entry.presenceCheckerName?? > <#if !entry.type.primitive> if ( ${entry.name} == null ) { return ${returnType.null}; 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 c2f1aa737..34630fba8 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,6 +18,15 @@ 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 @@ -25,3 +34,4 @@ targetWriteAccessorName=targetWriteAccessorName targetType=targetType defaultValueAssignment=defaultValueAssignment /> + diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntry.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntry.java index e8232a234..2089f206e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntry.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntry.java @@ -18,7 +18,6 @@ */ package org.mapstruct.ap.test.nestedsourceproperties; -import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; @@ -55,14 +54,6 @@ public interface ArtistToChartEntry { }) ChartEntry map(Song song); - @InheritInverseConfiguration - @Mappings({ - @Mapping(target = "artist", ignore = true), - @Mapping(target = "positions", ignore = true) - }) - Song map(ChartEntry song); - - @Mappings({ @Mapping(target = "chartName", source = "name"), @Mapping(target = "songTitle", ignore = true), diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryComposedReverse.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryComposedReverse.java new file mode 100644 index 000000000..129916a8b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryComposedReverse.java @@ -0,0 +1,62 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntryComposed; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntryLabel; +import org.mapstruct.ap.test.nestedsourceproperties.source.Label; +import org.mapstruct.ap.test.nestedsourceproperties.source.Song; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public abstract class ArtistToChartEntryComposedReverse { + + public static final ArtistToChartEntryComposedReverse MAPPER = + Mappers.getMapper( ArtistToChartEntryComposedReverse.class ); + + @Mappings({ + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistName", source = "artist.name"), + @Mapping(target = "label", source = "artist.label"), + @Mapping(target = "position", ignore = true), + @Mapping(target = "chartName", ignore = true ) + }) + abstract ChartEntryComposed mapForward(Song song); + + @Mappings({ + @Mapping(target = "name", source = "name"), + @Mapping(target = "recordedAt", source = "studio.name"), + @Mapping(target = "city", source = "studio.city") + }) + abstract ChartEntryLabel mapForward(Label label); + + @InheritInverseConfiguration + @Mapping(target = "positions", ignore = true) + abstract Song mapReverse(ChartEntryComposed ce); + + @InheritInverseConfiguration + abstract Label mapReverse(ChartEntryLabel label); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryConfig.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryConfig.java new file mode 100644 index 000000000..64538b485 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryConfig.java @@ -0,0 +1,41 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties; + +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.nestedsourceproperties._target.BaseChartEntry; +import org.mapstruct.ap.test.nestedsourceproperties.source.Song; + +/** + * + * @author Sjaak Derksen + */ +@MapperConfig( unmappedTargetPolicy = ReportingPolicy.ERROR ) +public interface ArtistToChartEntryConfig { + + @Mappings({ + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistName", source = "artist.name"), + @Mapping(target = "chartName", ignore = true ) + }) + BaseChartEntry mapForward( Song song ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryReverse.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryReverse.java new file mode 100644 index 000000000..b7faa6ae1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryReverse.java @@ -0,0 +1,51 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntry; +import org.mapstruct.ap.test.nestedsourceproperties.source.Song; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public abstract class ArtistToChartEntryReverse { + + public static final ArtistToChartEntryReverse MAPPER = Mappers.getMapper( ArtistToChartEntryReverse.class ); + + @Mappings({ + + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistName", source = "artist.name"), + @Mapping(target = "recordedAt", source = "artist.label.studio.name"), + @Mapping(target = "city", source = "artist.label.studio.city"), + @Mapping(target = "position", ignore = true), + @Mapping(target = "chartName", ignore = true ) + }) + abstract ChartEntry mapForward(Song song); + + @InheritInverseConfiguration + @Mapping(target = "positions", ignore = true) + abstract Song mapReverse(ChartEntry ce); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryUpdateReverse.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryUpdateReverse.java new file mode 100644 index 000000000..e41fd1199 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryUpdateReverse.java @@ -0,0 +1,54 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntry; +import org.mapstruct.ap.test.nestedsourceproperties.source.Song; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public abstract class ArtistToChartEntryUpdateReverse { + + public static final ArtistToChartEntryUpdateReverse MAPPER = + Mappers.getMapper( ArtistToChartEntryUpdateReverse.class ); + + @Mappings({ + + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistName", source = "artist.name"), + @Mapping(target = "recordedAt", source = "artist.label.studio.name"), + @Mapping(target = "city", source = "artist.label.studio.city"), + @Mapping(target = "position", ignore = true), + @Mapping(target = "chartName", ignore = true ) + }) + abstract ChartEntry mapForward(Song song); + + @InheritInverseConfiguration + @Mapping(target = "positions", ignore = true) + abstract void mapReverse(ChartEntry ce, @MappingTarget Song song); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithConfigReverse.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithConfigReverse.java new file mode 100644 index 000000000..aac9181ba --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithConfigReverse.java @@ -0,0 +1,50 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntryWithBase; +import org.mapstruct.ap.test.nestedsourceproperties.source.Song; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper( config = ArtistToChartEntryConfig.class ) +public abstract class ArtistToChartEntryWithConfigReverse { + + public static final ArtistToChartEntryWithConfigReverse MAPPER = + Mappers.getMapper( ArtistToChartEntryWithConfigReverse.class ); + + @InheritConfiguration + @Mappings({ + @Mapping(target = "recordedAt", source = "artist.label.studio.name"), + @Mapping(target = "city", source = "artist.label.studio.city"), + @Mapping(target = "position", ignore = true) + }) + abstract ChartEntryWithBase mapForward(Song song); + + @InheritInverseConfiguration + @Mapping(target = "positions", ignore = true) + abstract Song mapReverse(ChartEntryWithBase ce); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithFactoryReverse.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithFactoryReverse.java new file mode 100644 index 000000000..0fadcb83b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithFactoryReverse.java @@ -0,0 +1,53 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntry; +import org.mapstruct.ap.test.nestedsourceproperties.source.Song; +import org.mapstruct.ap.test.nestedsourceproperties.source.SourceDtoFactory; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper( uses = { SourceDtoFactory.class } ) +public abstract class ArtistToChartEntryWithFactoryReverse { + + public static final ArtistToChartEntryWithFactoryReverse MAPPER + = Mappers.getMapper( ArtistToChartEntryWithFactoryReverse.class ); + + @Mappings({ + + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistName", source = "artist.name"), + @Mapping(target = "recordedAt", source = "artist.label.studio.name"), + @Mapping(target = "city", source = "artist.label.studio.city"), + @Mapping(target = "position", ignore = true), + @Mapping(target = "chartName", ignore = true ) + }) + abstract ChartEntry mapForward(Song song); + + @InheritInverseConfiguration + @Mapping(target = "positions", ignore = true) + abstract Song mapReverse(ChartEntry ce); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithIgnoresReverse.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithIgnoresReverse.java new file mode 100644 index 000000000..2736cb06b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithIgnoresReverse.java @@ -0,0 +1,54 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntry; +import org.mapstruct.ap.test.nestedsourceproperties.source.Song; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public abstract class ArtistToChartEntryWithIgnoresReverse { + + public static final ArtistToChartEntryWithIgnoresReverse MAPPER = + Mappers.getMapper( ArtistToChartEntryWithIgnoresReverse.class ); + + @Mappings({ + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistName", source = "artist.name"), + @Mapping(target = "recordedAt", source = "artist.label.studio.name"), + @Mapping(target = "city", source = "artist.label.studio.city"), + @Mapping(target = "position", ignore = true), + @Mapping(target = "chartName", ignore = true ) + }) + abstract ChartEntry mapForward(Song song); + + @InheritInverseConfiguration + @Mappings({ + @Mapping(target = "positions", ignore = true), + @Mapping(target = "artist", ignore = true) + }) + abstract Song mapReverse(ChartEntry ce); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithMappingReverse.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithMappingReverse.java new file mode 100644 index 000000000..e1f56141d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryWithMappingReverse.java @@ -0,0 +1,72 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntryWithMapping; +import org.mapstruct.ap.test.nestedsourceproperties.source.Song; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public abstract class ArtistToChartEntryWithMappingReverse { + + public static final ArtistToChartEntryWithMappingReverse MAPPER = + Mappers.getMapper( ArtistToChartEntryWithMappingReverse.class ); + + @Mappings({ + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistId", source = "artist.name"), + @Mapping(target = "recordedAt", source = "artist.label.studio.name"), + @Mapping(target = "city", source = "artist.label.studio.city"), + @Mapping(target = "position", ignore = true), + @Mapping(target = "chartName", ignore = true ) + }) + abstract ChartEntryWithMapping mapForward(Song song); + + @InheritInverseConfiguration + @Mapping(target = "positions", ignore = true) + abstract Song mapReverse(ChartEntryWithMapping ce); + + int mapArtistToArtistId(String in) { + + if ( "The Beatles".equals( in ) ) { + return 1; + } + else { + return -1; + } + } + + String mapArtistIdToArtist(int in) { + + if ( in == 1 ) { + return "The Beatles"; + } + else { + return "Unknown"; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/NestedSourcePropertiesTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/NestedSourcePropertiesTest.java index 0cd8e3524..fd12d2927 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/NestedSourcePropertiesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/NestedSourcePropertiesTest.java @@ -170,20 +170,6 @@ public class NestedSourcePropertiesTest { assertFalse( AdderUsageObserver.isUsed() ); } - - @Test - @IssueKey("337") - @WithClasses({ ArtistToChartEntry.class }) - public void reverseShouldIgnoreNestedProperties() { - - ChartEntry entry = new ChartEntry(); - entry.setSongTitle( "Another brick in the wall" ); - - Song song = ArtistToChartEntry.MAPPER.map( entry ); - assertThat( song ).isNotNull(); - assertThat( song.getTitle() ).isEqualTo( "Another brick in the wall" ); - } - @Test @IssueKey( "337" ) @ExpectedCompilationOutcome( diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ReversingNestedSourcePropertiesTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ReversingNestedSourcePropertiesTest.java new file mode 100644 index 000000000..64c79ac3a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ReversingNestedSourcePropertiesTest.java @@ -0,0 +1,281 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties; + + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntry; +import org.mapstruct.ap.test.nestedsourceproperties.source.Artist; +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.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.fest.assertions.Assertions.assertThat; +import org.mapstruct.ap.test.nestedsourceproperties._target.BaseChartEntry; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntryComposed; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntryLabel; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntryWithBase; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntryWithMapping; +import org.mapstruct.ap.test.nestedsourceproperties.source.SourceDtoFactory; + +/** + * @author Sjaak Derksen + */ +@IssueKey("389") +@WithClasses({ Song.class, Artist.class, Chart.class, Label.class, Studio.class, ChartEntry.class }) +@RunWith(AnnotationProcessorTestRunner.class) +public class ReversingNestedSourcePropertiesTest { + + @Test + @WithClasses({ ArtistToChartEntryReverse.class }) + public void shouldGenerateNestedReverse() { + + Song song1 = prepareSong(); + + ChartEntry chartEntry = ArtistToChartEntryReverse.MAPPER.mapForward( song1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + // and now in reverse + Song song2 = ArtistToChartEntryReverse.MAPPER.mapReverse( chartEntry ); + + assertThat( song2 ).isNotNull(); + assertThat( song2.getArtist() ).isNotNull(); + assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" ); + assertThat( song2.getArtist().getLabel() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getName() ).isNull(); + assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" ); + assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); + } + + @Test + @WithClasses({ ArtistToChartEntryWithIgnoresReverse.class }) + public void shouldIgnoreEverytingBelowArtist() { + + Song song1 = prepareSong(); + + ChartEntry chartEntry = ArtistToChartEntryWithIgnoresReverse.MAPPER.mapForward( song1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + // and now in reverse + Song song2 = ArtistToChartEntryWithIgnoresReverse.MAPPER.mapReverse( chartEntry ); + + assertThat( song2 ).isNotNull(); + assertThat( song2.getArtist() ).isNull(); + } + + @Test + @WithClasses({ ArtistToChartEntryUpdateReverse.class }) + public void shouldGenerateNestedUpdateReverse() { + + Song song1 = prepareSong(); + + ChartEntry chartEntry = ArtistToChartEntryUpdateReverse.MAPPER.mapForward( song1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + // and now in reverse + Song song2 = new Song(); + ArtistToChartEntryUpdateReverse.MAPPER.mapReverse( chartEntry, song2 ); + + assertThat( song2 ).isNotNull(); + assertThat( song2.getArtist() ).isNotNull(); + assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" ); + assertThat( song2.getArtist().getLabel() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getName() ).isNull(); + assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" ); + assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); + } + + + @Test + @WithClasses( { ArtistToChartEntryWithFactoryReverse.class, SourceDtoFactory.class } ) + public void shouldGenerateNestedReverseWithFactory() { + + Song song1 = prepareSong(); + + ChartEntry chartEntry = ArtistToChartEntryWithFactoryReverse.MAPPER.mapForward( song1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + // and now in reverse + Song song2 = ArtistToChartEntryWithFactoryReverse.MAPPER.mapReverse( chartEntry ); + + assertThat( song2 ).isNotNull(); + assertThat( song2.getArtist() ).isNotNull(); + assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" ); + assertThat( song2.getArtist().getLabel() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getName() ).isNull(); + assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" ); + assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); + + assertThat( SourceDtoFactory.isCreateSongCalled() ).isTrue(); + assertThat( SourceDtoFactory.isCreateStudioCalled() ).isTrue(); + assertThat( SourceDtoFactory.isCreateLabelCalled() ).isTrue(); + assertThat( SourceDtoFactory.isCreateArtistCalled() ).isTrue(); + + } + + + @Test + @WithClasses({ ArtistToChartEntryComposedReverse.class, ChartEntryComposed.class, ChartEntryLabel.class }) + public void shouldGenerateNestedComposedReverse() { + + Song song1 = prepareSong(); + + ChartEntryComposed chartEntry = ArtistToChartEntryComposedReverse.MAPPER.mapForward( song1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getLabel().getName() ).isEqualTo( "EMY" ); + assertThat( chartEntry.getLabel().getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getLabel().getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + // and now in reverse + Song song2 = ArtistToChartEntryComposedReverse.MAPPER.mapReverse( chartEntry ); + + assertThat( song2 ).isNotNull(); + assertThat( song2.getArtist() ).isNotNull(); + assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" ); + assertThat( song2.getArtist().getLabel() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getName() ).isEqualTo( "EMY" ); + assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" ); + assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); + } + + @Test + @WithClasses({ ArtistToChartEntryWithMappingReverse.class, ChartEntryWithMapping.class }) + public void shouldGenerateNestedWithMappingReverse() { + + Song song1 = prepareSong(); + + ChartEntryWithMapping chartEntry = ArtistToChartEntryWithMappingReverse.MAPPER.mapForward( song1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistId() ).isEqualTo( 1 ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + // and now in reverse + Song song2 = ArtistToChartEntryWithMappingReverse.MAPPER.mapReverse( chartEntry ); + + assertThat( song2 ).isNotNull(); + assertThat( song2.getArtist() ).isNotNull(); + assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" ); + assertThat( song2.getArtist().getLabel() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getName() ).isNull(); + assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" ); + assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); + } + + @Test + @WithClasses({ + ArtistToChartEntryWithConfigReverse.class, + ArtistToChartEntryConfig.class, + BaseChartEntry.class, + ChartEntryWithBase.class + }) + public void shouldGenerateNestedWithConfigReverse() { + + Song song1 = prepareSong(); + + ChartEntryWithBase chartEntry = ArtistToChartEntryWithConfigReverse.MAPPER.mapForward( song1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + // and now in reverse + Song song2 = ArtistToChartEntryWithConfigReverse.MAPPER.mapReverse( chartEntry ); + + assertThat( song2 ).isNotNull(); + assertThat( song2.getArtist() ).isNotNull(); + assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" ); + assertThat( song2.getArtist().getLabel() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getName() ).isNull(); + assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" ); + assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); + } + + private Song prepareSong() { + Studio studio = new Studio(); + studio.setName( "Abbey Road" ); + studio.setCity( "London" ); + + Label label = new Label(); + label.setStudio( studio ); + label.setName( "EMY" ); + + Artist artist = new Artist(); + artist.setName( "The Beatles" ); + artist.setLabel( label ); + + Song song = new Song(); + song.setArtist( artist ); + song.setTitle( "A Hard Day's Night" ); + return song; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/BaseChartEntry.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/BaseChartEntry.java new file mode 100644 index 000000000..534ad4946 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/BaseChartEntry.java @@ -0,0 +1,53 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties._target; + +/** + * @author Sjaak Derksen + */ +public class BaseChartEntry { + + private String chartName; + private String songTitle; + private String artistName; + + public String getChartName() { + return chartName; + } + + public void setChartName(String chartName) { + this.chartName = chartName; + } + + public String getSongTitle() { + return songTitle; + } + + public void setSongTitle(String songTitle) { + this.songTitle = songTitle; + } + + public String getArtistName() { + return artistName; + } + + public void setArtistName(String artistName) { + this.artistName = artistName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryComposed.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryComposed.java new file mode 100644 index 000000000..28a636110 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryComposed.java @@ -0,0 +1,71 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties._target; + +/** + * @author Sjaak Derksen + */ +public class ChartEntryComposed { + + private String chartName; + private String songTitle; + private String artistName; + private ChartEntryLabel label; + private int position; + + public String getChartName() { + return chartName; + } + + public void setChartName(String chartName) { + this.chartName = chartName; + } + + public String getSongTitle() { + return songTitle; + } + + public void setSongTitle(String songTitle) { + this.songTitle = songTitle; + } + + public String getArtistName() { + return artistName; + } + + public void setArtistName(String artistName) { + this.artistName = artistName; + } + + public ChartEntryLabel getLabel() { + return label; + } + + public void setLabel(ChartEntryLabel label) { + this.label = label; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryLabel.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryLabel.java new file mode 100644 index 000000000..208d7582d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryLabel.java @@ -0,0 +1,54 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties._target; + + +/** + * @author Sjaak Derksen + */ +public class ChartEntryLabel { + + private String name; + private String city; + private String recordedAt; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getRecordedAt() { + return recordedAt; + } + + public void setRecordedAt(String recordedAt) { + this.recordedAt = recordedAt; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryWithBase.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryWithBase.java new file mode 100644 index 000000000..4ac56fccc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryWithBase.java @@ -0,0 +1,53 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties._target; + +/** + * @author Sjaak Derksen + */ +public class ChartEntryWithBase extends BaseChartEntry { + + private String recordedAt; + private String city; + private int position; + + public String getRecordedAt() { + return recordedAt; + } + + public void setRecordedAt(String recordedAt) { + this.recordedAt = recordedAt; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryWithMapping.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryWithMapping.java new file mode 100644 index 000000000..ca103bd3f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryWithMapping.java @@ -0,0 +1,80 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties._target; + +/** + * @author Sjaak Derksen + */ +public class ChartEntryWithMapping { + + private String chartName; + private String songTitle; + private int artistId; + private String recordedAt; + private String city; + private int position; + + public String getChartName() { + return chartName; + } + + public void setChartName(String chartName) { + this.chartName = chartName; + } + + public String getSongTitle() { + return songTitle; + } + + public void setSongTitle(String songTitle) { + this.songTitle = songTitle; + } + + public int getArtistId() { + return artistId; + } + + public void setArtistId(int artistId) { + this.artistId = artistId; + } + + public String getRecordedAt() { + return recordedAt; + } + + public void setRecordedAt(String recordedAt) { + this.recordedAt = recordedAt; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/source/SourceDtoFactory.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/source/SourceDtoFactory.java new file mode 100644 index 000000000..c6baaa976 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/source/SourceDtoFactory.java @@ -0,0 +1,70 @@ +/** + * Copyright 2012-2016 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.nestedsourceproperties.source; + +/** + * + * @author Sjaak Derksen + */ +public class SourceDtoFactory { + + private SourceDtoFactory() { } + + private static boolean createArtistCalled = false; + private static boolean createStudioCalled = false; + private static boolean createLabelCalled = false; + private static boolean createSongCalled = false; + + public static Artist createArtist() { + createArtistCalled = true; + return new Artist(); + } + + public static Studio createStudio() { + createStudioCalled = true; + return new Studio(); + } + + public static Label createLabel() { + createLabelCalled = true; + return new Label(); + } + + public static Song createSong() { + createSongCalled = true; + return new Song(); + } + + public static boolean isCreateArtistCalled() { + return createArtistCalled; + } + + public static boolean isCreateStudioCalled() { + return createStudioCalled; + } + + public static boolean isCreateLabelCalled() { + return createLabelCalled; + } + + public static boolean isCreateSongCalled() { + return createSongCalled; + } + +} 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 new file mode 100644 index 000000000..ccf86a91a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtist.java @@ -0,0 +1,83 @@ +/** + * Copyright 2012-2016 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.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +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 +public abstract class ChartEntryToArtist { + + public static final ChartEntryToArtist MAPPER = Mappers.getMapper( ChartEntryToArtist.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 Chart map(ChartEntry chartEntry); + + + @Mappings({ + @Mapping(target = "type", ignore = true), + @Mapping(target = "name", source = "chartEntry2.chartName"), + @Mapping(target = "song.title", source = "chartEntry1.songTitle" ), + @Mapping(target = "song.artist.name", source = "chartEntry1.artistName" ), + @Mapping(target = "song.artist.label.studio.name", source = "chartEntry1.recordedAt"), + @Mapping(target = "song.artist.label.studio.city", source = "chartEntry1.city" ), + @Mapping(target = "song.positions", source = "chartEntry2.position" ) + }) + public abstract Chart map(ChartEntry chartEntry1, ChartEntry chartEntry2); + + @InheritInverseConfiguration + public abstract ChartEntry map(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/NestedTargetPropertiesTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedTargetPropertiesTest.java new file mode 100644 index 000000000..f62c6185b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedTargetPropertiesTest.java @@ -0,0 +1,128 @@ +/** + * Copyright 2012-2016 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 static org.fest.assertions.Assertions.assertThat; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntry; +import org.mapstruct.ap.test.nestedsourceproperties.source.Artist; +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.testutil.IssueKey; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +/** + * + * @author Sjaak Derksen + */ +@WithClasses({Song.class, Artist.class, Chart.class, Label.class, Studio.class, ChartEntry.class}) +@IssueKey("389") +@RunWith(AnnotationProcessorTestRunner.class) +public class NestedTargetPropertiesTest { + + @Test + @WithClasses({ChartEntryToArtist.class}) + public void shouldMapNestedTarget() { + + 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( "Purple Rain" ); + + Chart result = ChartEntryToArtist.MAPPER.map( chartEntry ); + + assertThat( result.getName() ).isEqualTo( "US Billboard Hot Rock Songs" ); + assertThat( result.getSong() ).isNotNull(); + assertThat( result.getSong().getArtist() ).isNotNull(); + assertThat( result.getSong().getTitle() ).isEqualTo( "Purple Rain" ); + 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 + @WithClasses({ChartEntryToArtist.class}) + public void shouldMapNestedComposedTarget() { + + ChartEntry chartEntry1 = new ChartEntry(); + chartEntry1.setArtistName( "Prince" ); + chartEntry1.setCity( "Minneapolis" ); + chartEntry1.setRecordedAt( "Live, First Avenue, Minneapolis" ); + chartEntry1.setSongTitle( "Purple Rain" ); + + ChartEntry chartEntry2 = new ChartEntry(); + chartEntry2.setChartName( "Italian Singles Chart" ); + chartEntry2.setPosition( 32 ); + + Chart result = ChartEntryToArtist.MAPPER.map( chartEntry1, chartEntry2 ); + + assertThat( result.getName() ).isEqualTo( "Italian Singles Chart" ); + assertThat( result.getSong() ).isNotNull(); + assertThat( result.getSong().getArtist() ).isNotNull(); + assertThat( result.getSong().getTitle() ).isEqualTo( "Purple Rain" ); + 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( 32 ); + + } + + @Test + @WithClasses({ChartEntryToArtist.class}) + public void shouldReverseNestedTarget() { + + 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( "Purple Rain" ); + + Chart chart = ChartEntryToArtist.MAPPER.map( chartEntry ); + ChartEntry result = ChartEntryToArtist.MAPPER.map( chart ); + + assertThat( result ).isNotNull(); + assertThat( result.getArtistName() ).isEqualTo( "Prince" ); + assertThat( result.getChartName() ).isEqualTo( "US Billboard Hot Rock Songs" ); + assertThat( result.getCity() ).isEqualTo( "Minneapolis" ); + assertThat( result.getPosition() ).isEqualTo( 1 ); + assertThat( result.getRecordedAt() ).isEqualTo( "Live, First Avenue, Minneapolis" ); + assertThat( result.getSongTitle() ).isEqualTo( "Purple Rain" ); + } +}