IDEA-140728 Suggest to automatically fill parameter of Stream#collect() method calls with standard Collectors instances

This commit is contained in:
peter
2015-07-03 18:21:31 +02:00
parent 209dea1eda
commit 1294ec93f4
14 changed files with 223 additions and 10 deletions

View File

@@ -0,0 +1,132 @@
/*
* 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.codeInsight.completion;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.codeInsight.lookup.TypedLookupItem;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.Consumer;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Set;
import static com.intellij.psi.CommonClassNames.*;
/**
* @author peter
*/
class CollectConversion {
static void addCollectConversion(PsiReferenceExpression ref, Collection<ExpectedTypeInfo> expectedTypes, Consumer<LookupElement> consumer) {
final PsiExpression qualifier = ref.getQualifierExpression();
PsiType component = qualifier == null ? null : PsiUtil.substituteTypeParameter(qualifier.getType(), JAVA_UTIL_STREAM_STREAM, 0, true);
if (component == null) return;
JavaPsiFacade facade = JavaPsiFacade.getInstance(ref.getProject());
GlobalSearchScope scope = ref.getResolveScope();
PsiClass list = facade.findClass(JAVA_UTIL_LIST, scope);
PsiClass set = facade.findClass(JAVA_UTIL_SET, scope);
if (facade.findClass(JAVA_UTIL_STREAM_COLLECTORS, scope) == null || list == null || set == null) return;
boolean hasList = false;
boolean hasSet = false;
for (ExpectedTypeInfo info : expectedTypes) {
PsiType type = info.getDefaultType();
PsiClass expectedClass = PsiUtil.resolveClassInClassTypeOnly(type);
PsiType expectedComponent = PsiUtil.extractIterableTypeParameter(type, true);
if (expectedClass == null || expectedComponent == null || !TypeConversionUtil.isAssignable(expectedComponent, component)) continue;
if (!hasList && InheritanceUtil.isInheritorOrSelf(list, expectedClass, true)) {
hasList = true;
consumer.consume(new MyLookupElement("toList", type));
}
if (!hasSet && InheritanceUtil.isInheritorOrSelf(set, expectedClass, true)) {
hasSet = true;
consumer.consume(new MyLookupElement("toSet", type));
}
}
}
private static class MyLookupElement extends LookupElement implements TypedLookupItem {
private final String myLookupString;
private final String myTypeText;
private final String myMethodName;
@NotNull private final PsiType myExpectedType;
MyLookupElement(String methodName, @NotNull PsiType expectedType) {
myMethodName = methodName;
myExpectedType = expectedType;
myLookupString = "collect(Collectors." + myMethodName + "())";
myTypeText = myExpectedType.getPresentableText();
}
@NotNull
@Override
public String getLookupString() {
return myLookupString;
}
@Override
public Set<String> getAllLookupStrings() {
return ContainerUtil.newHashSet(myLookupString, myMethodName);
}
@Override
public void renderElement(LookupElementPresentation presentation) {
super.renderElement(presentation);
presentation.setTypeText(myTypeText);
presentation.setIcon(PlatformIcons.METHOD_ICON);
}
@Override
public void handleInsert(InsertionContext context) {
context.getDocument().replaceString(context.getStartOffset(), context.getTailOffset(), getInsertString());
context.commitDocument();
PsiMethodCallExpression call =
PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiMethodCallExpression.class, false);
if (call == null) return;
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length != 1 || !(args[0] instanceof PsiMethodCallExpression)) return;
JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(args[0]);
}
@NotNull
private String getInsertString() {
return "collect(" + JAVA_UTIL_STREAM_COLLECTORS + "." + myMethodName + "())";
}
@Override
public PsiType getType() {
return myExpectedType;
}
}
}

View File

@@ -15,6 +15,8 @@
*/
package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.ExpectedTypesProvider;
import com.intellij.codeInsight.TailType;
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFix;
@@ -233,6 +235,13 @@ public class JavaCompletionContributor extends CompletionContributor {
new JavaInheritorsGetter(ConstructorInsertHandler.BASIC_INSTANCE).generateVariants(parameters, matcher, inheritors);
}
PsiElement parent = position.getParent();
if (parent instanceof PsiReferenceExpression) {
final List<ExpectedTypeInfo> expected = Arrays.asList(ExpectedTypesProvider.getExpectedTypes((PsiExpression)parent, true));
CollectConversion.addCollectConversion((PsiReferenceExpression)parent, expected,
JavaSmartCompletionContributor.decorateWithoutTypeCheck(result, expected));
}
if (IMPORT_REFERENCE.accepts(position)) {
result.addElement(LookupElementBuilder.create("*"));
}
@@ -254,7 +263,6 @@ public class JavaCompletionContributor extends CompletionContributor {
addAllClasses(parameters, result, inheritors);
final PsiElement parent = position.getParent();
if (parent instanceof PsiReferenceExpression &&
!((PsiReferenceExpression)parent).isQualified() &&
parameters.isExtendedCompletion() &&

View File

@@ -215,12 +215,7 @@ public class JavaSmartCompletionContributor extends CompletionContributor {
extend(CompletionType.SMART, INSIDE_EXPRESSION, new ExpectedTypeBasedCompletionProvider() {
@Override
protected void addCompletions(final CompletionParameters params, final CompletionResultSet result, final Collection<ExpectedTypeInfo> _infos) {
Consumer<LookupElement> noTypeCheck = new Consumer<LookupElement>() {
@Override
public void consume(final LookupElement lookupElement) {
result.addElement(decorate(lookupElement, _infos));
}
};
Consumer<LookupElement> noTypeCheck = decorateWithoutTypeCheck(result, _infos);
THashSet<ExpectedTypeInfo> mergedInfos = new THashSet<ExpectedTypeInfo>(_infos, EXPECTED_TYPE_INFO_STRATEGY);
List<Runnable> chainedEtc = new ArrayList<Runnable>();
@@ -231,6 +226,11 @@ public class JavaSmartCompletionContributor extends CompletionContributor {
}
addExpectedTypeMembers(params, mergedInfos, true, noTypeCheck);
PsiElement parent = params.getPosition().getParent();
if (parent instanceof PsiReferenceExpression) {
CollectConversion.addCollectConversion((PsiReferenceExpression)parent, mergedInfos, noTypeCheck);
}
for (final ExpectedTypeInfo info : mergedInfos) {
BasicExpressionCompletionContributor.fillCompletionVariants(new JavaSmartCompletionParameters(params, info), new Consumer<LookupElement>() {
@Override
@@ -363,6 +363,16 @@ public class JavaSmartCompletionContributor extends CompletionContributor {
extend(CompletionType.SMART, METHOD_REFERENCE, new MethodReferenceCompletionProvider());
}
@NotNull
static Consumer<LookupElement> decorateWithoutTypeCheck(final CompletionResultSet result, final Collection<ExpectedTypeInfo> infos) {
return new Consumer<LookupElement>() {
@Override
public void consume(final LookupElement lookupElement) {
result.addElement(decorate(lookupElement, infos));
}
};
}
private static void addExpectedTypeMembers(CompletionParameters params,
THashSet<ExpectedTypeInfo> mergedInfos,
boolean quick,

View File

@@ -67,7 +67,7 @@ public class SmartCompletionDecorator extends TailTypeDecorator<LookupElement> {
final PsiExpression enclosing = PsiTreeUtil.getContextOfType(myPosition, PsiExpression.class, true);
if (enclosing != null && object instanceof PsiElement) {
if (enclosing != null) {
final PsiType type = JavaCompletionUtil.getLookupElementType(delegate);
final TailType itemType = item != null ? item.getTailType() : TailType.NONE;
if (type != null && type.isValid()) {

View File

@@ -0,0 +1,8 @@
import java.util.*;
import java.util.stream.Collectors;
class Foo {
void m() {
List<CharSequence> l = Arrays.asList("a", "b").stream().collect(Collectors.toList());<caret>
}
}

View File

@@ -0,0 +1,7 @@
import java.util.*;
class Foo {
void m() {
List<CharSequence> l = Arrays.asList("a", "b").stream().toli<caret>
}
}

View File

@@ -7,6 +7,6 @@ class FooFactory {
class XXX {
{
Foo f = FooFactory.createFoo()<caret>
Foo f = FooFactory.createFoo();<caret>
}
}

View File

@@ -0,0 +1,7 @@
import java.util.*;
class Foo {
void m() {
List<CharSequence> l = Arrays.asList("a", "b").stream().colle<caret>
}
}

View File

@@ -0,0 +1,8 @@
import java.util.*;
import java.util.stream.Collectors;
class Foo {
void m() {
List<CharSequence> l = Arrays.asList("a", "b").stream().collect(Collectors.toList());<caret>
}
}

View File

@@ -0,0 +1,7 @@
import java.util.*;
class Foo {
void m() {
Iterable<CharSequence> l = Arrays.asList("a", "b").stream().colle<caret>
}
}

View File

@@ -0,0 +1,8 @@
import java.util.*;
import java.util.stream.Collectors;
class Foo {
void m() {
Iterable<CharSequence> l = Arrays.asList("a", "b").stream().collect(Collectors.toSet());<caret>
}
}

View File

@@ -146,6 +146,10 @@ public void testConvertToObjectStream() {
checkResultByFile("/" + getTestName(false) + "-out.java");
}
public void testCollectorsToList() {
doTest(false);
}
private void doTest() {
doTest(true);
}

View File

@@ -26,7 +26,7 @@ public class Normal8CompletionTest extends LightFixtureCompletionTestCase {
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_LATEST;
return JAVA_8;
}
@Override
@@ -110,4 +110,16 @@ class Test88 {
}
"""
}
public void testCollectorsToList() {
configureByTestName()
selectItem(myItems.find { it.lookupString.contains('toList') })
checkResultByFile(getTestName(false) + "_after.java")
}
public void testCollectorsToSet() {
configureByTestName()
selectItem(myItems.find { it.lookupString.contains('toSet') })
checkResultByFile(getTestName(false) + "_after.java")
}
}

View File

@@ -101,6 +101,8 @@ public interface CommonClassNames {
@NonNls String JAVA_UTIL_CONCURRENT_CALLABLE = "java.util.concurrent.Callable";
@NonNls String JAVA_UTIL_STREAM_BASE_STREAM = "java.util.stream.BaseStream";
@NonNls String JAVA_UTIL_STREAM_STREAM = "java.util.stream.Stream";
@NonNls String JAVA_UTIL_STREAM_COLLECTORS = "java.util.stream.Collectors";
@NonNls String JAVA_LANG_INVOKE_MH_POLYMORPHIC = "java.lang.invoke.MethodHandle.PolymorphicSignature";