/*******************************************************************************
* Copyright (c) 2009, 2016 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.internal.core.typeinference.evaluators;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.declarations.ModuleDeclaration;
import org.eclipse.dltk.ast.expressions.Expression;
import org.eclipse.dltk.ast.references.SimpleReference;
import org.eclipse.dltk.ast.references.TypeReference;
import org.eclipse.dltk.ast.references.VariableReference;
import org.eclipse.dltk.ast.statements.Statement;
import org.eclipse.dltk.core.*;
import org.eclipse.dltk.internal.core.SourceRefElement;
import org.eclipse.dltk.ti.GoalState;
import org.eclipse.dltk.ti.IContext;
import org.eclipse.dltk.ti.goals.ExpressionTypeGoal;
import org.eclipse.dltk.ti.goals.IGoal;
import org.eclipse.dltk.ti.types.IEvaluatedType;
import org.eclipse.php.core.PHPVersion;
import org.eclipse.php.core.compiler.ast.nodes.*;
import org.eclipse.php.core.project.ProjectOptions;
import org.eclipse.php.internal.core.typeinference.IModelAccessCache;
import org.eclipse.php.internal.core.typeinference.PHPModelUtils;
import org.eclipse.php.internal.core.typeinference.PHPTypeInferenceUtils;
import org.eclipse.php.internal.core.typeinference.context.ContextFinder;
import org.eclipse.php.internal.core.typeinference.context.IModelCacheContext;
import org.eclipse.php.internal.core.typeinference.context.MethodContext;
import org.eclipse.php.internal.core.typeinference.context.TypeContext;
import org.eclipse.php.internal.core.typeinference.goals.ClassVariableDeclarationGoal;
/**
* This evaluator finds class field declaration using method body and field
* access.
*
* @property-read, @property-write
*/
public class ClassVariableDeclarationEvaluator extends AbstractPHPGoalEvaluator {
private List<IEvaluatedType> evaluated = new LinkedList<IEvaluatedType>();
public ClassVariableDeclarationEvaluator(IGoal goal) {
super(goal);
}
public IGoal[] init() {
ClassVariableDeclarationGoal typedGoal = (ClassVariableDeclarationGoal) goal;
IType[] types = typedGoal.getTypes();
if (types == null) {
TypeContext context = (TypeContext) typedGoal.getContext();
types = PHPTypeInferenceUtils.getModelElements(context.getInstanceType(), context);
}
if (types == null) {
return null;
}
IContext context = typedGoal.getContext();
IModelAccessCache cache = null;
if (context instanceof IModelCacheContext) {
cache = ((IModelCacheContext) context).getCache();
}
String variableName = PHPEvaluationUtils.removeArrayBrackets(typedGoal.getVariableName());
final List<IGoal> subGoals = new LinkedList<IGoal>();
for (final IType type : types) {
try {
ITypeHierarchy hierarchy = null;
if (cache != null) {
hierarchy = cache.getSuperTypeHierarchy(type, null);
}
IField[] fields = PHPModelUtils.getTypeHierarchyField(type, hierarchy, variableName, true, null);
Map<IType, IType> fieldDeclaringTypeSet = new HashMap<IType, IType>();
for (IField field : fields) {
IType declaringType = field.getDeclaringType();
if (declaringType != null) {
fieldDeclaringTypeSet.put(declaringType, type);
if (field instanceof SourceRefElement) {
ISourceReference sourceRefElement = (ISourceReference) field;
ISourceRange sourceRange = sourceRefElement.getSourceRange();
ISourceModule sourceModule = declaringType.getSourceModule();
ModuleDeclaration moduleDeclaration = SourceParserUtil.getModuleDeclaration(sourceModule);
ClassDeclarationSearcher searcher = new ClassDeclarationSearcher(sourceModule,
declaringType.getSourceRange(), sourceRange.getOffset(), sourceRange.getLength(),
null, type, declaringType);
try {
moduleDeclaration.traverse(searcher);
if (searcher.getResult() != null) {
subGoals.add(new ExpressionTypeGoal(searcher.getContext(), searcher.getResult()));
}
} catch (Exception e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
}
}
}
addGoalFromStaticDeclaration(variableName, subGoals, type, null);
fieldDeclaringTypeSet.remove(type);
for (Entry<IType, IType> entry : fieldDeclaringTypeSet.entrySet()) {
addGoalFromStaticDeclaration(variableName, subGoals, entry.getKey(), entry.getValue());
}
} catch (CoreException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
}
return subGoals.toArray(new IGoal[subGoals.size()]);
}
protected void addGoalFromStaticDeclaration(String variableName, final List<IGoal> subGoals,
final IType declaringType, IType realType) throws ModelException {
ISourceModule sourceModule = declaringType.getSourceModule();
ModuleDeclaration moduleDeclaration = SourceParserUtil.getModuleDeclaration(sourceModule);
// try to search declarations of type "self::$var =" or "$this->var ="
ClassDeclarationSearcher searcher;
if (realType != null) {
searcher = new ClassDeclarationSearcher(sourceModule, declaringType.getSourceRange(), 0, 0, variableName,
realType, declaringType);
} else {
searcher = new ClassDeclarationSearcher(sourceModule, declaringType.getSourceRange(), 0, 0, variableName);
}
try {
moduleDeclaration.traverse(searcher);
for (Entry<ASTNode, IContext> entry : searcher.getStaticDeclarations().entrySet()) {
final IContext context = entry.getValue();
if (context instanceof MethodContext) {
MethodContext methodContext = (MethodContext) context;
methodContext.setCurrentType(realType);
}
if (context instanceof IModelCacheContext
&& ClassVariableDeclarationEvaluator.this.goal.getContext() instanceof IModelCacheContext) {
((IModelCacheContext) context).setCache(
((IModelCacheContext) ClassVariableDeclarationEvaluator.this.goal.getContext()).getCache());
}
subGoals.add(new ExpressionTypeGoal(context, entry.getKey()));
}
} catch (Exception e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
}
public Object produceResult() {
return PHPTypeInferenceUtils.combineTypes(evaluated);
}
public IGoal[] subGoalDone(IGoal subgoal, Object result, GoalState state) {
if (state != GoalState.RECURSIVE && result != null) {
evaluated.add((IEvaluatedType) result);
}
return IGoal.NO_GOALS;
}
/**
* Searches for all class variable declarations using offset and length
* which is hold by model element
*
* @author michael
*/
class ClassDeclarationSearcher extends ContextFinder {
private static final String NULL = "null"; //$NON-NLS-1$
private ISourceRange typeDeclarationRange;
private ASTNode result;
private IContext context;
private int offset;
private int length;
private String variableName;
private ISourceModule sourceModule;
private Map<ASTNode, IContext> staticDeclarations = new HashMap<ASTNode, IContext>();
public ClassDeclarationSearcher(ISourceModule sourceModule, ISourceRange typeDeclarationRange, int offset,
int length, String variableName) {
super(sourceModule);
this.typeDeclarationRange = typeDeclarationRange;
this.offset = offset;
this.length = length;
this.sourceModule = sourceModule;
this.variableName = variableName;
}
public ClassDeclarationSearcher(ISourceModule sourceModule, ISourceRange typeDeclarationRange, int offset,
int length, String variableName, IType realType, IType declaringType) {
super(sourceModule, realType, declaringType);
this.typeDeclarationRange = typeDeclarationRange;
this.offset = offset;
this.length = length;
this.sourceModule = sourceModule;
this.variableName = variableName;
}
public ASTNode getResult() {
return result;
}
public Map<ASTNode, IContext> getStaticDeclarations() {
return staticDeclarations;
}
public IContext getContext() {
if (context instanceof IModelCacheContext
&& ClassVariableDeclarationEvaluator.this.goal.getContext() instanceof IModelCacheContext) {
((IModelCacheContext) context).setCache(
((IModelCacheContext) ClassVariableDeclarationEvaluator.this.goal.getContext()).getCache());
}
return context;
}
public boolean visit(Statement e) throws Exception {
if (typeDeclarationRange.getOffset() < e.sourceStart()
&& (typeDeclarationRange.getOffset() + typeDeclarationRange.getLength()) > e.sourceEnd()) {
if (e instanceof PHPFieldDeclaration) {
PHPFieldDeclaration phpFieldDecl = (PHPFieldDeclaration) e;
if (phpFieldDecl.getDeclarationStart() == offset
&& phpFieldDecl.sourceEnd() - phpFieldDecl.getDeclarationStart() == length) {
result = ((PHPFieldDeclaration) e).getVariableValue();
if (result instanceof Scalar) {
Scalar scalar = (Scalar) result;
if (scalar.getScalarType() == Scalar.TYPE_STRING
&& scalar.getValue().equalsIgnoreCase(NULL)) {
result = null;
}
}
context = contextStack.peek();
}
}
}
return visitGeneral(e);
}
public boolean visit(Expression e) throws Exception {
if (typeDeclarationRange.getOffset() < e.sourceStart()
&& (typeDeclarationRange.getOffset() + typeDeclarationRange.getLength()) > e.sourceEnd()) {
if (e instanceof Assignment) {
if (e.sourceStart() == offset && e.sourceEnd() - e.sourceStart() == length) {
result = ((Assignment) e).getValue();
context = contextStack.peek();
} else if (variableName != null) {
Assignment assignment = (Assignment) e;
Expression left = assignment.getVariable();
Expression right = assignment.getValue();
if (left instanceof StaticFieldAccess) {
StaticFieldAccess fieldAccess = (StaticFieldAccess) left;
Expression dispatcher = fieldAccess.getDispatcher();
if (isSelf(dispatcher)) {
Expression field = fieldAccess.getField();
if (field instanceof VariableReference
&& variableName.equals(((VariableReference) field).getName())) {
staticDeclarations.put(right, contextStack.peek());
}
}
} else if (left instanceof FieldAccess) {
FieldAccess fieldAccess = (FieldAccess) left;
Expression dispatcher = fieldAccess.getDispatcher();
if (dispatcher instanceof VariableReference
&& "$this".equals(((VariableReference) dispatcher).getName())) { //$NON-NLS-1$
Expression field = fieldAccess.getField();
if (field instanceof SimpleReference
&& variableName.equals('$' + ((SimpleReference) field).getName())) {
staticDeclarations.put(right, contextStack.peek());
}
}
}
}
}
}
return visitGeneral(e);
}
public boolean visitGeneral(ASTNode e) throws Exception {
return e.sourceStart() <= offset || variableName != null;
}
private boolean isSelf(Expression dispatcher) {
if (!(dispatcher instanceof TypeReference)) {
return false;
}
if ("self".equals(((TypeReference) dispatcher).getName())) { //$NON-NLS-1$
return true;
} else if (PHPVersion.PHP5_4.isLessThan(ProjectOptions.getPHPVersion(sourceModule))
&& "self".equalsIgnoreCase(((TypeReference) dispatcher).getName())) { //$NON-NLS-1$
return true;
}
return false;
}
}
}