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 cd7b96bdc0bf..da494ef99498 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java @@ -184,34 +184,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } } else { if(nonFinalVariables.isEmpty() && tb.getSingleStatement() instanceof PsiReturnStatement) { - PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement(); - PsiExpression value = returnStatement.getReturnValue(); - PsiReturnStatement nextReturnStatement = getNextReturnStatement(statement); - if(nextReturnStatement != null) { - if(isLiteral(value, Boolean.TRUE) || isLiteral(value, Boolean.FALSE)) { - boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue(); - if(isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) { - String methodName; - if (foundResult) { - methodName = "anyMatch"; - } - else { - methodName = "noneMatch"; - if(!operations.isEmpty()) { - Operation lastOp = operations.get(operations.size() - 1); - if(lastOp instanceof FilterOp && BoolUtils.isNegation(lastOp.getExpression())) { - methodName = "allMatch"; - } - } - } - registerProblem(holder, isOnTheFly, statement, methodName, new ReplaceWithMatchFix(methodName)); - return; - } - } - if(ExpressionUtils.isSimpleExpression(nextReturnStatement.getReturnValue())) { - registerProblem(holder, isOnTheFly, statement, "findFirst", new ReplaceWithFindFirstFix()); - } - } + handleSingleReturn(statement, tb, operations); } } } @@ -222,6 +195,40 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } } + void handleSingleReturn(PsiForeachStatement statement, + TerminalBlock tb, + List operations) { + PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement(); + PsiExpression value = returnStatement.getReturnValue(); + PsiReturnStatement nextReturnStatement = getNextReturnStatement(statement); + if(nextReturnStatement != null && (isLiteral(value, Boolean.TRUE) || isLiteral(value, Boolean.FALSE))) { + boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue(); + if(isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) { + String methodName; + if (foundResult) { + methodName = "anyMatch"; + } + else { + methodName = "noneMatch"; + if(!operations.isEmpty()) { + Operation lastOp = operations.get(operations.size() - 1); + if(lastOp instanceof FilterOp && BoolUtils.isNegation(lastOp.getExpression())) { + methodName = "allMatch"; + } + } + } + registerProblem(holder, isOnTheFly, statement, methodName, new ReplaceWithMatchFix(methodName)); + return; + } + } + if (!isVariableReferenced(tb.getVariable(), value)) { + registerProblem(holder, isOnTheFly, statement, "anyMatch", new ReplaceWithMatchFix("anyMatch")); + } + if(nextReturnStatement != null && ExpressionUtils.isSimpleExpression(nextReturnStatement.getReturnValue())) { + registerProblem(holder, isOnTheFly, statement, "findFirst", new ReplaceWithFindFirstFix()); + } + } + private boolean isRawSubstitution(PsiType iteratedValueType, PsiClass collectionClass) { return iteratedValueType instanceof PsiClassType && PsiUtil .isRawSubstitutor(collectionClass, TypeConversionUtil.getSuperClassSubstitutor(collectionClass, (PsiClassType)iteratedValueType)); @@ -229,6 +236,12 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo }; } + @Contract("_, null -> false") + private static boolean isVariableReferenced(PsiVariable variable, PsiExpression value) { + return !PsiTreeUtil + .processElements(value, e -> !(e instanceof PsiReferenceExpression) || ((PsiReferenceExpression)e).resolve() != variable); + } + @Nullable static PsiReturnStatement getNextReturnStatement(PsiStatement statement) { PsiElement nextStatement = PsiTreeUtil.skipSiblingsForward(statement, PsiWhiteSpace.class, PsiComment.class); @@ -859,24 +872,47 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo @NotNull List intermediateOps) { PsiReturnStatement returnStatement = (PsiReturnStatement)tb.getSingleStatement(); PsiExpression value = returnStatement.getReturnValue(); - if(!isLiteral(value, Boolean.TRUE) && !isLiteral(value, Boolean.FALSE)) return; - boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue(); - PsiReturnStatement nextReturnStatement = getNextReturnStatement(foreachStatement); - if (nextReturnStatement == null || !isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) return; - String methodName = foundResult ? "anyMatch" : "noneMatch"; - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); - String streamText = generateStream(iteratedValue, intermediateOps).toString(); - PsiExpression stream = - elementFactory.createExpressionFromText(streamText, foreachStatement); - if(!(stream instanceof PsiMethodCallExpression)) return; + PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); + if (isLiteral(value, Boolean.TRUE) || isLiteral(value, Boolean.FALSE)) { + boolean foundResult = (boolean)((PsiLiteralExpression)value).getValue(); + PsiReturnStatement nextReturnStatement = getNextReturnStatement(foreachStatement); + if (nextReturnStatement != null && isLiteral(nextReturnStatement.getReturnValue(), !foundResult)) { + String methodName = foundResult ? "anyMatch" : "noneMatch"; + String streamText = generateStream(iteratedValue, intermediateOps).toString(); + streamText = addTerminalOperation(streamText, methodName, foreachStatement, tb); + boolean siblings = nextReturnStatement.getParent() == foreachStatement.getParent(); + PsiElement result = + foreachStatement.replace(elementFactory.createStatementFromText("return " + streamText + ";", foreachStatement)); + if (siblings) { + nextReturnStatement.delete(); + } + simplifyAndFormat(project, result); + return; + } + } + if(!isVariableReferenced(tb.getVariable(), value)) { + String streamText = generateStream(iteratedValue, intermediateOps).toString(); + streamText = addTerminalOperation(streamText, "anyMatch", foreachStatement, tb); + String replacement = "if(" + streamText + "){" + returnStatement.getText() + "}"; + PsiElement result = foreachStatement.replace(elementFactory.createStatementFromText(replacement, foreachStatement)); + simplifyAndFormat(project, result); + } + } + + private static String addTerminalOperation(String origStream, String methodName, @NotNull PsiElement contextElement, + @NotNull TerminalBlock tb) { + PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(contextElement.getProject()); + PsiExpression stream = elementFactory.createExpressionFromText(origStream, contextElement); + LOG.assertTrue(stream instanceof PsiMethodCallExpression); PsiElement nameElement = ((PsiMethodCallExpression)stream).getMethodExpression().getReferenceNameElement(); - if(nameElement != null && nameElement.getText().equals("filter")) { - if(!foundResult) { + if (nameElement != null && nameElement.getText().equals("filter")) { + if (methodName.equals("noneMatch")) { + // Try to reduce noneMatch(x -> !(condition)) to allMatch(x -> condition) PsiExpression[] expressions = ((PsiMethodCallExpression)stream).getArgumentList().getExpressions(); - if(expressions.length == 1 && expressions[0] instanceof PsiLambdaExpression) { + if (expressions.length == 1 && expressions[0] instanceof PsiLambdaExpression) { PsiLambdaExpression lambda = (PsiLambdaExpression)expressions[0]; PsiElement lambdaBody = lambda.getBody(); - if(lambdaBody instanceof PsiExpression && BoolUtils.isNegation((PsiExpression)lambdaBody)) { + if (lambdaBody instanceof PsiExpression && BoolUtils.isNegation((PsiExpression)lambdaBody)) { PsiExpression negated = BoolUtils.getNegated((PsiExpression)lambdaBody); LOG.assertTrue(negated != null, lambdaBody.getText()); lambdaBody.replace(negated); @@ -885,16 +921,9 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } } nameElement.replace(elementFactory.createIdentifier(methodName)); - streamText = stream.getText(); - } else { - streamText += "."+methodName+"("+tb.getVariable().getName()+" -> true)"; + return stream.getText(); } - boolean siblings = nextReturnStatement.getParent() == foreachStatement.getParent(); - PsiElement result = foreachStatement.replace(elementFactory.createStatementFromText("return " + streamText + ";", foreachStatement)); - if(siblings) { - nextReturnStatement.delete(); - } - simplifyAndFormat(project, result); + return origStream + "." + methodName + "(" + tb.getVariable().getName() + " -> true)"; } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterAnyMatchCustomReturn.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterAnyMatchCustomReturn.java new file mode 100644 index 000000000000..67fd55a5209e --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterAnyMatchCustomReturn.java @@ -0,0 +1,16 @@ +// "Replace with anyMatch()" "true" + +import java.util.Arrays; +import java.util.Objects; + +public class Main { + String contains(String[][] haystack, String needle) { + if(haystack != null) { + if (Arrays.stream(haystack).filter(Objects::nonNull).flatMap(Arrays::stream).anyMatch(needle::equals)) { + return "yes" + needle.length(); + } + System.out.println("oops"); + } + return "no"; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterAnyMatchEnclosingIfInterrupted.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterAnyMatchEnclosingIfInterrupted.java new file mode 100644 index 000000000000..8fbc984a1167 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterAnyMatchEnclosingIfInterrupted.java @@ -0,0 +1,16 @@ +// "Replace with anyMatch()" "true" + +import java.util.Arrays; +import java.util.Objects; + +public class Main { + boolean contains(String[][] haystack, String needle) { + if(haystack != null) { + if (Arrays.stream(haystack).filter(Objects::nonNull).flatMap(Arrays::stream).anyMatch(needle::equals)) { + return true; + } + System.out.println("Oops"); + } + return false; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterAnyMatchVoid.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterAnyMatchVoid.java new file mode 100644 index 000000000000..70fb09d8d62d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterAnyMatchVoid.java @@ -0,0 +1,15 @@ +// "Replace with anyMatch()" "true" + +import java.util.Arrays; +import java.util.Objects; + +public class Main { + void contains(String[][] haystack, String needle) { + if(haystack != null) { + if (Arrays.stream(haystack).filter(Objects::nonNull).flatMap(Arrays::stream).anyMatch(needle::equals)) { + return; + } + } + throw new IllegalStateException(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAnyMatchCustomReturn.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAnyMatchCustomReturn.java new file mode 100644 index 000000000000..5ea7b5b1d6ca --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAnyMatchCustomReturn.java @@ -0,0 +1,17 @@ +// "Replace with anyMatch()" "true" + +public class Main { + String contains(String[][] haystack, String needle) { + if(haystack != null) { + for (String[] row : haystack) { + if (row == null) continue; + for (String str : row) { + if (needle.equals(str)) + return "yes"+needle.length(); + } + } + System.out.println("oops"); + } + return "no"; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAnyMatchEnclosingIfInterrupted.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAnyMatchEnclosingIfInterrupted.java index cea665b8dab1..59bf357b7aae 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAnyMatchEnclosingIfInterrupted.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAnyMatchEnclosingIfInterrupted.java @@ -1,4 +1,4 @@ -// "Replace with anyMatch()" "false" +// "Replace with anyMatch()" "true" public class Main { boolean contains(String[][] haystack, String needle) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAnyMatchUsesVar.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAnyMatchUsesVar.java new file mode 100644 index 000000000000..16653183f96f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAnyMatchUsesVar.java @@ -0,0 +1,17 @@ +// "Replace with anyMatch()" "false" + +public class Main { + String contains(String[][] haystack, String needle) { + if(haystack != null) { + for (String[] row : haystack) { + if (row == null) continue; + for (String str : row) { + if (needle.equals(str)) + return "yes"+str; + } + } + System.out.println("oops"); + } + return "no"; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAnyMatchVoid.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAnyMatchVoid.java new file mode 100644 index 000000000000..4cc7b244f862 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeAnyMatchVoid.java @@ -0,0 +1,16 @@ +// "Replace with anyMatch()" "true" + +public class Main { + void contains(String[][] haystack, String needle) { + if(haystack != null) { + for (String[] row : haystack) { + if (row == null) continue; + for (String str : row) { + if (needle.equals(str)) + return; + } + } + } + throw new IllegalStateException(); + } +}