/**
* Copyright 2012-2017 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 accessed through {@link EclipseClassLoaderBridge} 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;
}
}
}