blob: d96d6dfe0f8338465d2c7a5fee7c7ec16437f0e4 [file] [log] [blame]
package com.intellij.compiler.classFilesIndex.chainsSearch.completion;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.compiler.classFilesIndex.api.index.ClassFilesIndexFeature;
import com.intellij.compiler.classFilesIndex.api.index.ClassFilesIndexFeaturesHolder;
import com.intellij.compiler.classFilesIndex.chainsSearch.*;
import com.intellij.compiler.classFilesIndex.chainsSearch.context.ChainCompletionContext;
import com.intellij.compiler.classFilesIndex.chainsSearch.context.ContextUtil;
import com.intellij.compiler.classFilesIndex.chainsSearch.context.TargetType;
import com.intellij.compiler.classFilesIndex.impl.MethodsUsageIndexReader;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.patterns.ElementPattern;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiImmediateClassType;
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.compiler.classFilesIndex.chainsSearch.completion.CompletionContributorPatternUtil.*;
import static com.intellij.patterns.PsiJavaPatterns.or;
/**
* @author Dmitry Batkovich
*/
public class MethodsChainsCompletionContributor extends CompletionContributor {
private final static boolean IS_UNIT_TEST_MODE = ApplicationManager.getApplication().isUnitTestMode();
public static final int INVOCATIONS_THRESHOLD = 2;
public static final CompletionType COMPLETION_TYPE = IS_UNIT_TEST_MODE ? CompletionType.BASIC : CompletionType.SMART;
private final static int MAX_SEARCH_RESULT_SIZE = 5;
private final static int MAX_CHAIN_SIZE = 4;
private final static int FILTER_RATIO = 10;
@Override
public void fillCompletionVariants(@NotNull final CompletionParameters parameters, @NotNull final CompletionResultSet result) {
if (parameters.getInvocationCount() >= INVOCATIONS_THRESHOLD &&
ClassFilesIndexFeaturesHolder.getInstance(parameters.getPosition().getProject())
.enableFeatureIfNeed(ClassFilesIndexFeature.METHOD_CHAINS_COMPLETION)) {
super.fillCompletionVariants(parameters, result);
}
}
@SuppressWarnings("unchecked")
public MethodsChainsCompletionContributor() {
final ElementPattern<PsiElement> pattern = or(patternForMethodParameter(), patternForVariableAssignment());
extend(COMPLETION_TYPE, pattern, new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(final @NotNull CompletionParameters parameters,
final ProcessingContext context,
final @NotNull CompletionResultSet result) {
final ChainCompletionContext completionContext = extractContext(parameters);
if (completionContext == null) return;
final Set<String> contextTypesKeysSet = completionContext.getContextTypes();
final Set<String> contextRelevantTypes = new HashSet<String>(contextTypesKeysSet.size() + 1);
for (final String type : contextTypesKeysSet) {
if (!ChainCompletionStringUtil.isPrimitiveOrArrayOfPrimitives(type)) {
contextRelevantTypes.add(type);
}
}
final TargetType target = completionContext.getTarget();
contextRelevantTypes.remove(target.getClassQName());
final List<LookupElement> elementsFoundByMethodsChainsSearch = searchForLookups(target, contextRelevantTypes, completionContext);
if (!IS_UNIT_TEST_MODE) {
result.runRemainingContributors(parameters, new Consumer<CompletionResult>() {
@Override
public void consume(final CompletionResult completionResult) {
final LookupElement lookupElement = completionResult.getLookupElement();
final PsiElement lookupElementPsi = lookupElement.getPsiElement();
if (lookupElementPsi != null) {
for (final LookupElement element : elementsFoundByMethodsChainsSearch) {
if (lookupElementPsi.isEquivalentTo(element.getPsiElement())) {
elementsFoundByMethodsChainsSearch.remove(element);
break;
}
}
}
result.passResult(completionResult);
}
});
} else {
result.stopHere();
}
result.addAllElements(elementsFoundByMethodsChainsSearch);
}
});
}
private static List<LookupElement> searchForLookups(final TargetType target,
final Set<String> contextRelevantTypes,
final ChainCompletionContext completionContext) {
final Project project = completionContext.getProject();
final MethodsUsageIndexReader methodsUsageIndexReader = MethodsUsageIndexReader.getInstance(project);
final List<MethodsChain> searchResult =
searchChains(target, contextRelevantTypes, MAX_SEARCH_RESULT_SIZE, MAX_CHAIN_SIZE, completionContext, methodsUsageIndexReader);
if (searchResult.size() < MAX_SEARCH_RESULT_SIZE) {
if (!target.isArray()) {
final List<MethodsChain> inheritorFilteredSearchResult = new SmartList<MethodsChain>();
final Processor<TargetType> consumer = new Processor<TargetType>() {
@Override
public boolean process(final TargetType targetType) {
for (final MethodsChain chain : searchChains(targetType,
contextRelevantTypes,
MAX_SEARCH_RESULT_SIZE,
MAX_CHAIN_SIZE,
completionContext,
methodsUsageIndexReader)) {
boolean insert = true;
for (final MethodsChain baseChain : searchResult) {
final MethodsChain.CompareResult r = MethodsChain.compare(baseChain, chain, completionContext.getPsiManager());
if (r != MethodsChain.CompareResult.NOT_EQUAL) {
insert = false;
break;
}
}
if (insert) {
inheritorFilteredSearchResult.add(chain);
}
}
searchResult.addAll(inheritorFilteredSearchResult);
return searchResult.size() < MAX_SEARCH_RESULT_SIZE;
}
};
DirectClassInheritorsSearch.search(((PsiClassType)target.getPsiType()).resolve()).forEach(new Processor<PsiClass>() {
@Override
public boolean process(final PsiClass psiClass) {
final String inheritorQName = psiClass.getQualifiedName();
if (inheritorQName == null) {
return true;
}
return consumer.process(new TargetType(inheritorQName, false, new PsiImmediateClassType(psiClass, PsiSubstitutor.EMPTY)));
}
});
}
}
final List<MethodsChain> chains = searchResult.size() > MAX_CHAIN_SIZE ? chooseHead(searchResult) : searchResult;
return MethodsChainLookupRangingHelper
.chainsToWeightableLookupElements(filterTailAndGetSumLastMethodOccurrence(chains), completionContext);
}
private static List<MethodsChain> chooseHead(final List<MethodsChain> elements) {
Collections.sort(elements, new Comparator<MethodsChain>() {
@Override
public int compare(final MethodsChain o1, final MethodsChain o2) {
return o2.getChainWeight() - o1.getChainWeight();
}
});
return elements.subList(0, MAX_CHAIN_SIZE);
}
@SuppressWarnings("unchecked")
@Nullable
private static ChainCompletionContext extractContext(final CompletionParameters parameters) {
final PsiElement parent = PsiTreeUtil
.getParentOfType(parameters.getPosition(), PsiAssignmentExpression.class, PsiLocalVariable.class, PsiMethodCallExpression.class);
if (parent == null) {
return null;
}
if (parent instanceof PsiAssignmentExpression) {
return tryExtractContextFromAssignment((PsiAssignmentExpression)parent);
}
if (parent instanceof PsiLocalVariable) {
final PsiLocalVariable localVariable = (PsiLocalVariable)parent;
return ContextUtil.createContext(localVariable.getType(), localVariable.getName(),
PsiTreeUtil.getParentOfType(parent, PsiDeclarationStatement.class));
}
final PsiMethod method = ((PsiMethodCallExpression)parent).resolveMethod();
if (method == null) return null;
final PsiExpression expression = PsiTreeUtil.getParentOfType(parameters.getPosition(), PsiExpression.class);
final PsiExpressionList expressionList = PsiTreeUtil.getParentOfType(parameters.getPosition(), PsiExpressionList.class);
if (expressionList == null) return null;
final int exprPosition = Arrays.asList(expressionList.getExpressions()).indexOf(expression);
final PsiParameter[] methodParameters = method.getParameterList().getParameters();
if (exprPosition < methodParameters.length) {
final PsiParameter methodParameter = methodParameters[exprPosition];
return ContextUtil
.createContext(methodParameter.getType(), null, PsiTreeUtil.getParentOfType(expression, PsiDeclarationStatement.class));
}
return null;
}
@Nullable
private static ChainCompletionContext tryExtractContextFromAssignment(final PsiAssignmentExpression assignmentExpression) {
final PsiType type = assignmentExpression.getLExpression().getType();
final PsiIdentifier identifier = PsiTreeUtil.getChildOfType(assignmentExpression.getLExpression(), PsiIdentifier.class);
if (identifier == null) return null;
final String identifierText = identifier.getText();
return ContextUtil.createContext(type, identifierText, assignmentExpression);
}
private static List<MethodsChain> filterTailAndGetSumLastMethodOccurrence(final List<MethodsChain> chains) {
int maxWeight = 0;
for (final MethodsChain chain : chains) {
final int chainWeight = chain.getChainWeight();
if (chainWeight > maxWeight) {
maxWeight = chainWeight;
}
}
final List<MethodsChain> filteredResult = new ArrayList<MethodsChain>();
for (final MethodsChain chain : chains) {
final int chainWeight = chain.getChainWeight();
if (chainWeight * FILTER_RATIO >= maxWeight) {
filteredResult.add(chain);
}
}
return filteredResult;
}
private static List<MethodsChain> searchChains(final TargetType target,
final Set<String> contextVarsQNames,
final int maxResultSize,
final int maxChainSize,
final ChainCompletionContext context,
final MethodsUsageIndexReader methodsUsageIndexReader) {
return ChainsSearcher.search(maxChainSize, target, contextVarsQNames, maxResultSize, context, methodsUsageIndexReader);
}
}