From e82277e0fc1a9743472e239dd641239fe3da84c4 Mon Sep 17 00:00:00 2001 From: sjaakd Date: Sun, 19 Jan 2014 23:45:39 +0100 Subject: [PATCH] #94 Allowing to map the same source property to several target properties --- .../mapstruct/ap/model/source/Mapping.java | 11 +- .../org/mapstruct/ap/model/source/Method.java | 55 +++---- .../ap/processor/MapperCreationProcessor.java | 143 ++++++++++-------- .../processor/MethodRetrievalProcessor.java | 9 +- .../severaltargets/MultipleSourcesTest.java | 58 +++++++ .../ap/test/severaltargets/Source.java | 36 +++++ .../severaltargets/SourceTargetMapper.java | 40 +++++ .../ap/test/severaltargets/Target.java | 47 ++++++ .../ap/test/severaltargets/TimeAndFormat.java | 46 ++++++ .../severaltargets/TimeAndFormatMapper.java | 36 +++++ 10 files changed, 381 insertions(+), 100 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/severaltargets/MultipleSourcesTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/severaltargets/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/severaltargets/SourceTargetMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/severaltargets/Target.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/severaltargets/TimeAndFormat.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/severaltargets/TimeAndFormatMapper.java diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java b/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java index 2129d40f5..037c71818 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java @@ -18,7 +18,9 @@ */ package org.mapstruct.ap.model.source; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; @@ -44,11 +46,14 @@ public class Mapping { private final AnnotationValue sourceAnnotationValue; private final AnnotationValue targetAnnotationValue; - public static Map fromMappingsPrism(MappingsPrism mappingsAnnotation, Element element) { - Map mappings = new HashMap(); + public static Map> fromMappingsPrism(MappingsPrism mappingsAnnotation, Element element) { + Map> mappings = new HashMap>(); for ( MappingPrism mapping : mappingsAnnotation.value() ) { - mappings.put( mapping.source(), fromMappingPrism( mapping, element ) ); + if (!mappings.containsKey( mapping.source())) { + mappings.put( mapping.source(), new ArrayList() ); + } + mappings.get( mapping.source() ).add( fromMappingPrism( mapping, element ) ); } return mappings; diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/model/source/Method.java index d3e1f984c..52531ce88 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/Method.java @@ -43,25 +43,17 @@ public class Method { private final List parameters; private final Type returnType; - private Map mappings; + private Map> mappings; private IterableMapping iterableMapping; private MapMapping mapMapping; private final Parameter targetParameter; public static Method forMethodRequiringImplementation(ExecutableElement executable, List parameters, - Type returnType, Map mappings, + Type returnType, Map> mappings, IterableMapping iterableMapping, MapMapping mapMapping) { - return new Method( - null, - executable, - parameters, - returnType, - mappings, - iterableMapping, - mapMapping - ); + return new Method( null, executable, parameters, returnType, mappings, iterableMapping, mapMapping ); } public static Method forReferencedMethod(Type declaringMapper, ExecutableElement executable, @@ -72,14 +64,13 @@ public class Method { executable, parameters, returnType, - Collections.emptyMap(), + Collections.> emptyMap(), null, - null - ); + null ); } private Method(Type declaringMapper, ExecutableElement executable, List parameters, Type returnType, - Map mappings, IterableMapping iterableMapping, MapMapping mapMapping) { + Map> mappings, IterableMapping iterableMapping, MapMapping mapMapping) { this.declaringMapper = declaringMapper; this.executable = executable; this.parameters = parameters; @@ -102,9 +93,8 @@ public class Method { } /** - * Returns the mapper type declaring this method if it is not declared by - * the mapper interface currently processed but by another mapper imported - * via {@code Mapper#users()}. + * Returns the mapper type declaring this method if it is not declared by the mapper interface currently processed + * but by another mapper imported via {@code Mapper#users()}. * * @return The declaring mapper type */ @@ -154,11 +144,11 @@ public class Method { return returnType; } - public Map getMappings() { + public Map> getMappings() { return mappings; } - public void setMappings(Map mappings) { + public void setMappings(Map> mappings) { this.mappings = mappings; } @@ -179,11 +169,9 @@ public class Method { } public boolean reverses(Method method) { - return - getSourceParameters().size() == 1 && - method.getSourceParameters().size() == 1 && - equals( getSourceParameters().iterator().next().getType(), method.getResultType() ) && - equals( getResultType(), method.getSourceParameters().iterator().next().getType() ); + return getSourceParameters().size() == 1 && method.getSourceParameters().size() == 1 + && equals( getSourceParameters().iterator().next().getType(), method.getResultType() ) + && equals( getResultType(), method.getSourceParameters().iterator().next().getType() ); } public Parameter getTargetParameter() { @@ -191,13 +179,13 @@ public class Method { } public boolean isIterableMapping() { - return getSourceParameters().size() == 1 && - getSourceParameters().iterator().next().getType().isIterableType() && getResultType().isIterableType(); + return getSourceParameters().size() == 1 && getSourceParameters().iterator().next().getType().isIterableType() + && getResultType().isIterableType(); } public boolean isMapMapping() { - return getSourceParameters().size() == 1 && getSourceParameters().iterator().next().getType().isMapType() && - getResultType().isMapType(); + return getSourceParameters().size() == 1 && getSourceParameters().iterator().next().getType().isMapType() + && getResultType().isMapType(); } private boolean equals(Object o1, Object o2) { @@ -219,12 +207,13 @@ public class Method { } public Mapping getMapping(String targetPropertyName) { - for ( Mapping mapping : mappings.values() ) { - if ( mapping.getTargetName().equals( targetPropertyName ) ) { - return mapping; + for ( Map.Entry> entry : mappings.entrySet() ) { + for ( Mapping mapping : entry.getValue()) { + if ( mapping.getTargetName().equals( targetPropertyName ) ) { + return mapping; + } } } - return null; } diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java index 7325e65bf..f13bdab97 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java @@ -216,11 +216,16 @@ public class MapperCreationProcessor implements ModelElementProcessor reverse(Map mappings) { - Map reversed = new HashMap(); + private Map> reverse(Map> mappings) { + Map> reversed = new HashMap>(); - for ( Mapping mapping : mappings.values() ) { - reversed.put( mapping.getTargetName(), mapping.reverse() ); + for ( List mappingList : mappings.values() ) { + for (Mapping mapping : mappingList) { + if (!reversed.containsKey( mapping.getTargetName())) { + reversed.put( mapping.getTargetName(), new ArrayList()); + } + reversed.get( mapping.getTargetName() ).add( mapping.reverse() ); + } } return reversed; } @@ -237,10 +242,25 @@ public class MapperCreationProcessor implements ModelElementProcessor sourceMappings = method.getMappings().get( sourcePropertyName ); + if (method.getMappings().containsKey( sourcePropertyName ) ) { + for (Mapping sourceMapping : sourceMappings) { + boolean mapsToOtherTarget = !sourceMapping.getTargetName().equals( targetPropertyName ); + if ( executables.getPropertyName( getter ).equals( sourcePropertyName ) && !mapsToOtherTarget ) { + return getPropertyMapping( + methods, + method, + parameter, + getter, + setterMethod, + dateFormat + ); + } + } + } + else if (executables.getPropertyName( getter ).equals( sourcePropertyName )) + { return getPropertyMapping( methods, method, @@ -378,31 +398,17 @@ public class MapperCreationProcessor implements ModelElementProcessor mappedProperties : method.getMappings().values() ) { + for (Mapping mappedProperty : mappedProperties) { + if ( mappedProperty.getSourceParameterName() != null ) { + Parameter sourceParameter = method.getSourceParameter( mappedProperty.getSourceParameterName() ); - if ( sourceParameter == null ) { - messager.printMessage( - Kind.ERROR, - String.format( - "Method has no parameter named \"%s\".", - mappedProperty.getSourceParameterName() - ), - method.getExecutable(), - mappedProperty.getMirror(), - mappedProperty.getSourceAnnotationValue() - ); - foundUnmappedProperty = true; - } - else { - if ( !hasProperty( sourceParameter, mappedProperty.getSourcePropertyName() ) ) { + if ( sourceParameter == null ) { messager.printMessage( Kind.ERROR, String.format( - "The type of parameter \"%s\" has no property named \"%s\".", - mappedProperty.getSourceParameterName(), - mappedProperty.getSourcePropertyName() + "Method has no parameter named \"%s\".", + mappedProperty.getSourceParameterName() ), method.getExecutable(), mappedProperty.getMirror(), @@ -410,41 +416,56 @@ public class MapperCreationProcessor implements ModelElementProcessor getMappings(ExecutableElement method) { - Map mappings = new HashMap(); + private Map> getMappings(ExecutableElement method) { + Map> mappings = new HashMap>(); MappingPrism mappingAnnotation = MappingPrism.getInstanceOn( method ); MappingsPrism mappingsAnnotation = MappingsPrism.getInstanceOn( method ); if ( mappingAnnotation != null ) { - mappings.put( mappingAnnotation.source(), Mapping.fromMappingPrism( mappingAnnotation, method ) ); + if (!mappings.containsKey( mappingAnnotation.source())) { + mappings.put( mappingAnnotation.source(), new ArrayList() ); + } + mappings.get( mappingAnnotation.source() ).add( Mapping.fromMappingPrism( mappingAnnotation, method ) ); } if ( mappingsAnnotation != null ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/severaltargets/MultipleSourcesTest.java b/processor/src/test/java/org/mapstruct/ap/test/severaltargets/MultipleSourcesTest.java new file mode 100644 index 000000000..1fb332758 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/severaltargets/MultipleSourcesTest.java @@ -0,0 +1,58 @@ +/** + * Copyright 2012-2014 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.severaltargets; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import org.mapstruct.ap.testutil.MapperTestBase; +import org.mapstruct.ap.testutil.WithClasses; +import org.testng.annotations.Test; + +import static org.fest.assertions.Assertions.assertThat; +import org.mapstruct.ap.testutil.IssueKey; + +/** + * Test for the generation of implementation of abstract base classes. + * + * @author Sjaak Derksen + */ +@WithClasses({ Source.class, Target.class, SourceTargetMapper.class, TimeAndFormat.class, + TimeAndFormatMapper.class }) +public class MultipleSourcesTest extends MapperTestBase { + + @Test + @IssueKey("94") + public void shouldMapMultipleSources() throws ParseException { + Source source = new Source(); + String sourceFormat = "dd-MM-yyyy"; + SimpleDateFormat dateFormat = new SimpleDateFormat( sourceFormat ); + Date sourceTime = dateFormat.parse( "09-01-2014" ); + TimeAndFormat sourceTimeAndFormat = new TimeAndFormat(); + sourceTimeAndFormat.setTfFormat( sourceFormat ); + sourceTimeAndFormat.setTfTime( sourceTime ); + source.setTimeAndFormat( sourceTimeAndFormat ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getFormat() ).isEqualTo( sourceFormat ); + assertThat( target.getTime() ).isEqualTo( sourceTime ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/severaltargets/Source.java b/processor/src/test/java/org/mapstruct/ap/test/severaltargets/Source.java new file mode 100644 index 000000000..b8e9dd6fa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/severaltargets/Source.java @@ -0,0 +1,36 @@ +/** + * Copyright 2012-2014 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.severaltargets; + +/** + * + * @author Sjaak Derksen + */ +public class Source { + + private TimeAndFormat timeAndFormat; + + public TimeAndFormat getTimeAndFormat() { + return timeAndFormat; + } + + public void setTimeAndFormat(TimeAndFormat timeAndFormat) { + this.timeAndFormat = timeAndFormat; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/severaltargets/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/severaltargets/SourceTargetMapper.java new file mode 100644 index 000000000..2ebb8f42c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/severaltargets/SourceTargetMapper.java @@ -0,0 +1,40 @@ +/** + * Copyright 2012-2014 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.severaltargets; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper(uses = TimeAndFormatMapper.class) +public interface SourceTargetMapper { + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + @Mappings( + { + @Mapping(source = "timeAndFormat", target = "time"), + @Mapping(source = "timeAndFormat", target = "format") + }) + Target sourceToTarget( Source s ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/severaltargets/Target.java b/processor/src/test/java/org/mapstruct/ap/test/severaltargets/Target.java new file mode 100644 index 000000000..3486bae20 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/severaltargets/Target.java @@ -0,0 +1,47 @@ +/** + * Copyright 2012-2014 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.severaltargets; + +import java.util.Date; + +/** + * + * @author Sjaak Derksen + */ +public class Target { + + private String format; + private Date time; + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public Date getTime() { + return time; + } + + public void setTime(Date time) { + this.time = time; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/severaltargets/TimeAndFormat.java b/processor/src/test/java/org/mapstruct/ap/test/severaltargets/TimeAndFormat.java new file mode 100644 index 000000000..b995d4f6d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/severaltargets/TimeAndFormat.java @@ -0,0 +1,46 @@ +/** + * Copyright 2012-2014 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.severaltargets; + +import java.util.Date; + +/** + * + * @author Sjaak Derksen + */ +public class TimeAndFormat { + private Date tfTime; + private String tfFormat; + + public Date getTfTime() { + return tfTime; + } + + public void setTfTime(Date tfTime) { + this.tfTime = tfTime; + } + + public String getTfFormat() { + return tfFormat; + } + + public void setTfFormat(String tfFormat) { + this.tfFormat = tfFormat; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/severaltargets/TimeAndFormatMapper.java b/processor/src/test/java/org/mapstruct/ap/test/severaltargets/TimeAndFormatMapper.java new file mode 100644 index 000000000..235abc0ec --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/severaltargets/TimeAndFormatMapper.java @@ -0,0 +1,36 @@ +/** + * Copyright 2012-2014 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.severaltargets; + +import java.util.Date; + +/** + * + * @author Sjaak Derksen + */ +public class TimeAndFormatMapper { + + public String getFormat(TimeAndFormat t) { + return t.getTfFormat(); + } + + public Date getTime(TimeAndFormat t) { + return t.getTfTime(); + } +}