/******************************************************************************* * Copyright (c) 2005, 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 *******************************************************************************/ package org.eclipse.wst.jsdt.core.infer; import java.util.ArrayList; import java.util.Iterator; import org.eclipse.wst.jsdt.core.ast.IASTNode; import org.eclipse.wst.jsdt.core.ast.IAbstractFunctionDeclaration; import org.eclipse.wst.jsdt.core.ast.IFunctionDeclaration; import org.eclipse.wst.jsdt.core.compiler.CharOperation; import org.eclipse.wst.jsdt.internal.compiler.ast.ASTNode; import org.eclipse.wst.jsdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.wst.jsdt.internal.compiler.lookup.ArrayBinding; import org.eclipse.wst.jsdt.internal.compiler.lookup.ClassScope; import org.eclipse.wst.jsdt.internal.compiler.lookup.MethodBinding; import org.eclipse.wst.jsdt.internal.compiler.lookup.MultipleTypeBinding; import org.eclipse.wst.jsdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.wst.jsdt.internal.compiler.lookup.Scope; import org.eclipse.wst.jsdt.internal.compiler.lookup.SourceTypeBinding; import org.eclipse.wst.jsdt.internal.compiler.lookup.TagBits; import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeBinding; import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeConstants; import org.eclipse.wst.jsdt.internal.compiler.util.HashtableOfObject; /** * The represenation of an inferred type. * * Provisional API: This class/interface is part of an interim API that is still under development and expected to * change significantly before reaching stability. It is being made available at this early stage to solicit feedback * from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken * (repeatedly) as the API evolves. */ public class InferredType extends ASTNode { char [] name; public ArrayList methods; public InferredAttribute[] attributes=new InferredAttribute[5]; public int numberAttributes=0; HashtableOfObject attributesHash = new HashtableOfObject(); public InferredType superClass; public InferredType referenceClass; public SourceTypeBinding binding; public boolean isDefinition; private TypeBinding resolvedType; public ClassScope scope; ReferenceBinding resolvedSuperType; public boolean isArray=false; public boolean isAnonymous=false; public boolean isObjectLiteral=false; private int nameStart = -1; public String inferenceProviderID; public String inferenceStyle; public ArrayList mixins; public final static char[] OBJECT_NAME=new char[]{'O','b','j','e','c','t'}; public final static char[] OBJECT_LITERAL_NAME = new char[]{'{','}'}; public final static char[] ARRAY_NAME=new char[]{'A','r','r','a','y'}; public final static char[] FUNCTION_NAME=new char[]{'F','u','n','c','t','i','o','n'}; public final static char[] GLOBAL_NAME=new char[]{'G','l','o','b','a','l'}; public Object userData; boolean allStatic=false; /** * Create a new inferred type * * @param className inferred type name */ public InferredType(char [] className) { this.name=className; this.sourceStart=-1; } /** * Gets the name of the inferred type * * @return the inferred type name */ public char [] getName() { return name; } /** * Get the superclass name of the inferred type * * @return superclass name */ public char [] getSuperClassName() { return superClass!=null ? superClass.getName() : OBJECT_NAME; } /** * Add a new inferred attribute to the inferred type * * @param name the attribute name * @param definer the ASTNode which this attribute is inferred from * @param nameStart character position (in the source) of the attribute name * @return a new InferredAttribute */ public InferredAttribute addAttribute(char [] name, IASTNode definer, int nameStart) { InferredAttribute attribute = findAttribute(name); if (attribute==null) { attribute=new InferredAttribute(name, this, definer); attribute.node=(ASTNode)definer; if (this.numberAttributes == this.attributes.length) System.arraycopy( this.attributes, 0, this.attributes = new InferredAttribute[this.numberAttributes * 2], 0, this.numberAttributes ); this.attributes [this.numberAttributes ++] = attribute; attributesHash.put(name, attribute); if (!isAnonymous) { this.updatePositions(definer.sourceStart(), definer.sourceEnd()); } } attribute.nameStart = nameStart; return attribute; } /** * Add an InferredAttribute to this inferred type. * * @param newAttribute the attribute to add. * @return */ public InferredAttribute addAttribute(InferredAttribute newAttribute) { IASTNode definer=newAttribute.node; InferredAttribute attribute = findAttribute(newAttribute.name); if (attribute==null) { if (this.numberAttributes == this.attributes.length) System.arraycopy( this.attributes, 0, this.attributes = new InferredAttribute[this.numberAttributes * 2], 0, this.numberAttributes ); this.attributes [this.numberAttributes ++] = newAttribute; attributesHash.put(newAttribute.name, newAttribute); if (!isAnonymous) { if (definer != null) { this.updatePositions(definer.sourceStart(), definer.sourceEnd()); } else { this.updatePositions(newAttribute.sourceStart(), newAttribute.sourceEnd()); } } } return newAttribute; } /** * Find the inferred attribute with the given name * * @param name name of the attribute to find * @return the found InferredAttribute, or null if not found */ public InferredAttribute findAttribute(char [] name) { return (InferredAttribute)attributesHash.get(name); // if (attributes!=null) // for (Iterator attrIterator = attributes.iterator(); attrIterator.hasNext();) { // InferredAttribute attribute = (InferredAttribute) attrIterator.next(); // if (CharOperation.equals(name,attribute.name)) // return attribute; // } // return null; } /** * Add a new constructor method to the inferred type * * @param methodName name of the method to add * @param functionDeclaration the AST Node containing the method bode * @param nameStart character position (in the source) of the method name * @return a new inferred method */ public InferredMethod addConstructorMethod(char [] methodName, IFunctionDeclaration functionDeclaration, int nameStart) { InferredMethod method = this.addMethod(methodName, functionDeclaration, nameStart, true); method.isConstructor = true; this.setNameStart(nameStart); method.getFunctionDeclaration().setInferredType(this); return method; } /** * Add a new method to the inferred type * * @param methodName name of the method to add * @param functionDeclaration the AST Node containing the method bode * @param nameStart character position (in the source) of the method name * @return a new inferred method */ public InferredMethod addMethod(char [] methodName, IFunctionDeclaration functionDeclaration, int nameStart) { return this.addMethod(methodName, functionDeclaration, nameStart, false); } /** * Add a new method to the inferred type * * @param methodName name of the method to add * @param functionDeclaration the AST Node containing the method bode * @param isConstructor true if it is a constructor * @return a new inferred method */ private InferredMethod addMethod(char [] methodName, IFunctionDeclaration functionDeclaration, int nameStart, boolean isConstructor) { MethodDeclaration methodDeclaration = (MethodDeclaration)functionDeclaration; InferredMethod method = findMethod(methodName, methodDeclaration); if (method==null) { method=new InferredMethod(methodName,methodDeclaration,this); if (methodDeclaration.inferredMethod==null) methodDeclaration.inferredMethod = method; else { if (isConstructor) { methodDeclaration.inferredMethod.inType=this; method.isStatic=methodDeclaration.inferredMethod.isStatic; method.bits=methodDeclaration.inferredMethod.bits; methodDeclaration.inferredMethod = method; } else if (methodDeclaration.inferredMethod.isConstructor) method.inType=methodDeclaration.inferredMethod.inType; } if (methods==null) methods=new ArrayList(); methods.add(method); if( !isAnonymous ) this.updatePositions(methodDeclaration.sourceStart, methodDeclaration.sourceEnd); method.isConstructor=isConstructor; method.nameStart = nameStart; } else { if (methodDeclaration.inferredMethod==null) { methodDeclaration.inferredMethod=method; } } return method; } /** * Find an inferred method * * @param methodName name of the method to find * @param methodDeclaration not used * @return the found method, or null */ public InferredMethod findMethod(char [] methodName, IFunctionDeclaration methodDeclaration) { boolean isConstructor= methodName==TypeConstants.INIT; if (methods!=null) for (Iterator methodIterator = methods.iterator(); methodIterator.hasNext();) { InferredMethod method = (InferredMethod) methodIterator.next(); if (CharOperation.equals(methodName,method.name)) return method; if (isConstructor && method.isConstructor) return method; } return null; } public TypeBinding resolveType(Scope scope, ASTNode node) { // handle the error here if (this.resolvedType != null) // is a shared type reference which was already resolved return this.resolvedType.isValidBinding() ? this.resolvedType : null; // already reported error if (isArray()) { TypeBinding memberType = (referenceClass!=null)?referenceClass.resolveType(scope,node):null; if (memberType==null) memberType=TypeBinding.UNKNOWN; this.resolvedType=new ArrayBinding(memberType, 1, scope.compilationUnitScope().environment) ; } else { if (CharOperation.indexOf('|', name)>0) { char[][] names = CharOperation.splitAndTrimOn('|', name); this.resolvedType=new MultipleTypeBinding(scope,names); } else this.resolvedType = scope.getType(name); /* the inferred type isn't valid, so don't assign it to the variable */ if(!this.resolvedType.isValidBinding()) this.resolvedType = null; } if (this.resolvedType == null) return null; // detected cycle while resolving hierarchy if (node!=null && !this.resolvedType.isValidBinding()) { scope.problemReporter().invalidType(node, this.resolvedType); return null; } if (node!=null && node.isTypeUseDeprecated(this.resolvedType, scope)) scope.problemReporter().deprecatedType(this.resolvedType, node); if( isAnonymous ) this.resolvedType.tagBits |= TagBits.AnonymousTypeMask; return this.resolvedType ; } public void dumpReference(StringBuffer sb) { sb.append(name); if (referenceClass!=null) { sb.append('('); referenceClass.dumpReference(sb); sb.append(')'); } } public boolean containsMethod(IAbstractFunctionDeclaration inMethod) { if (methods!=null) for (Iterator iter = methods.iterator(); iter.hasNext();) { InferredMethod method = (InferredMethod) iter.next(); if (method.getFunctionDeclaration()==inMethod) return true; } return false; } public ReferenceBinding resolveSuperType(ClassScope classScope) { if (this.resolvedSuperType != null) return this.resolvedSuperType; if(superClass != null) this.resolvedSuperType = (ReferenceBinding)classScope.getType(superClass.getName()); return this.resolvedSuperType; } public boolean isArray() { return CharOperation.equals(ARRAY_NAME, name); } public boolean isFunction() { return CharOperation.equals(FUNCTION_NAME, name); } public StringBuffer print(int indent, StringBuffer output) { printIndent(indent, output); char[] superName= getSuperClassName(); output.append("class ").append(name).append(" extends ").append(superName).append("{\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ for (int i=0;i<this.numberAttributes;i++) { this.attributes[i].print(indent+1,output); output.append(";\n"); //$NON-NLS-1$ } if (methods!=null) for (Iterator methodIterator = methods.iterator(); methodIterator.hasNext();) { InferredMethod method = (InferredMethod) methodIterator.next(); method.print(indent+1,output); output.append("\n"); //$NON-NLS-1$ } output.append("}"); //$NON-NLS-1$ return output; } public boolean isInferred() { return true; } public void updatePositions(int start, int end) { if (this.sourceStart==-1 ||(start>=0 && start<this.sourceStart)) this.sourceStart=start; if (end>0&&end>this.sourceEnd) this.sourceEnd=end; } public IAbstractFunctionDeclaration declarationOf(MethodBinding methodBinding) { if (methodBinding != null && this.methods != null) { for (int i = 0, max = this.methods.size(); i < max; i++) { InferredMethod method=(InferredMethod) this.methods.get(i); if (method.methodBinding==methodBinding) return method.getFunctionDeclaration(); } } return null; } public boolean isNamed() { return !isAnonymous || !CharOperation.prefixEquals(IInferEngine.ANONYMOUS_PREFIX, this.name); } /** * Set the charactor position (in the source) of the type name * * @param start type name position */ public void setNameStart(int start) { this.nameStart=start; } public int getNameStart() { return this.nameStart!= -1 ? this.nameStart : this.sourceStart; } public boolean isEmptyGlobal() { return (CharOperation.equals(GLOBAL_NAME, this.name) && this.numberAttributes==0 && (this.methods==null || this.methods.isEmpty())); } /** * <p>Adds the name of a type to mix into this type once all of the types have * been inferred</p> * * @param mixinTypeName the name of the type to mix into this type */ public void addMixin(char[] mixinTypeName) { if (mixins==null) mixins=new ArrayList(); mixins.add(mixinTypeName); } }