diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/BaseStreamApiMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/BaseStreamApiMigration.java index 46cba24a9d6b..4f439f5c7bfa 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/BaseStreamApiMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/BaseStreamApiMigration.java @@ -28,7 +28,7 @@ import org.jetbrains.annotations.Nullable; * @author Tagir Valeev */ abstract class BaseStreamApiMigration { - private final boolean myShouldWarn; + private boolean myShouldWarn; private final String myReplacement; protected BaseStreamApiMigration(boolean shouldWarn, String replacement) { @@ -40,19 +40,23 @@ abstract class BaseStreamApiMigration { return myReplacement; } - abstract PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb); + abstract PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb); public boolean isShouldWarn() { return myShouldWarn; } - static PsiElement replaceWithOperation(PsiLoopStatement loopStatement, + public void setShouldWarn(boolean shouldWarn) { + myShouldWarn = shouldWarn; + } + + static PsiElement replaceWithOperation(PsiStatement loopStatement, PsiVariable var, String streamText, PsiType expressionType, OperationReductionMigration.ReductionOperation reductionOperation) { PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(loopStatement.getProject()); - restoreComments(loopStatement, loopStatement.getBody()); + restoreComments(loopStatement, loopStatement instanceof PsiLoopStatement? ((PsiLoopStatement)loopStatement).getBody(): loopStatement); InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(var, loopStatement); if (status != InitializerUsageStatus.UNKNOWN) { PsiExpression initializer = var.getInitializer(); @@ -67,7 +71,7 @@ abstract class BaseStreamApiMigration { loopStatement)); } - static PsiElement replaceInitializer(PsiLoopStatement loopStatement, + static PsiElement replaceInitializer(PsiStatement loopStatement, PsiVariable var, PsiExpression initializer, String replacement, @@ -90,11 +94,11 @@ abstract class BaseStreamApiMigration { @Nullable - static PsiElement replaceWithFindExtremum(@NotNull PsiLoopStatement loopStatement, + static PsiElement replaceWithFindExtremum(@NotNull PsiStatement loopStatement, @NotNull PsiVariable extremumHolder, @NotNull String streamText, @Nullable PsiVariable keyExtremum) { - restoreComments(loopStatement, loopStatement.getBody()); + restoreComments(loopStatement, loopStatement instanceof PsiLoopStatement? ((PsiLoopStatement)loopStatement).getBody(): loopStatement); if(keyExtremum != null) { keyExtremum.delete(); } @@ -102,14 +106,16 @@ abstract class BaseStreamApiMigration { return replaceInitializer(loopStatement, extremumHolder, extremumHolder.getInitializer(), streamText, status); } - static void restoreComments(PsiLoopStatement loopStatement, PsiStatement body) { - final PsiElement parent = loopStatement.getParent(); - for (PsiElement comment : PsiTreeUtil.findChildrenOfType(body, PsiComment.class)) { - parent.addBefore(comment, loopStatement); + static void restoreComments(PsiStatement statement, PsiElement body) { + if(statement instanceof PsiLoopStatement || statement instanceof PsiExpressionStatement) { + final PsiElement parent = statement.getParent(); + for (PsiElement comment : PsiTreeUtil.findChildrenOfType(body, PsiComment.class)) { + parent.addBefore(comment, statement); + } } } - static void removeLoop(@NotNull PsiLoopStatement statement) { + static void removeLoop(@NotNull PsiStatement statement) { PsiElement parent = statement.getParent(); if (parent instanceof PsiLabeledStatement) { parent.delete(); diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/CollectMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/CollectMigration.java index 18a34d8263a7..72cc0d338d01 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/CollectMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/CollectMigration.java @@ -75,8 +75,8 @@ class CollectMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { - PsiLoopStatement loopStatement = tb.getMainLoop(); + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { + PsiStatement loopStatement = tb.getStreamSourceStatement(); PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); CollectTerminal terminal = extractCollectTerminal(tb, null); if (terminal == null) return null; @@ -139,12 +139,12 @@ class CollectMigration extends BaseStreamApiMigration { CollectTerminal terminal = StreamEx.of(extractors).map(extractor -> extractor.apply(tb, call)).nonNull().findFirst().orElse(null); if (terminal != null) { if (terminal.getStatus() == ControlFlowUtils.InitializerUsageStatus.UNKNOWN) return null; - terminal = includePostStatements(terminal, tb.getMainLoop()); + terminal = includePostStatements(terminal, tb.getStreamSourceStatement()); } return terminal; } - static CollectTerminal includePostStatements(CollectTerminal terminal, PsiLoopStatement loop) { + static CollectTerminal includePostStatements(CollectTerminal terminal, PsiStatement loop) { List> wrappers = Arrays.asList(SortingTerminal::tryWrap, ToArrayTerminal::tryWrap, NewListTerminal::tryWrap); PsiElement nextStatement = loop; @@ -194,9 +194,9 @@ class CollectMigration extends BaseStreamApiMigration { abstract static class CollectTerminal { private final PsiLocalVariable myTargetVariable; private final InitializerUsageStatus myStatus; - final PsiLoopStatement myLoop; + final PsiStatement myLoop; - protected CollectTerminal(PsiLocalVariable variable, PsiLoopStatement loop, InitializerUsageStatus status) { + protected CollectTerminal(PsiLocalVariable variable, PsiStatement loop, InitializerUsageStatus status) { myTargetVariable = variable; myLoop = loop; myStatus = status; @@ -235,7 +235,7 @@ class CollectMigration extends BaseStreamApiMigration { AddingTerminal(@NotNull PsiLocalVariable target, PsiVariable element, PsiMethodCallExpression addCall, - PsiLoopStatement loop, + PsiStatement loop, InitializerUsageStatus status) { super(target, loop, hasLambdaCompatibleEmptyInitializer(target) ? status : ControlFlowUtils.InitializerUsageStatus.UNKNOWN); myTargetType = target.getType(); @@ -288,8 +288,8 @@ class CollectMigration extends BaseStreamApiMigration { PsiExpression count = tb.getCountExpression(); PsiLocalVariable variable = extractQualifierVariable(tb, call); if (variable != null) { - InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getMainLoop()); - AddingTerminal terminal = new AddingTerminal(variable, tb.getVariable(), call, tb.getMainLoop(), status); + InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getStreamSourceStatement()); + AddingTerminal terminal = new AddingTerminal(variable, tb.getVariable(), call, tb.getStreamSourceStatement(), status); if (count == null) return terminal; // like "list.add(x); if(list.size() >= limit) break;" if (!(count instanceof PsiMethodCallExpression)) return null; @@ -346,7 +346,7 @@ class CollectMigration extends BaseStreamApiMigration { AddingAllTerminal(PsiLocalVariable target, PsiVariable element, PsiMethodCallExpression addAllCall, - PsiLoopStatement loop, + PsiStatement loop, InitializerUsageStatus status) { super(target, element, null, loop, status); myAddAllCall = addAllCall; @@ -380,8 +380,8 @@ class CollectMigration extends BaseStreamApiMigration { if (collectionReference == null || tb.dependsOn(collectionReference)) return null; PsiLocalVariable target = tryCast(collectionReference.resolve(), PsiLocalVariable.class); if (target == null || StreamEx.of(args).skip(1).anyMatch(arg -> VariableAccessUtils.variableIsUsed(target, arg))) return null; - InitializerUsageStatus status = getInitializerUsageStatus(target, tb.getMainLoop()); - return new AddingAllTerminal(target, tb.getVariable(), call, tb.getMainLoop(), status); + InitializerUsageStatus status = getInitializerUsageStatus(target, tb.getStreamSourceStatement()); + return new AddingAllTerminal(target, tb.getVariable(), call, tb.getStreamSourceStatement(), status); } } @@ -447,7 +447,7 @@ class CollectMigration extends BaseStreamApiMigration { PsiType valueType = PsiUtil.substituteTypeParameter(mapType, CommonClassNames.JAVA_UTIL_MAP, 1, false); if (valueType == null) return null; AddingTerminal adding = new AddingTerminal(valueType, body, tb.getVariable(), call); - InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getMainLoop()); + InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getStreamSourceStatement()); return new GroupingTerminal(adding, variable, args[0], status); } } @@ -464,7 +464,7 @@ class CollectMigration extends BaseStreamApiMigration { ToMapTerminal(PsiMethodCallExpression call, PsiVariable elementVariable, PsiLocalVariable variable, - PsiLoopStatement loop, + PsiStatement loop, InitializerUsageStatus status) { super(variable, loop, status); myMapUpdateCall = call; @@ -517,8 +517,8 @@ class CollectMigration extends BaseStreamApiMigration { } PsiLocalVariable variable = extractQualifierVariable(tb, call); if (!hasLambdaCompatibleEmptyInitializer(variable)) return null; - InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getMainLoop()); - return new ToMapTerminal(call, tb.getVariable(), variable, tb.getMainLoop(), status); + InitializerUsageStatus status = getInitializerUsageStatus(variable, tb.getStreamSourceStatement()); + return new ToMapTerminal(call, tb.getVariable(), variable, tb.getStreamSourceStatement(), status); } } diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/CountMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/CountMigration.java index 47317ec23267..1675d439e282 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/CountMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/CountMigration.java @@ -31,7 +31,7 @@ class CountMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { PsiExpression expression = tb.getSingleExpression(PsiExpression.class); if (expression == null) { expression = tb.getCountExpression(); @@ -41,6 +41,6 @@ class CountMigration extends BaseStreamApiMigration { PsiElement element = ((PsiReferenceExpression)operand).resolve(); if (!(element instanceof PsiLocalVariable)) return null; PsiLocalVariable var = (PsiLocalVariable)element; - return replaceWithOperation(tb.getMainLoop(), var, tb.generate() + ".count()", PsiType.LONG, SUM_OPERATION); + return replaceWithOperation(tb.getStreamSourceStatement(), var, tb.generate() + ".count()", PsiType.LONG, SUM_OPERATION); } } diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindExtremumMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindExtremumMigration.java index 253832c593b4..97eb53417491 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindExtremumMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindExtremumMigration.java @@ -46,7 +46,7 @@ class FindExtremumMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { ExtremumTerminal terminal = extract(tb, null); if (terminal == null) return null; return terminal.replace(); @@ -187,7 +187,7 @@ class FindExtremumMigration extends BaseStreamApiMigration { static private boolean mayChangeBeforeLoop(@NotNull PsiVariable variable, @NotNull TerminalBlock terminalBlock) { ControlFlowUtils.InitializerUsageStatus status = - ControlFlowUtils.getInitializerUsageStatus(variable, terminalBlock.getMainLoop()); + ControlFlowUtils.getInitializerUsageStatus(variable, terminalBlock.getStreamSourceStatement()); return status.equals(ControlFlowUtils.InitializerUsageStatus.UNKNOWN); } @@ -286,7 +286,7 @@ class FindExtremumMigration extends BaseStreamApiMigration { if (method == null) return null; String inFilterOperation = myMax ? ">=" : "<="; - PsiLoopStatement loop = myTerminalBlock.getMainLoop(); + PsiStatement loop = myTerminalBlock.getStreamSourceStatement(); PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(loop.getProject()); String extremumInitializer = myExtremumKeyInitializer.getText(); PsiExpression condition = @@ -304,7 +304,7 @@ class FindExtremumMigration extends BaseStreamApiMigration { comparator = comparatorName; } String stream = blockWithFilter.generate() + "." + getOperation(myMax) + "(" + comparator + ").orElse(null)"; - return replaceWithFindExtremum(myTerminalBlock.getMainLoop(), myExtremum, stream, myExtremumKey); + return replaceWithFindExtremum(myTerminalBlock.getStreamSourceStatement(), myExtremum, stream, myExtremumKey); } @Override @@ -473,7 +473,7 @@ class FindExtremumMigration extends BaseStreamApiMigration { terminalBlock = blockWithMap; } String inFilterOperation = myMax ? ">=" : "<="; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(loop.getProject()); String extremumInitializer = myExtremumInitializer.getText(); Object nonFilterableInitialValue = getNonFilterableInitialValue(type, myMax); @@ -573,7 +573,7 @@ class FindExtremumMigration extends BaseStreamApiMigration { comparator = comparatorName; } String stream = myTerminalBlock.generate() + "." + getOperation(myMax) + "(" + comparator + ").orElse(null)"; - return replaceWithFindExtremum(myTerminalBlock.getMainLoop(), myExtremum, stream, null); + return replaceWithFindExtremum(myTerminalBlock.getStreamSourceStatement(), myExtremum, stream, null); } @Override diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindFirstMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindFirstMigration.java index 735530f08d9b..fe0cec4c4070 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindFirstMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/FindFirstMigration.java @@ -34,10 +34,10 @@ class FindFirstMigration extends BaseStreamApiMigration { FindFirstMigration(boolean shouldWarn) {super(shouldWarn, "findFirst()");} @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { PsiStatement statement = tb.getSingleStatement(); PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); - PsiLoopStatement loopStatement = tb.getMainLoop(); + PsiStatement loopStatement = tb.getStreamSourceStatement(); if (statement instanceof PsiReturnStatement) { PsiReturnStatement returnStatement = (PsiReturnStatement)statement; PsiExpression value = returnStatement.getReturnValue(); diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ForEachMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ForEachMigration.java index 46333df8eb63..0646a1556782 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ForEachMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ForEachMigration.java @@ -57,8 +57,8 @@ class ForEachMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { - PsiLoopStatement loopStatement = tb.getMainLoop(); + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { + PsiStatement loopStatement = tb.getStreamSourceStatement(); restoreComments(loopStatement, body); PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/JoiningMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/JoiningMigration.java index 96d8ab64bc16..2adc34927586 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/JoiningMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/JoiningMigration.java @@ -26,6 +26,7 @@ import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; +import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import com.siyeh.ig.callMatcher.CallMatcher; @@ -37,6 +38,7 @@ import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.BiFunction; +import java.util.function.Predicate; import java.util.stream.Collectors; import static com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil.isEffectivelyFinal; @@ -53,12 +55,12 @@ public class JoiningMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { JoiningTerminal terminal = extractTerminal(tb, null); if(terminal == null) return null; TerminalBlock block = terminal.getTerminalBlock(); - PsiLoopStatement loopStatement = block.getMainLoop(); + PsiStatement loopStatement = block.getStreamSourceStatement(); String stream = terminal.generateStreamCode(); restoreComments(loopStatement, body); PsiLocalVariable builder = terminal.getBuilder(); @@ -171,10 +173,22 @@ public class JoiningMigration extends BaseStreamApiMigration { } } - private static boolean canBeMadeNonFinal(@NotNull PsiLocalVariable variable, @NotNull PsiLoopStatement loop) { - NavigatablePsiElement loopBound = PsiTreeUtil.getParentOfType(loop, PsiMember.class, PsiLambdaExpression.class); - return ReferencesSearch.search(variable) - .forEach(reference -> PsiTreeUtil.getParentOfType(reference.getElement(), PsiMember.class, PsiLambdaExpression.class) == loopBound); + private static boolean canBeMadeNonFinal(@NotNull PsiLocalVariable variable, @NotNull PsiStatement sourceStatement) { + NavigatablePsiElement loopBound = PsiTreeUtil.getParentOfType(sourceStatement, PsiMember.class, PsiLambdaExpression.class); + + Predicate referenceBoundPredicate; + if (sourceStatement instanceof PsiLoopStatement) { + referenceBoundPredicate = + (reference) -> PsiTreeUtil.getParentOfType(reference.getElement(), PsiMember.class, PsiLambdaExpression.class) == loopBound; + } + else { + referenceBoundPredicate = (reference) -> { + PsiLambdaExpression lambda = PsiTreeUtil.getParentOfType(reference.getElement(), PsiLambdaExpression.class); + return PsiTreeUtil.getParentOfType(lambda, PsiMember.class, PsiLambdaExpression.class) == loopBound; + }; + } + return ReferencesSearch.search(variable).forEach((Processor)reference -> referenceBoundPredicate.test(reference)) && + FinalUtils.canBeFinal(variable); } String generateTerminal() { @@ -197,6 +211,11 @@ public class JoiningMigration extends BaseStreamApiMigration { } String generateIntermediate() { + if (TypeUtils.isJavaLangString(myLoopVariable.getType()) && + myMainJoinParts.size() == 1 && + myMainJoinParts.get(0) instanceof PsiReferenceExpression) { + return ""; + } PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(myLoopVariable.getProject()); String joinTransformation = getExpressionText(myMainJoinParts); PsiExpression mapping = elementFactory.createExpressionFromText(joinTransformation, myLoopVariable); @@ -664,12 +683,13 @@ public class JoiningMigration extends BaseStreamApiMigration { if(declarations == null) return null; PsiMethodCallExpression beforeLoopAppend = getCallBeforeStatement(firstAppendSuccessor, targetBuilder, APPEND, declarations); - if(!canBeMadeNonFinal(targetBuilder, terminalBlock.getMainLoop())) return null; + + if (!canBeMadeNonFinal(targetBuilder, terminalBlock.getStreamSourceStatement())) return null; List refs = StreamEx.of(ReferencesSearch.search(targetBuilder).findAll()) .map(PsiReference::getElement) .remove(e -> PsiTreeUtil.isAncestor(targetBuilder, e, false) || - PsiTreeUtil.isAncestor(terminalBlock.getMainLoop(), e, false)) + PsiTreeUtil.isAncestor(terminalBlock.getStreamSourceStatement(), e, false)) .toList(); allowedReferencePlaces.add(afterLoopAppend); @@ -732,7 +752,7 @@ public class JoiningMigration extends BaseStreamApiMigration { if (mainJoinParts == null || mainJoinParts.isEmpty()) return null; PsiLocalVariable targetBuilder = extractStringBuilder(statements.get(0)); if (targetBuilder == null) return null; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PrefixSuffixContext context = PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, emptyList(), new HashSet<>(emptyList())); if (context == null) return null; @@ -776,7 +796,7 @@ public class JoiningMigration extends BaseStreamApiMigration { List mainJoinParts = extractJoinParts(withoutCondition); if (mainJoinParts == null) return null; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PrefixSuffixContext context = PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, emptyList(), new HashSet<>(emptyList())); if (context == null) return null; @@ -846,7 +866,7 @@ public class JoiningMigration extends BaseStreamApiMigration { PsiLocalVariable targetBuilder = extractStringBuilder(firstIterationStatements.get(0)); if (targetBuilder == null) return null; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PrefixSuffixContext context = PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, singletonList(boolVar), new HashSet<>(emptyList())); @@ -896,7 +916,7 @@ public class JoiningMigration extends BaseStreamApiMigration { List mainJoinParts = joinData.getMainJoinParts(); List delimiterJoinParts = joinData.getDelimiterJoinParts(); - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PsiIfStatement ifStatement = tryCast(PsiTreeUtil.skipWhitespacesAndCommentsForward(loop), PsiIfStatement.class); if(ifStatement == null) return null; @@ -1019,7 +1039,7 @@ public class JoiningMigration extends BaseStreamApiMigration { PsiLocalVariable targetBuilder = extractStringBuilder(mainStatements.get(0)); if (targetBuilder == null) return null; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PrefixSuffixContext context = PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, singletonList(delimiterVar), new HashSet<>(emptyList())); @@ -1103,7 +1123,7 @@ public class JoiningMigration extends BaseStreamApiMigration { PsiLocalVariable targetBuilder = extractStringBuilder(firstIterationStatements.get(0)); if (targetBuilder == null) return null; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PrefixSuffixContext context = PrefixSuffixContext.extractAndVerifyRefs(loop, loop, targetBuilder, terminalBlock, emptyList(), new HashSet<>(emptyList())); if (context == null) return null; @@ -1180,7 +1200,7 @@ public class JoiningMigration extends BaseStreamApiMigration { JoinData joinData = JoinData.extractLeftDelimiter(joinParts); List delimiterJoinParts = joinData.getDelimiterJoinParts(); if (delimiterJoinParts.isEmpty()) return null; - PsiLoopStatement loop = terminalBlock.getMainLoop(); + PsiStatement loop = terminalBlock.getStreamSourceStatement(); PsiLocalVariable variable = tryCast(terminalBlock.getVariable(), PsiLocalVariable.class); if (variable == null) return null; PsiLocalVariable targetBuilder = extractStringBuilder(statements.get(0)); diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/MatchMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/MatchMigration.java index f2c792bc37fd..01fb925c3630 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/MatchMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/MatchMigration.java @@ -36,29 +36,29 @@ class MatchMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { - PsiLoopStatement loopStatement = tb.getMainLoop(); + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { + PsiStatement sourceStatement = tb.getStreamSourceStatement(); PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); 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(loopStatement); + PsiReturnStatement nextReturnStatement = StreamApiMigrationInspection.getNextReturnStatement(sourceStatement); if (nextReturnStatement != null) { PsiExpression returnValue = nextReturnStatement.getReturnValue(); if(returnValue == null) return null; String methodName = foundResult ? "anyMatch" : "noneMatch"; - String streamText = addTerminalOperation(methodName, loopStatement, tb); - restoreComments(loopStatement, body); - if (nextReturnStatement.getParent() == loopStatement.getParent()) { + String streamText = addTerminalOperation(methodName, sourceStatement, tb); + restoreComments(sourceStatement, body); + if (nextReturnStatement.getParent() == sourceStatement.getParent()) { if(!ExpressionUtils.isLiteral(returnValue, !foundResult)) { streamText+= (foundResult ? "||" : "&&") + ParenthesesUtils.getText(returnValue, ParenthesesUtils.AND_PRECEDENCE); } - removeLoop(loopStatement); + removeLoop(sourceStatement); return returnValue.replace(elementFactory.createExpressionFromText(streamText, nextReturnStatement)); } - PsiElement result = loopStatement.replace(elementFactory.createStatementFromText("return " + streamText + ";", loopStatement)); + PsiElement result = sourceStatement.replace(elementFactory.createStatementFromText("return " + streamText + ";", sourceStatement)); if(!isReachable(nextReturnStatement)) { nextReturnStatement.delete(); } @@ -67,11 +67,12 @@ class MatchMigration extends BaseStreamApiMigration { } } PsiStatement[] statements = tb.getStatements(); - if (!(statements.length == 1 || (statements.length == 2 && ControlFlowUtils.statementBreaksLoop(statements[1], loopStatement)))) { + if (!(statements.length == 1 || (sourceStatement instanceof PsiLoopStatement && statements.length == 2 && ControlFlowUtils.statementBreaksLoop(statements[1], + (PsiLoopStatement)sourceStatement)))) { return null; } - restoreComments(loopStatement, body); - String streamText = addTerminalOperation("anyMatch", loopStatement, tb); + restoreComments(sourceStatement, body); + String streamText = addTerminalOperation("anyMatch", sourceStatement, tb); PsiStatement statement = statements[0]; PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statement); if(assignment != null) { @@ -85,7 +86,7 @@ class MatchMigration extends BaseStreamApiMigration { // for(....) if(...) {flag = true; break;} PsiVariable var = (PsiVariable)maybeVar; PsiExpression initializer = var.getInitializer(); - InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(var, loopStatement); + InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(var, sourceStatement); if (initializer != null && status != ControlFlowUtils.InitializerUsageStatus.UNKNOWN) { String replacement; if (ExpressionUtils.isLiteral(initializer, Boolean.FALSE) && @@ -99,13 +100,13 @@ class MatchMigration extends BaseStreamApiMigration { else { replacement = streamText + "?" + rValue.getText() + ":" + initializer.getText(); } - return replaceInitializer(loopStatement, var, initializer, replacement, status); + return replaceInitializer(sourceStatement, var, initializer, replacement, status); } } } } String replacement = "if(" + streamText + "){" + statement.getText() + "}"; - return loopStatement.replace(elementFactory.createStatementFromText(replacement, loopStatement)); + return sourceStatement.replace(elementFactory.createStatementFromText(replacement, sourceStatement)); } private static String addTerminalOperation(String methodName, @NotNull PsiElement contextElement, @NotNull TerminalBlock tb) { 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 b7ab3486f0c4..14e275f9eae6 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/MigrateToStreamFix.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/MigrateToStreamFix.java @@ -21,30 +21,33 @@ import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.SimplifyStreamApiCallChainsInspection; import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.StreamSource; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiLoopStatement; -import com.intellij.psi.PsiStatement; +import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.impl.PsiDiamondTypeUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static com.intellij.util.ObjectUtils.tryCast; /** * @author Tagir Valeev */ class MigrateToStreamFix implements LocalQuickFix { private BaseStreamApiMigration myMigration; + @Nullable private final String myCustomName; - protected MigrateToStreamFix(BaseStreamApiMigration migration) { + protected MigrateToStreamFix(BaseStreamApiMigration migration, @Nullable String customName) { myMigration = migration; + myCustomName = customName; } @Nls @NotNull @Override public String getName() { - return "Replace with "+myMigration.getReplacement(); + return myCustomName!= null? myCustomName: "Replace with "+myMigration.getReplacement(); } @SuppressWarnings("DialogTitleCapitalization") @@ -63,11 +66,29 @@ class MigrateToStreamFix implements LocalQuickFix { PsiStatement body = loopStatement.getBody(); if(body == null || source == null) return; TerminalBlock tb = TerminalBlock.from(source, body); - PsiElement result = myMigration.migrate(project, body, tb); - if(result != null) { - tb.operations().forEach(StreamApiMigrationInspection.Operation::cleanUp); - simplifyAndFormat(project, result); - } + migrate(project, body, tb); + } else if(element instanceof PsiExpressionStatement) { + PsiMethodCallExpression call = tryCast(((PsiExpressionStatement)element).getExpression(), PsiMethodCallExpression.class); + if(call == null) return; + + PsiLambdaExpression lambda = SimplifyForEachInspection.extractLambdaFromForEach(call); + if (lambda == null) return; + PsiElement lambdaBody = lambda.getBody(); + SimplifyForEachInspection.ExistingStreamSource + source = SimplifyForEachInspection.ExistingStreamSource.extractSource(call, lambda); + if(source == null) return; + TerminalBlock terminalBlock = SimplifyForEachInspection.extractTerminalBlock(lambdaBody, source); + if (terminalBlock == null) return; + + migrate(project, lambdaBody, terminalBlock); + } + } + + private void migrate(@NotNull Project project, PsiElement block, TerminalBlock tb) { + PsiElement result = myMigration.migrate(project, block, tb); + if(result != null) { + tb.operations().forEach(StreamApiMigrationInspection.Operation::cleanUp); + simplifyAndFormat(project, result); } } diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/OperationReductionMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/OperationReductionMigration.java index 01034b4c4592..ced125ffe11e 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/OperationReductionMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/OperationReductionMigration.java @@ -40,7 +40,7 @@ public class OperationReductionMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class); if (assignment == null) return null; PsiVariable var = StreamApiMigrationInspection.extractAccumulator(assignment, myReductionOperation.getCompoundAssignmentOp()); @@ -71,7 +71,7 @@ public class OperationReductionMigration extends BaseStreamApiMigration { + String.format(Locale.ENGLISH, ".reduce(%s, (%s, %s) -> %s %s %s)", identity, leftOperand, rightOperand, leftOperand, myReductionOperation.getOperation(), rightOperand); - return replaceWithOperation(tb.getMainLoop(), var, stream, type, myReductionOperation); + return replaceWithOperation(tb.getStreamSourceStatement(), var, stream, type, myReductionOperation); } static class ReductionOperation { diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/SimplifyForEachInspection.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/SimplifyForEachInspection.java new file mode 100644 index 000000000000..acbea41d792d --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/SimplifyForEachInspection.java @@ -0,0 +1,195 @@ +/* + * 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.codeInsight.daemon.GroupNames; +import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.lang.java.JavaLanguage; +import com.intellij.openapi.roots.FileIndexFacade; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.impl.light.LightElement; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.PsiUtil; +import com.siyeh.ig.callMatcher.CallMatcher; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static com.intellij.util.ObjectUtils.tryCast; + +public class SimplifyForEachInspection extends BaseJavaBatchLocalInspectionTool { + private static final CallMatcher.Simple ITERABLE_FOREACH = + CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_ITERABLE, "forEach").parameterCount(1); + private static final CallMatcher.Simple STREAM_FOREACH_ORDERED = + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, "forEachOrdered").parameterCount(1); + private static final CallMatcher STREAM_FOREACH = + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, "forEach", "forEachOrdered").parameterCount(1); + private static final CallMatcher FOREACH = CallMatcher.anyOf( + STREAM_FOREACH, + ITERABLE_FOREACH + ); + + + @Nls + @NotNull + @Override + public String getGroupDisplayName() { + return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME; + } + + @Nls + @NotNull + @Override + public String getDisplayName() { + return "forEach call can be simplified"; + } + + + @NotNull + @Override + public String getShortName() { + return "SimplifyForEach"; + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + PsiFile file = holder.getFile(); + VirtualFile virtualFile = file.getVirtualFile(); + if (!PsiUtil.isLanguageLevel8OrHigher(file) || virtualFile == null || + !FileIndexFacade.getInstance(holder.getProject()).isInSourceContent(virtualFile)) { + return PsiElementVisitor.EMPTY_VISITOR; + } + return new JavaElementVisitor() { + @Override + public void visitMethodCallExpression(PsiMethodCallExpression call) { + PsiLambdaExpression lambda = extractLambdaFromForEach(call); + if (lambda == null) return; + PsiElement lambdaBody = lambda.getBody(); + ExistingStreamSource + source = ExistingStreamSource.extractSource(call, lambda); + if (source == null) return; + TerminalBlock terminalBlock = extractTerminalBlock(lambdaBody, source); + if (terminalBlock == null) return; + + PsiStatement mainStatement = source.getMainStatement(); + BaseStreamApiMigration migration = + StreamApiMigrationInspection.findMigration(mainStatement, lambdaBody, terminalBlock, holder, true, true); + boolean opCountChanged = terminalBlock.getOperationCount() > 1; + boolean lastOpChanged = !(migration instanceof ForEachMigration); + if (opCountChanged || lastOpChanged) { + String customMessage; + if (opCountChanged && !lastOpChanged) { + customMessage = "Extract intermediate operations"; + if (STREAM_FOREACH_ORDERED.test(call)) { + migration = new ForEachMigration(migration.isShouldWarn(), "forEachOrdered"); + } + } + else { + customMessage = null; + } + if (migration != null) { + migration.setShouldWarn(true); + } + + StreamApiMigrationInspection + .offerMigration(mainStatement, terminalBlock, migration, m -> getRange(call).shiftRight(-call.getTextOffset()), customMessage, false, holder); + } + } + }; + } + + @NotNull + private static TextRange getRange(PsiMethodCallExpression call) { + PsiReferenceExpression methodExpression = call.getMethodExpression(); + return new TextRange(methodExpression.getTextOffset(), call.getArgumentList().getTextOffset()); + } + + @Nullable + static TerminalBlock extractTerminalBlock(@Nullable PsiElement lambdaBody, + @NotNull ExistingStreamSource source) { + if (lambdaBody instanceof PsiCodeBlock) { + return TerminalBlock.from(source, (PsiCodeBlock)lambdaBody); + } + if (lambdaBody instanceof PsiExpression) { + return TerminalBlock.fromStatements(source, new LightExpressionStatement((PsiExpression)lambdaBody)); + } + return null; + } + + @Nullable + static PsiLambdaExpression extractLambdaFromForEach(PsiMethodCallExpression call) { + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (qualifier == null || + !FOREACH.test(call) || + !InheritanceUtil.isInheritor(qualifier.getType(), CommonClassNames.JAVA_UTIL_COLLECTION)) return null; + PsiExpression arg = call.getArgumentList().getExpressions()[0]; + return tryCast(PsiUtil.skipParenthesizedExprDown(arg), PsiLambdaExpression.class); + } + + static class LightExpressionStatement extends LightElement implements PsiExpressionStatement { + @NotNull private final PsiExpression myExpression; + + protected LightExpressionStatement(@NotNull PsiExpression expression) { + super(expression.getManager(), JavaLanguage.INSTANCE); + myExpression = expression; + } + + @NotNull + @Override + public PsiExpression getExpression() { + return myExpression; + } + + @Override + public String toString() { + return myExpression.getText() + ";"; + } + } + + + static class ExistingStreamSource extends StreamApiMigrationInspection.StreamSource { + private final boolean myIsCollectionForEach; + + protected ExistingStreamSource(PsiStatement mainStatement, PsiVariable variable, PsiExpression expression, boolean isCollectionForEach) { + super(mainStatement, variable, expression); + myIsCollectionForEach = isCollectionForEach; + } + + @Override + String createReplacement() { + return myExpression.getText() + (myIsCollectionForEach? ".stream()" : ""); + } + + @Nullable + static ExistingStreamSource extractSource(PsiMethodCallExpression call, PsiLambdaExpression lambda) { + PsiParameter[] parameters = lambda.getParameterList().getParameters(); + if (parameters.length != 1) return null; + PsiParameter parameter = parameters[0]; + + boolean isCollectionForEach = ITERABLE_FOREACH.test(call); + + PsiExpression qualifier = call.getMethodExpression().getQualifierExpression(); + if (qualifier == null) return null; + PsiStatement parent = tryCast(call.getParent(), PsiExpressionStatement.class); + if (parent == null) return null; + return new ExistingStreamSource(parent, parameter, qualifier, isCollectionForEach); + } + } +} 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 16d6b5a449e7..a6ee70f36c56 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java @@ -25,6 +25,7 @@ import com.intellij.codeInspection.LambdaCanBeMethodReferenceInspection; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel; +import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.FileIndexFacade; @@ -33,6 +34,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.*; import com.intellij.psi.controlFlow.*; +import com.intellij.psi.impl.light.LightElement; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.tree.IElementType; @@ -41,6 +43,8 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; +import com.siyeh.ig.callMatcher.CallMatcher; import com.siyeh.ig.psiutils.*; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Contract; @@ -52,6 +56,7 @@ import javax.swing.*; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.function.Function; import static com.intellij.codeInspection.streamMigration.OperationReductionMigration.SUM_OPERATION; import static com.intellij.util.ObjectUtils.tryCast; @@ -60,8 +65,10 @@ import static com.siyeh.ig.psiutils.ControlFlowUtils.InitializerUsageStatus.UNKN public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTool { private static final Logger LOG = Logger.getInstance(StreamApiMigrationInspection.class); + public boolean REPLACE_TRIVIAL_FOREACH; public boolean SUGGEST_FOREACH; + private static final String SHORT_NAME = "Convert2streamapi"; @Nullable @Override @@ -94,7 +101,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo @NotNull @Override public String getShortName() { - return "Convert2streamapi"; + return SHORT_NAME; } @NotNull @@ -315,7 +322,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } return variable != null && ExpressionUtils.isZero(variable.getInitializer()) && - ControlFlowUtils.getInitializerUsageStatus(variable, tb.getMainLoop()) != UNKNOWN; + ControlFlowUtils.getInitializerUsageStatus(variable, tb.getStreamSourceStatement()) != UNKNOWN; } private static boolean isTrivial(TerminalBlock tb) { @@ -437,203 +444,222 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo if (!ExceptionUtil.getThrownCheckedExceptions(body).isEmpty()) return; TerminalBlock tb = TerminalBlock.from(source, body); - BaseStreamApiMigration migration = findMigration(statement, body, tb); - if (migration != null && (myIsOnTheFly || migration.isShouldWarn())) { - MigrateToStreamFix[] fixes = {new MigrateToStreamFix(migration)}; - if (migration instanceof ForEachMigration && !(tb.getLastOperation() instanceof CollectionStream)) { //for .stream() - fixes = ArrayUtil.append(fixes, new MigrateToStreamFix(new ForEachMigration(migration.isShouldWarn(), "forEachOrdered"))); - } - ProblemHighlightType highlightType = - migration.isShouldWarn() ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.INFORMATION; - myHolder.registerProblem(statement, "Can be replaced with '" + migration.getReplacement() + "' call", - highlightType, getRange(migration.isShouldWarn(), statement).shiftRight(-statement.getTextOffset()), - fixes); - } + BaseStreamApiMigration migration = findMigration(statement, body, tb, myHolder, SUGGEST_FOREACH, REPLACE_TRIVIAL_FOREACH); + offerMigration(statement, tb, migration, (streamApiMigration) -> getRange(streamApiMigration.isShouldWarn(), statement, myIsOnTheFly) + .shiftRight(-statement.getTextOffset()), null, myIsOnTheFly, myHolder); } + } - @Nullable - private BaseStreamApiMigration findMigration(PsiLoopStatement loop, PsiStatement body, TerminalBlock tb) { - final ControlFlow controlFlow; - try { - controlFlow = ControlFlowFactory.getInstance(myHolder.getProject()) - .getControlFlow(body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance()); - } - catch (AnalysisCanceledException ignored) { - return null; - } - int startOffset = controlFlow.getStartOffset(body); - int endOffset = controlFlow.getEndOffset(body); - if (startOffset < 0 || endOffset < 0) return null; - PsiElement surrounder = PsiTreeUtil.getParentOfType(loop, PsiLambdaExpression.class, PsiClass.class); - final List nonFinalVariables = StreamEx.of(ControlFlowUtil.getUsedVariables(controlFlow, startOffset, endOffset)) - .remove(variable -> variable instanceof PsiField) - .remove(variable -> PsiTreeUtil.getParentOfType(variable, PsiLambdaExpression.class, PsiClass.class) != surrounder) - .remove(variable -> isVariableSuitableForStream(variable, loop, tb)).toList(); + static void offerMigration(PsiStatement statement, + TerminalBlock tb, + BaseStreamApiMigration migration, + Function rangeSupplier, + @Nullable String customMessage, + boolean isOnTheFly, + ProblemsHolder holder) { + if (migration != null && (isOnTheFly || migration.isShouldWarn())) { - if (isCountOperation(nonFinalVariables, tb)) { - return new CountMigration(true); - } - if (nonFinalVariables.size() == 0) { - CollectMigration.CollectTerminal terminal = CollectMigration.extractCollectTerminal(tb, nonFinalVariables); - if (terminal != null) { - boolean addAll = loop instanceof PsiForeachStatement && !tb.hasOperations() && isAddAllCall(tb); - // Don't suggest to convert the loop which can be trivially replaced via addAll: - // this is covered by UseBulkOperationInspection and ManualArrayToCollectionCopyInspection - if (addAll) return null; - boolean shouldWarn = REPLACE_TRIVIAL_FOREACH || - tb.hasOperations() || - tb.getLastOperation() instanceof BufferedReaderLines || - !terminal.isTrivial(); - return new CollectMigration(shouldWarn, terminal.getMethodName()); - } - } - if(JoiningMigration.extractTerminal(tb, nonFinalVariables) != null) { - return new JoiningMigration(true); - } - if (tb.getCountExpression() != null || tb.isEmpty()) return null; - if (nonFinalVariables.isEmpty() && extractArray(tb) != null) { - return new ToArrayMigration(true); - } - if (getAccumulatedVariable(tb, nonFinalVariables, SUM_OPERATION) != null) { - return new SumMigration(true); - } - FindExtremumMigration.ExtremumTerminal extremumTerminal = FindExtremumMigration.extract(tb, nonFinalVariables); - if(extremumTerminal != null) { - return new FindExtremumMigration(true, FindExtremumMigration.getOperation(extremumTerminal.isMax()) + "()"); - } - for (OperationReductionMigration.ReductionOperation reductionOperation : OperationReductionMigration.OPERATIONS) { - if (getAccumulatedVariable(tb, nonFinalVariables, reductionOperation) != null) { - return new OperationReductionMigration(true, reductionOperation); - } - } - Collection exitPoints = tb.findExitPoints(controlFlow); - if (exitPoints == null) return null; - boolean onlyNonLabeledContinue = StreamEx.of(exitPoints).allMatch(statement -> statement instanceof PsiContinueStatement && - ((PsiContinueStatement)statement).getLabelIdentifier() == null); - if (onlyNonLabeledContinue && nonFinalVariables.isEmpty()) { - boolean shouldWarn = SUGGEST_FOREACH && - (REPLACE_TRIVIAL_FOREACH || - tb.hasOperations() || - ForEachMigration.tryExtractMapExpression(tb) != null || - !isTrivial(tb)); - return new ForEachMigration(shouldWarn, "forEach"); - } - if (nonFinalVariables.isEmpty() && tb.getSingleStatement() instanceof PsiReturnStatement) { - return findMigrationForReturn(loop, tb); - } - // Source and intermediate ops should not refer to non-final variables - if (tb.intermediateAndSourceExpressions() - .flatCollection(expr -> PsiTreeUtil.collectElementsOfType(expr, PsiReferenceExpression.class)) - .map(PsiReferenceExpression::resolve).select(PsiVariable.class).anyMatch(nonFinalVariables::contains)) { - return null; - } - PsiStatement[] statements = tb.getStatements(); - if (statements.length == 2) { - PsiStatement breakStatement = statements[1]; - if (ControlFlowUtils.statementBreaksLoop(breakStatement, loop) && - exitPoints.size() == 1 && - exitPoints.contains(breakStatement)) { - return findMigrationForBreak(tb, nonFinalVariables, statements[0]); - } + MigrateToStreamFix[] fixes = {new MigrateToStreamFix(migration, customMessage)}; + if (migration instanceof ForEachMigration && !(tb.getLastOperation() instanceof CollectionStream)) { //for .stream() + fixes = ArrayUtil.append(fixes, new MigrateToStreamFix(new ForEachMigration(migration.isShouldWarn(), "forEachOrdered"), customMessage)); } + ProblemHighlightType highlightType = + migration.isShouldWarn() ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.INFORMATION; + String message = customMessage != null ? customMessage : "Can be replaced with '" + migration.getReplacement() + "' call"; + holder.registerProblem(statement, message, highlightType, rangeSupplier.apply(migration), fixes); + } + } + + + @Nullable + static BaseStreamApiMigration findMigration(PsiStatement loop, + PsiElement body, + TerminalBlock tb, + ProblemsHolder holder, + boolean suggestForeach, + boolean replaceTrivialForEach) { + final ControlFlow controlFlow; + try { + controlFlow = ControlFlowFactory.getInstance(holder.getProject()) + .getControlFlow(body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance()); + } + catch (AnalysisCanceledException ignored) { return null; } + int startOffset = controlFlow.getStartOffset(body); + int endOffset = controlFlow.getEndOffset(body); + if (startOffset < 0 || endOffset < 0) return null; + PsiElement surrounder = PsiTreeUtil.getParentOfType(loop, PsiLambdaExpression.class, PsiClass.class); + final List nonFinalVariables = StreamEx.of(ControlFlowUtil.getUsedVariables(controlFlow, startOffset, endOffset)) + .remove(variable -> variable instanceof PsiField) + .remove(variable -> PsiTreeUtil.getParentOfType(variable, PsiLambdaExpression.class, PsiClass.class) != surrounder) + .remove(variable -> isVariableSuitableForStream(variable, loop, tb)).toList(); - @Nullable - private BaseStreamApiMigration findMigrationForBreak(TerminalBlock tb, List nonFinalVariables, PsiStatement statement) { - boolean shouldWarn = REPLACE_TRIVIAL_FOREACH || tb.hasOperations(); - if (ReferencesSearch.search(tb.getVariable(), new LocalSearchScope(statement)).findFirst() == null) { - return new MatchMigration(shouldWarn, "anyMatch"); + if (isCountOperation(nonFinalVariables, tb)) { + return new CountMigration(true); + } + if (nonFinalVariables.size() == 0) { + CollectMigration.CollectTerminal terminal = CollectMigration.extractCollectTerminal(tb, nonFinalVariables); + if (terminal != null) { + boolean addAll = loop instanceof PsiForeachStatement && !tb.hasOperations() && isAddAllCall(tb); + // Don't suggest to convert the loop which can be trivially replaced via addAll: + // this is covered by UseBulkOperationInspection and ManualArrayToCollectionCopyInspection + if (addAll) return null; + boolean shouldWarn = replaceTrivialForEach || + tb.hasOperations() || + tb.getLastOperation() instanceof BufferedReaderLines || + !terminal.isTrivial(); + return new CollectMigration(shouldWarn, terminal.getMethodName()); } - if (nonFinalVariables.isEmpty() && statement instanceof PsiExpressionStatement) { - return new FindFirstMigration(shouldWarn); - } - if (nonFinalVariables.size() == 1) { - PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statement); - if (assignment == null) return null; - PsiReferenceExpression lValue = tryCast(assignment.getLExpression(), PsiReferenceExpression.class); - if (lValue == null) return null; - PsiVariable var = tryCast(lValue.resolve(), PsiVariable.class); - if (var == null || !nonFinalVariables.contains(var)) return null; - PsiExpression rValue = assignment.getRExpression(); - if (rValue == null || VariableAccessUtils.variableIsUsed(var, rValue)) return null; - if (tb.getVariable().getType() instanceof PsiPrimitiveType && !ExpressionUtils.isReferenceTo(rValue, tb.getVariable())) return null; - return new FindFirstMigration(shouldWarn); + } + if(JoiningMigration.extractTerminal(tb, nonFinalVariables) != null) { + return new JoiningMigration(true); + } + if (tb.getCountExpression() != null || tb.isEmpty()) return null; + if (nonFinalVariables.isEmpty() && extractArray(tb) != null) { + return new ToArrayMigration(true); + } + if (getAccumulatedVariable(tb, nonFinalVariables, SUM_OPERATION) != null) { + return new SumMigration(true); + } + FindExtremumMigration.ExtremumTerminal extremumTerminal = FindExtremumMigration.extract(tb, nonFinalVariables); + if(extremumTerminal != null) { + return new FindExtremumMigration(true, FindExtremumMigration.getOperation(extremumTerminal.isMax()) + "()"); + } + for (OperationReductionMigration.ReductionOperation reductionOperation : OperationReductionMigration.OPERATIONS) { + if (getAccumulatedVariable(tb, nonFinalVariables, reductionOperation) != null) { + return new OperationReductionMigration(true, reductionOperation); } + } + Collection exitPoints = tb.findExitPoints(controlFlow); + if (exitPoints == null) return null; + boolean onlyNonLabeledContinue = StreamEx.of(exitPoints).allMatch(statement -> statement instanceof PsiContinueStatement && ((PsiContinueStatement)statement).getLabelIdentifier() == null); + if (onlyNonLabeledContinue && nonFinalVariables.isEmpty()) { + boolean shouldWarn = suggestForeach && + (replaceTrivialForEach || + tb.hasOperations() || + ForEachMigration.tryExtractMapExpression(tb) != null || + !isTrivial(tb)); + return new ForEachMigration(shouldWarn, "forEach"); + } + if (nonFinalVariables.isEmpty() && tb.getSingleStatement() instanceof PsiReturnStatement) { + return findMigrationForReturn(loop, tb, replaceTrivialForEach); + } + // Source and intermediate ops should not refer to non-final variables + if (tb.intermediateAndSourceExpressions() + .flatCollection(expr -> PsiTreeUtil.collectElementsOfType(expr, PsiReferenceExpression.class)) + .map(PsiReferenceExpression::resolve).select(PsiVariable.class).anyMatch(nonFinalVariables::contains)) { return null; } - - @Nullable - private BaseStreamApiMigration findMigrationForReturn(PsiLoopStatement statement, TerminalBlock tb) { - boolean shouldWarn = REPLACE_TRIVIAL_FOREACH || tb.hasOperations(); - PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement(); - PsiExpression value = returnStatement.getReturnValue(); - PsiReturnStatement nextReturnStatement = getNextReturnStatement(statement); - if (nextReturnStatement != null && - (ExpressionUtils.isLiteral(value, Boolean.TRUE) || ExpressionUtils.isLiteral(value, Boolean.FALSE))) { - boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue(); - String methodName; - if (foundResult) { - methodName = "anyMatch"; - } - else { - methodName = "noneMatch"; - FilterOp lastFilter = tb.getLastOperation(FilterOp.class); - if (lastFilter != null && (lastFilter.isNegated() ^ BoolUtils.isNegation(lastFilter.getExpression()))) { - methodName = "allMatch"; - } - } - if (nextReturnStatement.getParent() == statement.getParent() || - ExpressionUtils.isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) { - return new MatchMigration(shouldWarn, methodName); - } + PsiStatement[] statements = tb.getStatements(); + if (statements.length == 2) { + PsiStatement breakStatement = statements[1]; + if (loop instanceof PsiLoopStatement && ControlFlowUtils.statementBreaksLoop(breakStatement, (PsiLoopStatement)loop) && + exitPoints.size() == 1 && + exitPoints.contains(breakStatement)) { + return findMigrationForBreak(tb, nonFinalVariables, statements[0], replaceTrivialForEach); } - if (!VariableAccessUtils.variableIsUsed(tb.getVariable(), value)) { - if (!REPLACE_TRIVIAL_FOREACH && !tb.hasOperations() || - (tb.getLastOperation() instanceof FilterOp && tb.operations().count() == 2)) { - return null; - } - return new MatchMigration(shouldWarn, "anyMatch"); - } - if (nextReturnStatement != null && ExpressionUtils.isSimpleExpression(nextReturnStatement.getReturnValue()) - && (!(tb.getVariable().getType() instanceof PsiPrimitiveType) || ExpressionUtils.isReferenceTo(value, tb.getVariable()))) { - return new FindFirstMigration(shouldWarn); - } - return null; } + return null; + } - @NotNull - private TextRange getRange(boolean shouldWarn, PsiLoopStatement statement) { - boolean wholeStatement = - myIsOnTheFly && (!shouldWarn || InspectionProjectProfileManager.isInformationLevel(getShortName(), statement)); - 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 if (statement instanceof PsiWhileStatement) { - PsiJavaToken rParenth = ((PsiWhileStatement)statement).getRParenth(); - if (wholeStatement && rParenth != null) { - return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1); - } - return statement.getFirstChild().getTextRange(); + @Nullable + private static BaseStreamApiMigration findMigrationForBreak(TerminalBlock tb, + List nonFinalVariables, + PsiStatement statement, + boolean replaceTrivialForEach) { + boolean shouldWarn = replaceTrivialForEach || tb.hasOperations(); + if (ReferencesSearch.search(tb.getVariable(), new LocalSearchScope(statement)).findFirst() == null) { + return new MatchMigration(shouldWarn, "anyMatch"); + } + if (nonFinalVariables.isEmpty() && statement instanceof PsiExpressionStatement) { + return new FindFirstMigration(shouldWarn); + } + if (nonFinalVariables.size() == 1) { + PsiAssignmentExpression assignment = ExpressionUtils.getAssignment(statement); + if (assignment == null) return null; + PsiReferenceExpression lValue = tryCast(assignment.getLExpression(), PsiReferenceExpression.class); + if (lValue == null) return null; + PsiVariable var = tryCast(lValue.resolve(), PsiVariable.class); + if (var == null || !nonFinalVariables.contains(var)) return null; + PsiExpression rValue = assignment.getRExpression(); + if (rValue == null || VariableAccessUtils.variableIsUsed(var, rValue)) return null; + if (tb.getVariable().getType() instanceof PsiPrimitiveType && !ExpressionUtils.isReferenceTo(rValue, tb.getVariable())) return null; + return new FindFirstMigration(shouldWarn); + } + return null; + } + + @Nullable + private static BaseStreamApiMigration findMigrationForReturn(PsiStatement statement, TerminalBlock tb, boolean replaceTrivialForEach) { + boolean shouldWarn = replaceTrivialForEach || tb.hasOperations(); + PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement(); + PsiExpression value = returnStatement.getReturnValue(); + PsiReturnStatement nextReturnStatement = getNextReturnStatement(statement); + if (nextReturnStatement != null && + (ExpressionUtils.isLiteral(value, Boolean.TRUE) || ExpressionUtils.isLiteral(value, Boolean.FALSE))) { + boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue(); + String methodName; + if (foundResult) { + methodName = "anyMatch"; } else { - throw new IllegalStateException("Unexpected statement type: " + statement); + methodName = "noneMatch"; + FilterOp lastFilter = tb.getLastOperation(FilterOp.class); + if (lastFilter != null && (lastFilter.isNegated() ^ BoolUtils.isNegation(lastFilter.getExpression()))) { + methodName = "allMatch"; + } } + if (nextReturnStatement.getParent() == statement.getParent() || + ExpressionUtils.isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) { + return new MatchMigration(shouldWarn, methodName); + } + } + if (!VariableAccessUtils.variableIsUsed(tb.getVariable(), value)) { + if (!replaceTrivialForEach && !tb.hasOperations() || + (tb.getLastOperation() instanceof FilterOp && tb.operations().count() == 2)) { + return null; + } + return new MatchMigration(shouldWarn, "anyMatch"); + } + if (nextReturnStatement != null && ExpressionUtils.isSimpleExpression(nextReturnStatement.getReturnValue()) + && (!(tb.getVariable().getType() instanceof PsiPrimitiveType) || ExpressionUtils.isReferenceTo(value, tb.getVariable()))) { + return new FindFirstMigration(shouldWarn); + } + return null; + } + + @NotNull + private static TextRange getRange(boolean shouldWarn, PsiStatement statement, boolean isOnTheFly) { + boolean wholeStatement = + isOnTheFly && (!shouldWarn || InspectionProjectProfileManager.isInformationLevel(SHORT_NAME, statement)); + 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 if (statement instanceof PsiWhileStatement) { + PsiJavaToken rParenth = ((PsiWhileStatement)statement).getRParenth(); + if (wholeStatement && rParenth != null) { + return new TextRange(statement.getTextOffset(), rParenth.getTextOffset() + 1); + } + return statement.getFirstChild().getTextRange(); + } + else { + throw new IllegalStateException("Unexpected statement type: " + statement); } } @@ -649,7 +675,9 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo PsiReferenceExpression arrayReference = tryCast(arrayAccess.getArrayExpression(), PsiReferenceExpression.class); if (arrayReference == null) return null; PsiLocalVariable arrayVariable = tryCast(arrayReference.resolve(), PsiLocalVariable.class); - if (arrayVariable == null || ControlFlowUtils.getInitializerUsageStatus(arrayVariable, tb.getMainLoop()) == UNKNOWN) return null; + if (arrayVariable == null || ControlFlowUtils.getInitializerUsageStatus(arrayVariable, tb.getStreamSourceStatement()) == UNKNOWN) { + return null; + } PsiNewExpression initializer = tryCast(arrayVariable.getInitializer(), PsiNewExpression.class); if (initializer == null) return null; PsiArrayType arrayType = tryCast(initializer.getType(), PsiArrayType.class); @@ -843,7 +871,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } boolean breaksMe(PsiBreakStatement statement) { - return statement.findExitedStatement() == mySource.getLoop(); + return statement.findExitedStatement() == mySource.getMainStatement(); } } @@ -915,15 +943,15 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } abstract static class StreamSource extends Operation { - private final PsiLoopStatement myLoop; + private final PsiStatement myMainStatement; - protected StreamSource(PsiLoopStatement loop, PsiVariable variable, PsiExpression expression) { + protected StreamSource(PsiStatement mainStatement, PsiVariable variable, PsiExpression expression) { super(expression, variable); - myLoop = loop; + myMainStatement = mainStatement; } - PsiLoopStatement getLoop() { - return myLoop; + PsiStatement getMainStatement() { + return myMainStatement; } @Contract("null -> null") @@ -1086,7 +1114,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo final PsiExpression myBound; final boolean myIncluding; - private CountingLoopSource(PsiLoopStatement loop, + private CountingLoopSource(PsiStatement loop, PsiVariable counter, PsiExpression initializer, PsiExpression bound, @@ -1109,11 +1137,11 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } CountingLoopSource withBound(PsiExpression bound) { - return new CountingLoopSource(getLoop(), getVariable(), getExpression(), bound, myIncluding); + return new CountingLoopSource(getMainStatement(), getVariable(), getExpression(), bound, myIncluding); } CountingLoopSource withInitializer(PsiExpression expression) { - return new CountingLoopSource(getLoop(), getVariable(), expression, myBound, myIncluding); + return new CountingLoopSource(getMainStatement(), getVariable(), expression, myBound, myIncluding); } @Override @@ -1139,4 +1167,5 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo return new CountingLoopSource(forStatement, loop.getCounter(), loop.getInitializer(), loop.getBound(), loop.isIncluding()); } } + } diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/SumMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/SumMigration.java index c33821ad90ec..51ddfb4c19cb 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/SumMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/SumMigration.java @@ -32,7 +32,7 @@ class SumMigration extends BaseStreamApiMigration { SumMigration(boolean shouldWarn) {super(shouldWarn, "sum()");} @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class); if (assignment == null) return null; PsiVariable var = StreamApiMigrationInspection.extractSumAccumulator(assignment); @@ -51,6 +51,6 @@ class SumMigration extends BaseStreamApiMigration { "(" + type.getCanonicalText() + ")" + ParenthesesUtils.getText(addend, ParenthesesUtils.MULTIPLICATIVE_PRECEDENCE), addend); } String stream = tb.add(new MapOp(addend, tb.getVariable(), type)).generate()+".sum()"; - return replaceWithOperation(tb.getMainLoop(), var, stream, type, SUM_OPERATION); + return replaceWithOperation(tb.getStreamSourceStatement(), var, stream, type, SUM_OPERATION); } } diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/TerminalBlock.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/TerminalBlock.java index b40f37f71032..1a142d7a552d 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/TerminalBlock.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/TerminalBlock.java @@ -100,6 +100,10 @@ class TerminalBlock { return null; } + int getOperationCount() { + return myOperations.length; + } + /** * @return PsiMethodCallExpression if this TerminalBlock contains single method call, null otherwise */ @@ -191,12 +195,14 @@ class TerminalBlock { } if(myStatements.length >= 1) { PsiStatement first = myStatements[0]; - if(PsiUtil.isLanguageLevel9OrHigher(first.getContainingFile()) && first instanceof PsiIfStatement) { + if(PsiUtil.isLanguageLevel9OrHigher(myVariable.getContainingFile()) && first instanceof PsiIfStatement) { PsiIfStatement ifStatement = (PsiIfStatement)first; PsiExpression condition = ifStatement.getCondition(); if(ifStatement.getElseBranch() == null && condition != null) { PsiStatement thenStatement = ControlFlowUtils.stripBraces(ifStatement.getThenBranch()); - if(ControlFlowUtils.statementBreaksLoop(thenStatement, getMainLoop())) { + PsiStatement sourceStatement = getStreamSourceStatement(); + if( sourceStatement instanceof PsiLoopStatement && ControlFlowUtils.statementBreaksLoop(thenStatement, + (PsiLoopStatement)sourceStatement)) { TakeWhileOp op = new TakeWhileOp(condition, myVariable, true); PsiStatement[] leftOver = Arrays.copyOfRange(myStatements, 1, myStatements.length); return new TerminalBlock(this, op, myVariable, leftOver); @@ -252,7 +258,9 @@ class TerminalBlock { statements = Arrays.copyOfRange(myStatements, 0, count); tb = new TerminalBlock(myOperations, myVariable, Arrays.copyOfRange(myStatements, count, myStatements.length)).extractFilter(); } - if (tb == null || !ControlFlowUtils.statementBreaksLoop(tb.getSingleStatement(), getMainLoop())) return this; + PsiStatement sourceStatement = getStreamSourceStatement(); + if (tb == null || (sourceStatement instanceof PsiLoopStatement && !ControlFlowUtils.statementBreaksLoop(tb.getSingleStatement(), + (PsiLoopStatement)sourceStatement))) return this; FilterOp filter = tb.getLastOperation(FilterOp.class); if (filter == null) return this; PsiBinaryExpression binOp = tryCast(PsiUtil.skipParenthesizedExprDown(filter.getExpression()), PsiBinaryExpression.class); @@ -323,7 +331,7 @@ class TerminalBlock { PsiExpressionList argumentList = initializer.getArgumentList(); if (argumentList == null || argumentList.getExpressions().length != 0 || - ControlFlowUtils.getInitializerUsageStatus(var, getMainLoop()) == ControlFlowUtils.InitializerUsageStatus.UNKNOWN) { + ControlFlowUtils.getInitializerUsageStatus(var, getStreamSourceStatement()) == ControlFlowUtils.InitializerUsageStatus.UNKNOWN) { return null; } return var; @@ -428,8 +436,11 @@ class TerminalBlock { return StreamEx.ofReversed(myOperations); } - PsiLoopStatement getMainLoop() { - return ((StreamSource)myOperations[0]).getLoop(); + /** + * @return generally {@link PsiLoopStatement} - main loop + */ + PsiStatement getStreamSourceStatement() { + return ((StreamSource)myOperations[0]).getMainStatement(); } /** @@ -494,7 +505,17 @@ class TerminalBlock { @NotNull static TerminalBlock from(StreamSource source, @NotNull PsiStatement body) { - return new TerminalBlock(null, source, source.myVariable, body).extractOperations().tryPeelLimit(false).tryExtractDistinct(); + return fromStatements(source, body); + } + + @NotNull + static TerminalBlock fromStatements(StreamSource source, @NotNull PsiStatement... statements) { + return new TerminalBlock(null, source, source.myVariable, statements).extractOperations().tryPeelLimit(false).tryExtractDistinct(); + } + + @NotNull + static TerminalBlock from(StreamSource source, @NotNull PsiCodeBlock block) { + return fromStatements(source, block.getStatements()); } boolean dependsOn(PsiExpression qualifier) { diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ToArrayMigration.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ToArrayMigration.java index 5f590ae2b352..f57f939c3250 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/ToArrayMigration.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/ToArrayMigration.java @@ -35,7 +35,7 @@ public class ToArrayMigration extends BaseStreamApiMigration { } @Override - PsiElement migrate(@NotNull Project project, @NotNull PsiStatement body, @NotNull TerminalBlock tb) { + PsiElement migrate(@NotNull Project project, @NotNull PsiElement body, @NotNull TerminalBlock tb) { PsiLocalVariable arrayVariable = StreamApiMigrationInspection.extractArray(tb); if(arrayVariable == null) return null; PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class); @@ -50,7 +50,7 @@ public class ToArrayMigration extends BaseStreamApiMigration { if(loop == null) return null; PsiArrayType arrayType = tryCast(initializer.getType(), PsiArrayType.class); if(arrayType == null) return null; - InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(arrayVariable, tb.getMainLoop()); + InitializerUsageStatus status = ControlFlowUtils.getInitializerUsageStatus(arrayVariable, tb.getStreamSourceStatement()); if(status == ControlFlowUtils.InitializerUsageStatus.UNKNOWN) return null; PsiType componentType = arrayType.getComponentType(); String supplier; @@ -61,6 +61,6 @@ public class ToArrayMigration extends BaseStreamApiMigration { } 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); + return replaceInitializer(tb.getStreamSourceStatement(), arrayVariable, initializer, replacementText, status); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEach.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEach.java new file mode 100644 index 000000000000..9837c16d1158 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEach.java @@ -0,0 +1,10 @@ +// "Extract intermediate operations" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + other.stream().filter(s -> s.length() > 2).forEach(System.out::println); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEachComments.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEachComments.java new file mode 100644 index 000000000000..e671a43190bd --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterBlockForEachToForEachComments.java @@ -0,0 +1,12 @@ +// "Extract intermediate operations" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + // c1 +//c2 + other.stream().filter(s -> s.length() > 2).forEach(System.out::println); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterCollectionForEachBlock.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterCollectionForEachBlock.java new file mode 100644 index 000000000000..49178ce0233b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterCollectionForEachBlock.java @@ -0,0 +1,10 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test(List strs) { + List other = strs.stream().filter(s -> s.length() > 2).collect(Collectors.toList()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlock.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlock.java new file mode 100644 index 000000000000..9b6ab2666df9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlock.java @@ -0,0 +1,12 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test() { + List strs; + List other = new ArrayList<>(); + strs = other.stream().collect(Collectors.toList()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilter.java new file mode 100644 index 000000000000..0e6bbb5d572f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilter.java @@ -0,0 +1,12 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test() { + List strs; + List other = new ArrayList<>(); + strs = other.stream().filter(s -> s.length() > 2).collect(Collectors.toList()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToArray.java new file mode 100644 index 000000000000..f917312e9d15 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToArray.java @@ -0,0 +1,10 @@ +// "Replace with toArray" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + String[] arr = other.stream().filter(s -> s.length() > 2).sorted(String.CASE_INSENSITIVE_ORDER).toArray(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToList.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToList.java new file mode 100644 index 000000000000..21135a76fa58 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockFilterSortToList.java @@ -0,0 +1,12 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test() { + List strs; + List other = new ArrayList<>(); + strs = other.stream().filter(s -> s.length() > 2).sorted(String::compareTo).collect(Collectors.toList()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockForEachOrdered.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockForEachOrdered.java new file mode 100644 index 000000000000..cb25201875b4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockForEachOrdered.java @@ -0,0 +1,10 @@ +// "Extract intermediate operations" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + other.stream().filter(s -> s.length() > 2).forEachOrdered(System.out::println); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockInlineInitializer.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockInlineInitializer.java new file mode 100644 index 000000000000..8166ed554f32 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachBlockInlineInitializer.java @@ -0,0 +1,10 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test(List other) { + List strs = other.stream().collect(Collectors.toList()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambda.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambda.java new file mode 100644 index 000000000000..9b6ab2666df9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambda.java @@ -0,0 +1,12 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test() { + List strs; + List other = new ArrayList<>(); + strs = other.stream().collect(Collectors.toList()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaCount.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaCount.java new file mode 100644 index 000000000000..183a086dadb6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaCount.java @@ -0,0 +1,10 @@ +// "Replace with count()" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + int count = (int) strs.stream().count(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaJoining.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaJoining.java new file mode 100644 index 000000000000..fb7bafb32432 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaJoining.java @@ -0,0 +1,11 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + String sb = strs.stream().collect(Collectors.joining()); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToArray.java new file mode 100644 index 000000000000..4b0ef9561e0c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToArray.java @@ -0,0 +1,9 @@ +// "Replace with toArray" "true" + +import java.util.*; + +public class Main { + private void test(List strs) { + String[] arr = strs.toArray(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToMap.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToMap.java new file mode 100644 index 000000000000..96d6090a83c2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/afterForEachExpressionLambdaToMap.java @@ -0,0 +1,11 @@ +// "Replace with collect" "true" + +import java.util.*; +import java.util.stream.Collectors; + +public class Main { + private void test() { + List other = new ArrayList<>(); + HashMap map = other.stream().collect(Collectors.toMap(String::length, s -> s, (a, b) -> a, HashMap::new)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEach.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEach.java new file mode 100644 index 000000000000..1b61e425e2a9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEach.java @@ -0,0 +1,12 @@ +// "Extract intermediate operations" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + other.stream().forEach(s -> { + if(s.length() > 2) System.out.println(s); + }); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEachComments.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEachComments.java new file mode 100644 index 000000000000..95edff6e61ec --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeBlockForEachToForEachComments.java @@ -0,0 +1,12 @@ +// "Extract intermediate operations" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + other.stream().forEach(s -> { // c1 + if(s.length() > 2) System.out.println(s); //c2 + }); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeCollectionForEachBlock.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeCollectionForEachBlock.java new file mode 100644 index 000000000000..cc055f421440 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeCollectionForEachBlock.java @@ -0,0 +1,14 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test(List strs) { + List other = new ArrayList<>(); + strs.stream().forEach(s -> { + if(s.length() > 2) { + other.add(s); + } + }); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlock.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlock.java new file mode 100644 index 000000000000..fef5ce81635b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlock.java @@ -0,0 +1,11 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + List other = new ArrayList<>(); + other.stream().forEach(s -> {strs.add(s)}); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilter.java new file mode 100644 index 000000000000..172148756f47 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilter.java @@ -0,0 +1,13 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + List other = new ArrayList<>(); + other.stream().forEach(s -> { + if(s.length() > 2) strs.add(s); + }); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToArray.java new file mode 100644 index 000000000000..6ffe22fa0269 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToArray.java @@ -0,0 +1,17 @@ +// "Replace with toArray" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + List other = new ArrayList<>(); + other.stream().forEach(s -> { + if(s.length() > 2) { + strs.add(s); + } + }); + String[] arr = strs.toArray(); + Arrays.sort(arr, String.CASE_INSENSITIVE_ORDER); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToList.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToList.java new file mode 100644 index 000000000000..c87f440e35ab --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockFilterSortToList.java @@ -0,0 +1,16 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + List other = new ArrayList<>(); + other.stream().forEach(s -> { + if(s.length() > 2) { + strs.add(s); + } + }); + strs.sort(String::compareTo); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockForEachOrdered.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockForEachOrdered.java new file mode 100644 index 000000000000..25fc5287df49 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockForEachOrdered.java @@ -0,0 +1,10 @@ +// "Extract intermediate operations" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + other.stream().forEachOrdered(s -> {if(s.length() > 2) System.out.println(s);}); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockInlineInitializer.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockInlineInitializer.java new file mode 100644 index 000000000000..4cc4647c973c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachBlockInlineInitializer.java @@ -0,0 +1,10 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test(List other) { + List strs = new ArrayList<>(); + other.stream().forEach(s -> {strs.add(s)}); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambda.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambda.java new file mode 100644 index 000000000000..c8f3e1e9e23f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambda.java @@ -0,0 +1,11 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + List other = new ArrayList<>(); + other.stream().forEach(s -> strs.add(s)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaCount.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaCount.java new file mode 100644 index 000000000000..fd7e758c9adc --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaCount.java @@ -0,0 +1,11 @@ +// "Replace with count()" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + int count = 0; + strs.stream().forEach(x -> count++) + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaJoining.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaJoining.java new file mode 100644 index 000000000000..716186d2b26d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaJoining.java @@ -0,0 +1,11 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test() { + List strs = new ArrayList<>(); + StringBuilder sb = new StringBuilder(); + strs.stream().forEach(x -> sb.append(x)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToArray.java new file mode 100644 index 000000000000..42949d7c66a6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToArray.java @@ -0,0 +1,11 @@ +// "Replace with toArray" "true" + +import java.util.*; + +public class Main { + private void test(List strs) { + List tmp = new ArrayList<>(); + strs.stream().forEach(x -> tmp.add(x)); + String[] arr = tmp.toArray(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToMap.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToMap.java new file mode 100644 index 000000000000..65e43533a622 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach/beforeForEachExpressionLambdaToMap.java @@ -0,0 +1,11 @@ +// "Replace with collect" "true" + +import java.util.*; + +public class Main { + private void test() { + List other = new ArrayList<>(); + HashMap map = new HashMap(); + other.stream().forEach(s -> map.putIfAbsent(s.length(), s)); + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SimplifyForEachInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SimplifyForEachInspectionTest.java new file mode 100644 index 000000000000..22e001639f73 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SimplifyForEachInspectionTest.java @@ -0,0 +1,45 @@ +/* + * 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.java.codeInsight.daemon.quickFix; + + +import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.streamMigration.SimplifyForEachInspection; +import com.intellij.pom.java.LanguageLevel; +import org.jetbrains.annotations.NotNull; + +public class SimplifyForEachInspectionTest extends LightQuickFixParameterizedTestCase { + @NotNull + @Override + protected LocalInspectionTool[] configureLocalInspectionTools() { + return new LocalInspectionTool[]{new SimplifyForEachInspection()}; + } + + @Override + protected LanguageLevel getDefaultLanguageLevel() { + return LanguageLevel.JDK_1_8; + } + + public void test() { doAllTests(); } + + + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/simplifyForEach"; + } +} diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ControlFlowUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ControlFlowUtils.java index 32dc6d6f2464..82dcc2646da8 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ControlFlowUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ControlFlowUtils.java @@ -765,7 +765,7 @@ public class ControlFlowUtils { */ private static boolean isVariableReferencedBeforeLoopEntry(final ControlFlow flow, final int start, - final PsiLoopStatement loop, + final PsiStatement loop, final PsiVariable variable) { final int loopStart = flow.getStartOffset(loop); final int loopEnd = flow.getEndOffset(loop); @@ -812,7 +812,7 @@ public class ControlFlowUtils { * @return initializer usage status for variable */ @NotNull - public static InitializerUsageStatus getInitializerUsageStatus(PsiVariable var, PsiLoopStatement loop) { + public static InitializerUsageStatus getInitializerUsageStatus(PsiVariable var, PsiStatement loop) { if(!(var instanceof PsiLocalVariable) || var.getInitializer() == null) return UNKNOWN; if(isDeclarationJustBefore(var, loop)) return DECLARED_JUST_BEFORE; // Check that variable is declared in the same method or the same lambda expression diff --git a/resources-en/src/inspectionDescriptions/SimplifyForEach.html b/resources-en/src/inspectionDescriptions/SimplifyForEach.html new file mode 100644 index 000000000000..d5e8ea343b2e --- /dev/null +++ b/resources-en/src/inspectionDescriptions/SimplifyForEach.html @@ -0,0 +1,9 @@ + + +

+ This inspection reports forEach which can be replaced with more concise method or intermediate steps can be extracted. +

+Stream API is not available under Java 1.7 or earlier JVMs +New in 2017.3 + + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 536a6366a913..5460c3d78cb7 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -805,6 +805,9 @@ +