/*******************************************************************************
* 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 org2.eclipse.php.internal.core.search;
import java.util.List;
import java.util.Stack;
import org2.eclipse.php.internal.core.ast.nodes.ASTNode;
import org2.eclipse.php.internal.core.ast.nodes.ArrayAccess;
import org2.eclipse.php.internal.core.ast.nodes.Assignment;
import org2.eclipse.php.internal.core.ast.nodes.Block;
import org2.eclipse.php.internal.core.ast.nodes.ClassDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.ConstantDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.FieldAccess;
import org2.eclipse.php.internal.core.ast.nodes.FieldsDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.FunctionDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.FunctionInvocation;
import org2.eclipse.php.internal.core.ast.nodes.FunctionName;
import org2.eclipse.php.internal.core.ast.nodes.Identifier;
import org2.eclipse.php.internal.core.ast.nodes.InterfaceDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.MethodDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.MethodInvocation;
import org2.eclipse.php.internal.core.ast.nodes.NamespaceDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.Program;
import org2.eclipse.php.internal.core.ast.nodes.Statement;
import org2.eclipse.php.internal.core.ast.nodes.StaticConstantAccess;
import org2.eclipse.php.internal.core.ast.nodes.StaticFieldAccess;
import org2.eclipse.php.internal.core.ast.nodes.StaticMethodInvocation;
import org2.eclipse.php.internal.core.ast.nodes.TypeDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.Variable;
import com.aptana.editor.php.core.model.IModelElement;
import com.aptana.editor.php.core.model.ISourceModule;
import com.aptana.editor.php.core.model.IType;
import com.aptana.editor.php.core.typebinding.ITypeBinding;
/**
* Class members occurrences finder.
*
* @author shalom
*/
public class ClassMembersOccurrencesFinder extends AbstractOccurrencesFinder {
private static final String THIS = "this"; //$NON-NLS-1$
public static final String ID = "ClassMembersOccurrencesFinder"; //$NON-NLS-1$
private String classMemberName; // The member's name
private String typeDeclarationName; // Class or Interface name // TODO - use
// Binding
private boolean isMethod;
private IType dispatcherType; // might be null
private IType dispatcherNamespace; // might be null
private ASTNode erroneousNode;
/**
* @param root
* the AST root
* @param node
* the selected node (must be an {@link Identifier} instance)
* @return returns a message if there is a problem
*/
public String initialize(Program root, ASTNode node) {
fASTRoot = root;
fProblems = getProblems(root);
typeDeclarationName = null;
isMethod = false;
if (node.getType() == ASTNode.IDENTIFIER) {
Identifier identifier = (Identifier) node;
IType[] types = resolveDispatcherType(identifier);
if (types != null) {
dispatcherNamespace = types[0];
dispatcherType = types[1];
}
classMemberName = identifier.getName();
// IBinding binding = identifier.resolveBinding(); // FIXME - This
// should be implemented...
ASTNode parent = identifier.getParent();
int type = parent.getType();
isMethod = type == ASTNode.FUNCTION_DECLARATION
|| parent.getLocationInParent() == FunctionName.NAME_PROPERTY
|| parent.getLocationInParent() == FunctionInvocation.FUNCTION_PROPERTY;
while (typeDeclarationName == null && parent != fASTRoot) {
if (type == ASTNode.CLASS_DECLARATION
|| type == ASTNode.INTERFACE_DECLARATION) {
typeDeclarationName = ((TypeDeclaration) parent).getName()
.getName();
break;
}
parent = parent.getParent();
type = parent.getType();
}
if (hasProblems(node.getStart(), node.getEnd())) {
erroneousNode = node;
}
return null;
}
fDescription = "OccurrencesFinder_occurrence_description"; //$NON-NLS-1$
return fDescription;
}
/*
* Tries to resolve the type of the dispatcher.
*/
private IType[] resolveDispatcherType(Identifier identifier) {
IType[] types = new IType[2];
ITypeBinding typeBinding = null;
ASTNode parent = identifier.getParent();
if (THIS.equals(identifier.getName())) {
// [Aptana Mod - Support '$this']
return resolveDeclaringClassType(identifier.getParent());
} else if (parent.getType() == ASTNode.VARIABLE) {
Variable var = (Variable) parent;
ASTNode varParent = var.getParent();
if (varParent.getType() == ASTNode.ARRAY_ACCESS) {
varParent = varParent.getParent();
}
if (varParent.getType() == ASTNode.FIELD_ACCESS
&& ((FieldAccess) varParent).getDispatcher() != null) {
typeBinding = ((FieldAccess) varParent).getDispatcher()
.resolveTypeBinding();
} else if (varParent.getType() == ASTNode.STATIC_FIELD_ACCESS) {
typeBinding = ((StaticFieldAccess) varParent).getClassName()
.resolveTypeBinding();
} else if (varParent.getType() == ASTNode.FUNCTION_NAME) {
FunctionName fn = (FunctionName) varParent;
if (fn.getParent().getType() == ASTNode.FUNCTION_INVOCATION) {
FunctionInvocation fi = (FunctionInvocation) fn.getParent();
if (fi.getParent().getType() == ASTNode.METHOD_INVOCATION
&& ((MethodInvocation) fi.getParent())
.getDispatcher() != null) {
typeBinding = ((MethodInvocation) fi.getParent())
.getDispatcher().resolveTypeBinding();
}
}
} else if (varParent.getType() == ASTNode.SINGLE_FIELD_DECLARATION) {
return resolveDeclaringClassType(var.getParent());
}
} else if (parent.getType() == ASTNode.FUNCTION_NAME) {
FunctionName fn = (FunctionName) parent;
if (fn.getParent().getType() == ASTNode.FUNCTION_INVOCATION) {
FunctionInvocation fi = (FunctionInvocation) fn.getParent();
if (fi.getParent().getType() == ASTNode.STATIC_METHOD_INVOCATION) {
typeBinding = ((StaticMethodInvocation) fi.getParent())
.getClassName().resolveTypeBinding();
}
}
} else if (parent.getType() == ASTNode.STATIC_CONSTANT_ACCESS) {
StaticConstantAccess sca = (StaticConstantAccess) parent;
typeBinding = sca.getClassName().resolveTypeBinding();
} else if (parent.getType() == ASTNode.STATIC_FIELD_ACCESS) {
StaticFieldAccess sfa = (StaticFieldAccess) parent;
typeBinding = sfa.getClassName().resolveTypeBinding();
} else if (parent.getType() == ASTNode.METHOD_DECLARATION) {
MethodDeclaration md = (MethodDeclaration) parent;
return resolveDeclaringClassType(md);
} else if (parent.getType() == ASTNode.FUNCTION_DECLARATION) {
FunctionDeclaration fd = (FunctionDeclaration) parent;
return resolveDeclaringClassType(fd);
} else if (parent.getType() == ASTNode.CONSTANT_DECLARATION) {
ConstantDeclaration ccd = (ConstantDeclaration) parent;
return resolveDeclaringClassType(ccd);
}
if (typeBinding != null && typeBinding.isClass()
&& typeBinding.getPHPElement() != null) {
IModelElement element = typeBinding.getPHPElement().getParent();
if (element instanceof IType) {
types[0] = (IType) element;
}
types[1] = (IType) typeBinding.getPHPElement();
return types;
}
return null;
}
private boolean isDispatcherTypeEquals(Identifier identifier) {
IType[] types = resolveDispatcherType(identifier);
if (types != null) {
if (dispatcherNamespace == null) {
if (types[0] != null) {
return false;
} else {
return dispatcherType.equals(types[1]);
}
} else {
return dispatcherNamespace.equals(types[0])
&& dispatcherType.equals(types[1]);
}
}
return false;
}
/*
* Resolve the class declaration type for the given node. This method
* traverse upward to find a defining ClassDeclaration and then resolves its
* IType.
*/
protected IType[] resolveDeclaringClassType(ASTNode node) {
IType[] types = new IType[2];
ASTNode parent = node.getParent();
TypeDeclaration typeDeclaration = null;
NamespaceDeclaration namespaceDeclaration = null;
while (typeDeclaration == null && parent != null) {
if (parent.getType() == ASTNode.CLASS_DECLARATION
|| parent.getType() == ASTNode.INTERFACE_DECLARATION) {
typeDeclaration = (TypeDeclaration) parent;
}
parent = parent.getParent();
}
while (namespaceDeclaration == null && parent != null) {
if (parent.getType() == ASTNode.NAMESPACE) {
namespaceDeclaration = (NamespaceDeclaration) parent;
}
parent = parent.getParent();
}
if (typeDeclaration != null) {
if (namespaceDeclaration != null
&& namespaceDeclaration.getName() != null) {
final ISourceModule source = namespaceDeclaration
.getProgramRoot().getSourceModule();
types[0] = source != null ? source.getType(namespaceDeclaration
.getName().getName()) : null;
}
ITypeBinding typeBinding = typeDeclaration.resolveTypeBinding();
if (typeBinding != null) {
types[1] = (IType) typeBinding.getPHPElement();
}
return types;
}
return null;
}
/*
* (non-Javadoc)
*
* @see
* org2.eclipse.php.internal.ui.search.AbstractOccurrencesFinder#findOccurrences
* ()
*/
protected void findOccurrences() {
if (isMethod) {
fDescription = Messages.format(BASE_DESCRIPTION, classMemberName
+ BRACKETS);
} else {
fDescription = Messages.format(BASE_DESCRIPTION, classMemberName);
}
if (erroneousNode != null) {
// Add just this node in order to handle re-factoring properly
fResult.add(new OccurrenceLocation(erroneousNode.getStart(),
erroneousNode.getLength(),
getOccurrenceType(erroneousNode), fDescription));
} else {
fASTRoot.accept(this);
}
}
// Holds the last class name that we entered into.
private Stack<String> currentClass = new Stack<String>();
/**
* context + Mark var on: ... public $a; ...
*/
public boolean visit(ClassDeclaration classDeclaration) {
currentClass.push(classDeclaration.getName().getName());
checkTypeDeclaration(classDeclaration);
return false;
}
/*
* [Aptana Mod - Support '$this']
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#endVisit(org2.eclipse.php.internal.core.ast.nodes.ClassDeclaration)
*/
public void endVisit(ClassDeclaration classDeclaration) {
if (!currentClass.isEmpty()) {
currentClass.pop();
}
}
/**
* context
*/
public boolean visit(InterfaceDeclaration interfaceDeclaration) {
checkTypeDeclaration(interfaceDeclaration);
return false;
}
/* [Aptana Mod - Support '$this']
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.Identifier)
*/
@Override
public boolean visit(Identifier identifier)
{
if (THIS.equals(identifier.getName()) && classMemberName.equals(THIS)) {
// Check the the identifier is declared in the same type (class)
String wrappingClass = (currentClass.isEmpty()) ? null : currentClass.peek();
if (wrappingClass != null && wrappingClass.equals(typeDeclarationName)) {
addOccurrence(new OccurrenceLocation(identifier.getStart() - 1, identifier.getLength() + 1,
getOccurrenceType(identifier), fDescription));
}
}
return super.visit(identifier);
}
/**
* Mark foo() on: $a->foo();
*/
public boolean visit(MethodInvocation methodInvocation) {
if (isMethod) {
checkDispatch(methodInvocation.getMethod().getFunctionName()
.getName());
}
return super.visit(methodInvocation);
}
/**
* Mark var on: $a->var;
*/
public boolean visit(FieldAccess fieldAccess) {
if (!isMethod) {
checkDispatch(fieldAccess.getField().getName());
}
return super.visit(fieldAccess);
}
/**
* Mark CON on: MyClass::CON;
*/
public boolean visit(StaticConstantAccess classConstantAccess) {
Identifier constant = classConstantAccess.getConstant();
if (classMemberName.equals(constant.getName())) {
if (dispatcherType != null) {
if (isDispatcherTypeEquals(constant)) {
addOccurrence(new OccurrenceLocation(constant.getStart(),
constant.getLength(), getOccurrenceType(constant),
fDescription));
}
} else {
addOccurrence(new OccurrenceLocation(constant.getStart(),
constant.getLength(), getOccurrenceType(constant),
fDescription));
}
}
return true;
}
/**
* Mark foo() on: MyClass::foo();
*/
public boolean visit(StaticMethodInvocation methodInvocation) {
if (isMethod) {
checkDispatch(methodInvocation.getMethod().getFunctionName()
.getName());
}
return super.visit(methodInvocation);
}
/**
* Mark var on: MyClass::var;
*/
public boolean visit(StaticFieldAccess fieldAccess) {
if (!isMethod) {
checkDispatch(fieldAccess.getField().getName());
}
return super.visit(fieldAccess);
}
/**
* @param dispatch
* @throws RuntimeException
*/
private void checkDispatch(ASTNode node) {
while (node.getType() == ASTNode.ARRAY_ACCESS) {
node = ((ArrayAccess) node).getName();
}
if (node.getType() == ASTNode.IDENTIFIER) {
Identifier id = (Identifier) node;
if (id.getName().equalsIgnoreCase(classMemberName)) {
if (dispatcherType != null) {
if (isDispatcherTypeEquals(id)) {
if (id.getParent() instanceof Variable) {
addOccurrence(new OccurrenceLocation(id.getParent()
.getStart(), id.getParent().getLength(),
getOccurrenceType(node), fDescription));
} else {
addOccurrence(new OccurrenceLocation(node
.getStart(), node.getLength(),
getOccurrenceType(node), fDescription));
}
}
} else {
int start = node.getStart();
int length = node.getLength();
if (node.getParent().getType() == ASTNode.VARIABLE)
{
if (((Variable) node.getParent()).isDollared() && start - 1 >= 0)
{
start--;
length++;
}
}
addOccurrence(new OccurrenceLocation(start, length, getOccurrenceType(node), fDescription));
}
}
}
if (node.getType() == ASTNode.VARIABLE
/* && node.getParent().getType() != ASTNode.FUNCTION_NAME */) {
Variable id = (Variable) node;
checkDispatch(id.getName());
}
}
private void checkTypeDeclaration(TypeDeclaration typeDeclaration) {
assert typeDeclaration != null;
Block body = typeDeclaration.getBody();
// definitions of the class property
List<Statement> statements = body.statements();
for (Statement statement : statements) {
if (statement.getType() == ASTNode.METHOD_DECLARATION) {
final MethodDeclaration classMethodDeclaration = (MethodDeclaration) statement;
if (isMethod) {
final Identifier functionName = classMethodDeclaration
.getFunction().getFunctionName();
if (classMemberName
.equalsIgnoreCase(functionName.getName())) {
if (dispatcherType != null) {
if (isDispatcherTypeEquals(functionName)) {
addOccurrence(new OccurrenceLocation(
functionName.getStart(), functionName
.getLength(),
getOccurrenceType(functionName),
fDescription));
}
} else {
addOccurrence(new OccurrenceLocation(functionName
.getStart(), functionName.getLength(),
getOccurrenceType(functionName),
fDescription));
}
}
}
} else if (statement.getType() == ASTNode.FIELD_DECLARATION) {
if (!isMethod) {
FieldsDeclaration classVariableDeclaration = (FieldsDeclaration) statement;
final Variable[] variableNames = classVariableDeclaration
.getVariableNames();
for (int j = 0; j < variableNames.length; j++) {
// safe cast to identifier
assert variableNames[j].getName().getType() == ASTNode.IDENTIFIER;
final Identifier variable = (Identifier) variableNames[j]
.getName();
if (classMemberName.equals(variable.getName())) {
if (dispatcherType != null) {
if (isDispatcherTypeEquals(variable)) {
addOccurrence(new OccurrenceLocation(
variable.getStart() - 1, variable
.getLength() + 1,
F_WRITE_OCCURRENCE, fDescription));
}
} else {
addOccurrence(new OccurrenceLocation(variable
.getStart() - 1,
variable.getLength() + 1,
F_WRITE_OCCURRENCE, fDescription));
}
}
}
}
} else if (statement.getType() == ASTNode.CONSTANT_DECLARATION) {
ConstantDeclaration classVariableDeclaration = (ConstantDeclaration) statement;
List<Identifier> variableNames = classVariableDeclaration
.names();
for (Identifier name : variableNames) {
if (classMemberName.equals(name.getName())) {
if (dispatcherType != null) {
if (isDispatcherTypeEquals(name)) {
addOccurrence(new OccurrenceLocation(name
.getStart(), name.getLength(),
getOccurrenceType(name), fDescription));
}
} else {
addOccurrence(new OccurrenceLocation(name
.getStart(), name.getLength(),
getOccurrenceType(name), fDescription));
}
}
}
}
}
// }
body.accept(this);
}
/*
* (non-Javadoc)
*
* @see org2.eclipse.php.internal.ui.search.AbstractOccurrencesFinder#
* getOccurrenceReadWriteType
* (org2.eclipse.php.internal.core.ast.nodes.ASTNode)
*/
protected int getOccurrenceType(ASTNode node) {
// Default return is F_READ_OCCURRENCE, although the implementation of
// the Scalar visit might also use F_WRITE_OCCURRENCE
if (node.getParent().getType() == ASTNode.CONSTANT_DECLARATION
|| isInAssignment(node)) {
return IOccurrencesFinder.F_WRITE_OCCURRENCE;
}
return IOccurrencesFinder.F_READ_OCCURRENCE;
}
/**
* Check if the given node is a variable in a field access that exists in an
* assignment expression.
*
* @param node
* @return
*/
protected boolean isInAssignment(ASTNode node) {
if (node.getParent().getType() == ASTNode.VARIABLE) {
Variable var = (Variable) node.getParent();
if (var.getParent().getType() == ASTNode.FIELD_ACCESS) {
FieldAccess fAccess = (FieldAccess) var.getParent();
if (fAccess.getLocationInParent() == Assignment.LEFT_HAND_SIDE_PROPERTY) {
return true;
}
}
}
return false;
}
/*
* (non-Javadoc)
*
* @see
* org2.eclipse.php.internal.ui.search.IOccurrencesFinder#getElementName()
*/
public String getElementName() {
return classMemberName;
}
/*
* (non-Javadoc)
*
* @see org2.eclipse.php.internal.ui.search.IOccurrencesFinder#getID()
*/
public String getID() {
return ID;
}
}