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 ee45271dd..17015c520 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/BeanMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/model/BeanMapping.java @@ -33,7 +33,7 @@ public class BeanMapping { this.propertyMappings = propertyMappings; this.mappingMethod = mappingMethod; this.reverseMappingMethod = reverseMappingMethod; - this.isIterableMapping = mappingMethod.getElementMappingMethod() != null; + this.isIterableMapping = sourceType.isIterableType() && targetType.isIterableType(); } public Type getSourceType() { @@ -56,7 +56,7 @@ public class BeanMapping { return reverseMappingMethod; } - public boolean getIterableMapping() { + public boolean isIterableMapping() { return isIterableMapping; } diff --git a/processor/src/main/java/org/mapstruct/ap/model/Type.java b/processor/src/main/java/org/mapstruct/ap/model/Type.java index ff2c2a675..d365b12c9 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/model/Type.java @@ -35,20 +35,26 @@ public class Type { Arrays.asList( "boolean", "char", "byte", "short", "int", "long", "float", "double" ) ); + private final static ConcurrentMap defaultIterableImplementationTypes = new ConcurrentHashMap(); private final static ConcurrentMap defaultCollectionImplementationTypes = new ConcurrentHashMap(); static { defaultCollectionImplementationTypes.put( List.class.getName(), forClass( ArrayList.class ) ); defaultCollectionImplementationTypes.put( Set.class.getName(), forClass( HashSet.class ) ); defaultCollectionImplementationTypes.put( Collection.class.getName(), forClass( ArrayList.class ) ); + + defaultIterableImplementationTypes.put( Iterable.class.getName(), forClass( ArrayList.class ) ); + defaultIterableImplementationTypes.putAll( defaultCollectionImplementationTypes ); } private final String packageName; private final String name; private final Type elementType; private final boolean isEnumType; - private final Type implementingType; private final boolean isCollectionType; + private final boolean isIterableType; + private final Type collectionImplementationType; + private final Type iterableImplementationType; public static Type forClass(Class clazz) { Package pakkage = clazz.getPackage(); @@ -59,7 +65,8 @@ public class Type { clazz.getSimpleName(), null, clazz.isEnum(), - Collection.class.isAssignableFrom( clazz ) + Collection.class.isAssignableFrom( clazz ), + Iterable.class.isAssignableFrom( clazz ) ); } else { @@ -68,16 +75,30 @@ public class Type { } public Type(String name) { - this( null, name, null, false, false ); + this( null, name, null, false, false, false ); } - public Type(String packageName, String name, Type elementType, boolean isEnumType, boolean isCollectionType) { + public Type(String packageName, String name, Type elementType, boolean isEnumType, boolean isCollectionType, boolean isIterableType) { this.packageName = packageName; this.name = name; this.elementType = elementType; this.isEnumType = isEnumType; - implementingType = defaultCollectionImplementationTypes.get( packageName + "." + name ); this.isCollectionType = isCollectionType; + this.isIterableType = isIterableType; + + if ( isCollectionType ) { + collectionImplementationType = defaultCollectionImplementationTypes.get( packageName + "." + name ); + } + else { + collectionImplementationType = null; + } + + if ( isIterableType ) { + iterableImplementationType = defaultIterableImplementationTypes.get( packageName + "." + name ); + } + else { + iterableImplementationType = null; + } } public String getPackageName() { @@ -100,14 +121,22 @@ public class Type { return isEnumType; } - public Type getImplementingType() { - return implementingType; + public Type getCollectionImplementationType() { + return collectionImplementationType; + } + + public Type getIterableImplementationType() { + return iterableImplementationType; } public boolean isCollectionType() { return isCollectionType; } + public boolean isIterableType() { + return isIterableType; + } + @Override public String toString() { if ( packageName == null ) { diff --git a/processor/src/main/java/org/mapstruct/ap/util/TypeUtil.java b/processor/src/main/java/org/mapstruct/ap/util/TypeUtil.java index 27ca1a498..f0d2fef8a 100644 --- a/processor/src/main/java/org/mapstruct/ap/util/TypeUtil.java +++ b/processor/src/main/java/org/mapstruct/ap/util/TypeUtil.java @@ -29,18 +29,21 @@ public class TypeUtil { private final Elements elementUtils; private final Types typeUtils; - private TypeMirror collectionType; + private final TypeMirror collectionType; + private final TypeMirror iterableType; public TypeUtil(Elements elementUtils, Types typeUtils) { this.elementUtils = elementUtils; this.typeUtils = typeUtils; collectionType = elementUtils.getTypeElement( Collection.class.getCanonicalName() ).asType(); + iterableType = elementUtils.getTypeElement( Iterable.class.getCanonicalName() ).asType(); } public Type getType(DeclaredType type) { Type elementType = null; - if ( isIterableType( type ) && !type.getTypeArguments().isEmpty() ) { + boolean isIterableType = isIterableType( type ); + if ( isIterableType && !type.getTypeArguments().isEmpty() ) { elementType = retrieveType( type.getTypeArguments().iterator().next() ); } @@ -49,13 +52,17 @@ public class TypeUtil { type.asElement().getSimpleName().toString(), elementType, type.asElement().getKind() == ElementKind.ENUM, - typeUtils.isAssignable( typeUtils.erasure( type ), typeUtils.erasure( collectionType ) ) + isCollectionType( type ), + isIterableType ); } private boolean isIterableType(DeclaredType type) { - TypeMirror iterableType = typeUtils.getDeclaredType( elementUtils.getTypeElement( Iterable.class.getCanonicalName() ) ); - return typeUtils.isSubtype( type, iterableType ); + return typeUtils.isAssignable( typeUtils.erasure( type ), typeUtils.erasure( iterableType ) ); + } + + private boolean isCollectionType(DeclaredType type) { + return typeUtils.isAssignable( typeUtils.erasure( type ), typeUtils.erasure( collectionType ) ); } public Type retrieveType(TypeMirror mirror) { diff --git a/processor/src/main/resources/mapper-implementation.ftl b/processor/src/main/resources/mapper-implementation.ftl index aba8eb272..58c1c5d6f 100644 --- a/processor/src/main/resources/mapper-implementation.ftl +++ b/processor/src/main/resources/mapper-implementation.ftl @@ -44,12 +44,13 @@ public class ${implementationName} implements ${interfaceName} { return null; } - ${beanMapping.targetType.name}<${beanMapping.targetType.elementType.name}> ${beanMapping.targetType.name?uncap_first} = new <#if beanMapping.targetType.implementingType??>${beanMapping.targetType.implementingType.name}<#else>${beanMapping.targetType.name}<${beanMapping.targetType.elementType.name}>(); - + <#-- 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.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} ) { ${beanMapping.targetType.name?uncap_first}.add( ${beanMapping.mappingMethod.elementMappingMethod.name}( ${beanMapping.sourceType.elementType.name?uncap_first} ) ); } - + return ${beanMapping.targetType.name?uncap_first}; } <#else> @@ -77,7 +78,7 @@ public class ${implementationName} implements ${interfaceName} { <#else> <#if propertyMapping.targetType.collectionType == true> if ( ${beanMapping.mappingMethod.parameterName}.get${propertyMapping.sourceName?cap_first}() != null ) { - ${beanMapping.targetType.name?uncap_first}.set${propertyMapping.targetName?cap_first}( new <#if propertyMapping.targetType.implementingType??>${propertyMapping.targetType.implementingType.name}<#else>${propertyMapping.targetType.name}<#if propertyMapping.targetType.elementType??><${propertyMapping.targetType.elementType.name}>( ${beanMapping.mappingMethod.parameterName}.get${propertyMapping.sourceName?cap_first}() ) ); + ${beanMapping.targetType.name?uncap_first}.set${propertyMapping.targetName?cap_first}( new <#if propertyMapping.targetType.collectionImplementationType??>${propertyMapping.targetType.collectionImplementationType.name}<#else>${propertyMapping.targetType.name}<#if propertyMapping.targetType.elementType??><${propertyMapping.targetType.elementType.name}>( ${beanMapping.mappingMethod.parameterName}.get${propertyMapping.sourceName?cap_first}() ) ); } <#else> ${beanMapping.targetType.name?uncap_first}.set${propertyMapping.targetName?cap_first}( ${beanMapping.mappingMethod.parameterName}.get${propertyMapping.sourceName?cap_first}() ); @@ -99,7 +100,8 @@ public class ${implementationName} implements ${interfaceName} { return null; } - ${beanMapping.sourceType.name}<${beanMapping.sourceType.elementType.name}> ${beanMapping.sourceType.name?uncap_first} = new <#if beanMapping.sourceType.implementingType??>${beanMapping.sourceType.implementingType.name}<#else>${beanMapping.sourceType.name}<${beanMapping.sourceType.elementType.name}>(); + <#-- 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} ) { ${beanMapping.sourceType.name?uncap_first}.add( ${beanMapping.reverseMappingMethod.elementMappingMethod.name}( ${beanMapping.targetType.elementType.name?uncap_first} ) ); @@ -132,7 +134,7 @@ public class ${implementationName} implements ${interfaceName} { <#else> <#if propertyMapping.sourceType.collectionType == true> if ( ${beanMapping.reverseMappingMethod.parameterName}.get${propertyMapping.targetName?cap_first}() != null ) { - ${beanMapping.sourceType.name?uncap_first}.set${propertyMapping.sourceName?cap_first}( new <#if propertyMapping.sourceType.implementingType??>${propertyMapping.sourceType.implementingType.name}<#else>${propertyMapping.sourceType.name}<#if propertyMapping.sourceType.elementType??><${propertyMapping.sourceType.elementType.name}>( ${beanMapping.reverseMappingMethod.parameterName}.get${propertyMapping.targetName?cap_first}() ) ); + ${beanMapping.sourceType.name?uncap_first}.set${propertyMapping.sourceName?cap_first}( new <#if propertyMapping.sourceType.collectionImplementationType??>${propertyMapping.sourceType.collectionImplementationType.name}<#else>${propertyMapping.sourceType.name}<#if propertyMapping.sourceType.elementType??><${propertyMapping.sourceType.elementType.name}>( ${beanMapping.reverseMappingMethod.parameterName}.get${propertyMapping.targetName?cap_first}() ) ); } <#else> ${beanMapping.sourceType.name?uncap_first}.set${propertyMapping.sourceName?cap_first}( ${beanMapping.reverseMappingMethod.parameterName}.get${propertyMapping.targetName?cap_first}() ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java index 209356a25..c51a934d8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java @@ -70,4 +70,15 @@ public class DefaultCollectionImplementationTest extends MapperTestBase { assertThat( target ).isNotNull(); assertThat( target.getFooCollection() ).containsOnly( new TargetFoo( "Bob" ), new TargetFoo( "Alice" ) ); } + + @Test + @IssueKey("6") + public void shouldUseDefaultImplementationForIterable() { + Source source = new Source(); + source.setFooIterable( Arrays.asList( new SourceFoo( "Bob" ), new SourceFoo( "Alice" ) ) ); + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getFooIterable() ).containsOnly( new TargetFoo( "Bob" ), new TargetFoo( "Alice" ) ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Source.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Source.java index 50c00e1a8..601716fb4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Source.java @@ -27,6 +27,8 @@ public class Source { private Collection fooCollection; + private Iterable fooIterable; + public List getFooList() { return fooList; } @@ -50,4 +52,12 @@ public class Source { public void setFooCollection(Collection fooCollection) { this.fooCollection = fooCollection; } + + public Iterable getFooIterable() { + return fooIterable; + } + + public void setFooIterable(Iterable fooIterable) { + this.fooIterable = fooIterable; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapper.java index 01b581d50..6aa597ee7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapper.java @@ -15,7 +15,6 @@ */ package org.mapstruct.ap.test.collection.defaultimplementation; - import java.util.Collection; import java.util.List; import java.util.Set; @@ -37,4 +36,6 @@ public interface SourceTargetMapper { Set sourceFoosToTargetFoos(Set foos); Collection sourceFoosToTargetFoos(Collection foos); + + Iterable sourceFoosToTargetFoos(Iterable foos); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Target.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Target.java index f10797491..729afbd2b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Target.java @@ -27,6 +27,8 @@ public class Target { private Collection fooCollection; + private Iterable fooIterable; + public List getFooList() { return fooList; } @@ -50,4 +52,12 @@ public class Target { public void setFooCollection(Collection fooCollection) { this.fooCollection = fooCollection; } + + public Iterable getFooIterable() { + return fooIterable; + } + + public void setFooIterable(Iterable fooIterable) { + this.fooIterable = fooIterable; + } }