From 0ad3891e96cb8040edda6320903a39c341a74e81 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 6 Oct 2016 17:58:04 +0700 Subject: [PATCH] IDEA-162157 Stream API migration: support counted for loop (both outer and flatMapped) --- .../streamMigration/MigrateToStreamFix.java | 83 ++-- .../ReplaceWithCollectFix.java | 35 +- .../streamMigration/ReplaceWithCountFix.java | 14 +- .../ReplaceWithFindFirstFix.java | 36 +- .../ReplaceWithForeachCallFix.java | 17 +- .../streamMigration/ReplaceWithMatchFix.java | 36 +- .../streamMigration/ReplaceWithSumFix.java | 15 +- .../StreamApiMigrationInspection.java | 400 ++++++++++++------ .../afterFlatMapForLoop.java | 11 + .../streamApiMigration/afterForLoop.java | 12 + .../streamApiMigration/afterForLoopBoxed.java | 12 + .../afterForLoopShortBound.java | 14 + .../afterPrimitiveArray.java | 8 + .../beforeFindFirstUsedInBound.java | 18 + .../beforeFlatMapForLoop.java | 17 + .../streamApiMigration/beforeForLoop.java | 13 + .../beforeForLoopBoxed.java | 13 + .../beforeForLoopFloatBound.java | 14 + .../beforeForLoopShortBound.java | 14 + .../beforeForLoopUpdated.java | 13 + .../beforeForLoopWrongBound.java | 13 + .../beforeForLoopWrongIncrement.java | 13 + .../beforeForLoopWrongInitializer.java | 13 + .../beforePrimitiveArray.java | 4 +- .../beforePrimitiveArrayWrongType.java | 10 + .../beforeSumAccessOuterFor.java | 18 + 26 files changed, 609 insertions(+), 257 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFlatMapForLoop.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterForLoop.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterForLoopBoxed.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterForLoopShortBound.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterPrimitiveArray.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstUsedInBound.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFlatMapForLoop.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoop.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopBoxed.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopFloatBound.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopShortBound.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopUpdated.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopWrongBound.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopWrongIncrement.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopWrongInitializer.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforePrimitiveArrayWrongType.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeSumAccessOuterFor.java diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/MigrateToStreamFix.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/MigrateToStreamFix.java index aeb404a75230..5a1a4c2d0583 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/MigrateToStreamFix.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/MigrateToStreamFix.java @@ -19,9 +19,7 @@ import com.intellij.codeInsight.FileModificationService; import com.intellij.codeInspection.LambdaCanBeMethodReferenceInspection; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemDescriptor; -import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.InitializerUsageStatus; -import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.Operation; -import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.TerminalBlock; +import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.*; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; @@ -30,7 +28,6 @@ import com.intellij.psi.util.PsiTreeUtil; import com.siyeh.ig.psiutils.ExpressionUtils; import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.ListIterator; @@ -49,65 +46,61 @@ abstract class MigrateToStreamFix implements LocalQuickFix { @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { PsiElement element = descriptor.getPsiElement(); - if (element instanceof PsiForeachStatement) { - PsiForeachStatement foreachStatement = (PsiForeachStatement)element; - PsiStatement body = foreachStatement.getBody(); - final PsiExpression iteratedValue = foreachStatement.getIteratedValue(); - if (body != null && iteratedValue != null) { - final PsiParameter parameter = foreachStatement.getIterationParameter(); - TerminalBlock tb = TerminalBlock.from(parameter, body); - if (!FileModificationService.getInstance().preparePsiElementForWrite(foreachStatement)) return; - PsiElement result = migrate(project, descriptor, foreachStatement, iteratedValue, body, tb); - if(result != null) { - simplifyAndFormat(project, result); - } + if (element instanceof PsiLoopStatement) { + PsiLoopStatement loopStatement = (PsiLoopStatement)element; + StreamSource source = StreamSource.tryCreate(loopStatement); + PsiStatement body = loopStatement.getBody(); + if(body == null || source == null) return; + TerminalBlock tb = TerminalBlock.from(source, body); + if (!FileModificationService.getInstance().preparePsiElementForWrite(loopStatement)) return; + PsiElement result = migrate(project, loopStatement, body, tb); + if(result != null) { + simplifyAndFormat(project, result); } } } abstract PsiElement migrate(@NotNull Project project, - @NotNull ProblemDescriptor descriptor, - @NotNull PsiForeachStatement foreachStatement, - @NotNull PsiExpression iteratedValue, - @NotNull PsiStatement body, - @NotNull TerminalBlock tb); + @NotNull PsiLoopStatement loopStatement, + @NotNull PsiStatement body, + @NotNull TerminalBlock tb); static PsiElement replaceWithNumericAddition(@NotNull Project project, - PsiForeachStatement foreachStatement, + PsiLoopStatement loopStatement, PsiVariable var, StringBuilder builder, PsiType expressionType) { PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); - restoreComments(foreachStatement, foreachStatement.getBody()); - InitializerUsageStatus status = StreamApiMigrationInspection.getInitializerUsageStatus(var, foreachStatement); + restoreComments(loopStatement, loopStatement.getBody()); + InitializerUsageStatus status = StreamApiMigrationInspection.getInitializerUsageStatus(var, loopStatement); if (status != InitializerUsageStatus.UNKNOWN) { PsiExpression initializer = var.getInitializer(); if (ExpressionUtils.isZero(initializer)) { PsiType type = var.getType(); String replacement = (type.equals(expressionType) ? "" : "(" + type.getCanonicalText() + ") ") + builder; - return replaceInitializer(foreachStatement, var, initializer, replacement, status); + return replaceInitializer(loopStatement, var, initializer, replacement, status); } } - return foreachStatement.replace(elementFactory.createStatementFromText(var.getName() + "+=" + builder + ";", foreachStatement)); + return loopStatement.replace(elementFactory.createStatementFromText(var.getName() + "+=" + builder + ";", loopStatement)); } - static PsiElement replaceInitializer(PsiForeachStatement foreachStatement, + static PsiElement replaceInitializer(PsiLoopStatement loopStatement, PsiVariable var, PsiExpression initializer, String replacement, InitializerUsageStatus status) { - Project project = foreachStatement.getProject(); + Project project = loopStatement.getProject(); PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); if(status == InitializerUsageStatus.DECLARED_JUST_BEFORE) { - initializer.replace(elementFactory.createExpressionFromText(replacement, foreachStatement)); - removeLoop(foreachStatement); + initializer.replace(elementFactory.createExpressionFromText(replacement, loopStatement)); + removeLoop(loopStatement); return var; } else { if(status == InitializerUsageStatus.AT_WANTED_PLACE_ONLY) { initializer.delete(); } return - foreachStatement.replace(elementFactory.createStatementFromText(var.getName() + " = " + replacement + ";", foreachStatement)); + loopStatement.replace(elementFactory.createStatementFromText(var.getName() + " = " + replacement + ";", loopStatement)); } } @@ -117,34 +110,26 @@ abstract class MigrateToStreamFix implements LocalQuickFix { CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(result)); } - static void restoreComments(PsiForeachStatement foreachStatement, PsiStatement body) { - final PsiElement parent = foreachStatement.getParent(); + static void restoreComments(PsiLoopStatement loopStatement, PsiStatement body) { + final PsiElement parent = loopStatement.getParent(); for (PsiElement comment : PsiTreeUtil.findChildrenOfType(body, PsiComment.class)) { - parent.addBefore(comment, foreachStatement); + parent.addBefore(comment, loopStatement); } } @NotNull - static StringBuilder generateStream(PsiExpression iteratedValue, @Nullable Operation lastOperation) { - return generateStream(iteratedValue, lastOperation, false); + static StringBuilder generateStream(@NotNull Operation lastOperation) { + return generateStream(lastOperation, false); } @NotNull - static StringBuilder generateStream(PsiExpression iteratedValue, @Nullable Operation lastOperation, boolean noStreamForEmpty) { + static StringBuilder generateStream(@NotNull Operation lastOperation, boolean noStreamForEmpty) { StringBuilder buffer = new StringBuilder(); - final PsiType iteratedValueType = iteratedValue.getType(); - if (iteratedValueType instanceof PsiArrayType) { - buffer.append("java.util.Arrays.stream(").append(iteratedValue.getText()).append(")"); + if(noStreamForEmpty && lastOperation instanceof CollectionStream) { + return buffer.append(lastOperation.getExpression().getText()); } - else { - buffer.append(getIteratedValueText(iteratedValue)); - if (!noStreamForEmpty || lastOperation != null) { - buffer.append(".stream()"); - } - } - PsiElementFactory factory = JavaPsiFacade.getElementFactory(iteratedValue.getProject()); List replacements = - StreamEx.iterate(lastOperation, Objects::nonNull, Operation::getPreviousOp).map(op -> op.createReplacement(factory)).toList(); + StreamEx.iterate(lastOperation, Objects::nonNull, Operation::getPreviousOp).map(Operation::createReplacement).toList(); for(ListIterator it = replacements.listIterator(replacements.size()); it.hasPrevious(); ) { buffer.append(it.previous()); } @@ -158,7 +143,7 @@ abstract class MigrateToStreamFix implements LocalQuickFix { iteratedValue instanceof PsiParenthesizedExpression ? iteratedValue.getText() : "(" + iteratedValue.getText() + ")"; } - static void removeLoop(@NotNull PsiForeachStatement statement) { + static void removeLoop(@NotNull PsiLoopStatement statement) { PsiElement parent = statement.getParent(); if (parent instanceof PsiLabeledStatement) { parent.delete(); diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCollectFix.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCollectFix.java index 4dd8e7c06070..74c85641b701 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCollectFix.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCollectFix.java @@ -15,8 +15,8 @@ */ package com.intellij.codeInspection.streamMigration; -import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.InitializerUsageStatus; +import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.MapOp; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; @@ -58,42 +58,41 @@ class ReplaceWithCollectFix extends MigrateToStreamFix { @Override PsiElement migrate(@NotNull Project project, - @NotNull ProblemDescriptor descriptor, - @NotNull PsiForeachStatement foreachStatement, - @NotNull PsiExpression iteratedValue, - @NotNull PsiStatement body, - @NotNull StreamApiMigrationInspection.TerminalBlock tb) { + @NotNull PsiLoopStatement loopStatement, + @NotNull PsiStatement body, + @NotNull StreamApiMigrationInspection.TerminalBlock tb) { final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); - final PsiType iteratedValueType = iteratedValue.getType(); final PsiMethodCallExpression methodCallExpression = tb.getSingleMethodCall(); if (methodCallExpression == null) return null; - restoreComments(foreachStatement, body); - if (!tb.hasOperations() && StreamApiMigrationInspection.isAddAllCall(tb)) { + restoreComments(loopStatement, body); + if (!tb.hasOperations() && StreamApiMigrationInspection.isAddAllCall(tb) && loopStatement instanceof PsiForeachStatement) { + PsiExpression iteratedValue = ((PsiForeachStatement)loopStatement).getIteratedValue(); + if (iteratedValue == null) return null; + final PsiType iteratedValueType = iteratedValue.getType(); final PsiExpression qualifierExpression = methodCallExpression.getMethodExpression().getQualifierExpression(); final String qualifierText = qualifierExpression != null ? qualifierExpression.getText() : ""; final String collectionText = iteratedValueType instanceof PsiArrayType ? "java.util.Arrays.asList(" + iteratedValue.getText() + ")" : getIteratedValueText(iteratedValue); final String callText = StringUtil.getQualifiedName(qualifierText, "addAll(" + collectionText + ");"); - return foreachStatement.replace(elementFactory.createStatementFromText(callText, foreachStatement)); + return loopStatement.replace(elementFactory.createStatementFromText(callText, loopStatement)); } PsiExpression itemToAdd = methodCallExpression.getArgumentList().getExpressions()[0]; PsiType addedType = getAddedElementType(methodCallExpression); if (addedType == null) addedType = itemToAdd.getType(); - final StringBuilder builder = - generateStream(iteratedValue, new StreamApiMigrationInspection.MapOp(tb.getLastOperation(), itemToAdd, tb.getVariable(), addedType)); + StringBuilder builder = generateStream(new MapOp(tb.getLastOperation(), itemToAdd, tb.getVariable(), addedType)); final PsiExpression qualifierExpression = methodCallExpression.getMethodExpression().getQualifierExpression(); final PsiLocalVariable variable = StreamApiMigrationInspection.extractCollectionVariable(qualifierExpression); if (variable != null) { - InitializerUsageStatus status = StreamApiMigrationInspection.getInitializerUsageStatus(variable, foreachStatement); + InitializerUsageStatus status = StreamApiMigrationInspection.getInitializerUsageStatus(variable, loopStatement); if(status != InitializerUsageStatus.UNKNOWN) { PsiExpression initializer = variable.getInitializer(); LOG.assertTrue(initializer != null); PsiMethodCallExpression toArrayExpression = - StreamApiMigrationInspection.extractToArrayExpression(foreachStatement, methodCallExpression); + StreamApiMigrationInspection.extractToArrayExpression(loopStatement, methodCallExpression); if(toArrayExpression != null) { PsiType type = initializer.getType(); if(type instanceof PsiClassType) { @@ -113,7 +112,7 @@ class ReplaceWithCollectFix extends MigrateToStreamFix { } PsiElement result = toArrayExpression.replace(elementFactory.createExpressionFromText(builder.toString(), toArrayExpression)); - removeLoop(foreachStatement); + removeLoop(loopStatement); if(status != InitializerUsageStatus.AT_WANTED_PLACE) { variable.delete(); } @@ -121,7 +120,7 @@ class ReplaceWithCollectFix extends MigrateToStreamFix { } } } - PsiElement nextStatement = PsiTreeUtil.skipSiblingsForward(foreachStatement, PsiComment.class, PsiWhiteSpace.class); + PsiElement nextStatement = PsiTreeUtil.skipSiblingsForward(loopStatement, PsiComment.class, PsiWhiteSpace.class); String comparatorText = StreamApiMigrationInspection.tryExtractSortComparatorText(nextStatement, variable); if(comparatorText != null) { builder.append(".sorted(").append(comparatorText).append(")"); @@ -130,7 +129,7 @@ class ReplaceWithCollectFix extends MigrateToStreamFix { String callText = builder.append(".collect(java.util.stream.Collectors.") .append(createInitializerReplacementText(qualifierExpression.getType(), initializer)) .append(")").toString(); - return replaceInitializer(foreachStatement, variable, initializer, callText, status); + return replaceInitializer(loopStatement, variable, initializer, callText, status); } } final String qualifierText = qualifierExpression != null ? qualifierExpression.getText() + "." : ""; @@ -147,7 +146,7 @@ class ReplaceWithCollectFix extends MigrateToStreamFix { elementFactory.createExpressionFromText(qualifierText + "add(" + varName + ")", qualifierExpression); final String callText = builder.append(".forEach(").append(varName).append("->").append(forEachBody.getText()).append(");").toString(); - return foreachStatement.replace(elementFactory.createStatementFromText(callText, foreachStatement)); + return loopStatement.replace(elementFactory.createStatementFromText(callText, loopStatement)); } private static String createInitializerReplacementText(PsiType varType, PsiExpression initializer) { diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCountFix.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCountFix.java index 836aa8b629b8..137630e080de 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCountFix.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithCountFix.java @@ -15,7 +15,6 @@ */ package com.intellij.codeInspection.streamMigration; -import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import org.jetbrains.annotations.NotNull; @@ -33,18 +32,15 @@ class ReplaceWithCountFix extends MigrateToStreamFix { @Override PsiElement migrate(@NotNull Project project, - @NotNull ProblemDescriptor descriptor, - @NotNull PsiForeachStatement foreachStatement, - @NotNull PsiExpression iteratedValue, - @NotNull PsiStatement body, - @NotNull StreamApiMigrationInspection.TerminalBlock tb) { + @NotNull PsiLoopStatement loopStatement, + @NotNull PsiStatement body, + @NotNull StreamApiMigrationInspection.TerminalBlock tb) { PsiExpression operand = StreamApiMigrationInspection.extractIncrementedLValue(tb.getSingleExpression(PsiExpression.class)); if (!(operand instanceof PsiReferenceExpression)) return null; PsiElement element = ((PsiReferenceExpression)operand).resolve(); if (!(element instanceof PsiLocalVariable)) return null; PsiLocalVariable var = (PsiLocalVariable)element; - final StringBuilder builder = generateStream(iteratedValue, tb.getLastOperation()); - builder.append(".count()"); - return replaceWithNumericAddition(project, foreachStatement, var, builder, PsiType.LONG); + StringBuilder builder = generateStream(tb.getLastOperation()).append(".count()"); + return replaceWithNumericAddition(project, loopStatement, var, builder, PsiType.LONG); } } diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithFindFirstFix.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithFindFirstFix.java index 68fcfca7a556..ff9b77733dfe 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithFindFirstFix.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithFindFirstFix.java @@ -16,7 +16,6 @@ package com.intellij.codeInspection.streamMigration; import com.intellij.codeInsight.PsiEquivalenceUtil; -import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.InitializerUsageStatus; import com.intellij.openapi.project.Project; import com.intellij.psi.*; @@ -36,28 +35,27 @@ class ReplaceWithFindFirstFix extends MigrateToStreamFix { @Override PsiElement migrate(@NotNull Project project, - @NotNull ProblemDescriptor descriptor, - @NotNull PsiForeachStatement foreachStatement, - @NotNull PsiExpression iteratedValue, - @NotNull PsiStatement body, - @NotNull StreamApiMigrationInspection.TerminalBlock tb) { + @NotNull PsiLoopStatement loopStatement, + @NotNull PsiStatement body, + @NotNull StreamApiMigrationInspection.TerminalBlock tb) { PsiStatement statement = tb.getSingleStatement(); PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); - String stream = generateStream(iteratedValue, tb.getLastOperation()).append(".findFirst()").toString(); + StringBuilder builder = generateStream(tb.getLastOperation()); + String stream = builder.append(".findFirst()").toString(); if (statement instanceof PsiReturnStatement) { PsiReturnStatement returnStatement = (PsiReturnStatement)statement; PsiExpression value = returnStatement.getReturnValue(); if (value == null) return null; - PsiReturnStatement nextReturnStatement = StreamApiMigrationInspection.getNextReturnStatement(foreachStatement); + PsiReturnStatement nextReturnStatement = StreamApiMigrationInspection.getNextReturnStatement(loopStatement); if (nextReturnStatement == null) return null; PsiExpression orElseExpression = nextReturnStatement.getReturnValue(); if (!ExpressionUtils.isSimpleExpression(orElseExpression)) return null; stream = generateOptionalUnwrap(stream, tb, value, orElseExpression, null); - restoreComments(foreachStatement, body); - if (nextReturnStatement.getParent() == foreachStatement.getParent()) { + restoreComments(loopStatement, body); + if (nextReturnStatement.getParent() == loopStatement.getParent()) { nextReturnStatement.delete(); } - return foreachStatement.replace(elementFactory.createStatementFromText("return " + stream + ";", foreachStatement)); + return loopStatement.replace(elementFactory.createStatementFromText("return " + stream + ";", loopStatement)); } else { PsiStatement[] statements = tb.getStatements(); @@ -66,9 +64,9 @@ class ReplaceWithFindFirstFix extends MigrateToStreamFix { if (assignment == null) { if(!(statements[0] instanceof PsiExpressionStatement)) return null; PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression(); - restoreComments(foreachStatement, body); - return foreachStatement.replace(elementFactory.createStatementFromText( - stream + ".ifPresent(" + LambdaUtil.createLambda(tb.getVariable(), expression) + ");", foreachStatement)); + restoreComments(loopStatement, body); + return loopStatement.replace(elementFactory.createStatementFromText( + stream + ".ifPresent(" + LambdaUtil.createLambda(tb.getVariable(), expression) + ");", loopStatement)); } PsiExpression lValue = assignment.getLExpression(); if (!(lValue instanceof PsiReferenceExpression)) return null; @@ -77,17 +75,17 @@ class ReplaceWithFindFirstFix extends MigrateToStreamFix { PsiVariable var = (PsiVariable)element; PsiExpression value = assignment.getRExpression(); if (value == null) return null; - restoreComments(foreachStatement, body); - InitializerUsageStatus status = StreamApiMigrationInspection.getInitializerUsageStatus(var, foreachStatement); + restoreComments(loopStatement, body); + InitializerUsageStatus status = StreamApiMigrationInspection.getInitializerUsageStatus(var, loopStatement); if (status != InitializerUsageStatus.UNKNOWN) { PsiExpression initializer = var.getInitializer(); if (initializer != null) { String replacementText = generateOptionalUnwrap(stream, tb, value, initializer, var.getType()); - return replaceInitializer(foreachStatement, var, initializer, replacementText, status); + return replaceInitializer(loopStatement, var, initializer, replacementText, status); } } - return foreachStatement.replace(elementFactory.createStatementFromText( - var.getName() + " = " + generateOptionalUnwrap(stream, tb, value, lValue, var.getType()) + ";", foreachStatement)); + return loopStatement.replace(elementFactory.createStatementFromText( + var.getName() + " = " + generateOptionalUnwrap(stream, tb, value, lValue, var.getType()) + ";", loopStatement)); } } diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithForeachCallFix.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithForeachCallFix.java index 6abacc64aa3e..becd6fd93e57 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithForeachCallFix.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithForeachCallFix.java @@ -15,7 +15,6 @@ */ package com.intellij.codeInspection.streamMigration; -import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.psi.*; @@ -41,24 +40,22 @@ class ReplaceWithForeachCallFix extends MigrateToStreamFix { @Override PsiElement migrate(@NotNull Project project, - @NotNull ProblemDescriptor descriptor, - @NotNull PsiForeachStatement foreachStatement, - @NotNull PsiExpression iteratedValue, - @NotNull PsiStatement body, - @NotNull StreamApiMigrationInspection.TerminalBlock tb) { - restoreComments(foreachStatement, body); + @NotNull PsiLoopStatement loopStatement, + @NotNull PsiStatement body, + @NotNull StreamApiMigrationInspection.TerminalBlock tb) { + restoreComments(loopStatement, body); final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); - StringBuilder buffer = generateStream(iteratedValue, tb.getLastOperation(), true); + StringBuilder buffer = generateStream(tb.getLastOperation(), true); PsiElement block = tb.convertToElement(elementFactory); buffer.append(".").append(myForEachMethodName).append("("); final String functionalExpressionText = tb.getVariable().getName() + " -> " + wrapInBlock(block); PsiExpressionStatement callStatement = (PsiExpressionStatement)elementFactory - .createStatementFromText(buffer.toString() + functionalExpressionText + ");", foreachStatement); - callStatement = (PsiExpressionStatement)foreachStatement.replace(callStatement); + .createStatementFromText(buffer.toString() + functionalExpressionText + ");", loopStatement); + callStatement = (PsiExpressionStatement)loopStatement.replace(callStatement); final PsiExpressionList argumentList = ((PsiCallExpression)callStatement.getExpression()).getArgumentList(); LOG.assertTrue(argumentList != null, callStatement.getText()); diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithMatchFix.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithMatchFix.java index d2a482e78190..1053a306f89c 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithMatchFix.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithMatchFix.java @@ -15,7 +15,6 @@ */ package com.intellij.codeInspection.streamMigration; -import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.InitializerUsageStatus; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; @@ -46,43 +45,40 @@ class ReplaceWithMatchFix extends MigrateToStreamFix { @Override PsiElement migrate(@NotNull Project project, - @NotNull ProblemDescriptor descriptor, - @NotNull PsiForeachStatement foreachStatement, - @NotNull PsiExpression iteratedValue, - @NotNull PsiStatement body, - @NotNull StreamApiMigrationInspection.TerminalBlock tb) { + @NotNull PsiLoopStatement loopStatement, + @NotNull PsiStatement body, + @NotNull StreamApiMigrationInspection.TerminalBlock tb) { PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); + StringBuilder builder = generateStream(tb.getLastOperation()); if(tb.getSingleStatement() instanceof PsiReturnStatement) { PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement(); PsiExpression value = returnStatement.getReturnValue(); if (ExpressionUtils.isLiteral(value, Boolean.TRUE) || ExpressionUtils.isLiteral(value, Boolean.FALSE)) { boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue(); - PsiReturnStatement nextReturnStatement = StreamApiMigrationInspection.getNextReturnStatement(foreachStatement); + PsiReturnStatement nextReturnStatement = StreamApiMigrationInspection.getNextReturnStatement(loopStatement); if (nextReturnStatement != null) { PsiExpression returnValue = nextReturnStatement.getReturnValue(); if(returnValue == null) return null; String methodName = foundResult ? "anyMatch" : "noneMatch"; - String streamText = generateStream(iteratedValue, tb.getLastOperation()).toString(); - streamText = addTerminalOperation(streamText, methodName, foreachStatement, tb); - restoreComments(foreachStatement, body); - if (nextReturnStatement.getParent() == foreachStatement.getParent()) { + String streamText = addTerminalOperation(builder.toString(), methodName, loopStatement, tb); + restoreComments(loopStatement, body); + if (nextReturnStatement.getParent() == loopStatement.getParent()) { if(!ExpressionUtils.isLiteral(returnValue, !foundResult)) { streamText+= (foundResult ? "||" : "&&") + ParenthesesUtils.getText(returnValue, ParenthesesUtils.AND_PRECEDENCE); } - removeLoop(foreachStatement); + removeLoop(loopStatement); return returnValue.replace(elementFactory.createExpressionFromText(streamText, nextReturnStatement)); } - return foreachStatement.replace(elementFactory.createStatementFromText("return " + streamText + ";", foreachStatement)); + return loopStatement.replace(elementFactory.createStatementFromText("return " + streamText + ";", loopStatement)); } } } PsiStatement[] statements = tb.getStatements(); - if (!(statements.length == 1 || (statements.length == 2 && ControlFlowUtils.statementBreaksLoop(statements[1], foreachStatement)))) { + if (!(statements.length == 1 || (statements.length == 2 && ControlFlowUtils.statementBreaksLoop(statements[1], loopStatement)))) { return null; } - restoreComments(foreachStatement, body); - String streamText = generateStream(iteratedValue, tb.getLastOperation()).toString(); - streamText = addTerminalOperation(streamText, "anyMatch", foreachStatement, tb); + restoreComments(loopStatement, body); + String streamText = addTerminalOperation(builder.toString(), "anyMatch", loopStatement, tb); PsiStatement statement = statements[0]; PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statement); if(assignment != null) { @@ -96,7 +92,7 @@ class ReplaceWithMatchFix extends MigrateToStreamFix { // for(....) if(...) {flag = true; break;} PsiVariable var = (PsiVariable)maybeVar; PsiExpression initializer = var.getInitializer(); - InitializerUsageStatus status = StreamApiMigrationInspection.getInitializerUsageStatus(var, foreachStatement); + InitializerUsageStatus status = StreamApiMigrationInspection.getInitializerUsageStatus(var, loopStatement); if(initializer != null && status != InitializerUsageStatus.UNKNOWN) { String replacement; if(ExpressionUtils.isLiteral(initializer, Boolean.FALSE) && @@ -108,12 +104,12 @@ class ReplaceWithMatchFix extends MigrateToStreamFix { } else { replacement = streamText + "?" + rValue.getText() + ":" + initializer.getText(); } - return replaceInitializer(foreachStatement, var, initializer, replacement, status); + return replaceInitializer(loopStatement, var, initializer, replacement, status); } } } String replacement = "if(" + streamText + "){" + statement.getText() + "}"; - return foreachStatement.replace(elementFactory.createStatementFromText(replacement, foreachStatement)); + return loopStatement.replace(elementFactory.createStatementFromText(replacement, loopStatement)); } private static String addTerminalOperation(String origStream, String methodName, @NotNull PsiElement contextElement, diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithSumFix.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithSumFix.java index 6c83570b1f2c..63cf08b98b11 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithSumFix.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ReplaceWithSumFix.java @@ -15,7 +15,7 @@ */ package com.intellij.codeInspection.streamMigration; -import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.MapOp; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import org.jetbrains.annotations.NotNull; @@ -33,11 +33,9 @@ class ReplaceWithSumFix extends MigrateToStreamFix { @Override PsiElement migrate(@NotNull Project project, - @NotNull ProblemDescriptor descriptor, - @NotNull PsiForeachStatement foreachStatement, - @NotNull PsiExpression iteratedValue, - @NotNull PsiStatement body, - @NotNull StreamApiMigrationInspection.TerminalBlock tb) { + @NotNull PsiLoopStatement loopStatement, + @NotNull PsiStatement body, + @NotNull StreamApiMigrationInspection.TerminalBlock tb) { PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class); if (assignment == null) return null; PsiVariable var = StreamApiMigrationInspection.extractAccumulator(assignment); @@ -50,9 +48,8 @@ class ReplaceWithSumFix extends MigrateToStreamFix { if (!type.equals(PsiType.DOUBLE) && !type.equals(PsiType.LONG)) { type = PsiType.INT; } - final StringBuilder builder = - generateStream(iteratedValue, new StreamApiMigrationInspection.MapOp(tb.getLastOperation(), addend, tb.getVariable(), type)); + StringBuilder builder = generateStream(new MapOp(tb.getLastOperation(), addend, tb.getVariable(), type)); builder.append(".sum()"); - return replaceWithNumericAddition(project, foreachStatement, var, builder, type); + return replaceWithNumericAddition(project, loopStatement, var, builder, type); } } 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 f4fb53ad8339..d84181e06202 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java @@ -32,6 +32,7 @@ import com.intellij.psi.controlFlow.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; @@ -43,6 +44,7 @@ import com.intellij.util.containers.IntArrayList; import com.siyeh.ig.psiutils.BoolUtils; import com.siyeh.ig.psiutils.ControlFlowUtils; import com.siyeh.ig.psiutils.ExpressionUtils; +import com.siyeh.ig.psiutils.ParenthesesUtils; import one.util.streamex.EntryStream; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; @@ -123,7 +125,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } static boolean isReferencedInOperations(PsiElement element, TerminalBlock tb) { - return ReferencesSearch.search(element, new LocalSearchScope(tb.intermediateExpressions().toArray(PsiElement[]::new))) + return ReferencesSearch.search(element, new LocalSearchScope(tb.intermediateAndSourceExpressions().toArray(PsiElement[]::new))) .findFirst() != null; } @@ -350,7 +352,10 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo return dependsOnCollection[0]; } - private static boolean isTrivial(PsiStatement body, PsiParameter parameter) { + @Contract("_, null -> false") + private static boolean isTrivial(PsiStatement body, PsiLoopStatement loopStatement) { + if(!(loopStatement instanceof PsiForeachStatement)) return false; + PsiParameter parameter = ((PsiForeachStatement)loopStatement).getIterationParameter(); //method reference final PsiExpression candidate = new LambdaCanBeMethodReferenceInspection() .canBeMethodReferenceProblem(body instanceof PsiBlockStatement ? ((PsiBlockStatement)body).getCodeBlock() : body, @@ -385,6 +390,27 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo return consumerClass != null ? psiFacade.getElementFactory().createType(consumerClass, variable.getType()) : null; } + static boolean isVariableSuitableForStream(PsiVariable variable, PsiStatement statement) { + PsiElement declaration = variable.getParent(); + // For-loop initializer is not effectively final, but suitable for stream conversion + if(declaration instanceof PsiDeclarationStatement) { + PsiElement grandParent = declaration.getParent(); + if (grandParent instanceof PsiForStatement) { + PsiForStatement forStatement = (PsiForStatement)grandParent; + if (forStatement.getInitialization() == declaration) { + PsiStatement body = forStatement.getBody(); + if(body != null && PsiTreeUtil.isAncestor(statement, body, false)) { + return ReferencesSearch.search(variable, new LocalSearchScope(body)).forEach(ref -> { + PsiElement element = ref.getElement(); + return !(element instanceof PsiExpression) || !PsiUtil.isAccessedForWriting((PsiExpression)element); + }); + } + } + } + } + return HighlightControlFlowUtil.isEffectivelyFinal(variable, statement, null); + } + @Contract("null -> null") static PsiLocalVariable extractCollectionVariable(PsiExpression qualifierExpression) { if (qualifierExpression instanceof PsiReferenceExpression) { @@ -465,26 +491,22 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo @Override public void visitForeachStatement(PsiForeachStatement statement) { super.visitForeachStatement(statement); - final PsiExpression iteratedValue = statement.getIteratedValue(); + processLoop(statement); + } + + @Override + public void visitForStatement(PsiForStatement statement) { + super.visitForStatement(statement); + processLoop(statement); + } + + void processLoop(PsiLoopStatement statement) { final PsiStatement body = statement.getBody(); - if (iteratedValue == null || body == null) return; - - final PsiType iteratedValueType = iteratedValue.getType(); - final PsiClass iteratorClass = PsiUtil.resolveClassInClassTypeOnly(iteratedValueType); - PsiClass collectionClass = null; - final boolean isArray; - if(iteratedValueType instanceof PsiArrayType) { - if(!isSupported(((PsiArrayType)iteratedValueType).getComponentType())) return; - isArray = true; - } else { - collectionClass = JavaPsiFacade.getInstance(body.getProject()).findClass(CommonClassNames.JAVA_UTIL_COLLECTION, statement.getResolveScope()); - if (collectionClass != null && InheritanceUtil.isInheritorOrSelf(iteratorClass, collectionClass, true)) { - isArray = false; - } else return; - } + if(body == null) return; + StreamSource source = StreamSource.tryCreate(statement); + if(source == null) return; if (!ExceptionUtil.getThrownCheckedExceptions(body).isEmpty()) return; - - TerminalBlock tb = TerminalBlock.from(statement.getIterationParameter(), body); + TerminalBlock tb = TerminalBlock.from(source, body); if(tb.isEmpty()) return; final ControlFlow controlFlow; @@ -502,7 +524,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo int startOffset = controlFlow.getStartOffset(body); int endOffset = controlFlow.getEndOffset(body); final List nonFinalVariables = StreamEx.of(ControlFlowUtil.getUsedVariables(controlFlow, startOffset, endOffset)) - .remove(variable -> HighlightControlFlowUtil.isEffectivelyFinal(variable, body, null)).toList(); + .remove(variable -> isVariableSuitableForStream(variable, statement)).toList(); if (exitPoints.isEmpty()) { if(getIncrementedVariable(tb, nonFinalVariables) != null) { @@ -514,8 +536,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo if(!nonFinalVariables.isEmpty()) { return; } - if ((isArray || !isRawSubstitution(iteratedValueType, collectionClass)) && isCollectCall(tb)) { - boolean addAll = !tb.hasOperations() && isAddAllCall(tb); + if (isCollectCall(tb)) { + boolean addAll = statement instanceof PsiForeachStatement && !tb.hasOperations() && isAddAllCall(tb); String methodName; if(addAll) { methodName = "addAll"; @@ -535,7 +557,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } // do not replace for(T e : arr) {} with Arrays.stream(arr).forEach(e -> {}) even if flag is set else if (SUGGEST_FOREACH && - (tb.hasOperations() || (!isArray && (REPLACE_TRIVIAL_FOREACH || !isTrivial(body, statement.getIterationParameter()))))) { + (tb.hasOperations() || (!(source instanceof ArrayStream) && (REPLACE_TRIVIAL_FOREACH || !isTrivial(body, statement))))) { ReplaceWithForeachCallFix forEachFix = new ReplaceWithForeachCallFix("forEach"); LocalQuickFix[] fixes = {forEachFix}; if (tb.hasOperations()) { //for .stream() @@ -548,8 +570,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo if (nonFinalVariables.isEmpty() && tb.getSingleStatement() instanceof PsiReturnStatement) { handleSingleReturn(statement, tb); } - // Intermediate ops should not refer to non-final variables - if (tb.intermediateExpressions() + // Source and intermediate ops should not refer to non-final variables + if (tb.intermediateAndSourceExpressions() .flatCollection(expr -> PsiTreeUtil.collectElementsOfType(expr, PsiReferenceExpression.class)) .map(PsiReferenceExpression::resolve).anyMatch(nonFinalVariables::contains)) { return; @@ -561,7 +583,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo return; } if (ReferencesSearch.search(tb.getVariable(), new LocalSearchScope(statements)).findFirst() == null - && exitPoints.size() == 1 && exitPoints.contains(breakStatement)) { + && exitPoints.size() == 1 && exitPoints.contains(breakStatement)) { registerProblem(statement, "anyMatch", new ReplaceWithMatchFix("anyMatch")); return; } @@ -583,14 +605,14 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } } - boolean canCollect(PsiForeachStatement statement, PsiMethodCallExpression methodCallExpression) { + boolean canCollect(PsiLoopStatement statement, PsiMethodCallExpression methodCallExpression) { if(methodCallExpression == null) return false; PsiLocalVariable variable = extractCollectionVariable(methodCallExpression.getMethodExpression().getQualifierExpression()); if(variable == null) return false; return getInitializerUsageStatus(variable, statement) != UNKNOWN; } - void handleSingleReturn(PsiForeachStatement statement, TerminalBlock tb) { + void handleSingleReturn(PsiLoopStatement statement, TerminalBlock tb) { PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement(); PsiExpression value = returnStatement.getReturnValue(); PsiReturnStatement nextReturnStatement = getNextReturnStatement(statement); @@ -616,7 +638,10 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } if (!isVariableReferenced(tb.getVariable(), value)) { Operation lastOp = tb.getLastOperation(); - if(!REPLACE_TRIVIAL_FOREACH && lastOp == null || (lastOp instanceof FilterOp && lastOp.getPreviousOp() == null)) return; + if (!REPLACE_TRIVIAL_FOREACH && lastOp instanceof StreamSource || + (lastOp instanceof FilterOp && lastOp.getPreviousOp() instanceof StreamSource)) { + return; + } registerProblem(statement, "anyMatch", new ReplaceWithMatchFix("anyMatch")); } if(nextReturnStatement != null && ExpressionUtils.isSimpleExpression(nextReturnStatement.getReturnValue()) @@ -625,13 +650,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } } - private boolean isRawSubstitution(PsiType iteratedValueType, PsiClass collectionClass) { - return iteratedValueType instanceof PsiClassType && PsiUtil - .isRawSubstitutor(collectionClass, TypeConversionUtil.getSuperClassSubstitutor(collectionClass, (PsiClassType)iteratedValueType)); - } - @NotNull - private TextRange getRange(PsiForeachStatement statement) { + private TextRange getRange(PsiLoopStatement statement) { boolean wholeStatement = false; if(myIsOnTheFly) { if (myKey == null) { @@ -643,18 +663,28 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo wholeStatement = HighlightDisplayLevel.DO_NOT_SHOW.equals(level); } } - PsiExpression iteratedValue = statement.getIteratedValue(); - LOG.assertTrue(iteratedValue != null); - PsiJavaToken rParenth = statement.getRParenth(); - if(wholeStatement && rParenth != null) { - return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1); + if(statement instanceof PsiForeachStatement) { + PsiJavaToken rParenth = ((PsiForeachStatement)statement).getRParenth(); + if (wholeStatement && rParenth != null) { + return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1); + } + PsiExpression iteratedValue = ((PsiForeachStatement)statement).getIteratedValue(); + LOG.assertTrue(iteratedValue != null); + return iteratedValue.getTextRange(); + } else if(statement instanceof PsiForStatement) { + PsiJavaToken rParenth = ((PsiForStatement)statement).getRParenth(); + if (wholeStatement && rParenth != null) { + return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1); + } + PsiStatement initialization = ((PsiForStatement)statement).getInitialization(); + LOG.assertTrue(initialization != null); + return initialization.getTextRange(); + } else { + throw new IllegalStateException("Unexpected statement type: "+statement); } - return iteratedValue.getTextRange(); } - private void registerProblem(PsiForeachStatement statement, String methodName, LocalQuickFix... fixes) { - PsiExpression iteratedValue = statement.getIteratedValue(); - LOG.assertTrue(iteratedValue != null); + private void registerProblem(PsiLoopStatement statement, String methodName, LocalQuickFix... fixes) { myHolder.registerProblem(statement, getRange(statement).shiftRight(-statement.getTextOffset()), "Can be replaced with '" + methodName + "' call", fixes); } @@ -700,7 +730,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } @Nullable - static PsiMethodCallExpression extractToArrayExpression(PsiForeachStatement statement, PsiMethodCallExpression expression) { + static PsiMethodCallExpression extractToArrayExpression(PsiLoopStatement statement, PsiMethodCallExpression expression) { // return collection.toArray() or collection.toArray(new Type[0]) or collection.toArray(new Type[collection.size()]); PsiElement nextElement = PsiTreeUtil.skipSiblingsForward(statement, PsiComment.class, PsiWhiteSpace.class); PsiExpression toArrayCandidate; @@ -799,7 +829,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo return StreamEx.of(myExpression); } - abstract String createReplacement(PsiElementFactory factory); + abstract String createReplacement(); } static class FilterOp extends Operation { @@ -815,7 +845,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } @Override - public String createReplacement(PsiElementFactory factory) { + public String createReplacement() { + PsiElementFactory factory = JavaPsiFacade.getElementFactory(myExpression.getProject()); PsiExpression intermediate = makeIntermediateExpression(factory); PsiExpression expression = myNegated ? factory.createExpressionFromText(BoolUtils.getNegatedExpressionText(intermediate), myExpression) : intermediate; @@ -858,7 +889,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } @Override - public String createReplacement(PsiElementFactory factory) { + public String createReplacement() { if (isIdentityMapping(myVariable, myExpression)) { if (!(myType instanceof PsiPrimitiveType)) { return myVariable.getType() instanceof PsiPrimitiveType ? ".boxed()" : ""; @@ -894,20 +925,33 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo static class FlatMapOp extends Operation { private final PsiLoopStatement myLoop; + private final StreamSource mySource; - FlatMapOp(@Nullable Operation previousOp, PsiExpression expression, PsiVariable variable, PsiLoopStatement loop) { - super(previousOp, expression, variable); + FlatMapOp(@Nullable Operation previousOp, StreamSource source, PsiVariable variable, PsiLoopStatement loop) { + super(previousOp, source.getExpression(), variable); myLoop = loop; + mySource = source; } @Override - public String createReplacement(PsiElementFactory factory) { - return ".flatMap(" + myVariable.getName() + " -> " + getStreamExpression() + ")"; + public String createReplacement() { + String operation = "flatMap"; + PsiType type = mySource.getVariable().getType(); + if(type instanceof PsiPrimitiveType && !type.equals(myVariable.getType())) { + if(type.equals(PsiType.INT)) { + operation = "flatMapToInt"; + } else if(type.equals(PsiType.LONG)) { + operation = "flatMapToLong"; + } else if(type.equals(PsiType.DOUBLE)) { + operation = "flatMapToDouble"; + } + } + return "." + operation + "(" + myVariable.getName() + " -> " + getStreamExpression() + ")"; } @NotNull String getStreamExpression() { - return myExpression.getText() + ".stream()"; + return mySource.createReplacement(); } boolean breaksMe(PsiBreakStatement statement) { @@ -915,35 +959,155 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } } - static class ArrayFlatMapOp extends FlatMapOp { - ArrayFlatMapOp(@Nullable Operation previousOp, PsiExpression expression, PsiVariable variable, PsiLoopStatement loop) { - super(previousOp, expression, variable, loop); + abstract static class StreamSource extends Operation { + protected StreamSource(PsiVariable variable, PsiExpression expression) { + super(null, expression, variable); } - @Override - public String createReplacement(PsiElementFactory factory) { - String operation = "flatMap"; - PsiType type = myExpression.getType(); - if(type instanceof PsiArrayType) { - PsiType componentType = ((PsiArrayType)type).getComponentType(); - if(componentType instanceof PsiPrimitiveType) { - if(componentType.equals(PsiType.INT)) { - operation = "flatMapToInt"; - } else if(componentType.equals(PsiType.LONG)) { - operation = "flatMapToLong"; - } else if(componentType.equals(PsiType.DOUBLE)) { - operation = "flatMapToDouble"; - } - } + @Contract("null -> null") + static StreamSource tryCreate(PsiLoopStatement statement) { + if(statement instanceof PsiForStatement) { + return CountingLoop.from((PsiForStatement)statement); } - return "." + operation + "(" + myVariable.getName() + " -> " + getStreamExpression() + ")"; + if(statement instanceof PsiForeachStatement) { + ArrayStream source = ArrayStream.from((PsiForeachStatement)statement); + return source == null ? CollectionStream.from((PsiForeachStatement)statement) : source; + } + return null; + } + } + + static class ArrayStream extends StreamSource { + private ArrayStream(PsiVariable variable, PsiExpression expression) { + super(variable, expression); } @Override - @NotNull - String getStreamExpression() { + String createReplacement() { return "java.util.Arrays.stream("+myExpression.getText() + ")"; } + + @Nullable + public static ArrayStream from(PsiForeachStatement statement) { + PsiExpression iteratedValue = statement.getIteratedValue(); + if (iteratedValue == null) return null; + + PsiType iteratedValueType = iteratedValue.getType(); + PsiParameter parameter = statement.getIterationParameter(); + + if (!(iteratedValueType instanceof PsiArrayType) || + !isSupported(((PsiArrayType)iteratedValueType).getComponentType()) || + ((parameter.getType() instanceof PsiPrimitiveType) && + !parameter.getType().equals(((PsiArrayType)iteratedValueType).getComponentType()))) { + return null; + } + + return new ArrayStream(parameter, iteratedValue); + } + } + + static class CollectionStream extends StreamSource { + + private CollectionStream(PsiVariable variable, PsiExpression expression) { + super(variable, expression); + } + + @Override + String createReplacement() { + return ParenthesesUtils.getText(myExpression, ParenthesesUtils.POSTFIX_PRECEDENCE) + ".stream()"; + } + + @Contract("null, _ -> false") + static boolean isRawSubstitution(PsiType iteratedValueType, PsiClass collectionClass) { + return iteratedValueType instanceof PsiClassType && PsiUtil + .isRawSubstitutor(collectionClass, TypeConversionUtil.getSuperClassSubstitutor(collectionClass, (PsiClassType)iteratedValueType)); + } + + @Nullable + public static CollectionStream from(PsiForeachStatement statement) { + PsiExpression iteratedValue = statement.getIteratedValue(); + if (iteratedValue == null) return null; + + PsiType iteratedValueType = iteratedValue.getType(); + PsiClass collectionClass = + JavaPsiFacade.getInstance(statement.getProject()).findClass(CommonClassNames.JAVA_UTIL_COLLECTION, statement.getResolveScope()); + PsiClass iteratorClass = PsiUtil.resolveClassInClassTypeOnly(iteratedValueType); + if (collectionClass == null || + !InheritanceUtil.isInheritorOrSelf(iteratorClass, collectionClass, true) || + isRawSubstitution(iteratedValueType, collectionClass)) { + return null; + } + return new CollectionStream(statement.getIterationParameter(), iteratedValue); + } + } + + static class CountingLoop extends StreamSource { + final PsiExpression myBound; + final boolean myIncluding; + + private CountingLoop(PsiLocalVariable counter, PsiExpression initializer, PsiExpression bound, boolean including) { + super(counter, initializer); + myBound = bound; + myIncluding = including; + } + + @Override + StreamEx expressions() { + return StreamEx.of(myExpression, myBound); + } + + @Override + public String createReplacement() { + String className = myVariable.getType().equals(PsiType.LONG) ? "java.util.stream.LongStream" : "java.util.stream.IntStream"; + String methodName = myIncluding ? "rangeClosed" : "range"; + return className+"."+methodName+"("+myExpression.getText()+", "+myBound.getText()+")"; + } + + @Nullable + public static CountingLoop from(PsiForStatement forStatement) { + // check that initialization is for(int/long i = ;...;...) + if(!(forStatement.getInitialization() instanceof PsiDeclarationStatement)) return null; + PsiDeclarationStatement initialization = (PsiDeclarationStatement)forStatement.getInitialization(); + if(initialization.getDeclaredElements().length != 1) return null; + PsiElement declaration = initialization.getDeclaredElements()[0]; + if(!(declaration instanceof PsiLocalVariable)) return null; + PsiLocalVariable counter = (PsiLocalVariable)declaration; + if(!counter.getType().equals(PsiType.INT) && !counter.getType().equals(PsiType.LONG)) return null; + + PsiExpression initializer = counter.getInitializer(); + if(initializer == null) return null; + + // check that increment is like for(...;...;i++) + if(!(forStatement.getUpdate() instanceof PsiExpressionStatement)) return null; + PsiExpression lValue = extractIncrementedLValue(((PsiExpressionStatement)forStatement.getUpdate()).getExpression()); + if(!(lValue instanceof PsiReferenceExpression) || ((PsiReferenceExpression)lValue).resolve() != counter) return null; + + // check that condition is like for(...;i subList.stream().anyMatch(condition)).forEach(subList -> ...) - TerminalBlock withFlatMapFilter = withFlatMap.extractFilter(); - if(withFlatMapFilter != null && !withFlatMapFilter.isEmpty()) { - PsiStatement[] statements = withFlatMapFilter.getStatements(); - PsiStatement lastStatement = statements[statements.length-1]; - if (lastStatement instanceof PsiBreakStatement && op.breaksMe((PsiBreakStatement)lastStatement) && - ReferencesSearch.search(withFlatMapFilter.getVariable(), new LocalSearchScope(statements)).findFirst() == null) { - return new TerminalBlock(new CompoundFilterOp((FilterOp)withFlatMapFilter.getLastOperation(), op), - myVariable, Arrays.copyOfRange(statements, 0, statements.length-1)); - } - } + if(getSingleStatement() instanceof PsiLoopStatement) { + PsiLoopStatement loopStatement = (PsiLoopStatement)getSingleStatement(); + StreamSource source = StreamSource.tryCreate(loopStatement); + final PsiStatement body = loopStatement.getBody(); + if(source == null || body == null) return null; + // flatMap from primitive to primitive is supported only if primitive types match + // otherwise it would be necessary to create bogus step like + // .mapToObj(var -> blahblah.stream()).flatMap(Function.identity()) + if(myVariable.getType() instanceof PsiPrimitiveType && !myVariable.getType().equals(source.getVariable().getType())) return null; + FlatMapOp op = new FlatMapOp(myPreviousOp, source, myVariable, loopStatement); + TerminalBlock withFlatMap = new TerminalBlock(op, source.getVariable(), body); + if(ReferencesSearch.search(myVariable, new LocalSearchScope(body)).findFirst() == null) { + return withFlatMap; + } else { + // Try extract nested filter like this: + // for(List subList : list) for(T t : subList) if(condition.test(t)) { ...; break; } + // if t is not used in "...", then this could be converted to + // list.stream().filter(subList -> subList.stream().anyMatch(condition)).forEach(subList -> ...) + TerminalBlock withFlatMapFilter = withFlatMap.extractFilter(); + if(withFlatMapFilter != null && !withFlatMapFilter.isEmpty()) { + PsiStatement[] statements = withFlatMapFilter.getStatements(); + PsiStatement lastStatement = statements[statements.length-1]; + if (lastStatement instanceof PsiBreakStatement && op.breaksMe((PsiBreakStatement)lastStatement) && + ReferencesSearch.search(withFlatMapFilter.getVariable(), new LocalSearchScope(statements)).findFirst() == null) { + return new TerminalBlock(new CompoundFilterOp((FilterOp)withFlatMapFilter.getLastOperation(), op), + myVariable, Arrays.copyOfRange(statements, 0, statements.length-1)); } } } @@ -1121,7 +1272,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo return null; } - @Nullable + @NotNull public Operation getLastOperation() { return myPreviousOp; } @@ -1141,7 +1292,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } public boolean hasOperations() { - return myPreviousOp != null; + return !(myPreviousOp instanceof StreamSource); } public boolean isEmpty() { @@ -1163,6 +1314,13 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo * @return stream of physical expressions used in intermediate operations in arbitrary order */ public StreamEx intermediateExpressions() { + return operations().remove(StreamSource.class::isInstance).flatMap(Operation::expressions); + } + + /** + * @return stream of physical expressions used in stream source and intermediate operations in arbitrary order + */ + public StreamEx intermediateAndSourceExpressions() { return operations().flatMap(Operation::expressions); } @@ -1184,8 +1342,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } @NotNull - public static TerminalBlock from(PsiVariable variable, PsiStatement statement) { - return new TerminalBlock(null, variable, statement).extractOperations(); + public static TerminalBlock from(StreamSource source, PsiStatement body) { + return new TerminalBlock(source, source.myVariable, body).extractOperations(); } } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFlatMapForLoop.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFlatMapForLoop.java new file mode 100644 index 000000000000..ed773c1fc647 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFlatMapForLoop.java @@ -0,0 +1,11 @@ +// "Replace with findFirst()" "true" + +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; + +public class Main { + public String testNestedForLoop(int[] data, List info) { + return Arrays.stream(data).flatMap(val -> IntStream.rangeClosed(0, val)).mapToObj(info::get).filter(str -> !str.isEmpty()).findFirst().orElse(null); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterForLoop.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterForLoop.java new file mode 100644 index 000000000000..6b3a851e108e --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterForLoop.java @@ -0,0 +1,12 @@ +// "Replace with collect" "true" + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class Main { + public void testForLoop(List input) { + List result = IntStream.range(0, 10).mapToObj(i -> input.get(i).length()).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterForLoopBoxed.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterForLoopBoxed.java new file mode 100644 index 000000000000..770bd8e3b061 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterForLoopBoxed.java @@ -0,0 +1,12 @@ +// "Replace with collect" "true" + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class Main { + public void testForLoop() { + List result = IntStream.rangeClosed(0, 10).boxed().collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterForLoopShortBound.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterForLoopShortBound.java new file mode 100644 index 000000000000..553dc8174c79 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterForLoopShortBound.java @@ -0,0 +1,14 @@ +// "Replace with collect" "true" + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class Main { + public void testForLoop(List input) { + List result; + short s = (short)input.size(); + result = IntStream.range(0, s).mapToObj(i -> input.get(i).length()).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterPrimitiveArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterPrimitiveArray.java new file mode 100644 index 000000000000..014c665a2163 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterPrimitiveArray.java @@ -0,0 +1,8 @@ +import java.util.Arrays; + +// "Replace with forEach" "true" +public class Main { + public void test(int[] arr) { + Arrays.stream(arr).filter(i -> i > 0).forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstUsedInBound.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstUsedInBound.java new file mode 100644 index 000000000000..8e8690af49c3 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstUsedInBound.java @@ -0,0 +1,18 @@ +// "Replace with findFirst()" "false" + +public class Main { + public boolean check(int value) { + return value == 3; + } + + public void find() { + int end = 10; + for(int i=0; i info) { + for(int val : data) { + for(int x = 0; x <= val; x++) { + String str = info.get(x); + if(!str.isEmpty()) { + return str; + } + } + } + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoop.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoop.java new file mode 100644 index 000000000000..10c9c38a60b1 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoop.java @@ -0,0 +1,13 @@ +// "Replace with collect" "true" + +import java.util.ArrayList; +import java.util.List; + +public class Main { + public void testForLoop(List input) { + List result = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + result.add(input.get(i).length()); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopBoxed.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopBoxed.java new file mode 100644 index 000000000000..e51f6d3667be --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopBoxed.java @@ -0,0 +1,13 @@ +// "Replace with collect" "true" + +import java.util.ArrayList; +import java.util.List; + +public class Main { + public void testForLoop() { + List result = new ArrayList<>(); + for (int i = 0; 10 >= i; i++) { + result.add(i); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopFloatBound.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopFloatBound.java new file mode 100644 index 000000000000..97d09fca2eda --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopFloatBound.java @@ -0,0 +1,14 @@ +// "Replace with collect" "false" + +import java.util.ArrayList; +import java.util.List; + +public class Main { + public void testForLoop(List input) { + List result = new ArrayList<>(); + float s = (float)input.size(); + for (int i = 0; i < s; i++) { + result.add(input.get(i).length()); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopShortBound.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopShortBound.java new file mode 100644 index 000000000000..18e38151d432 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopShortBound.java @@ -0,0 +1,14 @@ +// "Replace with collect" "true" + +import java.util.ArrayList; +import java.util.List; + +public class Main { + public void testForLoop(List input) { + List result = new ArrayList<>(); + short s = (short)input.size(); + for (int i = 0; i < s; i++) { + result.add(input.get(i).length()); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopUpdated.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopUpdated.java new file mode 100644 index 000000000000..24ad53592047 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopUpdated.java @@ -0,0 +1,13 @@ +// "Replace with collect" "false" + +import java.util.ArrayList; +import java.util.List; + +public class Main { + public void testForLoop(List input) { + List result = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + result.add(input.get(i++).length()); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopWrongBound.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopWrongBound.java new file mode 100644 index 000000000000..c19b9f975bea --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopWrongBound.java @@ -0,0 +1,13 @@ +// "Replace with collect" "false" + +import java.util.ArrayList; +import java.util.List; + +public class Main { + public void testForLoop(List input) { + List result = new ArrayList<>(); + for (int i = 0; i > 10; i++) { + result.add(input.get(i).length()); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopWrongIncrement.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopWrongIncrement.java new file mode 100644 index 000000000000..1c213239e97c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopWrongIncrement.java @@ -0,0 +1,13 @@ +// "Replace with collect" "false" + +import java.util.ArrayList; +import java.util.List; + +public class Main { + public void testForLoop(List input) { + List result = new ArrayList<>(); + for (int i = 0; i < 10; i+=2) { + result.add(input.get(i).length()); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopWrongInitializer.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopWrongInitializer.java new file mode 100644 index 000000000000..021899c429a2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeForLoopWrongInitializer.java @@ -0,0 +1,13 @@ +// "Replace with collect" "false" + +import java.util.ArrayList; +import java.util.List; + +public class Main { + public void testForLoop(List input) { + List result = new ArrayList<>(); + for (int i = 0, j=0; i < 10; i++) { + result.add(input.get(i).length()); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforePrimitiveArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforePrimitiveArray.java index 16a9a198f4c1..1ce9d739ffed 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforePrimitiveArray.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforePrimitiveArray.java @@ -1,7 +1,7 @@ -// "Replace with forEach" "false" +// "Replace with forEach" "true" public class Main { public void test(int[] arr) { - for(int i : arr) { + for(int i : arr) { if(i > 0) { System.out.println(i); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforePrimitiveArrayWrongType.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforePrimitiveArrayWrongType.java new file mode 100644 index 000000000000..21c93cb842be --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforePrimitiveArrayWrongType.java @@ -0,0 +1,10 @@ +// "Replace with forEach" "false" +public class Main { + public void test(int[] arr) { + for(float i : arr) { + if(i > 0) { + System.out.println(i); + } + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeSumAccessOuterFor.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeSumAccessOuterFor.java new file mode 100644 index 000000000000..82623b227e99 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeSumAccessOuterFor.java @@ -0,0 +1,18 @@ +// "Replace with sum()" "false" + +import java.util.List; + +public class Main { + public long testFor(List> list) { + long count = 0; + for (int i = 0; i < list.size(); i++) { + for (String s : list.get(i)) { + String trimmed = s.trim(); + if (trimmed.isEmpty()) { + count += i; + } + } + } + return count; + } +} \ No newline at end of file