From ae4655a2ba083f8085f472f0d4822e0ba6ac53cc Mon Sep 17 00:00:00 2001 From: Gunnar Morling Date: Sun, 5 May 2013 19:12:19 +0200 Subject: [PATCH] #6 Making primitive conversions usable for iterable types --- .../mapstruct/ap/MapperGenerationVisitor.java | 39 ++++++++++++++- .../mapstruct/ap/conversion/Conversions.java | 3 ++ .../org/mapstruct/ap/model/BeanMapping.java | 16 +++++- .../main/resources/mapper-implementation.ftl | 11 +++- .../collection/CollectionMappingTest.java | 50 +++++++++++++++++++ .../mapstruct/ap/test/collection/Colour.java | 20 ++++++++ .../mapstruct/ap/test/collection/Source.java | 20 ++++++++ .../test/collection/SourceTargetMapper.java | 13 ++++- .../mapstruct/ap/test/collection/Target.java | 20 ++++++++ 9 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/Colour.java diff --git a/processor/src/main/java/org/mapstruct/ap/MapperGenerationVisitor.java b/processor/src/main/java/org/mapstruct/ap/MapperGenerationVisitor.java index c570809da..7d84395c5 100644 --- a/processor/src/main/java/org/mapstruct/ap/MapperGenerationVisitor.java +++ b/processor/src/main/java/org/mapstruct/ap/MapperGenerationVisitor.java @@ -178,12 +178,36 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { ) ); } + + boolean isIterableMapping = method.getSourceType().isIterableType() && method.getTargetType() + .isIterableType(); + + String toConversionString = null; + String fromConversionString = null; + + if ( isIterableMapping ) { + toConversionString = getIterableConversionString( + conversions, + method.getSourceType().getElementType(), + method.getTargetType().getElementType(), + true + ); + fromConversionString = getIterableConversionString( + conversions, + method.getTargetType().getElementType(), + method.getSourceType().getElementType(), + false + ); + } + BeanMapping mapping = new BeanMapping( method.getSourceType(), method.getTargetType(), propertyMappings, mappingMethod, - reverseMappingMethod + reverseMappingMethod, + toConversionString, + fromConversionString ); mappings.add( mapping ); @@ -191,6 +215,19 @@ public class MapperGenerationVisitor extends ElementKindVisitor6 { return mappings; } + private String getIterableConversionString(Conversions conversions, Type sourceElementType, Type targetElementType, boolean isToConversion) { + Conversion conversion = conversions.getConversion( sourceElementType, targetElementType ); + + if ( conversion == null ) { + return null; + } + + return conversion.to( + Introspector.decapitalize( sourceElementType.getName() ), + targetElementType + ); + } + private List getUsedMapperTypes(TypeElement element) { List usedMapperTypes = new LinkedList(); MapperPrism mapperPrism = MapperPrism.getInstanceOn( element ); diff --git a/processor/src/main/java/org/mapstruct/ap/conversion/Conversions.java b/processor/src/main/java/org/mapstruct/ap/conversion/Conversions.java index 82c6f0b49..6f1f50523 100644 --- a/processor/src/main/java/org/mapstruct/ap/conversion/Conversions.java +++ b/processor/src/main/java/org/mapstruct/ap/conversion/Conversions.java @@ -54,6 +54,9 @@ public class Conversions { if ( sourceType.isEnumType() && targetType.equals( typeUtil.getType( stringType ) ) ) { sourceType = typeUtil.getType( enumType ); } + else if ( targetType.isEnumType() && sourceType.equals( typeUtil.getType( stringType ) ) ) { + targetType = typeUtil.getType( enumType ); + } return conversions.get( new Key( sourceType, targetType ) ); } diff --git a/processor/src/main/java/org/mapstruct/ap/model/BeanMapping.java b/processor/src/main/java/org/mapstruct/ap/model/BeanMapping.java index 17015c520..6f8ae8101 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/BeanMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/model/BeanMapping.java @@ -25,15 +25,19 @@ public class BeanMapping { private final MappingMethod mappingMethod; private final MappingMethod reverseMappingMethod; private final boolean isIterableMapping; + private final String toConversion; + private final String fromConversion; public BeanMapping(Type sourceType, Type targetType, List propertyMappings, MappingMethod mappingMethod, - MappingMethod reverseMappingMethod) { + MappingMethod reverseMappingMethod, String toConversion, String fromConversion) { this.sourceType = sourceType; this.targetType = targetType; this.propertyMappings = propertyMappings; this.mappingMethod = mappingMethod; this.reverseMappingMethod = reverseMappingMethod; this.isIterableMapping = sourceType.isIterableType() && targetType.isIterableType(); + this.toConversion = toConversion; + this.fromConversion = fromConversion; } public Type getSourceType() { @@ -60,6 +64,14 @@ public class BeanMapping { return isIterableMapping; } + public String getToConversion() { + return toConversion; + } + + public String getFromConversion() { + return fromConversion; + } + @Override public String toString() { StringBuilder sb = new StringBuilder( "BeanMapping {" ); @@ -76,6 +88,8 @@ public class BeanMapping { sb.append( "\n mappingMethod=" + mappingMethod.toString().replaceAll( "\n", "\n " ) + ',' ); sb.append( "\n reverseMappingMethod=" + reverseMappingMethod + ',' ); + sb.append( "\n toConversion=" + toConversion + ',' ); + sb.append( "\n fromConversion=" + fromConversion + ',' ); sb.append( "\n isIterableMapping=" + isIterableMapping ); sb.append( "\n}" ); diff --git a/processor/src/main/resources/mapper-implementation.ftl b/processor/src/main/resources/mapper-implementation.ftl index 58c1c5d6f..0db4c8be5 100644 --- a/processor/src/main/resources/mapper-implementation.ftl +++ b/processor/src/main/resources/mapper-implementation.ftl @@ -48,7 +48,11 @@ public class ${implementationName} implements ${interfaceName} { <#if beanMapping.targetType.name == "Iterable" && beanMapping.targetType.packageName == "java.lang">${beanMapping.targetType.iterableImplementationType.name}<#else>${beanMapping.targetType.name}<${beanMapping.targetType.elementType.name}> ${beanMapping.targetType.name?uncap_first} = new <#if beanMapping.targetType.iterableImplementationType??>${beanMapping.targetType.iterableImplementationType.name}<#else>${beanMapping.targetType.name}<${beanMapping.targetType.elementType.name}>(); for ( ${beanMapping.sourceType.elementType.name} ${beanMapping.sourceType.elementType.name?uncap_first} : ${beanMapping.mappingMethod.parameterName} ) { + <#if beanMapping.toConversion??> + ${beanMapping.targetType.name?uncap_first}.add( ${beanMapping.toConversion} ); + <#else> ${beanMapping.targetType.name?uncap_first}.add( ${beanMapping.mappingMethod.elementMappingMethod.name}( ${beanMapping.sourceType.elementType.name?uncap_first} ) ); + } return ${beanMapping.targetType.name?uncap_first}; @@ -94,6 +98,7 @@ public class ${implementationName} implements ${interfaceName} { <#if beanMapping.reverseMappingMethod??> <#if beanMapping.reverseMappingMethod.generationRequired == true> <#if beanMapping.iterableMapping == true> + @Override public ${beanMapping.sourceType.name}<${beanMapping.sourceType.elementType.name}> ${beanMapping.reverseMappingMethod.name}(${beanMapping.targetType.name}<${beanMapping.targetType.elementType.name}> ${beanMapping.reverseMappingMethod.parameterName}) { if( ${beanMapping.reverseMappingMethod.parameterName} == null ) { @@ -102,9 +107,13 @@ public class ${implementationName} implements ${interfaceName} { <#-- Use the interface type on the left side, except it is java.lang.Iterable; use the implementation type - if present - on the right side --> <#if beanMapping.sourceType.name == "Iterable" && beanMapping.sourceType.packageName == "java.lang">${beanMapping.sourceType.iterableImplementationType.name}<#else>${beanMapping.sourceType.name}<${beanMapping.sourceType.elementType.name}> ${beanMapping.sourceType.name?uncap_first} = new <#if beanMapping.sourceType.iterableImplementationType??>${beanMapping.sourceType.iterableImplementationType.name}<#else>${beanMapping.sourceType.name}<${beanMapping.sourceType.elementType.name}>(); - + for ( ${beanMapping.targetType.elementType.name} ${beanMapping.targetType.elementType.name?uncap_first} : ${beanMapping.reverseMappingMethod.parameterName} ) { + <#if beanMapping.fromConversion??> + ${beanMapping.sourceType.name?uncap_first}.add( ${beanMapping.fromConversion} ); + <#else> ${beanMapping.sourceType.name?uncap_first}.add( ${beanMapping.reverseMappingMethod.elementMappingMethod.name}( ${beanMapping.targetType.elementType.name?uncap_first} ) ); + } return ${beanMapping.sourceType.name?uncap_first}; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/CollectionMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/CollectionMappingTest.java index c00d42f4f..4fd0f5ba1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/CollectionMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/CollectionMappingTest.java @@ -17,6 +17,7 @@ package org.mapstruct.ap.test.collection; import java.util.ArrayList; import java.util.Arrays; +import java.util.EnumSet; import java.util.HashSet; import java.util.List; @@ -33,6 +34,7 @@ public class CollectionMappingTest extends MapperTestBase { return Arrays.>asList( Source.class, Target.class, + Colour.class, SourceTargetMapper.class ); } @@ -214,4 +216,52 @@ public class CollectionMappingTest extends MapperTestBase { assertThat( target ).isNotNull(); assertThat( target.getSet() ).containsOnly( 1, 2 ); } + + @Test + @IssueKey("6") + public void shouldMapIntegerSetToStringSet() { + Source source = new Source(); + source.setAnotherIntegerSet( new HashSet( Arrays.asList( 1, 2 ) ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getAnotherStringSet() ).containsOnly( "1", "2" ); + } + + @Test + @IssueKey("6") + public void shouldReverseMapIntegerSetToStringSet() { + Target target = new Target(); + target.setAnotherStringSet( new HashSet( Arrays.asList( "1", "2" ) ) ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getAnotherIntegerSet() ).containsOnly( 1, 2 ); + } + + @Test + @IssueKey("6") + public void shouldMapSetOfEnumToStringSet() { + Source source = new Source(); + source.setColours( EnumSet.of( Colour.BLUE, Colour.GREEN ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getColours() ).containsOnly( "BLUE", "GREEN" ); + } + + @Test + @IssueKey("6") + public void shouldReverseMapSetOfEnumToStringSet() { + Target target = new Target(); + target.setColours( new HashSet( Arrays.asList( "BLUE", "GREEN" ) ) ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getColours() ).containsOnly( Colour.GREEN, Colour.BLUE ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/Colour.java b/processor/src/test/java/org/mapstruct/ap/test/collection/Colour.java new file mode 100644 index 000000000..62968100e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/Colour.java @@ -0,0 +1,20 @@ +/** + * Copyright 2012-2013 Gunnar Morling (http://www.gunnarmorling.de/) + * + * 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.collection; + +public enum Colour { + RED, GREEN, BLUE; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/Source.java b/processor/src/test/java/org/mapstruct/ap/test/collection/Source.java index 01867783a..069b74b95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/Source.java @@ -35,6 +35,10 @@ public class Source { private Set integerSet; + private Set anotherIntegerSet; + + private Set colours; + public List getStringList() { return stringList; } @@ -90,4 +94,20 @@ public class Source { public void setIntegerSet(Set integerSet) { this.integerSet = integerSet; } + + public Set getAnotherIntegerSet() { + return anotherIntegerSet; + } + + public void setAnotherIntegerSet(Set anotherIntegerSet) { + this.anotherIntegerSet = anotherIntegerSet; + } + + public Set getColours() { + return colours; + } + + public void setColours(Set colours) { + this.colours = colours; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/SourceTargetMapper.java index d965d7f2a..476b83ec4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/SourceTargetMapper.java @@ -15,6 +15,8 @@ */ package org.mapstruct.ap.test.collection; +import java.util.Set; + import org.mapstruct.Mapper; import org.mapstruct.Mappers; import org.mapstruct.Mapping; @@ -27,9 +29,18 @@ public interface SourceTargetMapper { @Mappings({ @Mapping(source = "integerList", target = "integerCollection"), - @Mapping(source = "integerSet", target = "set") + @Mapping(source = "integerSet", target = "set"), + @Mapping(source = "anotherIntegerSet", target = "anotherStringSet") }) Target sourceToTarget(Source source); Source targetToSource(Target target); + + Set integerSetToStringSet(Set integers); + + Set stringSetToIntegerSet(Set strings); + + Set colourSetToStringSet(Set colours); + + Set stringSetToColourSet(Set colours); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/Target.java b/processor/src/test/java/org/mapstruct/ap/test/collection/Target.java index 68d7dc5eb..4e872da28 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/Target.java @@ -33,6 +33,10 @@ public class Target { private Collection integerCollection; + private Set anotherStringSet; + + private Set colours; + @SuppressWarnings("rawtypes") private Set set; @@ -93,4 +97,20 @@ public class Target { public void setSet(Set set) { this.set = set; } + + public Set getAnotherStringSet() { + return anotherStringSet; + } + + public void setAnotherStringSet(Set anotherStringSet) { + this.anotherStringSet = anotherStringSet; + } + + public void setColours(Set colours) { + this.colours = colours; + } + + public Set getColours() { + return colours; + } }