go back to using non-stub index for functional expressions, because the inevitable stub-AST switch is quite more expensive than finding element by offset (because a lot less code blocks will be parsed in the latter case)

This commit is contained in:
peter
2016-08-05 13:41:37 +02:00
parent 396b621fda
commit 093b231dcd
9 changed files with 426 additions and 329 deletions
@@ -0,0 +1,349 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed 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 com.intellij.psi.impl.java;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.lang.LighterAST;
import com.intellij.lang.LighterASTNode;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiBinaryExpression;
import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.JavaFileElementType;
import com.intellij.psi.impl.source.JavaLightTreeUtil;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.LightTreeUtil;
import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.indexing.*;
import com.intellij.util.io.DataExternalizer;
import com.intellij.util.io.DataInputOutputUtil;
import com.intellij.util.io.KeyDescriptor;
import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.intellij.psi.impl.source.tree.JavaElementType.*;
public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<FunctionalExpressionKey, TIntArrayList> implements PsiDependentIndex {
public static final ID<FunctionalExpressionKey, TIntArrayList> INDEX_ID = ID.create("java.fun.expression");
private static final KeyDescriptor<FunctionalExpressionKey> KEY_DESCRIPTOR = new KeyDescriptor<FunctionalExpressionKey>() {
@Override
public int getHashCode(FunctionalExpressionKey value) {
return value.hashCode();
}
@Override
public boolean isEqual(FunctionalExpressionKey val1, FunctionalExpressionKey val2) {
return val1.equals(val2);
}
@Override
public void save(@NotNull DataOutput out, FunctionalExpressionKey value) throws IOException {
value.serializeKey(out);
}
@Override
public FunctionalExpressionKey read(@NotNull DataInput in) throws IOException {
return FunctionalExpressionKey.deserializeKey(in);
}
};
@NotNull
private static FunctionalExpressionKey.Location calcLocation(LighterAST tree, LighterASTNode funExpr) {
LighterASTNode call = getContainingCall(tree, funExpr);
List<LighterASTNode> args = JavaLightTreeUtil.getArgList(tree, call);
int argIndex = args == null ? -1 : getArgIndex(args, funExpr);
String methodName = call == null ? null : getCalledMethodName(tree, call);
return methodName == null || argIndex < 0
? createTypedLocation(tree, funExpr)
: new FunctionalExpressionKey.CallLocation(methodName, args.size(), argIndex, StreamApiDetector.isStreamApiCall(tree, call));
}
@NotNull
private static FunctionalExpressionKey.Location createTypedLocation(LighterAST tree, LighterASTNode funExpr) {
LighterASTNode scope = skipExpressionsUp(tree, funExpr, TokenSet.create(LOCAL_VARIABLE, FIELD, TYPE_CAST_EXPRESSION, RETURN_STATEMENT));
if (scope != null) {
if (scope.getTokenType() == RETURN_STATEMENT) {
scope = LightTreeUtil.getParentOfType(tree, scope,
TokenSet.create(METHOD),
TokenSet.orSet(ElementType.MEMBER_BIT_SET, TokenSet.create(LAMBDA_EXPRESSION)));
}
LighterASTNode typeElement = LightTreeUtil.firstChildOfType(tree, scope, TYPE);
String typeText = JavaLightTreeUtil.getNameIdentifierText(tree, LightTreeUtil.firstChildOfType(tree, typeElement, JAVA_CODE_REFERENCE));
if (typeText != null) {
return new FunctionalExpressionKey.TypedLocation(typeText);
}
}
return FunctionalExpressionKey.Location.UNKNOWN;
}
private static int getArgIndex(List<LighterASTNode> args, LighterASTNode expr) {
for (int i = 0; i < args.size(); i++) {
if (args.get(i).getEndOffset() >= expr.getEndOffset()) {
return i;
}
}
return -1;
}
private static FunctionalExpressionKey.CoarseType calcType(final LighterAST tree, LighterASTNode funExpr) {
if (funExpr.getTokenType() == METHOD_REF_EXPRESSION) return FunctionalExpressionKey.CoarseType.UNKNOWN;
LighterASTNode block = LightTreeUtil.firstChildOfType(tree, funExpr, CODE_BLOCK);
if (block == null) {
LighterASTNode expr = findExpressionChild(funExpr, tree);
return isBooleanExpression(tree, expr) ? FunctionalExpressionKey.CoarseType.BOOLEAN : FunctionalExpressionKey.CoarseType.UNKNOWN;
}
final Ref<Boolean> returnsSomething = Ref.create(null);
final AtomicBoolean isBoolean = new AtomicBoolean();
final AtomicBoolean hasStatements = new AtomicBoolean();
new RecursiveLighterASTNodeWalkingVisitor(tree) {
@Override
public void visitNode(@NotNull LighterASTNode element) {
IElementType type = element.getTokenType();
if (type == LAMBDA_EXPRESSION || ElementType.MEMBER_BIT_SET.contains(type)) {
return;
}
if (type == RETURN_STATEMENT) {
LighterASTNode expr = findExpressionChild(element, tree);
returnsSomething.set(expr != null);
if (isBooleanExpression(tree, expr)) {
isBoolean.set(true);
}
return;
}
if (type == EXPRESSION_STATEMENT) {
hasStatements.set(true);
}
super.visitNode(element);
}
}.visitNode(block);
if (isBoolean.get()) {
return FunctionalExpressionKey.CoarseType.BOOLEAN;
}
if (returnsSomething.isNull()) {
return hasStatements.get() ? FunctionalExpressionKey.CoarseType.VOID : FunctionalExpressionKey.CoarseType.UNKNOWN;
}
return returnsSomething.get() ? FunctionalExpressionKey.CoarseType.NON_VOID : FunctionalExpressionKey.CoarseType.VOID;
}
private static LighterASTNode findExpressionChild(@NotNull LighterASTNode element, LighterAST tree) {
return LightTreeUtil.firstChildOfType(tree, element, ElementType.EXPRESSION_BIT_SET);
}
private static boolean isBooleanExpression(LighterAST tree, @Nullable LighterASTNode expr) {
if (expr == null) return false;
IElementType type = expr.getTokenType();
if (type == LITERAL_EXPRESSION) {
IElementType child = tree.getChildren(expr).get(0).getTokenType();
return child == JavaTokenType.TRUE_KEYWORD || child == JavaTokenType.FALSE_KEYWORD;
}
if (type == POLYADIC_EXPRESSION || type == BINARY_EXPRESSION) {
return LightTreeUtil.firstChildOfType(tree, expr, PsiBinaryExpression.BOOLEAN_OPERATION_TOKENS) != null;
}
if (type == PREFIX_EXPRESSION) {
return tree.getChildren(expr).get(0).getTokenType() == JavaTokenType.EXCL;
}
if (type == PARENTH_EXPRESSION) {
return isBooleanExpression(tree, findExpressionChild(expr, tree));
}
if (type == CONDITIONAL_EXPRESSION) {
List<LighterASTNode> children = LightTreeUtil.getChildrenOfType(tree, expr, ElementType.EXPRESSION_BIT_SET);
return children.size() == 3 && (isBooleanExpression(tree, children.get(1)) || isBooleanExpression(tree, children.get(2)));
}
return false;
}
private static int getFunExprParameterCount(LighterAST tree, LighterASTNode funExpr) {
if (funExpr.getTokenType() == METHOD_REF_EXPRESSION) return FunctionalExpressionKey.UNKNOWN_PARAM_COUNT;
assert funExpr.getTokenType() == LAMBDA_EXPRESSION;
LighterASTNode paramList = LightTreeUtil.firstChildOfType(tree, funExpr, PARAMETER_LIST);
assert paramList != null;
return LightTreeUtil.getChildrenOfType(tree, paramList, Constants.PARAMETER_BIT_SET).size();
}
@Nullable
private static String getCalledMethodName(LighterAST tree, LighterASTNode call) {
if (call.getTokenType() == NEW_EXPRESSION) {
LighterASTNode anonClass = LightTreeUtil.firstChildOfType(tree, call, ANONYMOUS_CLASS);
LighterASTNode ref = LightTreeUtil.firstChildOfType(tree, anonClass != null ? anonClass : call, JAVA_CODE_REFERENCE);
return ref == null ? null : JavaLightTreeUtil.getNameIdentifierText(tree, ref);
}
LighterASTNode methodExpr = tree.getChildren(call).get(0);
if (LightTreeUtil.firstChildOfType(tree, methodExpr, JavaTokenType.SUPER_KEYWORD) != null) {
return getSuperClassName(tree, call);
}
if (LightTreeUtil.firstChildOfType(tree, methodExpr, JavaTokenType.THIS_KEYWORD) != null) {
return JavaLightTreeUtil.getNameIdentifierText(tree, findClass(tree, call));
}
return JavaLightTreeUtil.getNameIdentifierText(tree, methodExpr);
}
@Nullable
private static String getSuperClassName(LighterAST tree, LighterASTNode call) {
LighterASTNode aClass = findClass(tree, call);
LighterASTNode extendsList = LightTreeUtil.firstChildOfType(tree, aClass, EXTENDS_LIST);
return JavaLightTreeUtil.getNameIdentifierText(tree, LightTreeUtil.firstChildOfType(tree, extendsList, JAVA_CODE_REFERENCE));
}
@Nullable
private static LighterASTNode getContainingCall(LighterAST tree, LighterASTNode node) {
LighterASTNode expressionList = skipExpressionsUp(tree, node, TokenSet.create(EXPRESSION_LIST));
if (expressionList != null) {
LighterASTNode parent = tree.getParent(expressionList);
if (parent != null && parent.getTokenType() == ANONYMOUS_CLASS) {
parent = tree.getParent(parent);
}
if (parent != null && (parent.getTokenType() == METHOD_CALL_EXPRESSION || parent.getTokenType() == NEW_EXPRESSION)) {
return parent;
}
}
return null;
}
private static LighterASTNode skipExpressionsUp(LighterAST tree, @NotNull LighterASTNode node, TokenSet types) {
node = tree.getParent(node);
while (node != null) {
final IElementType type = node.getTokenType();
if (types.contains(type)) return node;
if (type != PARENTH_EXPRESSION && type != CONDITIONAL_EXPRESSION) return null;
node = tree.getParent(node);
}
return null;
}
private static LighterASTNode findClass(LighterAST tree, LighterASTNode node) {
while (node != null) {
final IElementType type = node.getTokenType();
if (type == CLASS) return node;
node = tree.getParent(node);
}
return null;
}
@NotNull
@Override
public KeyDescriptor<FunctionalExpressionKey> getKeyDescriptor() {
return KEY_DESCRIPTOR;
}
@Override
public int getVersion() {
return 1;
}
@NotNull
@Override
public ID<FunctionalExpressionKey, TIntArrayList> getName() {
return INDEX_ID;
}
@NotNull
@Override
public DataIndexer<FunctionalExpressionKey, TIntArrayList, FileContent> getIndexer() {
return inputData -> {
Map<FunctionalExpressionKey, TIntArrayList> result = new HashMap<>();
LighterAST tree = ((FileContentImpl)inputData).getLighterASTForPsiDependentIndex();
new RecursiveLighterASTNodeWalkingVisitor(tree) {
@Override
public void visitNode(@NotNull LighterASTNode element) {
if (element.getTokenType() == METHOD_REF_EXPRESSION ||
element.getTokenType() == LAMBDA_EXPRESSION) {
FunctionalExpressionKey key = new FunctionalExpressionKey(getFunExprParameterCount(tree, element),
calcType(tree, element),
calcLocation(tree, element));
TIntArrayList list = result.get(key);
if (list == null) {
result.put(key, list = new TIntArrayList());
}
list.add(element.getStartOffset());
}
super.visitNode(element);
}
}.visitNode(tree.getRoot());
return result;
};
}
@NotNull
@Override
public DataExternalizer<TIntArrayList> getValueExternalizer() {
return new DataExternalizer<TIntArrayList>() {
@Override
public void save(@NotNull DataOutput out, TIntArrayList value) throws IOException {
DataInputOutputUtil.writeINT(out, value.size());
for (int i = 0; i < value.size(); i++) {
DataInputOutputUtil.writeINT(out, value.get(i));
}
}
@Override
public TIntArrayList read(@NotNull DataInput in) throws IOException {
int length = DataInputOutputUtil.readINT(in);
TIntArrayList list = new TIntArrayList(length);
for (int i = 0; i < length; i++) {
list.add(DataInputOutputUtil.readINT(in));
}
return list;
}
};
}
@NotNull
@Override
public FileBasedIndex.InputFilter getInputFilter() {
return new DefaultFileTypeSpecificInputFilter(JavaFileType.INSTANCE) {
@Override
public boolean acceptInput(@NotNull VirtualFile file) {
return super.acceptInput(file) && JavaFileElementType.isInSourceContent(file);
}
};
}
@Override
public boolean dependsOnFileContent() {
return true;
}
}
@@ -1,73 +0,0 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed 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.
*/
/*
* @author max
*/
package com.intellij.psi.impl.java.stubs.index;
import com.intellij.psi.PsiFunctionalExpression;
import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey;
import com.intellij.psi.stubs.AbstractStubIndex;
import com.intellij.psi.stubs.StubIndexKey;
import com.intellij.util.io.KeyDescriptor;
import org.jetbrains.annotations.NotNull;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class JavaFunctionalExpressionIndex extends AbstractStubIndex<FunctionalExpressionKey, PsiFunctionalExpression> {
private static final KeyDescriptor<FunctionalExpressionKey> KEY_DESCRIPTOR = new KeyDescriptor<FunctionalExpressionKey>() {
@Override
public int getHashCode(FunctionalExpressionKey value) {
return value.hashCode();
}
@Override
public boolean isEqual(FunctionalExpressionKey val1, FunctionalExpressionKey val2) {
return val1.equals(val2);
}
@Override
public void save(@NotNull DataOutput out, FunctionalExpressionKey value) throws IOException {
value.serializeKey(out);
}
@Override
public FunctionalExpressionKey read(@NotNull DataInput in) throws IOException {
return FunctionalExpressionKey.deserializeKey(in);
}
};
@NotNull
@Override
public KeyDescriptor<FunctionalExpressionKey> getKeyDescriptor() {
return KEY_DESCRIPTOR;
}
@Override
public int getVersion() {
return 1;
}
@NotNull
@Override
public StubIndexKey<FunctionalExpressionKey, PsiFunctionalExpression> getKey() {
return JavaStubIndexKeys.FUNCTIONAL_EXPRESSIONS;
}
}
@@ -32,12 +32,12 @@ import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.java.JavaFunctionalExpressionIndex;
import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey;
import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey.CallLocation;
import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey.Location;
import com.intellij.psi.impl.java.stubs.JavaMethodElementType;
import com.intellij.psi.impl.java.stubs.index.JavaMethodParameterTypesIndex;
import com.intellij.psi.impl.java.stubs.index.JavaStubIndexKeys;
import com.intellij.psi.search.*;
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
import com.intellij.psi.search.searches.FunctionalExpressionSearch;
@@ -48,6 +48,7 @@ import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.indexing.FileBasedIndex;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -57,7 +58,7 @@ import java.util.stream.*;
import static com.intellij.util.ObjectUtils.assertNotNull;
public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunctionalExpression, FunctionalExpressionSearch.SearchParameters> {
private static final Logger LOG = Logger.getInstance("#" + JavaFunctionalExpressionSearcher.class.getName());
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.search.JavaFunctionalExpressionSearcher");
/**
* The least number of candidate files with functional expressions that directly scanning them becomes expensive
* and more advanced ways of searching become necessary: e.g. first searching for methods where the functional interface class is used
@@ -104,22 +105,37 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
if (descriptor == null) return;
MultiMap<Location, GlobalSearchScope> queries = collectQueryKeys(descriptor, highLevelModules);
for (PsiFunctionalExpression expression : getCandidates(descriptor, queries)) {
if (!processExpression(consumer, funInterface, expression)) {
for (Map.Entry<VirtualFile, Collection<Integer>> entry : getCandidateOffsets(descriptor, queries).entrySet()) {
if (!processFile(consumer, funInterface, entry.getKey(), entry.getValue())) {
return;
}
}
}
}
private static Boolean processExpression(@NotNull Processor<PsiFunctionalExpression> consumer,
PsiClass aClass,
PsiFunctionalExpression expression) {
private static boolean processFile(@NotNull Processor<PsiFunctionalExpression> consumer,
PsiClass samClass,
VirtualFile vFile, Collection<Integer> offsets) {
return ReadAction.compute(() -> {
if (expression.isValid() &&
InheritanceUtil.isInheritorOrSelf(PsiUtil.resolveClassInType(expression.getFunctionalInterfaceType()), aClass, true)) {
if (!consumer.process(expression)) {
return false;
if (!samClass.isValid()) return true;
PsiFile file = samClass.getManager().findFile(vFile);
if (!(file instanceof PsiJavaFile)) {
LOG.error("Non-java file " + file + "; " + vFile);
return true;
}
for (Integer offset : offsets) {
PsiFunctionalExpression expression = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiFunctionalExpression.class, false);
if (expression == null || expression.getTextRange().getStartOffset() != offset) {
LOG.error("Fun expression not found in " + file + " at " + offset);
continue;
}
if (InheritanceUtil.isInheritorOrSelf(PsiUtil.resolveClassInType(expression.getFunctionalInterfaceType()), samClass, true)) {
if (!consumer.process(expression)) {
return false;
}
}
}
@@ -128,30 +144,49 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
}
@NotNull
private static Collection<? extends PsiFunctionalExpression> getCandidates(SamDescriptor descriptor, MultiMap<Location, GlobalSearchScope> queries) {
MultiMap<PsiFile, PsiFunctionalExpression> exprs = MultiMap.createLinked();
for (Map.Entry<Location, Collection<GlobalSearchScope>> entry : queries.entrySet()) {
ReadAction.run(() -> {
ProgressManager.checkCanceled();
GlobalSearchScope combinedScope = descriptor.useScope.intersectWith(
GlobalSearchScope.union(entry.getValue().toArray(new GlobalSearchScope[0])));
for (FunctionalExpressionKey key : descriptor.generateKeys(entry.getKey())) {
StubIndex.getInstance().processElements(JavaStubIndexKeys.FUNCTIONAL_EXPRESSIONS, key,
descriptor.samClass.getProject(), combinedScope, null,
PsiFunctionalExpression.class,
expression -> {
exprs.putValue(expression.getContainingFile(), expression);
return true;
});
}
});
private static MultiMap<VirtualFile, Integer> getCandidateOffsets(SamDescriptor descriptor, MultiMap<Location, GlobalSearchScope> queries) {
MultiMap<VirtualFile, Integer> result = MultiMap.create();
for (Location location : queries.keySet()) {
MultiMap<VirtualFile, Integer> offsets = getOffsetsForLocation(descriptor, location, queries.get(location));
result.putAllValues(offsets);
if (LOG.isDebugEnabled()) {
logIfLarge(offsets, location);
}
}
Collection<? extends PsiFunctionalExpression> result = exprs.values();
LOG.info("checking " + result.size() + " fun-expressions in " + exprs.size() + " files");
if (LOG.isDebugEnabled()) {
LOG.debug("checking " + result.values().size() + " fun-expressions in " + result.keySet().size() + " files");
}
return result;
}
private static void logIfLarge(MultiMap<VirtualFile, Integer> offsets, Location location) {
int delta = offsets.values().size();
if (delta > 5) {
String sample = offsets.entrySet().stream().limit(10).map(e -> e.getKey().getName() + "->" + e.getValue()).collect(Collectors.joining(", "));
LOG.debug(delta + " expressions for " + location + "; " + sample);
}
}
@NotNull
private static MultiMap<VirtualFile, Integer> getOffsetsForLocation(SamDescriptor descriptor, Location location, Collection<GlobalSearchScope> scopes) {
MultiMap<VirtualFile, Integer> map = MultiMap.create();
ReadAction.run(() -> {
ProgressManager.checkCanceled();
GlobalSearchScope combinedScope = descriptor.useScope.intersectWith(
GlobalSearchScope.union(scopes.toArray(new GlobalSearchScope[0])));
for (FunctionalExpressionKey key : descriptor.generateKeys(location)) {
FileBasedIndex.getInstance().processValues(JavaFunctionalExpressionIndex.INDEX_ID, key, null, (file, offsets) -> {
for (int i : offsets.toNativeArray()) {
map.putValue(file, i);
}
return true;
}, new JavaSourceFilterScope(combinedScope));
}
});
return map;
}
@NotNull
private static MultiMap<Location, GlobalSearchScope> collectQueryKeys(SamDescriptor descriptor, Set<Module> candidateModules) {
MultiMap<Location, GlobalSearchScope> queries = MultiMap.createSet();
@@ -273,7 +308,7 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
Project project = descriptor.samClass.getProject();
index.processElements(key, assertNotNull(descriptor.samClass.getName()), project, descriptor.useScope.intersectWith(visibleFromCandidates), PsiMethod.class, methodProcessor);
index.processElements(key, JavaMethodElementType.TYPE_PARAMETER_PSEUDO_NAME, project, visibleFromCandidates, PsiMethod.class, methodProcessor);
LOG.info("#methods: " + methods.size());
LOG.debug("#methods: " + methods.size());
return methods;
}
});
@@ -282,7 +317,7 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
@NotNull
private static Set<String> collectMethodNamesCalledWithFunExpressions(SamDescriptor descriptor) {
Set<String> usedMethodNames = new HashSet<>();
StubIndex.getInstance().processAllKeys(JavaStubIndexKeys.FUNCTIONAL_EXPRESSIONS, key -> {
FileBasedIndex.getInstance().processAllKeys(JavaFunctionalExpressionIndex.INDEX_ID, key -> {
ProgressManager.checkCanceled();
if (key.canRepresent(descriptor.samParamCount, descriptor.booleanCompatible, descriptor.isVoid) &&
key.location instanceof CallLocation) {
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.psi.impl.java.stubs;
package com.intellij.psi.impl.java;
import com.intellij.lang.LighterAST;
import com.intellij.lang.LighterASTNode;
@@ -17,31 +17,14 @@ package com.intellij.psi.impl.java.stubs;
import com.intellij.lang.LighterAST;
import com.intellij.lang.LighterASTNode;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiBinaryExpression;
import com.intellij.psi.PsiFunctionalExpression;
import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey.CoarseType;
import com.intellij.psi.impl.java.stubs.index.JavaStubIndexKeys;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.JavaLightTreeUtil;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.LightTreeUtil;
import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor;
import com.intellij.psi.stubs.IndexSink;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.stubs.StubInputStream;
import com.intellij.psi.stubs.StubOutputStream;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.intellij.psi.impl.source.tree.JavaElementType.*;
public abstract class FunctionalExpressionElementType<T extends PsiFunctionalExpression> extends JavaStubElementType<FunctionalExpressionStub<T>,T> {
public FunctionalExpressionElementType(String debugName) {
@@ -50,211 +33,20 @@ public abstract class FunctionalExpressionElementType<T extends PsiFunctionalExp
@Override
public void serialize(@NotNull FunctionalExpressionStub<T> stub, @NotNull StubOutputStream dataStream) throws IOException {
stub.getIndexKey().serializeKey(dataStream);
}
@NotNull
@Override
public FunctionalExpressionStub<T> deserialize(@NotNull StubInputStream dataStream, StubElement parentStub) throws IOException {
return new FunctionalExpressionStub<T>(parentStub, this, FunctionalExpressionKey.deserializeKey(dataStream));
return new FunctionalExpressionStub<T>(parentStub, this);
}
@Override
public void indexStub(@NotNull FunctionalExpressionStub<T> stub, @NotNull IndexSink sink) {
sink.occurrence(JavaStubIndexKeys.FUNCTIONAL_EXPRESSIONS, stub.getIndexKey());
}
@Override
public FunctionalExpressionStub<T> createStub(LighterAST tree, LighterASTNode funExpr, StubElement parentStub) {
return new FunctionalExpressionStub<T>(parentStub, this,
new FunctionalExpressionKey(getFunExprParameterCount(tree, funExpr),
calcType(tree, funExpr),
calcLocation(tree, funExpr)));
}
@NotNull
private static FunctionalExpressionKey.Location calcLocation(LighterAST tree, LighterASTNode funExpr) {
LighterASTNode call = getContainingCall(tree, funExpr);
List<LighterASTNode> args = JavaLightTreeUtil.getArgList(tree, call);
int argIndex = args == null ? -1 : getArgIndex(args, funExpr);
String methodName = call == null ? null : getCalledMethodName(tree, call);
return methodName == null || argIndex < 0
? createTypedLocation(tree, funExpr)
: new FunctionalExpressionKey.CallLocation(methodName, args.size(), argIndex, StreamApiDetector.isStreamApiCall(tree, call));
}
@NotNull
private static FunctionalExpressionKey.Location createTypedLocation(LighterAST tree, LighterASTNode funExpr) {
LighterASTNode scope = skipExpressionsUp(tree, funExpr, TokenSet.create(LOCAL_VARIABLE, FIELD, TYPE_CAST_EXPRESSION, RETURN_STATEMENT));
if (scope != null) {
if (scope.getTokenType() == RETURN_STATEMENT) {
scope = LightTreeUtil.getParentOfType(tree, scope,
TokenSet.create(METHOD),
TokenSet.orSet(ElementType.MEMBER_BIT_SET, TokenSet.create(LAMBDA_EXPRESSION)));
}
LighterASTNode typeElement = LightTreeUtil.firstChildOfType(tree, scope, TYPE);
String typeText = JavaLightTreeUtil.getNameIdentifierText(tree, LightTreeUtil.firstChildOfType(tree, typeElement, JAVA_CODE_REFERENCE));
if (typeText != null) {
return new FunctionalExpressionKey.TypedLocation(typeText);
}
}
return FunctionalExpressionKey.Location.UNKNOWN;
}
private static int getArgIndex(List<LighterASTNode> args, LighterASTNode expr) {
for (int i = 0; i < args.size(); i++) {
if (args.get(i).getEndOffset() >= expr.getEndOffset()) {
return i;
}
}
return -1;
}
private static CoarseType calcType(final LighterAST tree, LighterASTNode funExpr) {
if (funExpr.getTokenType() == METHOD_REF_EXPRESSION) return CoarseType.UNKNOWN;
LighterASTNode block = LightTreeUtil.firstChildOfType(tree, funExpr, CODE_BLOCK);
if (block == null) {
LighterASTNode expr = findExpressionChild(funExpr, tree);
return isBooleanExpression(tree, expr) ? CoarseType.BOOLEAN : CoarseType.UNKNOWN;
}
final Ref<Boolean> returnsSomething = Ref.create(null);
final AtomicBoolean isBoolean = new AtomicBoolean();
final AtomicBoolean hasStatements = new AtomicBoolean();
new RecursiveLighterASTNodeWalkingVisitor(tree) {
@Override
public void visitNode(@NotNull LighterASTNode element) {
IElementType type = element.getTokenType();
if (type == LAMBDA_EXPRESSION || ElementType.MEMBER_BIT_SET.contains(type)) {
return;
}
if (type == RETURN_STATEMENT) {
LighterASTNode expr = findExpressionChild(element, tree);
returnsSomething.set(expr != null);
if (isBooleanExpression(tree, expr)) {
isBoolean.set(true);
}
return;
}
if (type == EXPRESSION_STATEMENT) {
hasStatements.set(true);
}
super.visitNode(element);
}
}.visitNode(block);
if (isBoolean.get()) {
return CoarseType.BOOLEAN;
}
if (returnsSomething.isNull()) {
return hasStatements.get() ? CoarseType.VOID : CoarseType.UNKNOWN;
}
return returnsSomething.get() ? CoarseType.NON_VOID : CoarseType.VOID;
}
private static LighterASTNode findExpressionChild(@NotNull LighterASTNode element, LighterAST tree) {
return LightTreeUtil.firstChildOfType(tree, element, ElementType.EXPRESSION_BIT_SET);
}
private static boolean isBooleanExpression(LighterAST tree, @Nullable LighterASTNode expr) {
if (expr == null) return false;
IElementType type = expr.getTokenType();
if (type == LITERAL_EXPRESSION) {
IElementType child = tree.getChildren(expr).get(0).getTokenType();
return child == JavaTokenType.TRUE_KEYWORD || child == JavaTokenType.FALSE_KEYWORD;
}
if (type == POLYADIC_EXPRESSION || type == BINARY_EXPRESSION) {
return LightTreeUtil.firstChildOfType(tree, expr, PsiBinaryExpression.BOOLEAN_OPERATION_TOKENS) != null;
}
if (type == PREFIX_EXPRESSION) {
return tree.getChildren(expr).get(0).getTokenType() == JavaTokenType.EXCL;
}
if (type == PARENTH_EXPRESSION) {
return isBooleanExpression(tree, findExpressionChild(expr, tree));
}
if (type == CONDITIONAL_EXPRESSION) {
List<LighterASTNode> children = LightTreeUtil.getChildrenOfType(tree, expr, ElementType.EXPRESSION_BIT_SET);
return children.size() == 3 && (isBooleanExpression(tree, children.get(1)) || isBooleanExpression(tree, children.get(2)));
}
return false;
}
private static int getFunExprParameterCount(LighterAST tree, LighterASTNode funExpr) {
if (funExpr.getTokenType() == METHOD_REF_EXPRESSION) return FunctionalExpressionKey.UNKNOWN_PARAM_COUNT;
assert funExpr.getTokenType() == LAMBDA_EXPRESSION;
LighterASTNode paramList = LightTreeUtil.firstChildOfType(tree, funExpr, PARAMETER_LIST);
assert paramList != null;
return LightTreeUtil.getChildrenOfType(tree, paramList, Constants.PARAMETER_BIT_SET).size();
}
@Nullable
private static String getCalledMethodName(LighterAST tree, LighterASTNode call) {
if (call.getTokenType() == NEW_EXPRESSION) {
LighterASTNode anonClass = LightTreeUtil.firstChildOfType(tree, call, ANONYMOUS_CLASS);
LighterASTNode ref = LightTreeUtil.firstChildOfType(tree, anonClass != null ? anonClass : call, JAVA_CODE_REFERENCE);
return ref == null ? null : JavaLightTreeUtil.getNameIdentifierText(tree, ref);
}
LighterASTNode methodExpr = tree.getChildren(call).get(0);
if (LightTreeUtil.firstChildOfType(tree, methodExpr, JavaTokenType.SUPER_KEYWORD) != null) {
return getSuperClassName(tree, call);
}
if (LightTreeUtil.firstChildOfType(tree, methodExpr, JavaTokenType.THIS_KEYWORD) != null) {
return JavaLightTreeUtil.getNameIdentifierText(tree, findClass(tree, call));
}
return JavaLightTreeUtil.getNameIdentifierText(tree, methodExpr);
}
@Nullable
private static String getSuperClassName(LighterAST tree, LighterASTNode call) {
LighterASTNode aClass = findClass(tree, call);
LighterASTNode extendsList = LightTreeUtil.firstChildOfType(tree, aClass, EXTENDS_LIST);
return JavaLightTreeUtil.getNameIdentifierText(tree, LightTreeUtil.firstChildOfType(tree, extendsList, JAVA_CODE_REFERENCE));
}
@Nullable
private static LighterASTNode getContainingCall(LighterAST tree, LighterASTNode node) {
LighterASTNode expressionList = skipExpressionsUp(tree, node, TokenSet.create(EXPRESSION_LIST));
if (expressionList != null) {
LighterASTNode parent = tree.getParent(expressionList);
if (parent != null && parent.getTokenType() == ANONYMOUS_CLASS) {
parent = tree.getParent(parent);
}
if (parent != null && (parent.getTokenType() == METHOD_CALL_EXPRESSION || parent.getTokenType() == NEW_EXPRESSION)) {
return parent;
}
}
return null;
}
private static LighterASTNode skipExpressionsUp(LighterAST tree, @NotNull LighterASTNode node, TokenSet types) {
node = tree.getParent(node);
while (node != null) {
final IElementType type = node.getTokenType();
if (types.contains(type)) return node;
if (type != PARENTH_EXPRESSION && type != CONDITIONAL_EXPRESSION) return null;
node = tree.getParent(node);
}
return null;
}
private static LighterASTNode findClass(LighterAST tree, LighterASTNode node) {
while (node != null) {
final IElementType type = node.getTokenType();
if (type == CLASS) return node;
node = tree.getParent(node);
}
return null;
return new FunctionalExpressionStub<T>(parentStub, this);
}
}
@@ -19,19 +19,11 @@ import com.intellij.psi.PsiFunctionalExpression;
import com.intellij.psi.stubs.IStubElementType;
import com.intellij.psi.stubs.StubBase;
import com.intellij.psi.stubs.StubElement;
import org.jetbrains.annotations.NotNull;
public class FunctionalExpressionStub<T extends PsiFunctionalExpression> extends StubBase<T> {
@NotNull private final FunctionalExpressionKey myIndexKey;
protected FunctionalExpressionStub(StubElement parent,
IStubElementType elementType, @NotNull FunctionalExpressionKey indexKey) {
protected FunctionalExpressionStub(StubElement parent, IStubElementType elementType) {
super(parent, elementType);
myIndexKey = indexKey;
}
@NotNull
public FunctionalExpressionKey getIndexKey() {
return myIndexKey;
}
}
@@ -16,7 +16,6 @@
package com.intellij.psi.impl.java.stubs.index;
import com.intellij.psi.*;
import com.intellij.psi.impl.java.stubs.FunctionalExpressionKey;
import com.intellij.psi.stubs.StubIndexKey;
/**
@@ -32,7 +31,6 @@ public class JavaStubIndexKeys {
public static final StubIndexKey<String, PsiAnonymousClass> ANONYMOUS_BASEREF = StubIndexKey.createIndexKey("java.anonymous.baseref");
public static final StubIndexKey<String, PsiMethod> METHOD_TYPES = StubIndexKey.createIndexKey("java.method.parameter.types");
public static final StubIndexKey<String, PsiClass> CLASS_SHORT_NAMES = StubIndexKey.createIndexKey("java.class.shortname");
public static final StubIndexKey<FunctionalExpressionKey, PsiFunctionalExpression> FUNCTIONAL_EXPRESSIONS = StubIndexKey.createIndexKey("java.functional.expressions");
public static final StubIndexKey<Integer, PsiClass> CLASS_FQN = StubIndexKey.createIndexKey("java.class.fqn");
public static final StubIndexKey<String, PsiJavaModule> MODULE_NAMES = StubIndexKey.createIndexKey("java.module.name");
@@ -38,7 +38,7 @@ import java.io.IOException;
* @author max
*/
public class JavaFileElementType extends ILightStubFileElementType<PsiJavaFileStub> {
public static final int STUB_VERSION = 34;
public static final int STUB_VERSION = 35;
public JavaFileElementType() {
super("java.FILE", JavaLanguage.INSTANCE);
@@ -56,6 +56,10 @@ public class JavaFileElementType extends ILightStubFileElementType<PsiJavaFileSt
@Override
public boolean shouldBuildStubFor(final VirtualFile file) {
return isInSourceContent(file);
}
public static boolean isInSourceContent(@NotNull VirtualFile file) {
final VirtualFile dir = file.getParent();
return dir == null || dir.getUserData(LanguageLevel.KEY) != null;
}
+1 -1
View File
@@ -1541,8 +1541,8 @@
<stubIndex implementation="com.intellij.psi.impl.java.stubs.index.JavaSuperClassNameOccurenceIndex"/>
<stubIndex implementation="com.intellij.psi.impl.java.stubs.index.JavaMethodParameterTypesIndex"/>
<stubIndex implementation="com.intellij.psi.impl.java.stubs.index.JavaModuleNameIndex"/>
<stubIndex implementation="com.intellij.psi.impl.java.stubs.index.JavaFunctionalExpressionIndex"/>
<fileBasedIndex implementation="com.intellij.psi.impl.java.JavaFunctionalExpressionIndex"/>
<fileBasedIndex implementation="com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisIndex"/>
<fileBasedIndex implementation="com.intellij.psi.RefQueueIndex"/>