From b7450da585fc59708fc43a70753685270c215172 Mon Sep 17 00:00:00 2001 From: Andreas Gudian Date: Mon, 14 Mar 2016 23:09:37 +0100 Subject: [PATCH] #775 Fix mapping method type matching for lower-bounded target type. --- .../internal/model/source/MethodMatcher.java | 26 +++++++++++--- .../IterableWithBoundedElementTypeTest.java | 13 +++++++ .../_775/MapperWithForgedIterableMapping.java | 34 +++++++++++++++++++ 3 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_775/MapperWithForgedIterableMapping.java diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java index e46545c8d..343b5560e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java @@ -224,17 +224,32 @@ public class MethodMatcher { } private enum Assignability { - VISITED_ASSIGNABLE_FROM, VISITED_ASSIGNABLE_TO + VISITED_ASSIGNABLE_FROM, VISITED_ASSIGNABLE_TO; + + Assignability invert() { + return this == VISITED_ASSIGNABLE_FROM + ? VISITED_ASSIGNABLE_TO + : VISITED_ASSIGNABLE_FROM; + } } private class TypeMatcher extends SimpleTypeVisitor6 { private final Assignability assignability; private final Map genericTypesMap; + private final TypeMatcher inverse; TypeMatcher(Assignability assignability, Map genericTypesMap) { super( Boolean.FALSE ); // default value this.assignability = assignability; this.genericTypesMap = genericTypesMap; + this.inverse = new TypeMatcher( this, genericTypesMap ); + } + + TypeMatcher(TypeMatcher inverse, Map genericTypesMap) { + super( Boolean.FALSE ); // default value + this.assignability = inverse.assignability.invert(); + this.genericTypesMap = genericTypesMap; + this.inverse = inverse; } @Override @@ -262,7 +277,7 @@ public class MethodMatcher { if ( assignabilityMatches( t, t1 ) && t.getTypeArguments().size() == t1.getTypeArguments().size() ) { for ( int i = 0; i < t.getTypeArguments().size(); i++ ) { - if ( !t.getTypeArguments().get( i ).accept( this, t1.getTypeArguments().get( i ) ) ) { + if ( !visit( t.getTypeArguments().get( i ), t1.getTypeArguments().get( i ) ) ) { return Boolean.FALSE; } } @@ -272,6 +287,9 @@ public class MethodMatcher { return Boolean.FALSE; } } + else if ( p.getKind() == TypeKind.WILDCARD ) { + return inverse.visit( p, t ); // inverse, as we switch the params + } else { return Boolean.FALSE; } @@ -326,7 +344,7 @@ public class MethodMatcher { case DECLARED: // for example method: String method(? extends String) // isSubType checks range [subtype, type], e.g. isSubtype [Object, String]==true - return typeUtils.isSubtype( p, extendsBound ); + return visit( extendsBound, p ); case TYPEVAR: // for example method: T method(? extends T) @@ -401,7 +419,7 @@ public class MethodMatcher { * @return true if within bounds */ private boolean isWithinBounds(TypeMirror t, TypeParameterElement tpe) { - List bounds = tpe.getBounds(); + List bounds = tpe != null ? tpe.getBounds() : null; if ( t != null && bounds != null ) { for ( TypeMirror bound : bounds ) { if ( !( bound.getKind() == TypeKind.DECLARED && typeUtils.isSubtype( t, bound ) ) ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/IterableWithBoundedElementTypeTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/IterableWithBoundedElementTypeTest.java index 407cb8beb..2fa0a01b9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/IterableWithBoundedElementTypeTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/IterableWithBoundedElementTypeTest.java @@ -32,6 +32,8 @@ import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; * Verifies: *
    *
  • For target properties of type {@link Iterable}, a forged method can be created. + *
  • For target properties of type {@code Iterable}, a custom mapping method that returns + * {@code List} is chosen as property mapping method. *
* * @author Andreas Gudian @@ -40,6 +42,7 @@ import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; @IssueKey("775") @WithClasses({ MapperWithForgedIterableMapping.class, + MapperWithCustomListMapping.class, ListContainer.class, IterableContainer.class }) @@ -54,4 +57,14 @@ public class IterableWithBoundedElementTypeTest { assertThat( result.getValues() ).contains( Integer.valueOf( 42 ), Integer.valueOf( 47 ) ); } + + @Test + public void usesListIntegerMethodForIterableLowerBoundInteger() { + ListContainer source = new ListContainer(); + + source.setValues( Arrays.asList( "42", "47" ) ); + IterableContainer result = MapperWithCustomListMapping.INSTANCE.toContainerWithIterable( source ); + + assertThat( result.getValues() ).contains( Integer.valueOf( 66 ), Integer.valueOf( 71 ) ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/MapperWithForgedIterableMapping.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/MapperWithForgedIterableMapping.java new file mode 100644 index 000000000..09a0eb31e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/MapperWithForgedIterableMapping.java @@ -0,0 +1,34 @@ +/** + * 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.bugs._775; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + */ +@Mapper +public abstract class MapperWithForgedIterableMapping { + public static final MapperWithForgedIterableMapping INSTANCE = + Mappers.getMapper( MapperWithForgedIterableMapping.class ); + + public abstract IterableContainer toContainerWithIterable(ListContainer source); + +}