From 1be3c4dbaa6ae728c6b712d06ba4a54a75d5d4c3 Mon Sep 17 00:00:00 2001 From: "stefan.may" Date: Tue, 2 Aug 2016 09:25:45 +0200 Subject: [PATCH] #853 Enable support for Iterable / Map classes which are not generic, like they are generated by JAXB (e.g. public class StringList extends List) --- .../internal/model/IterableMappingMethod.java | 14 +++-- .../ap/internal/model/MapMappingMethod.java | 15 ++++- .../ap/internal/model/common/Type.java | 26 +++++++++ .../internal/model/IterableMappingMethod.ftl | 3 +- .../ap/internal/model/MapMappingMethod.ftl | 7 +-- .../collection/CollectionMappingTest.java | 56 ++++++++++++++++++- .../mapstruct/ap/test/collection/Source.java | 20 +++++++ .../test/collection/SourceTargetMapper.java | 4 +- .../ap/test/collection/StringToLongMap.java | 30 ++++++++++ .../mapstruct/ap/test/collection/Target.java | 22 +++++++- .../test/collection/TestNonGenericList.java | 29 ++++++++++ 11 files changed, 209 insertions(+), 17 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/StringToLongMap.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/TestNonGenericList.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java index 234a4df86..d09217c24 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java @@ -92,10 +92,12 @@ public class IterableMappingMethod extends MappingMethod { Type sourceParameterType = first( method.getSourceParameters() ).getType(); Type resultType = method.getResultType(); - Type sourceElementType = sourceParameterType.isArrayType() ? sourceParameterType.getComponentType() - : first( sourceParameterType.getTypeParameters() ).getTypeBound(); - Type targetElementType = resultType.isArrayType() ? resultType.getComponentType() - : first( resultType.getTypeParameters() ).getTypeBound(); + Type sourceElementType = + sourceParameterType.isArrayType() ? sourceParameterType.getComponentType() : first( + sourceParameterType.determineTypeArguments( Iterable.class ) ).getTypeBound(); + Type targetElementType = + resultType.isArrayType() ? resultType.getComponentType() : first( + resultType.determineTypeArguments( Iterable.class ) ).getTypeBound(); String loopVariableName = Strings.getSaveVariableName( sourceElementType.getName(), method.getParameterNames() ); @@ -251,7 +253,7 @@ public class IterableMappingMethod extends MappingMethod { return sourceParameterType.getComponentType(); } else { - return sourceParameterType.getTypeParameters().get( 0 ).getTypeBound(); + return sourceParameterType.determineTypeArguments( Iterable.class ).get( 0 ).getTypeBound(); } } @@ -260,7 +262,7 @@ public class IterableMappingMethod extends MappingMethod { return getResultType().getComponentType(); } else { - return getResultType().getTypeParameters().get( 0 ); + return getResultType().determineTypeArguments( Iterable.class ).get( 0 ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java index 9df422ca6..3430753e4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java @@ -21,6 +21,7 @@ package org.mapstruct.ap.internal.model; import static org.mapstruct.ap.internal.util.Collections.first; import java.util.List; +import java.util.Map; import java.util.Set; import org.mapstruct.ap.internal.model.assignment.Assignment; @@ -96,8 +97,9 @@ public class MapMappingMethod extends MappingMethod { public MapMappingMethod build() { - List sourceTypeParams = first( method.getSourceParameters() ).getType().getTypeParameters(); - List resultTypeParams = method.getResultType().getTypeParameters(); + List sourceTypeParams = + first( method.getSourceParameters() ).getType().determineTypeArguments( Map.class ); + List resultTypeParams = method.getResultType().determineTypeArguments( Map.class ); // find mapping method or conversion for key Type keySourceType = sourceTypeParams.get( 0 ).getTypeBound(); @@ -217,6 +219,15 @@ public class MapMappingMethod extends MappingMethod { throw new IllegalStateException( "Method " + this + " has no source parameter." ); } + public List getSourceElementTypes() { + Type sourceParameterType = getSourceParameter().getType(); + return sourceParameterType.determineTypeArguments( Map.class ); + } + + public List getResultElementTypes() { + return getResultType().determineTypeArguments( Map.class ); + } + public Assignment getKeyAssignment() { return keyAssignment; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 2fadbfc03..c0a444d85 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -790,4 +790,30 @@ public class Type extends ModelElement implements Comparable { } return hasEmptyAccessibleContructor; } + + /** + * Searches for the given superclass and collects all type arguments for the given class + * + * @param superclass the superclass or interface the generic type arguments are searched for + * @return a list of type arguments or null, if superclass was not found + */ + public List determineTypeArguments(Class superclass) { + TypeMirror superclassMirror = + typeUtils.erasure( elementUtils.getTypeElement( superclass.getCanonicalName() ).asType() ); + if ( typeUtils.isAssignable( superclassMirror, typeMirror ) + && typeUtils.isAssignable( typeMirror, superclassMirror ) ) { + return getTypeParameters(); + } + + List directSupertypes = typeUtils.directSupertypes( typeMirror ); + for ( TypeMirror supertypemirror : directSupertypes ) { + Type supertype = typeFactory.getType( supertypemirror ); + List supertypeTypeArguments = supertype.determineTypeArguments( superclass ); + if ( supertypeTypeArguments != null ) { + return supertypeTypeArguments; + } + } + + return null; + } } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl index 6705ad7b7..528661f82 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl @@ -133,8 +133,7 @@ <#if resultType.implementationType??> <@includeModel object=resultType.implementationType/> <#else> - <@includeModel object=resultType/> - () + <@includeModel object=resultType/>() diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl index f3756c206..9017d96b9 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl @@ -56,11 +56,11 @@ <#-- key --> <@includeModel object=keyAssignment targetWriteAccessorName=keyVariableName - targetType=resultType.typeParameters[0].typeBound/> + targetType=resultElementTypes[0].typeBound/> <#-- value --> <@includeModel object=valueAssignment targetWriteAccessorName=valueVariableName - targetType=resultType.typeParameters[1].typeBound/> + targetType=resultElementTypes[1].typeBound/> ${resultName}.put( ${keyVariableName}, ${valueVariableName} ); } <#list afterMappingReferences as callback> @@ -91,8 +91,7 @@ <#if resultType.implementationType??> <@includeModel object=resultType.implementationType /> <#else> - <@includeModel object=resultType /> - () + <@includeModel object=resultType />() 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 5eea0a03d..e7b68e5c5 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 @@ -29,13 +29,15 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.fest.assertions.MapAssert; import org.junit.Test; import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -@WithClasses({ Source.class, Target.class, Colour.class, SourceTargetMapper.class, TestList.class, TestMap.class }) +@WithClasses({ Source.class, Target.class, Colour.class, SourceTargetMapper.class, TestList.class, TestMap.class, + TestNonGenericList.class, StringToLongMap.class }) @RunWith(AnnotationProcessorTestRunner.class) public class CollectionMappingTest { @@ -389,4 +391,56 @@ public class CollectionMappingTest { assertThat( source.getEnumSet() ).containsOnly( Colour.BLUE, Colour.GREEN, Colour.RED ); assertThat( target.getEnumSet() ).containsOnly( Colour.BLUE, Colour.GREEN ); } + + @Test + @IssueKey("TODO") + public void shouldMapNonGenericList() { + Source source = new Source(); + source.setStringList3( new ArrayList( Arrays.asList( "Bob", "Alice" ) ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getNonGenericStringList() ).containsExactly( "Bob", "Alice" ); + + // Inverse direction + Target newTarget = new Target(); + TestNonGenericList nonGenericStringList = new TestNonGenericList(); + nonGenericStringList.addAll( Arrays.asList( "Bill", "Bob" ) ); + newTarget.setNonGenericStringList( nonGenericStringList ); + + Source mappedSource = SourceTargetMapper.INSTANCE.targetToSource( newTarget ); + + assertThat( mappedSource ).isNotNull(); + assertThat( mappedSource.getStringList3() ).containsExactly( "Bill", "Bob" ); + } + + @Test + @IssueKey("TODO") + public void shouldMapNonGenericMap() { + Source source = new Source(); + Map map = new HashMap(); + map.put( "Bob", 123L ); + map.put( "Alice", 456L ); + source.setStringLongMapForNonGeneric( map ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getNonGenericMapStringtoLong() ).includes( MapAssert.entry( "Bob", 123L ), + MapAssert.entry( "Alice", 456L ) ); + + // Inverse direction + Target newTarget = new Target(); + StringToLongMap stringToLongMap = new StringToLongMap(); + stringToLongMap.put( "Blue", 321L ); + stringToLongMap.put( "Green", 654L ); + newTarget.setNonGenericMapStringtoLong( stringToLongMap ); + + Source mappedSource = SourceTargetMapper.INSTANCE.targetToSource( newTarget ); + + assertThat( mappedSource ).isNotNull(); + assertThat( mappedSource.getStringLongMapForNonGeneric() ).includes( MapAssert.entry( "Blue", 321L ), + MapAssert.entry( "Green", 654L ) ); + } } 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 9cf974134..60a99781a 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 @@ -49,12 +49,16 @@ public class Source { private Map otherStringLongMap; + private Map stringLongMapForNonGeneric; + private List stringList2; private Set stringSet2; private EnumSet enumSet; + private List stringList3; + public List getStringList() { return stringList; } @@ -174,4 +178,20 @@ public class Source { public void setEnumSet(EnumSet enumSet) { this.enumSet = enumSet; } + + public List getStringList3() { + return stringList3; + } + + public void setStringList3(List stringList3) { + this.stringList3 = stringList3; + } + + public Map getStringLongMapForNonGeneric() { + return stringLongMapForNonGeneric; + } + + public void setStringLongMapForNonGeneric(Map stringLongMapForNonGeneric) { + this.stringLongMapForNonGeneric = stringLongMapForNonGeneric; + } } 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 af60116b7..cae8f6cf7 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 @@ -38,7 +38,9 @@ public interface SourceTargetMapper { @Mapping(source = "integerSet", target = "set"), @Mapping(source = "anotherIntegerSet", target = "anotherStringSet"), @Mapping(source = "stringList2", target = "stringListNoSetter"), - @Mapping(source = "stringSet2", target = "stringListNoSetter2") + @Mapping(source = "stringSet2", target = "stringListNoSetter2"), + @Mapping(source = "stringList3", target = "nonGenericStringList"), + @Mapping(source = "stringLongMapForNonGeneric", target = "nonGenericMapStringtoLong") }) Target sourceToTarget(Source source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/StringToLongMap.java b/processor/src/test/java/org/mapstruct/ap/test/collection/StringToLongMap.java new file mode 100644 index 000000000..631b8f595 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/StringToLongMap.java @@ -0,0 +1,30 @@ +/** + * 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.collection; + +import java.util.HashMap; + +/** + * @author Stefan May + */ +public class StringToLongMap extends HashMap { + + private static final long serialVersionUID = 1L; + +} 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 8ddd9d210..d6ee6f14b 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 @@ -53,11 +53,15 @@ public class Target { private List stringListNoSetter2; - @SuppressWarnings("rawtypes") + @SuppressWarnings( "rawtypes" ) private Set set; private EnumSet enumSet; + private TestNonGenericList nonGenericStringList; + + private StringToLongMap nonGenericMapStringtoLong; + public Target() { otherStringLongMap = Maps.newHashMap(); otherStringLongMap.put( "not-present-after-mapping", 42L ); @@ -184,4 +188,20 @@ public class Target { public void setEnumSet(EnumSet enumSet) { this.enumSet = enumSet; } + + public TestNonGenericList getNonGenericStringList() { + return nonGenericStringList; + } + + public void setNonGenericStringList(TestNonGenericList nonGenericStringList) { + this.nonGenericStringList = nonGenericStringList; + } + + public StringToLongMap getNonGenericMapStringtoLong() { + return nonGenericMapStringtoLong; + } + + public void setNonGenericMapStringtoLong(StringToLongMap nonGenericMapStringtoLong) { + this.nonGenericMapStringtoLong = nonGenericMapStringtoLong; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/TestNonGenericList.java b/processor/src/test/java/org/mapstruct/ap/test/collection/TestNonGenericList.java new file mode 100644 index 000000000..5102c3067 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/TestNonGenericList.java @@ -0,0 +1,29 @@ +/** + * 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.collection; + +import java.util.ArrayList; + +/** + * @author Stefan May + */ +public class TestNonGenericList extends ArrayList { + + private static final long serialVersionUID = 1L; +}