From 89e08570b41f3f7d155466de69a8582efb81ad48 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 15 Dec 2016 15:36:32 +0700 Subject: [PATCH] IDEA-165395 Replace stream API with loop: l.stream().toArray(String[]::new) --- ...SimplifyStreamApiCallChainsInspection.java | 98 ++++++++++++++++++- .../afterToArrayLambda.java | 10 ++ .../afterToArrayMethodRef.java | 9 ++ .../afterToArrayNoArg.java | 9 ++ .../beforeToArrayGenerator.java | 10 ++ .../beforeToArrayLambda.java | 9 ++ .../beforeToArrayMethodRef.java | 9 ++ .../beforeToArrayNoArg.java | 9 ++ .../SimplifyStreamApiCallChains.html | 1 + 9 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 java/java-tests/testData/inspection/streamApiCallChains/afterToArrayLambda.java create mode 100644 java/java-tests/testData/inspection/streamApiCallChains/afterToArrayMethodRef.java create mode 100644 java/java-tests/testData/inspection/streamApiCallChains/afterToArrayNoArg.java create mode 100644 java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayGenerator.java create mode 100644 java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayLambda.java create mode 100644 java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayMethodRef.java create mode 100644 java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayNoArg.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/SimplifyStreamApiCallChainsInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/SimplifyStreamApiCallChainsInspection.java index d1bbf5327cb9..0e7c7276dfd4 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/SimplifyStreamApiCallChainsInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/SimplifyStreamApiCallChainsInspection.java @@ -24,6 +24,7 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.impl.PsiDiamondTypeUtil; import com.intellij.psi.util.*; import com.siyeh.ig.psiutils.BoolUtils; +import com.siyeh.ig.psiutils.CommentTracker; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.StreamApiUtil; import org.jetbrains.annotations.Contract; @@ -59,6 +60,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns private static final String ANY_MATCH_METHOD = "anyMatch"; private static final String NONE_MATCH_METHOD = "noneMatch"; private static final String ALL_MATCH_METHOD = "allMatch"; + private static final String TO_ARRAY_METHOD = "toArray"; private static final String COUNTING_COLLECTOR = "counting"; private static final String TO_LIST_COLLECTOR = "toList"; @@ -100,7 +102,10 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns else if (isCallOf(method, CommonClassNames.JAVA_UTIL_OPTIONAL, IS_PRESENT_METHOD, 0)) { handleOptionalIsPresent(methodCall); } - else if (isStreamCall(method, ANY_MATCH_METHOD)) { + else if (isStreamCall(method, TO_ARRAY_METHOD, false)) { + handleToArray(methodCall); + } + else if (isStreamCall(method, ANY_MATCH_METHOD, true)) { if(isParentNegated(methodCall)) { boolean argNegated = isArgumentLambdaNegated(methodCall); registerMatchFix(methodCall, @@ -109,7 +114,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns argNegated ? ALL_MATCH_METHOD : NONE_MATCH_METHOD)); } } - else if (isStreamCall(method, NONE_MATCH_METHOD)) { + else if (isStreamCall(method, NONE_MATCH_METHOD, true)) { if(isParentNegated(methodCall)) { registerMatchFix(methodCall, new SimplifyMatchNegationFix("!"+psiClass.getName()+".noneMatch(...)", ANY_MATCH_METHOD)); } @@ -117,7 +122,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns registerMatchFix(methodCall, new SimplifyMatchNegationFix(psiClass.getName()+".noneMatch(x -> !(...))", ALL_MATCH_METHOD)); } } - else if (isStreamCall(method, ALL_MATCH_METHOD)) { + else if (isStreamCall(method, ALL_MATCH_METHOD, true)) { if(isArgumentLambdaNegated(methodCall)) { boolean parentNegated = isParentNegated(methodCall); registerMatchFix(methodCall, @@ -131,6 +136,22 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns } } + private void handleToArray(PsiMethodCallExpression methodCall) { + PsiReferenceExpression methodExpression = methodCall.getMethodExpression(); + PsiExpression qualifier = methodExpression.getQualifierExpression(); + if(qualifier instanceof PsiMethodCallExpression && isCollectionStream((PsiMethodCallExpression)qualifier)) { + PsiArrayType type = getArrayType(methodCall); + if(type != null) { + PsiElement nameElement = methodExpression.getReferenceNameElement(); + LOG.assertTrue(nameElement != null); + String replacement = type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT+"[]") ? "" : + "new "+type.getCanonicalText().replaceFirst("\\[]", "[0]"); + holder.registerProblem(nameElement, "Can be replaced with collection.toArray()", + new SimplifyCallChainFix(new ReplaceWithToArrayFix(replacement))); + } + } + } + void registerMatchFix(PsiMethodCallExpression methodCall, SimplifyMatchNegationFix fix) { PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement(); if(nameElement != null) { @@ -341,6 +362,44 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns }; } + @Nullable + private static PsiArrayType getArrayType(PsiMethodCallExpression call) { + PsiType type = call.getType(); + if(!(type instanceof PsiArrayType)) return null; + PsiArrayType candidate = (PsiArrayType)type; + PsiExpression[] args = call.getArgumentList().getExpressions(); + if(args.length == 0) return candidate; + if(args.length != 1) return null; + PsiExpression supplier = args[0]; + if(supplier instanceof PsiMethodReferenceExpression) { + // like toArray(String[]::new) + PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)supplier; + PsiTypeElement qualifierType = methodRef.getQualifierType(); + if (methodRef.isConstructor() && qualifierType != null && candidate.isAssignableFrom(qualifierType.getType())) { + return candidate; + } + } else if(supplier instanceof PsiLambdaExpression) { + // like toArray(size -> new String[size]) + PsiLambdaExpression lambda = (PsiLambdaExpression)supplier; + PsiParameter[] parameters = lambda.getParameterList().getParameters(); + if(parameters.length != 1) return null; + PsiParameter sizeParameter = parameters[0]; + PsiExpression body = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody()); + if(body instanceof PsiNewExpression) { + PsiNewExpression newExpression = (PsiNewExpression)body; + PsiExpression[] dimensions = newExpression.getArrayDimensions(); + PsiType newExpressionType = newExpression.getType(); + if (dimensions.length != 0 && + ExpressionUtils.isReferenceTo(dimensions[0], sizeParameter) && + newExpressionType != null && + candidate.isAssignableFrom(newExpressionType)) { + return candidate; + } + } + } + return null; + } + private static boolean isCollectionConstructor(PsiMethod ctor) { if(!ctor.getModifierList().hasExplicitModifier(PsiModifier.PUBLIC)) return false; PsiParameterList list = ctor.getParameterList(); @@ -452,8 +511,10 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns return false; } - static boolean isStreamCall(@Nullable PsiMethod method, @NotNull String methodName) { - if (method == null || !methodName.equals(method.getName()) || method.getParameterList().getParametersCount() != 1) { + @Contract("null, _, _ -> false") + static boolean isStreamCall(@Nullable PsiMethod method, @NotNull String methodName, boolean checkSingleParameter) { + if (method == null || !methodName.equals(method.getName()) || + (checkSingleParameter && method.getParameterList().getParametersCount() != 1)) { return false; } final PsiClass containingClass = method.getContainingClass(); @@ -890,4 +951,31 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns ((PsiMethodCallExpression)grandParent).getTypeArgumentList().delete(); } } + + private static class ReplaceWithToArrayFix implements CallChainFix { + private final String myReplacement; + + private ReplaceWithToArrayFix(String replacement) { + myReplacement = replacement; + } + + @Override + public String getName() { + return "Replace 'collection.stream().toArray()' with 'collection.toArray()'"; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiMethodCallExpression toArrayCall = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiMethodCallExpression.class); + if (toArrayCall == null) return; + PsiExpression qualifier = toArrayCall.getMethodExpression().getQualifierExpression(); + if(!(qualifier instanceof PsiMethodCallExpression)) return; + PsiMethodCallExpression streamCall = (PsiMethodCallExpression)qualifier; + PsiExpression collectionExpression = streamCall.getMethodExpression().getQualifierExpression(); + if(collectionExpression == null) return; + CommentTracker ct = new CommentTracker(); + PsiElement result = ct.replaceAndRestoreComments(toArrayCall, ct.text(collectionExpression) + ".toArray(" + myReplacement + ")"); + CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(result)); + } + } } diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterToArrayLambda.java b/java/java-tests/testData/inspection/streamApiCallChains/afterToArrayLambda.java new file mode 100644 index 000000000000..0102e3b564e8 --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/afterToArrayLambda.java @@ -0,0 +1,10 @@ +// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "true" + +import java.util.*; + +class Test { + public void testToArray(List data) { + /*generate array*/ + String[][] array = data.subList(0, /*max number*/ 10).toArray(new String[0][]); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterToArrayMethodRef.java b/java/java-tests/testData/inspection/streamApiCallChains/afterToArrayMethodRef.java new file mode 100644 index 000000000000..25edc83fb602 --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/afterToArrayMethodRef.java @@ -0,0 +1,9 @@ +// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "true" + +import java.util.*; + +class Test { + public void testToArray(List data) { + Object[] array = data.toArray(new String[0]); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterToArrayNoArg.java b/java/java-tests/testData/inspection/streamApiCallChains/afterToArrayNoArg.java new file mode 100644 index 000000000000..912701802841 --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/afterToArrayNoArg.java @@ -0,0 +1,9 @@ +// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "true" + +import java.util.*; + +class Test { + public void testToArray(List data) { + Object[] array = data.toArray(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayGenerator.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayGenerator.java new file mode 100644 index 000000000000..512e226d8aab --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayGenerator.java @@ -0,0 +1,10 @@ +// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "false" + +import java.util.*; +import java.util.function.*; + +class Test { + public void testToArray(List data, IntFunction generator) { + Object[] array = data.stream().toArray(generator); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayLambda.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayLambda.java new file mode 100644 index 000000000000..5e46ad818a69 --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayLambda.java @@ -0,0 +1,9 @@ +// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "true" + +import java.util.*; + +class Test { + public void testToArray(List data) { + String[][] array = data.subList(0, /*max number*/ 10).stream().toArray((size) -> /*generate array*/ new String[(size)][]); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayMethodRef.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayMethodRef.java new file mode 100644 index 000000000000..5ab47ff4a578 --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayMethodRef.java @@ -0,0 +1,9 @@ +// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "true" + +import java.util.*; + +class Test { + public void testToArray(List data) { + Object[] array = data.stream().toArray(String[]::new); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayNoArg.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayNoArg.java new file mode 100644 index 000000000000..83bee90d1e2d --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/beforeToArrayNoArg.java @@ -0,0 +1,9 @@ +// "Replace 'collection.stream().toArray()' with 'collection.toArray()'" "true" + +import java.util.*; + +class Test { + public void testToArray(List data) { + Object[] array = data.stream().toArray(); + } +} \ No newline at end of file diff --git a/resources-en/src/inspectionDescriptions/SimplifyStreamApiCallChains.html b/resources-en/src/inspectionDescriptions/SimplifyStreamApiCallChains.html index b16ab16d4000..a7d64eb764ec 100644 --- a/resources-en/src/inspectionDescriptions/SimplifyStreamApiCallChains.html +++ b/resources-en/src/inspectionDescriptions/SimplifyStreamApiCallChains.html @@ -11,6 +11,7 @@ It allows to avoid creating redundant temporary objects when traversing a collec
  • collection.stream().collect(Collectors.toList()) → new ArrayList<>(collection)
  • collection.stream().collect(Collectors.toSet()) → new HashSet<>(collection)
  • collection.stream().collect(Collectors.toCollection(CollectionType::new)) → new CollectionType<>(collection)
  • +
  • collection.stream().toArray() → collection.toArray()
  • Arrays.asList().stream() → Arrays.stream() or Stream.of()
  • Collections.singleton().stream() → Stream.of()
  • Collections.singletonList().stream() → Stream.of()