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);