/******************************************************************************* * Copyright (c) 2000, 2013 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.internal.corext.dom; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import org.eclipse.wst.jsdt.core.IJavaScriptElement; import org.eclipse.wst.jsdt.core.IJavaScriptUnit; import org.eclipse.wst.jsdt.core.JavaScriptModelException; import org.eclipse.wst.jsdt.core.dom.AST; import org.eclipse.wst.jsdt.core.dom.ASTNode; import org.eclipse.wst.jsdt.core.dom.AbstractTypeDeclaration; import org.eclipse.wst.jsdt.core.dom.AnonymousClassDeclaration; import org.eclipse.wst.jsdt.core.dom.Block; import org.eclipse.wst.jsdt.core.dom.BodyDeclaration; import org.eclipse.wst.jsdt.core.dom.CatchClause; import org.eclipse.wst.jsdt.core.dom.ClassInstanceCreation; import org.eclipse.wst.jsdt.core.dom.Expression; import org.eclipse.wst.jsdt.core.dom.FieldAccess; import org.eclipse.wst.jsdt.core.dom.ForInStatement; import org.eclipse.wst.jsdt.core.dom.ForStatement; import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration; import org.eclipse.wst.jsdt.core.dom.FunctionInvocation; import org.eclipse.wst.jsdt.core.dom.IBinding; import org.eclipse.wst.jsdt.core.dom.IFunctionBinding; import org.eclipse.wst.jsdt.core.dom.ITypeBinding; import org.eclipse.wst.jsdt.core.dom.IVariableBinding; import org.eclipse.wst.jsdt.core.dom.ImportDeclaration; import org.eclipse.wst.jsdt.core.dom.Initializer; import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit; import org.eclipse.wst.jsdt.core.dom.Modifier; import org.eclipse.wst.jsdt.core.dom.QualifiedName; import org.eclipse.wst.jsdt.core.dom.SimpleName; import org.eclipse.wst.jsdt.core.dom.Statement; import org.eclipse.wst.jsdt.core.dom.SuperMethodInvocation; import org.eclipse.wst.jsdt.core.dom.SwitchCase; import org.eclipse.wst.jsdt.core.dom.SwitchStatement; import org.eclipse.wst.jsdt.core.dom.Type; import org.eclipse.wst.jsdt.core.dom.TypeDeclarationStatement; import org.eclipse.wst.jsdt.core.dom.VariableDeclaration; import org.eclipse.wst.jsdt.core.dom.VariableDeclarationExpression; import org.eclipse.wst.jsdt.core.dom.VariableDeclarationStatement; import org.eclipse.wst.jsdt.internal.core.SearchableEnvironment; import org.eclipse.wst.jsdt.internal.ui.text.correction.ASTResolving; /** * * 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 ScopeAnalyzer { private static final IBinding[] NO_BINDING= new IBinding[0]; /** * Flag to specify that method should be reported. */ public static final int METHODS= 1; /** * Flag to specify that variables should be reported. */ public static final int VARIABLES= 2; /** * Flag to specify that types should be reported. */ public static final int TYPES= 4; /** * Flag to specify that only visible elements should be added. */ public static final int CHECK_VISIBILITY= 16; private static interface IBindingRequestor { boolean acceptBinding(IBinding binding); } private static class DefaultBindingRequestor implements IBindingRequestor { private final List fResult; private final HashSet fNamesAdded; private final int fFlags; private final ITypeBinding fParentTypeBinding; public DefaultBindingRequestor(ITypeBinding parentTypeBinding, int flags) { fParentTypeBinding= parentTypeBinding; fFlags= flags; fResult= new ArrayList(); fNamesAdded= new HashSet(); } public DefaultBindingRequestor() { this(null, 0); } /** * {@inheritDoc} */ public boolean acceptBinding(IBinding binding) { if (binding == null) return false; String signature= getSignature(binding); if (signature != null && fNamesAdded.add(signature)) { // avoid duplicated results from inheritance fResult.add(binding); } return false; } public List getResult() { if (hasFlag(CHECK_VISIBILITY, fFlags)) { for (int i= fResult.size() - 1; i >= 0; i--) { IBinding binding= (IBinding) fResult.get(i); if (!isVisible(binding, fParentTypeBinding)) { fResult.remove(i); } } } return fResult; } } private HashSet fTypesVisited; private JavaScriptUnit fRoot; public ScopeAnalyzer(JavaScriptUnit root) { fTypesVisited= new HashSet(); fRoot= root; } private void clearLists() { fTypesVisited.clear(); } private static String getSignature(IBinding binding) { if (binding != null) { switch (binding.getKind()) { case IBinding.METHOD: StringBuffer buf= new StringBuffer(); buf.append('M'); buf.append(binding.getName()).append('('); ITypeBinding[] parameters= ((IFunctionBinding) binding).getParameterTypes(); for (int i= 0; i < parameters.length; i++) { if (i > 0) { buf.append(','); } ITypeBinding paramType= parameters[i].getErasure(); buf.append(paramType.getQualifiedName()); } buf.append(')'); return buf.toString(); case IBinding.VARIABLE: return 'V' + binding.getName(); case IBinding.TYPE: return 'T' + binding.getName(); } } return null; } static final boolean hasFlag(int property, int flags) { return (flags & property) != 0; } /** * Collects all elements available in a type and its hierarchy * @param binding The type binding * @param flags Flags defining the elements to report * @param requestor the requestor to which all results are reported * @return return <code>true</code> if the requestor has reported the binding as found and no further results are required */ private boolean addInherited(ITypeBinding binding, int flags, IBindingRequestor requestor) { if (!fTypesVisited.add(binding)) { return false; } if (hasFlag(VARIABLES, flags)) { IVariableBinding[] variableBindings= binding.getDeclaredFields(); for (int i= 0; i < variableBindings.length; i++) { if (requestor.acceptBinding(variableBindings[i])) return true; } } if (hasFlag(METHODS, flags)) { IFunctionBinding[] methodBindings= binding.getDeclaredMethods(); for (int i= 0; i < methodBindings.length; i++) { IFunctionBinding curr= methodBindings[i]; if (!curr.isConstructor()) { if (requestor.acceptBinding(curr)) return true; } } } if (hasFlag(TYPES, flags)) { ITypeBinding[] typeBindings= binding.getDeclaredTypes(); for (int i= 0; i < typeBindings.length; i++) { ITypeBinding curr= typeBindings[i]; if (requestor.acceptBinding(curr)) return true; } } ITypeBinding superClass= binding.getSuperclass(); if (superClass != null) { if (addInherited(superClass, flags, requestor)) // recursive return true; } else if (binding.isArray()) { if (addInherited(fRoot.getAST().resolveWellKnownType("java.lang.Object"), flags, requestor)) //$NON-NLS-1$ return true; } return false; } /** * Collects all elements available in a type: its hierarchy and its outer scopes. * @param binding The type binding * @param flags Flags defining the elements to report * @param requestor the requestor to which all results are reported * @return return <code>true</code> if the requestor has reported the binding as found and no further results are required */ private boolean addTypeDeclarations(ITypeBinding binding, int flags, IBindingRequestor requestor) { if (hasFlag(TYPES, flags) && !binding.isAnonymous()) { if (requestor.acceptBinding(binding)) return true; } addInherited(binding, flags, requestor); // add inherited if (binding.isLocal()) { addOuterDeclarationsForLocalType(binding, flags, requestor); } else { ITypeBinding declaringClass= binding.getDeclaringClass(); if (declaringClass != null) { if (addTypeDeclarations(declaringClass, flags, requestor)) // Recursively add inherited return true; } else if (hasFlag(TYPES, flags)) { if (fRoot.findDeclaringNode(binding) != null) { List types= fRoot.types(); for (int i= 0; i < types.size(); i++) { if (requestor.acceptBinding(((AbstractTypeDeclaration) types.get(i)).resolveBinding())) return true; } } } } return false; } private boolean addOuterDeclarationsForLocalType(ITypeBinding localBinding, int flags, IBindingRequestor requestor) { ASTNode node= fRoot.findDeclaringNode(localBinding); if (node == null) { return false; } if (node instanceof AbstractTypeDeclaration || node instanceof AnonymousClassDeclaration) { if (addLocalDeclarations(node.getParent(), flags, requestor)) return true; ITypeBinding parentTypeBinding= Bindings.getBindingOfParentType(node.getParent()); if (parentTypeBinding != null) { if (addTypeDeclarations(parentTypeBinding, flags, requestor)) return true; } }else if (node instanceof JavaScriptUnit) { addLocalDeclarations(node, flags, requestor); ITypeBinding parentTypeBinding= Bindings.getBindingOfParentType(node.getParent()); if (parentTypeBinding != null) { if (addTypeDeclarations(parentTypeBinding, flags, requestor)) return true; } IJavaScriptElement element = ((JavaScriptUnit) node).getJavaElement(); try { SearchableEnvironment env = element.newSearchableNameEnvironment(new IJavaScriptUnit[]{}); if(env!=null) { //SearchRequestor searchReq = new SearchRequestor(localBinding,); //env.findTypes(new char[] {}, true, false,flags, ); } } catch (JavaScriptModelException ex) { // TODO Auto-generated catch block ex.printStackTrace(); } return true; } return false; } private static ITypeBinding getBinding(Expression node) { if (node != null) { return node.resolveTypeBinding(); } return null; } private static ITypeBinding getQualifier(SimpleName selector) { ASTNode parent= selector.getParent(); switch (parent.getNodeType()) { case ASTNode.FUNCTION_INVOCATION: FunctionInvocation decl= (FunctionInvocation) parent; if (selector == decl.getName()) { return getBinding(decl.getExpression()); } return null; case ASTNode.QUALIFIED_NAME: QualifiedName qualifiedName= (QualifiedName) parent; if (selector == qualifiedName.getName()) { return getBinding(qualifiedName.getQualifier()); } return null; case ASTNode.FIELD_ACCESS: FieldAccess fieldAccess= (FieldAccess) parent; if (selector == fieldAccess.getName()) { return getBinding(fieldAccess.getExpression()); } return null; case ASTNode.SUPER_FIELD_ACCESS: { ITypeBinding curr= Bindings.getBindingOfParentType(parent); return curr.getSuperclass(); } case ASTNode.SUPER_METHOD_INVOCATION: { SuperMethodInvocation superInv= (SuperMethodInvocation) parent; if (selector == superInv.getName()) { ITypeBinding curr= Bindings.getBindingOfParentType(parent); return curr.getSuperclass(); } return null; } default: if (parent instanceof Type) { // bug 67644: in 'a.new X()', all member types of A are visible as location of X. ASTNode normalizedNode= ASTNodes.getNormalizedNode(parent); if (normalizedNode.getLocationInParent() == ClassInstanceCreation.TYPE_PROPERTY) { ClassInstanceCreation creation= (ClassInstanceCreation) normalizedNode.getParent(); return getBinding(creation.getExpression()); } } return null; } } public IBinding[] getDeclarationsInScope(SimpleName selector, int flags) { try { // special case for switch on enum if (selector.getLocationInParent() == SwitchCase.EXPRESSION_PROPERTY) { ((SwitchStatement) selector.getParent().getParent()).getExpression().resolveTypeBinding(); } ITypeBinding parentTypeBinding= Bindings.getBindingOfParentType(selector); if (parentTypeBinding != null) { ITypeBinding binding= getQualifier(selector); DefaultBindingRequestor requestor= new DefaultBindingRequestor(parentTypeBinding, flags); if (binding == null) { addLocalDeclarations(selector, flags, requestor); addTypeDeclarations(parentTypeBinding, flags, requestor); } else { addInherited(binding, flags, requestor); } List result= requestor.getResult(); return (IBinding[]) result.toArray(new IBinding[result.size()]); } return NO_BINDING; } finally { clearLists(); } } private static class SearchRequestor implements IBindingRequestor { private final int fFlags; private final ITypeBinding fParentTypeBinding; private final IBinding fToSearch; private boolean fFound; private boolean fIsVisible; public SearchRequestor(IBinding toSearch, ITypeBinding parentTypeBinding, int flag) { fFlags= flag; fToSearch= toSearch; fParentTypeBinding= parentTypeBinding; fFound= false; fIsVisible= true; } public boolean acceptBinding(IBinding binding) { if (fFound) return true; if (binding == null) return false; if (fToSearch.getKind() != binding.getKind()) { return false; } boolean checkVisibility= hasFlag(CHECK_VISIBILITY, fFlags); if (binding == fToSearch) { fFound= true; } else { IBinding bindingDeclaration= Bindings.getDeclaration(binding); if (bindingDeclaration == fToSearch) { fFound= true; } else if (bindingDeclaration.getName().equals(fToSearch.getName())) { String signature= getSignature(bindingDeclaration); if (signature != null && signature.equals(getSignature(fToSearch))) { if (checkVisibility) { fIsVisible= false; } return true; // found element that hides the binding to find } } } if (fFound && checkVisibility) { fIsVisible= ScopeAnalyzer.isVisible(binding, fParentTypeBinding); } return fFound; } public boolean found() { return fFound; } public boolean isVisible() { return fIsVisible; } } public boolean isDeclaredInScope(IBinding declaration, SimpleName selector, int flags) { try { // special case for switch on enum if (selector.getLocationInParent() == SwitchCase.EXPRESSION_PROPERTY) { ((SwitchStatement) selector.getParent().getParent()).getExpression().resolveTypeBinding(); } ITypeBinding parentTypeBinding= Bindings.getBindingOfParentTypeContext(selector); if (parentTypeBinding != null) { ITypeBinding binding= getQualifier(selector); SearchRequestor requestor= new SearchRequestor(declaration, parentTypeBinding, flags); if (binding == null) { addLocalDeclarations(selector, flags, requestor); if (requestor.found()) return requestor.isVisible(); addTypeDeclarations(parentTypeBinding, flags, requestor); if (requestor.found()) return requestor.isVisible(); } else { addInherited(binding, flags, requestor); if (requestor.found()) return requestor.isVisible(); } } return false; } finally { clearLists(); } } public IBinding[] getDeclarationsInScope(int offset, int flags) { NodeFinder finder= new NodeFinder(offset, 0); fRoot.accept(finder); ASTNode node= finder.getCoveringNode(); if (node == null) { return NO_BINDING; } if (node instanceof SimpleName) { return getDeclarationsInScope((SimpleName) node, flags); } try { ITypeBinding binding= Bindings.getBindingOfParentType(node); DefaultBindingRequestor requestor= new DefaultBindingRequestor(binding, flags); addLocalDeclarations(node, offset, flags, requestor); if (binding != null) { addTypeDeclarations(binding, flags, requestor); } List result= requestor.getResult(); return (IBinding[]) result.toArray(new IBinding[result.size()]); } finally { clearLists(); } } private static ITypeBinding getDeclaringType(IBinding binding) { switch (binding.getKind()) { case IBinding.VARIABLE: return ((IVariableBinding) binding).getDeclaringClass(); case IBinding.METHOD: return ((IFunctionBinding) binding).getDeclaringClass(); case IBinding.TYPE: ITypeBinding typeBinding= (ITypeBinding) binding; if (typeBinding.getDeclaringClass() != null) { return typeBinding; } return typeBinding; } return null; } /** * Evaluates if the declaration is visible in a certain context. * @param binding The binding of the declaration to examine * @param context The context to test in * @return Returns */ public static boolean isVisible(IBinding binding, ITypeBinding context) { if (binding.getKind() == IBinding.VARIABLE && !((IVariableBinding) binding).isField()) { return true; // all local variables found are visible } ITypeBinding declaring= getDeclaringType(binding); if (declaring == null) { return false; } int modifiers= binding.getModifiers(); if (Modifier.isPublic(modifiers)) { return true; } else if (Modifier.isProtected(modifiers) || !Modifier.isPrivate(modifiers)) { if (declaring.getPackage() == context.getPackage()) { return true; } return isTypeInScope(declaring, context, Modifier.isProtected(modifiers)); } // private visibility return isTypeInScope(declaring, context, false); } private static boolean isTypeInScope(ITypeBinding declaring, ITypeBinding context, boolean includeHierarchy) { ITypeBinding curr= context; while (curr != null && curr != declaring) { if (includeHierarchy && Bindings.isSuperType(declaring, curr)) { return true; } curr= curr.getDeclaringClass(); } return curr == declaring; } public IBinding[] getDeclarationsAfter(int offset, int flags) { try { NodeFinder finder= new NodeFinder(offset, 0); fRoot.accept(finder); ASTNode node= finder.getCoveringNode(); if (node == null) { return NO_BINDING; } ASTNode declaration= ASTResolving.findParentStatement(node); while (declaration instanceof Statement && declaration.getNodeType() != ASTNode.BLOCK) { declaration= declaration.getParent(); } if (declaration instanceof Block) { DefaultBindingRequestor requestor= new DefaultBindingRequestor(); DeclarationsAfterVisitor visitor= new DeclarationsAfterVisitor(node.getStartPosition(), flags, requestor); declaration.accept(visitor); List result= requestor.getResult(); return (IBinding[])result.toArray(new IBinding[result.size()]); } return NO_BINDING; } finally { clearLists(); } } private class ScopeAnalyzerVisitor extends HierarchicalASTVisitor { private int fPosition; private int fFlags; private final IBindingRequestor fRequestor; private boolean fBreak; public ScopeAnalyzerVisitor(int position, int flags, IBindingRequestor requestor) { fPosition= position; fFlags= flags; fRequestor= requestor; fBreak= false; } private boolean isInside(ASTNode node) { int start= node.getStartPosition(); int end= start + node.getLength(); return start <= fPosition && fPosition < end; } public boolean visit(FunctionDeclaration node) { if (isInside(node)) { Block body= node.getBody(); if (body != null) { body.accept(this); } visitBackwards(node.parameters()); } return false; } public boolean visit(SwitchCase node) { // switch on enum allows to use enum constants without qualification if (hasFlag(VARIABLES, fFlags) && !node.isDefault() && isInside(node.getExpression())) { SwitchStatement switchStatement= (SwitchStatement) node.getParent(); switchStatement.getExpression().resolveTypeBinding(); } return false; } public boolean visit(Initializer node) { return !fBreak && isInside(node); } public boolean visit(Statement node) { return !fBreak && isInside(node); } public boolean visit(ASTNode node) { return false; } public boolean visit(Block node) { if (isInside(node)) { visitBackwards(node.statements()); } return false; } public boolean visit(VariableDeclaration node) { if (hasFlag(VARIABLES, fFlags) && node.getStartPosition() < fPosition) { fBreak= fRequestor.acceptBinding(node.resolveBinding()); } return !fBreak; } public boolean visit(VariableDeclarationStatement node) { visitBackwards(node.fragments()); return false; } public boolean visit(VariableDeclarationExpression node) { visitBackwards(node.fragments()); return false; } public boolean visit(CatchClause node) { if (isInside(node)) { node.getBody().accept(this); node.getException().accept(this); } return false; } public boolean visit(ForStatement node) { if (isInside(node)) { node.getBody().accept(this); visitBackwards(node.initializers()); } return false; } public boolean visit(ForInStatement node) { if (isInside(node)) { node.getBody().accept(this); node.getIterationVariable().accept(this); } return false; } public boolean visit(TypeDeclarationStatement node) { if (hasFlag(TYPES, fFlags) && node.getStartPosition() + node.getLength() < fPosition) { if (node.getAST().apiLevel() == AST.JLS2) { fBreak= fRequestor.acceptBinding(node.getTypeDeclaration().resolveBinding()); } else { fBreak= fRequestor.acceptBinding(node.getDeclaration().getName().resolveBinding()); } return false; } return !fBreak && isInside(node); } private void visitBackwards(List list) { if (fBreak) return; for (int i= list.size() - 1; i >= 0; i--) { ASTNode curr= (ASTNode) list.get(i); if (curr.getStartPosition() < fPosition) { curr.accept(this); } } } } private class DeclarationsAfterVisitor extends HierarchicalASTVisitor { private final int fPosition; private final int fFlags; private final IBindingRequestor fRequestor; private boolean fBreak; public DeclarationsAfterVisitor(int position, int flags, IBindingRequestor requestor) { fPosition= position; fFlags= flags; fRequestor= requestor; fBreak= false; } public boolean visit(ASTNode node) { return !fBreak; } public boolean visit(VariableDeclaration node) { if (hasFlag(VARIABLES, fFlags) && fPosition < node.getStartPosition()) { fBreak= fRequestor.acceptBinding(node.resolveBinding()); } return false; } public boolean visit(AnonymousClassDeclaration node) { return false; } public boolean visit(TypeDeclarationStatement node) { if (hasFlag(TYPES, fFlags) && fPosition < node.getStartPosition()) { fBreak= fRequestor.acceptBinding(node.resolveBinding()); } return false; } } private boolean addLocalDeclarations(ASTNode node, int flags, IBindingRequestor requestor) { return addLocalDeclarations(node, node.getStartPosition(), flags, requestor); } private boolean addLocalDeclarations(ASTNode node, int offset, int flags, IBindingRequestor requestor) { if (hasFlag(VARIABLES, flags) || hasFlag(TYPES, flags)) { BodyDeclaration declaration= ASTResolving.findParentBodyDeclaration(node); if (declaration instanceof FunctionDeclaration || declaration instanceof Initializer) { ScopeAnalyzerVisitor visitor= new ScopeAnalyzerVisitor(offset, flags, requestor); declaration.accept(visitor); return visitor.fBreak; } } return false; } public Collection getUsedVariableNames(int offset, int length) { HashSet result= new HashSet(); IBinding[] bindingsBefore= getDeclarationsInScope(offset, VARIABLES); for (int i= 0; i < bindingsBefore.length; i++) { result.add(bindingsBefore[i].getName()); } IBinding[] bindingsAfter= getDeclarationsAfter(offset + length, VARIABLES); for (int i= 0; i < bindingsAfter.length; i++) { result.add(bindingsAfter[i].getName()); } List imports= fRoot.imports(); for (int i= 0; i < imports.size(); i++) { ImportDeclaration decl= (ImportDeclaration) imports.get(i); if (decl.isStatic() && !decl.isOnDemand()) { result.add(ASTNodes.getSimpleNameIdentifier(decl.getName())); } } return result; } }