#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.

This commit is contained in:
Andreas Gudian 2015-11-06 23:40:25 +01:00
parent 2291f2dedd
commit 8a3d5418df
11 changed files with 429 additions and 73 deletions

View File

@ -57,7 +57,12 @@
<artifactId>mapstruct</artifactId> <artifactId>mapstruct</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-jdt</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<!-- Test --> <!-- Test -->
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
@ -84,11 +89,7 @@
<artifactId>javax.inject</artifactId> <artifactId>javax.inject</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-jdt</artifactId>
<scope>test</scope>
</dependency>
<!-- plexus-container-default is a runtime-dependency of the tyco-compiler --> <!-- plexus-container-default is a runtime-dependency of the tyco-compiler -->
<dependency> <dependency>
<groupId>org.codehaus.plexus</groupId> <groupId>org.codehaus.plexus</groupId>
@ -238,6 +239,11 @@
<artifactId>hickory</artifactId> <artifactId>hickory</artifactId>
<version>${com.jolira.hickory.version}</version> <version>${com.jolira.hickory.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-compiler-jdt</artifactId>
<version>${org.eclipse.tyco.compiler-jdt.version}</version>
</dependency>
</dependencies> </dependencies>
</plugin> </plugin>
<plugin> <plugin>

View File

@ -46,7 +46,6 @@ import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism;
import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.Executables;
import org.mapstruct.ap.internal.util.Filters; import org.mapstruct.ap.internal.util.Filters;
import org.mapstruct.ap.internal.util.Nouns; 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. * 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<Type> {
return new Type( return new Type(
typeUtils, typeUtils,
elementUtils, elementUtils,
typeFactory, typeFactory,
SpecificCompilerWorkarounds.erasure( typeUtils, typeMirror ), typeUtils.erasure( typeMirror ),
typeElement, typeElement,
typeParameters, typeParameters,
implementationType, implementationType,
@ -573,9 +572,8 @@ public class Type extends ModelElement implements Comparable<Type> {
private boolean isSubType(TypeMirror candidate, Class<?> clazz) { private boolean isSubType(TypeMirror candidate, Class<?> clazz) {
String className = clazz.getCanonicalName(); String className = clazz.getCanonicalName();
TypeMirror classType = TypeMirror classType = typeUtils.erasure( elementUtils.getTypeElement( className ).asType() );
SpecificCompilerWorkarounds.erasure( typeUtils, elementUtils.getTypeElement( className ).asType() ); return typeUtils.isSubtype( candidate, classType );
return SpecificCompilerWorkarounds.isSubType( typeUtils, candidate, classType );
} }
/** /**

View File

@ -48,18 +48,14 @@ import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements; 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 javax.lang.model.util.Types;
import org.mapstruct.ap.internal.prism.MappingTargetPrism; import org.mapstruct.ap.internal.prism.MappingTargetPrism;
import org.mapstruct.ap.internal.prism.TargetTypePrism; import org.mapstruct.ap.internal.prism.TargetTypePrism;
import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.AnnotationProcessingException;
import org.mapstruct.ap.internal.util.Collections; 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.workarounds.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary;
import static org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary;
/** /**
* Factory creating {@link Type} instances. * Factory creating {@link Type} instances.
@ -82,10 +78,10 @@ public class TypeFactory {
this.elementUtils = elementUtils; this.elementUtils = elementUtils;
this.typeUtils = typeUtils; this.typeUtils = typeUtils;
iterableType = erasure( typeUtils, elementUtils.getTypeElement( Iterable.class.getCanonicalName() ).asType() ); iterableType = typeUtils.erasure( elementUtils.getTypeElement( Iterable.class.getCanonicalName() ).asType() );
collectionType = collectionType =
erasure( typeUtils, elementUtils.getTypeElement( Collection.class.getCanonicalName() ).asType() ); typeUtils.erasure( elementUtils.getTypeElement( Collection.class.getCanonicalName() ).asType() );
mapType = erasure( typeUtils, elementUtils.getTypeElement( Map.class.getCanonicalName() ).asType() ); mapType = typeUtils.erasure( elementUtils.getTypeElement( Map.class.getCanonicalName() ).asType() );
implementationTypes.put( Iterable.class.getName(), getType( ArrayList.class ) ); implementationTypes.put( Iterable.class.getName(), getType( ArrayList.class ) );
implementationTypes.put( Collection.class.getName(), getType( ArrayList.class ) ); implementationTypes.put( Collection.class.getName(), getType( ArrayList.class ) );
@ -139,9 +135,9 @@ public class TypeFactory {
Type implementationType = getImplementationType( mirror ); Type implementationType = getImplementationType( mirror );
boolean isIterableType = SpecificCompilerWorkarounds.isSubType( typeUtils, mirror, iterableType ); boolean isIterableType = typeUtils.isSubtype( mirror, iterableType );
boolean isCollectionType = SpecificCompilerWorkarounds.isSubType( typeUtils, mirror, collectionType ); boolean isCollectionType = typeUtils.isSubtype( mirror, collectionType );
boolean isMapType = SpecificCompilerWorkarounds.isSubType( typeUtils, mirror, mapType ); boolean isMapType = typeUtils.isSubtype( mirror, mapType );
boolean isEnumType; boolean isEnumType;
boolean isInterface; boolean isInterface;
@ -159,7 +155,7 @@ public class TypeFactory {
isInterface = declaredType.asElement().getKind() == ElementKind.INTERFACE; isInterface = declaredType.asElement().getKind() == ElementKind.INTERFACE;
name = declaredType.asElement().getSimpleName().toString(); name = declaredType.asElement().getSimpleName().toString();
typeElement = declaredType.asElement().accept( new TypeElementRetrievalVisitor(), null ); typeElement = (TypeElement) declaredType.asElement();
if ( typeElement != null ) { if ( typeElement != null ) {
packageName = elementUtils.getPackageOf( typeElement ).getQualifiedName().toString(); packageName = elementUtils.getPackageOf( typeElement ).getQualifiedName().toString();
@ -177,8 +173,7 @@ public class TypeFactory {
if ( componentTypeMirror.getKind() == TypeKind.DECLARED ) { if ( componentTypeMirror.getKind() == TypeKind.DECLARED ) {
DeclaredType declaredType = (DeclaredType) componentTypeMirror; DeclaredType declaredType = (DeclaredType) componentTypeMirror;
TypeElement componentTypeElement = TypeElement componentTypeElement = (TypeElement) declaredType.asElement();
declaredType.asElement().accept( new TypeElementRetrievalVisitor(), null );
name = componentTypeElement.getSimpleName().toString() + "[]"; name = componentTypeElement.getSimpleName().toString() + "[]";
packageName = elementUtils.getPackageOf( componentTypeElement ).getQualifiedName().toString(); packageName = elementUtils.getPackageOf( componentTypeElement ).getQualifiedName().toString();
@ -262,8 +257,7 @@ public class TypeFactory {
public ExecutableType getMethodType(TypeElement includingType, ExecutableElement method) { public ExecutableType getMethodType(TypeElement includingType, ExecutableElement method) {
DeclaredType asType = (DeclaredType) replaceTypeElementIfNecessary( elementUtils, includingType ).asType(); DeclaredType asType = (DeclaredType) replaceTypeElementIfNecessary( elementUtils, includingType ).asType();
TypeMirror asMemberOf = typeUtils.asMemberOf( asType, method ); TypeMirror asMemberOf = typeUtils.asMemberOf( asType, method );
ExecutableType methodType = asMemberOf.accept( new ExecutableTypeRetrievalVisitor(), null ); return (ExecutableType) asMemberOf;
return methodType;
} }
public Parameter getSingleParameter(TypeElement includingType, ExecutableElement method) { public Parameter getSingleParameter(TypeElement includingType, ExecutableElement method) {
@ -421,20 +415,6 @@ public class TypeFactory {
return imported; return imported;
} }
private static class TypeElementRetrievalVisitor extends SimpleElementVisitor6<TypeElement, Void> {
@Override
public TypeElement visitType(TypeElement e, Void p) {
return e;
}
}
private static class ExecutableTypeRetrievalVisitor extends SimpleTypeVisitor6<ExecutableType, Void> {
@Override
public ExecutableType visitExecutable(ExecutableType t, Void p) {
return t;
}
}
/** /**
* Converts any collection type, e.g. {@code List<T>} to {@code Collection<T>} and any map type, e.g. * Converts any collection type, e.g. {@code List<T>} to {@code Collection<T>} and any map type, e.g.
* {@code HashMap<K,V>} to {@code Map<K,V>}. * {@code HashMap<K,V>} to {@code Map<K,V>}.

View File

@ -39,7 +39,6 @@ import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.common.TypeFactory;
import static org.mapstruct.ap.internal.util.Collections.hasNonNullElements; 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: * 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 Assignability assignability;
private final Map<TypeVariable, TypeMirror> genericTypesMap; private final Map<TypeVariable, TypeMirror> genericTypesMap;
public TypeMatcher(Assignability assignability, Map<TypeVariable, TypeMirror> genericTypesMap) { TypeMatcher(Assignability assignability, Map<TypeVariable, TypeMirror> genericTypesMap) {
super( Boolean.FALSE ); // default value super( Boolean.FALSE ); // default value
this.assignability = assignability; this.assignability = assignability;
this.genericTypesMap = genericTypesMap; this.genericTypesMap = genericTypesMap;
@ -302,8 +301,8 @@ public class MethodMatcher {
// check if types are in bound // check if types are in bound
TypeMirror lowerBound = t.getLowerBound(); TypeMirror lowerBound = t.getLowerBound();
TypeMirror upperBound = t.getUpperBound(); TypeMirror upperBound = t.getUpperBound();
if ( ( isNullType( lowerBound ) || isSubType( typeUtils, lowerBound, p ) ) if ( ( isNullType( lowerBound ) || typeUtils.isSubtype( lowerBound, p ) )
&& ( isNullType( upperBound ) || isSubType( typeUtils, p, upperBound ) ) ) { && ( isNullType( upperBound ) || typeUtils.isSubtype( p, upperBound ) ) ) {
genericTypesMap.put( t, p ); genericTypesMap.put( t, p );
return Boolean.TRUE; return Boolean.TRUE;
} }
@ -327,7 +326,7 @@ public class MethodMatcher {
case DECLARED: case DECLARED:
// for example method: String method(? extends String) // for example method: String method(? extends String)
// isSubType checks range [subtype, type], e.g. isSubtype [Object, String]==true // isSubType checks range [subtype, type], e.g. isSubtype [Object, String]==true
return isSubType( typeUtils, p, extendsBound ); return typeUtils.isSubtype( p, extendsBound );
case TYPEVAR: case TYPEVAR:
// for example method: <T extends String & Serializable> T method(? extends T) // for example method: <T extends String & Serializable> T method(? extends T)
@ -349,7 +348,7 @@ public class MethodMatcher {
// for example method: String method(? super String) // for example method: String method(? super String)
// to check super type, we can simply reverse the argument, but that would initially yield // to check super type, we can simply reverse the argument, but that would initially yield
// a result: <type, superType] (so type not included) so we need to check sameType also. // a result: <type, superType] (so type not included) so we need to check sameType also.
return isSubType( typeUtils, superBound, p ) || typeUtils.isSameType( p, superBound ); return typeUtils.isSubtype( superBound, p ) || typeUtils.isSameType( p, superBound );
case TYPEVAR: case TYPEVAR:
@ -367,7 +366,7 @@ public class MethodMatcher {
// to check super type, we can simply reverse the argument, but that would initially yield // to check super type, we can simply reverse the argument, but that would initially yield
// a result: <type, superType] (so type not included) so we need to check sameType also. // a result: <type, superType] (so type not included) so we need to check sameType also.
TypeMirror superBoundAsDeclared = typeParameter.getBounds().get( 0 ); TypeMirror superBoundAsDeclared = typeParameter.getBounds().get( 0 );
return ( isSubType( typeUtils, superBoundAsDeclared, p ) || typeUtils.isSameType( return ( typeUtils.isSubtype( superBoundAsDeclared, p ) || typeUtils.isSameType(
p, p,
superBoundAsDeclared ) ); superBoundAsDeclared ) );
default: default:
@ -405,7 +404,7 @@ public class MethodMatcher {
List<? extends TypeMirror> bounds = tpe.getBounds(); List<? extends TypeMirror> bounds = tpe.getBounds();
if ( t != null && bounds != null ) { if ( t != null && bounds != null ) {
for ( TypeMirror bound : bounds ) { for ( TypeMirror bound : bounds ) {
if ( !( bound.getKind() == TypeKind.DECLARED && isSubType( typeUtils, t, bound ) ) ) { if ( !( bound.getKind() == TypeKind.DECLARED && typeUtils.isSubtype( t, bound ) ) ) {
return false; return false;
} }
} }

View File

@ -33,6 +33,7 @@ import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.processor.ModelElementProcessor.ProcessorContext; import org.mapstruct.ap.internal.processor.ModelElementProcessor.ProcessorContext;
import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.workarounds.TypesDecorator;
import org.mapstruct.ap.internal.version.VersionInformation; import org.mapstruct.ap.internal.version.VersionInformation;
/** /**
@ -47,13 +48,15 @@ public class DefaultModelElementProcessorContext implements ProcessorContext {
private final Options options; private final Options options;
private final TypeFactory typeFactory; private final TypeFactory typeFactory;
private final VersionInformation versionInformation; private final VersionInformation versionInformation;
private final Types delegatingTypes;
public DefaultModelElementProcessorContext(ProcessingEnvironment processingEnvironment, Options options) { public DefaultModelElementProcessorContext(ProcessingEnvironment processingEnvironment, Options options) {
this.processingEnvironment = processingEnvironment; this.processingEnvironment = processingEnvironment;
this.messager = new DelegatingMessager( processingEnvironment.getMessager() ); this.messager = new DelegatingMessager( processingEnvironment.getMessager() );
this.delegatingTypes = new TypesDecorator( processingEnvironment );
this.typeFactory = new TypeFactory( this.typeFactory = new TypeFactory(
processingEnvironment.getElementUtils(), processingEnvironment.getElementUtils(),
processingEnvironment.getTypeUtils() delegatingTypes
); );
this.options = options; this.options = options;
this.versionInformation = DefaultVersionInformation.fromProcessingEnvironment( processingEnvironment ); this.versionInformation = DefaultVersionInformation.fromProcessingEnvironment( processingEnvironment );
@ -66,7 +69,7 @@ public class DefaultModelElementProcessorContext implements ProcessorContext {
@Override @Override
public Types getTypeUtils() { public Types getTypeUtils() {
return processingEnvironment.getTypeUtils(); return delegatingTypes;
} }
@Override @Override
@ -99,19 +102,19 @@ public class DefaultModelElementProcessorContext implements ProcessorContext {
return messager.isErroneous(); return messager.isErroneous();
} }
private static class DelegatingMessager implements FormattingMessager { private static final class DelegatingMessager implements FormattingMessager {
private final Messager delegate; private final Messager delegate;
private boolean isErroneous = false; private boolean isErroneous = false;
public DelegatingMessager(Messager delegate) { DelegatingMessager(Messager delegate) {
this.delegate = delegate; this.delegate = delegate;
} }
@Override @Override
public void printMessage(Message msg, Object... args) { public void printMessage(Message msg, Object... args) {
String message = String.format( msg.getDescription(), args ); String message = String.format( msg.getDescription(), args );
delegate.printMessage( msg.getDiagnosticKind(), message); delegate.printMessage( msg.getDiagnosticKind(), message );
if ( msg.getDiagnosticKind() == Kind.ERROR ) { if ( msg.getDiagnosticKind() == Kind.ERROR ) {
isErroneous = true; isErroneous = true;
} }

View File

@ -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.model.source.selector.SelectionCriteria;
import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Message;
import org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
/** /**
@ -559,7 +558,7 @@ public class MappingResolverImpl implements MappingResolver {
? typeFactory.getType( Object.class ).getTypeMirror() ? typeFactory.getType( Object.class ).getTypeMirror()
: targetType.getTypeParameters().get( 0 ).getTypeMirror(); : targetType.getTypeParameters().get( 0 ).getTypeMirror();
return SpecificCompilerWorkarounds.isAssignable( typeUtils, sourceElementType, targetElementType ); return typeUtils.isAssignable( sourceElementType, targetElementType );
} }
/** /**

View File

@ -38,7 +38,7 @@ import org.mapstruct.ap.spi.AccessorNamingStrategy;
import org.mapstruct.ap.spi.MethodType; import org.mapstruct.ap.spi.MethodType;
import static javax.lang.model.util.ElementFilter.methodsIn; 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. * Provides functionality around {@link ExecutableElement}s.

View File

@ -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.
* <p>
* <strong>This class may only be loaded when running within Eclipse</strong>
*
* @author Andreas Gudian
*/
final class EclipseAsMemberOfWorkaround {
private EclipseAsMemberOfWorkaround() {
}
/**
* Eclipse-specific implementation of {@link Types#asMemberOf(DeclaredType, Element)}.
* <p>
* 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<MethodBinding> candidatesFromInterfaces = new ArrayList<MethodBinding>();
collectFromInterfaces(
methodBinding,
referenceBinding,
new HashSet<ReferenceBinding>(),
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> T tryCast(Object instance, Class<T> type) {
if ( instance != null && type.isInstance( instance ) ) {
return type.cast( instance );
}
return null;
}
private static void collectFromInterfaces(MethodBinding methodBinding, ReferenceBinding typeBinding,
Set<ReferenceBinding> visitedTypes, List<MethodBinding> 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 <em>lower</em>.
*
* @author Andreas Gudian
*/
private static final class MostSpecificMethodBindingComparator implements Comparator<MethodBinding> {
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;
}
}
}

View File

@ -16,9 +16,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements; import javax.lang.model.util.Elements;
@ -31,14 +34,10 @@ import javax.lang.model.util.Types;
* @author Andreas Gudian * @author Andreas Gudian
*/ */
public class SpecificCompilerWorkarounds { public class SpecificCompilerWorkarounds {
private SpecificCompilerWorkarounds() { } private SpecificCompilerWorkarounds() { }
/** /**
* Tests whether one type is assignable to another. * Tests whether one type is assignable to another, checking for VOID first.
*
* <p>
* Work-around for a bug most likely related to problem solved with {@link #isSubType}
* *
* @param types the type utils * @param types the type utils
* @param t1 the first type * @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 * @return {@code true} if and only if the first type is assignable to the second
* @throws IllegalArgumentException if given an executable or package type * @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 ) { if ( t1.getKind() == TypeKind.VOID ) {
return false; 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 * @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 * @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 ) { if ( t1.getKind() == TypeKind.VOID ) {
return false; return false;
} }
@ -86,7 +85,7 @@ public class SpecificCompilerWorkarounds {
* @return the erasure of the given type * @return the erasure of the given type
* @throws IllegalArgumentException if given a package 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 ) { if ( t.getKind() == TypeKind.VOID || t.getKind() == TypeKind.NULL ) {
return t; return t;
} }
@ -115,4 +114,34 @@ public class SpecificCompilerWorkarounds {
} }
return element; return element;
} }
/**
* Workaround for Bugs in the Eclipse implementation of {@link Types#asMemberOf(DeclaredType, Element)}.
*
* @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=382590">Eclipse Bug 382590</a>
* @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=481555">Eclipse Bug 481555</a>
*/
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;
}
} }

View File

@ -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<? extends TypeMirror> 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 );
}
}

View File

@ -18,8 +18,6 @@
*/ */
package org.mapstruct.ap.test.inheritedmappingmethod; package org.mapstruct.ap.test.inheritedmappingmethod;
import static org.fest.assertions.Assertions.assertThat;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mapstruct.ap.test.inheritedmappingmethod._target.CarDto; 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.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.fest.assertions.Assertions.assertThat;
@IssueKey( "274" ) @IssueKey( "274" )
@WithClasses({ @WithClasses({
Car.class, CarDto.class, UnboundMappable.class, CarMapper.class, // Car.class, CarDto.class, UnboundMappable.class, CarMapper.class, //
@ -39,7 +39,7 @@ import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
public class InheritedMappingMethodTest { public class InheritedMappingMethodTest {
@Test @Test
public void shouldProvideUnboundedMapperInstance() throws Exception { public void shouldProvideUnboundedMapperInstance() {
UnboundMappable<CarDto, Car> instance = CarMapper.INSTANCE; UnboundMappable<CarDto, Car> instance = CarMapper.INSTANCE;
assertThat( instance ).isNotNull(); assertThat( instance ).isNotNull();
} }
@ -59,7 +59,7 @@ public class InheritedMappingMethodTest {
} }
@Test @Test
public void shouldProvideBoundedMapperInstance() throws Exception { public void shouldProvideBoundedMapperInstance() {
BoundMappable<? extends CarDto, ? extends Car> instance = FastCarMapper.INSTANCE; BoundMappable<? extends CarDto, ? extends Car> instance = FastCarMapper.INSTANCE;
assertThat( instance ).isNotNull(); assertThat( instance ).isNotNull();
} }