/*
* 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 com.google.devtools.j2objc.util;
import com.google.j2objc.annotations.RetainedWith;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Modifier;
/**
* Utility methods for working with binding types.
*
* @author Keith Stanger
*/
public final class BindingUtil {
// Flags defined in JVM spec, table 4.1. These constants are also defined in
// java.lang.reflect.Modifier, but aren't public.
public static final int ACC_BRIDGE = 0x40;
public static final int ACC_VARARGS = 0x80;
public static final int ACC_SYNTHETIC = 0x1000;
public static final int ACC_ANNOTATION = 0x2000;
public static final int ACC_ENUM = 0x4000;
// Not defined in JVM spec, but used by reflection support.
public static final int ACC_ANONYMOUS = 0x8000;
// Class files can only use the lower 16 bits.
public static final int ACC_FLAG_MASK = 0xFFFF;
public static boolean isStatic(IBinding binding) {
return Modifier.isStatic(binding.getModifiers());
}
public static boolean isDefault(IBinding binding) {
return Modifier.isDefault(binding.getModifiers());
}
public static boolean isFinal(IBinding binding) {
return Modifier.isFinal(binding.getModifiers());
}
public static boolean isPublic(IBinding binding) {
return Modifier.isPublic(binding.getModifiers());
}
public static boolean isPrivate(IBinding binding) {
return Modifier.isPrivate(binding.getModifiers());
}
public static boolean isVolatile(IVariableBinding binding) {
return Modifier.isVolatile(binding.getModifiers());
}
public static boolean isPrimitiveConstant(IVariableBinding binding) {
return isFinal(binding) && binding.getType().isPrimitive()
&& binding.getConstantValue() != null
// Exclude local variables declared final.
&& binding.getDeclaringClass() != null;
}
public static boolean isConstant(IVariableBinding binding) {
Object constantValue = binding.getConstantValue();
return constantValue != null
&& (binding.getType().isPrimitive()
|| (constantValue instanceof String
&& UnicodeUtils.hasValidCppCharacters((String) constantValue)));
}
/**
* Returns whether this variable will be declared in global scope in ObjC.
*/
public static boolean isGlobalVar(IVariableBinding binding) {
return isStatic(binding) || isPrimitiveConstant(binding);
}
/**
* Returns whether this variable will be an ObjC instance variable.
*/
public static boolean isInstanceVar(IVariableBinding binding) {
return binding.isField() && !isGlobalVar(binding);
}
/**
* Return if a class or a method in a class or in an interface is abstract.
*
* The following cases are true:
* - if binding is an abstract class;
* - if binding is an abstract method in a class; or
* - if binding is a non-default method in an interface.
*
* If your code is run before AbstractMethodRewriter, a MethodDeclaration's modifiers may not be
* identical to its method binding's. This is because JDT only marks a MethodDeclaration node
* abstract when the "abstract" modifier is present in the code (and therefore the method must be
* from a class), whereas a binding's modifier reflects the method's abstract nature (and
* therefore a non-default method from an interface is abstract).
*
* Code past AbstractMethodRewriter can safely assume Modifier.isAbstract(decl.getModifiers())
* is the same as BindingUtil.isAbstract(decl.getMethodBinding()).
*/
public static boolean isAbstract(IBinding binding) {
return Modifier.isAbstract(binding.getModifiers());
}
public static boolean isNative(IBinding binding) {
return Modifier.isNative(binding.getModifiers());
}
public static boolean isSynchronized(IBinding binding) {
return Modifier.isSynchronized(binding.getModifiers());
}
public static boolean isSynthetic(int modifiers) {
return (modifiers & ACC_SYNTHETIC) > 0;
}
public static boolean isSynthetic(IBinding binding) {
return isSynthetic(binding.getModifiers());
}
public static boolean isVoid(ITypeBinding type) {
return type.isPrimitive() && type.getBinaryName().charAt(0) == 'V';
}
public static boolean isBoolean(ITypeBinding type) {
return type.isPrimitive() && type.getBinaryName().charAt(0) == 'Z';
}
public static boolean isFloatingPoint(ITypeBinding type) {
if (!type.isPrimitive()) {
return false;
}
char binaryName = type.getBinaryName().charAt(0);
return binaryName == 'F' || binaryName == 'D';
}
public static boolean isPackageInfo(ITypeBinding type) {
return type.getName().equals(NameTable.PACKAGE_INFO_CLASS_NAME);
}
/**
* Tests if this type is private to it's source file. A public type declared
* within a private type is considered private.
*/
public static boolean isPrivateInnerType(ITypeBinding type) {
if (isPrivate(type) || type.isLocal() || type.isAnonymous()) {
return true;
}
ITypeBinding declaringClass = type.getDeclaringClass();
if (declaringClass != null) {
return isPrivateInnerType(declaringClass);
}
return false;
}
/**
* Determines if a type can access fields and methods from an outer class.
*/
public static boolean hasOuterContext(ITypeBinding type) {
if (type.getDeclaringClass() == null) {
return false;
}
// Local types can't be declared static, but if the declaring method is
// static then the local type is effectively static.
IMethodBinding declaringMethod = type.getTypeDeclaration().getDeclaringMethod();
if (declaringMethod != null) {
return !BindingUtil.isStatic(declaringMethod);
}
return !BindingUtil.isStatic(type);
}
/**
* Convert an IBinding to a ITypeBinding. Returns null if the binding cannot
* be converted to a type binding.
*/
public static ITypeBinding toTypeBinding(IBinding binding) {
if (binding instanceof ITypeBinding) {
return (ITypeBinding) binding;
} else if (binding instanceof IMethodBinding) {
IMethodBinding m = (IMethodBinding) binding;
return m.isConstructor() ? m.getDeclaringClass() : m.getReturnType();
} else if (binding instanceof IVariableBinding) {
return ((IVariableBinding) binding).getType();
} else if (binding instanceof IAnnotationBinding) {
return ((IAnnotationBinding) binding).getAnnotationType();
}
return null;
}
/**
* Gets the list of erased types that would be included in a ObjC declaration
* for this type. For example, if the type would be declared as
* "Foo<Bar, Baz> *" then the returned bounds are the bindings for the class
* Foo, and interfaces Bar and Baz. If one of the bounds is a class type, then
* that type will be the first element in the list.
*/
public static List<ITypeBinding> getTypeBounds(ITypeBinding type) {
if (isIntersectionType(type)) {
return getIntersectionTypeBounds(type);
} else if (!(type.isTypeVariable() || type.isCapture() || type.isWildcardType())) {
return Collections.singletonList(type.getErasure());
}
List<ITypeBinding> bounds = new ArrayList<>();
// The first element of the list will be the class type. If it is still null
// after collecting the bounds then remove it.
bounds.add(null);
collectBounds(type, bounds);
if (bounds.get(0) == null) {
bounds.remove(0);
}
return bounds;
}
private static List<ITypeBinding> getIntersectionTypeBounds(ITypeBinding type) {
ITypeBinding[] interfaces = type.getInterfaces();
List<ITypeBinding> bounds = new ArrayList<>(interfaces.length);
ITypeBinding superclass = type.getSuperclass();
if (superclass != null) {
bounds.add(superclass.getErasure());
}
for (ITypeBinding bound : interfaces) {
bounds.add(bound.getErasure());
}
return bounds;
}
private static void collectBounds(ITypeBinding type, List<ITypeBinding> bounds) {
ITypeBinding[] boundsArr = type.getTypeBounds();
if (boundsArr.length == 0) {
addBound(type, bounds);
} else {
for (ITypeBinding bound : boundsArr) {
collectBounds(bound, bounds);
}
}
}
private static void addBound(ITypeBinding type, List<ITypeBinding> bounds) {
type = type.getErasure();
if (type.isInterface()) {
bounds.add(type);
} else if (bounds.get(0) != null) {
throw new AssertionError("Type has multiple class type bounds");
} else {
bounds.set(0, type);
}
}
/**
* Returns a set containing the bindings of all classes and interfaces that
* are inherited by the given type.
*/
public static Set<ITypeBinding> getInheritedTypes(ITypeBinding type) {
Set<ITypeBinding> inheritedTypes = getInheritedTypesInclusive(type);
inheritedTypes.remove(type);
return inheritedTypes;
}
public static Set<ITypeBinding> getInheritedTypesInclusive(ITypeBinding type) {
Set<ITypeBinding> inheritedTypes = new HashSet<>();
collectInheritedTypesInclusive(type, inheritedTypes);
return inheritedTypes;
}
/**
* Returns a set containing the bindings of all classes and interfaces that
* are inherited by the given type in the same order that they would be
* searched by the ObjC compiler.
*/
public static LinkedHashSet<ITypeBinding> getOrderedInheritedTypes(ITypeBinding type) {
LinkedHashSet<ITypeBinding> inheritedTypes = getOrderedInheritedTypesInclusive(type);
inheritedTypes.remove(type);
return inheritedTypes;
}
public static LinkedHashSet<ITypeBinding> getOrderedInheritedTypesInclusive(ITypeBinding type) {
LinkedHashSet<ITypeBinding> inheritedTypes = new LinkedHashSet<>();
collectInheritedTypesInclusive(type, inheritedTypes);
return inheritedTypes;
}
private static void collectInheritedTypesInclusive(
ITypeBinding type, Set<ITypeBinding> inheritedTypes) {
if (type == null) {
return;
}
inheritedTypes.add(type);
for (ITypeBinding interfaze : type.getInterfaces()) {
collectInheritedTypesInclusive(interfaze, inheritedTypes);
}
collectInheritedTypesInclusive(type.getSuperclass(), inheritedTypes);
}
/**
* Returns a set containing bindings of all interfaces implemented by the
* given class, and all super-interfaces of those.
*/
public static Set<ITypeBinding> getAllInterfaces(ITypeBinding type) {
Set<ITypeBinding> interfaces = new HashSet<>();
collectAllInterfaces(type, interfaces);
return interfaces;
}
public static void collectAllInterfaces(ITypeBinding type, Set<ITypeBinding> interfaces) {
Deque<ITypeBinding> typeQueue = new LinkedList<>();
while (type != null) {
typeQueue.add(type);
type = type.getSuperclass();
}
while (!typeQueue.isEmpty()) {
ITypeBinding nextType = typeQueue.poll();
List<ITypeBinding> newInterfaces = Arrays.asList(nextType.getInterfaces());
interfaces.addAll(newInterfaces);
typeQueue.addAll(newInterfaces);
}
}
/**
* Returns the type binding for a specific interface of a specific type.
*/
public static ITypeBinding findInterface(ITypeBinding implementingType, String qualifiedName) {
if (implementingType.isInterface()
&& implementingType.getErasure().getQualifiedName().equals(qualifiedName)) {
return implementingType;
}
for (ITypeBinding interfaze : getAllInterfaces(implementingType)) {
if (interfaze.getErasure().getQualifiedName().equals(qualifiedName)) {
return interfaze;
}
}
return null;
}
/**
* Returns the inner type with the specified name.
*/
public static ITypeBinding findDeclaredType(ITypeBinding type, String name) {
for (ITypeBinding innerType : type.getDeclaredTypes()) {
if (innerType.getName().equals(name)) {
return innerType;
}
}
return null;
}
/**
* Returns the method binding for a specific method of a specific type.
*/
public static IMethodBinding findDeclaredMethod(
ITypeBinding type, String methodName, String... paramTypes) {
outer: for (IMethodBinding method : type.getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
ITypeBinding[] foundParamTypes = method.getParameterTypes();
if (paramTypes.length == foundParamTypes.length) {
for (int i = 0; i < paramTypes.length; i++) {
if (!paramTypes[i].equals(foundParamTypes[i].getQualifiedName())) {
continue outer;
}
}
return method;
}
}
}
return null;
}
public static String getMethodKey(IMethodBinding binding) {
return binding.getDeclaringClass().getBinaryName() + '.' + binding.getName()
+ getSignature(binding);
}
public static String getSignature(IMethodBinding binding) {
StringBuilder sb = new StringBuilder("(");
appendParametersSignature(binding, sb);
sb.append(')');
appendReturnTypeSignature(binding, sb);
return sb.toString();
}
/**
* Get a method's signature for dead code elimination purposes.
*
* Since DeadCodeEliminator runs before InnerClassExtractor, inner class constructors do not yet
* have the parameter for capturing outer class, and therefore we need this special case.
*/
public static String getProGuardSignature(IMethodBinding binding) {
StringBuilder sb = new StringBuilder("(");
// If the method is an inner class constructor, prepend the outer class type.
if (binding.isConstructor()) {
ITypeBinding declClass = binding.getDeclaringClass();
ITypeBinding outerClass = declClass.getDeclaringClass();
if (outerClass != null && !declClass.isInterface() && !declClass.isAnnotation()
&& !Modifier.isStatic(declClass.getModifiers())) {
appendParameterSignature(outerClass.getErasure(), sb);
}
}
appendParametersSignature(binding, sb);
sb.append(')');
appendReturnTypeSignature(binding, sb);
return sb.toString();
}
private static void appendReturnTypeSignature(IMethodBinding binding, StringBuilder sb) {
if (binding.getReturnType() != null) {
appendParameterSignature(binding.getReturnType().getErasure(), sb);
}
}
private static void appendParametersSignature(IMethodBinding binding, StringBuilder sb) {
for (ITypeBinding parameter : binding.getParameterTypes()) {
appendParameterSignature(parameter.getErasure(), sb);
}
}
private static void appendParameterSignature(ITypeBinding parameter, StringBuilder sb) {
if (!parameter.isPrimitive() && !parameter.isArray()) {
sb.append('L');
}
sb.append(parameter.getBinaryName().replace('.', '/'));
if (!parameter.isPrimitive() && !parameter.isArray()) {
sb.append(';');
}
}
public static boolean hasAnnotation(IBinding binding, Class<?> annotationClass) {
return getAnnotation(binding, annotationClass) != null;
}
/**
* Less strict version of the above where we don't care about the annotation's package.
*/
public static boolean hasNamedAnnotation(IBinding binding, String annotationName) {
for (IAnnotationBinding annotation : binding.getAnnotations()) {
if (annotation.getName().equals(annotationName)) {
return true;
}
}
return false;
}
/**
* Like hasNamedAnnotation, but for TypeAnnotations.
*/
public static boolean hasNamedTypeAnnotation(ITypeBinding binding, String annotationName) {
for (IAnnotationBinding annotation : binding.getTypeAnnotations()) {
if (annotation.getName().equals(annotationName)) {
return true;
}
}
return false;
}
/**
* Return true if a binding has a named "Nullable" annotation. Package names aren't
* checked because different nullable annotations are defined by several different
* Java frameworks.
*/
public static boolean hasNullableAnnotation(IBinding binding) {
return hasNamedAnnotation(binding, "Nullable");
}
/**
* Return true if a binding has a named "Nonnull" annotation. Package names aren't
* checked because different nonnull annotations are defined in several Java
* frameworks, with varying but similar names.
*/
public static boolean hasNonnullAnnotation(IBinding binding) {
Pattern p = Pattern.compile("No[nt][Nn]ull");
for (IAnnotationBinding annotation : binding.getAnnotations()) {
if (p.matcher(annotation.getName()).matches()) {
return true;
}
}
return false;
}
public static IAnnotationBinding getAnnotation(IBinding binding, Class<?> annotationClass) {
for (IAnnotationBinding annotation : binding.getAnnotations()) {
if (typeEqualsClass(annotation.getAnnotationType(), annotationClass)) {
return annotation;
}
}
return null;
}
public static boolean typeEqualsClass(ITypeBinding type, Class<?> cls) {
return type.getQualifiedName().equals(cls.getName());
}
public static Object getAnnotationValue(IAnnotationBinding annotation, String name) {
for (IMemberValuePairBinding pair : annotation.getAllMemberValuePairs()) {
if (name.equals(pair.getName())) {
return pair.getValue();
}
}
return null;
}
public static boolean isRetainedWithField(IVariableBinding var) {
return hasAnnotation(var, RetainedWith.class);
}
/**
* Returns true if the specified binding is of an annotation that has
* a runtime retention policy.
*/
public static boolean isRuntimeAnnotation(ITypeBinding binding) {
if (binding != null && binding.isAnnotation()) {
for (IAnnotationBinding ann : binding.getAnnotations()) {
if (ann.getName().equals("Retention")) {
IVariableBinding retentionBinding =
(IVariableBinding) ann.getDeclaredMemberValuePairs()[0].getValue();
return retentionBinding.getName().equals(RetentionPolicy.RUNTIME.name());
}
}
if (binding.isNested()) {
return BindingUtil.isRuntimeAnnotation(binding.getDeclaringClass());
}
}
return false;
}
/**
* Returns the attributes of a Property annotation.
*/
public static Set<String> parseAttributeString(IAnnotationBinding propertyAnnotation) {
assert propertyAnnotation.getName().equals("Property");
String attributesStr = (String) getAnnotationValue(propertyAnnotation, "value");
Set<String> attributes = new HashSet<>();
attributes.addAll(Arrays.asList(attributesStr.split(",\\s*")));
attributes.remove(""); // Clear any empty strings.
return attributes;
}
/**
* Returns all declared constructors for a specified type.
*/
public static Set<IMethodBinding> getDeclaredConstructors(ITypeBinding type) {
Set<IMethodBinding> constructors = new HashSet<>();
for (IMethodBinding m : type.getDeclaredMethods()) {
if (m.isConstructor()) {
constructors.add(m);
}
}
return constructors;
}
/**
* Returns true if any of the declared methods in the interface is static.
*/
public static boolean hasStaticInterfaceMethods(ITypeBinding type) {
assert type.isInterface();
for (IMethodBinding method : type.getDeclaredMethods()) {
if (isStatic(method)) {
return true;
}
}
return false;
}
/**
* Returns true if the binding is a Java 8 intersection type. For example,
* Comparator.thenComparing() returns a lambda with a return type of
* "Comparator<T> & Serializable". Since there's no ITypeBinding
* isIntersectionType(), we rely on the JDT returning an empty string for the
* type's qualified name.
*/
public static boolean isIntersectionType(ITypeBinding binding) {
return (binding.isInterface() && binding.getQualifiedName().isEmpty())
|| (binding.isWildcardType() && !binding.toString().startsWith("?"));
}
/**
* Dumps the results from all methods in ITypeBinding to System.out.
* Used only when debugging.
*/
public static void dumpTypeBinding(ITypeBinding binding) {
System.out.println("dump type: " + binding.getName());
System.out.println(" getBinaryName: " + binding.getBinaryName());
System.out.println(" getBound: " + getKey(binding.getBound()));
System.out.println(" getClass: " + binding.getClass());
System.out.println(" getComponentType: " + binding.getComponentType());
dumpArray(" getDeclaredTypes:", binding.getDeclaredTypes());
System.out.println(" getDeclaringClass: " + binding.getDeclaringClass());
System.out.println(" getElementType: " + binding.getElementType());
System.out.println(" getErasure: " + binding.getErasure());
System.out.println(" getGenericTypeOfWildcardType: " + binding.getGenericTypeOfWildcardType());
dumpArray(" getInterfaces:", binding.getInterfaces());
System.out.println(" getKey: " + binding.getKey());
System.out.println(" getModifiers:0x" + Integer.toHexString(binding.getModifiers()));
System.out.println(" getName: " + binding.getName());
System.out.println(" getPackage: " + binding.getPackage());
System.out.println(" getQualifiedName: " + binding.getQualifiedName());
System.out.println(" getSuperclass: " + binding.getSuperclass());
dumpArray(" getTypeArguments:", binding.getTypeArguments());
dumpArray(" getTypeBounds:", binding.getTypeBounds());
System.out.println(" getTypeDeclaration: " + binding.getTypeDeclaration());
dumpArray(" getTypeParameters:", binding.getTypeParameters());
System.out.println(" getWildcard: " + binding.getWildcard());
System.out.println(" isAnnotation: " + binding.isAnnotation());
System.out.println(" isAnonymous: " + binding.isAnonymous());
System.out.println(" isArray: " + binding.isArray());
System.out.println(" isCapture: " + binding.isCapture());
System.out.println(" isClass: " + binding.isClass());
System.out.println(" isEnum: " + binding.isEnum());
System.out.println(" isFromSource: " + binding.isFromSource());
System.out.println(" isGenericType: " + binding.isGenericType());
System.out.println(" isInterface: " + binding.isInterface());
System.out.println(" isLocal: " + binding.isLocal());
System.out.println(" isMember: " + binding.isMember());
System.out.println(" isNested: " + binding.isNested());
System.out.println(" isNullType: " + binding.isNullType());
System.out.println(" isParameterizedType: " + binding.isParameterizedType());
System.out.println(" isPrimitive: " + binding.isPrimitive());
System.out.println(" isRawType: " + binding.isRawType());
System.out.println(" isSynthetic: " + binding.isSynthetic());
System.out.println(" isTopLevel: " + binding.isTopLevel());
System.out.println(" isTypeVariable: " + binding.isTypeVariable());
System.out.println(" isUpperbound: " + binding.isUpperbound());
System.out.println(" isWildcardType: " + binding.isWildcardType());
System.out.println(" toString: " + binding.toString());
}
private static String getKey(ITypeBinding type) {
return type == null ? "null" : type.getKey();
}
private static void dumpArray(String description, ITypeBinding[] array) {
System.out.print(description + " [");
for (int i = 0; i < array.length; i++) {
if (i > 0) {
System.out.print(", ");
}
System.out.print(array[i].getName());
}
System.out.println("]");
}
}