/******************************************************************************* * 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.*; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.dltk.ast.Modifiers; import org.eclipse.dltk.core.*; import org.eclipse.dltk.evaluation.types.MultiTypeType; import org.eclipse.dltk.evaluation.types.SimpleType; import org.eclipse.dltk.ti.types.IEvaluatedType; import org.eclipse.php.core.compiler.PHPFlags; import org.eclipse.php.internal.core.typeinference.PHPClassType; import org.eclipse.php.internal.core.typeinference.PHPModelUtils; import org.eclipse.php.internal.core.typeinference.evaluators.PHPTraitType; public class TypeBinding implements ITypeBinding { private IEvaluatedType type; private IModelElement[] elements; private BindingResolver resolver; private IType[] superTypes; private ITypeBinding superClass; private ITypeBinding[] interfaces; private IVariableBinding[] fields; private IMethodBinding[] methods; private Map<IType, ITypeHierarchy> hierarchy = new HashMap<IType, ITypeHierarchy>(); /** * Constructs a new TypeBinding. * * @param resolver * @param type * @param elements */ public TypeBinding(BindingResolver resolver, IEvaluatedType type, IModelElement[] elements) { this.resolver = resolver; this.type = type; if (elements != null && elements.length > 0) { final int length = elements.length; this.elements = new IModelElement[length]; System.arraycopy(elements, 0, this.elements, 0, length); } else { this.elements = new IModelElement[0]; } } /** * Constructs a new TypeBinding. * * @param resolver * @param type * @param element */ public TypeBinding(BindingResolver resolver, IEvaluatedType type, IModelElement element) { this.resolver = resolver; this.type = type; if (element != null) { this.elements = new IModelElement[] { element }; } else { this.elements = new IModelElement[0]; } } /** * Answer an array type binding using the receiver and the given dimension. * * <p> * If the receiver is an array binding, then the resulting dimension is the * given dimension plus the dimension of the receiver. Otherwise the * resulting dimension is the given dimension. * </p> * * @param dimension * the given dimension * @return an array type binding * @throws IllegalArgumentException * : * <ul> * <li>if the receiver represents the void type</li> * <li>if the resulting dimensions is lower than one or greater * than 255</li> * </ul> */ public ITypeBinding createArrayType(int dimension) { // TODO Auto-generated method stub return null; } /** * Returns the binary name of this type binding. The binary name of a class * is defined in the Java Language Specification 3rd edition, section 13.1. * <p> * Note that in some cases, the binary name may be unavailable. This may * happen, for example, for a local type declared in unreachable code. * </p> * * @return the binary name of this type, or <code>null</code> if the binary * name is unknown */ public String getBinaryName() { if (isUnknown() || isAmbiguous()) { return null; } return this.elements[0].getHandleIdentifier(); } /** * Returns the binding representing the component type of this array type, * or <code>null</code> if this is not an array type binding. The component * type of an array might be an array type. * <p> * This is subject to change before 3.2 release. * </p> * * @return the component type binding, or <code>null</code> if this is not * an array type */ public ITypeBinding getComponentType() { if (!isArray()) { return null; } // TODO - This should be implemented as soon as the we will be able to // identify the types that // the array is holding. // Once we have that, we can return a TypeBinding or a // CompositeTypeBinding for multiple types array. return null; } /** * Returns a list of bindings representing all the fields declared as * members of this class or interface type. * * <p> * These include public, protected, default (package-private) access, and * private fields declared by the class, but excludes inherited fields. * Fields from binary types that reference unresolvable types may not be * included. * </p> * * <p> * Returns an empty list if the class or interface declares no fields, and * for other kinds of type bindings that do not directly have members. * </p> * * <p> * The resulting bindings are in no particular order. * </p> * * @return the list of bindings for the field members of this type, or the * empty list if this type does not have field members */ public IVariableBinding[] getDeclaredFields() { if (isUnknown()) { return new IVariableBinding[0]; } if (fields == null) { if (isClass()) { List<IVariableBinding> variableBindings = new ArrayList<IVariableBinding>(); for (IModelElement element : this.elements) { IType type = (IType) element; try { IField[] fields = type.getFields(); for (int i = 0; i < fields.length; i++) { IVariableBinding variableBinding = resolver.getVariableBinding(fields[i]); if (variableBinding != null) { variableBindings.add(variableBinding); } } } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } } fields = variableBindings.toArray(new IVariableBinding[variableBindings.size()]); } else { fields = new IVariableBinding[0]; } } return fields; } /** * Returns a list of method bindings representing all the methods and * constructors declared for this class, interface or annotation type. * <p> * These include public, protected, default (package-private) access, and * private methods Synthetic methods and constructors may or may not be * included. Returns an empty list if the class or interface type declares * no methods or constructors, if the annotation type declares no members, * or if this type binding represents some other kind of type binding. * Methods from binary types that reference unresolvable types may not be * included. * </p> * <p> * The resulting bindings are in no particular order. * </p> * * @return the list of method bindings for the methods and constructors * declared by this class, interface, or annotation type, or the * empty list if this type does not declare any methods or * constructors */ public IMethodBinding[] getDeclaredMethods() { if (isUnknown()) { return new IMethodBinding[0]; } if (methods == null) { if (isClass() || isTrait()) { List<IMethodBinding> methodBindings = new ArrayList<IMethodBinding>(); for (IModelElement element : this.elements) { IType type = (IType) element; try { IMethod[] methods = type.getMethods(); if (methods != null) { for (int i = 0; i < methods.length; i++) { IMethodBinding methodBinding = resolver.getMethodBinding(methods[i]); methodBindings.add(methodBinding); } } } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } } methods = methodBindings.toArray(new IMethodBinding[methodBindings.size()]); } else { methods = new IMethodBinding[0]; // TODO - Implement // IMethodBinding } } return methods; } /** * Returns the declared modifiers for this class or interface binding as * specified in the original source declaration of the class or interface. * The result may not correspond to the modifiers in the compiled binary, * since the compiler may change them (in particular, for inner class * emulation). The <code>getModifiers</code> method should be used if the * compiled modifiers are needed. Returns -1 if this type does not represent * a class or interface. * * @return the bit-wise or of <code>Modifiers</code> constants * @see Modifiers */ public int getModifiers() { if (isClass()) { // element. } return -1; } /** * Returns the dimensionality of this array type, or <code>0</code> if this * is not an array type binding. * * @return the number of dimension of this array type binding, or * <code>0</code> if this is not an array type */ public int getDimensions() { // TODO Auto-generated method stub return 0; } /** * Returns the binding representing the element type of this array type, or * <code>null</code> if this is not an array type binding. The element type * of an array is never itself an array type. * * @return the element type binding, or <code>null</code> if this is not an * array type */ public ITypeBinding getElementType() { // TODO Auto-generated method stub if (null == this.elements || this.elements.length != 1) { return null; } return null; } /** * Returns a list of type bindings representing the direct superinterfaces * of the class, interface, or enum type represented by this type binding. * <p> * If this type binding represents a class or enum type, the return value is * an array containing type bindings representing all interfaces directly * implemented by this class. The number and order of the interface objects * in the array corresponds to the number and order of the interface names * in the <code>implements</code> clause of the original declaration of this * type. * </p> * <p> * If this type binding represents an interface, the array contains type * bindings representing all interfaces directly extended by this interface. * The number and order of the interface objects in the array corresponds to * the number and order of the interface names in the <code>extends</code> * clause of the original declaration of this interface. * </p> * <p> * If the class or enum implements no interfaces, or the interface extends * no interfaces, or if this type binding represents an array type, a * primitive type, the null type, a type variable, an annotation type, a * wildcard type, or a capture binding, this method returns an array of * length 0. * </p> * * @return the list of type bindings for the interfaces extended by this * class or enum, or interfaces extended by this interface, or * otherwise the empty list */ public ITypeBinding[] getInterfaces() { if (isUnknown()) { return new ITypeBinding[0]; } if (this.interfaces == null) { IType[] types = getSuperTypes(); List<ITypeBinding> interfaces = new LinkedList<ITypeBinding>(); for (IType type : types) { try { if (PHPFlags.isInterface(type.getFlags())) { interfaces.add(resolver.getTypeBinding(type)); } } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } } this.interfaces = (ITypeBinding[]) interfaces.toArray(new ITypeBinding[interfaces.size()]); } return this.interfaces; } /** * Returns the unqualified name of the type represented by this binding if * it has one. * <ul> * <li>For top-level types, member types, and local types, the name is the * simple name of the type. Example: <code>"String"</code> or * <code>"Collection"</code>. Note that the type parameters of a generic * type are not included.</li> * <li>For primitive types, the name is the keyword for the primitive type. * Example: <code>"int"</code>.</li> * <li>For the null type, the name is the string "null".</li> * <li>For anonymous classes, which do not have a name, this method returns * an empty string.</li> * <li>For array types, the name is the unqualified name of the component * type (as computed by this method) followed by "[]". Example: * <code>"String[]"</code>. Note that the component type is never an an * anonymous class.</li> * <li>For type variables, the name is just the simple name of the type * variable (type bounds are not included). Example: <code>"X"</code>.</li> * <li>For type bindings that correspond to particular instances of a * generic type arising from a parameterized type reference, the name is the * unqualified name of the erasure type (as computed by this method) * followed by the names (again, as computed by this method) of the type * arguments surrounded by "<>" and separated by ",". Example: * <code>"Collection<String>"</code>.</li> * <li>For type bindings that correspond to particular instances of a * generic type arising from a raw type reference, the name is the * unqualified name of the erasure type (as computed by this method). * Example: <code>"Collection"</code>.</li> * <li>For wildcard types, the name is "?" optionally followed by a single * space followed by the keyword "extends" or "super" followed a single * space followed by the name of the bound (as computed by this method) when * present. Example: <code>"? extends InputStream"</code>.</li> * <li>Capture types do not have a name. For these types, and array types * thereof, this method returns an empty string.</li> * </ul> * * @return the unqualified name of the type represented by this binding, or * the empty string if it has none * @see #getQualifiedName() */ public String getName() { return isUnknown() ? null : this.type.getTypeName(); } /** * Returns the type associated with this binding. * * @return the type */ public IEvaluatedType getEvaluatedType() { return type; } protected IType[] getSuperTypes() { if (superTypes == null) { if (elements != null && elements.length > 0) { Set<String> superTypeNames = new HashSet<String>(); for (IModelElement element : elements) { IType type = (IType) element; try { String[] superClassNames = type.getSuperClasses(); if (superClassNames != null) { for (String name : superClassNames) { if (!superTypeNames.contains(name)) { superTypeNames.add(name); } } } } catch (CoreException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } } List<IType> typeList = new ArrayList<IType>(); List<IModelElement> elementList = Arrays.asList(elements); for (String superTypeName : superTypeNames) { try { ISourceModule sourceModule = (ISourceModule) elements[0] .getAncestor(IModelElement.SOURCE_MODULE); String typeName = PHPModelUtils.extractElementName(superTypeName); String nameSpace = PHPModelUtils.extractNameSpaceName(superTypeName); Collection<IType> types = resolver.getModelAccessCache().getClassesOrInterfaces(sourceModule, typeName, nameSpace, null); if (types != null) { for (IType type : types) { if (!elementList.contains(type)) { typeList.add(type); } } } } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } } if (typeList.size() > 0) { superTypes = typeList.toArray(new IType[typeList.size()]); } else { superTypes = new IType[0]; } } else { superTypes = new IType[0]; } } return superTypes; } /** * Returns the type binding for the superclass of the type represented by * this class binding. * <p> * If this type binding represents any class other than the class * <code>java.lang.Object</code>, then the type binding for the direct * superclass of this class is returned. If this type binding represents the * class <code>java.lang.Object</code>, then <code>null</code> is returned. * <p> * Loops that ascend the class hierarchy need a suitable termination test. * Rather than test the superclass for <code>null</code>, it is more * transparent to check whether the class is <code>Object</code>, by * comparing whether the class binding is identical to * <code>ast.resolveWellKnownType("java.lang.Object")</code>. * </p> * <p> * If this type binding represents an interface, an array type, a primitive * type, the null type, a type variable, an enum type, an annotation type, a * wildcard type, or a capture binding then <code>null</code> is returned. * </p> * * @return the superclass of the class represented by this type binding, or * <code>null</code> if none * @see AST#resolveWellKnownType(String) */ public ITypeBinding getSuperclass() { if (isUnknown()) { return null; } if (superClass == null) { IType[] types = getSuperTypes(); List<IType> superClasses = new LinkedList<IType>(); for (IType type : types) { try { if (!PHPFlags.isInterface(type.getFlags())) { superClasses.add(type); } } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } } superClass = resolver.getTypeBinding(superClasses.toArray(new IType[superClasses.size()])); } return superClass; } /** * Returns the binding for the type declaration corresponding to this type * binding. * <p> * For parameterized types ({@link #isParameterizedType()}) and most raw * types ({@link #isRawType()}), this method returns the binding for the * corresponding generic type. * </p> * <p> * For raw member types ({@link #isRawType()}, {@link #isMember()}) of a raw * declaring class, the type declaration is a generic or a non-generic type. * </p> * <p> * A different non-generic binding will be returned when one of the * declaring types/methods was parameterized. * </p> * <p> * For other type bindings, this returns the same binding. * </p> * * @return the type binding */ public ITypeBinding getTypeDeclaration() { if (elements.length > 0) return resolver.getTypeBinding((IType) elements[0]); return null; } /** * Returns whether this type binding represents an array type. * * @return <code>true</code> if this type binding is for an array type, and * <code>false</code> otherwise * @see #getElementType() * @see #getDimensions() */ public boolean isArray() { return type instanceof MultiTypeType; } /** * Returns whether this type binding represents a class type or a recovered * binding. * * @return <code>true</code> if this object represents a class or a * recovered binding, and <code>false</code> otherwise */ public boolean isClass() { if (isUnknown()) { return false; } return type.getClass() == PHPClassType.class; } /** * Returns whether this type binding represents a class trait or a recovered * binding. * * @return <code>true</code> if this object represents a trait or a * recovered binding, and <code>false</code> otherwise */ public boolean isTrait() { if (isUnknown()) { return false; } return type.getClass() == PHPTraitType.class; } /** * Returns whether this type binding represents an interface type. * <p> * Note that an interface can also be an annotation type. * </p> * * @return <code>true</code> if this object represents an interface, and * <code>false</code> otherwise */ public boolean isInterface() { if (isUnknown()) { return false; } boolean result = true; for (IModelElement element : elements) { IType member = (IType) element; try { result &= (member.getFlags() & Modifiers.AccInterface) != 0; } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } } return result; } /** * Returns whether this type binding represents the null type. * <p> * The null type is the type of a <code>NullLiteral</code> node. * </p> * * @return <code>true</code> if this type binding is for the null type, and * <code>false</code> otherwise */ public boolean isNullType() { if (type instanceof SimpleType) { return ((SimpleType) type).getType() == SimpleType.TYPE_NULL; } return false; } /** * Returns whether this type binding represents a primitive type. * <p> * There are nine predefined type bindings to represent the eight primitive * types and <code>void</code>. These have the same names as the primitive * types that they represent, namely boolean, byte, char, short, int, long, * float, and double, and void. * </p> * * @return <code>true</code> if this type binding is for a primitive type, * and <code>false</code> otherwise */ public boolean isPrimitive() { return type instanceof SimpleType && !isNullType(); } /** * Returns whether this type is subtype compatible with the given type. * * @param type * the type to check compatibility against * @return <code>true</code> if this type is subtype compatible with the * given type, and <code>false</code> otherwise * * NOTE: if one of the resolved types are not compatible with this * type <code>false</code> is returned */ public boolean isSubTypeCompatible(ITypeBinding otherType) { if (otherType == null || elements == null || elements.length == 0) { return false; } boolean isSubTypeCompatible = false; for (IModelElement element : elements) { IType type = (IType) element; try { if (type.getSuperClasses() == null || type.getSuperClasses().length == 0) { return false; } ITypeHierarchy supertypeHierarchy = hierarchy.get(type); if (supertypeHierarchy == null) { supertypeHierarchy = type.newSupertypeHierarchy(new NullProgressMonitor()); hierarchy.put(type, supertypeHierarchy); } IModelElement[] otherElements = ((TypeBinding) otherType).elements; if (otherElements != null) { for (IModelElement modelElement : otherElements) { if (modelElement instanceof IType && supertypeHierarchy.contains((IType) modelElement)) { isSubTypeCompatible = true; break; } } } } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } } return isSubTypeCompatible; } /** * Returns the key for this binding. * <p> * Within a connected cluster of bindings (for example, all bindings * reachable from a given AST), each binding will have a distinct keys. The * keys are generated in a manner that is predictable and as stable as * possible. This last property makes these keys useful for comparing * bindings between disconnected clusters of bindings (for example, the * bindings between the "before" and "after" ASTs of the same compilation * unit). * </p> * <p> * The exact details of how the keys are generated is unspecified. However, * it is a function of the following information: * <ul> * <li>packages - the name of the package (for an unnamed package, some * internal id)</li> * <li>classes or interfaces - the VM name of the type and the key of its * package</li> * <li>array types - the key of the component type and number of dimensions * </li> * <li>primitive types - the name of the primitive type</li> * <li>fields - the name of the field and the key of its declaring type</li> * <li>methods - the name of the method, the key of its declaring type, and * the keys of the parameter types</li> * <li>constructors - the key of its declaring class, and the keys of the * parameter types</li> * <li>local variables - the name of the local variable, the index of the * declaring block relative to its parent, the key of its method</li> * <li>local types - the name of the type, the index of the declaring block * relative to its parent, the key of its method</li> * <li>anonymous types - the occurrence count of the anonymous type relative * to its declaring type, the key of its declaring type</li> * <li>enum types - treated like classes</li> * <li>annotation types - treated like interfaces</li> * <li>type variables - the name of the type variable and the key of the * generic type or generic method that declares that type variable</li> * <li>wildcard types - the key of the optional wildcard type bound</li> * <li>capture type bindings - the key of the wildcard captured</li> * <li>generic type instances - the key of the generic type and the keys of * the type arguments used to instantiate it, and whether the instance is * explicit (a parameterized type reference) or implicit (a raw type * reference)</li> * <li>generic method instances - the key of the generic method and the keys * of the type arguments used to instantiate it, and whether the instance is * explicit (a parameterized method reference) or implicit (a raw method * reference)</li> * <li>members of generic type instances - the key of the generic type * instance and the key of the corresponding member in the generic type</li> * <li>annotations - the key of the annotated element and the key of the * annotation type</li> * </ul> * </p> * <p> * Note that the key for member value pair bindings is not yet implemented. * This returns <code>null</code> for this kind of bindings.<br> * Recovered bindings have a unique key. * </p> * * @return the key for this binding */ public String getKey() { if (isUnknown() || isAmbiguous()) { return null; } return elements[0].getHandleIdentifier(); } /** * Returns the kind of bindings this is. That is one of the kind constants: * <code>TYPE</code>, <code>VARIABLE</code>, <code>METHOD</code>, or * <code>MEMBER_VALUE_PAIR</code>. * <p> * Note that additional kinds might be added in the future, so clients * should not assume this list is exhaustive and should program defensively, * e.g. by having a reasonable default in a switch statement. * </p> * * @return one of the kind constants */ public int getKind() { return IBinding.TYPE; } /** * Returns the PHP element that corresponds to this binding. Returns * <code>null</code> if this binding has no corresponding PHP element. * <p> * For array types, this method returns the PHP element that corresponds to * the array's element type. For raw and parameterized types, this method * returns the PHP element of the erasure. For annotations, this method * returns the PHP element of the annotation (i.e. an {@link IAnnotation}). * </p> * <p> * Here are the cases where a <code>null</code> should be expected: * <ul> * <li>primitive types, including void</li> * <li>null type</li> * <li>wildcard types</li> * <li>capture types</li> * <li>array types of any of the above</li> * <li>the "length" field of an array type</li> * <li>the default constructor of a source class</li> * <li>the constructor of an anonymous class</li> * <li>member value pairs</li> * </ul> * For all other kind of type, method, variable, annotation and package * bindings, this method returns non-<code>null</code>. * </p> * * @return the PHP element that corresponds to this binding, or * <code>null</code> if none * @since 3.1 */ public IModelElement getPHPElement() { if (isUnknown() || isAmbiguous()) { return null; } return elements[0]; } /** * Return whether this binding is for something that is deprecated. A * deprecated class, interface, field, method, or constructor is one that is * marked with the 'deprecated' tag in its PHPdoc comment. * * Note: Currently we return false for all type bindings since we do not * check for the PHPDoc deprecated annotation. * * @return <code>true</code> if this binding is deprecated, and * <code>false</code> otherwise (Currently, returns false all the * time). */ public boolean isDeprecated() { return false; } /** * There is no special definition of equality for bindings; equality is * simply object identity. Within the context of a single cluster of * bindings, each binding is represented by a distinct object. However, * between different clusters of bindings, the binding objects may or may * not be different; in these cases, the client should compare bindings * using {@link #isEqualTo(IBinding)}, which checks their keys. * * @param other * {@inheritDoc} * @return {@inheritDoc} */ public boolean equals(Object other) { if (other == this) { // identical binding - equal (key or no key) return true; } if (other == null) { // other binding missing return false; } if (!(other instanceof TypeBinding)) { return false; } TypeBinding otherBinding = (TypeBinding) other; if (!this.type.equals(otherBinding.type)) { return false; } if (this.elements == null) { return otherBinding.elements == null; } if (elements.length != otherBinding.elements.length) { return false; } for (int i = 0; i < elements.length; i++) { boolean hasEqual = false; for (int j = 0; j < otherBinding.elements.length; j++) { // if found equals, break the inner loop if (elements[i].equals(otherBinding.elements[j])) { hasEqual = true; break; } } // if no equal element found, return false. if (!hasEqual) { return false; } } return true; } /* * (non-Java) * * @see ITypeBinding#isAmbiguous() */ public boolean isAmbiguous() { return !isUnknown() && (elements.length != 1); } /* * (non-Java) * * @see ITypeBinding#isUnknown() */ public boolean isUnknown() { return this.elements == null && !(isPrimitive() || isNullType() || isArray()); } public List<IType> getTraitList(boolean isMethod, String classMemberName, boolean includeSuper) { List<IType> result = new LinkedList<IType>(); if (this.elements == null || elements.length == 0) { return result; } for (IModelElement type : elements) { IType trait = getTrait((IType) type, isMethod, classMemberName); if (trait != null) { result.add(trait); } } if (includeSuper) { for (IModelElement element : elements) { IType type = (IType) element; try { if (type.getSuperClasses() == null || type.getSuperClasses().length == 0 || PHPFlags.isTrait(type.getFlags())) { return result; } ITypeHierarchy supertypeHierarchy = hierarchy.get(type); if (supertypeHierarchy == null) { supertypeHierarchy = type.newSupertypeHierarchy(new NullProgressMonitor()); hierarchy.put(type, supertypeHierarchy); } IType trait = getTrait(type, isMethod, classMemberName); if (trait != null) { result.add(trait); } } catch (ModelException e) { if (DLTKCore.DEBUG) { e.printStackTrace(); } } } } return result; } private IType getTrait(IType type, boolean isMethod, String classMemberName) { if (type != null) { try { // if (PHPFlags.isTrait(element.getFlags())) { // return element; // } IMember[] members; if (isMethod) { members = PHPModelUtils.getTypeMethod(type, classMemberName, true); } else { members = PHPModelUtils.getTypeField(type, classMemberName, true); } for (IMember member : members) { IType declaringType = member.getDeclaringType(); if (PHPFlags.isTrait(declaringType.getFlags())) { return declaringType; } } } catch (ModelException e) { } } return null; } public IModelElement[] getPHPElements() { return elements; } }