/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Dmitry Stalnov (dstalnov@fusionone.com) - contributed fix for * bug "inline method - doesn't handle implicit cast" (see * https://bugs.eclipse.org/bugs/show_bug.cgi?id=24941). *******************************************************************************/ package org.eclipse.che.ide.ext.java.jdt.internal.corext.dom; import org.eclipse.che.ide.ext.java.jdt.core.dom.AST; import org.eclipse.che.ide.ext.java.jdt.core.dom.ASTNode; import org.eclipse.che.ide.ext.java.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.Annotation; import org.eclipse.che.ide.ext.java.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.ArrayAccess; import org.eclipse.che.ide.ext.java.jdt.core.dom.Assignment; import org.eclipse.che.ide.ext.java.jdt.core.dom.CastExpression; import org.eclipse.che.ide.ext.java.jdt.core.dom.ClassInstanceCreation; import org.eclipse.che.ide.ext.java.jdt.core.dom.EnumDeclaration; import org.eclipse.che.ide.ext.java.jdt.core.dom.Expression; import org.eclipse.che.ide.ext.java.jdt.core.dom.FieldAccess; import org.eclipse.che.ide.ext.java.jdt.core.dom.IBinding; import org.eclipse.che.ide.ext.java.jdt.core.dom.IMethodBinding; import org.eclipse.che.ide.ext.java.jdt.core.dom.IPackageBinding; import org.eclipse.che.ide.ext.java.jdt.core.dom.ITypeBinding; import org.eclipse.che.ide.ext.java.jdt.core.dom.IVariableBinding; import org.eclipse.che.ide.ext.java.jdt.core.dom.MethodInvocation; import org.eclipse.che.ide.ext.java.jdt.core.dom.Modifier; import org.eclipse.che.ide.ext.java.jdt.core.dom.Name; import org.eclipse.che.ide.ext.java.jdt.core.dom.ParenthesizedExpression; import org.eclipse.che.ide.ext.java.jdt.core.dom.PostfixExpression; import org.eclipse.che.ide.ext.java.jdt.core.dom.PrefixExpression; import org.eclipse.che.ide.ext.java.jdt.core.dom.QualifiedName; import org.eclipse.che.ide.ext.java.jdt.core.dom.SimpleName; import org.eclipse.che.ide.ext.java.jdt.core.dom.StructuralPropertyDescriptor; import org.eclipse.che.ide.ext.java.jdt.core.dom.SuperFieldAccess; import org.eclipse.che.ide.ext.java.jdt.core.dom.SuperMethodInvocation; import org.eclipse.che.ide.runtime.Assert; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; public class Bindings { public static final String ARRAY_LENGTH_FIELD_BINDING_STRING = "(array type):length";//$NON-NLS-1$ private Bindings() { // No instance } /** * Checks if the two bindings are equals. Also works across binding environments. * * @param b1 * first binding treated as <code>this</code>. So it must not be <code>null</code> * @param b2 * the second binding. * @return boolean */ public static boolean equals(IBinding b1, IBinding b2) { return b1.isEqualTo(b2); } /** * Checks if the declarations of two bindings are equals. Also works across binding environments. * * @param b1 * first binding, must not be <code>null</code> * @param b2 * second binding, must not be <code>null</code> * @return boolean */ public static boolean equalDeclarations(IBinding b1, IBinding b2) { if (b1.getKind() != b2.getKind()) return false; return getDeclaration(b1).isEqualTo(getDeclaration(b2)); } /** * Checks if the two arrays of bindings have the same length and their elements are equal. Uses * <code>Bindings.equals(IBinding, IBinding)</code> to compare. * * @param b1 * the first array of bindings. Must not be <code>null</code>. * @param b2 * the second array of bindings. * @return boolean */ public static boolean equals(IBinding[] b1, IBinding[] b2) { Assert.isNotNull(b1); if (b1 == b2) return true; if (b2 == null) return false; if (b1.length != b2.length) return false; for (int i = 0; i < b1.length; i++) { if (!Bindings.equals(b1[i], b2[i])) return false; } return true; } public static int hashCode(IBinding binding) { Assert.isNotNull(binding); String key = binding.getKey(); if (key == null) return binding.hashCode(); return key.hashCode(); } public static String getTypeQualifiedName(ITypeBinding type) { List<String> result = new ArrayList<String>(5); createName(type, false, result); StringBuffer buffer = new StringBuffer(); for (int i = 0; i < result.size(); i++) { if (i > 0) { buffer.append('.'); } buffer.append(result.get(i)); } return buffer.toString(); } /** * Returns the fully qualified name of the specified type binding. * <p/> * If the binding resolves to a generic type, the fully qualified name of the raw type is returned. * * @param type * the type binding to get its fully qualified name * @return the fully qualified name */ public static String getFullyQualifiedName(ITypeBinding type) { String name = type.getQualifiedName(); final int index = name.indexOf('<'); if (index > 0) name = name.substring(0, index); return name; } public static String getImportName(IBinding binding) { ITypeBinding declaring = null; switch (binding.getKind()) { case IBinding.TYPE: return getRawQualifiedName((ITypeBinding)binding); case IBinding.PACKAGE: return binding.getName() + ".*"; //$NON-NLS-1$ case IBinding.METHOD: declaring = ((IMethodBinding)binding).getDeclaringClass(); break; case IBinding.VARIABLE: declaring = ((IVariableBinding)binding).getDeclaringClass(); if (declaring == null) { return binding.getName(); // array.length } break; default: return binding.getName(); } return concatenateName(getRawQualifiedName(declaring), binding.getName()); } /** * Concatenates two names. Uses a dot for separation. Both strings can be empty or <code>null</code>. * * @param name1 * the first name * @param name2 * the second name * @return the concatenated name */ public static String concatenateName(String name1, String name2) { StringBuffer buf = new StringBuffer(); if (name1 != null && name1.length() > 0) { buf.append(name1); } if (name2 != null && name2.length() > 0) { if (buf.length() > 0) { buf.append('.'); } buf.append(name2); } return buf.toString(); } private static void createName(ITypeBinding type, boolean includePackage, List<String> list) { ITypeBinding baseType = type; if (type.isArray()) { baseType = type.getElementType(); } if (!baseType.isPrimitive() && !baseType.isNullType()) { ITypeBinding declaringType = baseType.getDeclaringClass(); if (declaringType != null) { createName(declaringType, includePackage, list); } else if (includePackage && !baseType.getPackage().isUnnamed()) { String[] components = baseType.getPackage().getNameComponents(); for (int i = 0; i < components.length; i++) { list.add(components[i]); } } } if (!baseType.isAnonymous()) { list.add(type.getName()); } else { list.add("$local$"); //$NON-NLS-1$ } } public static String[] getNameComponents(ITypeBinding type) { List<String> result = new ArrayList<String>(5); createName(type, false, result); return result.toArray(new String[result.size()]); } public static String[] getAllNameComponents(ITypeBinding type) { List<String> result = new ArrayList<String>(5); createName(type, true, result); return result.toArray(new String[result.size()]); } public static ITypeBinding getTopLevelType(ITypeBinding type) { ITypeBinding parent = type.getDeclaringClass(); while (parent != null) { type = parent; parent = type.getDeclaringClass(); } return type; } /** * Checks whether the passed type binding is a runtime exception. * * @param thrownException * the type binding * @return <code>true</code> if the passed type binding is a runtime exception; otherwise <code>false</code> is returned */ public static boolean isRuntimeException(ITypeBinding thrownException) { if (thrownException == null || thrownException.isPrimitive() || thrownException.isArray()) return false; return findTypeInHierarchy(thrownException, "java.lang.RuntimeException") != null; //$NON-NLS-1$ } /** * Finds the field specified by <code>fieldName<code> in * the given <code>type</code>. Returns <code>null</code> if no such field exits. * * @param type * the type to search the field in * @param fieldName * the field name * @return the binding representing the field or <code>null</code> */ public static IVariableBinding findFieldInType(ITypeBinding type, String fieldName) { if (type.isPrimitive()) return null; IVariableBinding[] fields = type.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { IVariableBinding field = fields[i]; if (field.getName().equals(fieldName)) return field; } return null; } /** * Finds the field specified by <code>fieldName</code> in the type hierarchy denoted by the given type. Returns * <code>null</code> if no such field exists. If the field is defined in more than one super type only the first match is * returned. First the super class is examined and then the implemented interfaces. * * @param type * The type to search the field in * @param fieldName * The name of the field to find * @return the variable binding representing the field */ public static IVariableBinding findFieldInHierarchy(ITypeBinding type, String fieldName) { IVariableBinding field = findFieldInType(type, fieldName); if (field != null) return field; ITypeBinding superClass = type.getSuperclass(); if (superClass != null) { field = findFieldInHierarchy(superClass, fieldName); if (field != null) return field; } ITypeBinding[] interfaces = type.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { field = findFieldInHierarchy(interfaces[i], fieldName); if (field != null) // no private fields in interfaces return field; } return null; } /** * Finds the method specified by <code>methodName<code> and </code>parameters</code> in the given <code>type</code>. Returns * <code>null</code> if no such method exits. * * @param type * The type to search the method in * @param methodName * The name of the method to find * @param parameters * The parameter types of the method to find. If <code>null</code> is passed, only the name is matched and * parameters are ignored. * @return the method binding representing the method */ public static IMethodBinding findMethodInType(ITypeBinding type, String methodName, ITypeBinding[] parameters) { if (type.isPrimitive()) return null; IMethodBinding[] methods = type.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { if (parameters == null) { if (methodName.equals(methods[i].getName())) return methods[i]; } else { if (isEqualMethod(methods[i], methodName, parameters)) return methods[i]; } } return null; } /** * Finds the method specified by <code>methodName</code> and </code>parameters</code> in the type hierarchy denoted by the * given type. Returns <code>null</code> if no such method exists. If the method is defined in more than one super type only * the first match is returned. First the super class is examined and then the implemented interfaces. * * @param type * The type to search the method in * @param methodName * The name of the method to find * @param parameters * The parameter types of the method to find. If <code>null</code> is passed, only the name is matched and * parameters are ignored. * @return the method binding representing the method */ public static IMethodBinding findMethodInHierarchy(ITypeBinding type, String methodName, ITypeBinding[] parameters) { IMethodBinding method = findMethodInType(type, methodName, parameters); if (method != null) return method; ITypeBinding superClass = type.getSuperclass(); if (superClass != null) { method = findMethodInHierarchy(superClass, methodName, parameters); if (method != null) return method; } ITypeBinding[] interfaces = type.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { method = findMethodInHierarchy(interfaces[i], methodName, parameters); if (method != null) return method; } return null; } /** * Finds the method specified by <code>methodName<code> and </code>parameters</code> in the given <code>type</code>. Returns * <code>null</code> if no such method exits. * * @param type * The type to search the method in * @param methodName * The name of the method to find * @param parameters * The parameter types of the method to find. If <code>null</code> is passed, only the name is matched and * parameters are ignored. * @return the method binding representing the method */ public static IMethodBinding findMethodInType(ITypeBinding type, String methodName, String[] parameters) { if (type.isPrimitive()) return null; IMethodBinding[] methods = type.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { if (parameters == null) { if (methodName.equals(methods[i].getName())) return methods[i]; } else { if (isEqualMethod(methods[i], methodName, parameters)) return methods[i]; } } return null; } /** * Finds the method specified by <code>methodName</code> and </code>parameters</code> in the type hierarchy denoted by the * given type. Returns <code>null</code> if no such method exists. If the method is defined in more than one super type only * the first match is returned. First the super class is examined and then the implemented interfaces. * * @param type * the type to search the method in * @param methodName * The name of the method to find * @param parameters * The parameter types of the method to find. If <code>null</code> is passed, only the name is matched and * parameters are ignored. * @return the method binding representing the method */ public static IMethodBinding findMethodInHierarchy(ITypeBinding type, String methodName, String[] parameters) { IMethodBinding method = findMethodInType(type, methodName, parameters); if (method != null) return method; ITypeBinding superClass = type.getSuperclass(); if (superClass != null) { method = findMethodInHierarchy(superClass, methodName, parameters); if (method != null) return method; } ITypeBinding[] interfaces = type.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { method = findMethodInHierarchy(interfaces[i], methodName, parameters); if (method != null) return method; } return null; } /** * Finds the method in the given <code>type</code> that is overridden by the specified <code>method<code>. * Returns <code>null</code> if no such method exits. * * @param type * The type to search the method in * @param method * The specified method that would override the result * @return the method binding of the method that is overridden by the specified <code>method<code>, or <code>null</code> */ public static IMethodBinding findOverriddenMethodInType(ITypeBinding type, IMethodBinding method) { IMethodBinding[] methods = type.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { if (isSubsignature(method, methods[i])) return methods[i]; } return null; } /** * Finds a method in the hierarchy of <code>type</code> that is overridden by </code>binding</code>. Returns <code>null</code> * if no such method exists. If the method is defined in more than one super type only the first match is returned. First the * super class is examined and then the implemented interfaces. * * @param type * The type to search the method in * @param binding * The method that overrides * @return the method binding overridden the method */ public static IMethodBinding findOverriddenMethodInHierarchy(ITypeBinding type, IMethodBinding binding) { IMethodBinding method = findOverriddenMethodInType(type, binding); if (method != null) return method; ITypeBinding superClass = type.getSuperclass(); if (superClass != null) { method = findOverriddenMethodInHierarchy(superClass, binding); if (method != null) return method; } ITypeBinding[] interfaces = type.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { method = findOverriddenMethodInHierarchy(interfaces[i], binding); if (method != null) return method; } return null; } /** * Finds the method that is overridden by the given method. The search is bottom-up, so this returns the nearest * defining/declaring method. * * @param overriding * overriding method * @param testVisibility * If true the result is tested on visibility. Null is returned if the method is not visible. * @return the method binding representing the method */ public static IMethodBinding findOverriddenMethod(IMethodBinding overriding, boolean testVisibility) { int modifiers = overriding.getModifiers(); if (Modifier.isPrivate(modifiers) || Modifier.isStatic(modifiers) || overriding.isConstructor()) { return null; } ITypeBinding type = overriding.getDeclaringClass(); if (type.getSuperclass() != null) { IMethodBinding res = findOverriddenMethodInHierarchy(type.getSuperclass(), overriding); if (res != null && !Modifier.isPrivate(res.getModifiers())) { if (!testVisibility || isVisibleInHierarchy(res, overriding.getDeclaringClass().getPackage())) { return res; } } } ITypeBinding[] interfaces = type.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { IMethodBinding res = findOverriddenMethodInHierarchy(interfaces[i], overriding); if (res != null) { return res; // methods from interfaces are always public and therefore visible } } return null; } public static boolean isVisibleInHierarchy(IMethodBinding member, IPackageBinding pack) { int otherflags = member.getModifiers(); ITypeBinding declaringType = member.getDeclaringClass(); if (Modifier.isPublic(otherflags) || Modifier.isProtected(otherflags) || (declaringType != null && declaringType.isInterface())) { return true; } else if (Modifier.isPrivate(otherflags)) { return false; } return declaringType != null && pack == declaringType.getPackage(); } /** * Returns all super types (classes and interfaces) for the given type. * * @param type * The type to get the supertypes of. * @return all super types (excluding <code>type</code>) */ public static ITypeBinding[] getAllSuperTypes(ITypeBinding type) { Set<ITypeBinding> result = new HashSet<ITypeBinding>(); collectSuperTypes(type, result); result.remove(type); return result.toArray(new ITypeBinding[result.size()]); } private static void collectSuperTypes(ITypeBinding curr, Set<ITypeBinding> collection) { if (collection.add(curr)) { ITypeBinding[] interfaces = curr.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { collectSuperTypes(interfaces[i], collection); } ITypeBinding superClass = curr.getSuperclass(); if (superClass != null) { collectSuperTypes(superClass, collection); } } } /** * Method to visit a type hierarchy defined by a given type. * * @param type * the type which hierarchy is to be visited * @param visitor * the visitor * @return <code>false</code> if the visiting got interrupted */ public static boolean visitHierarchy(ITypeBinding type, TypeBindingVisitor visitor) { boolean result = visitSuperclasses(type, visitor); if (result) { result = visitInterfaces(type, visitor); } return result; } /** * Method to visit a interface hierarchy defined by a given type. * * @param type * the type which interface hierarchy is to be visited * @param visitor * the visitor * @return <code>false</code> if the visiting got interrupted */ public static boolean visitInterfaces(ITypeBinding type, TypeBindingVisitor visitor) { ITypeBinding[] interfaces = type.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { if (!visitor.visit(interfaces[i])) { return false; } } return true; } /** * Method to visit a super class hierarchy defined by a given type. * * @param type * the type which super class hierarchy is to be visited * @param visitor * the visitor * @return <code>false</code> if the visiting got interrupted */ public static boolean visitSuperclasses(ITypeBinding type, TypeBindingVisitor visitor) { while ((type = type.getSuperclass()) != null) { if (!visitor.visit(type)) { return false; } } return true; } /** * Tests whether the two methods are erasure-equivalent. * * @param method * the first method * @param methodName * the name of the second method * @param parameters * the parameters of the second parameters * @return return <code>true</code> if the two bindings are equal * @deprecated use {@link #isSubsignature(IMethodBinding, IMethodBinding)} */ // TODO: rename to isErasureEquivalentMethod and change to two IMethodBinding parameters public static boolean isEqualMethod(IMethodBinding method, String methodName, ITypeBinding[] parameters) { if (!method.getName().equals(methodName)) return false; ITypeBinding[] methodParameters = method.getParameterTypes(); if (methodParameters.length != parameters.length) return false; for (int i = 0; i < parameters.length; i++) { if (!equals(methodParameters[i].getErasure(), parameters[i].getErasure())) return false; } // Can't use this fix, since some clients assume that this method tests erasure equivalence: // if (method.getTypeParameters().length == 0) { // //a method without type parameters cannot be overridden by one that declares type parameters -> can be exact here // for (int i= 0; i < parameters.length; i++) { // if ( ! (equals(methodParameters[i], parameters[i]) // || equals(methodParameters[i].getErasure(), parameters[i]))) // subsignature // return false; // } // } else { // //this will find all overridden methods, but may generate false positives in some cases: // for (int i= 0; i < parameters.length; i++) { // if (!equals(methodParameters[i].getErasure(), parameters[i].getErasure())) // return false; // } // } return true; } /** * @param overriding * overriding method (m1) * @param overridden * overridden method (m2) * @return <code>true</code> iff the method <code>m1</code> is a subsignature of the method <code>m2</code>. This is one of the * requirements for m1 to override m2. Accessibility and return types are not taken into account. Note that * subsignature is <em>not</em> symmetric! */ public static boolean isSubsignature(IMethodBinding overriding, IMethodBinding overridden) { // TODO: use IMethodBinding#isSubsignature(..) once it is tested and fixed (only erasure of m1's parameter types, // considering type variable counts, doing type variable substitution if (!overriding.getName().equals(overridden.getName())) return false; ITypeBinding[] m1Params = overriding.getParameterTypes(); ITypeBinding[] m2Params = overridden.getParameterTypes(); if (m1Params.length != m2Params.length) return false; ITypeBinding[] m1TypeParams = overriding.getTypeParameters(); ITypeBinding[] m2TypeParams = overridden.getTypeParameters(); if (m1TypeParams.length != m2TypeParams.length && m1TypeParams.length != 0) // non-generic m1 can override a generic m2 return false; // m1TypeParameters.length == (m2TypeParameters.length || 0) if (m2TypeParams.length != 0) { // Note: this branch does not 100% adhere to the spec and may report some false positives. // Full compliance would require major duplication of compiler code. // Compare type parameter bounds: for (int i = 0; i < m1TypeParams.length; i++) { // loop over m1TypeParams, which is either empty, or equally long as m2TypeParams Set<ITypeBinding> m1Bounds = getTypeBoundsForSubsignature(m1TypeParams[i]); Set<ITypeBinding> m2Bounds = getTypeBoundsForSubsignature(m2TypeParams[i]); if (!m1Bounds.equals(m2Bounds)) return false; } // Compare parameter types: if (equals(m2Params, m1Params)) return true; for (int i = 0; i < m1Params.length; i++) { ITypeBinding m1Param = m1Params[i]; ITypeBinding m2Param = m2Params[i]; if (containsTypeVariables(m1Param) || m1Param.isRawType()) m1Param = m1Param.getErasure(); // try to achieve effect of "rename type variables" if (!(equals(m1Param, m2Param) || equals(m1Param, m2Param.getErasure()))) return false; } return true; } else { // m1TypeParams.length == m2TypeParams.length == 0 if (equals(m1Params, m2Params)) return true; for (int i = 0; i < m1Params.length; i++) { ITypeBinding m1Param = m1Params[i]; ITypeBinding m2Param = m2Params[i]; if (m1Param.isRawType()) m1Param = m1Param.getTypeDeclaration(); if (!(equals(m1Param, m2Param) || equals(m1Param, m2Param.getErasure()))) return false; } return true; } } static boolean containsTypeVariables(ITypeBinding type) { if (type.isTypeVariable()) return true; if (type.isArray()) return containsTypeVariables(type.getElementType()); if (type.isCapture()) return containsTypeVariables(type.getWildcard()); if (type.isParameterizedType()) return containsTypeVariables(type.getTypeArguments()); if (type.isTypeVariable()) return containsTypeVariables(type.getTypeBounds()); if (type.isWildcardType() && type.getBound() != null) return containsTypeVariables(type.getBound()); return false; } private static boolean containsTypeVariables(ITypeBinding[] types) { for (int i = 0; i < types.length; i++) if (containsTypeVariables(types[i])) return true; return false; } private static Set<ITypeBinding> getTypeBoundsForSubsignature(ITypeBinding typeParameter) { ITypeBinding[] typeBounds = typeParameter.getTypeBounds(); int count = typeBounds.length; if (count == 0) return Collections.emptySet(); Set<ITypeBinding> result = new HashSet<ITypeBinding>(typeBounds.length); for (int i = 0; i < typeBounds.length; i++) { ITypeBinding bound = typeBounds[i]; if ("java.lang.Object".equals(typeBounds[0].getQualifiedName())) //$NON-NLS-1$ continue; else if (containsTypeVariables(bound)) result.add(bound.getErasure()); // try to achieve effect of "rename type variables" else if (bound.isRawType()) result.add(bound.getTypeDeclaration()); else result.add(bound); } return result; } /** * Checks whether a method with the given name and parameter types is a subsignature of the given method binding. * * @param method * a method * @param methodName * method name to match * @param parameters * the parameter types of the method to find. If <code>null</code> is passed, only the name is matched and * parameters are ignored. * @return <code>true</code> iff the method m1 (with name <code>methodName</code> and method parameters <code>parameters</code> * ) is a subsignature of the method <code>m2</code>. Accessibility and return types are not taken into account. */ public static boolean isEqualMethod(IMethodBinding method, String methodName, String[] parameters) { if (!method.getName().equals(methodName)) return false; ITypeBinding[] methodParameters = method.getParameterTypes(); if (methodParameters.length != parameters.length) return false; String first, second; int index; for (int i = 0; i < parameters.length; i++) { first = parameters[i]; index = first.indexOf('<'); if (index > 0) first = first.substring(0, index); second = methodParameters[i].getErasure().getQualifiedName(); index = second.indexOf('<'); if (index > 0) second = second.substring(0, index); if (!first.equals(second)) return false; } return true; } /** * Finds a type binding for a given fully qualified type in the hierarchy of a type. Returns <code>null</code> if no type * binding is found. * * @param hierarchyType * the binding representing the hierarchy * @param fullyQualifiedTypeName * the fully qualified name to search for * @return the type binding */ public static ITypeBinding findTypeInHierarchy(ITypeBinding hierarchyType, String fullyQualifiedTypeName) { if (hierarchyType.isArray() || hierarchyType.isPrimitive()) { return null; } if (fullyQualifiedTypeName.equals(hierarchyType.getQualifiedName())) { return hierarchyType; } ITypeBinding superClass = hierarchyType.getSuperclass(); if (superClass != null) { ITypeBinding res = findTypeInHierarchy(superClass, fullyQualifiedTypeName); if (res != null) { return res; } } ITypeBinding[] superInterfaces = hierarchyType.getInterfaces(); for (int i = 0; i < superInterfaces.length; i++) { ITypeBinding res = findTypeInHierarchy(superInterfaces[i], fullyQualifiedTypeName); if (res != null) { return res; } } return null; } /** * Returns the binding of the variable written in an Assignment. * * @param assignment * The assignment * @return The binding or <code>null</code> if no bindings are available. */ public static IVariableBinding getAssignedVariable(Assignment assignment) { Expression leftHand = assignment.getLeftHandSide(); switch (leftHand.getNodeType()) { case ASTNode.SIMPLE_NAME: return (IVariableBinding)((SimpleName)leftHand).resolveBinding(); case ASTNode.QUALIFIED_NAME: return (IVariableBinding)((QualifiedName)leftHand).getName().resolveBinding(); case ASTNode.FIELD_ACCESS: return ((FieldAccess)leftHand).resolveFieldBinding(); case ASTNode.SUPER_FIELD_ACCESS: return ((SuperFieldAccess)leftHand).resolveFieldBinding(); default: return null; } } /** * Returns <code>true</code> if the given type is a super type of a candidate. <code>true</code> is returned if the two type * bindings are identical. * <p/> * <p> * <b>Warning:</b> With the addition of generics, this method is valid in less cases than before. Consider using * {@link TypeRules#canAssign(ITypeBinding, ITypeBinding)} if you're dealing with types of variables. The classical notion of * supertypes only makes sense if you really need to walk the type hierarchy but don't need to play the assignment rules. * </p> * * @param possibleSuperType * the type to inspect * @param type * the type whose super types are looked at * @return <code>true</code> iff <code>possibleSuperType</code> is a super type of <code>type</code> or is equal to it */ public static boolean isSuperType(ITypeBinding possibleSuperType, ITypeBinding type) { return isSuperType(possibleSuperType, type, true); } /** * Returns <code>true</code> if the given type is a super type of a candidate. <code>true</code> is returned if the two type * bindings are identical (TODO) * * @param possibleSuperType * the type to inspect * @param type * the type whose super types are looked at * @param considerTypeArguments * if <code>true</code>, consider type arguments of <code>type</code> * @return <code>true</code> iff <code>possibleSuperType</code> is a super type of <code>type</code> or is equal to it */ public static boolean isSuperType(ITypeBinding possibleSuperType, ITypeBinding type, boolean considerTypeArguments) { if (type.isArray() || type.isPrimitive()) { return false; } if (!considerTypeArguments) { type = type.getTypeDeclaration(); } if (Bindings.equals(type, possibleSuperType)) { return true; } ITypeBinding superClass = type.getSuperclass(); if (superClass != null) { if (isSuperType(possibleSuperType, superClass, considerTypeArguments)) { return true; } } if (possibleSuperType.isInterface()) { ITypeBinding[] superInterfaces = type.getInterfaces(); for (int i = 0; i < superInterfaces.length; i++) { if (isSuperType(possibleSuperType, superInterfaces[i], considerTypeArguments)) { return true; } } } return false; } // /** // * Finds the compilation unit where the type of the given <code>ITypeBinding</code> is defined, // * using the class path defined by the given Java project. Returns <code>null</code> // * if no compilation unit is found (e.g. type binding is from a binary type) // * @param typeBinding the type binding to search for // * @param project the project used as a scope // * @return the compilation unit containing the type // * @throws JavaModelException if an errors occurs in the Java model // */ // public static ICompilationUnit findCompilationUnit(ITypeBinding typeBinding, IJavaProject project) throws JavaModelException // { // IJavaElement type= typeBinding.getJavaElement(); // if (type instanceof IType) // return ((IType) type).getCompilationUnit(); // else // return null; // } // // /** // * Finds a method for the given <code>IMethodBinding</code>. Returns // * <code>null</code> if the type doesn't contain a corresponding method. // * @param method the method to find // * @param type the type to look in // * @return the corresponding IMethod or <code>null</code> // * @throws JavaModelException if an error occurs in the Java model // * @deprecated Use {@link #findMethodInHierarchy(ITypeBinding, String, String[])} or {@link JavaModelUtil} // */ // public static IMethod findMethod(IMethodBinding method, IType type) throws JavaModelException { // method= method.getMethodDeclaration(); // // IMethod[] candidates= type.getMethods(); // for (int i= 0; i < candidates.length; i++) { // IMethod candidate= candidates[i]; // if (candidate.getElementName().equals(method.getName()) && sameParameters(method, candidate)) { // return candidate; // } // } // return null; // } // // //---- Helper methods to convert a method --------------------------------------------- // // private static boolean sameParameters(IMethodBinding method, IMethod candidate) throws JavaModelException { // ITypeBinding[] methodParamters= method.getParameterTypes(); // String[] candidateParameters= candidate.getParameterTypes(); // if (methodParamters.length != candidateParameters.length) // return false; // IType scope= candidate.getDeclaringType(); // for (int i= 0; i < methodParamters.length; i++) { // ITypeBinding methodParameter= methodParamters[i]; // String candidateParameter= candidateParameters[i]; // if (!sameParameter(methodParameter, candidateParameter, scope)) // return false; // } // return true; // } // // private static boolean sameParameter(ITypeBinding type, String candidate, IType scope) throws JavaModelException { // if (type.getDimensions() != Signature.getArrayCount(candidate)) // return false; // // // Normalizes types // if (type.isArray()) // type= type.getElementType(); // candidate= Signature.getElementType(candidate); // // if ((Signature.getTypeSignatureKind(candidate) == Signature.BASE_TYPE_SIGNATURE) != type.isPrimitive()) { // return false; // } // // if (type.isPrimitive() || type.isTypeVariable()) { // return type.getName().equals(Signature.toString(candidate)); // } else { // // normalize (quick hack until binding.getJavaElement works) // candidate= Signature.getTypeErasure(candidate); // type= type.getErasure(); // // if (candidate.charAt(Signature.getArrayCount(candidate)) == Signature.C_RESOLVED) { // return Signature.toString(candidate).equals(Bindings.getFullyQualifiedName(type)); // } else { // String[][] qualifiedCandidates= scope.resolveType(Signature.toString(candidate)); // if (qualifiedCandidates == null || qualifiedCandidates.length == 0) // return false; // String packageName= type.getPackage().isUnnamed() ? "" : type.getPackage().getName(); //$NON-NLS-1$ // String typeName= getTypeQualifiedName(type); // for (int i= 0; i < qualifiedCandidates.length; i++) { // String[] qualifiedCandidate= qualifiedCandidates[i]; // if ( qualifiedCandidate[0].equals(packageName) && // qualifiedCandidate[1].equals(typeName)) // return true; // } // } // } // return false; // } /** * Normalizes a type binding received from an expression to a type binding that can be used inside a declaration signature, but * <em>not</em> as type of a declaration (use {@link #normalizeForDeclarationUse(ITypeBinding, AST)} for that). * <p> * Anonymous types are normalized to the super class or interface. For null or void bindings, <code>null</code> is returned. * </p> * * @param binding * the binding to normalize * @return the normalized binding, can be <code>null</code> * @see #normalizeForDeclarationUse(ITypeBinding, AST) */ public static ITypeBinding normalizeTypeBinding(ITypeBinding binding) { if (binding != null && !binding.isNullType() && !isVoidType(binding)) { if (binding.isAnonymous()) { ITypeBinding[] baseBindings = binding.getInterfaces(); if (baseBindings.length > 0) { return baseBindings[0]; } return binding.getSuperclass(); } if (binding.isCapture()) { return binding.getWildcard(); } return binding; } return null; } public static boolean isVoidType(ITypeBinding binding) { return "void".equals(binding.getName()); //$NON-NLS-1$ } /** * Normalizes the binding so that it can be used as a type inside a declaration (e.g. variable declaration, method return type, * parameter type, ...). For null bindings, java.lang.Object is returned. For void bindings, <code>null</code> is returned. * * @param binding * binding to normalize * @param ast * current AST * @return the normalized type to be used in declarations, or <code>null</code> */ public static ITypeBinding normalizeForDeclarationUse(ITypeBinding binding, AST ast) { if (binding.isNullType()) return ast.resolveWellKnownType("java.lang.Object"); //$NON-NLS-1$ if (binding.isPrimitive()) return binding; binding = normalizeTypeBinding(binding); if (binding == null || !binding.isWildcardType()) return binding; if (binding.isUpperbound()) { return binding.getBound(); } else { return ast.resolveWellKnownType("java.lang.Object"); //$NON-NLS-1$ } } /** * Returns the type binding of the node's enclosing type declaration. * * @param node * an AST node * @return the type binding of the node's parent type declaration, or <code>null</code> */ public static ITypeBinding getBindingOfParentType(ASTNode node) { while (node != null) { if (node instanceof AbstractTypeDeclaration) { return ((AbstractTypeDeclaration)node).resolveBinding(); } else if (node instanceof AnonymousClassDeclaration) { return ((AnonymousClassDeclaration)node).resolveBinding(); } node = node.getParent(); } return null; } /** * Returns the type binding of the node's type context or null if the node is inside an annotation, type parameter, super type * declaration, or Javadoc of a top level type. The result of this method is equal to the result of * {@link #getBindingOfParentType(ASTNode)} for nodes in the type's body. * * @param node * an AST node * @return the type binding of the node's parent type context, or <code>null</code> */ public static ITypeBinding getBindingOfParentTypeContext(ASTNode node) { StructuralPropertyDescriptor lastLocation = null; while (node != null) { if (node instanceof AbstractTypeDeclaration) { AbstractTypeDeclaration decl = (AbstractTypeDeclaration)node; if (lastLocation == decl.getBodyDeclarationsProperty() || lastLocation == decl.getJavadocProperty()) { return decl.resolveBinding(); } else if (decl instanceof EnumDeclaration && lastLocation == EnumDeclaration.ENUM_CONSTANTS_PROPERTY) { return decl.resolveBinding(); } } else if (node instanceof AnonymousClassDeclaration) { return ((AnonymousClassDeclaration)node).resolveBinding(); } lastLocation = node.getLocationInParent(); node = node.getParent(); } return null; } public static String getRawName(ITypeBinding binding) { String name = binding.getName(); if (binding.isParameterizedType() || binding.isGenericType()) { int idx = name.indexOf('<'); if (idx != -1) { return name.substring(0, idx); } } return name; } public static String getRawQualifiedName(ITypeBinding binding) { final String EMPTY = ""; //$NON-NLS-1$ if (binding.isAnonymous() || binding.isLocal()) { return EMPTY; } if (binding.isPrimitive() || binding.isNullType() || binding.isTypeVariable()) { return binding.getName(); } if (binding.isArray()) { String elementTypeQualifiedName = getRawQualifiedName(binding.getElementType()); if (elementTypeQualifiedName.length() != 0) { StringBuffer stringBuffer = new StringBuffer(elementTypeQualifiedName); stringBuffer.append('[').append(']'); return stringBuffer.toString(); } else { return EMPTY; } } if (binding.isMember()) { String outerName = getRawQualifiedName(binding.getDeclaringClass()); if (outerName.length() > 0) { StringBuffer buffer = new StringBuffer(); buffer.append(outerName); buffer.append('.'); buffer.append(getRawName(binding)); return buffer.toString(); } else { return EMPTY; } } else if (binding.isTopLevel()) { IPackageBinding packageBinding = binding.getPackage(); StringBuffer buffer = new StringBuffer(); if (packageBinding != null && packageBinding.getName().length() > 0) { buffer.append(packageBinding.getName()).append('.'); } buffer.append(getRawName(binding)); return buffer.toString(); } return EMPTY; } /** * Tests if the given node is a declaration, not a instance of a generic type, method or field. Declarations can be found in * AST with CompilationUnit.findDeclaringNode * * @param binding * binding to test * @return returns <code>true</code> if the binding is a declaration binding */ public static boolean isDeclarationBinding(IBinding binding) { switch (binding.getKind()) { case IBinding.TYPE: return ((ITypeBinding)binding).getTypeDeclaration() == binding; case IBinding.VARIABLE: return ((IVariableBinding)binding).getVariableDeclaration() == binding; case IBinding.METHOD: return ((IMethodBinding)binding).getMethodDeclaration() == binding; } return true; } public static IBinding getDeclaration(IBinding binding) { switch (binding.getKind()) { case IBinding.TYPE: return ((ITypeBinding)binding).getTypeDeclaration(); case IBinding.VARIABLE: return ((IVariableBinding)binding).getVariableDeclaration(); case IBinding.METHOD: return ((IMethodBinding)binding).getMethodDeclaration(); } return binding; } /** * @param candidates * the candidates * @param overridable * the overriding method * @return returns <code>true></code> if the overriding method overrides a candidate * @deprecated Need to review: Use {@link #isSubsignature(IMethodBinding, IMethodBinding)} if the two bindings are in the same * hierarchy (directly overrides each other), or * {@link #findMethodInHierarchy(ITypeBinding, String, ITypeBinding[])} else. */ public static boolean containsSignatureEquivalentConstructor(IMethodBinding[] candidates, IMethodBinding overridable) { for (int index = 0; index < candidates.length; index++) { if (isSignatureEquivalentConstructor(candidates[index], overridable)) return true; } return false; } private static boolean isSignatureEquivalentConstructor(IMethodBinding overridden, IMethodBinding overridable) { if (!overridden.isConstructor() || !overridable.isConstructor()) return false; if (overridden.isDefaultConstructor()) return false; return areSubTypeCompatible(overridden, overridable); } /** * @param overridden * the overridden method * @param overridable * the overriding method * @return returns <code>true</code> if the overriding method overrrides the overridden * @deprecated Need to review: Use {@link #isSubsignature(IMethodBinding, IMethodBinding)} if the two bindings are in the same * hierarchy (directly overrides each other), or * {@link #findMethodInHierarchy(ITypeBinding, String, ITypeBinding[])} else. */ public static boolean areOverriddenMethods(IMethodBinding overridden, IMethodBinding overridable) { if (!overridden.getName().equals(overridable.getName())) return false; return areSubTypeCompatible(overridden, overridable); } private static boolean areSubTypeCompatible(IMethodBinding overridden, IMethodBinding overridable) { if (overridden.getParameterTypes().length != overridable.getParameterTypes().length) return false; ITypeBinding overriddenReturn = overridden.getReturnType(); ITypeBinding overridableReturn = overridable.getReturnType(); if (overriddenReturn == null || overridableReturn == null) return false; if (!overriddenReturn.getErasure().isSubTypeCompatible(overridableReturn.getErasure())) return false; ITypeBinding[] overriddenTypes = overridden.getParameterTypes(); ITypeBinding[] overridableTypes = overridable.getParameterTypes(); Assert.isTrue(overriddenTypes.length == overridableTypes.length); for (int index = 0; index < overriddenTypes.length; index++) { final ITypeBinding overridableErasure = overridableTypes[index].getErasure(); final ITypeBinding overriddenErasure = overriddenTypes[index].getErasure(); if (!overridableErasure.isSubTypeCompatible(overriddenErasure) || !overridableErasure.getKey().equals(overriddenErasure.getKey())) return false; } ITypeBinding[] overriddenExceptions = overridden.getExceptionTypes(); ITypeBinding[] overridableExceptions = overridable.getExceptionTypes(); boolean checked = false; for (int index = 0; index < overriddenExceptions.length; index++) { checked = false; for (int offset = 0; offset < overridableExceptions.length; offset++) { if (overriddenExceptions[index].isSubTypeCompatible(overridableExceptions[offset])) checked = true; } if (!checked) return false; } return true; } /** * Returns the boxed type binding according to JLS3 5.1.7, or the original binding if the given type is not a primitive type. * * @param type * a type binding * @param ast * an AST to resolve the boxed type * @return the boxed type, or the original type if no boxed type found */ public static ITypeBinding getBoxedTypeBinding(ITypeBinding type, AST ast) { if (!type.isPrimitive()) return type; String boxedTypeName = getBoxedTypeName(type.getName()); if (boxedTypeName == null) return type; ITypeBinding boxed = ast.resolveWellKnownType(boxedTypeName); if (boxed == null) return type; return boxed; } private static String getBoxedTypeName(String primitiveName) { if ("long".equals(primitiveName)) //$NON-NLS-1$ return "java.lang.Long"; //$NON-NLS-1$ else if ("int".equals(primitiveName)) //$NON-NLS-1$ return "java.lang.Integer"; //$NON-NLS-1$ else if ("short".equals(primitiveName)) //$NON-NLS-1$ return "java.lang.Short"; //$NON-NLS-1$ else if ("char".equals(primitiveName)) //$NON-NLS-1$ return "java.lang.Character"; //$NON-NLS-1$ else if ("byte".equals(primitiveName)) //$NON-NLS-1$ return "java.lang.Byte"; //$NON-NLS-1$ else if ("boolean".equals(primitiveName)) //$NON-NLS-1$ return "java.lang.Boolean"; //$NON-NLS-1$ else if ("float".equals(primitiveName)) //$NON-NLS-1$ return "java.lang.Float"; //$NON-NLS-1$ else if ("double".equals(primitiveName)) //$NON-NLS-1$ return "java.lang.Double"; //$NON-NLS-1$ else return null; } /** * Returns the unboxed type binding according to JLS3 5.1.7, or the original binding if the given type is not a boxed type. * * @param type * a type binding * @param ast * an AST to resolve the unboxed type * @return the unboxed type, or the original type if no unboxed type found */ public static ITypeBinding getUnboxedTypeBinding(ITypeBinding type, AST ast) { if (!type.isClass()) return type; String unboxedTypeName = getUnboxedTypeName(type.getQualifiedName()); if (unboxedTypeName == null) return type; ITypeBinding unboxed = ast.resolveWellKnownType(unboxedTypeName); if (unboxed == null) return type; return unboxed; } private static String getUnboxedTypeName(String boxedName) { if ("java.lang.Long".equals(boxedName)) //$NON-NLS-1$ return "long"; //$NON-NLS-1$ else if ("java.lang.Integer".equals(boxedName)) //$NON-NLS-1$ return "int"; //$NON-NLS-1$ else if ("java.lang.Short".equals(boxedName)) //$NON-NLS-1$ return "short"; //$NON-NLS-1$ else if ("java.lang.Character".equals(boxedName)) //$NON-NLS-1$ return "char"; //$NON-NLS-1$ else if ("java.lang.Byte".equals(boxedName)) //$NON-NLS-1$ return "byte"; //$NON-NLS-1$ else if ("java.lang.Boolean".equals(boxedName)) //$NON-NLS-1$ return "boolean"; //$NON-NLS-1$ else if ("java.lang.Float".equals(boxedName)) //$NON-NLS-1$ return "float"; //$NON-NLS-1$ else if ("java.lang.Double".equals(boxedName)) //$NON-NLS-1$ return "double"; //$NON-NLS-1$ else return null; } /** * Resolve the binding (<em>not</em> the type binding) for the expression or a nested expression (e.g. nested in parentheses, * cast, ...). * * @param expression * an expression node * @param goIntoCast * iff <code>true</code>, go into a CastExpression's expression to resolve * @return the expression binding, or <code>null</code> if the expression has no binding or the binding could not be resolved * @see org.eclipse.che.ide.ext.java.jdt.internal.corext.codemanipulation.StubUtility#getVariableNameSuggestions(int, IJavaProject, ITypeBinding, Expression, java.util.Collection) * @since 3.5 */ public static IBinding resolveExpressionBinding(Expression expression, boolean goIntoCast) { // TODO: search for callers of resolve*Binding() methods and replace with call to this method // similar to StubUtility#getVariableNameSuggestions(int, IJavaProject, ITypeBinding, Expression, Collection) switch (expression.getNodeType()) { case ASTNode.SIMPLE_NAME: case ASTNode.QUALIFIED_NAME: return ((Name)expression).resolveBinding(); case ASTNode.FIELD_ACCESS: return ((FieldAccess)expression).resolveFieldBinding(); case ASTNode.SUPER_FIELD_ACCESS: return ((SuperFieldAccess)expression).resolveFieldBinding(); case ASTNode.METHOD_INVOCATION: return ((MethodInvocation)expression).resolveMethodBinding(); case ASTNode.SUPER_METHOD_INVOCATION: return ((SuperMethodInvocation)expression).resolveMethodBinding(); case ASTNode.CLASS_INSTANCE_CREATION: return ((ClassInstanceCreation)expression).resolveConstructorBinding(); case ASTNode.MARKER_ANNOTATION: case ASTNode.SINGLE_MEMBER_ANNOTATION: case ASTNode.NORMAL_ANNOTATION: return ((Annotation)expression).resolveAnnotationBinding(); case ASTNode.ARRAY_ACCESS: return resolveExpressionBinding(((ArrayAccess)expression).getArray(), goIntoCast); case ASTNode.CAST_EXPRESSION: if (goIntoCast) { return resolveExpressionBinding(((CastExpression)expression).getExpression(), true); } else { return null; } case ASTNode.PARENTHESIZED_EXPRESSION: return resolveExpressionBinding(((ParenthesizedExpression)expression).getExpression(), goIntoCast); case ASTNode.PREFIX_EXPRESSION: return resolveExpressionBinding(((PrefixExpression)expression).getOperand(), goIntoCast); case ASTNode.POSTFIX_EXPRESSION: return resolveExpressionBinding(((PostfixExpression)expression).getOperand(), goIntoCast); default: return null; } } }