Bug 566001 - [cleanup & saveaction] Pattern matching for instanceof
Change-Id: I51f3e5915f60ed6107fe800e6b9c376de1e87e12
Signed-off-by: Fabrice Tiercelin <fabrice.tiercelin@yahoo.fr>
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java
index 31ebe19..5b3ff15 100644
--- a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java
@@ -107,6 +107,7 @@
public static String CodeFormatFix_description;
public static String VarCleanUp_description;
+ public static String PatternMatchingForInstanceofCleanup_description;
public static String LambdaExpressionsCleanUp_use_lambda_where_possible;
public static String LambdaExpressionsCleanUp_use_anonymous;
public static String LambdaExpressionAndMethodRefCleanUp_description;
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties
index c3ef213..a27f06c 100644
--- a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties
@@ -85,6 +85,7 @@
CodeFormatCleanUp_description=Format source code
ImportsCleanUp_OrganizeImports_Description=Organize imports
VarCleanUp_description=Use the local variable type inference where possible
+PatternMatchingForInstanceofCleanup_description=Use pattern matching for instanceof
LambdaExpressionsCleanUp_use_lambda_where_possible=Use lambda where possible
LambdaExpressionsCleanUp_use_anonymous=Use anonymous class creations
LambdaExpressionAndMethodRefCleanUp_description=Simplify lambda expression and method reference syntax
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java
index 3acfbc2..a5f1d36 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/ASTNodes.java
@@ -70,6 +70,7 @@
import org.eclipse.jdt.core.dom.BooleanLiteral;
import org.eclipse.jdt.core.dom.BreakStatement;
import org.eclipse.jdt.core.dom.CastExpression;
+import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.CharacterLiteral;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
@@ -122,6 +123,7 @@
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.ThrowStatement;
+import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.UnionType;
import org.eclipse.jdt.core.dom.VariableDeclaration;
@@ -851,6 +853,60 @@
return null;
}
+ /**
+ * Return true if it always ends with a jump statement.
+ *
+ * @param statement the statement
+ * @return true if the statement falls through.
+ */
+ public static boolean fallsThrough(final Statement statement) {
+ List<Statement> statements= asList(statement);
+
+ if (statements.isEmpty()) {
+ return false;
+ }
+
+ Statement lastStatement= statements.get(statements.size() - 1);
+ switch (lastStatement.getNodeType()) {
+ case ASTNode.RETURN_STATEMENT:
+ case ASTNode.THROW_STATEMENT:
+ case ASTNode.BREAK_STATEMENT:
+ case ASTNode.CONTINUE_STATEMENT:
+ return true;
+
+ case ASTNode.BLOCK:
+ Block block= (Block) lastStatement;
+ return fallsThrough(block);
+
+ case ASTNode.IF_STATEMENT:
+ IfStatement ifStatement= (IfStatement) lastStatement;
+ Statement thenStatement= ifStatement.getThenStatement();
+ Statement elseStatement= ifStatement.getElseStatement();
+ return fallsThrough(thenStatement) && fallsThrough(elseStatement);
+
+ case ASTNode.TRY_STATEMENT:
+ TryStatement tryStatement= (TryStatement) lastStatement;
+
+ if (!fallsThrough(tryStatement.getBody())
+ || (tryStatement.getFinally() != null && fallsThrough(tryStatement.getFinally()))) {
+ return false;
+ }
+
+ if (tryStatement.catchClauses() != null) {
+ for (Object catchClause : tryStatement.catchClauses()) {
+ if (!fallsThrough(((CatchClause) catchClause).getBody())) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
public static List<BodyDeclaration> getBodyDeclarations(ASTNode node) {
if (node instanceof AbstractTypeDeclaration) {
return ((AbstractTypeDeclaration)node).bodyDeclarations();
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
index 584228b..6061e35 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
@@ -706,6 +706,18 @@
public static final String USE_VAR= "cleanup.use_var"; //$NON-NLS-1$
/**
+ * Uses pattern matching for the instanceof operator when possible.
+ * <p>
+ * Possible values: {TRUE, FALSE}
+ * <p>
+ *
+ * @see CleanUpOptionsCore#TRUE
+ * @see CleanUpOptionsCore#FALSE
+ * @since 4.17
+ */
+ public static final String USE_PATTERN_MATCHING_FOR_INSTANCEOF= "cleanup.instanceof"; //$NON-NLS-1$
+
+ /**
* Controls conversion between lambda expressions and anonymous class creations. For detailed
* settings, use {@link #USE_LAMBDA} or {@link #USE_ANONYMOUS_CLASS_CREATION}
* <p>
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/util/JavaModelUtil.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/util/JavaModelUtil.java
index e2bbd80..f1b895d6 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/util/JavaModelUtil.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/util/JavaModelUtil.java
@@ -935,7 +935,7 @@
}
/**
- * Checks if the given project or workspace has source compliance 14 or greater.
+ * Checks if the given project or workspace has source compliance 15 or greater.
*
* @param project the project to test or <code>null</code> to test the workspace settings
* @return <code>true</code> if the given project or workspace has source compliance 15 or
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d15.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d15.java
index 908625d..7452241 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d15.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest1d15.java
@@ -28,13 +28,14 @@
import org.eclipse.jdt.ui.tests.core.rules.Java15ProjectTestSetup;
import org.eclipse.jdt.ui.tests.core.rules.ProjectTestSetup;
+import org.eclipse.jdt.internal.ui.fix.MultiFixMessages;
+
/**
* Tests the cleanup features related to Java 15.
*/
public class CleanUpTest1d15 extends CleanUpTestCase {
-
@Rule
- public ProjectTestSetup projectSetup = new Java15ProjectTestSetup(true);
+ public ProjectTestSetup projectSetup= new Java15ProjectTestSetup(true);
@Override
protected IJavaProject getProject() {
@@ -87,4 +88,296 @@
assertRefactoringResultAsExpected(new ICompilationUnit[] { cu1 }, new String[] { expected1 });
}
+ @Test
+ public void testPatternMatchingForInstanceof() throws Exception {
+ IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+ String input= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import java.util.Date;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " public long matchPatternForInstanceof(Object object) {\n" //
+ + " // Keep this comment\n" //
+ + " if (object instanceof Date) {\n" //
+ + " final Date date = (Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long matchPatternInConditionalAndExpression(Object object, boolean isValid) {\n" //
+ + " if (isValid && object instanceof Date) {\n" //
+ + " final Date date = (Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + "\n" //
+ + " public long matchPatternInAndExpression(Object object, boolean isValid) {\n" //
+ + " if (object instanceof Date & isValid) {\n" //
+ + " final Date date = (Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long matchPatternInElse(Object object) {\n" //
+ + " if (!(object instanceof Date)) {\n" //
+ + " return 0;\n" //
+ + " } else {\n" //
+ + " final Date date = (Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long matchPatternInConditionalOrExpression(Object object, boolean isValid) {\n" //
+ + " if (!(object instanceof Date) || isValid) {\n" //
+ + " return 0;\n" //
+ + " } else {\n" //
+ + " final Date date = (Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long matchPatternInOrExpression(Object object, boolean isValid) {\n" //
+ + " if (isValid | !(object instanceof Date)) {\n" //
+ + " return 0;\n" //
+ + " } else {\n" //
+ + " final Date date = (Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long matchPatternInElse(Object object) {\n" //
+ + " if (!(object instanceof Date)) {\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " final Date date = (Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long matchPatternOnLoneStatement(Object object) {\n" //
+ + " // Keep this comment\n" //
+ + " if (object instanceof Date) final Date date = (Date) object;\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "}\n";
+ ICompilationUnit cu= pack.createCompilationUnit("E.java", input, false, null);
+
+ enable(CleanUpConstants.USE_PATTERN_MATCHING_FOR_INSTANCEOF);
+
+ String output= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import java.util.Date;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " public long matchPatternForInstanceof(Object object) {\n" //
+ + " // Keep this comment\n" //
+ + " if (object instanceof Date date) {\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long matchPatternInConditionalAndExpression(Object object, boolean isValid) {\n" //
+ + " if (isValid && object instanceof Date date) {\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + "\n" //
+ + " public long matchPatternInAndExpression(Object object, boolean isValid) {\n" //
+ + " if (object instanceof Date date & isValid) {\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long matchPatternInElse(Object object) {\n" //
+ + " if (!(object instanceof Date date)) {\n" //
+ + " return 0;\n" //
+ + " } else {\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long matchPatternInConditionalOrExpression(Object object, boolean isValid) {\n" //
+ + " if (!(object instanceof Date date) || isValid) {\n" //
+ + " return 0;\n" //
+ + " } else {\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long matchPatternInOrExpression(Object object, boolean isValid) {\n" //
+ + " if (isValid | !(object instanceof Date date)) {\n" //
+ + " return 0;\n" //
+ + " } else {\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long matchPatternInElse(Object object) {\n" //
+ + " if (!(object instanceof Date date)) {\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long matchPatternOnLoneStatement(Object object) {\n" //
+ + " // Keep this comment\n" //
+ + " if (object instanceof Date date) {}\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "}\n";
+ assertGroupCategoryUsed(new ICompilationUnit[] { cu }, new String[] { MultiFixMessages.PatternMatchingForInstanceofCleanup_description });
+ assertRefactoringResultAsExpected(new ICompilationUnit[] { cu }, new String[] { output });
+ }
+
+ @Test
+ public void testDoNotMatchPatternForInstanceof() throws Exception {
+ IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+ String sample= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import java.util.Date;\n" //
+ + "import java.util.List;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " public long doNotMatchInOppositeCondition(Object object) {\n" //
+ + " if (!(object instanceof Date)) {\n" //
+ + " final Date theDate = (Date) object;\n" //
+ + " return theDate.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long doNotMatchNotFinalVariable(Object object) {\n" //
+ + " if (object instanceof Date) {\n" //
+ + " Date date = (Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long doNotMatchWithBadOperator(Object object, boolean isEnabled) {\n" //
+ + " if (object instanceof Date || isEnabled) {\n" //
+ + " final Date date = (Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long doNotMatchElseWithAndOperator(Object object, boolean isValid) {\n" //
+ + " if (isValid && !(object instanceof Date)) {\n" //
+ + " return 0;\n" //
+ + " } else {\n" //
+ + " final Date date = (Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long doNotMatchWrongObject(Object object, Object object2) {\n" //
+ + " if (object instanceof Date) {\n" //
+ + " final Date date = (Date) object2;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long doNotMatchWrongType(Object object) {\n" //
+ + " if (object instanceof Date) {\n" //
+ + " final java.sql.Date date = (java.sql.Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long doNotMatchActiveExpression(List<Object> objects) {\n" //
+ + " if (objects.remove(0) instanceof Date) {\n" //
+ + " final Date date = (Date) objects.remove(0);\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long doNotMatchAlreadyMatchedInstanceof(Object object) {\n" //
+ + " if (object instanceof Date anotherDate) {\n" //
+ + " final Date date = (Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long doNotMatchReassignedObject(Object object, Object object2) {\n" //
+ + " if (object instanceof Date) {\n" //
+ + " object = object2;\n" //
+ + " final Date date = (Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long doNotMatchPatternInWhile(Object object) {\n" //
+ + " while (object instanceof Date) {\n" //
+ + " final Date date = (Date) object;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long doNotMatchActiveExpression(List<Object> objects) {\n" //
+ + " if (objects.remove(0) instanceof Date) {\n" //
+ + " final Date date = (Date) objects.remove(0);\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public long doNotMatchMultiDeclaration(Object object) {\n" //
+ + " if (object instanceof Date) {\n" //
+ + " final Date date = (Date) object, anotherDate = null;\n" //
+ + " return date.getTime();\n" //
+ + " }\n" //
+ + "\n" //
+ + " return 0;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public void doNotMatchOppositeStatements() {\n" //
+ + " Object bah = 1;\n" //
+ + " if (bah instanceof Integer) return;\n" //
+ + " final Integer i = (Integer) bah;\n" //
+ + " System.out.println(i);\n" //
+ + " }\n" //
+ + "}\n";
+ ICompilationUnit cu= pack.createCompilationUnit("E.java", sample, false, null);
+
+ enable(CleanUpConstants.USE_PATTERN_MATCHING_FOR_INSTANCEOF);
+
+ assertRefactoringHasNoChange(new ICompilationUnit[] { cu });
+ }
}
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
index 4ce7636..df32be5 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
@@ -104,6 +104,7 @@
options.setOption(MAP_CLONING, CleanUpOptions.FALSE);
options.setOption(REMOVE_REDUNDANT_MODIFIERS, CleanUpOptions.FALSE);
options.setOption(RAISE_EMBEDDED_IF, CleanUpOptions.FALSE);
+ options.setOption(USE_PATTERN_MATCHING_FOR_INSTANCEOF, CleanUpOptions.FALSE);
options.setOption(REMOVE_REDUNDANT_SEMICOLONS, CleanUpOptions.FALSE);
options.setOption(REMOVE_UNNECESSARY_ARRAY_CREATION, CleanUpOptions.FALSE);
options.setOption(REMOVE_USELESS_RETURN, CleanUpOptions.FALSE);
@@ -218,6 +219,7 @@
options.setOption(MAP_CLONING, CleanUpOptions.FALSE);
options.setOption(REMOVE_REDUNDANT_MODIFIERS, CleanUpOptions.FALSE);
options.setOption(RAISE_EMBEDDED_IF, CleanUpOptions.FALSE);
+ options.setOption(USE_PATTERN_MATCHING_FOR_INSTANCEOF, CleanUpOptions.FALSE);
options.setOption(REMOVE_REDUNDANT_SEMICOLONS, CleanUpOptions.FALSE);
options.setOption(REMOVE_UNNECESSARY_ARRAY_CREATION, CleanUpOptions.FALSE);
options.setOption(REMOVE_USELESS_RETURN, CleanUpOptions.FALSE);
diff --git a/org.eclipse.jdt.ui/plugin.xml b/org.eclipse.jdt.ui/plugin.xml
index dac2b1a..4b14122 100644
--- a/org.eclipse.jdt.ui/plugin.xml
+++ b/org.eclipse.jdt.ui/plugin.xml
@@ -7146,9 +7146,14 @@
runAfter="org.eclipse.jdt.ui.cleanup.unnecessary_modifiers">
</cleanUp>
<cleanUp
+ class="org.eclipse.jdt.internal.ui.fix.PatternMatchingForInstanceofCleanUp"
+ id="org.eclipse.jdt.ui.cleanup.pattern_matching"
+ runAfter="org.eclipse.jdt.ui.cleanup.embedded_if">
+ </cleanUp>
+ <cleanUp
class="org.eclipse.jdt.internal.ui.fix.RedundantSemicolonsCleanUp"
id="org.eclipse.jdt.ui.cleanup.unnecessary_semicolons"
- runAfter="org.eclipse.jdt.ui.cleanup.embedded_if">
+ runAfter="org.eclipse.jdt.ui.cleanup.pattern_matching">
</cleanUp>
<cleanUp
class="org.eclipse.jdt.internal.ui.fix.UnnecessaryArrayCreationCleanUp"
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/PatternMatchingForInstanceofCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/PatternMatchingForInstanceofCleanUp.java
new file mode 100644
index 0000000..9fd3eb9
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/PatternMatchingForInstanceofCleanUp.java
@@ -0,0 +1,288 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Fabrice TIERCELIN and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Fabrice TIERCELIN - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.fix;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+import org.eclipse.text.edits.TextEditGroup;
+
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.Block;
+import org.eclipse.jdt.core.dom.CastExpression;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.IfStatement;
+import org.eclipse.jdt.core.dom.InfixExpression;
+import org.eclipse.jdt.core.dom.InstanceofExpression;
+import org.eclipse.jdt.core.dom.Modifier;
+import org.eclipse.jdt.core.dom.PrefixExpression;
+import org.eclipse.jdt.core.dom.SimpleName;
+import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
+import org.eclipse.jdt.core.dom.Statement;
+import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
+import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
+
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
+import org.eclipse.jdt.internal.corext.fix.CleanUpConstants;
+import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix;
+import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation;
+import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel;
+import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
+import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
+
+import org.eclipse.jdt.ui.cleanup.CleanUpRequirements;
+import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
+import org.eclipse.jdt.ui.text.java.IProblemLocation;
+
+import org.eclipse.jdt.internal.ui.text.correction.PreviewFeaturesSubProcessor;
+
+/**
+ * A fix that uses pattern matching for the instanceof expression when possible.
+ */
+public class PatternMatchingForInstanceofCleanUp extends AbstractMultiFix implements ICleanUpFix {
+ public PatternMatchingForInstanceofCleanUp() {
+ this(Collections.emptyMap());
+ }
+
+ public PatternMatchingForInstanceofCleanUp(Map<String, String> options) {
+ super(options);
+ }
+
+ @Override
+ public CleanUpRequirements getRequirements() {
+ boolean requireAST= isEnabled(CleanUpConstants.USE_PATTERN_MATCHING_FOR_INSTANCEOF);
+ return new CleanUpRequirements(requireAST, false, false, null);
+ }
+
+ @Override
+ public String[] getStepDescriptions() {
+ if (isEnabled(CleanUpConstants.USE_PATTERN_MATCHING_FOR_INSTANCEOF)) {
+ return new String[] { MultiFixMessages.PatternMatchingForInstanceofCleanup_description };
+ }
+
+ return new String[0];
+ }
+
+ @Override
+ public String getPreview() {
+ if (isEnabled(CleanUpConstants.USE_PATTERN_MATCHING_FOR_INSTANCEOF)) {
+ return "" //$NON-NLS-1$
+ + "if (object instanceof Integer i) {\n" //$NON-NLS-1$
+ + " return i.intValue();\n" //$NON-NLS-1$
+ + "}\n\n"; //$NON-NLS-1$
+ }
+
+ return "" //$NON-NLS-1$
+ + "if (object instanceof Integer) {\n" //$NON-NLS-1$
+ + " final Integer i = (Integer) object;\n" //$NON-NLS-1$
+ + " return i.intValue();\n" //$NON-NLS-1$
+ + "}\n"; //$NON-NLS-1$
+ }
+
+ @Override
+ protected ICleanUpFix createFix(CompilationUnit unit) throws CoreException {
+ if (!isEnabled(CleanUpConstants.USE_PATTERN_MATCHING_FOR_INSTANCEOF)
+ || !PreviewFeaturesSubProcessor.isPreviewFeatureEnabled(unit.getJavaElement().getJavaProject())
+ || !JavaModelUtil.is14OrHigher(unit.getJavaElement().getJavaProject())) {
+ return null;
+ }
+
+ final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>();
+
+ unit.accept(new ASTVisitor() {
+ @Override
+ public boolean visit(final Block node) {
+ InstanceofVisitor instanceofVisitor= new InstanceofVisitor(node);
+ node.accept(instanceofVisitor);
+ return instanceofVisitor.getResult();
+ }
+
+ final class InstanceofVisitor extends ASTVisitor {
+ private final Block startNode;
+ private boolean result= true;
+
+ public InstanceofVisitor(final Block startNode) {
+ this.startNode= startNode;
+ }
+
+ /**
+ * @return The result
+ */
+ public boolean getResult() {
+ return result;
+ }
+
+ @Override
+ public boolean visit(final Block node) {
+ return startNode == node;
+ }
+
+ @Override
+ public boolean visit(final InstanceofExpression node) {
+ if (node.getPatternVariable() != null
+ || !ASTNodes.isPassive(node.getLeftOperand())
+ || node.getRightOperand().resolveBinding() == null) {
+ return true;
+ }
+
+ boolean isPositiveCaseToAnalyze= true;
+ ASTNode currentNode= node;
+
+ while (currentNode.getParent() != null
+ && (!(currentNode.getParent() instanceof IfStatement)
+ || currentNode.getLocationInParent() != IfStatement.EXPRESSION_PROPERTY)) {
+ switch (currentNode.getParent().getNodeType()) {
+ case ASTNode.PARENTHESIZED_EXPRESSION:
+ break;
+
+ case ASTNode.PREFIX_EXPRESSION:
+ if (!ASTNodes.hasOperator((PrefixExpression) currentNode.getParent(), PrefixExpression.Operator.NOT)) {
+ return true;
+ }
+
+ isPositiveCaseToAnalyze= !isPositiveCaseToAnalyze;
+ break;
+
+ case ASTNode.INFIX_EXPRESSION:
+ if (isPositiveCaseToAnalyze
+ ? !ASTNodes.hasOperator((InfixExpression) currentNode.getParent(), InfixExpression.Operator.CONDITIONAL_AND, InfixExpression.Operator.AND)
+ : !ASTNodes.hasOperator((InfixExpression) currentNode.getParent(), InfixExpression.Operator.CONDITIONAL_OR, InfixExpression.Operator.OR)) {
+ return true;
+ }
+
+ break;
+
+ default:
+ return true;
+ }
+
+ currentNode= currentNode.getParent();
+ }
+
+ if (currentNode.getParent() == null) {
+ return true;
+ }
+
+ IfStatement ifStatement= (IfStatement) currentNode.getParent();
+
+ if (isPositiveCaseToAnalyze) {
+ return maybeMatchPattern(node, ifStatement.getThenStatement());
+ }
+
+ if (ifStatement.getElseStatement() != null) {
+ return maybeMatchPattern(node, ifStatement.getElseStatement());
+ }
+
+ if (ASTNodes.fallsThrough(ifStatement.getThenStatement())) {
+ return maybeMatchPattern(node, ASTNodes.getNextSibling(ifStatement));
+ }
+
+ return true;
+ }
+
+ private boolean maybeMatchPattern(final InstanceofExpression node, final Statement expression) {
+ List<Statement> statements= ASTNodes.asList(expression);
+
+ if (!statements.isEmpty()) {
+ VariableDeclarationStatement variableDeclarationExpression= ASTNodes.as(statements.get(0), VariableDeclarationStatement.class);
+ VariableDeclarationFragment variableDeclarationFragment= ASTNodes.getUniqueFragment(variableDeclarationExpression);
+
+ if (variableDeclarationFragment != null && Modifier.isFinal(variableDeclarationExpression.getModifiers())) {
+ CastExpression castExpression= ASTNodes.as(variableDeclarationFragment.getInitializer(), CastExpression.class);
+
+ if (castExpression != null
+ && Objects.equals(node.getRightOperand().resolveBinding(), variableDeclarationExpression.getType().resolveBinding())
+ && Objects.equals(node.getRightOperand().resolveBinding(), castExpression.getType().resolveBinding())
+ && ASTNodes.isPassive(node.getLeftOperand())
+ && ASTNodes.match(node.getLeftOperand(), castExpression.getExpression())) {
+ rewriteOperations.add(new PatternMatchingForInstanceofOperation(node, variableDeclarationExpression, variableDeclarationFragment.getName()));
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+ });
+
+ if (rewriteOperations.isEmpty()) {
+ return null;
+ }
+
+ return new CompilationUnitRewriteOperationsFix(MultiFixMessages.PatternMatchingForInstanceofCleanup_description, unit,
+ rewriteOperations.toArray(new CompilationUnitRewriteOperation[0]));
+ }
+
+ @Override
+ public CompilationUnitChange createChange(IProgressMonitor progressMonitor) throws CoreException {
+ return null;
+ }
+
+ @Override
+ public boolean canFix(final ICompilationUnit compilationUnit, final IProblemLocation problem) {
+ return false;
+ }
+
+ @Override
+ protected ICleanUpFix createFix(final CompilationUnit unit, final IProblemLocation[] problems) throws CoreException {
+ return null;
+ }
+
+ private static class PatternMatchingForInstanceofOperation extends CompilationUnitRewriteOperation {
+ private final InstanceofExpression nodeToComplete;
+ private final VariableDeclarationStatement statementToRemove;
+ private final SimpleName expressionToMove;
+
+ public PatternMatchingForInstanceofOperation(final InstanceofExpression nodeToComplete, final VariableDeclarationStatement statementToRemove, final SimpleName expressionToMove) {
+ this.nodeToComplete= nodeToComplete;
+ this.statementToRemove= statementToRemove;
+ this.expressionToMove= expressionToMove;
+ }
+
+ @Override
+ public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException {
+ ASTRewrite rewrite= cuRewrite.getASTRewrite();
+ AST ast= cuRewrite.getRoot().getAST();
+ TextEditGroup group= createTextEditGroup(MultiFixMessages.PatternMatchingForInstanceofCleanup_description, cuRewrite);
+
+ SingleVariableDeclaration newSingleVariableDeclaration= ast.newSingleVariableDeclaration();
+ newSingleVariableDeclaration.setName(ASTNodes.createMoveTarget(rewrite, expressionToMove));
+ newSingleVariableDeclaration.setType(ASTNodes.createMoveTarget(rewrite, nodeToComplete.getRightOperand()));
+
+ InstanceofExpression newInstanceof= ast.newInstanceofExpression();
+ newInstanceof.setLeftOperand(ASTNodes.createMoveTarget(rewrite, nodeToComplete.getLeftOperand()));
+ newInstanceof.setPatternVariable(newSingleVariableDeclaration);
+
+ ASTNodes.replaceButKeepComment(rewrite, nodeToComplete, newInstanceof, group);
+
+ if (ASTNodes.canHaveSiblings(statementToRemove)) {
+ rewrite.remove(statementToRemove, group);
+ } else {
+ ASTNodes.replaceButKeepComment(rewrite, statementToRemove, ast.newBlock(), group);
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
index 970fb9d..e4fd30e 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
@@ -127,6 +127,7 @@
public static String UnnecessaryCodeTabPage_CheckboxName_ArraysFill;
public static String UnnecessaryCodeTabPage_CheckboxName_Autoboxing;
public static String UnnecessaryCodeTabPage_CheckboxName_Unboxing;
+ public static String UnnecessaryCodeTabPage_CheckboxName_PatternMatchingForInstanceof;
public static String UnnecessaryCodeTabPage_CheckboxName_PushDownNegation;
public static String UnnecessaryCodeTabPage_CheckboxName_RedundantSuperCall;
public static String UnnecessaryCodeTabPage_CheckboxName_MergeConditionalBlocks;
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
index 1026949..7885919 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
@@ -112,6 +112,7 @@
UnnecessaryCodeTabPage_CheckboxName_ArraysFill=Use Arrays.&fill() when possible
UnnecessaryCodeTabPage_CheckboxName_Autoboxing=Use Autobo&xing (1.5 or higher)
UnnecessaryCodeTabPage_CheckboxName_Unboxing=Use Un&boxing (1.5 or higher)
+UnnecessaryCodeTabPage_CheckboxName_PatternMatchingForInstanceof=&Pattern matching for instanceof (Java 15 or higher)
UnnecessaryCodeTabPage_CheckboxName_PushDownNegation=Push d&own negation
UnnecessaryCodeTabPage_CheckboxName_RedundantSuperCall=Remove redundant s&uper() call in constructor
UnnecessaryCodeTabPage_CheckboxName_MergeConditionalBlocks=Merge &conditions of if/else if/else that have the same blocks
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/UnnecessaryCodeTabPage.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/UnnecessaryCodeTabPage.java
index 544d376..8b43151 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/UnnecessaryCodeTabPage.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/UnnecessaryCodeTabPage.java
@@ -30,6 +30,7 @@
import org.eclipse.jdt.internal.ui.fix.MapMethodCleanUp;
import org.eclipse.jdt.internal.ui.fix.MergeConditionalBlocksCleanUp;
import org.eclipse.jdt.internal.ui.fix.ObjectsEqualsCleanUp;
+import org.eclipse.jdt.internal.ui.fix.PatternMatchingForInstanceofCleanUp;
import org.eclipse.jdt.internal.ui.fix.PushDownNegationCleanUp;
import org.eclipse.jdt.internal.ui.fix.RedundantModifiersCleanUp;
import org.eclipse.jdt.internal.ui.fix.RedundantSemicolonsCleanUp;
@@ -63,6 +64,7 @@
new MapCloningCleanUp(values),
new RedundantModifiersCleanUp(values),
new EmbeddedIfCleanUp(values),
+ new PatternMatchingForInstanceofCleanUp(values),
new RedundantSemicolonsCleanUp(values),
new UnnecessaryArrayCreationCleanUp(values),
new UselessReturnCleanUp(values),
@@ -108,6 +110,9 @@
CheckboxPreference unboxing= createCheckboxPref(unnecessaryGroup, numColumns, CleanUpMessages.UnnecessaryCodeTabPage_CheckboxName_Unboxing, CleanUpConstants.USE_UNBOXING, CleanUpModifyDialog.FALSE_TRUE);
registerPreference(unboxing);
+ CheckboxPreference patternMatchingForInstanceof= createCheckboxPref(unnecessaryGroup, numColumns, CleanUpMessages.UnnecessaryCodeTabPage_CheckboxName_PatternMatchingForInstanceof, CleanUpConstants.USE_PATTERN_MATCHING_FOR_INSTANCEOF, CleanUpModifyDialog.FALSE_TRUE);
+ registerPreference(patternMatchingForInstanceof);
+
CheckboxPreference pushDownNegation= createCheckboxPref(unnecessaryGroup, numColumns, CleanUpMessages.UnnecessaryCodeTabPage_CheckboxName_PushDownNegation, CleanUpConstants.PUSH_DOWN_NEGATION,
CleanUpModifyDialog.FALSE_TRUE);
registerPreference(pushDownNegation);