mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-05-06 05:10:22 +07:00
speedup functional expression search (IDEA-159107)
* filter inapplicable expressions without loading AST if possible, via approximate resolve * for that, store the approximate call chains in index * iterate over files once, not for each empty marker Runnable interface separately * don't rely on generic method parameter index: it's huge, memory-hungry and works only in Java
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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.google.common.base.MoreObjects;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.search.ApproximateResolver;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.io.DataInputOutputUtil;
|
||||
import com.intellij.util.io.IOUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class FunExprOccurrence {
|
||||
public final int funExprOffset;
|
||||
private final int argIndex;
|
||||
private final List<ReferenceChainLink> referenceContext;
|
||||
|
||||
public FunExprOccurrence(int funExprOffset,
|
||||
int argIndex,
|
||||
List<ReferenceChainLink> referenceContext) {
|
||||
this.funExprOffset = funExprOffset;
|
||||
this.argIndex = argIndex;
|
||||
this.referenceContext = referenceContext;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof FunExprOccurrence)) return false;
|
||||
|
||||
FunExprOccurrence that = (FunExprOccurrence)o;
|
||||
|
||||
if (funExprOffset != that.funExprOffset) return false;
|
||||
if (argIndex != that.argIndex) return false;
|
||||
if (!referenceContext.equals(that.referenceContext)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = funExprOffset;
|
||||
result = 31 * result + argIndex;
|
||||
result = 31 * result + referenceContext.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("offset", funExprOffset)
|
||||
.add("argIndex", argIndex)
|
||||
.add("chain", referenceContext)
|
||||
.toString();
|
||||
}
|
||||
|
||||
void serialize(DataOutput out) throws IOException {
|
||||
DataInputOutputUtil.writeINT(out, funExprOffset);
|
||||
DataInputOutputUtil.writeINT(out, argIndex);
|
||||
|
||||
DataInputOutputUtil.writeINT(out, referenceContext.size());
|
||||
for (ReferenceChainLink link : referenceContext) {
|
||||
serializeLink(out, link);
|
||||
}
|
||||
}
|
||||
|
||||
static FunExprOccurrence deserialize(DataInput in) throws IOException {
|
||||
int offset = DataInputOutputUtil.readINT(in);
|
||||
int argIndex = DataInputOutputUtil.readINT(in);
|
||||
|
||||
int contextSize = DataInputOutputUtil.readINT(in);
|
||||
List<ReferenceChainLink> context = new ArrayList<>(contextSize);
|
||||
for (int i = 0; i < contextSize; i++) {
|
||||
context.add(deserializeLink(in));
|
||||
}
|
||||
return new FunExprOccurrence(offset, argIndex, context);
|
||||
}
|
||||
|
||||
private static void serializeLink(DataOutput out, ReferenceChainLink link) throws IOException {
|
||||
IOUtil.writeUTF(out, link.referenceName);
|
||||
out.writeBoolean(link.isCall);
|
||||
if (link.isCall) {
|
||||
DataInputOutputUtil.writeINT(out, link.argCount);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static ReferenceChainLink deserializeLink(DataInput in) throws IOException {
|
||||
String referenceName = IOUtil.readUTF(in);
|
||||
boolean isCall = in.readBoolean();
|
||||
return new ReferenceChainLink(referenceName, isCall, isCall ? DataInputOutputUtil.readINT(in) : -1);
|
||||
}
|
||||
|
||||
public boolean canHaveType(@NotNull List<PsiClass> samClasses, @NotNull VirtualFile placeFile) {
|
||||
if (referenceContext.isEmpty()) return true;
|
||||
|
||||
Set<PsiClass> qualifiers = null;
|
||||
for (int i = 0; i < referenceContext.size(); i++) {
|
||||
ReferenceChainLink link = referenceContext.get(i);
|
||||
List<? extends PsiMember> candidates = i == 0 ? link.getGlobalMembers(placeFile, samClasses.get(0).getProject())
|
||||
: link.getSymbolMembers(qualifiers);
|
||||
if (candidates == null) return true;
|
||||
|
||||
if (i == referenceContext.size() - 1) {
|
||||
return ContainerUtil.exists(candidates, m -> isCompatible(link, m, samClasses));
|
||||
}
|
||||
qualifiers = ApproximateResolver.getDefiniteSymbolTypes(candidates);
|
||||
if (qualifiers == null) return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isCompatible(ReferenceChainLink link, PsiMember member, List<PsiClass> samClasses) {
|
||||
if (link.isCall) {
|
||||
return member instanceof PsiMethod && hasCompatibleParameter((PsiMethod)member, argIndex, samClasses);
|
||||
}
|
||||
if (member instanceof PsiClass) {
|
||||
return ContainerUtil.exists(samClasses, c -> InheritanceUtil.isInheritorOrSelf((PsiClass)member, c, true));
|
||||
}
|
||||
return member instanceof PsiField &&
|
||||
ContainerUtil.exists(samClasses, c -> canPassFunctionalExpression(c, ((PsiField)member).getType()));
|
||||
}
|
||||
|
||||
public static boolean hasCompatibleParameter(PsiMethod method, int argIndex, List<PsiClass> samClasses) {
|
||||
PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
int paramIndex = method.isVarArgs() ? Math.min(argIndex, parameters.length - 1) : argIndex;
|
||||
return paramIndex < parameters.length &&
|
||||
ContainerUtil.exists(samClasses, c -> canPassFunctionalExpression(c, parameters[paramIndex].getType()));
|
||||
}
|
||||
|
||||
private static boolean canPassFunctionalExpression(PsiClass sam, PsiType paramType) {
|
||||
if (paramType instanceof PsiEllipsisType) {
|
||||
paramType = ((PsiEllipsisType)paramType).getComponentType();
|
||||
}
|
||||
PsiClass functionalCandidate = PsiUtil.resolveClassInClassTypeOnly(paramType);
|
||||
if (functionalCandidate instanceof PsiTypeParameter) {
|
||||
return InheritanceUtil.isInheritorOrSelf(sam, PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(paramType)), true);
|
||||
}
|
||||
|
||||
return InheritanceUtil.isInheritorOrSelf(functionalCandidate, sam, true);
|
||||
}
|
||||
}
|
||||
@@ -20,11 +20,13 @@ 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.util.text.StringUtil;
|
||||
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.FileLocalResolver;
|
||||
import com.intellij.psi.impl.source.JavaFileElementType;
|
||||
import com.intellij.psi.impl.source.JavaLightTreeUtil;
|
||||
import com.intellij.psi.impl.source.tree.ElementType;
|
||||
@@ -32,26 +34,26 @@ 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.SmartList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.JBIterable;
|
||||
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.*;
|
||||
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");
|
||||
public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<FunctionalExpressionKey, List<FunExprOccurrence>> implements PsiDependentIndex {
|
||||
public static final ID<FunctionalExpressionKey, List<FunExprOccurrence>> INDEX_ID = ID.create("java.fun.expression");
|
||||
private static final KeyDescriptor<FunctionalExpressionKey> KEY_DESCRIPTOR = new KeyDescriptor<FunctionalExpressionKey>() {
|
||||
@Override
|
||||
public int getHashCode(FunctionalExpressionKey value) {
|
||||
@@ -75,33 +77,100 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
|
||||
};
|
||||
|
||||
@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));
|
||||
private static List<ReferenceChainLink> createCallChain(FileLocalResolver resolver, @Nullable LighterASTNode expr) {
|
||||
List<ReferenceChainLink> chain = new ArrayList<>();
|
||||
while (true) {
|
||||
if (expr == null) return reversedChain(chain);
|
||||
if (expr.getTokenType() == PARENTH_EXPRESSION) {
|
||||
expr = LightTreeUtil.firstChildOfType(resolver.getLightTree(), expr, ElementType.EXPRESSION_BIT_SET);
|
||||
continue;
|
||||
}
|
||||
if (expr.getTokenType() == TYPE_CAST_EXPRESSION) {
|
||||
String typeName = resolver.getShortClassTypeName(expr);
|
||||
ContainerUtil.addIfNotNull(chain, typeName != null ? new ReferenceChainLink(typeName, false, -1) : null);
|
||||
return reversedChain(chain);
|
||||
}
|
||||
|
||||
boolean isCall = expr.getTokenType() == METHOD_CALL_EXPRESSION || expr.getTokenType() == NEW_EXPRESSION;
|
||||
String referenceName = getReferencedMemberName(resolver.getLightTree(), expr, isCall);
|
||||
if (referenceName == null) return reversedChain(chain);
|
||||
|
||||
LighterASTNode qualifier = getQualifier(resolver.getLightTree(), expr, isCall);
|
||||
if (qualifier == null) {
|
||||
ContainerUtil.addIfNotNull(chain, createChainStart(resolver, expr, isCall, referenceName));
|
||||
return reversedChain(chain);
|
||||
}
|
||||
|
||||
chain.add(new ReferenceChainLink(referenceName, isCall, getArgCount(resolver.getLightTree(), expr)));
|
||||
expr = qualifier;
|
||||
}
|
||||
}
|
||||
|
||||
@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));
|
||||
private static List<ReferenceChainLink> reversedChain(List<ReferenceChainLink> chain) {
|
||||
Collections.reverse(chain);
|
||||
return chain;
|
||||
}
|
||||
|
||||
private static int getArgCount(LighterAST tree, LighterASTNode expr) {
|
||||
List<LighterASTNode> args = JavaLightTreeUtil.getArgList(tree, expr);
|
||||
return args == null ? -1 : args.size();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static LighterASTNode getQualifier(LighterAST tree, LighterASTNode expr, boolean isCall) {
|
||||
LighterASTNode qualifier = tree.getChildren(expr).get(0);
|
||||
if (isCall) {
|
||||
List<LighterASTNode> children = tree.getChildren(qualifier);
|
||||
qualifier = children.isEmpty() ? null : children.get(0);
|
||||
}
|
||||
return qualifier != null && ElementType.EXPRESSION_BIT_SET.contains(qualifier.getTokenType()) ? qualifier : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getReferencedMemberName(LighterAST tree, LighterASTNode expr, boolean isCall) {
|
||||
if (isCall) {
|
||||
return getCalledMethodName(tree, expr);
|
||||
}
|
||||
if (expr.getTokenType() == REFERENCE_EXPRESSION) {
|
||||
return JavaLightTreeUtil.getNameIdentifierText(tree, expr);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ReferenceChainLink createChainStart(FileLocalResolver resolver,
|
||||
LighterASTNode expr,
|
||||
boolean isCall,
|
||||
String referenceName) {
|
||||
if (!isCall) {
|
||||
FileLocalResolver.LightResolveResult result = resolver.resolveLocally(expr);
|
||||
if (result == FileLocalResolver.LightResolveResult.UNKNOWN) return null;
|
||||
|
||||
LighterASTNode target = result.getTarget();
|
||||
if (target != null) {
|
||||
String typeName = resolver.getShortClassTypeName(target);
|
||||
return typeName != null ? new ReferenceChainLink(typeName, false, -1) : null;
|
||||
}
|
||||
}
|
||||
return new ReferenceChainLink(referenceName, isCall, getArgCount(resolver.getLightTree(), expr));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String calcExprType(LighterASTNode funExpr, FileLocalResolver resolver) {
|
||||
LighterASTNode scope = skipExpressionsUp(resolver.getLightTree(), funExpr, TokenSet.create(LOCAL_VARIABLE, FIELD, TYPE_CAST_EXPRESSION, RETURN_STATEMENT, ASSIGNMENT_EXPRESSION));
|
||||
if (scope != null) {
|
||||
if (scope.getTokenType() == RETURN_STATEMENT) {
|
||||
scope = LightTreeUtil.getParentOfType(tree, scope,
|
||||
if (scope.getTokenType() == ASSIGNMENT_EXPRESSION) {
|
||||
LighterASTNode lValue = findExpressionChild(scope, resolver.getLightTree());
|
||||
scope = lValue == null ? null : resolver.resolveLocally(lValue).getTarget();
|
||||
}
|
||||
else if (scope.getTokenType() == RETURN_STATEMENT) {
|
||||
scope = LightTreeUtil.getParentOfType(resolver.getLightTree(), 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;
|
||||
return StringUtil.notNullize(scope == null ? null : resolver.getShortClassTypeName(scope));
|
||||
}
|
||||
|
||||
private static int getArgIndex(List<LighterASTNode> args, LighterASTNode expr) {
|
||||
@@ -113,7 +182,7 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static FunctionalExpressionKey.CoarseType calcType(final LighterAST tree, LighterASTNode funExpr) {
|
||||
private static FunctionalExpressionKey.CoarseType calcReturnType(final LighterAST tree, LighterASTNode funExpr) {
|
||||
if (funExpr.getTokenType() == METHOD_REF_EXPRESSION) return FunctionalExpressionKey.CoarseType.UNKNOWN;
|
||||
|
||||
LighterASTNode block = LightTreeUtil.firstChildOfType(tree, funExpr, CODE_BLOCK);
|
||||
@@ -161,6 +230,7 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
|
||||
return returnsSomething.get() ? FunctionalExpressionKey.CoarseType.NON_VOID : FunctionalExpressionKey.CoarseType.VOID;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static LighterASTNode findExpressionChild(@NotNull LighterASTNode element, LighterAST tree) {
|
||||
return LightTreeUtil.firstChildOfType(tree, element, ElementType.EXPRESSION_BIT_SET);
|
||||
}
|
||||
@@ -221,8 +291,12 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
|
||||
@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));
|
||||
return getReferenceName(tree, LightTreeUtil.firstChildOfType(tree, aClass, EXTENDS_LIST));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getReferenceName(LighterAST tree, LighterASTNode refParent) {
|
||||
return JavaLightTreeUtil.getNameIdentifierText(tree, LightTreeUtil.firstChildOfType(tree, refParent, JAVA_CODE_REFERENCE));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -252,12 +326,7 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
|
||||
}
|
||||
|
||||
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 JBIterable.generate(node, tree::getParent).find(n -> n.getTokenType() == CLASS);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -268,35 +337,38 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ID<FunctionalExpressionKey, TIntArrayList> getName() {
|
||||
public ID<FunctionalExpressionKey, List<FunExprOccurrence>> getName() {
|
||||
return INDEX_ID;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public DataIndexer<FunctionalExpressionKey, TIntArrayList, FileContent> getIndexer() {
|
||||
public DataIndexer<FunctionalExpressionKey, List<FunExprOccurrence>, FileContent> getIndexer() {
|
||||
return inputData -> {
|
||||
Map<FunctionalExpressionKey, TIntArrayList> result = new HashMap<>();
|
||||
CharSequence text = inputData.getContentAsText();
|
||||
if (!StringUtil.contains(text, "->") && !StringUtil.contains(text, "::")) return Collections.emptyMap();
|
||||
|
||||
Map<FunctionalExpressionKey, List<FunExprOccurrence>> result = new HashMap<>();
|
||||
LighterAST tree = ((FileContentImpl)inputData).getLighterASTForPsiDependentIndex();
|
||||
FileLocalResolver resolver = new FileLocalResolver(tree);
|
||||
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);
|
||||
calcReturnType(tree, element),
|
||||
calcExprType(element, resolver));
|
||||
List<FunExprOccurrence> list = result.get(key);
|
||||
if (list == null) {
|
||||
result.put(key, list = new TIntArrayList());
|
||||
result.put(key, list = new SmartList<>());
|
||||
}
|
||||
list.add(element.getStartOffset());
|
||||
list.add(createOccurrence(element, resolver));
|
||||
}
|
||||
|
||||
super.visitNode(element);
|
||||
@@ -307,24 +379,43 @@ public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<Funct
|
||||
};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static FunExprOccurrence createOccurrence(@NotNull LighterASTNode funExpr, FileLocalResolver resolver) {
|
||||
LighterAST tree = resolver.getLightTree();
|
||||
LighterASTNode containingCall = getContainingCall(tree, funExpr);
|
||||
List<LighterASTNode> args = JavaLightTreeUtil.getArgList(tree, containingCall);
|
||||
int argIndex = args == null ? -1 : getArgIndex(args, funExpr);
|
||||
|
||||
LighterASTNode chainExpr = containingCall;
|
||||
if (chainExpr == null) {
|
||||
LighterASTNode assignment = skipExpressionsUp(tree, funExpr, TokenSet.create(ASSIGNMENT_EXPRESSION));
|
||||
if (assignment != null) {
|
||||
chainExpr = findExpressionChild(assignment, tree);
|
||||
}
|
||||
}
|
||||
|
||||
return new FunExprOccurrence(funExpr.getStartOffset(), argIndex,
|
||||
createCallChain(resolver, chainExpr));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public DataExternalizer<TIntArrayList> getValueExternalizer() {
|
||||
return new DataExternalizer<TIntArrayList>() {
|
||||
public DataExternalizer<List<FunExprOccurrence>> getValueExternalizer() {
|
||||
return new DataExternalizer<List<FunExprOccurrence>>() {
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, TIntArrayList value) throws IOException {
|
||||
public void save(@NotNull DataOutput out, List<FunExprOccurrence> value) throws IOException {
|
||||
DataInputOutputUtil.writeINT(out, value.size());
|
||||
for (int i = 0; i < value.size(); i++) {
|
||||
DataInputOutputUtil.writeINT(out, value.get(i));
|
||||
for (FunExprOccurrence info : value) {
|
||||
info.serialize(out);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TIntArrayList read(@NotNull DataInput in) throws IOException {
|
||||
public List<FunExprOccurrence> read(@NotNull DataInput in) throws IOException {
|
||||
int length = DataInputOutputUtil.readINT(in);
|
||||
TIntArrayList list = new TIntArrayList(length);
|
||||
List<FunExprOccurrence> list = new SmartList<FunExprOccurrence>();
|
||||
for (int i = 0; i < length; i++) {
|
||||
list.add(DataInputOutputUtil.readINT(in));
|
||||
list.add(FunExprOccurrence.deserialize(in));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.ResolveScopeManager;
|
||||
import com.intellij.psi.impl.search.ApproximateResolver;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.PsiShortNamesCache;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class ReferenceChainLink {
|
||||
final String referenceName;
|
||||
final boolean isCall;
|
||||
final int argCount;
|
||||
|
||||
public ReferenceChainLink(@NotNull String referenceName, boolean isCall, int argCount) {
|
||||
this.referenceName = referenceName;
|
||||
this.isCall = isCall;
|
||||
this.argCount = argCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ReferenceChainLink)) return false;
|
||||
|
||||
ReferenceChainLink link = (ReferenceChainLink)o;
|
||||
|
||||
if (isCall != link.isCall) return false;
|
||||
if (argCount != link.argCount) return false;
|
||||
if (!referenceName.equals(link.referenceName)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = referenceName.hashCode();
|
||||
result = 31 * result + (isCall ? 1 : 0);
|
||||
result = 31 * result + argCount;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return referenceName + (isCall ? "(" + argCount + ")" : "");
|
||||
}
|
||||
|
||||
private static final Key<Set<ReferenceChainLink>> EXPENSIVE_LINKS = Key.create("EXPENSIVE_CHAIN_LINKS");
|
||||
|
||||
|
||||
@Nullable
|
||||
List<PsiMember> getGlobalMembers(VirtualFile placeFile, Project project) {
|
||||
if (isExpensive(project)) return null;
|
||||
|
||||
List<PsiMember> candidates = new ArrayList<>();
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
Processor<PsiMember> processor = member -> {
|
||||
if (canBeAccessible(placeFile, member) && (!(member instanceof PsiMethod) ||
|
||||
ApproximateResolver.canHaveArgCount((PsiMethod)member, argCount))) {
|
||||
candidates.add(member);
|
||||
}
|
||||
return count.incrementAndGet() < 42;
|
||||
};
|
||||
PsiShortNamesCache cache = PsiShortNamesCache.getInstance(project);
|
||||
GlobalSearchScope scope = ResolveScopeManager.getInstance(project).getDefaultResolveScope(placeFile);
|
||||
if (isCall) {
|
||||
if (!cache.processMethodsWithName(referenceName, processor, scope, null)) {
|
||||
markExpensive(project);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
PsiPackage pkg = JavaPsiFacade.getInstance(project).findPackage(referenceName);
|
||||
if (pkg != null && pkg.getDirectories(scope).length > 0) return null;
|
||||
|
||||
if (!cache.processFieldsWithName(referenceName, processor, scope, null) ||
|
||||
!cache.processClassesWithName(referenceName, processor, scope, null)) {
|
||||
markExpensive(project);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
private static boolean canBeAccessible(VirtualFile placeFile, PsiMember member) {
|
||||
return !member.hasModifierProperty(PsiModifier.PRIVATE) || placeFile.equals(PsiUtilCore.getVirtualFile(member));
|
||||
}
|
||||
|
||||
private boolean isExpensive(Project project) {
|
||||
Set<ReferenceChainLink> expensive = project.getUserData(EXPENSIVE_LINKS);
|
||||
return expensive != null && expensive.contains(this);
|
||||
}
|
||||
|
||||
private void markExpensive(Project project) {
|
||||
Set<ReferenceChainLink> expensive = project.getUserData(EXPENSIVE_LINKS);
|
||||
if (expensive == null) {
|
||||
project.putUserData(EXPENSIVE_LINKS, expensive = ContainerUtil.newConcurrentSet());
|
||||
}
|
||||
expensive.add(this);
|
||||
}
|
||||
|
||||
public List<? extends PsiMember> getSymbolMembers(Set<PsiClass> qualifiers) {
|
||||
return isCall ? ApproximateResolver.getPossibleMethods(qualifiers, referenceName, argCount)
|
||||
: ApproximateResolver.getPossibleNonMethods(qualifiers, referenceName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,16 +18,16 @@ package com.intellij.psi.impl.search;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.JBIterable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
class ApproximateResolver {
|
||||
public class ApproximateResolver {
|
||||
/**
|
||||
* Tries to calculate the type of a given expression using lightweight resolve: no type inference for method calls, no exact candidate selection, works only with raw types.
|
||||
* If it returns a non-empty set, the "real" type class is guaranteed to be present in that set. This method can be used to quickly
|
||||
@@ -43,31 +43,37 @@ class ApproximateResolver {
|
||||
|
||||
expression = PsiUtil.skipParenthesizedExprDown(expression);
|
||||
|
||||
if (expression instanceof PsiTypeCastExpression) return extractClass(expression.getType());
|
||||
return expression instanceof PsiTypeCastExpression ? extractClass(expression.getType()) :
|
||||
expression instanceof PsiMethodCallExpression ? getCallType(expression, maxDepth) :
|
||||
expression instanceof PsiReferenceExpression ? getNonCallType((PsiReferenceExpression)expression, maxDepth) :
|
||||
null;
|
||||
}
|
||||
|
||||
boolean method = false;
|
||||
if (expression instanceof PsiMethodCallExpression) {
|
||||
method = true;
|
||||
expression = ((PsiMethodCallExpression)expression).getMethodExpression();
|
||||
@Nullable
|
||||
private static Set<PsiClass> getCallType(@NotNull PsiExpression expression, int maxDepth) {
|
||||
PsiReferenceExpression ref = ((PsiMethodCallExpression)expression).getMethodExpression();
|
||||
PsiExpression qualifier = ref.getQualifierExpression();
|
||||
if (qualifier == null) return extractClass(expression.getType());
|
||||
|
||||
Set<PsiClass> qualifierType = getPossibleTypes(qualifier, maxDepth - 1);
|
||||
String refName = ref.getReferenceName();
|
||||
int argCount = ((PsiMethodCallExpression)expression).getArgumentList().getExpressions().length;
|
||||
List<PsiMethod> methods = refName == null || qualifierType == null ? null : getPossibleMethods(qualifierType, refName, argCount);
|
||||
return methods == null ? null : getDefiniteSymbolTypes(methods);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Set<PsiClass> getNonCallType(@NotNull PsiReferenceExpression expression, int maxDepth) {
|
||||
PsiExpression qualifier = expression.getQualifierExpression();
|
||||
if (qualifier == null) {
|
||||
PsiElement target = expression.resolve();
|
||||
return target instanceof PsiClass ? Collections.singleton((PsiClass)target) : extractClass(expression.getType());
|
||||
}
|
||||
|
||||
if (!(expression instanceof PsiReferenceExpression)) return null;
|
||||
|
||||
String name = ((PsiReferenceExpression)expression).getReferenceName();
|
||||
if (name == null) return null;
|
||||
|
||||
PsiExpression qualifier = ((PsiReferenceExpression)expression).getQualifierExpression();
|
||||
if (qualifier != null) {
|
||||
Set<PsiClass> qualifierType = getPossibleTypes(qualifier, maxDepth - 1);
|
||||
if (qualifierType == null) return null;
|
||||
|
||||
return method ? getPossibleCallTypes(qualifierType, name) : getPossibleMemberTypes(qualifierType, name);
|
||||
}
|
||||
|
||||
if (method) return null;
|
||||
|
||||
PsiElement target = ((PsiReferenceExpression)expression).resolve();
|
||||
return target instanceof PsiClass ? Collections.singleton((PsiClass)target) : extractClass(expression.getType());
|
||||
Set<PsiClass> qualifierType = getPossibleTypes(qualifier, maxDepth - 1);
|
||||
String refName = expression.getReferenceName();
|
||||
List<? extends PsiMember> members = refName == null || qualifierType == null ? null : getPossibleNonMethods(qualifierType, refName);
|
||||
return members == null ? null : getDefiniteSymbolTypes(members);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -76,32 +82,54 @@ class ApproximateResolver {
|
||||
return psiClass == null || psiClass instanceof PsiTypeParameter ? null : Collections.singleton(psiClass);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Set<PsiClass> getPossibleCallTypes(Set<PsiClass> inClasses, @NotNull String name) {
|
||||
Set<PsiClass> allTypes = ContainerUtil.newHashSet();
|
||||
for (PsiClass aClass : inClasses) {
|
||||
for (PsiMethod method : aClass.findMethodsByName(name, true)) {
|
||||
PsiClass type = PsiUtil.resolveClassInClassTypeOnly(method.getReturnType());
|
||||
if (type == null || type instanceof PsiTypeParameter) return null;
|
||||
allTypes.add(type);
|
||||
}
|
||||
@NotNull
|
||||
public static List<PsiMethod> getPossibleMethods(@NotNull Set<PsiClass> symbols, @NotNull String name, int callArgCount) {
|
||||
return JBIterable.from(symbols).
|
||||
flatMap(sym -> Arrays.asList(sym.findMethodsByName(name, true))).
|
||||
filter(m -> canHaveArgCount(m, callArgCount)).
|
||||
toList();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<PsiMember> getPossibleNonMethods(@NotNull Set<PsiClass> symbols, @NotNull String name) {
|
||||
List<PsiMember> result = new ArrayList<>();
|
||||
for (PsiClass sym : symbols) {
|
||||
ContainerUtil.addIfNotNull(result, sym.findFieldByName(name, true));
|
||||
ContainerUtil.addIfNotNull(result, sym.findInnerClassByName(name, true));
|
||||
}
|
||||
return allTypes;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Set<PsiClass> getPossibleMemberTypes(Set<PsiClass> inClasses, @NotNull String name) {
|
||||
Set<PsiClass> allTypes = ContainerUtil.newHashSet();
|
||||
for (PsiClass aClass : inClasses) {
|
||||
PsiField field = aClass.findFieldByName(name, true);
|
||||
if (field != null) {
|
||||
PsiClass fieldType = PsiUtil.resolveClassInClassTypeOnly(field.getType());
|
||||
if (fieldType == null || fieldType instanceof PsiTypeParameter) return null;
|
||||
allTypes.add(fieldType);
|
||||
public static Set<PsiClass> getDefiniteSymbolTypes(@NotNull List<? extends PsiMember> candidates) {
|
||||
Set<PsiClass> possibleTypes = new HashSet<>();
|
||||
for (PsiMember candidate : candidates) {
|
||||
if (candidate instanceof PsiClass) {
|
||||
possibleTypes.add((PsiClass)candidate);
|
||||
}
|
||||
else if (candidate instanceof PsiMethod && ((PsiMethod)candidate).isConstructor()) {
|
||||
ContainerUtil.addIfNotNull(possibleTypes, candidate.getContainingClass());
|
||||
}
|
||||
else {
|
||||
//noinspection ConstantConditions
|
||||
PsiType type = candidate instanceof PsiField ? ((PsiField)candidate).getType() : ((PsiMethod)candidate).getReturnType();
|
||||
if (type instanceof PsiPrimitiveType) continue;
|
||||
if (type instanceof PsiArrayType) {
|
||||
type = PsiType.getJavaLangObject(candidate.getManager(), candidate.getResolveScope());
|
||||
}
|
||||
|
||||
ContainerUtil.addIfNotNull(allTypes, aClass.findInnerClassByName(name, true));
|
||||
PsiClass typeClass = PsiUtil.resolveClassInClassTypeOnly(type);
|
||||
if (typeClass == null || typeClass instanceof PsiTypeParameter) {
|
||||
return null;
|
||||
}
|
||||
possibleTypes.add(typeClass);
|
||||
}
|
||||
}
|
||||
return allTypes;
|
||||
return possibleTypes;
|
||||
}
|
||||
|
||||
public static boolean canHaveArgCount(PsiMethod method, int argCount) {
|
||||
return method.isVarArgs() ? argCount >= method.getParameterList().getParametersCount() - 1
|
||||
: argCount == method.getParameterList().getParametersCount();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
package com.intellij.psi.impl.search;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.openapi.application.AccessToken;
|
||||
import com.intellij.openapi.application.QueryExecutorBase;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
@@ -30,88 +28,148 @@ import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.java.FunExprOccurrence;
|
||||
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.search.*;
|
||||
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
|
||||
import com.intellij.psi.search.searches.FunctionalExpressionSearch;
|
||||
import com.intellij.psi.search.searches.FunctionalExpressionSearch.SearchParameters;
|
||||
import com.intellij.psi.stubs.StubIndex;
|
||||
import com.intellij.psi.stubs.StubIndexKey;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.PairProcessor;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.Processors;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
import com.intellij.util.containers.JBIterable;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.indexing.FileBasedIndex;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static com.intellij.util.ObjectUtils.assertNotNull;
|
||||
|
||||
public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunctionalExpression, FunctionalExpressionSearch.SearchParameters> {
|
||||
public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunctionalExpression, SearchParameters> {
|
||||
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
|
||||
* and then for their usages,
|
||||
*/
|
||||
public static final int SMART_SEARCH_THRESHOLD = 5;
|
||||
private static final java.util.HashSet<String> KNOWN_STREAM_CLASSES =
|
||||
ContainerUtil.newHashSet(Stream.class.getName(),
|
||||
IntStream.class.getName(), DoubleStream.class.getName(), LongStream.class.getName());
|
||||
|
||||
@Override
|
||||
public void processQuery(@NotNull FunctionalExpressionSearch.SearchParameters queryParameters,
|
||||
@NotNull Processor<PsiFunctionalExpression> consumer) {
|
||||
final Set<Module> highLevelModules;
|
||||
final List<PsiClass> funInterfaces;
|
||||
|
||||
try (AccessToken ignored = ReadAction.start()) {
|
||||
PsiClass aClass = queryParameters.getElementToSearch();
|
||||
if (!aClass.isValid() ||
|
||||
!aClass.isInterface() ||
|
||||
InjectedLanguageManager.getInstance(aClass.getProject()).isInjectedFragment(aClass.getContainingFile())) {
|
||||
return;
|
||||
}
|
||||
|
||||
highLevelModules = getJava8Modules(aClass.getProject());
|
||||
if (highLevelModules.isEmpty()) return;
|
||||
|
||||
funInterfaces = ContainerUtil.filter(processSubInterfaces(aClass), LambdaUtil::isFunctionalClass);
|
||||
}
|
||||
|
||||
for (PsiClass funInterface : funInterfaces) {
|
||||
SamDescriptor descriptor = ReadAction.compute(() -> {
|
||||
if (!funInterface.isValid()) return null;
|
||||
|
||||
final PsiMethod functionalInterfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(funInterface);
|
||||
LOG.assertTrue(functionalInterfaceMethod != null);
|
||||
final PsiType samType = functionalInterfaceMethod.getReturnType();
|
||||
if (samType == null) return null;
|
||||
|
||||
return new SamDescriptor(funInterface, functionalInterfaceMethod, samType,
|
||||
convertToGlobalScope(funInterface.getProject(),
|
||||
funInterface.getUseScope().intersectWith(queryParameters.getEffectiveSearchScope())));
|
||||
});
|
||||
if (descriptor == null || !descriptor.search(consumer, highLevelModules)) return;
|
||||
public void processQuery(@NotNull SearchParameters p, @NotNull Processor<PsiFunctionalExpression> consumer) {
|
||||
List<SamDescriptor> descriptors = calcDescriptors(p);
|
||||
AtomicInteger exprCount = new AtomicInteger();
|
||||
AtomicInteger fileCount = new AtomicInteger();
|
||||
processOffsets(descriptors, (file, offsets) -> {
|
||||
fileCount.incrementAndGet();
|
||||
exprCount.addAndGet(offsets.size());
|
||||
return processFile(consumer, descriptors, file, offsets);
|
||||
});
|
||||
if (exprCount.get() > 0) {
|
||||
LOG.debug("Loaded " + exprCount.get() + " fun-expressions in " + fileCount.get() + " files");
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static Set<VirtualFile> getFilesToSearchInPsi(PsiClass samClass) {
|
||||
Set<VirtualFile> result = new HashSet<>();
|
||||
processOffsets(calcDescriptors(new SearchParameters(samClass, samClass.getUseScope())), (file, offsets) -> result.add(file));
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<SamDescriptor> calcDescriptors(@NotNull SearchParameters queryParameters) {
|
||||
List<SamDescriptor> descriptors = new ArrayList<>();
|
||||
|
||||
ReadAction.run(() -> {
|
||||
PsiClass aClass = queryParameters.getElementToSearch();
|
||||
if (!aClass.isValid() || !aClass.isInterface()) {
|
||||
return;
|
||||
}
|
||||
Project project = aClass.getProject();
|
||||
if (InjectedLanguageManager.getInstance(project).isInjectedFragment(aClass.getContainingFile()) || !hasJava8Modules(project)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (PsiClass samClass : processSubInterfaces(aClass)) {
|
||||
if (LambdaUtil.isFunctionalClass(samClass)) {
|
||||
PsiMethod saMethod = assertNotNull(LambdaUtil.getFunctionalInterfaceMethod(samClass));
|
||||
PsiType samType = saMethod.getReturnType();
|
||||
if (samType == null) continue;
|
||||
|
||||
SearchScope scope = samClass.getUseScope().intersectWith(queryParameters.getEffectiveSearchScope());
|
||||
descriptors.add(new SamDescriptor(samClass, saMethod, samType, convertToGlobalScope(project, scope)));
|
||||
}
|
||||
}
|
||||
});
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Set<VirtualFile> getLikelyFiles(List<SamDescriptor> descriptors) {
|
||||
return JBIterable.from(descriptors).flatMap(SamDescriptor::getMostLikelyFiles).toSet();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static MultiMap<VirtualFile, FunExprOccurrence> getAllOccurrences(List<SamDescriptor> descriptors) {
|
||||
MultiMap<VirtualFile, FunExprOccurrence> result = MultiMap.createLinkedSet();
|
||||
for (SamDescriptor descriptor : descriptors) {
|
||||
ReadAction.run(() -> {
|
||||
for (FunctionalExpressionKey key : descriptor.generateKeys()) {
|
||||
FileBasedIndex.getInstance().processValues(JavaFunctionalExpressionIndex.INDEX_ID, key, null, (file, infos) -> {
|
||||
ProgressManager.checkCanceled();
|
||||
result.putValues(file, infos);
|
||||
return true;
|
||||
}, new JavaSourceFilterScope(descriptor.useScope));
|
||||
}
|
||||
});
|
||||
}
|
||||
LOG.debug("Found " + result.values().size() + " fun-expressions in " + result.keySet().size() + " files");
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void processOffsets(List<SamDescriptor> descriptors, PairProcessor<VirtualFile, List<Integer>> processor) {
|
||||
if (descriptors.isEmpty()) return;
|
||||
|
||||
List<PsiClass> samClasses = ContainerUtil.map(descriptors, d -> d.samClass);
|
||||
MultiMap<VirtualFile, FunExprOccurrence> allCandidates = getAllOccurrences(descriptors);
|
||||
for (VirtualFile vFile : putLikelyFilesFirst(descriptors, allCandidates.keySet())) {
|
||||
List<FunExprOccurrence> toLoad = filterInapplicable(samClasses, vFile, allCandidates.get(vFile));
|
||||
if (!toLoad.isEmpty()) {
|
||||
LOG.trace("To load " + vFile.getPath() + " with values: " + toLoad);
|
||||
if (!processor.process(vFile, ContainerUtil.map(toLoad, it -> it.funExprOffset))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Set<VirtualFile> putLikelyFilesFirst(List<SamDescriptor> descriptors, Set<VirtualFile> allFiles) {
|
||||
Set<VirtualFile> orderedFiles = new LinkedHashSet<>(allFiles);
|
||||
orderedFiles.retainAll(getLikelyFiles(descriptors));
|
||||
orderedFiles.addAll(allFiles);
|
||||
return orderedFiles;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<FunExprOccurrence> filterInapplicable(List<PsiClass> samClasses,
|
||||
VirtualFile vFile,
|
||||
Collection<FunExprOccurrence> occurrences) {
|
||||
return ReadAction.compute(() -> ContainerUtil.filter(occurrences, it -> it.canHaveType(samClasses, vFile)));
|
||||
}
|
||||
|
||||
private static boolean processFile(@NotNull Processor<PsiFunctionalExpression> consumer,
|
||||
PsiClass samClass,
|
||||
List<SamDescriptor> descriptors,
|
||||
VirtualFile vFile, Collection<Integer> offsets) {
|
||||
return ReadAction.compute(() -> {
|
||||
if (!samClass.isValid()) return true;
|
||||
|
||||
PsiFile file = samClass.getManager().findFile(vFile);
|
||||
PsiFile file = descriptors.get(0).samClass.getManager().findFile(vFile);
|
||||
if (!(file instanceof PsiJavaFile)) {
|
||||
LOG.error("Non-java file " + file + "; " + vFile);
|
||||
return true;
|
||||
@@ -124,7 +182,7 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasType(samClass, expression) && !consumer.process(expression)) {
|
||||
if (hasType(descriptors, expression) && !consumer.process(expression)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -133,205 +191,45 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean hasType(PsiClass samClass, PsiFunctionalExpression expression) {
|
||||
if (!canHaveType(expression, samClass)) return false;
|
||||
private static boolean hasType(List<SamDescriptor> descriptors, PsiFunctionalExpression expression) {
|
||||
if (!canHaveType(expression, ContainerUtil.map(descriptors, d -> d.samClass))) return false;
|
||||
|
||||
return InheritanceUtil.isInheritorOrSelf(PsiUtil.resolveClassInType(expression.getFunctionalInterfaceType()), samClass, true);
|
||||
PsiClass actualClass = PsiUtil.resolveClassInType(expression.getFunctionalInterfaceType());
|
||||
return ContainerUtil.exists(descriptors, d -> InheritanceUtil.isInheritorOrSelf(actualClass, d.samClass, true));
|
||||
}
|
||||
|
||||
private static boolean canHaveType(PsiFunctionalExpression expression, PsiClass samClass) {
|
||||
private static boolean canHaveType(PsiFunctionalExpression expression, List<PsiClass> samClasses) {
|
||||
PsiElement parent = expression.getParent();
|
||||
if (parent instanceof PsiExpressionList && parent.getParent() instanceof PsiMethodCallExpression) {
|
||||
int argIndex = Arrays.asList(((PsiExpressionList)parent).getExpressions()).indexOf(expression);
|
||||
PsiExpression[] args = ((PsiExpressionList)parent).getExpressions();
|
||||
int argIndex = Arrays.asList(args).indexOf(expression);
|
||||
PsiReferenceExpression methodExpression = ((PsiMethodCallExpression)parent.getParent()).getMethodExpression();
|
||||
PsiExpression qualifier = methodExpression.getQualifierExpression();
|
||||
String methodName = methodExpression.getReferenceName();
|
||||
if (qualifier != null && methodName != null && argIndex >= 0) {
|
||||
Set<PsiClass> approximateTypes = ApproximateResolver.getPossibleTypes(qualifier, 10);
|
||||
return approximateTypes == null || hasMethodWithSamCompatibleParameter(approximateTypes, methodName, argIndex, samClass);
|
||||
List<PsiMethod> methods = approximateTypes == null ? null :
|
||||
ApproximateResolver.getPossibleMethods(approximateTypes, methodName, args.length);
|
||||
return methods == null ||
|
||||
ContainerUtil.exists(methods, m -> FunExprOccurrence.hasCompatibleParameter(m, argIndex, samClasses));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean hasMethodWithSamCompatibleParameter(Set<PsiClass> qualifierClasses, String methodName, int argIndex, PsiClass samClass) {
|
||||
for (PsiClass qualifierClass : qualifierClasses) {
|
||||
for (PsiMethod method : qualifierClass.findMethodsByName(methodName, true)) {
|
||||
PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
int paramIndex = method.isVarArgs() ? Math.min(argIndex, parameters.length - 1) : argIndex;
|
||||
if (paramIndex < parameters.length && canPassFunctionalExpression(samClass, parameters[paramIndex])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static MultiMap<VirtualFile, Integer> getCandidateOffsets(SamDescriptor descriptor, MultiMap<Location, GlobalSearchScope> queries, Set<VirtualFile> likelyFiles) {
|
||||
MultiMap<VirtualFile, Integer> result = MultiMap.createLinked();
|
||||
MultiMap<VirtualFile, Integer> unlikely = MultiMap.createLinked();
|
||||
for (Location location : queries.keySet()) {
|
||||
MultiMap<VirtualFile, Integer> offsets = getOffsetsForLocation(descriptor, location, queries.get(location));
|
||||
for (VirtualFile file : offsets.keySet()) {
|
||||
(likelyFiles.contains(file) ? result : unlikely).putValues(file, offsets.get(file));
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
logIfLarge(offsets, location);
|
||||
}
|
||||
}
|
||||
|
||||
result.putAllValues(unlikely);
|
||||
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, Iterable<PsiMethod> methods) {
|
||||
MultiMap<Location, GlobalSearchScope> queries = MultiMap.createSet();
|
||||
queries.putValue(Location.UNKNOWN, descriptor.useScope);
|
||||
queries.putValue(new FunctionalExpressionKey.TypedLocation(descriptor.getClassName()), descriptor.useScope);
|
||||
|
||||
for (PsiMethod psiMethod : methods) {
|
||||
ReadAction.run(() -> {
|
||||
if (!psiMethod.isValid()) return;
|
||||
|
||||
GlobalSearchScope methodUseScope = getGlobalUseScope(psiMethod);
|
||||
for (Location location : getPossibleCallLocations(descriptor.samClass, psiMethod)) {
|
||||
queries.putValue(location, methodUseScope);
|
||||
}
|
||||
});
|
||||
}
|
||||
return queries;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static GlobalSearchScope getGlobalUseScope(PsiMethod method) {
|
||||
if (PsiTreeUtil.getContextOfType(method, PsiAnonymousClass.class) != null || method.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
// don't call method.getUseScope as it'll be too specific and might cause stub-AST switch
|
||||
VirtualFile vFile = PsiUtilCore.getVirtualFile(method);
|
||||
if (vFile != null) {
|
||||
return GlobalSearchScope.fileScope(method.getProject(), vFile);
|
||||
}
|
||||
}
|
||||
|
||||
return convertToGlobalScope(method.getProject(), method.getUseScope());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Set<Location> getPossibleCallLocations(PsiClass samClass, PsiMethod calledMethod) {
|
||||
Set<Location> keys = new HashSet<>();
|
||||
|
||||
String samName = samClass.getQualifiedName();
|
||||
boolean includeStreamApi = samName != null && samName.startsWith("java.util.function.") ||
|
||||
hasStreamLikeApi(samClass.getProject());
|
||||
|
||||
String methodName = calledMethod.getName();
|
||||
PsiParameter[] parameters = calledMethod.getParameterList().getParameters();
|
||||
for (int paramIndex = 0; paramIndex < parameters.length; paramIndex++) {
|
||||
PsiParameter parameter = parameters[paramIndex];
|
||||
if (canPassFunctionalExpression(samClass, parameter)) {
|
||||
for (int argCount : getPossibleArgCounts(parameters, paramIndex)) {
|
||||
for (int argIndex : getPossibleArgIndices(parameter, paramIndex, argCount)) {
|
||||
keys.add(new CallLocation(methodName, argCount, argIndex, false));
|
||||
if (includeStreamApi) {
|
||||
keys.add(new CallLocation(methodName, argCount, argIndex, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
private static int[] getPossibleArgCounts(PsiParameter[] parameters, int paramIndex) {
|
||||
if (parameters[parameters.length - 1].isVarArgs()) {
|
||||
return IntStream
|
||||
.rangeClosed(parameters.length - 1, CallLocation.MAX_ARG_COUNT)
|
||||
.filter(i -> i > paramIndex)
|
||||
.toArray();
|
||||
}
|
||||
return new int[]{Math.min(parameters.length, CallLocation.MAX_ARG_COUNT)};
|
||||
}
|
||||
|
||||
private static int[] getPossibleArgIndices(PsiParameter parameter, int paramIndex, int argCount) {
|
||||
if (parameter.isVarArgs()) {
|
||||
return IntStream
|
||||
.rangeClosed(paramIndex + 1, CallLocation.MAX_ARG_COUNT)
|
||||
.filter(i -> i < argCount)
|
||||
.toArray();
|
||||
}
|
||||
return new int[]{Math.min(paramIndex, CallLocation.MAX_ARG_COUNT)};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Set<Module> getJava8Modules(Project project) {
|
||||
private static boolean hasJava8Modules(Project project) {
|
||||
final boolean projectLevelIsHigh = PsiUtil.getLanguageLevel(project).isAtLeast(LanguageLevel.JDK_1_8);
|
||||
|
||||
final Set<Module> highLevelModules = new HashSet<>();
|
||||
for (Module module : ModuleManager.getInstance(project).getModules()) {
|
||||
final LanguageLevelModuleExtension extension = ModuleRootManager.getInstance(module).getModuleExtension(LanguageLevelModuleExtension.class);
|
||||
if (extension != null) {
|
||||
final LanguageLevel level = extension.getLanguageLevel();
|
||||
if (level == null && projectLevelIsHigh || level != null && level.isAtLeast(LanguageLevel.JDK_1_8)) {
|
||||
highLevelModules.add(module);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return highLevelModules;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Set<String> collectMethodNamesCalledWithFunExpressions(SamDescriptor descriptor) {
|
||||
Set<String> usedMethodNames = new HashSet<>();
|
||||
ReadAction.run(() -> FileBasedIndex.getInstance().processAllKeys(JavaFunctionalExpressionIndex.INDEX_ID, key -> {
|
||||
ProgressManager.checkCanceled();
|
||||
if (key.canRepresent(descriptor.samParamCount, descriptor.booleanCompatible, descriptor.isVoid) &&
|
||||
key.location instanceof CallLocation) {
|
||||
usedMethodNames.add(((CallLocation)key.location).methodName);
|
||||
}
|
||||
return true;
|
||||
}, descriptor.useScope, null));
|
||||
return usedMethodNames;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static GlobalSearchScope combineResolveScopes(Set<Module> candidateModules, PsiClass samClass) {
|
||||
List<GlobalSearchScope> scopes = candidateModules.stream()
|
||||
.map(GlobalSearchScope::moduleWithDependenciesAndLibrariesScope)
|
||||
.filter(s -> PsiSearchScopeUtil.isInScope(s, samClass))
|
||||
.collect(Collectors.toList());
|
||||
return scopes.isEmpty() ? null : GlobalSearchScope.union(scopes.toArray(new GlobalSearchScope[scopes.size()]));
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -351,19 +249,6 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
|
||||
return scope;
|
||||
}
|
||||
|
||||
private static boolean canPassFunctionalExpression(PsiClass sam, PsiParameter parameter) {
|
||||
PsiType paramType = parameter.getType();
|
||||
if (paramType instanceof PsiEllipsisType) {
|
||||
paramType = ((PsiEllipsisType)paramType).getComponentType();
|
||||
}
|
||||
PsiClass functionalCandidate = PsiUtil.resolveClassInClassTypeOnly(paramType);
|
||||
if (functionalCandidate instanceof PsiTypeParameter) {
|
||||
return InheritanceUtil.isInheritorOrSelf(sam, PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(paramType)), true);
|
||||
}
|
||||
|
||||
return InheritanceUtil.isInheritorOrSelf(functionalCandidate, sam, true);
|
||||
}
|
||||
|
||||
private static Set<PsiClass> processSubInterfaces(PsiClass base) {
|
||||
Set<PsiClass> result = new HashSet<>();
|
||||
new Object() {
|
||||
@@ -396,120 +281,52 @@ public class JavaFunctionalExpressionSearcher extends QueryExecutorBase<PsiFunct
|
||||
this.isVoid = PsiType.VOID.equals(samType);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private MultiMap<VirtualFile, Integer> getOffsets(Set<Module> highLevelModules) {
|
||||
GlobalSearchScope visibleFromCandidates = ReadAction.compute(
|
||||
() -> samClass.isValid() ? combineResolveScopes(highLevelModules, samClass) : null);
|
||||
if (visibleFromCandidates == null) return MultiMap.empty();
|
||||
|
||||
Set<String> usedMethodNames = collectMethodNamesCalledWithFunExpressions(this);
|
||||
Set<PsiMethod> exactTypeMethods = getMethodsWithParameterType(usedMethodNames, useScope.intersectWith(visibleFromCandidates), getClassName());
|
||||
Set<PsiMethod> genericMethods = getMethodsWithParameterType(usedMethodNames, visibleFromCandidates, JavaMethodElementType.TYPE_PARAMETER_PSEUDO_NAME);
|
||||
LOG.debug("#methods: " + (exactTypeMethods.size() + genericMethods.size()));
|
||||
|
||||
MultiMap<Location, GlobalSearchScope> queries = collectQueryKeys(this, ContainerUtil.concat(exactTypeMethods, genericMethods));
|
||||
return getCandidateOffsets(this, queries, getMostLikelyFiles(exactTypeMethods));
|
||||
}
|
||||
|
||||
private boolean search(@NotNull Processor<PsiFunctionalExpression> consumer, Set<Module> highLevelModules) {
|
||||
for (Map.Entry<VirtualFile, Collection<Integer>> entry : getOffsets(highLevelModules).entrySet()) {
|
||||
if (!processFile(consumer, samClass, entry.getKey(), entry.getValue())) {
|
||||
return false;
|
||||
List<FunctionalExpressionKey> generateKeys() {
|
||||
List<FunctionalExpressionKey> result = new ArrayList<>();
|
||||
for (String lambdaType : new String[]{assertNotNull(samClass.getName()), ""}) {
|
||||
for (int lambdaParamCount : new int[]{FunctionalExpressionKey.UNKNOWN_PARAM_COUNT, samParamCount}) {
|
||||
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.UNKNOWN, lambdaType));
|
||||
if (isVoid) {
|
||||
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.VOID, lambdaType));
|
||||
} else {
|
||||
if (booleanCompatible) {
|
||||
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.BOOLEAN, lambdaType));
|
||||
}
|
||||
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.NON_VOID, lambdaType));
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Set<VirtualFile> getMostLikelyFiles(Set<PsiMethod> exactTypeMethods) {
|
||||
private Set<VirtualFile> getMostLikelyFiles() {
|
||||
Set<VirtualFile> files = ContainerUtil.newLinkedHashSet();
|
||||
ReadAction.run(() -> {
|
||||
if (!samClass.isValid()) return;
|
||||
|
||||
Set<String> likelyNames = ContainerUtil.newLinkedHashSet(getClassName());
|
||||
for (PsiMethod method : exactTypeMethods) {
|
||||
likelyNames.add(method.getName());
|
||||
}
|
||||
String className = samClass.getName();
|
||||
Project project = samClass.getProject();
|
||||
if (className == null) return;
|
||||
|
||||
PsiSearchHelperImpl helper = (PsiSearchHelperImpl)PsiSearchHelper.SERVICE.getInstance(samClass.getProject());
|
||||
Set<String> likelyNames = ContainerUtil.newLinkedHashSet(className);
|
||||
StubIndex.getInstance().processElements(JavaMethodParameterTypesIndex.getInstance().getKey(), className,
|
||||
project, useScope, PsiMethod.class, method -> {
|
||||
ProgressManager.checkCanceled();
|
||||
likelyNames.add(method.getName());
|
||||
return true;
|
||||
});
|
||||
|
||||
PsiSearchHelperImpl helper = (PsiSearchHelperImpl)PsiSearchHelper.SERVICE.getInstance(project);
|
||||
Processor<VirtualFile> processor = Processors.cancelableCollectProcessor(files);
|
||||
for (String name : likelyNames) {
|
||||
helper.processFilesWithText(this.useScope, UsageSearchContext.IN_CODE, true, name, processor);
|
||||
for (String word : likelyNames) {
|
||||
helper.processFilesWithText(useScope, UsageSearchContext.IN_CODE, true, word, processor);
|
||||
}
|
||||
});
|
||||
return files;
|
||||
}
|
||||
|
||||
private Set<PsiMethod> getMethodsWithParameterType(Set<String> usedMethodNames,
|
||||
GlobalSearchScope scope,
|
||||
String type) {
|
||||
Set<PsiMethod> methods = new HashSet<>();
|
||||
ReadAction.run(() -> {
|
||||
if (!samClass.isValid()) return;
|
||||
|
||||
StubIndexKey<String, PsiMethod> key = JavaMethodParameterTypesIndex.getInstance().getKey();
|
||||
StubIndex.getInstance().processElements(key, type, samClass.getProject(), scope, PsiMethod.class, method -> {
|
||||
ProgressManager.checkCanceled();
|
||||
if (usedMethodNames.contains(method.getName())) {
|
||||
methods.add(method);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
return methods;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
String getClassName() {
|
||||
return assertNotNull(samClass.getName());
|
||||
}
|
||||
|
||||
List<FunctionalExpressionKey> generateKeys(Location location) {
|
||||
List<FunctionalExpressionKey> result = new ArrayList<>();
|
||||
for (int lambdaParamCount : new int[]{FunctionalExpressionKey.UNKNOWN_PARAM_COUNT, samParamCount}) {
|
||||
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.UNKNOWN, location));
|
||||
if (isVoid) {
|
||||
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.VOID, location));
|
||||
} else {
|
||||
if (booleanCompatible) {
|
||||
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.BOOLEAN, location));
|
||||
}
|
||||
result.add(new FunctionalExpressionKey(lambdaParamCount, FunctionalExpressionKey.CoarseType.NON_VOID, location));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static boolean hasStreamLikeApi(final Project project) {
|
||||
return CachedValuesManager.getManager(project).getCachedValue(project, () ->
|
||||
CachedValueProvider.Result.create(hasStreamLikeApi(project, "Arrays", "stream") || hasStreamLikeApi(project, "Stream", "of"),
|
||||
PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT));
|
||||
}
|
||||
|
||||
private static boolean hasStreamLikeApi(Project project, String qualifier, String methodName) {
|
||||
PsiShortNamesCache cache = PsiShortNamesCache.getInstance(project);
|
||||
for (PsiClass candidate : cache.getClassesByName(qualifier, GlobalSearchScope.allScope(project))) {
|
||||
if (hasMethodWithNonStreamType(methodName, candidate)) return true;
|
||||
}
|
||||
|
||||
for (PsiField field : cache.getFieldsByName(qualifier, GlobalSearchScope.allScope(project))) {
|
||||
PsiClass fieldType = PsiUtil.resolveClassInClassTypeOnly(field.getType());
|
||||
if (fieldType == null || fieldType instanceof PsiTypeParameter || hasMethodWithNonStreamType(methodName, fieldType)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean hasMethodWithNonStreamType(@NotNull String methodName, @NotNull PsiClass candidate) {
|
||||
for (PsiMethod method : candidate.findMethodsByName(methodName, true)) {
|
||||
PsiClass returnType = PsiUtil.resolveClassInClassTypeOnly(method.getReturnType());
|
||||
if (returnType == null || !KNOWN_STREAM_CLASSES.contains(returnType.getQualifiedName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,87 +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.
|
||||
*/
|
||||
package com.intellij.psi.impl.java;
|
||||
|
||||
import com.intellij.lang.LighterAST;
|
||||
import com.intellij.lang.LighterASTNode;
|
||||
import com.intellij.psi.impl.source.JavaLightTreeUtil;
|
||||
import com.intellij.psi.impl.source.tree.RecursiveLighterASTNodeWalkingVisitor;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static com.intellij.psi.impl.source.tree.JavaElementType.*;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class StreamApiDetector {
|
||||
public static final Set<String> STREAM_INTERMEDIATE_METHODS = ContainerUtil.newHashSet("filter",
|
||||
"map", "mapToInt", "mapToLong", "mapToDouble",
|
||||
"flatMap", "flatMapToInt", "flatMapToLong", "flatMapToDouble",
|
||||
"distinct", "sorted", "peek", "limit", "skip",
|
||||
"forEach", "forEachOrdered",
|
||||
"reduce", "collect", "max",
|
||||
"anyMatch", "allMatch", "noneMatch",
|
||||
"findFirst", "findAny");
|
||||
|
||||
static boolean isStreamApiCall(LighterAST tree, LighterASTNode methodCall) {
|
||||
LighterASTNode methodExpr = tree.getChildren(methodCall).get(0);
|
||||
String name = JavaLightTreeUtil.getNameIdentifierText(tree, methodExpr);
|
||||
if (STREAM_INTERMEDIATE_METHODS.contains(name)) {
|
||||
LighterASTNode qualifier = tree.getChildren(methodExpr).get(0);
|
||||
return qualifier.getTokenType() == METHOD_CALL_EXPRESSION && isStreamApiCall(tree, qualifier);
|
||||
}
|
||||
return isRootCall(tree, methodCall, methodExpr, name);
|
||||
}
|
||||
|
||||
private static boolean isRootCall(LighterAST tree, LighterASTNode methodCall, LighterASTNode methodExpr, String name) {
|
||||
if ("stream".equals(name) || "parallelStream".equals(name)) {
|
||||
List<LighterASTNode> argList = JavaLightTreeUtil.getArgList(tree, methodCall);
|
||||
if (argList == null) return false;
|
||||
return argList.isEmpty() || !argList.isEmpty() && hasQualifier(tree, methodExpr, "Arrays");
|
||||
}
|
||||
if ("of".equals(name)) {
|
||||
List<LighterASTNode> argList = JavaLightTreeUtil.getArgList(tree, methodCall);
|
||||
return argList != null && !argList.isEmpty() && hasQualifier(tree, methodExpr, "Stream");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean hasQualifier(LighterAST tree, LighterASTNode methodExpr, String refName) {
|
||||
LighterASTNode node = tree.getChildren(methodExpr).get(0);
|
||||
return node.getTokenType() == REFERENCE_EXPRESSION &&
|
||||
refName.equals(JavaLightTreeUtil.getNameIdentifierText(tree, node)) &&
|
||||
!hasContextVariable(tree, node, refName);
|
||||
}
|
||||
|
||||
private static boolean hasContextVariable(final LighterAST tree, LighterASTNode node, final String varName) {
|
||||
final AtomicBoolean result = new AtomicBoolean(false);
|
||||
new RecursiveLighterASTNodeWalkingVisitor(tree) {
|
||||
@Override
|
||||
public void visitNode(@NotNull LighterASTNode element) {
|
||||
if (element.getTokenType() == LOCAL_VARIABLE && varName.equals(JavaLightTreeUtil.getNameIdentifierText(tree, element))) {
|
||||
result.set(true);
|
||||
}
|
||||
super.visitNode(element);
|
||||
}
|
||||
}.visitNode(tree.getRoot());
|
||||
return result.get();
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,12 @@
|
||||
package com.intellij.psi.impl.java.stubs;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.io.IOUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
@@ -32,47 +34,26 @@ public class FunctionalExpressionKey {
|
||||
public static final int UNKNOWN_PARAM_COUNT = -1;
|
||||
private final int lambdaParameterCount;
|
||||
private final CoarseType lambdaReturnType;
|
||||
public final Location location;
|
||||
@NotNull private final String knownType;
|
||||
|
||||
public FunctionalExpressionKey(int lambdaParameterCount, @NotNull CoarseType lambdaReturnType, @NotNull Location location) {
|
||||
this.location = location;
|
||||
public FunctionalExpressionKey(int lambdaParameterCount, @NotNull CoarseType lambdaReturnType, @Nullable String knownFunExprType) {
|
||||
this.lambdaParameterCount = lambdaParameterCount;
|
||||
this.lambdaReturnType = lambdaReturnType;
|
||||
this.knownType = StringUtil.notNullize(knownFunExprType);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FunctionalExpressionKey deserializeKey(@NotNull DataInput dataStream) throws IOException {
|
||||
int parameterCount = dataStream.readByte();
|
||||
CoarseType type = CoarseType.values()[dataStream.readByte()];
|
||||
return new FunctionalExpressionKey(parameterCount, type, deserializeLocation(dataStream));
|
||||
String knownType = IOUtil.readUTF(dataStream);
|
||||
return new FunctionalExpressionKey(parameterCount, type, knownType);
|
||||
}
|
||||
|
||||
public void serializeKey(@NotNull DataOutput dataStream) throws IOException {
|
||||
dataStream.writeByte(lambdaParameterCount);
|
||||
dataStream.writeByte(lambdaReturnType.ordinal());
|
||||
serializeLocation(dataStream);
|
||||
}
|
||||
|
||||
private static Location deserializeLocation(DataInput dataStream) throws IOException {
|
||||
byte locationType = dataStream.readByte();
|
||||
if (locationType == 0) return Location.UNKNOWN;
|
||||
if (locationType == 1) return CallLocation.deserializeCall(dataStream);
|
||||
if (locationType == 2) return TypedLocation.deserializeField(dataStream);
|
||||
throw new AssertionError(locationType);
|
||||
}
|
||||
|
||||
private void serializeLocation(@NotNull DataOutput dataStream) throws IOException {
|
||||
if (location == Location.UNKNOWN) {
|
||||
dataStream.writeByte(0);
|
||||
}
|
||||
else if (location instanceof CallLocation) {
|
||||
dataStream.writeByte(1);
|
||||
((CallLocation)location).serializeCall(dataStream);
|
||||
}
|
||||
else if (location instanceof TypedLocation) {
|
||||
dataStream.writeByte(2);
|
||||
((TypedLocation)location).serializeVariable(dataStream);
|
||||
}
|
||||
IOUtil.writeUTF(dataStream, knownType);
|
||||
}
|
||||
|
||||
public boolean canRepresent(int samParamCount, boolean booleanCompatible, boolean isVoid) {
|
||||
@@ -99,7 +80,7 @@ public class FunctionalExpressionKey {
|
||||
|
||||
if (lambdaParameterCount != key.lambdaParameterCount) return false;
|
||||
if (lambdaReturnType != key.lambdaReturnType) return false;
|
||||
if (!location.equals(key.location)) return false;
|
||||
if (!knownType.equals(key.knownType)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -108,7 +89,7 @@ public class FunctionalExpressionKey {
|
||||
public int hashCode() {
|
||||
int result = lambdaParameterCount;
|
||||
result = 31 * result + lambdaReturnType.ordinal();
|
||||
result = 31 * result + location.hashCode();
|
||||
result = 31 * result + knownType.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -117,130 +98,10 @@ public class FunctionalExpressionKey {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("lambdaParameterCount", lambdaParameterCount)
|
||||
.add("type", lambdaReturnType)
|
||||
.add("location", location)
|
||||
.add("knownType", knownType)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public interface Location {
|
||||
Location UNKNOWN = new Location() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class CallLocation implements Location {
|
||||
public static final int MAX_ARG_COUNT = 10;
|
||||
@NotNull public final String methodName;
|
||||
public final int methodArgsLength;
|
||||
public final int callArgIndex;
|
||||
public final boolean streamApi;
|
||||
|
||||
public CallLocation(@NotNull String methodName, int methodArgsLength, int callArgIndex, boolean streamApi) {
|
||||
this.methodName = methodName;
|
||||
this.methodArgsLength = Math.min(methodArgsLength, MAX_ARG_COUNT);
|
||||
this.callArgIndex = Math.min(callArgIndex, MAX_ARG_COUNT - 1);
|
||||
this.streamApi = streamApi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof CallLocation)) return false;
|
||||
|
||||
CallLocation location = (CallLocation)o;
|
||||
|
||||
if (methodArgsLength != location.methodArgsLength) return false;
|
||||
if (callArgIndex != location.callArgIndex) return false;
|
||||
if (!methodName.equals(location.methodName)) return false;
|
||||
if (streamApi != location.streamApi) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = methodName.hashCode();
|
||||
result = 31 * result + methodArgsLength;
|
||||
result = 31 * result + callArgIndex;
|
||||
result = 31 * result + (streamApi ? 0 : 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("methodName", methodName)
|
||||
.add("methodArgsLength", methodArgsLength)
|
||||
.add("callArgIndex", callArgIndex)
|
||||
.add("streamApi", streamApi)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Location deserializeCall(DataInput dataStream) throws IOException {
|
||||
String methodName = IOUtil.readUTF(dataStream);
|
||||
int methodArgsLength = dataStream.readByte();
|
||||
int argIndex = dataStream.readByte();
|
||||
boolean streamApi = dataStream.readBoolean();
|
||||
return new CallLocation(methodName, methodArgsLength, argIndex, streamApi);
|
||||
}
|
||||
|
||||
private void serializeCall(@NotNull DataOutput dataStream) throws IOException {
|
||||
IOUtil.writeUTF(dataStream, methodName);
|
||||
dataStream.writeByte(methodArgsLength);
|
||||
dataStream.writeByte(callArgIndex);
|
||||
dataStream.writeBoolean(streamApi);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TypedLocation implements Location {
|
||||
@NotNull public final String varType;
|
||||
|
||||
public TypedLocation(@NotNull String varType) {
|
||||
this.varType = varType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof TypedLocation)) return false;
|
||||
|
||||
TypedLocation location = (TypedLocation)o;
|
||||
|
||||
if (!varType.equals(location.varType)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return varType.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("fieldType", varType)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public static TypedLocation deserializeField(DataInput dataStream) throws IOException {
|
||||
return new TypedLocation(IOUtil.readUTF(dataStream));
|
||||
}
|
||||
|
||||
public void serializeVariable(DataOutput dataStream) throws IOException {
|
||||
IOUtil.writeUTF(dataStream, varType);
|
||||
}
|
||||
}
|
||||
|
||||
public enum CoarseType {
|
||||
VOID, UNKNOWN, BOOLEAN, NON_VOID
|
||||
}
|
||||
|
||||
@@ -13,6 +13,6 @@ interface MyStream {
|
||||
class Test {
|
||||
|
||||
{
|
||||
Arrays.stream().map(p -> "a");
|
||||
Arrays.stream(1).map(p -> "a");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
interface I {
|
||||
int a();
|
||||
}
|
||||
|
||||
class A<Z> {
|
||||
void foo(I i);
|
||||
}
|
||||
|
||||
class C<X, Y, SR extends A<Y>> {
|
||||
void bar(SR t) {
|
||||
t.foo(() -> 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class C {
|
||||
{
|
||||
pkg.p1.p2.p3.Util.foo(() -> {});
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.intellij.codeInsight.daemon.lambda;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.search.JavaFunctionalExpressionSearcher;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
@@ -122,13 +123,55 @@ public class FindFunctionalInterfaceTest extends LightCodeInsightFixtureTestCase
|
||||
}
|
||||
|
||||
public void testStreamOfLikeApiWithField() {
|
||||
assertFalse(JavaFunctionalExpressionSearcher.hasStreamLikeApi(getProject()));
|
||||
myFixture.addClass("class Base { StrType Stream = null; }");
|
||||
configure();
|
||||
assertTrue(JavaFunctionalExpressionSearcher.hasStreamLikeApi(getProject()));
|
||||
assertSize(1, FunctionalExpressionSearch.search(findClass("I")).findAll());
|
||||
}
|
||||
|
||||
public void testCallWithQualifiedName() {
|
||||
myFixture.addClass("package pkg.p1.p2.p3; public interface I { void run() {} }");
|
||||
myFixture.addClass("package pkg.p1.p2.p3; public class Util { public static void foo(I i) {} }");
|
||||
configure();
|
||||
assertSize(1, FunctionalExpressionSearch.search(findClass("pkg.p1.p2.p3.I")).findAll());
|
||||
}
|
||||
|
||||
public void testCallOnGenericParameter() {
|
||||
configure();
|
||||
assertSize(1, FunctionalExpressionSearch.search(findClass("I")).findAll());
|
||||
}
|
||||
|
||||
public void testDontVisitInapplicableFiles() {
|
||||
PsiClass sam = myFixture.addClass("interface I { void foo(); }");
|
||||
myFixture.addClass("class Some { " +
|
||||
"{ I i = () -> {}; }" +
|
||||
"void doTest(int a) {} " +
|
||||
"void doTest(I i, I j) {} " +
|
||||
"Some intermediate() {} " +
|
||||
"Object intermediate(int a, int b) {} " +
|
||||
"}");
|
||||
myFixture.addClass("class _WrongSignature {{ I i = a -> {}; I j = () -> true; }}");
|
||||
myFixture.addClass("class _CallArgumentCountMismatch extends Some {{ " +
|
||||
" doTest(() -> {}); " +
|
||||
" intermediate(4).doTest(() -> {}, () -> {}); " +
|
||||
"}}");
|
||||
myFixture.addClass("class _KnownTypeVariableAssignment {" +
|
||||
"static Runnable field;" +
|
||||
"{ Runnable r = () -> {}; field = () -> {}; } " +
|
||||
"}");
|
||||
myFixture.addClass("class _SuperFieldAssignment extends _KnownTypeVariableAssignment {" +
|
||||
"{ field = () -> {}; } " +
|
||||
"}");
|
||||
myFixture.addClass("import static _KnownTypeVariableAssignment.*; " +
|
||||
"class _StaticallyImportedFieldAssignment {" +
|
||||
"{ field = () -> {}; } " +
|
||||
"}");
|
||||
|
||||
assertSize(1, FunctionalExpressionSearch.search(sam).findAll());
|
||||
for (VirtualFile file : JavaFunctionalExpressionSearcher.getFilesToSearchInPsi(sam)) {
|
||||
assertFalse(file.getName(), file.getName().startsWith("_"));
|
||||
}
|
||||
}
|
||||
|
||||
private PsiClass findClass(String i) {
|
||||
return JavaPsiFacade.getInstance(getProject()).findClass(i, GlobalSearchScope.allScope(getProject()));
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.jetbrains.plugins.groovy.lang.overriding
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.psi.search.searches.ClassInheritorsSearch
|
||||
import com.intellij.psi.search.searches.FunctionalExpressionSearch
|
||||
import com.intellij.psi.search.searches.OverridingMethodsSearch
|
||||
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
|
||||
@@ -62,4 +63,10 @@ public class FindOverridingMethodsAndClassesTest extends LightCodeInsightFixture
|
||||
final Collection<PsiClass> classes = ClassInheritorsSearch.search(psiClass).findAll()
|
||||
assertEquals("Class count is wrong", classCount, classes.size())
|
||||
}
|
||||
|
||||
public void "test find java functional expression passed into a groovy method"() {
|
||||
myFixture.addFileToProject("a.groovy", "interface I { void foo(); }; class C { static void bar(I i) {}}")
|
||||
myFixture.addFileToProject("a.java", "class D {{ C.bar(() -> {}); }")
|
||||
assertSize(1, FunctionalExpressionSearch.search(myFixture.findClass("I")).findAll());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user