method chains search: check assignability; use name suggester; use JavaChainLookupElement

This commit is contained in:
Dmitry Batkovich
2017-04-28 18:20:11 +03:00
parent 615ee3533c
commit a82a3842bb
9 changed files with 63 additions and 75 deletions
@@ -22,6 +22,7 @@ import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiUtil
import org.jetbrains.jps.backwardRefs.LightRef
import org.jetbrains.jps.backwardRefs.SignatureData
import java.util.function.Predicate
class MethodIncompleteSignature(val ref: LightRef.JavaLightMethodRef,
private val signatureData: SignatureData,
@@ -48,15 +49,23 @@ class MethodIncompleteSignature(val ref: LightRef.JavaLightMethodRef,
val isStatic: Boolean
get() = signatureData.isStatic
fun resolveQualifier(project: Project, resolveScope: GlobalSearchScope) = JavaPsiFacade.getInstance(project).findClass(owner, resolveScope)
fun resolveQualifier(project: Project,
resolveScope: GlobalSearchScope,
accessValidator: Predicate<PsiMember>): PsiClass? {
val clazz = JavaPsiFacade.getInstance(project).findClass(owner, resolveScope)
return if (clazz != null && accessValidator.test(clazz)) clazz else null
}
fun resolve(project: Project, resolveScope: GlobalSearchScope): Array<PsiMethod> {
fun resolve(project: Project,
resolveScope: GlobalSearchScope,
accessValidator: Predicate<PsiMember>): Array<PsiMethod> {
if (CONSTRUCTOR_METHOD_NAME == name) {
return PsiMethod.EMPTY_ARRAY
}
val aClass = resolveQualifier(project, resolveScope) ?: return PsiMethod.EMPTY_ARRAY
val aClass = resolveQualifier(project, resolveScope, accessValidator) ?: return PsiMethod.EMPTY_ARRAY
return aClass.findMethodsByName(name, true)
.filter { it.hasModifierProperty(PsiModifier.STATIC) == isStatic }
.filter { accessValidator.test(it) }
.filter {
val returnType = it.returnType
when (signatureData.iteratorKind) {
@@ -49,7 +49,6 @@ public class ChainsSearcher {
initializer,
pathMaximalLength,
maxResultSize,
searchTarget.getClassQName(),
context);
}
@@ -75,7 +74,6 @@ public class ChainsSearcher {
SearchInitializer initializer,
int pathMaximalLength,
int maxResultSize,
String targetQName,
ChainCompletionContext context) {
SearchInitializer.InitResult initResult = initializer.init(Collections.emptySet());
@@ -90,7 +88,7 @@ public class ChainsSearcher {
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedList::new));
ResultHolder result = new ResultHolder(context.getPsiManager());
ResultHolder result = new ResultHolder();
while (!q.isEmpty()) {
ProgressManager.checkCanceled();
OccurrencesAware<MethodsChain> currentVertex = q.poll();
@@ -109,6 +107,7 @@ public class ChainsSearcher {
SortedSet<OccurrencesAware<MethodIncompleteSignature>> nextMethods = indexReader.findMethodReferenceOccurrences(currentReturnType, SignatureData.ZERO_DIM);
MaxSizeTreeSet<OccurrencesAware<MethodIncompleteSignature>> currentSignatures =
new MaxSizeTreeSet<>(maxResultSize);
String targetQName = context.getTarget().getClassQName();
for (OccurrencesAware<MethodIncompleteSignature> indexValue : nextMethods) {
MethodIncompleteSignature vertex = indexValue.getUnderlying();
int occurrences = indexValue.getOccurrences();
@@ -181,10 +180,8 @@ public class ChainsSearcher {
private static class ResultHolder {
private final List<MethodsChain> myResult;
private final PsiManager myContext;
private ResultHolder(PsiManager psiManager) {
myContext = psiManager;
private ResultHolder() {
myResult = new ArrayList<>();
}
@@ -224,14 +221,14 @@ public class ChainsSearcher {
}
public List<MethodsChain> getResult() {
return findSimilar(reduceChainsSize(myResult, PsiManager.getInstance(myContext.getProject())), myContext);
return findSimilar(reduceChainsSize(myResult));
}
public int size() {
return myResult.size();
}
private static List<MethodsChain> reduceChainsSize(List<MethodsChain> chains, PsiManager psiManager) {
private static List<MethodsChain> reduceChainsSize(List<MethodsChain> chains) {
return ContainerUtil.map(chains, chain -> {
Iterator<PsiMethod[]> chainIterator = chain.iterator();
if (!chainIterator.hasNext()) {
@@ -255,11 +252,11 @@ public class ChainsSearcher {
PsiMethod[] fSupers = f.findDeepestSuperMethods();
PsiMethod fSuper = fSupers.length == 0 ? first[0] : fSupers[0];
for (PsiMethod currentMethod : currentMethods) {
if (psiManager.areElementsEquivalent(currentMethod, fSuper)) {
if (currentMethod == fSuper) {
return createChainFromFirstElement(chain, currentMethod.getContainingClass());
}
for (PsiMethod method : currentMethod.findDeepestSuperMethods()) {
if (psiManager.areElementsEquivalent(method, fSuper)) {
if (method == fSuper) {
return createChainFromFirstElement(chain, method.getContainingClass());
}
}
@@ -271,8 +268,8 @@ public class ChainsSearcher {
});
}
private static List<MethodsChain> findSimilar(List<MethodsChain> chains, PsiManager psiManager) {
ResultHolder resultHolder = new ResultHolder(psiManager);
private static List<MethodsChain> findSimilar(List<MethodsChain> chains) {
ResultHolder resultHolder = new ResultHolder();
for (MethodsChain chain : chains) {
resultHolder.add(chain);
}
@@ -95,7 +95,7 @@ public class MethodsChainLookupRangingHelper {
}
if (newVariableClass != null) {
chainLookupElement = new ChainCompletionNewVariableLookupElement(newVariableClass, chainLookupElement);
chainLookupElement = new JavaChainLookupElement(new ChainCompletionNewVariableLookupElement(newVariableClass), chainLookupElement);
}
ChainRelevance relevance =
@@ -16,43 +16,47 @@
package com.intellij.compiler.chainsSearch.completion.lookup;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementDecorator;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Comparator;
import java.util.stream.Stream;
/**
* @author Dmitry Batkovich
*/
public class ChainCompletionNewVariableLookupElement extends LookupElementDecorator<LookupElement> {
private static final Logger LOG = Logger.getInstance(ChainCompletionNewVariableLookupElement.class);
public class ChainCompletionNewVariableLookupElement extends LookupElement {
@NotNull
private final PsiClass myQualifierClass;
@NotNull
private final String myNewVarName;
public ChainCompletionNewVariableLookupElement(@NotNull final PsiClass qualifierClass, final LookupElement calledMethods) {
super(calledMethods);
myNewVarName = StringUtil.decapitalize(ObjectUtils.notNull(qualifierClass.getName()));
public ChainCompletionNewVariableLookupElement(@NotNull final PsiClass qualifierClass) {
Project project = qualifierClass.getProject();
JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project);
PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
myNewVarName = Stream
.of(codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, null, elementFactory.createType(qualifierClass)).names)
.sorted(Comparator.comparing(String::length).reversed())
.findFirst()
.orElseThrow(IllegalStateException::new);
myQualifierClass = qualifierClass;
}
@Override
public AutoCompletionPolicy getAutoCompletionPolicy() {
return AutoCompletionPolicy.NEVER_AUTOCOMPLETE;
}
@Override
public void handleInsert(final InsertionContext context) {
final RangeMarker rangeMarker = context.getDocument().createRangeMarker(context.getStartOffset(), context.getStartOffset());
getDelegate().handleInsert(context);
context.getDocument().insertString(rangeMarker.getStartOffset(), myNewVarName + ".");
context.commitDocument();
final PsiFile file = context.getFile();
((PsiJavaFile)file).importClass(myQualifierClass);
final PsiElement caretElement = ObjectUtils.notNull(file.findElementAt(context.getEditor().getCaretModel().getOffset()));
@@ -71,33 +75,12 @@ public class ChainCompletionNewVariableLookupElement extends LookupElementDecora
elementFactory.createType(myQualifierClass),
elementFactory.createExpressionFromText(PsiKeyword.NULL, null));
PsiStatement newVarDeclaration = (PsiStatement)statement.getParent().addBefore(newVarDeclarationTemplate, statement);
final PsiLiteralExpression nullKeyword = findNullElement(newVarDeclaration);
final int offset = nullKeyword.getTextOffset();
final int endOffset = offset + nullKeyword.getTextLength();
context.getEditor().getSelectionModel().setSelection(offset, endOffset);
context.getEditor().getCaretModel().moveToOffset(offset);
statement.getParent().addBefore(newVarDeclarationTemplate, statement);
}
@NotNull
@Override
public String getLookupString() {
return getDelegate().getLookupString();
}
@Override
public void renderElement(final LookupElementPresentation presentation) {
super.renderElement(presentation);
presentation.setItemText(myNewVarName + "." + presentation.getItemText());
}
private static PsiLiteralExpression findNullElement(final PsiElement psiElement) {
final Collection<PsiLiteralExpression> literalExpressions = PsiTreeUtil.findChildrenOfType(psiElement, PsiLiteralExpression.class);
for (final PsiLiteralExpression literalExpression : literalExpressions) {
if (PsiKeyword.NULL.equals(literalExpression.getText())) {
return literalExpression;
}
}
throw new IllegalArgumentException();
return myNewVarName;
}
}
@@ -33,6 +33,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -50,7 +51,7 @@ public class ChainCompletionContext {
@NotNull
private final Project myProject;
@NotNull
private final PsiManager myPsiManager;
private final PsiResolveHelper myResolveHelper;
@NotNull
private final FactoryMap<MethodIncompleteSignature, PsiClass> myQualifierClassResolver;
@NotNull
@@ -66,19 +67,19 @@ public class ChainCompletionContext {
myContext = context;
myResolveScope = context.getResolveScope();
myProject = context.getProject();
myPsiManager = PsiManager.getInstance(myProject);
myResolveHelper = PsiResolveHelper.SERVICE.getInstance(myProject);
myQualifierClassResolver = new FactoryMap<MethodIncompleteSignature, PsiClass>() {
@Nullable
@Override
protected PsiClass create(MethodIncompleteSignature sign) {
return sign.resolveQualifier(myProject, myResolveScope);
return sign.resolveQualifier(myProject, myResolveScope, accessValidator());
}
};
myResolver = new FactoryMap<MethodIncompleteSignature, PsiMethod[]>() {
@NotNull
@Override
protected PsiMethod[] create(MethodIncompleteSignature sign) {
return sign.resolve(myProject, myResolveScope);
return sign.resolve(myProject, myResolveScope, accessValidator());
}
};
}
@@ -119,11 +120,6 @@ public class ChainCompletionContext {
return myProject;
}
@NotNull
public PsiManager getPsiManager() {
return myPsiManager;
}
@Nullable
public PsiElement findRelevantStringInContext(String stringParameterName) {
String sanitizedTarget = MethodChainsSearchUtil.sanitizedToLowerCase(stringParameterName);
@@ -159,6 +155,10 @@ public class ChainCompletionContext {
return myResolver.get(sign);
}
private Predicate<PsiMember> accessValidator() {
return m -> myResolveHelper.isAccessible(m, myContext, null);
}
@Nullable
public static ChainCompletionContext createContext(@Nullable PsiType targetType,
@Nullable PsiElement containingElement, boolean suggestIterators) {
@@ -100,9 +100,7 @@ public class ChainSearchTarget {
@Nullable
private static ChainSearchTarget create(PsiClassType classType) {
PsiClass resolvedClass = PsiUtil.resolveClassInClassTypeOnly(classType);
if (resolvedClass == null || resolvedClass instanceof PsiTypeParameter || LambdaUtil.isFunctionalClass(resolvedClass)) return null;
String packageName = JavaHierarchyUtil.getPackageName(resolvedClass);
if (packageName == null || EXCLUDED_PACKAGES.contains(packageName)) return null;
if (resolvedClass == null) return null;
byte iteratorKind = SignatureData.ZERO_DIM;
String iteratorClass = getIteratorKind(resolvedClass);
if (iteratorClass != null) {
@@ -110,9 +108,10 @@ public class ChainSearchTarget {
if (resolvedClass == null) return null;
iteratorKind = SignatureData.ITERATOR_ONE_DIM;
}
if (resolvedClass.hasTypeParameters()) {
return null;
}
if (resolvedClass.hasTypeParameters() || resolvedClass instanceof PsiTypeParameter || LambdaUtil.isFunctionalClass(resolvedClass)) return null;
String packageName = JavaHierarchyUtil.getPackageName(resolvedClass);
if (packageName == null || EXCLUDED_PACKAGES.contains(packageName)) return null;
String classQName = resolvedClass.getQualifiedName();
if (classQName == null) {
return null;
@@ -8,7 +8,7 @@ interface Project {}
public class TestCompletion {
void m() {
PsiManager psiManager = <selection><caret>null</selection>;
PsiManager psiManager = null;
Project p = psiManager.getProject()
}
}
@@ -10,7 +10,7 @@ interface Project {}
public class TestCompletion {
void m() {
String asd = "123";
PsiManager psiManager = <selection><caret>null</selection>;
PsiManager psiManager = null;
Project p = psiManager.getProject(asd, zxc)
}
}
@@ -84,7 +84,7 @@ public class MethodChainsCompletionTest extends AbstractCompilerAwareTest {
}
public void testOneChainContainsOther2() {
assertLookupElementStringEquals(assertOneElement(doCompletion()), "getManager");
assertLookupElementStringEquals(assertOneElement(doCompletion()), "psiElement.getManager");
}
public void testTwoVariablesWithOneTypeOrSuperType() {
@@ -177,7 +177,7 @@ public class MethodChainsCompletionTest extends AbstractCompilerAwareTest {
assertLookupElementStringEquals(lookupElements.get(0), "f.createFileFromText");
assertLookupElementStringEquals(lookupElements.get(1), "getInstance().findFile");
assertLookupElementStringEquals(lookupElements.get(2), "getInstance().getPsiFile");
assertLookupElementStringEquals(lookupElements.get(3), "getContainingClass");
assertLookupElementStringEquals(lookupElements.get(3), "psiClass.getContainingClass");
}
public void testResultRelevance() {
@@ -191,7 +191,7 @@ public class MethodChainsCompletionTest extends AbstractCompilerAwareTest {
List<WeightableChainLookupElement> weightableChainLookupElements = doCompletion();
assertSize(2, weightableChainLookupElements);
assertEquals("e.getProject1", weightableChainLookupElements.get(0).getLookupString());
assertEquals("getProject", weightableChainLookupElements.get(1).getLookupString());
assertEquals("psiManager.getProject", weightableChainLookupElements.get(1).getLookupString());
}
public void testRenderingVariableInContextAndNotInContext() {