function expression search optimization: index for method names with function expression argument

This commit is contained in:
Anna Kozlova
2015-07-28 18:34:48 +02:00
parent 4045e5d366
commit 79ec3b6b88
8 changed files with 471 additions and 56 deletions
@@ -0,0 +1,50 @@
/*
* Copyright 2000-2015 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.openapi.project.Project;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.impl.search.JavaSourceFilterScope;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.stubs.StringStubIndexExtension;
import com.intellij.psi.stubs.StubIndex;
import com.intellij.psi.stubs.StubIndexKey;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
public class JavaMethodParameterTypesIndex extends StringStubIndexExtension<PsiMethod> {
private static final JavaMethodParameterTypesIndex ourInstance = new JavaMethodParameterTypesIndex();
public static JavaMethodParameterTypesIndex getInstance() {
return ourInstance;
}
@NotNull
@Override
public StubIndexKey<String, PsiMethod> getKey() {
return JavaStubIndexKeys.METHOD_TYPES;
}
@Override
public Collection<PsiMethod> get(@NotNull final String s, @NotNull final Project project, @NotNull final GlobalSearchScope scope) {
return StubIndex.getElements(getKey(), s, project, new JavaSourceFilterScope(scope), PsiMethod.class);
}
}
@@ -0,0 +1,223 @@
/*
* Copyright 2000-2015 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.search;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.java.stubs.JavaStubElementTypes;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.indexing.*;
import com.intellij.util.io.DataExternalizer;
import com.intellij.util.io.DataInputOutputUtil;
import com.intellij.util.io.EnumeratorStringDescriptor;
import com.intellij.util.io.KeyDescriptor;
import org.jetbrains.annotations.NotNull;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.*;
public class JavaFunctionalExpressionIndex extends FileBasedIndexExtension<String, Collection<JavaFunctionalExpressionIndex.IndexHolder>> implements PsiDependentIndex {
public static final ID<String, Collection<IndexHolder>> JAVA_FUNCTIONAL_EXPRESSION_INDEX_ID = ID.create("java.functional.expression");
private static final String THIS_REF_NAME = "this";
private static final String SUPER_REF_NAME = "super";
@NotNull
@Override
public ID<String, Collection<IndexHolder>> getName() {
return JAVA_FUNCTIONAL_EXPRESSION_INDEX_ID;
}
@NotNull
@Override
public DataIndexer<String, Collection<IndexHolder>, FileContent> getIndexer() {
return new DataIndexer<String, Collection<IndexHolder>, FileContent>() {
@NotNull
@Override
public Map<String, Collection<IndexHolder>> map(@NotNull FileContent inputData) {
final CharSequence contentAsText = inputData.getContentAsText();
if (!StringUtil.contains(contentAsText, "::") && !StringUtil.contains(contentAsText, "->")) {
return Collections.emptyMap();
}
final PsiFile file = ((FileContentImpl)inputData).getPsiFileForPsiDependentIndex();
if (!(file instanceof PsiJavaFile) || !JavaStubElementTypes.JAVA_FILE.shouldBuildStubFor(inputData.getFile())) {
return Collections.emptyMap();
}
final HashMap<String, Collection<IndexHolder>> methodsMap = ContainerUtil.newHashMap();
for (PsiFunctionalExpression expression : SyntaxTraverser.psiTraverser().withRoot(file).filter(PsiFunctionalExpression.class)) {
final PsiExpressionList expressionList =
PsiTreeUtil.getParentOfType(expression, PsiExpressionList.class, true, PsiStatement.class, PsiModifierListOwner.class);
if (expressionList != null) {
final PsiElement parent = expressionList.getParent();
String methodName = null;
if (parent instanceof PsiMethodCallExpression) {
methodName = ((PsiMethodCallExpression)parent).getMethodExpression().getReferenceName();
if (methodName != null) {
final boolean thisRef = methodName.equals(THIS_REF_NAME);
if (thisRef || methodName.equals(SUPER_REF_NAME)) {
methodName = null;
final PsiClass containingClass = PsiTreeUtil.getParentOfType(parent, PsiClass.class);
if (containingClass != null) {
if (thisRef) {
methodName = containingClass.getName();
} else {
final PsiReferenceList extendsList = containingClass.getExtendsList();
if (extendsList != null) {
final PsiJavaCodeReferenceElement[] referenceElements = extendsList.getReferenceElements();
if (referenceElements.length > 0) {
methodName = referenceElements[0].getReferenceName();
}
}
}
}
}
}
}
else if (parent instanceof PsiNewExpression) {
final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression)parent).getClassOrAnonymousClassReference();
if (classReference != null) {
methodName = classReference.getReferenceName();
}
}
else if (parent instanceof PsiEnumConstant) {
final PsiClass containingClass = ((PsiEnumConstant)parent).getContainingClass();
if (containingClass != null) {
final String shortEnumName = containingClass.getName();
if (shortEnumName != null) { //should be always true as enums can't be local
methodName = shortEnumName;
}
}
}
if (methodName != null) {
Collection<IndexHolder> holders = methodsMap.get(methodName);
if (holders == null) {
holders = new HashSet<IndexHolder>();
methodsMap.put(methodName, holders);
}
holders.add(new IndexHolder(expression instanceof PsiLambdaExpression ? ((PsiLambdaExpression)expression).getParameterList().getParametersCount() : -1,
expressionList.getExpressions().length,
LambdaUtil.getLambdaIdx(expressionList, expression)));
}
}
}
return methodsMap;
}
};
}
@NotNull
@Override
public KeyDescriptor<String> getKeyDescriptor() {
return EnumeratorStringDescriptor.INSTANCE;
}
@NotNull
@Override
public DataExternalizer<Collection<IndexHolder>> getValueExternalizer() {
return new DataExternalizer<Collection<IndexHolder>>() {
@Override
public void save(@NotNull DataOutput out, Collection<IndexHolder> holders) throws IOException {
DataInputOutputUtil.writeINT(out, holders.size());
for (IndexHolder holder : holders) {
DataInputOutputUtil.writeINT(out, holder.getLambdaParamsNumber());
DataInputOutputUtil.writeINT(out, holder.getMethodArgsLength());
DataInputOutputUtil.writeINT(out, holder.getFunctionExpressionIndex());
}
}
@Override
public Collection<IndexHolder> read(@NotNull DataInput in) throws IOException {
int l = DataInputOutputUtil.readINT(in);
final Collection<IndexHolder> holders = new HashSet<IndexHolder>(l);
while (l-- > 0) {
holders.add(new IndexHolder(DataInputOutputUtil.readINT(in),
DataInputOutputUtil.readINT(in),
DataInputOutputUtil.readINT(in)));
}
return holders;
}
};
}
@NotNull
@Override
public FileBasedIndex.InputFilter getInputFilter() {
return new DefaultFileTypeSpecificInputFilter(JavaFileType.INSTANCE);
}
@Override
public boolean dependsOnFileContent() {
return true;
}
@Override
public int getVersion() {
return 0;
}
public static class IndexHolder {
private final int myLambdaParamsNumber;
private final int myMethodArgsLength;
private final int myFunctionExpressionIndex;
public IndexHolder(int lambdaParamsNumber, int methodArgsLength, int functionExpressionIndex) {
myLambdaParamsNumber = lambdaParamsNumber;
myMethodArgsLength = methodArgsLength;
myFunctionExpressionIndex = functionExpressionIndex;
}
public int getLambdaParamsNumber() {
return myLambdaParamsNumber;
}
public int getMethodArgsLength() {
return myMethodArgsLength;
}
public int getFunctionExpressionIndex() {
return myFunctionExpressionIndex;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IndexHolder holder = (IndexHolder)o;
if (myLambdaParamsNumber != holder.myLambdaParamsNumber) return false;
if (myMethodArgsLength != holder.myMethodArgsLength) return false;
if (myFunctionExpressionIndex != holder.myFunctionExpressionIndex) return false;
return true;
}
@Override
public int hashCode() {
int result = myLambdaParamsNumber;
result = 31 * result + myMethodArgsLength;
result = 31 * result + myFunctionExpressionIndex;
return result;
}
}
}
@@ -15,8 +15,8 @@
*/
package com.intellij.psi.impl.search;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadActionProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
@@ -24,26 +24,28 @@ import com.intellij.openapi.module.impl.scopes.ModulesScope;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.LanguageLevelModuleExtension;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
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.FunctionalExpressionSearch;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.*;
import com.intellij.util.CommonProcessors;
import com.intellij.util.Function;
import com.intellij.util.Processor;
import com.intellij.util.QueryExecutor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.util.indexing.FileBasedIndex;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
public class JavaFunctionalExpressionSearcher implements QueryExecutor<PsiFunctionalExpression, FunctionalExpressionSearch.SearchParameters> {
@@ -86,50 +88,8 @@ public class JavaFunctionalExpressionSearcher implements QueryExecutor<PsiFuncti
}), consumer, highLevelModules);
}
public static boolean collectFunctionalExpressions(final PsiClass aClass,
final SearchScope searchScope,
final Processor<PsiFunctionalExpression> consumer,
final Set<Module> highLevelModules) {
final Project project = PsiUtilCore.getProjectInReadAction(aClass);
final GlobalSearchScope scope = ApplicationManager.getApplication().runReadAction(new Computable<GlobalSearchScope>() {
@Override
public GlobalSearchScope compute() {
return prepareScopeToProcessFiles(highLevelModules, aClass, searchScope, project);
}
});
final ProjectFileIndex index = ProjectRootManager.getInstance(project).getFileIndex();
final HashSet<VirtualFile> files = new HashSet<VirtualFile>();
CommonProcessors.CollectProcessor<VirtualFile> processor = new CommonProcessors.CollectProcessor<VirtualFile>(files) {
@Override
protected boolean accept(VirtualFile virtualFile) {
return scope.contains(virtualFile) && virtualFile.getFileType() == JavaFileType.INSTANCE && index.isInSource(virtualFile);
}
};
final PsiSearchHelperImpl helper = (PsiSearchHelperImpl)PsiSearchHelper.SERVICE.getInstance(project);
helper.processFilesWithText(scope, UsageSearchContext.IN_CODE, true, "::", processor);
helper.processFilesWithText(scope, UsageSearchContext.IN_CODE, true, "->", processor);
LOG.info("#files: " + files.size());
final PsiManager psiManager = PsiManager.getInstance(project);
for (final VirtualFile file : files) {
if (!ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
@Override
public Boolean compute() {
return processFileWithFunctionalInterfaces(aClass, consumer, psiManager, file);
}
})) return false;
}
return true;
}
@NotNull
protected static GlobalSearchScope prepareScopeToProcessFiles(final Set<Module> highLevelModules,
final PsiClass aClass,
final SearchScope searchScope,
final Project project) {
final SearchScope useScope = searchScope.intersectWith(aClass.getUseScope());
private static GlobalSearchScope convertToGlobalScope(Project project, SearchScope useScope) {
final GlobalSearchScope scope;
if (useScope instanceof GlobalSearchScope) {
scope = (GlobalSearchScope)useScope;
@@ -145,16 +105,162 @@ public class JavaFunctionalExpressionSearcher implements QueryExecutor<PsiFuncti
scope = GlobalSearchScope.filesScope(project, files);
}
else {
scope = new ModulesScope(highLevelModules, project).intersectWith(new EverythingGlobalScope(project));
scope = new EverythingGlobalScope(project);
}
return scope;
}
public static boolean collectFunctionalExpressions(final PsiClass aClass,
final SearchScope searchScope,
final Processor<PsiFunctionalExpression> consumer,
final Set<Module> highLevelModules) {
final Project project = PsiUtilCore.getProjectInReadAction(aClass);
final ModulesScope modulesScope = new ModulesScope(highLevelModules, project);
final GlobalSearchScope useScope = ApplicationManager.getApplication().runReadAction(new Computable<GlobalSearchScope>() {
@Override
public GlobalSearchScope compute() {
return modulesScope.intersectWith(convertToGlobalScope(project, searchScope.intersectWith(aClass.getUseScope())));
}
});
final PsiSearchHelperImpl helper = (PsiSearchHelperImpl)PsiSearchHelper.SERVICE.getInstance(project);
final HashSet<VirtualFile> files = new HashSet<VirtualFile>();
final CommonProcessors.CollectProcessor<VirtualFile> processor = new CommonProcessors.CollectProcessor<VirtualFile>(files);
helper.processFilesWithText(useScope, UsageSearchContext.IN_CODE, true, "::", processor);
helper.processFilesWithText(useScope, UsageSearchContext.IN_CODE, true, "->", processor);
final GlobalSearchScope filesScope = GlobalSearchScope.filesScope(project, files);
final Collection<PsiMethod> lambdaCandidates = ApplicationManager.getApplication().runReadAction(new Computable<Collection<PsiMethod>>() {
@Override
public Collection<PsiMethod> compute() {
final String functionalInterfaceName = aClass.getName();
JavaMethodParameterTypesIndex parameterTypesIndex = JavaMethodParameterTypesIndex.getInstance();
LinkedHashSet<PsiMethod> methods = new LinkedHashSet<PsiMethod>(parameterTypesIndex.get(functionalInterfaceName, project, useScope));
methods.addAll(parameterTypesIndex.get(JavaMethodElementType.TYPE_PARAMETER_PSEUDO_NAME, project,
GlobalSearchScope.allScope(project)));
return methods;
}
});
final LinkedHashSet<VirtualFile> usageFiles = new LinkedHashSet<VirtualFile>();
final MethodSignature functionalInterfaceMethod = ApplicationManager.getApplication().runReadAction(new Computable<MethodSignature>() {
@Override
public MethodSignature compute() {
return LambdaUtil.getFunction(aClass);
}
});
LOG.assertTrue(functionalInterfaceMethod != null);
final int expectedFunExprParamsCount = functionalInterfaceMethod.getParameterTypes().length;
final FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance();
for (final PsiMethod psiMethod : lambdaCandidates) {
final int parametersCount = psiMethod.getParameterList().getParametersCount();
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
final boolean varArgs = psiMethod.isVarArgs();
final PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
final GlobalSearchScope methodUseScope = modulesScope.intersectWith(convertToGlobalScope(project, psiMethod.getUseScope()));
fileBasedIndex.processValues(JavaFunctionalExpressionIndex.JAVA_FUNCTIONAL_EXPRESSION_INDEX_ID, psiMethod.getName(), null,
new FileBasedIndex.ValueProcessor<Collection<JavaFunctionalExpressionIndex.IndexHolder>>() {
@Override
public boolean process(VirtualFile file, Collection<JavaFunctionalExpressionIndex.IndexHolder> holders) {
for (JavaFunctionalExpressionIndex.IndexHolder holder : holders) {
if (holder.getLambdaParamsNumber() == expectedFunExprParamsCount &&
(varArgs ? holder.getMethodArgsLength() >= parametersCount - 1 : holder.getMethodArgsLength() == parametersCount) &&
canBeFunctional(holder)
) {
usageFiles.add(file);
break;
}
}
return true;
}
private boolean canBeFunctional(JavaFunctionalExpressionIndex.IndexHolder holder) {
final int paramIdx = holder.getFunctionExpressionIndex();
final PsiClass functionalCandidate = PsiUtil.resolveClassInClassTypeOnly(parameters[paramIdx].getType());
return functionalCandidate instanceof PsiTypeParameter ||
LambdaUtil.isFunctionalClass(functionalCandidate);
}
}, useScope.intersectWith(methodUseScope));
}
});
}
return new ModulesScope(highLevelModules, project).intersectWith(scope);
collectFilesWithAssignments(aClass, filesScope, usageFiles);
LOG.info("#usage files: " + usageFiles.size());
return ContainerUtil.process(usageFiles, new ReadActionProcessor<VirtualFile>() {
@Override
public boolean processInReadAction(VirtualFile file) {
return processFileWithFunctionalInterfaces(aClass, expectedFunExprParamsCount, consumer, file);
}
});
}
/**
* Collect files where:
* aClass is used, e.g. in type declaration or method return type;
* fields with type aClass are used on the left side of assignments. Should find Bar of the following example
<pre/>
class Foo {
Runnable myRunnable;
}
class Bar {
void foo(Foo foo){
foo.myRunnable = () -> {};
}
}
</pre>
*/
private static void collectFilesWithAssignments(PsiClass aClass,
GlobalSearchScope filesScope,
final LinkedHashSet<VirtualFile> usageFiles) {
final Set<PsiField> fields = new LinkedHashSet<PsiField>();
for (final PsiReference reference : ReferencesSearch.search(aClass, filesScope)) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
final PsiElement element = reference.getElement();
if (element != null) {
ContainerUtil.addIfNotNull(usageFiles, PsiUtilCore.getVirtualFile(element));
final PsiElement parent = element.getParent();
if (parent instanceof PsiTypeElement) {
final PsiElement gParent = parent.getParent();
if (gParent instanceof PsiField &&
!((PsiField)gParent).hasModifierProperty(PsiModifier.PRIVATE) &&
!((PsiField)gParent).hasModifierProperty(PsiModifier.FINAL)) {
fields.add((PsiField)gParent);
}
}
}
}
});
}
for (PsiField field : fields) {
ReferencesSearch.search(field, filesScope).forEach(new ReadActionProcessor<PsiReference>() {
@Override
public boolean processInReadAction(PsiReference fieldRef) {
final PsiElement fieldElement = fieldRef.getElement();
final PsiAssignmentExpression varElementParent = PsiTreeUtil.getParentOfType(fieldElement, PsiAssignmentExpression.class);
if (varElementParent != null && PsiTreeUtil.isAncestor(varElementParent.getLExpression(), fieldElement, false)) {
ContainerUtil.addIfNotNull(usageFiles, PsiUtilCore.getVirtualFile(fieldElement));
}
return true;
}
});
}
}
private static boolean processFileWithFunctionalInterfaces(final PsiClass aClass,
final int expectedParamCount,
final Processor<PsiFunctionalExpression> consumer,
final PsiManager psiManager, VirtualFile file) {
final PsiFile psiFile = psiManager.findFile(file);
VirtualFile file) {
final PsiFile psiFile = aClass.getManager().findFile(file);
if (psiFile != null) {
final Ref<Boolean> ref = new Ref<Boolean>(true);
psiFile.accept(new JavaRecursiveElementWalkingVisitor() {
@@ -178,7 +284,9 @@ public class JavaFunctionalExpressionSearcher implements QueryExecutor<PsiFuncti
@Override
public void visitLambdaExpression(PsiLambdaExpression expression) {
super.visitLambdaExpression(expression);
visitFunctionalExpression(expression);
if (expression.getParameterList().getParametersCount() == expectedParamCount) {
visitFunctionalExpression(expression);
}
}
@Override