blob: 7ac62d01c3407131db0745d9e6fffa9912efefa7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.php.editor.codegen;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.modules.csl.api.Modifier;
import org.netbeans.modules.csl.spi.ParserResult;
import org.netbeans.modules.php.api.util.StringUtils;
import org.netbeans.modules.php.editor.CodeUtils;
import org.netbeans.modules.php.editor.api.ElementQuery.Index;
import org.netbeans.modules.php.editor.api.ElementQueryFactory;
import org.netbeans.modules.php.editor.api.NameKind;
import org.netbeans.modules.php.editor.api.elements.ClassElement;
import org.netbeans.modules.php.editor.api.elements.ConstantElement;
import org.netbeans.modules.php.editor.api.elements.FieldElement;
import org.netbeans.modules.php.editor.api.elements.MethodElement;
import org.netbeans.modules.php.editor.api.elements.PhpElement;
import org.netbeans.modules.php.editor.api.elements.TypeConstantElement;
import org.netbeans.modules.php.editor.NavUtils;
import org.netbeans.modules.php.editor.codegen.SemiAttribute.AttributedElement.Kind;
import org.netbeans.modules.php.editor.parser.api.Utils;
import org.netbeans.modules.php.editor.parser.astnodes.ASTNode;
import org.netbeans.modules.php.editor.parser.astnodes.ArrayAccess;
import org.netbeans.modules.php.editor.parser.astnodes.Assignment;
import org.netbeans.modules.php.editor.parser.astnodes.BodyDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.CatchClause;
import org.netbeans.modules.php.editor.parser.astnodes.ClassDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.ClassInstanceCreation;
import org.netbeans.modules.php.editor.parser.astnodes.ClassName;
import org.netbeans.modules.php.editor.parser.astnodes.ConstantDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.Dispatch;
import org.netbeans.modules.php.editor.parser.astnodes.Expression;
import org.netbeans.modules.php.editor.parser.astnodes.FieldAccess;
import org.netbeans.modules.php.editor.parser.astnodes.FieldsDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.FormalParameter;
import org.netbeans.modules.php.editor.parser.astnodes.FunctionDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.FunctionInvocation;
import org.netbeans.modules.php.editor.parser.astnodes.GlobalStatement;
import org.netbeans.modules.php.editor.parser.astnodes.Identifier;
import org.netbeans.modules.php.editor.parser.astnodes.InstanceOfExpression;
import org.netbeans.modules.php.editor.parser.astnodes.InterfaceDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.MethodDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.MethodInvocation;
import org.netbeans.modules.php.editor.parser.astnodes.Program;
import org.netbeans.modules.php.editor.parser.astnodes.Reference;
import org.netbeans.modules.php.editor.parser.astnodes.Scalar;
import org.netbeans.modules.php.editor.parser.astnodes.Scalar.Type;
import org.netbeans.modules.php.editor.parser.astnodes.SingleFieldDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.StaticConstantAccess;
import org.netbeans.modules.php.editor.parser.astnodes.StaticFieldAccess;
import org.netbeans.modules.php.editor.parser.astnodes.StaticMethodInvocation;
import org.netbeans.modules.php.editor.parser.astnodes.Variable;
import org.netbeans.modules.php.editor.parser.astnodes.VariableBase;
import org.netbeans.modules.php.editor.parser.astnodes.Variadic;
import org.netbeans.modules.php.editor.parser.astnodes.visitors.DefaultVisitor;
import org.openide.filesystems.FileObject;
import org.openide.util.Union2;
/**
*
* @author Jan Lahoda, Radek Matous
*/
public class SemiAttribute extends DefaultVisitor {
private static final List<String> SUPERGLOBALS = Arrays.asList(
"GLOBALS", "_SERVER", "_GET", "_POST", "_FILES", //NOI18N
"_COOKIE", "_SESSION", "_REQUEST", "_ENV"); //NOI18N
public DefinitionScope global;
private ArrayDeque<DefinitionScope> scopes = new ArrayDeque<>();
private Map<ASTNode, AttributedElement> node2Element = new HashMap<>();
private int offset;
private ParserResult info;
private ArrayDeque<ASTNode> nodes = new ArrayDeque<>();
public SemiAttribute(ParserResult info) {
this(info, -1);
}
public SemiAttribute(ParserResult info, int o) {
this.offset = o;
this.info = info;
global = new DefinitionScope();
scopes.push(global);
}
@Override
public void scan(ASTNode node) {
if (node == null) {
return;
}
if ((offset != (-1) && offset <= node.getStartOffset())) {
throw new Stop();
}
nodes.push(node);
super.scan(node);
nodes.pop();
if ((offset != (-1) && offset <= node.getEndOffset())) {
throw new Stop();
}
}
@Override
public void visit(Program program) {
//functions defined on top-level of the current file are visible before declared:
performEnterPass(global, program.getStatements());
//enterAllIndexedClasses();
super.visit(program);
}
@Override
public void visit(Assignment node) {
final VariableBase vb = node.getLeftHandSide();
if (vb instanceof Variable) {
AttributedType at = null;
Expression rightSideExpression = node.getRightHandSide();
if (rightSideExpression instanceof Reference) {
rightSideExpression = ((Reference) rightSideExpression).getExpression();
}
if (rightSideExpression instanceof ClassInstanceCreation) {
ClassInstanceCreation classInstanceCreation = (ClassInstanceCreation) rightSideExpression;
Expression className = classInstanceCreation.getClassName().getName();
if (className instanceof Identifier) {
Identifier identifier = (Identifier) className;
ClassElementAttribute ce = (ClassElementAttribute) lookup(identifier.getName(), Kind.CLASS);
if (ce != null) {
at = new ClassType(ce);
}
}
} else if (rightSideExpression instanceof FieldAccess) {
FieldAccess access = (FieldAccess) rightSideExpression;
Variable field = access.getField();
String name = extractVariableName(field);
if (name != null) {
node2Element.put(vb, scopes.peek().enterWrite(name, Kind.VARIABLE, access, at));
}
}
String name = extractVariableName((Variable) vb);
if (name != null) {
node2Element.put(vb, scopes.peek().enterWrite(name, Kind.VARIABLE, vb, at));
}
}
super.visit(node);
}
@Override
public void visit(FunctionDeclaration node) {
String name = node.getFunctionName().getName();
FunctionElementAttribute fc = (FunctionElementAttribute) global.enterWrite(name, Kind.FUNC, node);
DefinitionScope top = scopes.peek();
if (!node2Element.containsKey(node)) {
assert !top.classScope;
node2Element.put(node, fc);
}
scopes.push(fc.enclosedElements);
if (top.classScope) {
assert top.thisVar != null;
scopes.peek().enter(top.thisVar.name, top.thisVar.getKind(), top.thisVar);
}
super.visit(node);
scopes.pop();
}
@Override
public void visit(InstanceOfExpression node) {
ClassName className = node.getClassName();
if (className != null) {
Expression expr = className.getName();
String name = (expr instanceof Identifier) ? ((Identifier) expr).getName() : null;
if (name != null) {
Collection<AttributedElement> namedGlobalElements = getNamedGlobalElements(Kind.CLASS, name);
if (!namedGlobalElements.isEmpty()) {
node2Element.put(expr, lookup(name, Kind.CLASS));
} else {
node2Element.put(expr, lookup(name, Kind.IFACE));
}
}
}
Expression expression = node.getExpression();
if (expression instanceof Variable) {
Variable var = (Variable) expression;
final String name = extractVariableName(var);
if (name != null) {
node2Element.put(var,
scopes.peek().enterWrite(name, Kind.VARIABLE, var));
}
}
super.visit(node);
}
@Override
public void visit(CatchClause node) {
for (Expression clsName : node.getClassNames()) {
Identifier className = (clsName != null) ? CodeUtils.extractUnqualifiedIdentifier(clsName) : null;
AttributedElement ae;
if (className != null) {
String name = className.getName();
Collection<AttributedElement> namedGlobalElements
= getNamedGlobalElements(Kind.CLASS, name);
if (!namedGlobalElements.isEmpty()) {
ae = lookup(name, Kind.CLASS);
node2Element.put(className, ae);
} else {
ae = lookup(name, Kind.IFACE);
node2Element.put(className, ae);
}
}
}
Variable var = node.getVariable();
final String name = extractVariableName(var);
if (var != null && name != null) {
node2Element.put(var,
scopes.peek().enterWrite(name, Kind.VARIABLE, var));
}
super.visit(node);
}
@Override
public void visit(FormalParameter node) {
Variable var = null;
if (node.getParameterName() instanceof Reference) {
Reference ref = (Reference) node.getParameterName();
Expression parameterName = ref.getExpression();
if (parameterName instanceof Variadic) {
parameterName = ((Variadic) parameterName).getExpression();
}
if (parameterName instanceof Variable) {
var = (Variable) parameterName;
}
} else if (node.getParameterName() instanceof Variable) {
var = (Variable) node.getParameterName();
}
if (var != null) {
String name = extractVariableName(var);
if (name != null) {
scopes.peek().enterWrite(name, Kind.VARIABLE, var);
}
}
Identifier parameterType = (node.getParameterType() != null) ? CodeUtils.extractUnqualifiedIdentifier(node.getParameterType()) : null;
if (parameterType != null) {
String name = parameterType.getName();
if (name != null) {
Collection<AttributedElement> namedGlobalElements = getNamedGlobalElements(Kind.CLASS, name);
if (!namedGlobalElements.isEmpty()) {
node2Element.put(parameterType, lookup(name, Kind.CLASS));
} else {
node2Element.put(parameterType, lookup(name, Kind.IFACE));
}
}
}
super.visit(node);
}
@Override
public void visit(Variable node) {
if (!node2Element.containsKey(node)) {
String name = extractVariableName(node);
if (name != null) {
node2Element.put(node, lookup(name, Kind.VARIABLE));
}
}
super.visit(node);
}
@Override
public void visit(FunctionInvocation node) {
Expression exp = node.getFunctionName().getName();
String name = null;
if (exp instanceof Identifier) {
name = ((Identifier) exp).getName();
}
if (exp instanceof Variable) {
Expression n = ((Variable) exp).getName();
if (n instanceof Identifier) {
name = ((Identifier) n).getName();
}
}
if (name != null) {
AttributedElement thisEl = null;
ASTNode n = nodes.pop();
ASTNode par = nodes.peek();
nodes.push(n);
if (par instanceof MethodInvocation) {
ClassElementAttribute ce = resolveTypeSimple((Dispatch) par);
if (ce != null) {
thisEl = ce.lookup(name, Kind.FUNC);
}
} else {
if (par instanceof StaticMethodInvocation) {
StaticMethodInvocation smi = (StaticMethodInvocation) par;
final String clsName = CodeUtils.extractUnqualifiedClassName(smi);
Collection<AttributedElement> nn = getNamedGlobalElements(Kind.CLASS, clsName);
if (!nn.isEmpty()) {
String contextClassName = clsName;
switch (clsName) {
case "parent": //NOI18N
contextClassName = getContextSuperClassName();
break;
case "self": //NOI18N
contextClassName = getContextClassName();
break;
default:
// no-op
}
for (AttributedElement ell : nn) {
ClassElementAttribute ce = (ClassElementAttribute) ell;
if (ce != null && (contextClassName == null || contextClassName.equals(ce.getName()))) {
thisEl = ce.lookup(name, Kind.FUNC);
if (thisEl != null) {
node2Element.put(smi.getDispatcher(), ce);
node2Element.put(smi, thisEl);
node2Element.put(smi.getMethod(), thisEl);
break;
}
}
}
}
} else {
thisEl = lookup(name, Kind.FUNC);
}
}
node2Element.put(node, thisEl);
if ("define".equals(name) && node.getParameters().size() == 2) {
Expression d = node.getParameters().get(0);
if (d instanceof Scalar && ((Scalar) d).getScalarType() == Type.STRING) {
String value = ((Scalar) d).getStringValue();
if (NavUtils.isQuoted(value)) {
node2Element.put(d, global.enterWrite(NavUtils.dequote(value), Kind.CONST, d));
}
}
}
}
if (node2Element.containsKey(node)) {
//super.visit(node);
scan(node.getParameters());
} else {
super.visit(node);
}
}
@Override
public void visit(InterfaceDeclaration node) {
String name = node.getName().getName();
ClassElementAttribute ce = (ClassElementAttribute) global.enterWrite(name, Kind.IFACE, node);
node2Element.put(node, ce);
List<Expression> interfaes = node.getInterfaes();
for (Expression identifier : interfaes) {
ClassElementAttribute iface = (ClassElementAttribute) lookup(CodeUtils.extractUnqualifiedName(identifier), Kind.IFACE);
ce.ifaces.add(iface);
node2Element.put(identifier, iface);
}
scopes.push(ce.enclosedElements);
if (node.getBody() != null) {
performEnterPass(ce.enclosedElements, node.getBody().getStatements());
}
super.visit(node);
scopes.pop();
}
@Override
public void visit(ClassDeclaration node) {
String name = node.getName().getName();
ClassElementAttribute ce = (ClassElementAttribute) global.enterWrite(name, Kind.CLASS, node);
node2Element.put(node, ce);
Identifier superClsName = (node.getSuperClass() != null) ? CodeUtils.extractUnqualifiedIdentifier(node.getSuperClass()) : null;
if (superClsName != null) {
ce.superClass = (ClassElementAttribute) lookup(superClsName.getName(), Kind.CLASS);
}
List<Expression> interfaes = node.getInterfaes();
for (Expression identifier : interfaes) {
ClassElementAttribute iface = (ClassElementAttribute) lookup(CodeUtils.extractUnqualifiedName(identifier), Kind.IFACE);
ce.ifaces.add(iface);
node2Element.put(identifier, iface);
}
scopes.push(ce.enclosedElements);
if (node.getBody() != null) {
performEnterPass(ce.enclosedElements, node.getBody().getStatements());
}
super.visit(node);
scopes.pop();
}
@Override
public void visit(ClassInstanceCreation node) {
Expression name = node.getClassName().getName();
if (name instanceof Identifier) {
node2Element.put(node, lookup(((Identifier) name).getName(), Kind.CLASS));
}
super.visit(node);
}
@Override
public void visit(GlobalStatement node) {
for (Variable v : node.getVariables()) {
String name = extractVariableName(v);
if (name != null) {
enterGlobalVariable(name);
}
}
super.visit(node);
}
@Override
public void visit(Scalar scalar) {
if (scalar.getScalarType() == Type.STRING && !NavUtils.isQuoted(scalar.getStringValue())) {
AttributedElement def = global.lookup(scalar.getStringValue(), Kind.CONST);
node2Element.put(scalar, def);
}
super.visit(scalar);
}
@Override
public void visit(FieldAccess node) {
scan(node.getDispatcher());
ClassElementAttribute ce = resolveTypeSimple(node);
String name = extractVariableName(node.getField());
if (ce != null && name != null) {
AttributedElement thisEl = ce.lookup(name, Kind.VARIABLE);
node2Element.put(node, thisEl);
Variable field = node.getField();
node2Element.put(field, thisEl);
if (field instanceof ArrayAccess) {
Expression exprName = field.getName();
if (exprName instanceof Variable) {
node2Element.put(exprName, thisEl);
}
super.visit(node);
}
} else {
scan(node.getField());
}
}
private ClassElementAttribute getCurrentClassElement() {
ClassElementAttribute c = null;
Iterator<DefinitionScope> elements = scopes.descendingIterator();
while (elements.hasNext()) {
DefinitionScope scope = elements.next();
if (scope != null
&& scope.enclosingClass != null) {
c = scope.enclosingClass;
break;
}
}
return c;
}
@Override
public void visit(StaticConstantAccess node) {
String clsName = CodeUtils.extractUnqualifiedClassName(node);
ClassElementAttribute c = getCurrentClassElement();
switch (clsName) {
case "self": //NOI18N
if (c != null) {
clsName = c.getName();
}
break;
case "parent": //NOI18N
if (c != null) {
c = c.getSuperClass();
if (c != null) {
clsName = c.getName();
}
}
break;
default:
//no-op
}
Collection<AttributedElement> nn = getNamedGlobalElements(Kind.CLASS, clsName); //NOI18N
if (!nn.isEmpty()) {
for (AttributedElement ell : nn) {
ClassElementAttribute ce = (ClassElementAttribute) ell;
if (ce != null && ce.getName().equals(clsName)) {
String name = CodeUtils.extractUnqualifiedClassName(node);
AttributedElement thisEl = ce.lookup(name, Kind.CONST);
node2Element.put(node.getDispatcher(), ce);
node2Element.put(node, thisEl);
node2Element.put(node.getConstant(), thisEl);
break;
}
}
}
super.visit(node);
}
@Override
public void visit(StaticFieldAccess node) {
Collection<AttributedElement> nn = getNamedGlobalElements(Kind.CLASS,
CodeUtils.extractUnqualifiedClassName(node));
if (!nn.isEmpty()) {
String contextClassName = CodeUtils.extractUnqualifiedClassName(node);
switch (contextClassName) {
case "parent": //NOI18N
contextClassName = getContextSuperClassName();
break;
case "self": //NOI18N
contextClassName = getContextClassName();
break;
default:
// no-op
}
for (AttributedElement ell : nn) {
ClassElementAttribute ce = (ClassElementAttribute) ell;
if (ce != null && (contextClassName == null || contextClassName.equals(ce.getName()))) {
String name = extractVariableName(node.getField());
if (name != null) {
AttributedElement thisEl = ce.lookup(name, Kind.VARIABLE);
if (thisEl != null) {
Variable field = node.getField();
node2Element.put(node.getDispatcher(), ce);
node2Element.put(node, thisEl);
node2Element.put(field, thisEl);
if (field instanceof ArrayAccess) {
Expression expr = field.getName();
if (expr instanceof Variable) {
node2Element.put(expr, thisEl);
}
}
break;
}
}
}
}
}
super.visit(node);
}
private AttributedElement enterGlobalVariable(String name) {
AttributedElement g = global.lookup(name, Kind.VARIABLE);
if (g == null) {
//XXX: untested:
g = global.enterWrite(name, Kind.VARIABLE, (ASTNode) null);
}
scopes.peek().enter(name, Kind.VARIABLE, g);
return g;
}
@Override
public void visit(ArrayAccess node) {
if (node.getName() instanceof Variable && node.getDimension().getIndex() instanceof Scalar) {
String variableName = extractVariableName((Variable) node.getName());
if (variableName != null && "GLOBALS".equals(variableName)) {
Scalar v = (Scalar) node.getDimension().getIndex();
if (v.getScalarType() == Type.STRING) {
String value = v.getStringValue();
if (NavUtils.isQuoted(value)) {
node2Element.put(v, enterGlobalVariable(NavUtils.dequote(value)));
}
}
}
}
super.visit(node);
}
private String getContextClassName() {
String contextClassName = null;
Iterator<DefinitionScope> elements = scopes.descendingIterator();
while (elements.hasNext()) {
DefinitionScope nextElement = elements.next();
if (nextElement.enclosingClass != null) {
contextClassName = nextElement.enclosingClass.getName();
}
}
return contextClassName;
}
private String getContextSuperClassName() {
String contextClassName = null;
Iterator<DefinitionScope> elements = scopes.descendingIterator();
while (elements.hasNext()) {
DefinitionScope nextElement = elements.next();
if (nextElement.enclosingClass != null && nextElement.enclosingClass.superClass != null) {
contextClassName = nextElement.enclosingClass.superClass.getName();
}
}
return contextClassName;
}
private ParserResult getInfo() {
return info;
}
private AttributedElement lookup(String name, Kind k) {
DefinitionScope ds = scopes.peek();
AttributedElement e;
switch (k) {
case FUNC:
case IFACE:
case CLASS:
e = global.lookup(name, k);
break;
default:
e = ds.lookup(name, k);
break;
}
if (e != null) {
return e;
}
switch (k) {
case FUNC:
case IFACE:
case CLASS:
return global.enterWrite(name, k, (ASTNode) null);
default:
return ds.enterWrite(name, k, (ASTNode) null);
}
}
public Collection<AttributedElement> getGlobalElements(Kind k) {
return global.getElements(k);
}
public Collection<AttributedElement> getNamedGlobalElements(Kind k, String fName) {
final List<AttributedElement> retval = new ArrayList<>();
final Map<String, AttributedElement> name2El = global.name2Writes.get(k);
if (StringUtils.hasText(fName)) {
if (fName.equals("self")) { //NOI18N
String ctxName = getContextClassName();
if (ctxName != null) {
fName = ctxName;
}
}
if (Kind.CLASS.equals(k) && fName.equals("parent")) { //NOI18N
if (name2El != null) {
Collection<AttributedElement> values = name2El.values();
for (AttributedElement ael : values) {
if (ael instanceof ClassElementAttribute) {
ClassElementAttribute ce = (ClassElementAttribute) ael;
ClassElementAttribute superClass = ce.getSuperClass();
if (superClass != null) {
retval.add(superClass);
}
}
}
}
} else {
AttributedElement el = (name2El != null) ? name2El.get(fName) : null;
if (el != null) {
retval.add(el);
} else {
Index index = ElementQueryFactory.getIndexQuery(info);
for (ClassElement m : index.getClasses(NameKind.prefix(fName))) {
String idxName = m.getName();
el = global.enterWrite(idxName, Kind.CLASS, m);
if (el != null) {
retval.add(el);
}
}
}
}
}
return retval;
}
public AttributedElement getElement(ASTNode n) {
return node2Element.get(n);
}
private Collection<PhpElement> name2ElementCache;
public void enterAllIndexedClasses() {
if (name2ElementCache == null) {
Index index = ElementQueryFactory.getIndexQuery(info);
name2ElementCache = new LinkedList<>();
name2ElementCache.addAll(index.getClasses(NameKind.empty()));
}
for (PhpElement f : name2ElementCache) {
if (f instanceof ClassElement) {
global.enterWrite(f.getName(), Kind.CLASS, f);
}
}
}
private void performEnterPass(DefinitionScope scope, Collection<? extends ASTNode> nodes) {
for (ASTNode n : nodes) {
if (n instanceof MethodDeclaration) {
FunctionDeclaration nn = ((MethodDeclaration) n).getFunction();
String name = nn.getFunctionName().getName();
node2Element.put(n, scope.enterWrite(name, Kind.FUNC, n));
node2Element.put(nn, scope.enterWrite(name, Kind.FUNC, n));
continue;
}
if (n instanceof FunctionDeclaration) {
String name = ((FunctionDeclaration) n).getFunctionName().getName();
node2Element.put(n, scope.enterWrite(name, Kind.FUNC, n));
}
if (n instanceof FieldsDeclaration) {
for (SingleFieldDeclaration f : ((FieldsDeclaration) n).getFields()) {
String name = extractVariableName(f.getName());
if (name != null) {
node2Element.put(n, scope.enterWrite(name, Kind.VARIABLE, n));
}
}
}
if (n instanceof ClassDeclaration) {
ClassDeclaration node = (ClassDeclaration) n;
String name = node.getName().getName();
ClassElementAttribute ce = (ClassElementAttribute) global.enterWrite(name, Kind.CLASS, node);
node2Element.put(node, ce);
Identifier superClsName = (node.getSuperClass() != null) ? CodeUtils.extractUnqualifiedIdentifier(node.getSuperClass()) : null;
if (superClsName != null) {
ce.superClass = (ClassElementAttribute) lookup(superClsName.getName(), Kind.CLASS);
node2Element.put(node.getSuperClass(), ce.superClass);
}
List<Expression> interfaces = node.getInterfaes();
for (Expression identifier : interfaces) {
//TODO: ifaces must be fixed;
}
if (node.getBody() != null) {
performEnterPass(ce.enclosedElements, node.getBody().getStatements());
}
}
if (n instanceof ConstantDeclaration) {
List<Identifier> constNames = ((ConstantDeclaration) n).getNames();
for (Identifier id : constNames) {
node2Element.put(n, scope.enterWrite(id.getName(), Kind.CONST, n));
}
}
}
}
private static Map<ParserResult, SemiAttribute> info2Attr = new WeakHashMap<>();
public static SemiAttribute semiAttribute(ParserResult info) {
SemiAttribute a = info2Attr.get(info);
if (a == null) {
long startTime = System.currentTimeMillis();
a = new SemiAttribute(info);
a.scan(Utils.getRoot(info));
a.info = null;
info2Attr.put(info, a);
long endTime = System.currentTimeMillis();
FileObject fo = info.getSnapshot().getSource().getFileObject();
Logger.getLogger("TIMER").log(Level.FINE, "SemiAttribute global instance", new Object[]{fo, a});
Logger.getLogger("TIMER").log(Level.FINE, "SemiAttribute global time", new Object[]{fo, (endTime - startTime)});
}
return a;
}
public static SemiAttribute semiAttribute(ParserResult info, int stopOffset) {
SemiAttribute a = new SemiAttribute(info, stopOffset);
try {
a.scan(Utils.getRoot(info));
} catch (Stop s) {
}
return a;
}
private static String name(ASTNode n) {
if (n instanceof Identifier) {
return ((Identifier) n).getName();
}
return null;
}
@CheckForNull
//TODO converge this method with CodeUtils.extractVariableName()
public static String extractVariableName(Variable var) {
String varName = CodeUtils.extractVariableName(var);
if (varName != null && varName.startsWith("$")) { //NOI18N
return varName.substring(1);
}
return varName;
}
private ClassElementAttribute resolveTypeSimple(Dispatch node) {
ClassElementAttribute ce = null;
AttributedElement el = node2Element.get(node.getDispatcher());
if (el != null) {
AttributedType type = el.writesTypes.get(el.getWrites().size() - 1);
if (type instanceof ClassType) {
ce = ((ClassType) type).getElement();
}
}
return ce;
}
public Collection<AttributedElement> getFunctions() {
Collection<AttributedElement> retval;
if (global != null) {
retval = global.getFunctions();
} else {
retval = Collections.emptyList();
}
return retval;
}
public Collection<AttributedElement> getConstants() {
Collection<AttributedElement> retval;
if (global != null) {
retval = global.getConstants();
} else {
retval = Collections.emptyList();
}
return retval;
}
public Collection<AttributedElement> getGlobalVariables() {
Collection<AttributedElement> retval;
if (global != null) {
retval = global.getVariables();
} else {
retval = Collections.emptyList();
}
return retval;
}
public Collection<ClassElementAttribute> getClasses() {
Collection<ClassElementAttribute> retval;
if (global != null) {
retval = global.getClasses();
} else {
retval = Collections.emptyList();
}
return retval;
}
public boolean hasGlobalVisibility(AttributedElement elem) {
if (elem.isClassMember()) {
assert (elem instanceof ClassMemberElement);
ClassMemberElement cme = (ClassMemberElement) elem;
boolean isGlobal = (cme.getModifier() == -1 || !cme.isPrivate()) && hasGlobalVisibility(cme.getClassElement());
return isGlobal;
}
return (global != null) ? global.getElements(elem.getKind()).contains(elem) : false;
}
public static class AttributedElement {
private List<Union2<ASTNode, PhpElement>> writes; //aka declarations
private List<AttributedType> writesTypes;
private String name;
private Kind k;
public AttributedElement(Union2<ASTNode, PhpElement> n, String name, Kind k) {
this(n, name, k, null);
}
public AttributedElement(Union2<ASTNode, PhpElement> n, String name, Kind k, AttributedType type) {
this.writes = new LinkedList<>();
this.writesTypes = new LinkedList<>();
this.writes.add(n);
this.writesTypes.add(type);
this.name = name;
this.k = k;
}
public boolean isClassMember() {
return false;
}
public List<Union2<ASTNode, PhpElement>> getWrites() {
return writes;
}
public Kind getKind() {
return k;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof AttributedElement)) {
return false;
}
AttributedElement element = (AttributedElement) obj;
return this.name.equals(element.name) && this.k.equals(element.k);
}
@Override
public int hashCode() {
int hash = 7;
hash = 53 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 53 * hash + (this.k != null ? this.k.hashCode() : 0);
return hash;
}
void addWrite(Union2<ASTNode, PhpElement> node, AttributedType type) {
writes.add(node);
writesTypes.add(type);
}
Types getTypes() {
return new Types(this);
}
public String getScopeName() {
String retval = ""; //NOI18N
Types types = getTypes();
for (int i = 0; i < types.size(); i++) {
AttributedType type = types.getType(i);
if (type != null) {
retval = type.getTypeName();
break;
}
}
return retval;
}
public enum Kind {
VARIABLE, FUNC, CLASS, CONST, IFACE;
}
}
private static class Types {
private AttributedElement el;
Types(AttributedElement el) {
this.el = el;
}
int size() {
return el.writesTypes.size();
}
AttributedType getType(int idx) {
return el.writesTypes.get(idx);
}
}
public static class ClassMemberElement extends AttributedElement {
private ClassElementAttribute classElement;
int modifier = -1;
public ClassMemberElement(Union2<ASTNode, PhpElement> n, ClassElementAttribute classElement, String name, Kind k) {
super(n, name, k);
this.classElement = classElement;
setModifiers(n, name);
assert classElement != null;
}
public String getClassName() {
return getClassElement().getName();
}
@Override
public String getScopeName() {
return getClassName();
}
public int getModifier() {
return modifier;
}
public boolean isPublic() {
return BodyDeclaration.Modifier.isPublic(getModifier());
}
public boolean isPrivate() {
return BodyDeclaration.Modifier.isPrivate(getModifier());
}
public boolean isProtected() {
return BodyDeclaration.Modifier.isProtected(getModifier());
}
public boolean isStatic() {
return BodyDeclaration.Modifier.isStatic(getModifier());
}
public ClassElementAttribute getClassElement() {
return classElement;
}
@Override
public boolean isClassMember() {
return true;
}
public ClassMemberKind getClassMemberKind() {
ClassMemberKind retval = null;
switch (getKind()) {
case CONST:
retval = ClassMemberKind.CONST;
break;
case FUNC:
retval = ClassMemberKind.METHOD;
break;
case VARIABLE:
retval = ClassMemberKind.FIELD;
break;
default:
assert false;
}
assert retval != null;
return retval;
}
private void setModifiers(Union2<ASTNode, PhpElement> n, String name) {
if (n.hasFirst()) {
ASTNode node = n.first();
if (node instanceof BodyDeclaration) {
modifier = ((BodyDeclaration) node).getModifier();
} else if (name.equals("this")) {
//NOI18N
assert false;
} else if (node instanceof ConstantDeclaration) {
modifier |= BodyDeclaration.Modifier.PUBLIC;
} else {
assert false : name;
}
} else if (n.hasSecond()) {
PhpElement index = n.second();
if (index != null) {
Set<Modifier> modifiers = index.getModifiers();
for (Modifier mod : modifiers) {
switch (mod) {
case PRIVATE:
modifier |= BodyDeclaration.Modifier.PRIVATE;
break;
case PROTECTED:
modifier |= BodyDeclaration.Modifier.PROTECTED;
break;
case PUBLIC:
modifier |= BodyDeclaration.Modifier.PUBLIC;
break;
case STATIC:
modifier |= BodyDeclaration.Modifier.STATIC;
break;
default:
// no-op
}
}
}
}
}
@Override
public int hashCode() {
int hash = 5;
hash = 97 * hash + (this.classElement != null ? this.classElement.hashCode() : 0);
hash = 97 * hash + this.modifier;
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ClassMemberElement other = (ClassMemberElement) obj;
if (this.classElement != other.classElement && (this.classElement == null || !this.classElement.equals(other.classElement))) {
return false;
}
return this.modifier == other.modifier;
}
public enum ClassMemberKind {
FIELD, METHOD, CONST;
}
}
public class ClassElementAttribute extends AttributedElement {
private final DefinitionScope enclosedElements;
private ClassElementAttribute superClass;
private Set<ClassElementAttribute> ifaces = new HashSet<>();
private boolean initialized;
public ClassElementAttribute(Union2<ASTNode, PhpElement> n, String name, Kind k) {
super(n, name, k);
enclosedElements = new DefinitionScope(this);
}
public AttributedElement lookup(String name, Kind k) {
AttributedElement el = enclosedElements.lookup(name, k);
if (el != null) {
return el;
}
Index index = ElementQueryFactory.getIndexQuery(info);
switch (k) {
case CONST:
for (TypeConstantElement classMember : index.getAllTypeConstants(NameKind.exact(getName()), NameKind.prefix(name))) {
String idxName = classMember.getName();
idxName = (idxName.startsWith("$")) ? idxName.substring(1) : idxName;
enclosedElements.enterWrite(idxName, Kind.CONST, classMember);
}
break;
case FUNC:
for (MethodElement classMember : index.getAllMethods(NameKind.exact(getName()), NameKind.prefix(name))) {
enclosedElements.enterWrite(classMember.getName(), Kind.FUNC, classMember);
}
break;
case VARIABLE:
for (FieldElement classMember : index.getAlllFields(NameKind.exact(getName()), NameKind.prefix(name))) {
String idxName = classMember.getName();
idxName = (idxName.startsWith("$")) ? idxName.substring(1) : idxName;
enclosedElements.enterWrite(idxName, Kind.VARIABLE, classMember);
}
break;
default:
//no-op
}
return enclosedElements.lookup(name, k);
}
public Collection<AttributedElement> getElements(Kind k) {
List<AttributedElement> elements = new ArrayList<>();
getElements0(elements, k);
return Collections.unmodifiableList(elements);
}
public Collection<AttributedElement> getNamedElements(Kind k, String... filterNames) {
Collection<AttributedElement> elements = getElements(k);
List<AttributedElement> retval = new ArrayList<>();
for (String fName : filterNames) {
for (AttributedElement el : elements) {
if (el.getName().equals(fName)) {
retval.add(el);
}
}
}
return retval;
}
public Collection<AttributedElement> getMethods() {
return getElements(Kind.FUNC);
}
public Collection<AttributedElement> getFields() {
Collection<AttributedElement> elems = getElements(Kind.VARIABLE);
List<AttributedElement> retval = new ArrayList<>();
for (AttributedElement elm : elems) {
if (!elm.getName().equals("this")) {
retval.add(elm);
}
}
return retval;
}
public ClassElementAttribute getSuperClass() {
return superClass;
}
private void getElements0(List<AttributedElement> elements, Kind k) {
elements.addAll(enclosedElements.getElements(k));
if (superClass != null) {
superClass.getElements0(elements, k);
}
}
boolean isInitialized() {
return initialized;
}
void initialized() {
initialized = true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 79 * hash + (this.enclosedElements != null ? this.enclosedElements.hashCode() : 0);
hash = 79 * hash + (this.superClass != null ? this.superClass.hashCode() : 0);
hash = 79 * hash + (this.ifaces != null ? this.ifaces.hashCode() : 0);
hash = 79 * hash + (this.initialized ? 1 : 0);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ClassElementAttribute other = (ClassElementAttribute) obj;
if (this.enclosedElements != other.enclosedElements && (this.enclosedElements == null || !this.enclosedElements.equals(other.enclosedElements))) {
return false;
}
if (this.superClass != other.superClass && (this.superClass == null || !this.superClass.equals(other.superClass))) {
return false;
}
if (this.ifaces != other.ifaces && (this.ifaces == null || !this.ifaces.equals(other.ifaces))) {
return false;
}
return this.initialized == other.initialized;
}
}
public class FunctionElementAttribute extends AttributedElement {
private final DefinitionScope enclosedElements;
private boolean initialized;
public FunctionElementAttribute(Union2<ASTNode, PhpElement> n, String name, Kind k) {
super(n, name, k);
enclosedElements = new DefinitionScope(this);
}
public AttributedElement lookup(String name, Kind k) {
return enclosedElements.lookup(name, k);
}
public Collection<AttributedElement> getElements(Kind k) {
List<AttributedElement> elements = new ArrayList<>();
getElements0(elements, k);
return Collections.unmodifiableList(elements);
}
public Collection<AttributedElement> getNamedElements(Kind k, String... filterNames) {
Collection<AttributedElement> elements = getElements(k);
List<AttributedElement> retval = new ArrayList<>();
for (String fName : filterNames) {
for (AttributedElement el : elements) {
if (el.getName().equals(fName)) {
retval.add(el);
}
}
}
return retval;
}
public Collection<AttributedElement> getVariables() {
return getElements(Kind.VARIABLE);
}
private void getElements0(List<AttributedElement> elements, Kind k) {
elements.addAll(enclosedElements.getElements(k));
}
boolean isInitialized() {
return initialized;
}
void initialized() {
initialized = true;
}
@Override
public int hashCode() {
int hash = 3;
hash = 41 * hash + (this.enclosedElements != null ? this.enclosedElements.hashCode() : 0);
hash = 41 * hash + (this.initialized ? 1 : 0);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final FunctionElementAttribute other = (FunctionElementAttribute) obj;
if (this.enclosedElements != other.enclosedElements && (this.enclosedElements == null || !this.enclosedElements.equals(other.enclosedElements))) {
return false;
}
return this.initialized == other.initialized;
}
}
public final class DefinitionScope {
private final Map<Kind, Map<String, AttributedElement>> name2Writes = new EnumMap<>(Kind.class);
private boolean classScope;
private boolean functionScope;
private AttributedElement thisVar;
private ClassElementAttribute enclosingClass;
private FunctionElementAttribute enclosingFunction;
public DefinitionScope() {
}
public DefinitionScope(ClassElementAttribute enclosingClass) {
this.enclosingClass = enclosingClass;
this.classScope = enclosingClass != null;
if (classScope) {
thisVar = enterWrite("this", Kind.VARIABLE, (ASTNode) null, new ClassType(enclosingClass));
}
}
public DefinitionScope(FunctionElementAttribute enclosingFunction) {
this.enclosingFunction = enclosingFunction;
this.functionScope = enclosingFunction != null;
}
public AttributedElement enterWrite(String name, Kind k, ASTNode node) {
return enterWrite(name, k, node, null);
}
public AttributedElement enterWrite(String name, Kind k, ASTNode node, AttributedType type) {
return enterWrite(name, k, Union2.<ASTNode, PhpElement>createFirst(node), type);
}
public AttributedElement enterWrite(String name, Kind k, PhpElement el) {
return enterWrite(name, k, Union2.<ASTNode, PhpElement>createSecond(el), null);
}
private AttributedElement enterWrite(String name, Kind k, Union2<ASTNode, PhpElement> node, AttributedType type) {
if (k == Kind.VARIABLE && this != global) {
//TODO: review
if (SUPERGLOBALS.contains(name)) {
return SemiAttribute.this.enterGlobalVariable(name);
}
}
Map<String, AttributedElement> name2El = name2Writes.get(k);
if (name2El == null) {
name2El = new HashMap<>();
name2Writes.put(k, name2El);
}
AttributedElement el = name2El.get(name);
if (el == null) {
if (k == Kind.CLASS || k == Kind.IFACE) {
el = new ClassElementAttribute(node, name, k);
} else {
if (classScope && !Arrays.asList(new String[]{"this"}).contains(name)) {
switch (k) {
case CONST:
case FUNC:
case VARIABLE:
el = new ClassMemberElement(node, enclosingClass, name, k);
break;
default:
assert false;
}
} else {
if (k == Kind.FUNC) {
el = new FunctionElementAttribute(node, name, k);
} else if (k == Kind.VARIABLE) {
if (type == null && functionScope && enclosingFunction != null) {
type = new FunctionType(enclosingFunction);
}
el = new AttributedElement(node, name, k, type);
} else {
el = new AttributedElement(node, name, k, type);
}
}
}
name2El.put(name, el);
} else {
el.addWrite(node, type);
}
return el;
}
public AttributedElement enter(String name, Kind k, AttributedElement el) {
Map<String, AttributedElement> name2El = name2Writes.get(k);
if (name2El == null) {
name2El = new HashMap<>();
name2Writes.put(k, name2El);
}
name2El.put(name, el);
return el;
}
public AttributedElement lookup(String name, Kind k) {
AttributedElement el = null;
Map<String, AttributedElement> name2El = name2Writes.get(k);
if (name2El != null) {
el = name2El.get(name);
}
if (el == null) {
Index index = ElementQueryFactory.getIndexQuery(info);
switch (k) {
case CONST:
for (ConstantElement m : index.getConstants(NameKind.prefix(name))) {
String idxName = m.getName();
el = enterWrite(idxName, Kind.CONST, m);
}
break;
default:
//no-op
}
}
return el;
}
public Collection<AttributedElement> getElements(Kind k) {
Map<String, AttributedElement> name2El = name2Writes.get(k);
if (name2El != null) {
return Collections.unmodifiableCollection(name2El.values());
}
return Collections.emptyList();
}
public Collection<AttributedElement> getFunctions() {
return getElements(Kind.FUNC);
}
public Collection<AttributedElement> getVariables() {
return getElements(Kind.VARIABLE);
}
private Collection<AttributedElement> getConstants() {
return getElements(Kind.CONST);
}
public Collection<ClassElementAttribute> getClasses() {
Collection<ClassElementAttribute> retval = new LinkedHashSet<>();
Collection<AttributedElement> elements = getElements(Kind.CLASS);
for (AttributedElement el : elements) {
assert el instanceof ClassElementAttribute;
retval.add((ClassElementAttribute) el);
}
return retval;
}
}
private static final class Stop extends Error {
}
public abstract static class AttributedType {
public abstract String getTypeName();
}
public static class ClassType extends AttributedType {
private final ClassElementAttribute element;
public ClassType(ClassElementAttribute element) {
this.element = element;
}
public ClassElementAttribute getElement() {
return element;
}
@Override
public String getTypeName() {
return getElement().getName();
}
}
public static class FunctionType extends AttributedType {
private final FunctionElementAttribute element;
public FunctionType(FunctionElementAttribute element) {
this.element = element;
}
public FunctionElementAttribute getElement() {
return element;
}
@Override
public String getTypeName() {
return getElement().getName();
}
}
}