method chain search use PsiType-s instead of raw strings

This commit is contained in:
Dmitry Batkovich
2017-04-03 15:53:54 +03:00
parent 0af6b83791
commit f874122f09
16 changed files with 343 additions and 777 deletions
@@ -41,9 +41,10 @@ public class CachedRelevantStaticMethodSearcher {
}
@NotNull
public List<ContextRelevantStaticMethod> getRelevantStaticMethods(final String resultQualifiedClassName, final int minOccurrence) {
public List<ContextRelevantStaticMethod> getRelevantStaticMethods(final PsiType type, final int minOccurrence) {
String resultQualifiedClassName = type.getCanonicalText();
if (resultQualifiedClassName == null ||
ChainCompletionStringUtil.isPrimitiveOrArrayOfPrimitives(resultQualifiedClassName) ||
ChainCompletionStringUtil.isPrimitiveOrArrayOfPrimitives(type) ||
myCompletionContext.getTarget().getClassQName().equals(resultQualifiedClassName)) {
return Collections.emptyList();
}
@@ -104,7 +105,7 @@ public class CachedRelevantStaticMethodSearcher {
final String shortClassName = typeAsShortString(type);
if (targetTypeShortName.equals(shortClassName)) return false;
if (!ChainCompletionStringUtil.isShortNamePrimitiveOrArrayOfPrimitives(shortClassName) &&
!completionContext.contains(type.getCanonicalText())) {
!completionContext.contains(type)) {
return false;
}
}
@@ -15,14 +15,11 @@
*/
package com.intellij.compiler.classFilesIndex.chainsSearch;
import com.intellij.openapi.util.text.StringUtilRt;
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Set;
/**
@@ -36,11 +33,21 @@ public final class ChainCompletionStringUtil {
* isPrimitiveOrArrayOfPrimitives("java.lang.Object") == true
* isPrimitiveOrArrayOfPrimitives("java.lang.Class") == true
*/
public static boolean isPrimitiveOrArrayOfPrimitives(final @Nullable String typeQName) {
if (typeQName == null) {
return false;
public static boolean isPrimitiveOrArrayOfPrimitives(@NotNull PsiType type) {
type = type.getDeepComponentType();
if (type instanceof PsiPrimitiveType) return true;
if (!(type instanceof PsiClassType)) return false;
if (PRIMITIVES_SHORT_NAMES.contains(((PsiClassType)type).getClassName())) return false;
final PsiClass resolvedClass = ((PsiClassType)type).resolve();
if (resolvedClass == null) return false;
final String qName = resolvedClass.getQualifiedName();
if (qName == null) return false;
for (String name : PRIMITIVES_NAMES) {
if (name.equals(qName)) {
return true;
}
}
return PRIMITIVES_NAMES.contains(deleteArraySigns(typeQName));
return false;
}
public static boolean isShortNamePrimitiveOrArrayOfPrimitives(final @Nullable String shortName) {
@@ -58,35 +65,8 @@ public final class ChainCompletionStringUtil {
return nameWithoutArraySign;
}
private static final Set<String> PRIMITIVES_NAMES = new HashSet<>();
private static final Set<String> PRIMITIVES_SHORT_NAMES = new HashSet<>();
static {
fillPrimitivesNames(PsiType.BOOLEAN);
fillPrimitivesNames(PsiType.INT);
fillPrimitivesNames(PsiType.LONG);
fillPrimitivesNames(PsiType.DOUBLE);
fillPrimitivesNames(PsiType.FLOAT);
fillPrimitivesNames(PsiType.SHORT);
fillPrimitivesNames(PsiType.CHAR);
fillPrimitivesNames(PsiType.BYTE);
fillPrimitivesNames(PsiType.VOID);
fillNonPrimitiveNames(CommonClassNames.JAVA_LANG_STRING);
fillNonPrimitiveNames(CommonClassNames.JAVA_LANG_OBJECT);
fillNonPrimitiveNames(CommonClassNames.JAVA_LANG_CLASS);
}
private static void fillNonPrimitiveNames(final String typeAsString) {
PRIMITIVES_NAMES.add(typeAsString);
PRIMITIVES_SHORT_NAMES.add(StringUtilRt.getShortName(typeAsString));
}
private static void fillPrimitivesNames(final PsiPrimitiveType type) {
PRIMITIVES_NAMES.add(type.getBoxedTypeName());
PRIMITIVES_NAMES.add(type.getCanonicalText());
PRIMITIVES_SHORT_NAMES.add(StringUtilRt.getShortName(type.getBoxedTypeName()));
PRIMITIVES_SHORT_NAMES.add(type.getCanonicalText());
}
private static final String[] PRIMITIVES_NAMES = new String [] {CommonClassNames.JAVA_LANG_STRING,
CommonClassNames.JAVA_LANG_OBJECT,
CommonClassNames.JAVA_LANG_CLASS};
private static final Set<String> PRIMITIVES_SHORT_NAMES = ContainerUtil.set("String", "Object", "Class");
}
@@ -22,38 +22,30 @@ import com.intellij.compiler.classFilesIndex.impl.MethodIncompleteSignature;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Dmitry Batkovich
*/
public final class ChainsSearcher {
public class ChainsSearcher {
private static final Logger LOG = Logger.getInstance(ChainsSearcher.class);
private ChainsSearcher() {
}
private static final Logger LOG = Logger.getInstance(ChainsSearcher.class);
private static final double NEXT_METHOD_IN_CHAIN_RATIO = 1.5;
@NotNull
public static List<MethodsChain> search(final int pathMaximalLength,
final TargetType targetType,
final Set<String> contextQNames,
final Set<PsiType> contextQNames,
final int maxResultSize,
final ChainCompletionContext context,
final CompilerReferenceServiceEx methodsUsageIndexReader) {
final SearchInitializer initializer = createInitializer(targetType, context.getExcludedQNames(), methodsUsageIndexReader, context);
if (initializer == null) {
return Collections.emptyList();
}
return search(methodsUsageIndexReader,
final CompilerReferenceServiceEx compilerReferenceServiceEx) {
final SearchInitializer initializer = createInitializer(targetType, compilerReferenceServiceEx, context);
return search(compilerReferenceServiceEx,
initializer,
contextQNames,
pathMaximalLength,
@@ -62,25 +54,24 @@ public final class ChainsSearcher {
context);
}
@Nullable
@NotNull
private static SearchInitializer createInitializer(final TargetType target,
final Set<String> excludedParamsTypesQNames,
final CompilerReferenceServiceEx methodsUsageIndexReader,
final CompilerReferenceServiceEx compilerReferenceServiceEx,
final ChainCompletionContext context) {
final SortedSet<OccurrencesAware<MethodIncompleteSignature>> methods = methodsUsageIndexReader.getMethods(target.getClassQName());
return new SearchInitializer(methods, target.getClassQName(), excludedParamsTypesQNames, context);
final SortedSet<OccurrencesAware<MethodIncompleteSignature>> methods = compilerReferenceServiceEx.getMethods(target.getClassQName());
return new SearchInitializer(methods, target.getPsiType(), context);
}
@NotNull
private static List<MethodsChain> search(final CompilerReferenceServiceEx indexReader,
final SearchInitializer initializer,
final Set<String> toSet,
final Set<PsiType> toSet,
final int pathMaximalLength,
final int maxResultSize,
final String targetQName,
final ChainCompletionContext context) {
final Set<String> allExcludedNames = MethodChainsSearchUtil.joinToHashSet(context.getExcludedQNames(), targetQName);
final SearchInitializer.InitResult initResult = initializer.init(Collections.<String>emptySet());
final Set<PsiType> allExcludedNames = Collections.singleton(context.getTarget().getPsiType());
final SearchInitializer.InitResult initResult = initializer.init(Collections.emptySet());
final Map<MethodIncompleteSignature, MethodsChain> knownDistance = initResult.getChains();
@@ -143,7 +134,7 @@ public final class ChainsSearcher {
if (currentVertexMethodsChain.size() < pathMaximalLength - 1) {
final MethodIncompleteSignature methodInvocation = indexValue.getUnderlying();
final PsiMethod[] psiMethods = context.resolveNotDeprecated(methodInvocation);
if (psiMethods.length != 0 && MethodChainsSearchUtil.checkParametersForTypesQNames(psiMethods, allExcludedNames)) {
if (psiMethods.length != 0 && !MethodChainsSearchUtil.doesMethodsContainParameters(psiMethods, allExcludedNames)) {
final MethodsChain newBestMethodsChain =
currentVertexMethodsChain.addEdge(psiMethods, indexValue.getUnderlying().getOwner(), vertexDistance);
currentSignatures
@@ -16,77 +16,78 @@
package com.intellij.compiler.classFilesIndex.chainsSearch;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.text.EditDistance;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import java.util.stream.Stream;
/**
* @author Dmitry Batkovich
*/
public final class MethodChainsSearchUtil {
private final static int COMMON_PART_MIN_LENGTH = 3;
private MethodChainsSearchUtil() {
}
public static boolean isSimilar(@NotNull final String target,
@NotNull final String candidate) {
return EditDistance.levenshtein(target, sanitizedToLowerCase(candidate), true) >= COMMON_PART_MIN_LENGTH;
}
@NotNull
public static String sanitizedToLowerCase(@NotNull final String name) {
final StringBuilder result = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
final char ch = name.charAt(i);
if (Character.isLetter(ch)) {
result.append(Character.toLowerCase(ch));
}
}
return result.toString();
}
@Nullable
public static PsiMethod getMethodWithMinNotPrimitiveParameters(final @NotNull PsiMethod[] methods,
final Set<String> excludedParamsQNames) {
PsiMethod minMethod = null;
int minParametersCount = Integer.MAX_VALUE;
for (final PsiMethod method : methods) {
final PsiParameterList parameterList = method.getParameterList();
boolean doContinue = false;
int parametersCount = parameterList.getParametersCount();
for (final PsiParameter p : parameterList.getParameters()) {
if (!(p.getType() instanceof PsiPrimitiveType)) {
if (excludedParamsQNames.contains(p.getType().getCanonicalText())) {
doContinue = true;
break;
}
parametersCount++;
}
}
if (doContinue) {
continue;
}
if (parametersCount < minParametersCount) {
if (parametersCount == 0) {
return method;
}
minParametersCount = parametersCount;
minMethod = method;
}
}
return minMethod;
final @NotNull Set<String> excludedQNames) {
return Stream.of(methods)
.filter(m -> !containsParameter(m, excludedQNames))
.sorted(Comparator.comparing(MethodChainsSearchUtil::getNonPrimitiveParameterCount))
.findFirst().orElse(null);
}
public static boolean checkParametersForTypesQNames(final PsiMethod[] psiMethods, final Set<String> excludedTypesQNames) {
if (psiMethods.length == 0) {
return true;
private static boolean containsParameter(@NotNull PsiMethod method, @NotNull Set<String> excludedQNames) {
for (PsiParameter parameter : method.getParameterList().getParameters()) {
final PsiType t = parameter.getType();
final boolean matched = isRawTypeOneOf(t, excludedQNames);
if (matched) {
return false;
}
}
for (final PsiMethod method : psiMethods) {
boolean hasTargetInParams = false;
for (final PsiParameter param : method.getParameterList().getParameters()) {
final String paramType = param.getType().getCanonicalText();
if (excludedTypesQNames.contains(paramType)) {
hasTargetInParams = true;
break;
}
}
if (!hasTargetInParams) {
return true;
}
return true;
}
private static boolean isRawTypeOneOf(@NotNull PsiType type, @NotNull Set<String> qNames) {
final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(type);
return aClass != null && qNames.contains(aClass.getQualifiedName());
}
private static int getNonPrimitiveParameterCount(PsiMethod method) {
return (int)Stream.of(method.getParameterList().getParameters())
.map(p -> p.getType())
.filter(t -> !TypeConversionUtil.isPrimitiveAndNotNull(t))
.count();
}
public static boolean doesMethodsContainParameters(@NotNull final PsiMethod[] psiMethods,
@NotNull final Set<PsiType> parameterRawTypes) {
for (PsiMethod m : psiMethods) {
//TODO
//if (!containsParameter(m, parameterRawTypes)) {
// return true;
//}
}
return false;
}
public static <T> HashSet<T> joinToHashSet(final Collection<T> collection, final T... items) {
final HashSet<T> result = new HashSet<>();
result.addAll(collection);
Collections.addAll(result, items);
return result;
}
}
@@ -17,16 +17,15 @@ package com.intellij.compiler.classFilesIndex.chainsSearch;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInsight.completion.JavaChainLookupElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.context.ChainCompletionContext;
import com.intellij.compiler.classFilesIndex.chainsSearch.context.ContextRelevantStaticMethod;
import com.intellij.compiler.classFilesIndex.chainsSearch.context.ContextRelevantVariableGetter;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.VariableLookupItem;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.ChainCompletionNewVariableLookupElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.WeightableChainLookupElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.sub.GetterLookupSubLookupElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.sub.SubLookupElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.sub.VariableSubLookupElement;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.VariableLookupItem;
import com.intellij.compiler.classFilesIndex.chainsSearch.context.ChainCompletionContext;
import com.intellij.compiler.classFilesIndex.chainsSearch.context.ContextRelevantStaticMethod;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.TIntObjectHashMap;
@@ -41,9 +40,6 @@ import java.util.List;
import static com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.ChainCompletionLookupElementUtil.createLookupElement;
import static com.intellij.psi.CommonClassNames.JAVA_LANG_STRING;
/**
* @author Dmitry Batkovich <dmitry.batkovich@jetbrains.com>
*/
public class MethodsChainLookupRangingHelper {
public static List<LookupElement> chainsToWeightableLookupElements(final List<MethodsChain> chains,
@@ -144,40 +140,26 @@ public class MethodsChainLookupRangingHelper {
final PsiParameter[] parameters = parameterList.getParameters();
for (int i = 0; i < parameters.length; i++) {
final PsiParameter parameter = parameters[i];
final String typeQName = parameter.getType().getCanonicalText();
if (JAVA_LANG_STRING.equals(typeQName)) {
final PsiVariable relevantStringVar = context.findRelevantStringInContext(parameter.getName());
if (relevantStringVar == null) {
final PsiType type = parameter.getType();
if (type.equalsToText(JAVA_LANG_STRING)) {
final PsiElement relevantStringElement = context.findRelevantStringInContext(parameter.getName());
if (relevantStringElement == null) {
notMatchedStringVars++;
}
else {
parametersMap.put(i, new VariableSubLookupElement(relevantStringVar));
parametersMap.put(i, createSubLookup(relevantStringElement));
}
}
else if (!ChainCompletionStringUtil.isPrimitiveOrArrayOfPrimitives(typeQName)) {
final Collection<PsiVariable> contextVariables = context.getVariables(typeQName);
final PsiVariable contextVariable = ContainerUtil.getFirstItem(contextVariables, null);
else if (!ChainCompletionStringUtil.isPrimitiveOrArrayOfPrimitives(type)) {
final Collection<PsiElement> contextVariables = context.getQualifiers(type);
final PsiElement contextVariable = ContainerUtil.getFirstItem(contextVariables, null);
if (contextVariable != null) {
if (contextVariables.size() == 1) parametersMap.put(i, new VariableSubLookupElement(contextVariable));
matchedParametersInContext++;
continue;
}
final Collection<ContextRelevantVariableGetter> relevantVariablesGetters = context.getRelevantVariablesGetters(typeQName);
final ContextRelevantVariableGetter contextVariableGetter = ContainerUtil.getFirstItem(relevantVariablesGetters, null);
if (contextVariableGetter != null) {
if (relevantVariablesGetters.size() == 1) parametersMap.put(i, contextVariableGetter.createSubLookupElement());
matchedParametersInContext++;
continue;
}
final Collection<PsiMethod> containingClassMethods = context.getContainingClassMethods(typeQName);
final PsiMethod contextRelevantGetter = ContainerUtil.getFirstItem(containingClassMethods, null);
if (contextRelevantGetter != null) {
if (containingClassMethods.size() == 1) parametersMap.put(i, new GetterLookupSubLookupElement(method.getName()));
if (contextVariables.size() == 1) parametersMap.put(i, createSubLookup(contextVariable));
matchedParametersInContext++;
continue;
}
final ContextRelevantStaticMethod contextRelevantStaticMethod =
ContainerUtil.getFirstItem(staticMethodSearcher.getRelevantStaticMethods(typeQName, weight), null);
ContainerUtil.getFirstItem(staticMethodSearcher.getRelevantStaticMethods(type, weight), null);
if (contextRelevantStaticMethod != null) {
//
// In most cases it is not really relevant
@@ -201,10 +183,7 @@ public class MethodsChainLookupRangingHelper {
return null;
}
else {
@SuppressWarnings("ConstantConditions")
final String classQName = qualifierClass.getQualifiedName();
if (classQName == null) return null;
final Object e = ContainerUtil.getFirstItem(context.getContextRefElements(classQName), null);
final Object e = ContainerUtil.getFirstItem(context.getQualifiers(qualifierClass), null);
if (e != null) {
final LookupElement firstChainElement;
if (e instanceof PsiVariable) {
@@ -224,9 +203,9 @@ public class MethodsChainLookupRangingHelper {
}
else {
lookupElement = createLookupElement(method, parametersMap);
if (!context.getContainingClassQNames().contains(classQName)) {
introduceNewVariable = true;
}
//if (!context.getContainingClassQNames().contains(classQName)) {
// introduceNewVariable = true;
//}
}
}
}
@@ -241,6 +220,13 @@ public class MethodsChainLookupRangingHelper {
matchedParametersInContext);
}
@NotNull
private static SubLookupElement createSubLookup(PsiElement relevantStringElement) {
return relevantStringElement instanceof PsiMethod
? new GetterLookupSubLookupElement((PsiMethod)relevantStringElement)
: new VariableSubLookupElement((PsiVariable)relevantStringElement);
}
private static class MethodProcResult {
private final LookupElement myMethodLookup;
private final int myUnreachableParametersCount;
@@ -1,111 +0,0 @@
/*
* Copyright 2000-2013 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.compiler.classFilesIndex.chainsSearch;
import com.intellij.compiler.classFilesIndex.chainsSearch.context.ChainCompletionContext;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
/**
* @author Dmitry Batkovich
*/
public final class ParametersMatcher {
private ParametersMatcher() {}
public static MatchResult matchParameters(final MethodsChain chain, final ChainCompletionContext context) {
MatchResult overallResult = EMPTY;
for (final PsiMethod[] methods : chain.getPath()) {
final NavigableSet<MatchResult> matchResults = new TreeSet<>();
for (final PsiMethod method : methods) {
matchResults.add(matchParameters(method, context, chain.getExcludedQNames()));
}
final MatchResult best = matchResults.first();
overallResult = overallResult.add(best);
}
return overallResult;
}
private static MatchResult matchParameters(final PsiMethod method, final ChainCompletionContext context, final Set<String> additionalExcludedNames) {
int matched = 0;
int unMatched = 0;
boolean hasTarget = false;
for (final PsiParameter parameter : method.getParameterList().getParameters()) {
final PsiType type = parameter.getType();
final String canonicalText = type.getCanonicalText();
if (context.contains(canonicalText) || type instanceof PsiPrimitiveType) {
matched++;
}
else {
unMatched++;
}
if (context.getTarget().getClassQName().equals(canonicalText) || additionalExcludedNames.contains(canonicalText)) {
hasTarget = true;
}
}
return new MatchResult(matched, unMatched, hasTarget);
}
private static final MatchResult EMPTY = new MatchResult(0, 0, false);
public static class MatchResult implements Comparable<MatchResult> {
private final int myMatched;
private final int myUnMatched;
private final boolean myHasTarget;
private MatchResult(final int matched, final int unMatched, final boolean hasTarget) {
myMatched = matched;
myUnMatched = unMatched;
myHasTarget = hasTarget;
}
public int getMatched() {
return myMatched;
}
public int getUnMatched() {
return myUnMatched;
}
public boolean hasTarget() {
return myHasTarget;
}
public MatchResult add(final MatchResult other) {
return new MatchResult(getMatched() + other.getMatched(), getUnMatched() + other.getUnMatched(), other.myHasTarget || myHasTarget);
}
public boolean noUnmatchedAndHasMatched() {
return myUnMatched == 0 && myMatched != 0;
}
@Override
public int compareTo(@NotNull final MatchResult other) {
final int sub = getUnMatched() - other.getUnMatched();
if (sub != 0) {
return sub;
}
return getMatched() - other.getMatched();
}
}
}
@@ -16,30 +16,30 @@
package com.intellij.compiler.classFilesIndex.chainsSearch;
import com.intellij.compiler.classFilesIndex.chainsSearch.context.ChainCompletionContext;
import com.intellij.compiler.classFilesIndex.impl.MethodIncompleteSignature;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiMethod;
import com.intellij.compiler.classFilesIndex.impl.MethodIncompleteSignature;
import com.intellij.psi.PsiType;
import java.util.*;
public class SearchInitializer {
private final static int CHAIN_SEARCH_MAGIC_RATIO = 12;
private final static int CHAIN_SEARCH_MAGIC_RATIO = 10;
private final LinkedHashMap<MethodIncompleteSignature, Pair<MethodsChain, Integer>> myChains;
private final ChainCompletionContext myContext;
public SearchInitializer(final SortedSet<OccurrencesAware<MethodIncompleteSignature>> indexValues,
final String targetQName,
final Set<String> excludedParamsTypesQNames,
final PsiType target,
final ChainCompletionContext context) {
myContext = context;
final int size = indexValues.size();
myChains = new LinkedHashMap<>(size);
add(indexValues, MethodChainsSearchUtil.joinToHashSet(excludedParamsTypesQNames, targetQName));
add(indexValues, Collections.singleton(target));
}
private void add(final Collection<OccurrencesAware<MethodIncompleteSignature>> indexValues,
final Set<String> excludedParamsTypesQNames) {
final Set<PsiType> excludedParamsTypesQNames) {
int bestOccurrences = -1;
for (final OccurrencesAware<MethodIncompleteSignature> indexValue : indexValues) {
if (add(indexValue, excludedParamsTypesQNames)) {
@@ -54,10 +54,10 @@ public class SearchInitializer {
}
}
private boolean add(final OccurrencesAware<MethodIncompleteSignature> indexValue, final Set<String> excludedParamsTypesQNames) {
private boolean add(final OccurrencesAware<MethodIncompleteSignature> indexValue, final Set<PsiType> excludedParamsTypesQNames) {
final MethodIncompleteSignature methodInvocation = indexValue.getUnderlying();
final PsiMethod[] psiMethods = myContext.resolveNotDeprecated(methodInvocation);
if (psiMethods.length != 0 && MethodChainsSearchUtil.checkParametersForTypesQNames(psiMethods, excludedParamsTypesQNames)) {
if (psiMethods.length != 0 && !MethodChainsSearchUtil.doesMethodsContainParameters(psiMethods, excludedParamsTypesQNames)) {
final int occurrences = indexValue.getOccurrences();
final MethodsChain methodsChain = new MethodsChain(psiMethods, occurrences, indexValue.getUnderlying().getOwner());
myChains.put(methodInvocation, Pair.create(methodsChain, occurrences));
@@ -23,11 +23,7 @@ import com.intellij.psi.impl.source.tree.java.PsiMethodCallExpressionImpl;
import static com.intellij.patterns.PsiJavaPatterns.psiElement;
import static com.intellij.patterns.StandardPatterns.or;
/**
* @author Dmitry Batkovich <dmitry.batkovich@jetbrains.com>
*/
public final class CompletionContributorPatternUtil {
public class CompletionContributorPatternUtil {
private CompletionContributorPatternUtil() {}
@SuppressWarnings("unchecked")
@@ -42,7 +38,7 @@ public final class CompletionContributorPatternUtil {
.inside(PsiMethod.class);
}
public static ElementPattern<PsiElement> patternForMethodParameter() {
public static ElementPattern<PsiElement> patternForMethodCallParameter() {
return psiElement().withSuperParent(3, PsiMethodCallExpressionImpl.class);
}
}
@@ -9,9 +9,9 @@ import com.intellij.compiler.classFilesIndex.chainsSearch.ChainsSearcher;
import com.intellij.compiler.classFilesIndex.chainsSearch.MethodsChain;
import com.intellij.compiler.classFilesIndex.chainsSearch.MethodsChainLookupRangingHelper;
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.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.patterns.ElementPattern;
import com.intellij.psi.*;
@@ -26,7 +26,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.compiler.classFilesIndex.chainsSearch.completion.CompletionContributorPatternUtil.patternForMethodParameter;
import static com.intellij.compiler.classFilesIndex.chainsSearch.completion.CompletionContributorPatternUtil.patternForMethodCallParameter;
import static com.intellij.compiler.classFilesIndex.chainsSearch.completion.CompletionContributorPatternUtil.patternForVariableAssignment;
import static com.intellij.patterns.PsiJavaPatterns.or;
@@ -34,18 +34,17 @@ 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();
private static final Logger LOG = Logger.getInstance(MethodsChainsCompletionContributor.class);
public static final int INVOCATIONS_THRESHOLD = 2;
private static final boolean IS_UNIT_TEST_MODE = ApplicationManager.getApplication().isUnitTestMode();
private static final int MAX_SEARCH_RESULT_SIZE = 5;
private static final int MAX_CHAIN_SIZE = 4;
private static final int FILTER_RATIO = 10;
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;
@SuppressWarnings("unchecked")
public MethodsChainsCompletionContributor() {
final ElementPattern<PsiElement> pattern = or(patternForMethodParameter(), patternForVariableAssignment());
final ElementPattern<PsiElement> pattern = or(patternForMethodCallParameter(), patternForVariableAssignment());
extend(COMPLETION_TYPE, pattern, new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(final @NotNull CompletionParameters parameters,
@@ -54,15 +53,15 @@ public class MethodsChainsCompletionContributor extends CompletionContributor {
final ChainCompletionContext completionContext = extractContext(parameters);
if (completionContext == null) return;
final Set<String> contextTypesKeysSet = completionContext.getContextTypes();
final Set<String> contextRelevantTypes = new HashSet<>(contextTypesKeysSet.size() + 1);
for (final String type : contextTypesKeysSet) {
final Set<PsiType> contextTypesKeysSet = completionContext.getContextTypes();
final Set<PsiType> contextRelevantTypes = new HashSet<>(contextTypesKeysSet.size() + 1);
for (final PsiType type : contextTypesKeysSet) {
if (!ChainCompletionStringUtil.isPrimitiveOrArrayOfPrimitives(type)) {
contextRelevantTypes.add(type);
}
}
final TargetType target = completionContext.getTarget();
contextRelevantTypes.remove(target.getClassQName());
contextRelevantTypes.remove(target.getPsiType());
final List<LookupElement> elementsFoundByMethodsChainsSearch = searchForLookups(target, contextRelevantTypes, completionContext);
if (!IS_UNIT_TEST_MODE) {
result.runRemainingContributors(parameters, completionResult -> {
@@ -87,7 +86,7 @@ public class MethodsChainsCompletionContributor extends CompletionContributor {
}
private static List<LookupElement> searchForLookups(final TargetType target,
final Set<String> contextRelevantTypes,
final Set<PsiType> contextRelevantTypes,
final ChainCompletionContext completionContext) {
final Project project = completionContext.getProject();
final CompilerReferenceServiceEx methodsUsageIndexReader = (CompilerReferenceServiceEx)CompilerReferenceService.getInstance(project);
@@ -137,22 +136,16 @@ public class MethodsChainsCompletionContributor extends CompletionContributor {
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;
}
final PsiElement parent = PsiTreeUtil.getParentOfType(parameters.getPosition(), PsiAssignmentExpression.class, PsiLocalVariable.class, PsiMethodCallExpression.class);
LOG.assertTrue(parent != null, "A completion position should match to a pattern");
if (parent instanceof PsiAssignmentExpression) {
return tryExtractContextFromAssignment((PsiAssignmentExpression)parent);
return extractContextFromAssignment((PsiAssignmentExpression)parent);
}
if (parent instanceof PsiLocalVariable) {
final PsiLocalVariable localVariable = (PsiLocalVariable)parent;
return ContextUtil.createContext(localVariable.getType(), localVariable.getName(),
PsiTreeUtil.getParentOfType(parent, PsiDeclarationStatement.class));
return extractContextFromVariable((PsiLocalVariable)parent);
}
final PsiMethod method = ((PsiMethodCallExpression)parent).resolveMethod();
if (method == null) return null;
@@ -163,19 +156,26 @@ public class MethodsChainsCompletionContributor extends CompletionContributor {
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 ChainCompletionContext.createContext(methodParameter.getType(), null, PsiTreeUtil.getParentOfType(expression, PsiDeclarationStatement.class));
}
return null;
}
@Nullable
private static ChainCompletionContext tryExtractContextFromAssignment(final PsiAssignmentExpression assignmentExpression) {
private static ChainCompletionContext extractContextFromVariable(PsiLocalVariable localVariable) {
final PsiType varType = localVariable.getType();
final String varName = localVariable.getName();
final PsiDeclarationStatement declaration = PsiTreeUtil.getParentOfType(localVariable, PsiDeclarationStatement.class);
return ChainCompletionContext.createContext(varType, varName, declaration);
}
@Nullable
private static ChainCompletionContext extractContextFromAssignment(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);
return ChainCompletionContext.createContext(type, identifierText, assignmentExpression);
}
private static List<MethodsChain> filterTailAndGetSumLastMethodOccurrence(final List<MethodsChain> chains) {
@@ -198,7 +198,7 @@ public class MethodsChainsCompletionContributor extends CompletionContributor {
}
private static List<MethodsChain> searchChains(final TargetType target,
final Set<String> contextVarsQNames,
final Set<PsiType> contextVarsQNames,
final int maxResultSize,
final int maxChainSize,
final ChainCompletionContext context,
@@ -16,6 +16,7 @@
package com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.sub;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.PsiMethod;
import org.jetbrains.annotations.Nullable;
/**
@@ -25,8 +26,8 @@ public class GetterLookupSubLookupElement implements SubLookupElement {
private final String myVariableName;
private final String myMethodName;
public GetterLookupSubLookupElement(final String methodName) {
this(null, methodName);
public GetterLookupSubLookupElement(final PsiMethod method) {
this(null, method.getName());
}
public GetterLookupSubLookupElement(@Nullable final String variableName, final String methodName) {
@@ -15,134 +15,97 @@
*/
package com.intellij.compiler.classFilesIndex.chainsSearch.context;
import com.intellij.compiler.classFilesIndex.chainsSearch.CachedRelevantStaticMethodSearcher;
import com.intellij.compiler.classFilesIndex.chainsSearch.ChainCompletionStringUtil;
import com.intellij.compiler.classFilesIndex.chainsSearch.MethodChainsSearchUtil;
import com.intellij.compiler.classFilesIndex.impl.MethodIncompleteSignature;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.psi.scope.BaseScopeProcessor;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.containers.MultiMap;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.intellij.compiler.classFilesIndex.impl.MethodIncompleteSignature;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author Dmitry Batkovich
*/
public class ChainCompletionContext {
@NotNull
private final TargetType myTarget;
private final Set<String> myContainingClassQNames;
private final MultiMap<String, PsiVariable> myContextVars;
private final MultiMap<String, PsiMethod> myContainingClassGetters;
private final MultiMap<String, ContextRelevantVariableGetter> myContextVarsGetters;
private final Map<String, PsiVariable> myStringVars;
private final Set<String> myExcludedQNames;
@NotNull
private final List<PsiNamedElement> myContextElements;
@NotNull
private final List<PsiNamedElement> myContextStrings;
@NotNull
private final PsiElement myContext;
@NotNull
private final GlobalSearchScope myResolveScope;
@NotNull
private final Project myProject;
@NotNull
private final PsiManager myPsiManager;
@NotNull
private final MethodIncompleteSignatureResolver myNotDeprecatedMethodsResolver;
private final NotNullLazyValue<Set<String>> contextTypesQNames = new NotNullLazyValue<Set<String>>() {
@SuppressWarnings("unchecked")
@NotNull
@Override
protected Set<String> compute() {
return unionToHashSet(myContainingClassQNames,
myContextVars.keySet(),
myContainingClassGetters.keySet(),
myContextVarsGetters.keySet());
}
};
public Set<String> getExcludedQNames() {
return myExcludedQNames;
}
ChainCompletionContext(final TargetType target,
final Set<String> containingClassQNames,
final MultiMap<String, PsiVariable> contextVars,
final MultiMap<String, PsiMethod> containingClassGetters,
final MultiMap<String, ContextRelevantVariableGetter> contextVarsGetters,
final Map<String, PsiVariable> stringVars,
final Set<String> excludedQNames,
final Project project,
final GlobalSearchScope resolveScope) {
public ChainCompletionContext(@NotNull TargetType target,
@NotNull List<PsiNamedElement> contextElements,
@NotNull List<PsiNamedElement> contextStrings,
@NotNull PsiElement context) {
myTarget = target;
myContainingClassQNames = containingClassQNames;
myContextVars = contextVars;
myContainingClassGetters = containingClassGetters;
myContextVarsGetters = contextVarsGetters;
myStringVars = stringVars;
myExcludedQNames = excludedQNames;
myResolveScope = resolveScope;
myProject = project;
myPsiManager = PsiManager.getInstance(project);
myNotDeprecatedMethodsResolver = new MethodIncompleteSignatureResolver(JavaPsiFacade.getInstance(project), resolveScope);
myContextElements = contextElements;
myContextStrings = contextStrings;
myContext = context;
myResolveScope = context.getResolveScope();
myProject = context.getProject();
myPsiManager = PsiManager.getInstance(myProject);
myNotDeprecatedMethodsResolver = new MethodIncompleteSignatureResolver(JavaPsiFacade.getInstance(myProject), myResolveScope);
}
@NotNull
public TargetType getTarget() {
return myTarget;
}
@Nullable
public PsiVariable findRelevantStringInContext(@Nullable final String stringParamName) {
if (stringParamName == null) {
return null;
}
for (final Map.Entry<String, PsiVariable> e : myStringVars.entrySet()) {
if (ChainCompletionContextStringUtil.isSimilar(e.getKey(), stringParamName)) {
return e.getValue();
@NotNull
public List<PsiNamedElement> getContextElements() {
return myContextElements;
}
public boolean contains(@Nullable final PsiType type) {
if (type == null) return false;
final Set<PsiType> types = getContextTypes();
if (types.contains(type)) return true;
for (PsiType contextType : types) {
if (type.isAssignableFrom(contextType)) {
return true;
}
}
return null;
return false;
}
public Set<String> getContainingClassQNames() {
return myContainingClassQNames;
}
public Collection<PsiVariable> getVariables(final String typeQName) {
return myContextVars.get(typeQName);
}
public Collection<PsiMethod> getContainingClassMethods(final String typeQName) {
return myContainingClassGetters.get(typeQName);
}
public Collection<ContextRelevantVariableGetter> getRelevantVariablesGetters(final String typeQName) {
return myContextVarsGetters.get(typeQName);
}
public Collection<?> getContextRefElements(final String typeQName) {
final Collection<PsiVariable> variables = getVariables(typeQName);
final Collection<PsiMethod> containingClassMethods = getContainingClassMethods(typeQName);
final Collection<UserDataHolder> refElements = new ArrayList<>(variables.size() + containingClassMethods.size());
refElements.addAll(variables);
refElements.addAll(containingClassMethods);
for (final ContextRelevantVariableGetter contextRelevantVariableGetter : getRelevantVariablesGetters(typeQName)) {
refElements.add(contextRelevantVariableGetter.createLookupElement());
}
return refElements;
}
public boolean contains(@Nullable final String typeQualifierName) {
return typeQualifierName != null && contextTypesQNames.getValue().contains(typeQualifierName);
}
public Set<String> getContextTypes() {
return contextTypesQNames.getValue();
@NotNull
public Set<PsiType> getContextTypes() {
return myContextElements.stream().map(ChainCompletionContext::getType).collect(Collectors.toSet());
}
@NotNull
public GlobalSearchScope getResolveScope() {
return myResolveScope;
}
@NotNull
public Project getProject() {
return myProject;
}
@NotNull
public PsiManager getPsiManager() {
return myPsiManager;
}
@@ -152,11 +115,113 @@ public class ChainCompletionContext {
return myNotDeprecatedMethodsResolver.get(methodIncompleteSignature);
}
private static <T> HashSet<T> unionToHashSet(final Collection<T>... collections) {
final HashSet<T> res = new HashSet<>();
for (final Collection<T> set : collections) {
res.addAll(set);
@Nullable
public PsiElement findRelevantStringInContext(String stringParameterName) {
String sanitizedTarget = MethodChainsSearchUtil.sanitizedToLowerCase(stringParameterName);
return myContextStrings.stream().filter(e -> {
String name = e.getName();
return name != null && MethodChainsSearchUtil.isSimilar(sanitizedTarget, name);
}).findFirst().orElse(null);
}
public Collection<PsiElement> getQualifiers(@Nullable PsiClass targetType) {
if (targetType == null) return Collections.emptyList();
return getQualifiers(JavaPsiFacade.getInstance(myProject).getElementFactory().createType(targetType));
}
public Collection<PsiElement> getQualifiers(@NotNull PsiType targetType) {
return myContextElements.stream().filter(e -> {
final PsiType elementType = getType(e);
return elementType != null && targetType.isAssignableFrom(elementType);
}).collect(Collectors.toList());
}
@Nullable
public static ChainCompletionContext createContext(final @Nullable PsiType variableType,
final @Nullable String variableName,
final @Nullable PsiElement containingElement) {
if (containingElement == null) return null;
final TargetType target = TargetType.create(variableType);
if (target == null) return null;
final ContextProcessor processor = new ContextProcessor(null, containingElement.getProject(), containingElement);
PsiScopesUtil.treeWalkUp(processor, containingElement, containingElement.getContainingFile());
final List<PsiNamedElement> contextElements = processor.getContextElements();
final List<PsiNamedElement> contextStrings = processor.getContextStrings();
return new ChainCompletionContext(target, contextElements, contextStrings, containingElement);
}
private static class ContextProcessor extends BaseScopeProcessor implements ElementClassHint {
private final List<PsiNamedElement> myContextElements = new SmartList<>();
private final List<PsiNamedElement> myContextStrings = new SmartList<>();
private final PsiVariable myCompletionVariable;
private final PsiResolveHelper myResolveHelper;
private final PsiElement myPlace;
private ContextProcessor(@Nullable PsiVariable variable,
@NotNull Project project,
@NotNull PsiElement place) {
myCompletionVariable = variable;
myResolveHelper = PsiResolveHelper.SERVICE.getInstance(project);
myPlace = place;
}
return res;
@Override
public boolean shouldProcess(DeclarationKind kind) {
return kind == DeclarationKind.ENUM_CONST ||
kind == DeclarationKind.FIELD ||
kind == DeclarationKind.METHOD ||
kind == DeclarationKind.VARIABLE;
}
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
if ((!(element instanceof PsiMethod) || PropertyUtil.isSimplePropertyAccessor((PsiMethod)element)) &&
(!(element instanceof PsiMember) || myResolveHelper.isAccessible((PsiMember)element, myPlace, null))) {
final PsiType type = getType(element);
if (type == null) {
return false;
}
if (ChainCompletionStringUtil.isPrimitiveOrArrayOfPrimitives(type)) {
if (type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
myContextStrings.add((PsiNamedElement)element);
}
return false;
}
myContextElements.add((PsiNamedElement)element);
}
return true;
}
@Override
public <T> T getHint(@NotNull Key<T> hintKey) {
if (hintKey == ElementClassHint.KEY) {
return (T)this;
}
return super.getHint(hintKey);
}
@NotNull
public List<PsiNamedElement> getContextElements() {
myContextElements.remove(myCompletionVariable);
return myContextElements;
}
@NotNull
public List<PsiNamedElement> getContextStrings() {
return myContextStrings;
}
}
@Nullable
private static PsiType getType(PsiElement element) {
if (element instanceof PsiVariable) {
return ((PsiVariable)element).getType();
}
if (element instanceof PsiMethod) {
return ((PsiMethod)element).getReturnType();
}
throw new AssertionError(element);
}
}
@@ -1,51 +0,0 @@
/*
* Copyright 2000-2013 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.compiler.classFilesIndex.chainsSearch.context;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
/**
* @author Dmitry Batkovich
*/
class ChainCompletionContextStringUtil {
private ChainCompletionContextStringUtil(){}
private final static int COMMON_PART_MIN_LENGTH = 3;
public static boolean isSimilar(@NotNull final String varName,
@NotNull final String parameterName) {
final String sanitizedParamName = sanitizedToLowerCase(parameterName);
if (StringUtil.commonPrefix(varName, sanitizedParamName).length() >= COMMON_PART_MIN_LENGTH) {
return true;
}
final String suffix = StringUtil.commonSuffix(varName, sanitizedParamName);
return suffix.length() >= COMMON_PART_MIN_LENGTH;
}
@NotNull
public static String sanitizedToLowerCase(@NotNull final String name) {
final StringBuilder result = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
final char ch = name.charAt(i);
if (Character.isLetter(ch)) {
result.append(Character.toLowerCase(ch));
}
}
return result.toString();
}
}
@@ -1,46 +0,0 @@
/*
* Copyright 2000-2013 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.compiler.classFilesIndex.chainsSearch.context;
import com.intellij.codeInsight.completion.JavaChainLookupElement;
import com.intellij.codeInsight.completion.JavaMethodCallElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.sub.GetterLookupSubLookupElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.sub.SubLookupElement;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.VariableLookupItem;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiVariable;
/**
* @author Dmitry Batkovich <dmitry.batkovich@jetbrains.com>
*/
public class ContextRelevantVariableGetter {
private final PsiVariable myVariable;
private final PsiMethod myMethod;
public ContextRelevantVariableGetter(final PsiVariable variable, final PsiMethod method) {
myVariable = variable;
myMethod = method;
}
public SubLookupElement createSubLookupElement() {
return new GetterLookupSubLookupElement(myVariable.getName(), myMethod.getName());
}
public LookupElement createLookupElement() {
return new JavaChainLookupElement(new VariableLookupItem(myVariable), new JavaMethodCallElement(myMethod));
}
}
@@ -1,257 +0,0 @@
/*
* Copyright 2000-2013 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.compiler.classFilesIndex.chainsSearch.context;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.SmartList;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Dmitry Batkovich
*/
public final class ContextUtil {
@Nullable
public static ChainCompletionContext createContext(final @Nullable PsiType variableType,
final @Nullable String variableName,
final @Nullable PsiElement containingElement) {
if (variableType == null || containingElement == null) {
return null;
}
final TargetType target;
if (variableType instanceof PsiClassType) {
target = TargetType.create((PsiClassType)variableType);
}
else if (variableType instanceof PsiArrayType) {
target = TargetType.create((PsiArrayType)variableType);
}
else {
return null;
}
if (target == null) {
return null;
}
final PsiMethod method = PsiTreeUtil.getParentOfType(containingElement, PsiMethod.class);
if (method == null) {
return null;
}
final PsiClass aClass = method.getContainingClass();
if (aClass == null) {
return null;
}
final Set<String> containingClassQNames = resolveSupersNamesRecursively(aClass);
final List<PsiVariable> contextVars = new SmartList<>();
for (final PsiField field : aClass.getFields()) {
final PsiClass containingClass = field.getContainingClass();
if (containingClass != null) {
if ((field.hasModifierProperty(PsiModifier.PUBLIC) ||
field.hasModifierProperty(PsiModifier.PROTECTED) ||
((field.hasModifierProperty(PsiModifier.PRIVATE) || field.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) &&
aClass.isEquivalentTo(containingClass))) && !field.getName().equals(variableName)) {
contextVars.add(field);
}
}
}
Collections.addAll(contextVars, method.getParameterList().getParameters());
final PsiCodeBlock methodBody = method.getBody();
assert methodBody != null;
boolean processMethodTail = false;
final List<PsiElement> afterElements = new ArrayList<>();
for (final PsiElement element : methodBody.getChildren()) {
if (element.isEquivalentTo(containingElement)) {
if (variableType instanceof PsiClassType) {
processMethodTail = true;
continue;
}
else {
break;
}
}
if (element instanceof PsiDeclarationStatement) {
if (processMethodTail) {
afterElements.add(element);
}
else {
for (final PsiElement declaredElement : ((PsiDeclarationStatement)element).getDeclaredElements()) {
if (declaredElement instanceof PsiLocalVariable &&
(variableName == null || !variableName.equals(((PsiLocalVariable)declaredElement).getName()))) {
contextVars.add((PsiVariable)declaredElement);
}
}
}
}
}
final Set<String> excludedQNames = processMethodTail
? generateExcludedQNames(afterElements, ((PsiClassType)variableType).resolve(), variableName,
contextVars)
: Collections.<String>emptySet();
final List<PsiMethod> contextMethods = new ArrayList<>();
for (final PsiMethod psiMethod : aClass.getMethods()) {
if ((psiMethod.hasModifierProperty(PsiModifier.PROTECTED) || psiMethod.hasModifierProperty(PsiModifier.PRIVATE)) &&
psiMethod.getParameterList().getParametersCount() == 0) {
contextMethods.add(psiMethod);
}
}
return create(target, contextVars, contextMethods, containingClassQNames, containingElement.getProject(),
containingElement.getResolveScope(), excludedQNames);
}
private static Set<String> generateExcludedQNames(final List<PsiElement> tailElements,
final @Nullable PsiClass psiClass,
final @Nullable String varName,
final List<PsiVariable> contextVars) {
if (psiClass == null) {
return Collections.emptySet();
}
final String classQName = psiClass.getQualifiedName();
if (classQName == null) {
return Collections.emptySet();
}
final Set<String> excludedQNames = new HashSet<>();
if (!tailElements.isEmpty()) {
final Set<String> contextVarTypes = new HashSet<>();
final Map<String, PsiVariable> contextVarNamesToVar = new HashMap<>();
for (final PsiVariable var : contextVars) {
contextVarTypes.add(var.getType().getCanonicalText());
contextVarNamesToVar.put(var.getName(), var);
}
for (final PsiElement element : tailElements) {
final Collection<PsiMethodCallExpression> methodCallExpressions =
PsiTreeUtil.findChildrenOfType(element, PsiMethodCallExpression.class);
for (final PsiMethodCallExpression methodCallExpression : methodCallExpressions) {
final PsiExpressionList args = methodCallExpression.getArgumentList();
final PsiMethod resolvedMethod = methodCallExpression.resolveMethod();
if (resolvedMethod != null) {
final PsiType returnType = resolvedMethod.getReturnType();
if (returnType != null) {
final String returnTypeAsString = returnType.getCanonicalText();
for (final PsiExpression expression : args.getExpressions()) {
final String qVarName = expression.getText();
if (qVarName != null) {
if (contextVarNamesToVar.containsKey(qVarName) || qVarName.equals(varName)) {
excludedQNames.add(returnTypeAsString);
}
}
}
if (!contextVarTypes.contains(returnTypeAsString)) {
excludedQNames.add(returnTypeAsString);
}
}
}
}
}
}
return excludedQNames;
}
@Nullable
private static ChainCompletionContext create(final TargetType target,
final List<PsiVariable> contextVars,
final List<PsiMethod> contextMethods,
final Set<String> containingClassQNames,
final Project project,
final GlobalSearchScope resolveScope,
final Set<String> excludedQNames) {
final MultiMap<String, PsiVariable> classQNameToVariable = new MultiMap<>();
final MultiMap<String, PsiMethod> containingClassGetters = new MultiMap<>();
final MultiMap<String, ContextRelevantVariableGetter> contextVarsGetters = new MultiMap<>();
final Map<String, PsiVariable> stringVars = new HashMap<>();
for (final PsiMethod method : contextMethods) {
final PsiType returnType = method.getReturnType();
if (returnType != null) {
final String returnTypeQName = returnType.getCanonicalText();
containingClassGetters.putValue(returnTypeQName, method);
}
}
for (final PsiVariable var : contextVars) {
final PsiType type = var.getType();
final Set<String> classQNames = new HashSet<>();
if (type instanceof PsiClassType) {
if (JAVA_LANG_STRING_SHORT_NAME.equals(((PsiClassType)type).getClassName())) {
final String varName = var.getName();
if (varName != null) {
stringVars.put(ChainCompletionContextStringUtil.sanitizedToLowerCase(varName), var);
continue;
}
}
final PsiClass aClass = ((PsiClassType)type).resolve();
if (aClass != null) {
final String classQName = type.getCanonicalText();
if (!target.getClassQName().equals(classQName)) {
classQNames.add(classQName);
classQNames.addAll(resolveSupersNamesRecursively(aClass));
for (final PsiMethod method : aClass.getAllMethods()) {
if (method.getParameterList().getParametersCount() == 0 && method.getName().startsWith("get")) {
final PsiType returnType = method.getReturnType();
if (returnType != null) {
final String getterReturnTypeQName = returnType.getCanonicalText();
contextVarsGetters.putValue(getterReturnTypeQName, new ContextRelevantVariableGetter(var, method));
}
}
}
}
}
}
else {
final String classQName = type.getCanonicalText();
classQNames.add(classQName);
}
for (final String qName : classQNames) {
classQNameToVariable.putValue(qName, var);
}
}
return new ChainCompletionContext(target, containingClassQNames, classQNameToVariable, containingClassGetters,
contextVarsGetters, stringVars, excludedQNames, project, resolveScope);
}
@NotNull
private static Set<String> resolveSupersNamesRecursively(@Nullable final PsiClass psiClass) {
final Set<String> result = new HashSet<>();
if (psiClass != null) {
for (final PsiClass superClass : psiClass.getSupers()) {
final String qualifiedName = superClass.getQualifiedName();
if (!CommonClassNames.JAVA_LANG_OBJECT.equals(qualifiedName)) {
if (qualifiedName != null) {
result.add(qualifiedName);
}
result.addAll(resolveSupersNamesRecursively(superClass));
}
}
}
return result;
}
private final static String JAVA_LANG_STRING_SHORT_NAME = StringUtil.getShortName(CommonClassNames.JAVA_LANG_STRING);
}
@@ -51,7 +51,18 @@ public class TargetType {
}
@Nullable
public static TargetType create(final PsiArrayType arrayType) {
public static TargetType create(final PsiType type) {
if (type instanceof PsiArrayType) {
return create((PsiArrayType)type);
}
else if (type instanceof PsiClassType) {
return create((PsiClassType)type);
}
return null;
}
@Nullable
private static TargetType create(final PsiArrayType arrayType) {
PsiType currentComponentType = arrayType.getComponentType();
while (currentComponentType instanceof PsiArrayType) {
currentComponentType = ((PsiArrayType)currentComponentType).getComponentType();
@@ -64,7 +75,7 @@ public class TargetType {
}
@Nullable
public static TargetType create(final PsiClassType classType) {
private static TargetType create(final PsiClassType classType) {
final PsiClassType.ClassResolveResult resolvedGenerics = classType.resolveGenerics();
final PsiClass resolvedClass = resolvedGenerics.getElement();
if (resolvedClass == null) {
@@ -18,7 +18,6 @@ package com.intellij.codeInsight.completion;
import com.intellij.JavaTestUtil;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.ChainRelevance;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.MethodsChainsCompletionContributor;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.ChainCompletionMethodCallLookupElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.WeightableChainLookupElement;
import com.intellij.ide.util.PropertiesComponent;
@@ -236,7 +235,7 @@ public class MethodChainsCompletionTest extends AbstractCompilerAwareTest {
.setValue(ChainCompletionMethodCallLookupElement.PROP_METHODS_CHAIN_COMPLETION_AUTO_COMPLETION, String.valueOf(true));
compileAndIndexData(TEST_INDEX_FILE_NAME);
myFixture.configureByFiles(getBeforeCompletionFilePath());
myFixture.complete(CompletionType.BASIC, MethodsChainsCompletionContributor.INVOCATIONS_THRESHOLD);
myFixture.complete(CompletionType.BASIC);
PropertiesComponent.getInstance(getProject())
.setValue(ChainCompletionMethodCallLookupElement.PROP_METHODS_CHAIN_COMPLETION_AUTO_COMPLETION, String.valueOf(false));
myFixture.checkResultByFile(getAfterCompletionFilePath());
@@ -257,7 +256,7 @@ public class MethodChainsCompletionTest extends AbstractCompilerAwareTest {
private LookupElement[] runCompletion() {
myFixture.configureByFiles(getTestCompletionFilePath());
final LookupElement[] lookupElements =
myFixture.complete(CompletionType.BASIC, MethodsChainsCompletionContributor.INVOCATIONS_THRESHOLD);
myFixture.complete(CompletionType.BASIC);
return lookupElements == null ? LookupElement.EMPTY_ARRAY : lookupElements;
}