From 5b047851649623463fb5946ed9237e7cbde01032 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 12 Jan 2017 17:32:57 +0700 Subject: [PATCH] IDEA-166499 Stream API migration: support array filling in a loop --- .../StreamApiMigrationInspection.java | 38 ++++++++++- .../streamMigration/ToArrayMigration.java | 65 +++++++++++++++++++ .../afterToArrayCounted.java | 12 ++++ .../afterToArrayCountedBoxed.java | 11 ++++ .../afterToArrayCountedLength2D.java | 11 ++++ .../afterToArrayCountedLengthGeneric.java | 12 ++++ .../beforeToArrayCounted.java | 14 ++++ .../beforeToArrayCountedBoxed.java | 13 ++++ .../beforeToArrayCountedLength2D.java | 13 ++++ .../beforeToArrayCountedLengthGeneric.java | 14 ++++ 10 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 java/java-impl/src/com/intellij/codeInspection/streamMigration/ToArrayMigration.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCounted.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCountedBoxed.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCountedLength2D.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCountedLengthGeneric.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCounted.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCountedBoxed.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCountedLength2D.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCountedLengthGeneric.java diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java index e53c497ee4e5..8bbd0e8e1d40 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java @@ -16,6 +16,7 @@ package com.intellij.codeInspection.streamMigration; import com.intellij.codeInsight.ExceptionUtil; +import com.intellij.codeInsight.PsiEquivalenceUtil; import com.intellij.codeInsight.daemon.GroupNames; import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil; import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool; @@ -566,6 +567,9 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } } if (tb.getCountExpression() != null || tb.isEmpty()) return null; + if (nonFinalVariables.isEmpty() && extractArray(tb) != null) { + return new ToArrayMigration(); + } if (getAccumulatedVariable(tb, nonFinalVariables) != null) { return new SumMigration(); } @@ -691,6 +695,34 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } } + @Nullable + static PsiLocalVariable extractArray(TerminalBlock tb) { + CountingLoop loop = tb.getLastOperation(CountingLoop.class); + if(loop == null || loop.myIncluding) return null; + PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class); + if(assignment == null || !assignment.getOperationTokenType().equals(JavaTokenType.EQ)) return null; + PsiArrayAccessExpression arrayAccess = tryCast(assignment.getLExpression(), PsiArrayAccessExpression.class); + if(arrayAccess == null) return null; + if(!ExpressionUtils.isReferenceTo(arrayAccess.getIndexExpression(), loop.getVariable())) return null; + PsiReferenceExpression arrayReference = tryCast(arrayAccess.getArrayExpression(), PsiReferenceExpression.class); + if(arrayReference == null) return null; + PsiLocalVariable arrayVariable = tryCast(arrayReference.resolve(), PsiLocalVariable.class); + if(arrayVariable == null || getInitializerUsageStatus(arrayVariable, tb.getMainLoop()) == UNKNOWN) return null; + PsiNewExpression initializer = tryCast(arrayVariable.getInitializer(), PsiNewExpression.class); + if(initializer == null) return null; + PsiArrayType arrayType = tryCast(initializer.getType(), PsiArrayType.class); + if(arrayType == null || !isSupported(arrayType.getComponentType())) return null; + PsiExpression dimension = ArrayUtil.getFirstElement(initializer.getArrayDimensions()); + if(dimension == null) return null; + PsiExpression bound = loop.myBound; + if (!PsiEquivalenceUtil.areElementsEquivalent(dimension, bound) && + !ExpressionUtils.isReferenceTo(ExpressionUtils.getArrayFromLengthExpression(bound), arrayVariable)) { + return null; + } + if(VariableAccessUtils.variableIsUsed(arrayVariable, assignment.getRExpression())) return null; + return arrayVariable; + } + /** * Intermediate stream operation representation */ @@ -1085,7 +1117,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo final boolean myIncluding; private CountingLoop(PsiLoopStatement loop, - PsiLocalVariable counter, + PsiVariable counter, PsiExpression initializer, PsiExpression bound, boolean including) { @@ -1106,6 +1138,10 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo return className+"."+methodName+"("+myExpression.getText()+", "+myBound.getText()+")"; } + CountingLoop withBound(PsiExpression bound) { + return new CountingLoop(getLoop(), getVariable(), getExpression(), bound, myIncluding); + } + @Override boolean isWriteAllowed(PsiVariable variable, PsiExpression reference) { if(variable == myVariable) { diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ToArrayMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ToArrayMigration.java new file mode 100644 index 000000000000..1343b9e30947 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ToArrayMigration.java @@ -0,0 +1,65 @@ +/* + * Copyright 2000-2017 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.codeInspection.streamMigration; + +import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.CountingLoop; +import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.InitializerUsageStatus; +import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.MapOp; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NotNull; + +import static com.intellij.util.ObjectUtils.tryCast; + +/** + * @author Tagir Valeev + */ +public class ToArrayMigration extends BaseStreamApiMigration { + protected ToArrayMigration() { + super("toArray"); + } + + @Override + PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiLocalVariable arrayVariable = StreamApiMigrationInspection.extractArray(tb); + if(arrayVariable == null) return null; + PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class); + if(assignment == null) return null; + PsiExpression rValue = assignment.getRExpression(); + if(rValue == null) return null; + PsiNewExpression initializer = tryCast(arrayVariable.getInitializer(), PsiNewExpression.class); + if(initializer == null) return null; + PsiExpression dimension = ArrayUtil.getFirstElement(initializer.getArrayDimensions()); + if(dimension == null) return null; + CountingLoop loop = tb.getLastOperation(CountingLoop.class); + if(loop == null) return null; + PsiArrayType arrayType = tryCast(initializer.getType(), PsiArrayType.class); + if(arrayType == null) return null; + InitializerUsageStatus status = StreamApiMigrationInspection.getInitializerUsageStatus(arrayVariable, tb.getMainLoop()); + if(status == InitializerUsageStatus.UNKNOWN) return null; + PsiType componentType = arrayType.getComponentType(); + String supplier; + if(componentType instanceof PsiPrimitiveType || componentType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { + supplier = ""; + } else { + supplier = arrayType.getCanonicalText()+"::new"; + } + MapOp mapping = new MapOp(rValue, tb.getVariable(), assignment.getType()); + String replacementText = loop.withBound(dimension).createReplacement() + mapping.createReplacement() + ".toArray(" + supplier + ")"; + return replaceInitializer(tb.getMainLoop(), arrayVariable, initializer, replacementText, status); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCounted.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCounted.java new file mode 100644 index 000000000000..1fb05510bdd9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCounted.java @@ -0,0 +1,12 @@ +// "Replace with toArray" "true" + +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; + +public class Test { + public void test(List ints) { + long[] arr = IntStream.range(0, ints.size()).mapToLong(ints::get).toArray(); + System.out.println(Arrays.toString(arr)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCountedBoxed.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCountedBoxed.java new file mode 100644 index 000000000000..8ddbedace66a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCountedBoxed.java @@ -0,0 +1,11 @@ +// "Replace with toArray" "true" + +import java.util.Arrays; +import java.util.stream.IntStream; + +public class Test { + public void test(int bound) { + Object[] arr = IntStream.range(0, bound).boxed().toArray(Integer[]::new); + System.out.println(Arrays.toString(arr)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCountedLength2D.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCountedLength2D.java new file mode 100644 index 000000000000..f28d6f15b2b2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCountedLength2D.java @@ -0,0 +1,11 @@ +// "Replace with toArray" "true" + +import java.util.Arrays; +import java.util.stream.IntStream; + +public class Test { + public void test(int bound) { + Integer[][] arr = IntStream.range(0, bound).mapToObj(i -> new Integer[]{i}).toArray(Integer[][]::new); + System.out.println(Arrays.toString(arr)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCountedLengthGeneric.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCountedLengthGeneric.java new file mode 100644 index 000000000000..7ae098f40930 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterToArrayCountedLengthGeneric.java @@ -0,0 +1,12 @@ +// "Replace with toArray" "true" + +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; + +public class Test { + public void test(List> list) { + List[] arr = IntStream.range(0, list.size()).mapToObj(list::get).toArray(List[]::new); + System.out.println(Arrays.toString(arr)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCounted.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCounted.java new file mode 100644 index 000000000000..edf7245451fb --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCounted.java @@ -0,0 +1,14 @@ +// "Replace with toArray" "true" + +import java.util.Arrays; +import java.util.List; + +public class Test { + public void test(List ints) { + long[] arr = new long[ints.size()]; + for(int i = 0; i < ints.size(); i++) { + arr[i] = ints.get(i); + } + System.out.println(Arrays.toString(arr)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCountedBoxed.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCountedBoxed.java new file mode 100644 index 000000000000..b10328eb90a3 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCountedBoxed.java @@ -0,0 +1,13 @@ +// "Replace with toArray" "true" + +import java.util.Arrays; + +public class Test { + public void test(int bound) { + Object[] arr = new Integer[bound]; + for(int i = 0; i < bound; i++) { + arr[i] = i; + } + System.out.println(Arrays.toString(arr)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCountedLength2D.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCountedLength2D.java new file mode 100644 index 000000000000..10e0048a46eb --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCountedLength2D.java @@ -0,0 +1,13 @@ +// "Replace with toArray" "true" + +import java.util.Arrays; + +public class Test { + public void test(int bound) { + Integer[][] arr = new Integer[bound][]; + for(int i = 0; i < arr.length; i++) { + arr[i] = new Integer[] {i}; + } + System.out.println(Arrays.toString(arr)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCountedLengthGeneric.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCountedLengthGeneric.java new file mode 100644 index 000000000000..8233b606b981 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeToArrayCountedLengthGeneric.java @@ -0,0 +1,14 @@ +// "Replace with toArray" "true" + +import java.util.Arrays; +import java.util.List; + +public class Test { + public void test(List> list) { + List[] arr = new List[list.size()]; + for(int i = 0; i < arr.length; i++) { + arr[i] = list.get(i); + } + System.out.println(Arrays.toString(arr)); + } +}