blob: d32217d717b34036c4642cb782660d6a0cad7021 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}