/*******************************************************************************
* Copyright (c) 2009 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
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.core.ast.nodes;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.php.core.compiler.PHPFlags;
public class Bindings {
public static final String ARRAY_LENGTH_FIELD_BINDING_STRING = "(array type):length";//$NON-NLS-1$
private Bindings() {
// No instance
}
public static int hashCode(IBinding binding) {
Assert.isNotNull(binding);
String key = binding.getKey();
if (key == null)
return binding.hashCode();
return key.hashCode();
}
/**
* Note: this method is for debugging and testing purposes only. There are
* tests whose pre-computed test results rely on the returned String's
* format.
*
* @param binding
* the binding
* @return a string representation of given binding
* @see org.eclipse.jdt.internal.ui.viewsupport.BindingLabelProvider
*/
public static String asString(IBinding binding) {
if (binding instanceof IMethodBinding)
return asString((IMethodBinding) binding);
else if (binding instanceof ITypeBinding)
return ((ITypeBinding) binding).getBinaryName(); // getQualifiedName()
else if (binding instanceof IVariableBinding)
return asString((IVariableBinding) binding);
return binding.toString();
}
private static String asString(IVariableBinding variableBinding) {
if (!variableBinding.isField())
return variableBinding.toString();
if (variableBinding.getDeclaringClass() == null) {
Assert.isTrue(variableBinding.getName().equals("length"));//$NON-NLS-1$
return ARRAY_LENGTH_FIELD_BINDING_STRING;
}
StringBuilder result = new StringBuilder();
result.append(variableBinding.getDeclaringClass().getName());
result.append(':');
result.append(variableBinding.getName());
return result.toString();
}
private static String asString(IMethodBinding method) {
StringBuilder result = new StringBuilder();
result.append(method.getDeclaringClass().getName());
result.append(':');
result.append(method.getName());
result.append('(');
ITypeBinding[] parameters = method.getParameterTypes();
int lastComma = parameters.length - 1;
for (int i = 0; i < parameters.length; i++) {
ITypeBinding parameter = parameters[i];
result.append(parameter.getName());
if (i < lastComma)
result.append(", "); //$NON-NLS-1$
}
result.append(')');
return result.toString();
}
public static ITypeBinding getTopLevelType(ITypeBinding type) {
ITypeBinding parent = type.getSuperclass();
while (parent != null) {
type = parent;
parent = type.getSuperclass();
}
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];
// TODO see if we can remove the dollar sign from here
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 than 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
* @return the method binding representing the method
*/
public static IMethodBinding findMethodInType(ITypeBinding type, String methodName) {
if (type.isPrimitive())
return null;
IMethodBinding[] methods = type.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
if (methodName.equalsIgnoreCase(methods[i].getName()))
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 than the implemented interfaces.
*
* @param type
* The type to search the method in
* @param methodName
* The name of the method to find
* @return the method binding representing the method
*/
public static IMethodBinding findMethodInHierarchy(ITypeBinding type, String methodName) {
IMethodBinding method = findMethodInType(type, methodName);
if (method != null)
return method;
ITypeBinding superClass = type.getSuperclass();
if (superClass != null) {
method = findMethodInHierarchy(superClass, methodName);
if (method != null)
return method;
}
ITypeBinding[] interfaces = type.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
method = findMethodInHierarchy(interfaces[i], methodName);
if (method != null)
return method;
}
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 than the implemented interfaces.
*
* @param type
* The type to search the method in
* @param methodName
* The name of the method to find
* @return the method binding representing the method
*/
public static IMethodBinding[] findAbstractMethodsInHierarchy(ITypeBinding type) {
List<IMethodBinding> methodsToOVerride = new ArrayList<IMethodBinding>();
Set<String> overridenMethodsNames = new HashSet<String>();
collectAbstractMethodsInHierarchy(type, methodsToOVerride, overridenMethodsNames);
return (IMethodBinding[]) methodsToOVerride.toArray(new IMethodBinding[methodsToOVerride.size()]);
}
private static void collectAbstractMethodsInHierarchy(ITypeBinding curr, List<IMethodBinding> methodsToOverride,
Set<String> overridenMethodsNames) {
// start of current IType method pass
if (curr != null) {
if (PHPFlags.isInterface(curr.getModifiers())) {
for (IMethodBinding methodBinding : curr.getDeclaredMethods()) {
if (!overridenMethodsNames.contains(methodBinding.getName())) {
methodsToOverride.add(methodBinding);
overridenMethodsNames.add(methodBinding.getName());
}
}
}
// an abstract class
else if (PHPFlags.isAbstract(curr.getModifiers())) {
for (IMethodBinding methodBinding : curr.getDeclaredMethods()) {
if (!PHPFlags.isAbstract(methodBinding.getModifiers())) {
overridenMethodsNames.add(methodBinding.getName());
} else if (!overridenMethodsNames.contains(methodBinding.getName())) {
methodsToOverride.add(methodBinding);
overridenMethodsNames.add(methodBinding.getName());
}
}
}
else {// add existing methods to exclude list
for (IMethodBinding methodBinding : curr.getDeclaredMethods()) {
if (!overridenMethodsNames.contains(methodBinding.getName())) {
overridenMethodsNames.add(methodBinding.getName());
}
}
}
// end of current IType method pass
// this class has a superclass
ITypeBinding superClassBinding = curr.getSuperclass();
if (superClassBinding != null && superClassBinding.getName() != null) {
collectAbstractMethodsInHierarchy(superClassBinding, methodsToOverride, overridenMethodsNames);
;
}
// this class has interfaces
// ITypeBinding[] interfaceBindings = curr.getInterfaces();
// for (ITypeBinding interfaceBinding : interfaceBindings) {
// collectAbstractMethodsInHierarchy(superClassBinding,
// methodsToOverride, overridenMethodsNames);
// ;
// }
}
}
/**
* 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 than 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) {
return innerFindOverriddenMethodInHierarchy(type, binding, new HashSet<ITypeBinding>());
}
public static IMethodBinding innerFindOverriddenMethodInHierarchy(ITypeBinding type, IMethodBinding binding,
Set<ITypeBinding> processedTypes) {
if (!processedTypes.add(type)) {
return null;
}
IMethodBinding method = findOverriddenMethodInType(type, binding);
if (method != null)
return method;
ITypeBinding superClass = type.getSuperclass();
if (superClass != null) {
method = innerFindOverriddenMethodInHierarchy(superClass, binding, processedTypes);
if (method != null) {
return method;
}
}
ITypeBinding[] interfaces = type.getInterfaces();
if (interfaces != null) {
for (int i = 0; i < interfaces.length; i++) {
method = innerFindOverriddenMethodInHierarchy(interfaces[i], binding, processedTypes);
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 (testVisibility && (PHPFlags
.isPrivate(modifiers) /*
* || PHPFlags.isStatic(modifiers) ||
* overriding.isConstructor()
*/)) {
return null;
}
ITypeBinding type = overriding.getDeclaringClass();
if (type == null) {
return null;
}
if (type.getSuperclass() != null) {
IMethodBinding res = findOverriddenMethodInHierarchy(type.getSuperclass(), overriding);
if (res != null && !PHPFlags.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 (PHPFlags.isPublic(otherflags) || PHPFlags.isProtected(otherflags)
|| (declaringType != null && declaringType.isInterface())) {
return true;
} else if (PHPFlags.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 result = new HashSet();
collectSuperTypes(type, result);
result.remove(type);
return (ITypeBinding[]) result.toArray(new ITypeBinding[result.size()]);
}
private static void collectSuperTypes(ITypeBinding curr, Set 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);
}
}
}
/**
* @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! TODO - PHP handling (shalom)
*/
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().equalsIgnoreCase(overridden.getName())) {
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.getBinaryName())) { // 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;
}
public static ITypeBinding getBindingOfParentType(ASTNode node) {
while (node != null) {
if (node instanceof TypeDeclaration) {
return ((TypeDeclaration) node).resolveTypeBinding();
} else if (node instanceof AnonymousClassDeclaration) {
return ((AnonymousClassDeclaration) node).resolveTypeBinding();
}
node = node.getParent();
}
return null;
}
}