blob: ba45cf38ef72610de24d2d6fdfbd19a5e9ced24e [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.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.modules.parsing.api.ParserManager;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.api.UserTask;
import org.netbeans.modules.parsing.spi.ParseException;
import org.netbeans.modules.parsing.spi.Parser;
import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport;
import org.netbeans.modules.php.editor.CodeUtils;
import org.netbeans.modules.php.editor.NamespaceIndexFilter;
import org.netbeans.modules.php.editor.api.AliasedName;
import org.netbeans.modules.php.editor.api.PhpElementKind;
import org.netbeans.modules.php.editor.api.QualifiedName;
import org.netbeans.modules.php.editor.api.QualifiedNameKind;
import org.netbeans.modules.php.editor.lexer.PHPTokenId;
import org.netbeans.modules.php.editor.model.impl.VariousUtils;
import org.netbeans.modules.php.editor.model.nodes.ASTNodeInfo;
import org.netbeans.modules.php.editor.model.nodes.NamespaceDeclarationInfo;
import org.netbeans.modules.php.editor.parser.PHPParseResult;
import org.netbeans.modules.php.editor.parser.astnodes.Assignment;
import org.netbeans.modules.php.editor.parser.astnodes.Expression;
import org.netbeans.modules.php.editor.parser.astnodes.NamespaceDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.StaticDispatch;
import org.netbeans.modules.php.editor.parser.astnodes.VariableBase;
import org.openide.filesystems.FileObject;
import org.openide.util.RequestProcessor;
/**
* @author Radek Matous
*/
public final class ModelUtils {
private static final Logger LOGGER = Logger.getLogger(ModelUtils.class.getName());
private static final RequestProcessor RP = new RequestProcessor(ModelUtils.class);
private ModelUtils() {
}
public static Set<AliasedName> getAliasedNames(final Model model, final int offset) {
final Set<AliasedName> aliases = new HashSet<>();
final NamespaceScope namespaceScope = ModelUtils.getNamespaceScope(model.getFileScope(), offset);
if (namespaceScope != null) {
Collection<? extends UseScope> declaredUses = namespaceScope.getAllDeclaredSingleUses();
for (UseScope useElement : declaredUses) {
AliasedName aliasedName = useElement.getAliasedName();
if (aliasedName != null) {
aliases.add(aliasedName);
}
}
}
return aliases;
}
public static NamespaceScope getNamespaceScope(NamespaceDeclaration currenNamespace, FileScope fileScope) {
NamespaceDeclarationInfo ndi = currenNamespace != null ? NamespaceDeclarationInfo.create(currenNamespace) : null;
NamespaceScope currentScope = ndi != null
? ModelUtils.getFirst(ModelUtils.filter(fileScope.getDeclaredNamespaces(), ndi.getName()))
: fileScope.getDefaultDeclaredNamespace();
return currentScope;
}
public static Collection<? extends TypeScope> getDeclaredTypes(FileScope fileScope) {
List<TypeScope> retval = new ArrayList<>();
Collection<? extends NamespaceScope> declaredNamespaces = fileScope.getDeclaredNamespaces();
for (NamespaceScope namespace : declaredNamespaces) {
retval.addAll(namespace.getDeclaredTypes());
}
return retval;
}
public static Collection<? extends ClassScope> getDeclaredClasses(FileScope fileScope) {
List<ClassScope> retval = new ArrayList<>();
Collection<? extends NamespaceScope> declaredNamespaces = fileScope.getDeclaredNamespaces();
for (NamespaceScope namespace : declaredNamespaces) {
retval.addAll(namespace.getDeclaredClasses());
}
return retval;
}
public static Collection<? extends InterfaceScope> getDeclaredInterfaces(FileScope fileScope) {
List<InterfaceScope> retval = new ArrayList<>();
Collection<? extends NamespaceScope> declaredNamespaces = fileScope.getDeclaredNamespaces();
for (NamespaceScope namespace : declaredNamespaces) {
retval.addAll(namespace.getDeclaredInterfaces());
}
return retval;
}
public static Collection<? extends TraitScope> getDeclaredTraits(FileScope fileScope) {
List<TraitScope> retval = new ArrayList<>();
Collection<? extends NamespaceScope> declaredNamespaces = fileScope.getDeclaredNamespaces();
for (NamespaceScope namespace : declaredNamespaces) {
retval.addAll(namespace.getDeclaredTraits());
}
return retval;
}
public static Collection<? extends ConstantElement> getDeclaredConstants(FileScope fileScope) {
List<ConstantElement> retval = new ArrayList<>();
Collection<? extends NamespaceScope> declaredNamespaces = fileScope.getDeclaredNamespaces();
for (NamespaceScope namespace : declaredNamespaces) {
retval.addAll(namespace.getDeclaredConstants());
}
return retval;
}
public static Collection<? extends FunctionScope> getDeclaredFunctions(FileScope fileScope) {
List<FunctionScope> retval = new ArrayList<>();
Collection<? extends NamespaceScope> declaredNamespaces = fileScope.getDeclaredNamespaces();
for (NamespaceScope namespace : declaredNamespaces) {
retval.addAll(namespace.getDeclaredFunctions());
}
return retval;
}
public static Collection<? extends VariableName> getDeclaredVariables(FileScope fileScope) {
List<VariableName> retval = new ArrayList<>();
Collection<? extends NamespaceScope> declaredNamespaces = fileScope.getDeclaredNamespaces();
for (NamespaceScope namespace : declaredNamespaces) {
retval.addAll(namespace.getDeclaredVariables());
}
return retval;
}
public static List<? extends ModelElement> getElements(Scope scope, boolean resursively) {
List<ModelElement> retval = new ArrayList<>();
List<? extends ModelElement> elements = scope.getElements();
retval.addAll(elements);
for (ModelElement modelElement : elements) {
if (modelElement instanceof Scope) {
retval.addAll(getElements((Scope) modelElement, resursively));
}
}
return retval;
}
public static Collection<? extends TypeScope> resolveType(Model model, StaticDispatch dispatch) {
VariableScope variableScope = model.getVariableScope(dispatch.getStartOffset());
QualifiedName fullyQualifiedName = VariousUtils.getFullyQualifiedName(
ASTNodeInfo.toQualifiedName(dispatch, true),
dispatch.getStartOffset(),
variableScope);
NamespaceIndexFilter filter = new NamespaceIndexFilter(fullyQualifiedName.toString());
Collection<? extends TypeScope> staticTypeName = VariousUtils.getStaticTypeName(
variableScope != null ? variableScope : model.getFileScope(), filter.getName());
return filter.filterModelElements(staticTypeName, true);
}
@NonNull
public static Collection<? extends TypeScope> resolveType(Model model, VariableBase varBase) {
return resolveType(model, varBase, true);
}
@NonNull
public static Collection<? extends TypeScope> resolveType(Model model, VariableBase varBase, boolean justDispatcher) {
Collection<? extends TypeScope> retval = Collections.emptyList();
VariableScope scp = model.getVariableScope(varBase.getStartOffset());
if (scp != null) {
String vartype = VariousUtils.extractTypeFroVariableBase(varBase);
if (vartype != null) {
retval = VariousUtils.getType(scp, vartype, varBase.getStartOffset(), justDispatcher);
}
}
return retval;
}
@NonNull
public static Collection<? extends TypeScope> resolveType(Model model, Assignment varBase) {
Collection<? extends TypeScope> retval = Collections.emptyList();
VariableScope scp = model.getVariableScope(varBase.getStartOffset());
if (scp != null) {
String vartype = CodeUtils.extractVariableType(varBase);
if (vartype == null) {
final Expression rightHandSide = varBase.getRightHandSide();
if (rightHandSide instanceof VariableBase) {
vartype = VariousUtils.extractTypeFroVariableBase((VariableBase) rightHandSide);
if (vartype != null) {
return VariousUtils.getType(scp, vartype, varBase.getStartOffset(), false);
}
} else if (rightHandSide instanceof StaticDispatch) {
QualifiedName qName = ASTNodeInfo.toQualifiedName(rightHandSide, true);
if (qName != null) {
VariableScope variableScope = model.getVariableScope(rightHandSide.getStartOffset());
QualifiedName fullyQualifiedName = VariousUtils.getFullyQualifiedName(
qName,
rightHandSide.getStartOffset(),
variableScope);
NamespaceIndexFilter filter = new NamespaceIndexFilter(fullyQualifiedName.toString());
Collection<? extends TypeScope> staticTypeName = VariousUtils.getStaticTypeName(
variableScope != null ? variableScope : model.getFileScope(), filter.getName());
return filter.filterModelElements(staticTypeName, true);
}
}
} else {
retval = VariousUtils.getType(scp, vartype, varBase.getStartOffset(), false);
}
}
return retval;
}
@NonNull
public static Collection<? extends TypeScope> resolveType(Model model, int offset) {
VariableScope variableScope = model.getVariableScope(offset);
TypeScope typeScope = getTypeScope(variableScope);
if (typeScope != null) {
return Collections.singletonList(typeScope);
}
return Collections.emptyList();
}
@NonNull
public static Collection<? extends TypeScope> resolveTypeAfterReferenceToken(Model model, TokenSequence<PHPTokenId> tokenSequence,
int offset, boolean specialVariable) {
tokenSequence.move(offset);
Collection<? extends TypeScope> retval = Collections.emptyList();
VariableScope scp = model.getVariableScope(offset);
if (specialVariable) {
// #247082
// typically 'self', '$this' etc.; it means that we need to find method scope since these
// variables can be used in lambda functions directly
Scope tmpScope = scp;
while (tmpScope != null) {
if (tmpScope instanceof MethodScope) {
scp = (VariableScope) tmpScope;
break;
}
tmpScope = tmpScope.getInScope();
}
}
if (scp != null) {
String semiType = VariousUtils.getSemiType(tokenSequence, VariousUtils.State.START, scp);
if (semiType != null) {
return VariousUtils.getType(scp, semiType, offset, true);
}
}
return retval;
}
@CheckForNull
public static <T> T getFirst(Collection<? extends T> all) {
if (all instanceof List) {
return all.size() > 0 ? ((List<T>) all).get(0) : null;
}
return all.size() > 0 ? all.iterator().next() : null;
}
@CheckForNull
public static <T extends ModelElement> T getLast(List<? extends T> all) {
return all.size() > 0 ? all.get(all.size() - 1) : null;
}
@NonNull
public static <T extends ModelElement> List<? extends T> filter(Collection<T> allElements,
final QuerySupport.Kind nameKind, final QualifiedName qualifiedName) {
final QualifiedNameKind kind = qualifiedName.getKind();
final String name = qualifiedName.toName().toString();
final String namespaceName = qualifiedName.toNamespaceName().toString();
return filter(allElements, new ElementFilter<T>() {
@Override
public boolean isAccepted(T element) {
if (nameKindMatch(element.getName(), nameKind, name)) {
switch(kind) {
case QUALIFIED:
return element.getNamespaceName().toString().endsWith(namespaceName);
case UNQUALIFIED:
return true;
case FULLYQUALIFIED:
return nameKindMatch(element.getNamespaceName().toString(), nameKind, namespaceName);
default:
assert false : kind;
}
}
return false;
}
});
}
@NonNull
public static <T extends ModelElement> List<? extends T> filter(Collection<T> allElements,
final QualifiedName qName) {
return filter(allElements, QuerySupport.Kind.EXACT, qName);
}
@NonNull
public static <T extends ModelElement> List<? extends T> filter(Collection<T> allElements,
final String... elementName) {
return filter(allElements, QuerySupport.Kind.EXACT, elementName);
}
@NonNull
public static <T extends ModelElement> List<? extends T> filter(Collection<T> allElements,
final QuerySupport.Kind nameKind, final String... elementName) {
return filter(allElements, new ElementFilter<T>() {
@Override
public boolean isAccepted(T element) {
final PhpElementKind kind = element.getPhpElementKind();
boolean caseSensitive = EnumSet.of(PhpElementKind.VARIABLE, PhpElementKind.FIELD).contains(kind);
return (elementName.length == 0 || nameKindMatch(!caseSensitive, element.getName(), nameKind, elementName));
}
});
}
@NonNull
public static <T extends ModelElement> List<? extends T> filter(Collection<? extends T> allElements,
FileObject fileObject) {
List<T> retval = new ArrayList<>();
for (T element : allElements) {
if (element.getFileObject() == fileObject) {
retval.add(element);
}
}
return retval;
}
@CheckForNull
public static <T extends ModelElement> T getFirst(Collection<T> allElements,
final String... elementName) {
return getFirst(filter(allElements, QuerySupport.Kind.EXACT, elementName));
}
@CheckForNull
public static <T extends ModelElement> T getFirst(Collection<T> allElements,
final QuerySupport.Kind nameKind, final String... elementName) {
return getFirst(filter(allElements, new ElementFilter<T>() {
@Override
public boolean isAccepted(T element) {
return (elementName.length == 0 || nameKindMatch(element.getName(), nameKind, elementName));
}
}));
}
@CheckForNull
public static <T extends ModelElement> T getFirst(Collection<? extends T> allElements,
FileObject fileObject) {
List<T> retval = new ArrayList<>();
for (T element : allElements) {
if (element.getFileObject() == fileObject) {
retval.add(element);
}
}
return getFirst(retval);
}
@SuppressWarnings("unchecked")
@NonNull
public static <T extends ModelElement> Collection<? extends T> merge(Collection<? extends T>... all) {
List<T> retval = new ArrayList<>();
for (Collection<? extends T> list : all) {
retval.addAll(list);
}
return retval;
}
@CheckForNull
public static FileScope getFileScope(ModelElement element) {
FileScope retval = (element instanceof FileScope) ? (FileScope) element : null;
while (retval == null && element != null) {
element = element.getInScope();
retval = (FileScope) ((element instanceof FileScope) ? element : null);
}
return retval;
}
@CheckForNull
public static NamespaceScope getNamespaceScope(ModelElement element) {
NamespaceScope retval = (element instanceof NamespaceScope) ? (NamespaceScope) element : null;
while (retval == null && element != null) {
element = element.getInScope();
retval = (NamespaceScope) ((element instanceof NamespaceScope) ? element : null);
}
return retval;
}
@CheckForNull
public static NamespaceScope getNamespaceScope(FileScope fileScope, int offset) {
NamespaceScope retval = fileScope.getDefaultDeclaredNamespace();
Collection<? extends NamespaceScope> declaredNamespaces = fileScope.getDeclaredNamespaces();
for (NamespaceScope namespaceScope : declaredNamespaces) {
OffsetRange blockRange = namespaceScope.getBlockRange();
if (blockRange != null && blockRange.containsInclusive(offset)) {
if (retval == null || !namespaceScope.isDefaultNamespace()) {
retval = namespaceScope;
}
}
}
return retval;
}
@CheckForNull
public static TypeScope getTypeScope(ModelElement element) {
TypeScope retval = (element instanceof TypeScope) ? (TypeScope) element : null;
while (retval == null && element != null) {
element = element.getInScope();
retval = (TypeScope) ((element instanceof TypeScope) ? element : null);
}
return retval;
}
@CheckForNull
public static ClassScope getClassScope(ModelElement element) {
ClassScope retval = (element instanceof ClassScope) ? (ClassScope) element : null;
while (retval == null && element != null) {
element = element.getInScope();
retval = (ClassScope) ((element instanceof ClassScope) ? element : null);
}
return retval;
}
@NonNull
public static IndexScope getIndexScope(ModelElement element) {
IndexScope retval = (element instanceof IndexScope) ? (IndexScope) element : null;
ModelElement tmpElement = element;
while (retval == null && tmpElement != null) {
tmpElement = tmpElement.getInScope();
retval = (IndexScope) ((tmpElement instanceof IndexScope) ? tmpElement : null);
}
if (retval == null) {
FileScope fileScope = getFileScope(element);
assert fileScope != null;
retval = fileScope.getIndexScope();
}
return retval;
}
public static <T extends ModelElement> List<? extends T> filter(final Collection<? extends T> instances, final ElementFilter<T> filter) {
List<T> retval = new ArrayList<>();
for (T baseElement : instances) {
boolean accepted = filter.isAccepted(baseElement);
if (accepted) {
retval.add(baseElement);
}
}
return retval;
}
public interface ElementFilter<T extends ModelElement> {
boolean isAccepted(T element);
}
public static boolean nameKindMatch(String text, QuerySupport.Kind nameKind, String... queries) {
return nameKindMatch(true, text, nameKind, queries);
}
private static boolean nameKindMatch(boolean forceCaseInsensitivity, String text, QuerySupport.Kind nameKind, String... queries) {
boolean result = false;
for (String query : queries) {
switch (nameKind) {
case CAMEL_CASE:
if (toCamelCase(text).startsWith(query)) {
result = true;
}
break;
case CASE_INSENSITIVE_PREFIX:
if (text.toLowerCase().startsWith(query.toLowerCase())) {
result = true;
}
break;
case CASE_INSENSITIVE_REGEXP:
text = text.toLowerCase();
result = regexpMatch(text, query);
break;
case REGEXP:
//TODO: might be perf. problem if called for large collections
// and ever and ever again would be compiled still the same query
result = regexpMatch(text, query);
break;
case EXACT:
boolean retval = (forceCaseInsensitivity) ? text.equalsIgnoreCase(query) : text.equals(query);
if (retval) {
result = true;
}
break;
case PREFIX:
if (text.startsWith(query)) {
result = true;
}
break;
default:
//no-op
}
}
return result;
}
private static boolean regexpMatch(String text, String query) {
boolean result = false;
Pattern p = Pattern.compile(query);
if (nameKindMatch(p, text)) {
result = true;
}
return result;
}
public static String getCamelCaseName(ModelElement element) {
return toCamelCase(element.getName());
}
public static String toCamelCase(String plainName) {
char[] retval = new char[plainName.length()];
int retvalSize = 0;
for (int i = 0; i < retval.length; i++) {
char c = plainName.charAt(i);
if (Character.isUpperCase(c)) {
retval[retvalSize] = c;
retvalSize++;
}
}
return String.valueOf(String.valueOf(retval, 0, retvalSize));
}
private static boolean nameKindMatch(Pattern p, String text) {
return p.matcher(text).matches();
}
@CheckForNull
public static FileScope getFileScope(final FileObject fileObject) {
return getFileScope(fileObject, 0);
}
/**
*
* @param fileObject
* @param timeout in milliseconds
* @return
*/
@CheckForNull
public static FileScope getFileScope(final FileObject fileObject, final int timeout) {
FileScope result = null;
final Future<FileScope> futureResult = RP.submit(new Callable<FileScope>() {
@Override
public FileScope call() throws Exception {
final FileScope[] fileScope = new FileScope[1];
try {
ParserManager.parse(Collections.singletonList(Source.create(fileObject)), new UserTask() {
@Override
public void run(ResultIterator resultIterator) throws Exception {
Parser.Result parserResult = resultIterator.getParserResult();
if (parserResult instanceof PHPParseResult) {
PHPParseResult phpResult = (PHPParseResult) parserResult;
fileScope[0] = phpResult.getModel().getFileScope();
}
}
});
} catch (ParseException ex) {
LOGGER.log(Level.WARNING, null, ex);
}
return fileScope[0];
}
});
try {
result = futureResult.get(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException | TimeoutException ex) {
LOGGER.log(Level.FINE, null, ex);
} catch (ExecutionException ex) {
LOGGER.log(Level.WARNING, null, ex);
}
return result;
}
@CheckForNull
public static Model getModel(final Source source) {
return getModel(source, 0);
}
/**
*
* @param source
* @param timeout in milliseconds
* @return
*/
@CheckForNull
public static Model getModel(final Source source, final int timeout) {
Model result = null;
final Future<Model> futureResult = RP.submit(new Callable<Model>() {
@Override
public Model call() throws Exception {
final Model[] model = new Model[1];
try {
ParserManager.parse(Collections.singletonList(source), new UserTask() {
@Override
public void run(ResultIterator resultIterator) throws Exception {
Parser.Result parserResult = resultIterator.getParserResult();
if (parserResult instanceof PHPParseResult) {
PHPParseResult phpResult = (PHPParseResult) parserResult;
model[0] = phpResult.getModel();
}
}
});
} catch (ParseException ex) {
LOGGER.log(Level.WARNING, null, ex);
}
return model[0];
}
});
try {
result = futureResult.get(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException | TimeoutException ex) {
LOGGER.log(Level.FINE, null, ex);
} catch (ExecutionException ex) {
LOGGER.log(Level.WARNING, null, ex);
}
return result;
}
/**
* Check whether the scope is anonymous function scope.
*
* @param scope the scope
* @return {@code true} if the scope is anonymous function scope,
* {@code false} otherwise
*/
public static boolean isAnonymousFunction(Scope scope) {
return scope instanceof FunctionScope
&& ((FunctionScope) scope).isAnonymous();
}
}