Bug 567779 - [AutoRefactor immigration #32/138] [cleanup & saveaction]
else-if

Change-Id: Id2dd31577bb097c31df079cbc3f67b9e4fb10691
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 f6be68d..e0a16e7 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
@@ -72,6 +72,7 @@
 	public static String CodeStyleCleanUp_AtomicObject_declaration;
 	public static String CodeStyleCleanUp_AtomicObject_usage;
 	public static String CodeStyleCleanUp_PullUpAssignment_description;
+	public static String CodeStyleCleanUp_ElseIf_description;
 	public static String CodeStyleCleanUp_numberSuffix_description;
 	public static String CodeStyleCleanUp_QualifyNonStaticMethod_description;
 	public static String CodeStyleCleanUp_QualifyStaticMethod_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 e0f912d..b3b0d49 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
@@ -36,6 +36,7 @@
 CodeStyleCleanUp_AtomicObject_declaration=Use atomic objects declaration
 CodeStyleCleanUp_AtomicObject_usage=Use atomic objects usage
 CodeStyleCleanUp_PullUpAssignment_description=Pull up assignment
+CodeStyleCleanUp_ElseIf_description=Combine nested 'if' statement in 'else' block to 'else if'
 CodeStyleCleanUp_numberSuffix_description=Use uppercase for long literal suffix
 CodeFormatFix_RemoveTrailingWhitespace_changeDescription=Remove trailing whitespace
 CodeFormatCleanUp_RemoveTrailingNoEmpty_description=Remove trailing white spaces on non empty lines
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 b07355a..09d7a68 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
@@ -1604,6 +1604,18 @@
 	public static final String PULL_UP_ASSIGNMENT= "cleanup.pull_up_assignment"; //$NON-NLS-1$
 
 	/**
+	 * Uses the <code>else if</code> pseudo keyword.
+	 * <p>
+	 * Possible values: {TRUE, FALSE}
+	 * <p>
+	 *
+	 * @see CleanUpOptionsCore#TRUE
+	 * @see CleanUpOptionsCore#FALSE
+	 * @since 4.18
+	 */
+	public static final String ELSE_IF= "cleanup.else_if"; //$NON-NLS-1$
+
+	/**
 	 * Controls whether long literal suffix should be rewritten in uppercase.<br>
 	 * <br>
 	 * Possible values: {TRUE, FALSE}<br>
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest.java
index ec7f8f6..d287af6 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest.java
@@ -9519,6 +9519,83 @@
 	}
 
 	@Test
+	public void testElseIf() throws Exception {
+		IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+		String input= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    public void refactor(boolean isValid, boolean isEnabled) throws Exception {\n" //
+				+ "        if (isValid) {\n" //
+				+ "            // Keep this comment\n" //
+				+ "            System.out.println(isValid);\n" //
+				+ "        } else {\n" //
+				+ "            if (isEnabled) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                System.out.println(isEnabled);\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "}\n";
+		ICompilationUnit cu= pack.createCompilationUnit("E.java", input, false, null);
+
+		enable(CleanUpConstants.ELSE_IF);
+
+		String output= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    public void refactor(boolean isValid, boolean isEnabled) throws Exception {\n" //
+				+ "        if (isValid) {\n" //
+				+ "            // Keep this comment\n" //
+				+ "            System.out.println(isValid);\n" //
+				+ "        } else if (isEnabled) {\n" //
+				+ "            // Keep this comment\n" //
+				+ "            System.out.println(isEnabled);\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "}\n";
+
+		assertNotEquals("The class must be changed", input, output);
+		assertGroupCategoryUsed(new ICompilationUnit[] { cu }, new HashSet<>(Arrays.asList(MultiFixMessages.CodeStyleCleanUp_ElseIf_description)));
+		assertRefactoringResultAsExpected(new ICompilationUnit[] { cu }, new String[] { output });
+	}
+
+	@Test
+	public void testDoNotUseElseIf() throws Exception {
+		IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+		String sample= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    public void doNotRefactor(boolean isValid, boolean isEnabled) throws Exception {\n" //
+				+ "        if (isValid) {\n" //
+				+ "            System.out.println(isValid);\n" //
+				+ "        } else if (isEnabled) {\n" //
+				+ "            System.out.println(isEnabled);\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public void doNotLoseRemainingStatements(boolean isValid, boolean isEnabled) throws Exception {\n" //
+				+ "        if (isValid) {\n" //
+				+ "            System.out.println(isValid);\n" //
+				+ "        } else {\n" //
+				+ "            if (isEnabled) {\n" //
+				+ "                System.out.println(isEnabled);\n" //
+				+ "            }\n" //
+				+ "\n" //
+				+ "            System.out.println(\"Don't forget me!\");\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "}\n";
+		ICompilationUnit cu= pack.createCompilationUnit("E.java", sample, false, null);
+
+		enable(CleanUpConstants.ELSE_IF);
+
+		assertRefactoringHasNoChange(new ICompilationUnit[] { cu });
+	}
+
+	@Test
 	public void testUnnecessaryCodeBug127704_1() throws Exception {
 
 		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
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 aa7956d..9aaca57 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
@@ -63,6 +63,7 @@
 		options.setOption(ATOMIC_OBJECT, CleanUpOptions.FALSE);
 		options.setOption(PULL_UP_ASSIGNMENT, CleanUpOptions.FALSE);
 
+		options.setOption(ELSE_IF, CleanUpOptions.FALSE);
 		options.setOption(NUMBER_SUFFIX, CleanUpOptions.FALSE);
 
 		//Variable Declarations
@@ -195,6 +196,7 @@
 		options.setOption(ATOMIC_OBJECT, CleanUpOptions.FALSE);
 		options.setOption(PULL_UP_ASSIGNMENT, CleanUpOptions.FALSE);
 
+		options.setOption(ELSE_IF, CleanUpOptions.FALSE);
 		options.setOption(NUMBER_SUFFIX, CleanUpOptions.FALSE);
 
 		//Variable Declarations
diff --git a/org.eclipse.jdt.ui/plugin.xml b/org.eclipse.jdt.ui/plugin.xml
index 05009ed..5e2347c 100644
--- a/org.eclipse.jdt.ui/plugin.xml
+++ b/org.eclipse.jdt.ui/plugin.xml
@@ -7081,9 +7081,14 @@
             runAfter="org.eclipse.jdt.ui.cleanup.var">
       </cleanUp>
       <cleanUp
+            class="org.eclipse.jdt.internal.ui.fix.ElseIfCleanUp"
+            id="org.eclipse.jdt.ui.cleanup.else_if"
+            runAfter="org.eclipse.jdt.ui.cleanup.lambda">
+      </cleanUp>
+      <cleanUp
             class="org.eclipse.jdt.internal.ui.fix.SwitchExpressionsCleanUp"
             id="org.eclipse.jdt.ui.cleanup.switch_expressions"
-            runAfter="org.eclipse.jdt.ui.cleanup.lambda">
+            runAfter="org.eclipse.jdt.ui.cleanup.else_if">
       </cleanUp>
       <cleanUp
             class="org.eclipse.jdt.internal.ui.fix.ExpressionsCleanUp"
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/ElseIfCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/ElseIfCleanUp.java
new file mode 100644
index 0000000..d8980f8
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/ElseIfCleanUp.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ * 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 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.ASTVisitor;
+import org.eclipse.jdt.core.dom.Block;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.IfStatement;
+import org.eclipse.jdt.core.dom.Statement;
+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.ui.cleanup.CleanUpRequirements;
+import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
+import org.eclipse.jdt.ui.text.java.IProblemLocation;
+
+/**
+ * A fix that uses the <code>else if</code> pseudo keyword.
+ */
+public class ElseIfCleanUp extends AbstractMultiFix implements ICleanUpFix {
+	public ElseIfCleanUp() {
+		this(Collections.emptyMap());
+	}
+
+	public ElseIfCleanUp(Map<String, String> options) {
+		super(options);
+	}
+
+	@Override
+	public CleanUpRequirements getRequirements() {
+		boolean requireAST= isEnabled(CleanUpConstants.ELSE_IF);
+		return new CleanUpRequirements(requireAST, false, false, null);
+	}
+
+	@Override
+	public String[] getStepDescriptions() {
+		if (isEnabled(CleanUpConstants.ELSE_IF)) {
+			return new String[] { MultiFixMessages.CodeStyleCleanUp_ElseIf_description };
+		}
+
+		return new String[0];
+	}
+
+	@Override
+	public String getPreview() {
+		if (isEnabled(CleanUpConstants.ELSE_IF)) {
+			return "" //$NON-NLS-1$
+					+ "if (isValid) {\n" //$NON-NLS-1$
+					+ "  System.out.println(isValid);\n" //$NON-NLS-1$
+					+ "} else if (isEnabled) {\n" //$NON-NLS-1$
+					+ "  System.out.println(isEnabled);\n" //$NON-NLS-1$
+					+ "}\n\n\n"; //$NON-NLS-1$
+		}
+
+		return "" //$NON-NLS-1$
+				+ "if (isValid) {\n" //$NON-NLS-1$
+				+ "  System.out.println(isValid);\n" //$NON-NLS-1$
+				+ "} else {\n" //$NON-NLS-1$
+				+ "  if (isEnabled) {\n" //$NON-NLS-1$
+				+ "    System.out.println(isEnabled);\n" //$NON-NLS-1$
+				+ "  }\n" //$NON-NLS-1$
+				+ "}\n"; //$NON-NLS-1$
+	}
+
+	@Override
+	protected ICleanUpFix createFix(CompilationUnit unit) throws CoreException {
+		if (!isEnabled(CleanUpConstants.ELSE_IF)) {
+			return null;
+		}
+
+		final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>();
+
+		unit.accept(new ASTVisitor() {
+			@Override
+			public boolean visit(final IfStatement visited) {
+				Statement elseStatement= visited.getElseStatement();
+
+				if (elseStatement instanceof Block) {
+					IfStatement innerIf= ASTNodes.as(elseStatement, IfStatement.class);
+
+					if (innerIf != null) {
+						rewriteOperations.add(new ElseIfOperation(visited, innerIf));
+						return false;
+					}
+				}
+
+				return true;
+			}
+		});
+
+		if (rewriteOperations.isEmpty()) {
+			return null;
+		}
+
+		return new CompilationUnitRewriteOperationsFix(MultiFixMessages.CodeStyleCleanUp_ElseIf_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 ElseIfOperation extends CompilationUnitRewriteOperation {
+		private final IfStatement visited;
+		private final IfStatement innerIf;
+
+		public ElseIfOperation(final IfStatement visited, final IfStatement innerIf) {
+			this.visited= visited;
+			this.innerIf= innerIf;
+		}
+
+		@Override
+		public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException {
+			ASTRewrite rewrite= cuRewrite.getASTRewrite();
+			TextEditGroup group= createTextEditGroup(MultiFixMessages.CodeStyleCleanUp_ElseIf_description, cuRewrite);
+
+			rewrite.replace(visited.getElseStatement(), ASTNodes.createMoveTarget(rewrite, innerIf), 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 94bc477..a9ff400 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
@@ -68,6 +68,7 @@
 	public static String CodeStyleTabPage_CheckboxName_CheckSignOfBitwiseOperation;
 	public static String CodeStyleTabPage_CheckboxName_AtomicObject;
 	public static String CodeStyleTabPage_CheckboxName_PullUpAssignment;
+	public static String CodeStyleTabPage_CheckboxName_ElseIf;
 
 	public static String CodeStyleTabPage_GroupName_NumberLiteral;
 	public static String CodeStyleTabPage_CheckboxName_NumberSuffix;
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 7ca08cf..0850320 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
@@ -38,6 +38,7 @@
 CodeStyleTabPage_CheckboxName_UseBlocks=Use bloc&ks in if/while/for/do statements
 CodeStyleTabPage_RadioName_AlwaysUseBlocks=Al&ways
 CodeStyleTabPage_RadioName_NeverUseBlocks=Only if &necessary
+CodeStyleTabPage_CheckboxName_ElseIf=C&ombine nested 'if' statement in 'else' block to 'else if'
 CodeStyleTabPage_GroupName_Expressions=Expressions
 CodeStyleTabPage_CheckboxName_CheckSignOfBitwiseOperation=&Compare with != 0 for bitwise expressions (use it carefully, it may alter the behavior)
 CodeStyleTabPage_CheckboxName_AtomicObject=Use atomic objects as reference (1.5 or higher)
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CodeStyleTabPage.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CodeStyleTabPage.java
index ee650aa..8a5988a 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CodeStyleTabPage.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CodeStyleTabPage.java
@@ -26,6 +26,7 @@
 import org.eclipse.jdt.internal.ui.fix.BitwiseConditionalExpressionCleanup;
 import org.eclipse.jdt.internal.ui.fix.ControlStatementsCleanUp;
 import org.eclipse.jdt.internal.ui.fix.ConvertLoopCleanUp;
+import org.eclipse.jdt.internal.ui.fix.ElseIfCleanUp;
 import org.eclipse.jdt.internal.ui.fix.ExpressionsCleanUp;
 import org.eclipse.jdt.internal.ui.fix.LambdaExpressionAndMethodRefCleanUp;
 import org.eclipse.jdt.internal.ui.fix.LambdaExpressionsCleanUp;
@@ -45,6 +46,7 @@
 				new ControlStatementsCleanUp(values),
 				new ConvertLoopCleanUp(values),
 				new AddAllCleanUp(values),
+				new ElseIfCleanUp(values),
 				new ExpressionsCleanUp(values),
 				new BitwiseConditionalExpressionCleanup(values),
 				new AtomicObjectCleanUp(values),
@@ -73,6 +75,9 @@
 		final RadioPreference useBlockNeverPref= createRadioPref(controlGroup, numColumns - 1, CleanUpMessages.CodeStyleTabPage_RadioName_NeverUseBlocks, CleanUpConstants.CONTROL_STATEMENTS_USE_BLOCKS_NEVER, CleanUpModifyDialog.FALSE_TRUE);
 		registerSlavePreference(useBlockPref, new RadioPreference[] {useBlockAlwaysPref, useBlockJDTStylePref, useBlockNeverPref});
 
+		CheckboxPreference elseIf= createCheckboxPref(controlGroup, numColumns, CleanUpMessages.CodeStyleTabPage_CheckboxName_ElseIf, CleanUpConstants.ELSE_IF, CleanUpModifyDialog.FALSE_TRUE);
+		registerPreference(elseIf);
+
 		CheckboxPreference convertLoop= createCheckboxPref(controlGroup, numColumns, CleanUpMessages.CodeStyleTabPage_CheckboxName_ConvertForLoopToEnhanced, CleanUpConstants.CONTROL_STATEMENTS_CONVERT_FOR_LOOP_TO_ENHANCED, CleanUpModifyDialog.FALSE_TRUE);
 		registerPreference(convertLoop);
 		intent(controlGroup);