| /* |
| * 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(); |
| } |
| } |
| } |