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 32496face5cb..0c0c50feb560 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java @@ -208,7 +208,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo return; } } - if(nextReturnStatement.getReturnValue() instanceof PsiLiteralExpression) { + if(ExpressionUtils.isSimpleExpression(nextReturnStatement.getReturnValue())) { registerProblem(holder, isOnTheFly, statement, "findFirst", new ReplaceWithFindFirstFix()); } } @@ -916,7 +916,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo PsiReturnStatement nextReturnStatement = getNextReturnStatement(foreachStatement); if(nextReturnStatement == null) return; PsiExpression orElseExpression = nextReturnStatement.getReturnValue(); - if(!(orElseExpression instanceof PsiLiteralExpression)) return; + if(!ExpressionUtils.isSimpleExpression(orElseExpression)) return; final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); StringBuilder builder = generateStream(iteratedValue, intermediateOps).append(".findFirst()"); if (!(value instanceof PsiReferenceExpression) || ((PsiReferenceExpression)value).resolve() != tb.getVariable()) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirstEnum.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirstEnum.java new file mode 100644 index 000000000000..0222f513d5aa --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirstEnum.java @@ -0,0 +1,13 @@ +// "Replace with findFirst()" "true" + +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; + +public class Main { + enum MyEnum { FOO, BAR, BAZ } + + public static MyEnum find(List> list) { + return list.stream().flatMap(Collection::stream).filter(val -> val.name().startsWith("B")).findFirst().orElse(MyEnum.FOO); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirstField.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirstField.java new file mode 100644 index 000000000000..d513498dc463 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirstField.java @@ -0,0 +1,14 @@ +// "Replace with findFirst()" "true" + +import java.awt.*; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; + +public class Main { + private Point field = new Point(0, 0); + + public Point find(List points) { + return points.stream().filter((Predicate) Objects::nonNull).findFirst().orElse(field); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirstStaticField.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirstStaticField.java new file mode 100644 index 000000000000..5023572711d0 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterFindFirstStaticField.java @@ -0,0 +1,14 @@ +// "Replace with findFirst()" "true" + +import java.awt.*; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; + +public class Main { + private static Point ZERO = new Point(0, 0); + + public static Point find(List points) { + return points.stream().filter((Predicate) Objects::nonNull).findFirst().orElse(ZERO); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstCall.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstCall.java new file mode 100644 index 000000000000..497d9041a64e --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstCall.java @@ -0,0 +1,13 @@ +// "Replace with findFirst()" "false" + +import java.awt.*; +import java.util.List; + +public class Main { + public Point find(List points, Main other) { + for (Point pt : points) { + if (pt != null) return pt; + } + return new Point(0, 0); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstEnum.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstEnum.java new file mode 100644 index 000000000000..78994e56a282 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstEnum.java @@ -0,0 +1,19 @@ +// "Replace with findFirst()" "true" + +import java.util.EnumSet; +import java.util.List; + +public class Main { + enum MyEnum { FOO, BAR, BAZ } + + public static MyEnum find(List> list) { + for (EnumSet set : list) { + for (MyEnum val : set) { + if (val.name().startsWith("B")) { + return val; + } + } + } + return MyEnum.FOO; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstField.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstField.java new file mode 100644 index 000000000000..dbfb19edc872 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstField.java @@ -0,0 +1,15 @@ +// "Replace with findFirst()" "true" + +import java.awt.*; +import java.util.List; + +public class Main { + private Point field = new Point(0, 0); + + public Point find(List points) { + for (Point pt : points) { + if (pt != null) return pt; + } + return field; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstFieldOther.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstFieldOther.java new file mode 100644 index 000000000000..7d94f6bd173d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstFieldOther.java @@ -0,0 +1,15 @@ +// "Replace with findFirst()" "false" + +import java.awt.*; +import java.util.List; + +public class Main { + private Point field = new Point(0, 0); + + public Point find(List points, Main other) { + for (Point pt : points) { + if (pt != null) return pt; + } + return other.field; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstStaticField.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstStaticField.java new file mode 100644 index 000000000000..e336ffe8fcb1 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeFindFirstStaticField.java @@ -0,0 +1,15 @@ +// "Replace with findFirst()" "true" + +import java.awt.*; +import java.util.List; + +public class Main { + private static Point ZERO = new Point(0, 0); + + public static Point find(List points) { + for (Point pt : points) { + if (pt != null) return pt; + } + return ZERO; + } +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java index 5a33055004a3..06d6d70a47c5 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/ExpressionUtils.java @@ -22,6 +22,7 @@ import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.ConstantExpressionUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -673,4 +674,33 @@ public class ExpressionUtils { NullableNotNullManager.isNullable(modifierListOwner): NullableNotNullManager.isNotNull(modifierListOwner); } + + /** + * Returns true if the expression can be moved to earlier point in program order without possible semantic change or + * notable performance handicap. Examples of simple expressions are: + * - literal (number, char, string, class literal, true, false, null) + * - this + * - static field access + * - instance field access having 'this' as qualifier + * + * @param expression an expression to test + * @return true if the supplied expression is simple + */ + @Contract("null -> false") + public static boolean isSimpleExpression(@Nullable PsiExpression expression) { + if (expression instanceof PsiLiteralExpression || + expression instanceof PsiThisExpression || + expression instanceof PsiClassObjectAccessExpression) { + return true; + } + if(expression instanceof PsiReferenceExpression) { + PsiExpression qualifier = ((PsiReferenceExpression)expression).getQualifierExpression(); + if(qualifier == null || qualifier instanceof PsiThisExpression) return true; + if(qualifier instanceof PsiReferenceExpression) { + PsiElement resolvedQualifier = ((PsiReferenceExpression)qualifier).resolve(); + if(resolvedQualifier instanceof PsiClass) return true; + } + } + return false; + } } \ No newline at end of file