IDEA-166211 Convert for-loop with Collections.addAll inside into Java 8's stream API calls chain

This commit is contained in:
Tagir Valeev
2017-01-11 15:57:54 +07:00
parent 616eb95766
commit 1ee8a1fbfe
6 changed files with 137 additions and 24 deletions
@@ -25,8 +25,10 @@ import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.EquivalenceChecker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.MethodCallUtils;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import one.util.streamex.EntryStream;
import one.util.streamex.StreamEx;
@@ -121,8 +123,8 @@ class CollectMigration extends BaseStreamApiMigration {
PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
if (tb.dependsOn(qualifierExpression)) return null;
List<BiFunction<TerminalBlock, PsiMethodCallExpression, CollectTerminal>> extractors =
Arrays.asList(AddingTerminal::tryExtract, GroupingTerminal::tryExtract, ToMapTerminal::tryExtract);
List<BiFunction<TerminalBlock, PsiMethodCallExpression, CollectTerminal>> extractors = Arrays
.asList(AddingTerminal::tryExtract, GroupingTerminal::tryExtract, ToMapTerminal::tryExtract, AddingAllTerminal::tryExtractAddAll);
CollectTerminal terminal = StreamEx.of(extractors).map(extractor -> extractor.apply(tb, call)).nonNull().findFirst().orElse(null);
if (terminal != null) {
@@ -153,6 +155,15 @@ class CollectMigration extends BaseStreamApiMigration {
}
}
@Contract("null -> false")
static boolean isEmptyCollectionInitializer(PsiExpression expression) {
if (expression instanceof PsiNewExpression) {
PsiExpressionList argumentList = ((PsiNewExpression)expression).getArgumentList();
return argumentList != null && argumentList.getExpressions().length == 0;
}
return false;
}
interface CollectTerminal {
@Nullable
default PsiElement getElementToReplace() { return null; }
@@ -174,21 +185,18 @@ class CollectMigration extends BaseStreamApiMigration {
}
static class AddingTerminal implements CollectTerminal {
private @Nullable PsiVariable myTarget;
private final PsiType myTargetType;
private final PsiExpression myInitializer;
private final PsiVariable myElement;
private final PsiMethodCallExpression myAddCall;
@Nullable PsiVariable myTarget;
final PsiType myTargetType;
final PsiExpression myInitializer;
final PsiVariable myElement;
final PsiMethodCallExpression myAddCall;
AddingTerminal(@NotNull PsiVariable target,
PsiVariable element,
PsiMethodCallExpression addCall) {
this(target.getType(), target.getInitializer(), element, addCall);
if (myInitializer instanceof PsiNewExpression) {
final PsiExpressionList argumentList = ((PsiNewExpression)myInitializer).getArgumentList();
if (argumentList != null && argumentList.getExpressions().length == 0) {
myTarget = target;
}
if (isEmptyCollectionInitializer(myInitializer)) {
myTarget = target;
}
}
@@ -276,6 +284,42 @@ class CollectMigration extends BaseStreamApiMigration {
}
}
static class AddingAllTerminal extends AddingTerminal {
private final PsiMethodCallExpression myAddAllCall;
AddingAllTerminal(PsiVariable target, PsiVariable element, PsiMethodCallExpression addAllCall) {
super(target, element, null);
myAddAllCall = addAllCall;
}
@Override
public String generateIntermediate() {
PsiType[] typeParameters = myAddAllCall.getMethodExpression().getTypeParameters();
String generic = "";
if(typeParameters.length == 1) {
generic = "<"+typeParameters[0].getCanonicalText()+">";
}
String method = MethodCallUtils.isVarArgCall(myAddAllCall) ? CommonClassNames.JAVA_UTIL_STREAM_STREAM + "." + generic + "of"
: CommonClassNames.JAVA_UTIL_ARRAYS + "." + generic + "stream";
return ".flatMap(" + myElement.getName() + "->" + method + "(" +
StreamEx.of(myAddAllCall.getArgumentList().getExpressions()).skip(1).map(PsiExpression::getText).joining(",") + "))";
}
@Nullable
static AddingAllTerminal tryExtractAddAll(TerminalBlock tb, PsiMethodCallExpression call) {
if(!MethodCallUtils.isCallToStaticMethod(call, CommonClassNames.JAVA_UTIL_COLLECTIONS, "addAll", 2)) {
return null;
}
PsiExpression[] args = call.getArgumentList().getExpressions();
if(args.length < 2) return null;
PsiReferenceExpression collectionReference = ObjectUtils.tryCast(args[0], PsiReferenceExpression.class);
if (collectionReference == null || tb.dependsOn(collectionReference)) return null;
PsiLocalVariable target = ObjectUtils.tryCast(collectionReference.resolve(), PsiLocalVariable.class);
if (target == null || StreamEx.of(args).skip(1).anyMatch(arg -> VariableAccessUtils.variableIsUsed(target, arg))) return null;
return new AddingAllTerminal(target, tb.getVariable(), call);
}
}
static class GroupingTerminal implements CollectTerminal {
private final AddingTerminal myDownstream;
private final PsiLocalVariable myTarget;
@@ -335,11 +379,9 @@ class CollectMigration extends BaseStreamApiMigration {
if (args.length != 2 || !(args[1] instanceof PsiLambdaExpression)) return null;
PsiLambdaExpression lambda = (PsiLambdaExpression)args[1];
PsiExpression body = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
if (!(body instanceof PsiNewExpression)) return null;
PsiExpressionList ctorArgs = ((PsiNewExpression)body).getArgumentList();
if (ctorArgs != null && ctorArgs.getExpressions().length == 0) {
if (isEmptyCollectionInitializer(body)) {
PsiLocalVariable variable = extractQualifierVariable(tb, qualifierCall);
if (variable != null && variable.getInitializer() instanceof PsiNewExpression) {
if (variable != null && isEmptyCollectionInitializer(variable.getInitializer())) {
PsiType mapType = variable.getType();
PsiType valueType = PsiUtil.substituteTypeParameter(mapType, CommonClassNames.JAVA_UTIL_MAP, 1, false);
if (valueType == null) return null;
@@ -415,11 +457,7 @@ class CollectMigration extends BaseStreamApiMigration {
return null;
}
PsiLocalVariable variable = extractQualifierVariable(tb, call);
if (variable == null) return null;
PsiExpression initializer = variable.getInitializer();
if (!(initializer instanceof PsiNewExpression)) return null;
PsiExpressionList argumentList = ((PsiNewExpression)initializer).getArgumentList();
if (argumentList == null || argumentList.getExpressions().length != 0) return null;
if (variable == null || !isEmptyCollectionInitializer(variable.getInitializer())) return null;
return new ToMapTerminal(call, tb.getVariable(), variable);
}
}
@@ -0,0 +1,8 @@
// "Replace with toArray" "true"
import java.util.*;
public class Test {
Object[] test(List<String[]> list) {
return list.stream().filter(Objects::nonNull).flatMap(Arrays::stream).sorted().toArray();
}
}
@@ -0,0 +1,13 @@
// "Replace with toArray" "true"
import java.util.*;
import java.util.stream.Stream;
public class Test {
Object[] test(List<String> list) {
return list.stream().filter(Objects::nonNull).flatMap(str -> Stream.of(str, str + str)).sorted().toArray();
}
public static void main(String[] args) {
System.out.println(Arrays.toString(new Test().test(Arrays.asList("a", "b", "ba", "x", null, "c"))));
}
}
@@ -0,0 +1,15 @@
// "Replace with toArray" "true"
import java.util.*;
public class Test {
Object[] test(List<String[]> list) {
List<Object> result = new LinkedList<>();
for(String[] str : li<caret>st) {
if(str != null) {
Collections.addAll(result, str);
}
}
result.sort(null);
return result.toArray();
}
}
@@ -0,0 +1,19 @@
// "Replace with toArray" "true"
import java.util.*;
public class Test {
Object[] test(List<String> list) {
List<Object> result = new LinkedList<>();
for(String str : lis<caret>t) {
if(str != null) {
Collections.addAll(result, str, str+str);
}
}
result.sort(null);
return result.toArray();
}
public static void main(String[] args) {
System.out.println(Arrays.toString(new Test().test(Arrays.asList("a", "b", "ba", "x", null, "c"))));
}
}
@@ -15,6 +15,7 @@
*/
package com.siyeh.ig.psiutils;
import com.intellij.codeInspection.dataFlow.instructions.MethodCallInstruction;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.MethodSignatureUtil;
@@ -119,13 +120,15 @@ public class MethodCallUtils {
public static boolean isCallToStaticMethod(@NotNull PsiMethodCallExpression expression, @NonNls @NotNull String calledOnClassName,
@NonNls @NotNull String methodName, int parameterCount) {
if (!methodName.equals(getMethodName(expression)) || expression.getArgumentList().getExpressions().length != parameterCount) {
PsiExpression[] args = expression.getArgumentList().getExpressions();
if (!methodName.equals(getMethodName(expression)) || args.length < parameterCount) {
return false;
}
PsiMethod method = expression.resolveMethod();
if (method == null ||
!method.getModifierList().hasExplicitModifier(PsiModifier.STATIC) ||
method.getParameterList().getParametersCount() != parameterCount) {
!method.hasModifierProperty(PsiModifier.STATIC) ||
method.getParameterList().getParametersCount() != parameterCount ||
!method.isVarArgs() && args.length != parameterCount) {
return false;
}
PsiClass aClass = method.getContainingClass();
@@ -137,6 +140,9 @@ public class MethodCallUtils {
final PsiReferenceExpression methodExpression = expression.getMethodExpression();
if (methodNamePattern != null) {
final String referenceName = methodExpression.getReferenceName();
if (referenceName == null) {
return false;
}
final Matcher matcher = methodNamePattern.matcher(referenceName);
if (!matcher.matches()) {
return false;
@@ -289,6 +295,20 @@ public class MethodCallUtils {
return targetMethod != null && MethodSignatureUtil.isSuperMethod(targetMethod, method);
}
/**
* Returns true if given method call is a var-arg call
*
* @param call a call to test
* @return true if call is resolved to the var-arg method and var-arg form is actually used
*/
public static boolean isVarArgCall(PsiMethodCallExpression call) {
PsiMethod method = call.resolveMethod();
if(method == null || !method.isVarArgs()) return false;
PsiSubstitutor substitutor = call.resolveMethodGenerics().getSubstitutor();
return MethodCallInstruction
.isVarArgCall(method, substitutor, call.getArgumentList().getExpressions(), method.getParameterList().getParameters());
}
public static boolean containsSuperMethodCall(@NotNull PsiMethod method) {
final SuperCallVisitor visitor = new SuperCallVisitor(method);
method.accept(visitor);