diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java index 68da73f5f495..547e092e4729 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java @@ -35,6 +35,7 @@ import com.intellij.psi.util.*; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.IntArrayList; +import com.siyeh.ig.psiutils.ParenthesesUtils; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -113,19 +114,20 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } else return; } try { - final ControlFlow controlFlow = ControlFlowFactory.getInstance(holder.getProject()) - .getControlFlow(body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance()); - int startOffset = controlFlow.getStartOffset(body); - int endOffset = controlFlow.getEndOffset(body); - final Collection exitPoints = ControlFlowUtil - .findExitPointsAndStatements(controlFlow, startOffset, endOffset, new IntArrayList(), PsiContinueStatement.class, - PsiBreakStatement.class, PsiReturnStatement.class, PsiThrowStatement.class); - if (exitPoints.isEmpty()) { + if (ExceptionUtil.getThrownCheckedExceptions(new PsiElement[]{body}).isEmpty()) { + TerminalBlock tb = TerminalBlock.from(statement.getIterationParameter(), body); + List operations = tb.extractOperations(); - if (ExceptionUtil.getThrownCheckedExceptions(new PsiElement[]{body}).isEmpty()) { - TerminalBlock tb = TerminalBlock.from(statement.getIterationParameter(), body); - List operations = tb.extractOperations(); + final ControlFlow controlFlow = ControlFlowFactory.getInstance(holder.getProject()) + .getControlFlow(body, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance()); + final Collection exitPoints = ControlFlowUtil + .findExitPointsAndStatements(controlFlow, tb.getStartOffset(controlFlow), tb.getEndOffset(controlFlow), + new IntArrayList(), PsiContinueStatement.class, + PsiBreakStatement.class, PsiReturnStatement.class, PsiThrowStatement.class); + if (exitPoints.isEmpty()) { + int startOffset = controlFlow.getStartOffset(body); + int endOffset = controlFlow.getEndOffset(body); final List nonFinalVariables = ControlFlowUtil.getUsedVariables(controlFlow, startOffset, endOffset) .stream().filter(variable -> !HighlightControlFlowUtil.isEffectivelyFinal(variable, body, null)) .collect(Collectors.toList()); @@ -859,14 +861,51 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } static class FilterOp extends Operation { + private final boolean myNegated; - FilterOp(PsiExpression condition, PsiVariable variable) { + FilterOp(PsiExpression condition, PsiVariable variable, boolean negated) { super(condition, variable); + myNegated = negated; + } + + @Contract("null -> false") + private static boolean isFloating(PsiExpression operand) { + if(operand == null) return false; + PsiType type = operand.getType(); + return type instanceof PsiPrimitiveType && (type.equalsToText("double") || type.equalsToText("float")); + } + + private static PsiExpression negate(PsiExpression expression, PsiElementFactory factory) { + if (expression instanceof PsiPrefixExpression && + JavaTokenType.EXCL.equals(((PsiPrefixExpression)expression).getOperationTokenType())) { + return PsiUtil.skipParenthesizedExprDown(((PsiPrefixExpression)expression).getOperand()); + } + if (expression instanceof PsiBinaryExpression) { + PsiBinaryExpression binOp = (PsiBinaryExpression)expression; + if(JavaTokenType.EQEQ.equals(binOp.getOperationTokenType()) || JavaTokenType.NE.equals(binOp.getOperationTokenType())) { + PsiExpression left = binOp.getLOperand(); + PsiExpression right = binOp.getROperand(); + // for float/double types == and != are not strictly opposite (NaNs are treated specially) + if (right != null && !isFloating(left) && !isFloating(right)) { + return factory.createExpressionFromText( + left.getText() + (JavaTokenType.EQEQ.equals(binOp.getOperationTokenType()) ? "!=" : "==") + right.getText(), expression); + } + } + } + String expString; + if (ParenthesesUtils.getPrecedence(expression) > ParenthesesUtils.PREFIX_PRECEDENCE) { + expString = "!(" + expression.getText() + ')'; + } + else { + expString = '!' + expression.getText(); + } + return factory.createExpressionFromText(expString, expression); } @Override public String createReplacement(PsiElementFactory factory) { - return ".filter(" + compoundLambdaOrMethodReference(myVariable, myExpression, + PsiExpression expression = myNegated ? negate(myExpression, factory) : myExpression; + return ".filter(" + compoundLambdaOrMethodReference(myVariable, expression, "java.util.function.Predicate", new PsiType[] {myVariable.getType()}) + ")"; } @@ -933,6 +972,14 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } } + int getStartOffset(ControlFlow cf) { + return cf.getStartOffset(myStatements[0]); + } + + int getEndOffset(ControlFlow cf) { + return cf.getEndOffset(myStatements[myStatements.length-1]); + } + PsiStatement getSingleStatement() { return myStatements.length == 1 ? myStatements[0] : null; } @@ -967,10 +1014,9 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo // extract filter if(getSingleStatement() instanceof PsiIfStatement) { PsiIfStatement ifStatement = (PsiIfStatement)getSingleStatement(); - if(ifStatement.getElseBranch() != null || ifStatement.getCondition() == null) - return null; + if(ifStatement.getElseBranch() != null || ifStatement.getCondition() == null) return null; replaceWith(ifStatement.getThenBranch()); - return new FilterOp(ifStatement.getCondition(), myVariable); + return new FilterOp(ifStatement.getCondition(), myVariable, false); } // extract flatMap if(getSingleStatement() instanceof PsiForeachStatement) { @@ -1001,9 +1047,9 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } } } - // extract map if(myStatements.length > 1) { PsiStatement first = myStatements[0]; + // extract map if(first instanceof PsiDeclarationStatement) { PsiDeclarationStatement decl = (PsiDeclarationStatement)first; PsiElement[] elements = decl.getDeclaredElements(); @@ -1028,6 +1074,21 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } } } + // extract filter with negation + if(first instanceof PsiIfStatement) { + PsiIfStatement ifStatement = (PsiIfStatement)first; + if(ifStatement.getElseBranch() != null || ifStatement.getCondition() == null) return null; + PsiStatement branch = ifStatement.getThenBranch(); + if(branch instanceof PsiBlockStatement) { + PsiStatement[] statements = ((PsiBlockStatement)branch).getCodeBlock().getStatements(); + if(statements.length == 1) + branch = statements[0]; + } + if(!(branch instanceof PsiContinueStatement)) return null; + myStatements = Arrays.copyOfRange(myStatements, 1, myStatements.length); + flatten(); + return new FilterOp(ifStatement.getCondition(), myVariable, true); + } } return null; } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterContinue.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterContinue.java new file mode 100644 index 000000000000..98297c763381 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterContinue.java @@ -0,0 +1,15 @@ +// "Replace with sum()" "true" + +import java.util.List; +import java.util.Objects; + +public class Main { + interface Person { + int getAge(); + } + + public long test(List collection) { + long i = collection.stream().filter(Objects::nonNull).mapToLong(Person::getAge).sum(); + return i; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterContinueNegate.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterContinueNegate.java new file mode 100644 index 000000000000..02e8894ff640 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterContinueNegate.java @@ -0,0 +1,14 @@ +// "Replace with sum()" "true" + +import java.util.List; + +public class Main { + interface Person { + int getAge(); + } + + public long test(List collection, Person include) { + long i = collection.stream().filter(include::equals).mapToLong(Person::getAge).sum(); + return i; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterContinueOr.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterContinueOr.java new file mode 100644 index 000000000000..99b377a621e6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterContinueOr.java @@ -0,0 +1,14 @@ +// "Replace with sum()" "true" + +import java.util.List; + +public class Main { + interface Person { + int getAge(); + } + + public long test(List collection) { + long i = collection.stream().filter(person -> !(person == null || person.getAge() < 10)).mapToLong(Person::getAge).sum(); + return i; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeContinue.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeContinue.java new file mode 100644 index 000000000000..af3f93107da4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeContinue.java @@ -0,0 +1,20 @@ +// "Replace with sum()" "true" + +import java.util.List; + +public class Main { + interface Person { + int getAge(); + } + + public long test(List collection) { + long i = 0; + for(Person person : collection) { + if(person == null) { + continue; + } + i = i + person.getAge(); + } + return i; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeContinueNegate.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeContinueNegate.java new file mode 100644 index 000000000000..f3b590f5323e --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeContinueNegate.java @@ -0,0 +1,19 @@ +// "Replace with sum()" "true" + +import java.util.List; + +public class Main { + interface Person { + int getAge(); + } + + public long test(List collection, Person include) { + long i = 0; + for(Person person : collection) { + if(!include.equals(person)) + continue; + i = i + person.getAge(); + } + return i; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeContinueOr.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeContinueOr.java new file mode 100644 index 000000000000..9d5aff17d602 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeContinueOr.java @@ -0,0 +1,19 @@ +// "Replace with sum()" "true" + +import java.util.List; + +public class Main { + interface Person { + int getAge(); + } + + public long test(List collection) { + long i = 0; + for(Person person : collection) { + if(person == null || person.getAge() < 10) + continue; + i = i + person.getAge(); + } + return i; + } +} \ No newline at end of file