From 8a3d5418df882a2835da518e96f577af8f20ff0f Mon Sep 17 00:00:00 2001 From: Andreas Gudian Date: Fri, 6 Nov 2015 23:40:25 +0100 Subject: [PATCH] #644 Workaround for problems with the Eclipse implementation of asMemberOf. Refactored SpecificCompilerWorkarounds to be primarily used through a decorated variant of Types that is now used throughout MapStruct. --- processor/pom.xml | 18 +- .../ap/internal/model/common/Type.java | 10 +- .../ap/internal/model/common/TypeFactory.java | 40 +--- .../internal/model/source/MethodMatcher.java | 15 +- .../DefaultModelElementProcessorContext.java | 13 +- .../creation/MappingResolverImpl.java | 3 +- .../ap/internal/util/Executables.java | 2 +- .../EclipseAsMemberOfWorkaround.java | 196 ++++++++++++++++++ .../SpecificCompilerWorkarounds.java | 51 ++++- .../util/workarounds/TypesDecorator.java | 146 +++++++++++++ .../InheritedMappingMethodTest.java | 8 +- 11 files changed, 429 insertions(+), 73 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/EclipseAsMemberOfWorkaround.java rename processor/src/main/java/org/mapstruct/ap/internal/util/{ => workarounds}/SpecificCompilerWorkarounds.java (71%) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/TypesDecorator.java diff --git a/processor/pom.xml b/processor/pom.xml index 652436ce3..285803c48 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -57,7 +57,12 @@ mapstruct provided - + + org.eclipse.tycho + tycho-compiler-jdt + provided + true + junit @@ -84,11 +89,7 @@ javax.inject test - - org.eclipse.tycho - tycho-compiler-jdt - test - + org.codehaus.plexus @@ -238,6 +239,11 @@ hickory ${com.jolira.hickory.version} + + org.eclipse.tycho + tycho-compiler-jdt + ${org.eclipse.tyco.compiler-jdt.version} + 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 611a315c4..10ac290d0 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 @@ -46,7 +46,6 @@ import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.Filters; import org.mapstruct.ap.internal.util.Nouns; -import org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds; /** * Represents (a reference to) the type of a bean property, parameter etc. Types are managed per generated source file. @@ -312,8 +311,8 @@ public class Type extends ModelElement implements Comparable { return new Type( typeUtils, elementUtils, - typeFactory, - SpecificCompilerWorkarounds.erasure( typeUtils, typeMirror ), + typeFactory, + typeUtils.erasure( typeMirror ), typeElement, typeParameters, implementationType, @@ -573,9 +572,8 @@ public class Type extends ModelElement implements Comparable { private boolean isSubType(TypeMirror candidate, Class clazz) { String className = clazz.getCanonicalName(); - TypeMirror classType = - SpecificCompilerWorkarounds.erasure( typeUtils, elementUtils.getTypeElement( className ).asType() ); - return SpecificCompilerWorkarounds.isSubType( typeUtils, candidate, classType ); + TypeMirror classType = typeUtils.erasure( elementUtils.getTypeElement( className ).asType() ); + return typeUtils.isSubtype( candidate, classType ); } /** diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index b5b5f75b7..072efa11e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -48,18 +48,14 @@ import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; -import javax.lang.model.util.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; import javax.lang.model.util.Types; import org.mapstruct.ap.internal.prism.MappingTargetPrism; import org.mapstruct.ap.internal.prism.TargetTypePrism; import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.Collections; -import org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds; -import static org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds.erasure; -import static org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary; +import static org.mapstruct.ap.internal.util.workarounds.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary; /** * Factory creating {@link Type} instances. @@ -82,10 +78,10 @@ public class TypeFactory { this.elementUtils = elementUtils; this.typeUtils = typeUtils; - iterableType = erasure( typeUtils, elementUtils.getTypeElement( Iterable.class.getCanonicalName() ).asType() ); + iterableType = typeUtils.erasure( elementUtils.getTypeElement( Iterable.class.getCanonicalName() ).asType() ); collectionType = - erasure( typeUtils, elementUtils.getTypeElement( Collection.class.getCanonicalName() ).asType() ); - mapType = erasure( typeUtils, elementUtils.getTypeElement( Map.class.getCanonicalName() ).asType() ); + typeUtils.erasure( elementUtils.getTypeElement( Collection.class.getCanonicalName() ).asType() ); + mapType = typeUtils.erasure( elementUtils.getTypeElement( Map.class.getCanonicalName() ).asType() ); implementationTypes.put( Iterable.class.getName(), getType( ArrayList.class ) ); implementationTypes.put( Collection.class.getName(), getType( ArrayList.class ) ); @@ -139,9 +135,9 @@ public class TypeFactory { Type implementationType = getImplementationType( mirror ); - boolean isIterableType = SpecificCompilerWorkarounds.isSubType( typeUtils, mirror, iterableType ); - boolean isCollectionType = SpecificCompilerWorkarounds.isSubType( typeUtils, mirror, collectionType ); - boolean isMapType = SpecificCompilerWorkarounds.isSubType( typeUtils, mirror, mapType ); + boolean isIterableType = typeUtils.isSubtype( mirror, iterableType ); + boolean isCollectionType = typeUtils.isSubtype( mirror, collectionType ); + boolean isMapType = typeUtils.isSubtype( mirror, mapType ); boolean isEnumType; boolean isInterface; @@ -159,7 +155,7 @@ public class TypeFactory { isInterface = declaredType.asElement().getKind() == ElementKind.INTERFACE; name = declaredType.asElement().getSimpleName().toString(); - typeElement = declaredType.asElement().accept( new TypeElementRetrievalVisitor(), null ); + typeElement = (TypeElement) declaredType.asElement(); if ( typeElement != null ) { packageName = elementUtils.getPackageOf( typeElement ).getQualifiedName().toString(); @@ -177,8 +173,7 @@ public class TypeFactory { if ( componentTypeMirror.getKind() == TypeKind.DECLARED ) { DeclaredType declaredType = (DeclaredType) componentTypeMirror; - TypeElement componentTypeElement = - declaredType.asElement().accept( new TypeElementRetrievalVisitor(), null ); + TypeElement componentTypeElement = (TypeElement) declaredType.asElement(); name = componentTypeElement.getSimpleName().toString() + "[]"; packageName = elementUtils.getPackageOf( componentTypeElement ).getQualifiedName().toString(); @@ -262,8 +257,7 @@ public class TypeFactory { public ExecutableType getMethodType(TypeElement includingType, ExecutableElement method) { DeclaredType asType = (DeclaredType) replaceTypeElementIfNecessary( elementUtils, includingType ).asType(); TypeMirror asMemberOf = typeUtils.asMemberOf( asType, method ); - ExecutableType methodType = asMemberOf.accept( new ExecutableTypeRetrievalVisitor(), null ); - return methodType; + return (ExecutableType) asMemberOf; } public Parameter getSingleParameter(TypeElement includingType, ExecutableElement method) { @@ -421,20 +415,6 @@ public class TypeFactory { return imported; } - private static class TypeElementRetrievalVisitor extends SimpleElementVisitor6 { - @Override - public TypeElement visitType(TypeElement e, Void p) { - return e; - } - } - - private static class ExecutableTypeRetrievalVisitor extends SimpleTypeVisitor6 { - @Override - public ExecutableType visitExecutable(ExecutableType t, Void p) { - return t; - } - } - /** * Converts any collection type, e.g. {@code List} to {@code Collection} and any map type, e.g. * {@code HashMap} to {@code Map}. 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 607d6f5a4..474cd1a65 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 @@ -39,7 +39,6 @@ import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import static org.mapstruct.ap.internal.util.Collections.hasNonNullElements; -import static org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds.isSubType; /** * SourceMethodMatcher $8.4 of the JavaLanguage specification describes a method body as such: @@ -232,7 +231,7 @@ public class MethodMatcher { private final Assignability assignability; private final Map genericTypesMap; - public TypeMatcher(Assignability assignability, Map genericTypesMap) { + TypeMatcher(Assignability assignability, Map genericTypesMap) { super( Boolean.FALSE ); // default value this.assignability = assignability; this.genericTypesMap = genericTypesMap; @@ -302,8 +301,8 @@ public class MethodMatcher { // check if types are in bound TypeMirror lowerBound = t.getLowerBound(); TypeMirror upperBound = t.getUpperBound(); - if ( ( isNullType( lowerBound ) || isSubType( typeUtils, lowerBound, p ) ) - && ( isNullType( upperBound ) || isSubType( typeUtils, p, upperBound ) ) ) { + if ( ( isNullType( lowerBound ) || typeUtils.isSubtype( lowerBound, p ) ) + && ( isNullType( upperBound ) || typeUtils.isSubtype( p, upperBound ) ) ) { genericTypesMap.put( t, p ); return Boolean.TRUE; } @@ -327,7 +326,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 isSubType( typeUtils, p, extendsBound ); + return typeUtils.isSubtype( p, extendsBound ); case TYPEVAR: // for example method: T method(? extends T) @@ -349,7 +348,7 @@ public class MethodMatcher { // for example method: String method(? super String) // to check super type, we can simply reverse the argument, but that would initially yield // a result: bounds = tpe.getBounds(); if ( t != null && bounds != null ) { for ( TypeMirror bound : bounds ) { - if ( !( bound.getKind() == TypeKind.DECLARED && isSubType( typeUtils, t, bound ) ) ) { + if ( !( bound.getKind() == TypeKind.DECLARED && typeUtils.isSubtype( t, bound ) ) ) { return false; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java index 26ff46e38..72f2acd30 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java @@ -33,6 +33,7 @@ import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.processor.ModelElementProcessor.ProcessorContext; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.workarounds.TypesDecorator; import org.mapstruct.ap.internal.version.VersionInformation; /** @@ -47,13 +48,15 @@ public class DefaultModelElementProcessorContext implements ProcessorContext { private final Options options; private final TypeFactory typeFactory; private final VersionInformation versionInformation; + private final Types delegatingTypes; public DefaultModelElementProcessorContext(ProcessingEnvironment processingEnvironment, Options options) { this.processingEnvironment = processingEnvironment; this.messager = new DelegatingMessager( processingEnvironment.getMessager() ); + this.delegatingTypes = new TypesDecorator( processingEnvironment ); this.typeFactory = new TypeFactory( processingEnvironment.getElementUtils(), - processingEnvironment.getTypeUtils() + delegatingTypes ); this.options = options; this.versionInformation = DefaultVersionInformation.fromProcessingEnvironment( processingEnvironment ); @@ -66,7 +69,7 @@ public class DefaultModelElementProcessorContext implements ProcessorContext { @Override public Types getTypeUtils() { - return processingEnvironment.getTypeUtils(); + return delegatingTypes; } @Override @@ -99,19 +102,19 @@ public class DefaultModelElementProcessorContext implements ProcessorContext { return messager.isErroneous(); } - private static class DelegatingMessager implements FormattingMessager { + private static final class DelegatingMessager implements FormattingMessager { private final Messager delegate; private boolean isErroneous = false; - public DelegatingMessager(Messager delegate) { + DelegatingMessager(Messager delegate) { this.delegate = delegate; } @Override public void printMessage(Message msg, Object... args) { String message = String.format( msg.getDescription(), args ); - delegate.printMessage( msg.getDiagnosticKind(), message); + delegate.printMessage( msg.getDiagnosticKind(), message ); if ( msg.getDiagnosticKind() == Kind.ERROR ) { isErroneous = true; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index 8566fbc09..2ac8d1e05 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -47,7 +47,6 @@ import org.mapstruct.ap.internal.model.source.selector.MethodSelectors; import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; -import org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds; import org.mapstruct.ap.internal.util.Strings; /** @@ -559,7 +558,7 @@ public class MappingResolverImpl implements MappingResolver { ? typeFactory.getType( Object.class ).getTypeMirror() : targetType.getTypeParameters().get( 0 ).getTypeMirror(); - return SpecificCompilerWorkarounds.isAssignable( typeUtils, sourceElementType, targetElementType ); + return typeUtils.isAssignable( sourceElementType, targetElementType ); } /** diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Executables.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Executables.java index 8debfdd64..3ba3aadb8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Executables.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Executables.java @@ -38,7 +38,7 @@ import org.mapstruct.ap.spi.AccessorNamingStrategy; import org.mapstruct.ap.spi.MethodType; import static javax.lang.model.util.ElementFilter.methodsIn; -import static org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary; +import static org.mapstruct.ap.internal.util.workarounds.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary; /** * Provides functionality around {@link ExecutableElement}s. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/EclipseAsMemberOfWorkaround.java b/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/EclipseAsMemberOfWorkaround.java new file mode 100644 index 000000000..af895a4e9 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/EclipseAsMemberOfWorkaround.java @@ -0,0 +1,196 @@ +/** + * Copyright 2012-2015 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.internal.util.workarounds; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.internal.compiler.apt.dispatch.BaseProcessingEnvImpl; +import org.eclipse.jdt.internal.compiler.apt.model.ElementImpl; +import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; +import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; + +/** + * Contains the workaround for {@link Types#asMemberOf(DeclaredType, Element)} using Eclipse implementation types. + *

+ * This class may only be loaded when running within Eclipse + * + * @author Andreas Gudian + */ +final class EclipseAsMemberOfWorkaround { + private EclipseAsMemberOfWorkaround() { + } + + /** + * Eclipse-specific implementation of {@link Types#asMemberOf(DeclaredType, Element)}. + *

+ * Returns {@code null} if the implementation could not determine the result. + */ + static TypeMirror asMemberOf(ProcessingEnvironment environment, DeclaredType containing, + Element element) { + + ElementImpl elementImpl = tryCast( element, ElementImpl.class ); + BaseProcessingEnvImpl env = tryCast( environment, BaseProcessingEnvImpl.class ); + + if ( elementImpl == null || env == null ) { + return null; + } + + ReferenceBinding referenceBinding = + (ReferenceBinding) ( (ElementImpl) environment.getTypeUtils().asElement( containing ) )._binding; + + MethodBinding methodBinding = (MethodBinding) elementImpl._binding; + + // matches in super-classes have priority + MethodBinding inSuperclassHiearchy = findInSuperclassHierarchy( methodBinding, referenceBinding ); + + if ( inSuperclassHiearchy != null ) { + return env.getFactory().newTypeMirror( inSuperclassHiearchy ); + } + + // if nothing was found, traverse the interfaces and collect all candidate methods that match + List candidatesFromInterfaces = new ArrayList(); + + collectFromInterfaces( + methodBinding, + referenceBinding, + new HashSet(), + candidatesFromInterfaces ); + + // there can be multiple matches for the same method name from adjacent interface hierarchies. + Collections.sort( candidatesFromInterfaces, MostSpecificMethodBindingComparator.INSTANCE ); + + if ( !candidatesFromInterfaces.isEmpty() ) { + // return the most specific match + return env.getFactory().newTypeMirror( candidatesFromInterfaces.get( 0 ) ); + } + + return null; + } + + private static T tryCast(Object instance, Class type) { + if ( instance != null && type.isInstance( instance ) ) { + return type.cast( instance ); + } + + return null; + } + + private static void collectFromInterfaces(MethodBinding methodBinding, ReferenceBinding typeBinding, + Set visitedTypes, List found) { + if ( typeBinding == null ) { + return; + } + + // also check the interfaces of the superclass hierarchy (the superclasses themselves don't contain a match, + // we checked that already) + collectFromInterfaces( methodBinding, typeBinding.superclass(), visitedTypes, found ); + + for ( ReferenceBinding ifc : typeBinding.superInterfaces() ) { + if ( visitedTypes.contains( ifc ) ) { + continue; + } + + visitedTypes.add( ifc ); + + // finding a match in one interface + MethodBinding f = findMatchingMethodBinding( methodBinding, ifc.methods() ); + + if ( f == null ) { + collectFromInterfaces( methodBinding, ifc, visitedTypes, found ); + } + else { + // no need for recursion if we found a candidate in this type already + found.add( f ); + } + } + } + + /** + * @param baseMethod binding to compare against + * @param methods the candidate methods + * @return The method from the list of candidates that matches the name and original/erasure of + * {@code methodBinding}, or {@code null} if none was found. + */ + private static MethodBinding findMatchingMethodBinding(MethodBinding baseMethod, MethodBinding[] methods) { + for ( MethodBinding method : methods ) { + if ( CharOperation.equals( method.selector, baseMethod.selector ) + && ( method.original() == baseMethod || method.areParameterErasuresEqual( baseMethod ) ) ) { + return method; + } + } + + return null; + } + + private static MethodBinding findInSuperclassHierarchy(MethodBinding baseMethod, ReferenceBinding typeBinding) { + while ( typeBinding != null ) { + MethodBinding matching = findMatchingMethodBinding( baseMethod, typeBinding.methods() ); + if ( matching != null ) { + return matching; + } + + typeBinding = typeBinding.superclass(); + } + + return null; + } + + /** + * Compares MethodBindings by their signature: the more specific method is considered lower. + * + * @author Andreas Gudian + */ + private static final class MostSpecificMethodBindingComparator implements Comparator { + private static final MostSpecificMethodBindingComparator INSTANCE = new MostSpecificMethodBindingComparator(); + + @Override + public int compare(MethodBinding first, MethodBinding second) { + boolean firstParamsAssignableFromSecond = + first.areParametersCompatibleWith( second.parameters ); + boolean secondParamsAssignableFromFirst = + second.areParametersCompatibleWith( first.parameters ); + + if ( firstParamsAssignableFromSecond != secondParamsAssignableFromFirst ) { + return firstParamsAssignableFromSecond ? 1 : -1; + } + + if ( TypeBinding.equalsEquals( first.returnType, second.returnType ) ) { + return 0; + } + + boolean firstReturnTypeAssignableFromSecond = + second.returnType.isCompatibleWith( first.returnType ); + + return firstReturnTypeAssignableFromSecond ? 1 : -1; + } + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/SpecificCompilerWorkarounds.java b/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/SpecificCompilerWorkarounds.java similarity index 71% rename from processor/src/main/java/org/mapstruct/ap/internal/util/SpecificCompilerWorkarounds.java rename to processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/SpecificCompilerWorkarounds.java index e8b86dd20..920154269 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/SpecificCompilerWorkarounds.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/SpecificCompilerWorkarounds.java @@ -16,9 +16,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mapstruct.ap.internal.util; +package org.mapstruct.ap.internal.util.workarounds; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; @@ -31,14 +34,10 @@ import javax.lang.model.util.Types; * @author Andreas Gudian */ public class SpecificCompilerWorkarounds { - private SpecificCompilerWorkarounds() { } - /** - * Tests whether one type is assignable to another. - * - *

- * Work-around for a bug most likely related to problem solved with {@link #isSubType} + /** + * Tests whether one type is assignable to another, checking for VOID first. * * @param types the type utils * @param t1 the first type @@ -46,12 +45,12 @@ public class SpecificCompilerWorkarounds { * @return {@code true} if and only if the first type is assignable to the second * @throws IllegalArgumentException if given an executable or package type */ - public static boolean isAssignable(Types types, TypeMirror t1, TypeMirror t2) { + static boolean isAssignable(Types types, TypeMirror t1, TypeMirror t2) { if ( t1.getKind() == TypeKind.VOID ) { return false; } - return types.isAssignable( erasure( types, t1 ), erasure( types, t2 ) ); + return types.isAssignable( t1, t2 ); } /** @@ -66,7 +65,7 @@ public class SpecificCompilerWorkarounds { * @return {@code true} if and only if the first type is a subtype of the second * @throws IllegalArgumentException if given an executable or package type */ - public static boolean isSubType(Types types, TypeMirror t1, TypeMirror t2) { + static boolean isSubtype(Types types, TypeMirror t1, TypeMirror t2) { if ( t1.getKind() == TypeKind.VOID ) { return false; } @@ -86,7 +85,7 @@ public class SpecificCompilerWorkarounds { * @return the erasure of the given type * @throws IllegalArgumentException if given a package type */ - public static TypeMirror erasure(Types types, TypeMirror t) { + static TypeMirror erasure(Types types, TypeMirror t) { if ( t.getKind() == TypeKind.VOID || t.getKind() == TypeKind.NULL ) { return t; } @@ -115,4 +114,34 @@ public class SpecificCompilerWorkarounds { } return element; } + + /** + * Workaround for Bugs in the Eclipse implementation of {@link Types#asMemberOf(DeclaredType, Element)}. + * + * @see Eclipse Bug 382590 + * @see Eclipse Bug 481555 + */ + static TypeMirror asMemberOf(Types typeUtils, ProcessingEnvironment env, DeclaredType containing, Element element) { + TypeMirror result = null; + Exception lastException = null; + try { + try { + result = typeUtils.asMemberOf( containing, element ); + } + catch ( IllegalArgumentException e ) { + lastException = e; + + result = EclipseAsMemberOfWorkaround.asMemberOf( env, containing, element ); + } + } + catch ( Exception e ) { + lastException = e; + } + if ( null == result ) { + throw new RuntimeException( "Fallback implementation of asMemberOf didn't work for " + + element + " in " + containing, lastException ); + } + + return result; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/TypesDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/TypesDecorator.java new file mode 100644 index 000000000..265309a6e --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/TypesDecorator.java @@ -0,0 +1,146 @@ +/** + * Copyright 2012-2015 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.internal.util.workarounds; + +import java.util.List; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.NullType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.Types; + +/** + * Replaces the usage of {@link Types} within MapStruct by delegating to the original implementation or to our specific + * workarounds if necessary. + * + * @author Andreas Gudian + */ +public class TypesDecorator implements Types { + private final Types delegate; + private final ProcessingEnvironment processingEnv; + + public TypesDecorator(ProcessingEnvironment processingEnv) { + this.delegate = processingEnv.getTypeUtils(); + this.processingEnv = processingEnv; + } + + @Override + public Element asElement(TypeMirror t) { + return delegate.asElement( t ); + } + + @Override + public boolean isSameType(TypeMirror t1, TypeMirror t2) { + return delegate.isSameType( t1, t2 ); + } + + @Override + public boolean isSubtype(TypeMirror t1, TypeMirror t2) { + return SpecificCompilerWorkarounds.isSubtype( delegate, t1, t2 ); + } + + @Override + public boolean isAssignable(TypeMirror t1, TypeMirror t2) { + return SpecificCompilerWorkarounds.isAssignable( delegate, t1, t2 ); + } + + @Override + public boolean contains(TypeMirror t1, TypeMirror t2) { + return delegate.contains( t1, t2 ); + } + + @Override + public boolean isSubsignature(ExecutableType m1, ExecutableType m2) { + return delegate.isSubsignature( m1, m2 ); + } + + @Override + public List directSupertypes(TypeMirror t) { + return delegate.directSupertypes( t ); + } + + @Override + public TypeMirror erasure(TypeMirror t) { + return SpecificCompilerWorkarounds.erasure( delegate, t ); + } + + @Override + public TypeElement boxedClass(PrimitiveType p) { + return delegate.boxedClass( p ); + } + + @Override + public PrimitiveType unboxedType(TypeMirror t) { + return delegate.unboxedType( t ); + } + + @Override + public TypeMirror capture(TypeMirror t) { + return delegate.capture( t ); + } + + @Override + public PrimitiveType getPrimitiveType(TypeKind kind) { + return delegate.getPrimitiveType( kind ); + } + + @Override + public NullType getNullType() { + return delegate.getNullType(); + } + + @Override + public NoType getNoType(TypeKind kind) { + return delegate.getNoType( kind ); + } + + @Override + public ArrayType getArrayType(TypeMirror componentType) { + return delegate.getArrayType( componentType ); + } + + @Override + public WildcardType getWildcardType(TypeMirror extendsBound, TypeMirror superBound) { + return delegate.getWildcardType( extendsBound, superBound ); + } + + @Override + public DeclaredType getDeclaredType(TypeElement typeElem, TypeMirror... typeArgs) { + return delegate.getDeclaredType( typeElem, typeArgs ); + } + + @Override + public DeclaredType getDeclaredType(DeclaredType containing, TypeElement typeElem, TypeMirror... typeArgs) { + return delegate.getDeclaredType( containing, typeElem, typeArgs ); + } + + @Override + public TypeMirror asMemberOf(DeclaredType containing, Element element) { + return SpecificCompilerWorkarounds.asMemberOf( delegate, processingEnv, containing, element ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/InheritedMappingMethodTest.java b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/InheritedMappingMethodTest.java index a5f851330..6d87be86c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/InheritedMappingMethodTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/InheritedMappingMethodTest.java @@ -18,8 +18,6 @@ */ package org.mapstruct.ap.test.inheritedmappingmethod; -import static org.fest.assertions.Assertions.assertThat; - import org.junit.Test; import org.junit.runner.RunWith; import org.mapstruct.ap.test.inheritedmappingmethod._target.CarDto; @@ -30,6 +28,8 @@ import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import static org.fest.assertions.Assertions.assertThat; + @IssueKey( "274" ) @WithClasses({ Car.class, CarDto.class, UnboundMappable.class, CarMapper.class, // @@ -39,7 +39,7 @@ import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; public class InheritedMappingMethodTest { @Test - public void shouldProvideUnboundedMapperInstance() throws Exception { + public void shouldProvideUnboundedMapperInstance() { UnboundMappable instance = CarMapper.INSTANCE; assertThat( instance ).isNotNull(); } @@ -59,7 +59,7 @@ public class InheritedMappingMethodTest { } @Test - public void shouldProvideBoundedMapperInstance() throws Exception { + public void shouldProvideBoundedMapperInstance() { BoundMappable instance = FastCarMapper.INSTANCE; assertThat( instance ).isNotNull(); }