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 7797edaeb..6584f2612 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 @@ -303,6 +303,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { for ( Entry entryByTP : optionsByNestedTarget.entrySet() ) { Map optionsBySourceParam = entryByTP.getValue().groupBySourceParameter(); + boolean forceUpdateMethod = optionsBySourceParam.keySet().size() > 1; for ( Entry entryByParam : optionsBySourceParam.entrySet() ) { SourceReference sourceRef = new SourceReference.BuilderFromProperty() @@ -319,6 +320,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { .existingVariableNames( existingVariableNames ) .dependsOn( entryByParam.getValue().collectNestedDependsOn() ) .forgeMethodWithMappingOptions( entryByParam.getValue() ) + .forceUpdateMethod( forceUpdateMethod ) .build(); unprocessedSourceParameters.remove( sourceRef.getParameter() ); 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 e746f4bf4..0f80ddd11 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 @@ -220,7 +220,6 @@ public abstract class MappingMethod extends ModelElement { @Override public int hashCode() { int hash = 7; - hash = 83 * hash + (this.name != null ? this.name.hashCode() : 0); hash = 83 * hash + (this.parameters != null ? this.parameters.hashCode() : 0); hash = 83 * hash + (this.returnType != null ? this.returnType.hashCode() : 0); return hash; @@ -237,10 +236,10 @@ public abstract class MappingMethod extends ModelElement { if ( getClass() != obj.getClass() ) { return false; } + //Do not add name to the equals check. + //Reason: Whenever we forge methods we can reuse mappings if they are the same. However, if we take the name + // into consideration, they'll never be the same, because we create safe methods names. final MappingMethod other = (MappingMethod) obj; - if ( (this.name == null) ? (other.name != null) : !this.name.equals( other.name ) ) { - return false; - } if ( this.parameters != other.parameters && (this.parameters == null || !this.parameters.equals( other.parameters )) ) { return false; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java index 9a5f7863c..e1e72eb62 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java @@ -71,7 +71,7 @@ public abstract class NormalTypeMappingMethod extends MappingMethod { @Override public int hashCode() { final int prime = 31; - int result = 1; + int result = super.hashCode(); result = prime * result + ( ( getResultType() == null ) ? 0 : getResultType().hashCode() ); return result; } @@ -89,6 +89,10 @@ public abstract class NormalTypeMappingMethod extends MappingMethod { } NormalTypeMappingMethod other = (NormalTypeMappingMethod) obj; + if ( !super.equals( obj ) ) { + return false; + } + if ( !getResultType().equals( other.getResultType() ) ) { return false; } 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 b6308e28d..9eb1290ac 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 @@ -187,6 +187,7 @@ public class PropertyMapping extends ModelElement { private FormattingParameters formattingParameters; private SelectionParameters selectionParameters; private MappingOptions forgeMethodWithMappingOptions; + private boolean forceUpdateMethod; PropertyMappingBuilder() { super( PropertyMappingBuilder.class ); @@ -217,6 +218,16 @@ public class PropertyMapping extends ModelElement { return this; } + /** + * Force the created mapping to use update methods when forging a method. + * + * @param forceUpdateMethod whether the mapping should force update method for forged mappings + */ + public PropertyMappingBuilder forceUpdateMethod(boolean forceUpdateMethod) { + this.forceUpdateMethod = forceUpdateMethod; + return this; + } + public PropertyMapping build() { // handle source this.rightHandSide = getSourceRHS( sourceReference ); @@ -607,8 +618,9 @@ public class PropertyMapping extends ModelElement { List parameters = new ArrayList( method.getContextParameters() ); Type returnType; // there's only one case for forging a method with mapping options: nested target properties. - // they should always forge an update method - if ( method.isUpdateMethod() || forgeMethodWithMappingOptions != null ) { + // They should forge an update method only if we set the forceUpdateMethod. This is set to true, + // because we are forging a Mapping for a method with multiple source parameters. + if ( method.isUpdateMethod() || forceUpdateMethod ) { parameters.add( Parameter.forForgedMappingTarget( targetType ) ); returnType = ctx.getTypeFactory().createVoidType(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedTargetPropertiesTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedTargetPropertiesTest.java index 8d05cc652..b7e9f94e3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedTargetPropertiesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedTargetPropertiesTest.java @@ -19,6 +19,7 @@ package org.mapstruct.ap.test.nestedtargetproperties; import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntry; @@ -36,6 +37,7 @@ import org.mapstruct.ap.test.nestedtargetproperties.source.WaterPlant; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; /** * @@ -62,6 +64,13 @@ import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; @RunWith(AnnotationProcessorTestRunner.class) public class NestedTargetPropertiesTest { + @Rule + public GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + ChartEntryToArtist.class, + ChartEntryToArtistUpdate.class, + FishTankMapper.class + ); + @Test public void shouldMapNestedTarget() { diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistImpl.java new file mode 100644 index 000000000..2e030f22b --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistImpl.java @@ -0,0 +1,338 @@ +/** + * Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.nestedtargetproperties; + +import java.util.List; +import javax.annotation.Generated; +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; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2017-02-07T21:05:06+0100", + comments = "version: , compiler: javac, environment: Java 1.8.0_112 (Oracle Corporation)" +) +public class ChartEntryToArtistImpl extends ChartEntryToArtist { + + @Override + public Chart map(ChartEntry chartEntry) { + if ( chartEntry == null ) { + return null; + } + + Chart chart = new Chart(); + + chart.setSong( chartEntryToSong( chartEntry ) ); + chart.setName( chartEntry.getChartName() ); + + return chart; + } + + @Override + public Chart map(ChartEntry chartEntry1, ChartEntry chartEntry2) { + if ( chartEntry1 == null && chartEntry2 == null ) { + return null; + } + + Chart chart = new Chart(); + + if ( chartEntry1 != null ) { + if ( chart.getSong() == null ) { + chart.setSong( new Song() ); + } + chartEntryToSong1( chartEntry1, chart.getSong() ); + } + if ( chartEntry2 != null ) { + if ( chart.getSong() == null ) { + chart.setSong( new Song() ); + } + chartEntryToSong2( chartEntry2, chart.getSong() ); + chart.setName( chartEntry2.getChartName() ); + } + + return chart; + } + + @Override + public ChartEntry map(Chart chart) { + if ( chart == null ) { + return null; + } + + ChartEntry chartEntry = new ChartEntry(); + + String title = chartSongTitle( chart ); + if ( title != null ) { + chartEntry.setSongTitle( title ); + } + chartEntry.setChartName( chart.getName() ); + String city = chartSongArtistLabelStudioCity( chart ); + if ( city != null ) { + chartEntry.setCity( city ); + } + String name = chartSongArtistLabelStudioName( chart ); + if ( name != null ) { + chartEntry.setRecordedAt( name ); + } + String name1 = chartSongArtistName( chart ); + if ( name1 != null ) { + chartEntry.setArtistName( name1 ); + } + List positions = chartSongPositions( chart ); + if ( positions != null ) { + chartEntry.setPosition( mapPosition( positions ) ); + } + + return chartEntry; + } + + protected Studio chartEntryToStudio(ChartEntry chartEntry) { + if ( chartEntry == null ) { + return null; + } + + Studio studio = new Studio(); + + studio.setCity( chartEntry.getCity() ); + studio.setName( chartEntry.getRecordedAt() ); + + return studio; + } + + protected Label chartEntryToLabel(ChartEntry chartEntry) { + if ( chartEntry == null ) { + return null; + } + + Label label = new Label(); + + label.setStudio( chartEntryToStudio( chartEntry ) ); + + return label; + } + + protected Artist chartEntryToArtist(ChartEntry chartEntry) { + if ( chartEntry == null ) { + return null; + } + + Artist artist = new Artist(); + + artist.setLabel( chartEntryToLabel( chartEntry ) ); + artist.setName( chartEntry.getArtistName() ); + + return artist; + } + + protected Song chartEntryToSong(ChartEntry chartEntry) { + if ( chartEntry == null ) { + return null; + } + + Song song = new Song(); + + song.setArtist( chartEntryToArtist( chartEntry ) ); + List list = mapPosition( chartEntry.getPosition() ); + if ( list != null ) { + song.setPositions( list ); + } + song.setTitle( chartEntry.getSongTitle() ); + + return song; + } + + protected void chartEntryToStudio1(ChartEntry chartEntry, Studio mappingTarget) { + if ( chartEntry == null ) { + return; + } + + mappingTarget.setCity( chartEntry.getCity() ); + mappingTarget.setName( chartEntry.getRecordedAt() ); + } + + protected void chartEntryToLabel1(ChartEntry chartEntry, Label mappingTarget) { + if ( chartEntry == null ) { + return; + } + + if ( mappingTarget.getStudio() == null ) { + mappingTarget.setStudio( new Studio() ); + } + chartEntryToStudio1( chartEntry, mappingTarget.getStudio() ); + } + + protected void chartEntryToArtist1(ChartEntry chartEntry, Artist mappingTarget) { + if ( chartEntry == null ) { + return; + } + + if ( mappingTarget.getLabel() == null ) { + mappingTarget.setLabel( new Label() ); + } + chartEntryToLabel1( chartEntry, mappingTarget.getLabel() ); + mappingTarget.setName( chartEntry.getArtistName() ); + } + + protected void chartEntryToSong1(ChartEntry chartEntry, Song mappingTarget) { + if ( chartEntry == null ) { + return; + } + + if ( mappingTarget.getArtist() == null ) { + mappingTarget.setArtist( new Artist() ); + } + chartEntryToArtist1( chartEntry, mappingTarget.getArtist() ); + mappingTarget.setTitle( chartEntry.getSongTitle() ); + } + + protected void chartEntryToSong2(ChartEntry chartEntry, Song mappingTarget) { + if ( chartEntry == null ) { + return; + } + + if ( mappingTarget.getPositions() != null ) { + List list = mapPosition( chartEntry.getPosition() ); + if ( list != null ) { + mappingTarget.getPositions().clear(); + mappingTarget.getPositions().addAll( list ); + } + else { + mappingTarget.setPositions( null ); + } + } + else { + List list = mapPosition( chartEntry.getPosition() ); + if ( list != null ) { + mappingTarget.setPositions( list ); + } + } + } + + private String chartSongTitle(Chart chart) { + + if ( chart == null ) { + return null; + } + Song song = chart.getSong(); + if ( song == null ) { + return null; + } + String title = song.getTitle(); + if ( title == null ) { + return null; + } + return title; + } + + private String chartSongArtistLabelStudioCity(Chart chart) { + + if ( chart == null ) { + return null; + } + Song song = chart.getSong(); + if ( song == null ) { + return null; + } + Artist artist = song.getArtist(); + if ( artist == null ) { + return null; + } + Label label = artist.getLabel(); + if ( label == null ) { + return null; + } + Studio studio = label.getStudio(); + if ( studio == null ) { + return null; + } + String city = studio.getCity(); + if ( city == null ) { + return null; + } + return city; + } + + private String chartSongArtistLabelStudioName(Chart chart) { + + if ( chart == null ) { + return null; + } + Song song = chart.getSong(); + if ( song == null ) { + return null; + } + Artist artist = song.getArtist(); + if ( artist == null ) { + return null; + } + Label label = artist.getLabel(); + if ( label == null ) { + return null; + } + Studio studio = label.getStudio(); + if ( studio == null ) { + return null; + } + String name = studio.getName(); + if ( name == null ) { + return null; + } + return name; + } + + private String chartSongArtistName(Chart chart) { + + if ( chart == null ) { + return null; + } + Song song = chart.getSong(); + if ( song == null ) { + return null; + } + Artist artist = song.getArtist(); + if ( artist == null ) { + return null; + } + String name = artist.getName(); + if ( name == null ) { + return null; + } + return name; + } + + private List chartSongPositions(Chart chart) { + + if ( chart == null ) { + return null; + } + Song song = chart.getSong(); + if ( song == null ) { + return null; + } + List positions = song.getPositions(); + if ( positions == null ) { + return null; + } + return positions; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistUpdateImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistUpdateImpl.java new file mode 100644 index 000000000..990bf30c9 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistUpdateImpl.java @@ -0,0 +1,116 @@ +/** + * Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.nestedtargetproperties; + +import java.util.List; +import javax.annotation.Generated; +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; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2017-02-07T21:05:06+0100", + comments = "version: , compiler: javac, environment: Java 1.8.0_112 (Oracle Corporation)" +) +public class ChartEntryToArtistUpdateImpl extends ChartEntryToArtistUpdate { + + @Override + public void map(ChartEntry chartEntry, Chart chart) { + if ( chartEntry == null ) { + return; + } + + if ( chart.getSong() == null ) { + chart.setSong( new Song() ); + } + chartEntryToSong( chartEntry, chart.getSong() ); + if ( chartEntry.getChartName() != null ) { + chart.setName( chartEntry.getChartName() ); + } + } + + protected void chartEntryToStudio(ChartEntry chartEntry, Studio mappingTarget) { + if ( chartEntry == null ) { + return; + } + + if ( chartEntry.getCity() != null ) { + mappingTarget.setCity( chartEntry.getCity() ); + } + if ( chartEntry.getRecordedAt() != null ) { + mappingTarget.setName( chartEntry.getRecordedAt() ); + } + } + + protected void chartEntryToLabel(ChartEntry chartEntry, Label mappingTarget) { + if ( chartEntry == null ) { + return; + } + + if ( mappingTarget.getStudio() == null ) { + mappingTarget.setStudio( new Studio() ); + } + chartEntryToStudio( chartEntry, mappingTarget.getStudio() ); + } + + protected void chartEntryToArtist(ChartEntry chartEntry, Artist mappingTarget) { + if ( chartEntry == null ) { + return; + } + + if ( mappingTarget.getLabel() == null ) { + mappingTarget.setLabel( new Label() ); + } + chartEntryToLabel( chartEntry, mappingTarget.getLabel() ); + if ( chartEntry.getArtistName() != null ) { + mappingTarget.setName( chartEntry.getArtistName() ); + } + } + + protected void chartEntryToSong(ChartEntry chartEntry, Song mappingTarget) { + if ( chartEntry == null ) { + return; + } + + if ( mappingTarget.getArtist() == null ) { + mappingTarget.setArtist( new Artist() ); + } + chartEntryToArtist( chartEntry, mappingTarget.getArtist() ); + if ( mappingTarget.getPositions() != null ) { + List list = mapPosition( chartEntry.getPosition() ); + if ( list != null ) { + mappingTarget.getPositions().clear(); + mappingTarget.getPositions().addAll( list ); + } + } + else { + List list = mapPosition( chartEntry.getPosition() ); + if ( list != null ) { + mappingTarget.setPositions( list ); + } + } + if ( chartEntry.getSongTitle() != null ) { + mappingTarget.setTitle( chartEntry.getSongTitle() ); + } + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/FishTankMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/FishTankMapperImpl.java new file mode 100644 index 000000000..8632961c5 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nestedtargetproperties/FishTankMapperImpl.java @@ -0,0 +1,92 @@ +/** + * Copyright 2012-2017 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.nestedtargetproperties; + +import javax.annotation.Generated; +import org.mapstruct.ap.test.nestedtargetproperties._target.FishDto; +import org.mapstruct.ap.test.nestedtargetproperties._target.FishTankDto; +import org.mapstruct.ap.test.nestedtargetproperties._target.WaterPlantDto; +import org.mapstruct.ap.test.nestedtargetproperties.source.Fish; +import org.mapstruct.ap.test.nestedtargetproperties.source.FishTank; +import org.mapstruct.ap.test.nestedtargetproperties.source.WaterPlant; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2017-02-07T21:05:06+0100", + comments = "version: , compiler: javac, environment: Java 1.8.0_112 (Oracle Corporation)" +) +public class FishTankMapperImpl implements FishTankMapper { + + @Override + public FishTankDto map(FishTank source) { + if ( source == null ) { + return null; + } + + FishTankDto fishTankDto = new FishTankDto(); + + fishTankDto.setFish( fishTankToFishDto( source ) ); + fishTankDto.setPlant( waterPlantToWaterPlantDto( source.getPlant() ) ); + + return fishTankDto; + } + + private String fishTankFishType(FishTank fishTank) { + + if ( fishTank == null ) { + return null; + } + Fish fish = fishTank.getFish(); + if ( fish == null ) { + return null; + } + String type = fish.getType(); + if ( type == null ) { + return null; + } + return type; + } + + protected FishDto fishTankToFishDto(FishTank fishTank) { + if ( fishTank == null ) { + return null; + } + + FishDto fishDto = new FishDto(); + + String type = fishTankFishType( fishTank ); + if ( type != null ) { + fishDto.setKind( type ); + } + + return fishDto; + } + + protected WaterPlantDto waterPlantToWaterPlantDto(WaterPlant waterPlant) { + if ( waterPlant == null ) { + return null; + } + + WaterPlantDto waterPlantDto = new WaterPlantDto(); + + waterPlantDto.setKind( waterPlant.getKind() ); + + return waterPlantDto; + } +}