Bug 572574 - [AutoRefactor #71/151] Do/while rather than while

Replace while by do/while:
 - The first evaluation must be always true,
 - The first evaluation must be passive.

Given:
        boolean isInitedToTrue= true;
        while (isInitedToTrue) {
            if (i > 100) {
                isInitedToTrue= false;
            }
            i *= 2;
        }

When:
Select while loop and perform CTRL+1 to do quick fix...

Then:
        boolean isInitedToTrue= true;
        do {
            if (i > 100) {
                isInitedToTrue= false;
            }
            i *= 2;
        } while (isInitedToTrue);

Change-Id: I7c778d6777a5e363e4fc1f2df6c57a57f0d4ff32
Signed-off-by: Fabrice Tiercelin <fabrice.tiercelin@yahoo.fr>
Signed-off-by: Jeff Johnston <jjohnstn@redhat.com>
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.ui/+/178824
Tested-by: JDT Bot <jdt-bot@eclipse.org>
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/DoWhileRatherThanWhileCleanUpCore.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/DoWhileRatherThanWhileCleanUpCore.java
new file mode 100644
index 0000000..6a0b179
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/DoWhileRatherThanWhileCleanUpCore.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright (c) 2021 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.Map;
+
+import org.eclipse.core.runtime.CoreException;
+
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.manipulation.CleanUpContextCore;
+import org.eclipse.jdt.core.manipulation.CleanUpRequirementsCore;
+import org.eclipse.jdt.core.manipulation.ICleanUpFixCore;
+
+import org.eclipse.jdt.internal.corext.fix.CleanUpConstants;
+import org.eclipse.jdt.internal.corext.fix.DoWhileRatherThanWhileFixCore;
+
+public class DoWhileRatherThanWhileCleanUpCore extends AbstractCleanUpCore {
+	public DoWhileRatherThanWhileCleanUpCore(final Map<String, String> options) {
+		super(options);
+	}
+
+	public DoWhileRatherThanWhileCleanUpCore() {
+	}
+
+	@Override
+	public CleanUpRequirementsCore getRequirementsCore() {
+		return new CleanUpRequirementsCore(requireAST(), false, false, null);
+	}
+
+	public boolean requireAST() {
+		return isEnabled(CleanUpConstants.DO_WHILE_RATHER_THAN_WHILE);
+	}
+
+	@Override
+	public ICleanUpFixCore createFixCore(final CleanUpContextCore context) throws CoreException {
+		CompilationUnit compilationUnit= context.getAST();
+
+		if (compilationUnit == null || !isEnabled(CleanUpConstants.DO_WHILE_RATHER_THAN_WHILE)) {
+			return null;
+		}
+
+		return DoWhileRatherThanWhileFixCore.createCleanUp(compilationUnit);
+	}
+
+	@Override
+	public String[] getStepDescriptions() {
+		if (isEnabled(CleanUpConstants.DO_WHILE_RATHER_THAN_WHILE)) {
+			return new String[] {MultiFixMessages.DoWhileRatherThanWhileCleanUp_description};
+		}
+
+		return new String[0];
+	}
+
+	@Override
+	public String getPreview() {
+		StringBuilder bld= new StringBuilder();
+
+		if (isEnabled(CleanUpConstants.DO_WHILE_RATHER_THAN_WHILE)) {
+			bld.append("do {\n"); //$NON-NLS-1$
+		} else {
+			bld.append("while (true) {\n"); //$NON-NLS-1$
+		}
+
+		bld.append("    if (i > 100) {\n"); //$NON-NLS-1$
+		bld.append("        return;\n"); //$NON-NLS-1$
+		bld.append("    }\n"); //$NON-NLS-1$
+		bld.append("    i *= 2;\n"); //$NON-NLS-1$
+
+		if (isEnabled(CleanUpConstants.DO_WHILE_RATHER_THAN_WHILE)) {
+			bld.append("} while (true);\n"); //$NON-NLS-1$
+		} else {
+			bld.append("}\n"); //$NON-NLS-1$
+		}
+
+		return bld.toString();
+	}
+}
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 a70a9cc..aa7d64b 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
@@ -130,6 +130,7 @@
 	public static String SingleUsedFieldCleanUp_description_new_local_var_declaration;
 	public static String SingleUsedFieldCleanUp_description_uses_of_the_var;
 	public static String BreakLoopCleanUp_description;
+	public static String DoWhileRatherThanWhileCleanUp_description;
 	public static String StaticInnerClassCleanUp_description;
 	public static String StringBuilderCleanUp_description;
 	public static String PlainReplacementCleanUp_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 ed7409c..a84926c 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
@@ -112,6 +112,7 @@
 SingleUsedFieldCleanUp_description_new_local_var_declaration=Convert field assignment into local variable declaration
 SingleUsedFieldCleanUp_description_uses_of_the_var=Convert field call into local variable call
 BreakLoopCleanUp_description=Exit loop earlier
+DoWhileRatherThanWhileCleanUp_description=Do/while rather than while
 StaticInnerClassCleanUp_description=Make inner classes static where possible
 StringBuilderCleanUp_description=Replace String concatenation by StringBuilder
 PlainReplacementCleanUp_description=Use String.replace() instead of String.replaceAll() when possible
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 42efb3f..f709e8f 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
@@ -2301,6 +2301,17 @@
 		return null;
 	}
 
+	/**
+	 * Returns the previous statements in the same block if it exists.
+	 *
+	 * @param startNode the start node
+	 * @return the previous statements in the same block if it exists, empty list
+	 *         otherwise
+	 */
+	public static List<Statement> getPreviousSiblings(final Statement startNode) {
+		return getSiblings(startNode, false);
+	}
+
 	private static List<Statement> getSiblings(final Statement startNode, final boolean isForward) {
 		Statement statementAtLevel= statementAtLevel(startNode);
 
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 9a62773..e2c86e0 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
@@ -1175,6 +1175,18 @@
 	public static final String BREAK_LOOP= "cleanup.break_loop"; //$NON-NLS-1$
 
 	/**
+	 * Replace <code>while</code> by <code>do</code>/<code>while</code>.
+	 * <p>
+	 * Possible values: {TRUE, FALSE}
+	 * <p>
+	 *
+	 * @see CleanUpOptionsCore#TRUE
+	 * @see CleanUpOptionsCore#FALSE
+	 * @since 4.20
+	 */
+	public static final String DO_WHILE_RATHER_THAN_WHILE= "cleanup.do_while_rather_than_while"; //$NON-NLS-1$
+
+	/**
 	 * Make inner <code>class</code> static.
 	 * <p>
 	 * Possible values: {TRUE, FALSE}
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/DoWhileRatherThanWhileFixCore.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/DoWhileRatherThanWhileFixCore.java
new file mode 100644
index 0000000..35021d8
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/DoWhileRatherThanWhileFixCore.java
@@ -0,0 +1,387 @@
+/*******************************************************************************
+ * Copyright (c) 2021 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.corext.fix;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+
+import org.eclipse.text.edits.TextEditGroup;
+
+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.AbstractTypeDeclaration;
+import org.eclipse.jdt.core.dom.Assignment;
+import org.eclipse.jdt.core.dom.Block;
+import org.eclipse.jdt.core.dom.CatchClause;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.ConditionalExpression;
+import org.eclipse.jdt.core.dom.CreationReference;
+import org.eclipse.jdt.core.dom.DoStatement;
+import org.eclipse.jdt.core.dom.EnhancedForStatement;
+import org.eclipse.jdt.core.dom.Expression;
+import org.eclipse.jdt.core.dom.ForStatement;
+import org.eclipse.jdt.core.dom.IBinding;
+import org.eclipse.jdt.core.dom.IVariableBinding;
+import org.eclipse.jdt.core.dom.IfStatement;
+import org.eclipse.jdt.core.dom.InfixExpression;
+import org.eclipse.jdt.core.dom.LambdaExpression;
+import org.eclipse.jdt.core.dom.MethodReference;
+import org.eclipse.jdt.core.dom.ParenthesizedExpression;
+import org.eclipse.jdt.core.dom.PostfixExpression;
+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.SuperMethodReference;
+import org.eclipse.jdt.core.dom.SwitchStatement;
+import org.eclipse.jdt.core.dom.TryStatement;
+import org.eclipse.jdt.core.dom.TypeMethodReference;
+import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
+import org.eclipse.jdt.core.dom.WhileStatement;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+import org.eclipse.jdt.core.dom.rewrite.TargetSourceRangeComputer;
+import org.eclipse.jdt.core.manipulation.ICleanUpFixCore;
+
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
+import org.eclipse.jdt.internal.corext.dom.VarDefinitionsUsesVisitor;
+import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
+
+import org.eclipse.jdt.internal.ui.fix.MultiFixMessages;
+
+public class DoWhileRatherThanWhileFixCore extends CompilationUnitRewriteOperationsFixCore {
+	public static final class DoWhileRatherThanWhileFinder extends ASTVisitor {
+		private List<CompilationUnitRewriteOperation> fResult;
+
+		public DoWhileRatherThanWhileFinder(List<CompilationUnitRewriteOperation> ops) {
+			fResult= ops;
+		}
+
+		@Override
+		public boolean visit(final WhileStatement visited) {
+			if (ASTNodes.isPassiveWithoutFallingThrough(visited.getExpression()) && Boolean.TRUE.equals(peremptoryValue(visited, visited.getExpression()))) {
+				fResult.add(new DoWhileRatherThanWhileOperation(visited));
+				return false;
+			}
+
+			return true;
+		}
+
+		private Object peremptoryValue(final ASTNode visited, final Expression condition) {
+			Object constantCondition= condition.resolveConstantExpressionValue();
+
+			if (constantCondition != null) {
+				return constantCondition;
+			}
+
+			Long integerLiteral= ASTNodes.getIntegerLiteral(condition);
+
+			if (integerLiteral != null) {
+				return integerLiteral;
+			}
+
+			SimpleName variable= ASTNodes.as(condition, SimpleName.class);
+
+			if (variable != null
+					&& variable.resolveBinding() != null
+					&& variable.resolveBinding().getKind() == IBinding.VARIABLE) {
+				List<ASTNode> precedingStatements= getPrecedingCode(visited);
+
+				Collections.reverse(precedingStatements);
+
+				for (ASTNode precedingStatement : precedingStatements) {
+					if (isConditionalCode(precedingStatement)) {
+						return null;
+					}
+
+					VarDefinitionsUsesVisitor visitor= new VarDefinitionsUsesVisitor((IVariableBinding) variable.resolveBinding(), precedingStatement, true);
+
+					if (visitor.getWrites().size() > 1) {
+						return null;
+					}
+
+					for (SimpleName astNode : visitor.getReads()) {
+						ASTNode parent= astNode.getParent();
+
+						while (parent instanceof ParenthesizedExpression) {
+							parent= astNode.getParent();
+						}
+
+						if (parent instanceof PrefixExpression && ASTNodes.hasOperator((PrefixExpression) parent, PrefixExpression.Operator.INCREMENT, PrefixExpression.Operator.DECREMENT)
+								|| parent instanceof PostfixExpression && ASTNodes.hasOperator((PostfixExpression) parent, PostfixExpression.Operator.INCREMENT, PostfixExpression.Operator.DECREMENT)) {
+							return null;
+						}
+					}
+
+					if (!visitor.getWrites().isEmpty()) {
+						SimpleName write= visitor.getWrites().get(0);
+						ASTNode parent= write;
+
+						while (parent != precedingStatement) {
+							if (isConditionalCode(parent)) {
+								return null;
+							}
+
+							parent= parent.getParent();
+						}
+
+						switch (write.getParent().getNodeType()) {
+						case ASTNode.ASSIGNMENT:
+							Assignment assignment= (Assignment) write.getParent();
+
+							if (ASTNodes.hasOperator(assignment, Assignment.Operator.ASSIGN)) {
+								return peremptoryValue(precedingStatement, assignment.getRightHandSide());
+							}
+
+							break;
+
+						case ASTNode.VARIABLE_DECLARATION_FRAGMENT:
+							VariableDeclarationFragment fragment= (VariableDeclarationFragment) write.getParent();
+
+							if (fragment.getInitializer() != null) {
+								return peremptoryValue(precedingStatement, fragment.getInitializer());
+							}
+
+							break;
+
+						case ASTNode.SINGLE_VARIABLE_DECLARATION:
+							SingleVariableDeclaration singleVariableDeclaration= (SingleVariableDeclaration) write.getParent();
+
+							if (singleVariableDeclaration.getInitializer() != null) {
+								return peremptoryValue(precedingStatement, singleVariableDeclaration.getInitializer());
+							}
+
+							break;
+
+						default:
+							break;
+						}
+
+						return null;
+					}
+				}
+
+				return null;
+			}
+
+			InfixExpression infixExpression= ASTNodes.as(condition, InfixExpression.class);
+
+			if (infixExpression != null) {
+				if (!infixExpression.hasExtendedOperands()
+						&& ASTNodes.hasOperator(infixExpression, InfixExpression.Operator.EQUALS,
+								InfixExpression.Operator.NOT_EQUALS,
+								InfixExpression.Operator.GREATER,
+								InfixExpression.Operator.GREATER_EQUALS,
+								InfixExpression.Operator.LESS,
+								InfixExpression.Operator.LESS_EQUALS)) {
+					Object leftOperand= peremptoryValue(visited, infixExpression.getLeftOperand());
+					Object rightOperand= peremptoryValue(visited, infixExpression.getRightOperand());
+
+					if (leftOperand instanceof Number && rightOperand instanceof Number) {
+						Number leftNumber= (Number) leftOperand;
+						Number rightNumber= (Number) rightOperand;
+
+						if (ASTNodes.hasOperator(infixExpression, InfixExpression.Operator.EQUALS)) {
+							return leftNumber.longValue() == rightNumber.longValue();
+						}
+
+						if (ASTNodes.hasOperator(infixExpression, InfixExpression.Operator.NOT_EQUALS)) {
+							return leftNumber.longValue() != rightNumber.longValue();
+						}
+
+						if (ASTNodes.hasOperator(infixExpression, InfixExpression.Operator.GREATER)) {
+							return leftNumber.longValue() > rightNumber.longValue();
+						}
+
+						if (ASTNodes.hasOperator(infixExpression, InfixExpression.Operator.GREATER_EQUALS)) {
+							return leftNumber.longValue() >= rightNumber.longValue();
+						}
+
+						if (ASTNodes.hasOperator(infixExpression, InfixExpression.Operator.LESS)) {
+							return leftNumber.longValue() < rightNumber.longValue();
+						}
+
+						if (ASTNodes.hasOperator(infixExpression, InfixExpression.Operator.LESS_EQUALS)) {
+							return leftNumber.longValue() <= rightNumber.longValue();
+						}
+					}
+
+					return null;
+				}
+
+				if (ASTNodes.hasOperator(infixExpression, InfixExpression.Operator.CONDITIONAL_AND,
+								InfixExpression.Operator.AND)) {
+					for (Expression operand : ASTNodes.allOperands(infixExpression)) {
+						final Object hasAlwaysValue= peremptoryValue(visited, operand);
+
+						if (!Boolean.TRUE.equals(hasAlwaysValue)) {
+							return hasAlwaysValue;
+						}
+					}
+
+					return Boolean.TRUE;
+				}
+
+				if (ASTNodes.hasOperator(infixExpression, InfixExpression.Operator.CONDITIONAL_OR,
+								InfixExpression.Operator.OR)) {
+					for (Expression operand : ASTNodes.allOperands(infixExpression)) {
+						final Object hasAlwaysValue= peremptoryValue(visited, operand);
+
+						if (!Boolean.FALSE.equals(hasAlwaysValue)) {
+							return hasAlwaysValue;
+						}
+					}
+
+					return Boolean.FALSE;
+				}
+			}
+
+			return Boolean.FALSE;
+		}
+
+		private boolean isConditionalCode(final ASTNode expression) {
+			return expression == null
+					|| expression instanceof IfStatement
+					|| expression instanceof ConditionalExpression
+					|| expression instanceof EnhancedForStatement
+					|| expression instanceof SwitchStatement
+					|| expression instanceof WhileStatement
+					|| expression instanceof ForStatement
+					|| expression instanceof DoStatement
+					|| expression instanceof AbstractTypeDeclaration
+					|| expression instanceof LambdaExpression
+					|| expression instanceof MethodReference
+					|| expression instanceof SuperMethodReference
+					|| expression instanceof CreationReference
+					|| expression instanceof TypeMethodReference
+					|| expression instanceof SuperMethodReference;
+		}
+
+		@SuppressWarnings({ "deprecation" })
+		private List<ASTNode> getPrecedingCode(final ASTNode node) {
+			Statement statement= null;
+
+			if (node instanceof Statement) {
+				statement= (Statement) node;
+			} else {
+				statement= ASTNodes.getTypedAncestor(node, Statement.class);
+			}
+
+			if (statement == null) {
+				return new ArrayList<>();
+			}
+
+			List<ASTNode> precedingStatements= new ArrayList<>(ASTNodes.getPreviousSiblings(statement));
+			ASTNode parent= statement.getParent();
+
+			if (parent instanceof Block) {
+				precedingStatements.addAll(0, getPrecedingCode(parent));
+				return precedingStatements;
+			}
+
+			if (parent instanceof IfStatement) {
+				precedingStatements.add(0, ((IfStatement) parent).getExpression());
+				precedingStatements.addAll(0, getPrecedingCode(parent));
+			}
+
+			if (parent instanceof CatchClause) {
+				TryStatement tryStatement= (TryStatement) parent.getParent();
+				precedingStatements.addAll(0, ASTNodes.asList(tryStatement.getBody()));
+
+				if (statement.getParent().getLocationInParent() != TryStatement.RESOURCES_PROPERTY) {
+					precedingStatements.addAll(0, tryStatement.resources());
+				}
+
+				precedingStatements.addAll(0, getPrecedingCode(tryStatement));
+			}
+
+			if (parent instanceof TryStatement) {
+				if (statement.getLocationInParent() == TryStatement.FINALLY_PROPERTY) {
+					return precedingStatements;
+				}
+
+				if (statement.getLocationInParent() != TryStatement.RESOURCES2_PROPERTY) {
+					precedingStatements.addAll(0, ((TryStatement) parent).resources());
+				}
+
+				precedingStatements.addAll(0, getPrecedingCode(parent));
+			}
+
+			return precedingStatements;
+		}
+	}
+
+	public static class DoWhileRatherThanWhileOperation extends CompilationUnitRewriteOperation {
+		private final WhileStatement visited;
+
+		public DoWhileRatherThanWhileOperation(final WhileStatement visited) {
+			this.visited= visited;
+		}
+
+		@Override
+		public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModelCore linkedModel) throws CoreException {
+			ASTRewrite rewrite= cuRewrite.getASTRewrite();
+			AST ast= cuRewrite.getRoot().getAST();
+			TextEditGroup group= createTextEditGroup(MultiFixMessages.DoWhileRatherThanWhileCleanUp_description, cuRewrite);
+			rewrite.setTargetSourceRangeComputer(new TargetSourceRangeComputer() {
+				@Override
+				public SourceRange computeSourceRange(final ASTNode nodeWithComment) {
+					if (Boolean.TRUE.equals(nodeWithComment.getProperty(ASTNodes.UNTOUCH_COMMENT))) {
+						return new SourceRange(nodeWithComment.getStartPosition(), nodeWithComment.getLength());
+					}
+
+					return super.computeSourceRange(nodeWithComment);
+				}
+			});
+
+			DoStatement newDoStatement= ast.newDoStatement();
+			newDoStatement.setExpression(ASTNodes.createMoveTarget(rewrite, ASTNodes.getUnparenthesedExpression(visited.getExpression())));
+			newDoStatement.setBody(ASTNodes.createMoveTarget(rewrite, visited.getBody()));
+			ASTNodes.replaceButKeepComment(rewrite, visited, newDoStatement, group);
+		}
+	}
+
+	public static ICleanUpFixCore createCleanUp(final CompilationUnit compilationUnit) {
+		List<CompilationUnitRewriteOperation> operations= new ArrayList<>();
+		DoWhileRatherThanWhileFinder finder= new DoWhileRatherThanWhileFinder(operations);
+		compilationUnit.accept(finder);
+
+		if (operations.isEmpty()) {
+			return null;
+		}
+
+		CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] ops= operations.toArray(new CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[0]);
+		return new DoWhileRatherThanWhileFixCore(FixMessages.DoWhileRatherThanWhileFix_description, compilationUnit, ops);
+	}
+
+	public static ICleanUpFixCore createCleanUp(final CompilationUnit compilationUnit, final WhileStatement whileStatement) {
+		List<CompilationUnitRewriteOperation> operations= new ArrayList<>();
+		DoWhileRatherThanWhileFinder finder= new DoWhileRatherThanWhileFinder(operations);
+		whileStatement.accept(finder);
+
+		if (operations.isEmpty()) {
+			return null;
+		}
+
+		CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] ops= operations.toArray(new CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[0]);
+		return new DoWhileRatherThanWhileFixCore(FixMessages.DoWhileRatherThanWhileFix_description, compilationUnit, ops);
+	}
+
+	protected DoWhileRatherThanWhileFixCore(final String name, final CompilationUnit compilationUnit, final CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] fixRewriteOperations) {
+		super(name, compilationUnit, fixRewriteOperations);
+	}
+}
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
index a7ac432..f38e2be 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.java
@@ -136,6 +136,7 @@
 	public static String VariableDeclarationFix_changeModifierOfUnknownToFinal_description;
 	public static String VariableDeclarationFix_ChangeMidifiersToFinalWherPossible_description;
 
+	public static String DoWhileRatherThanWhileFix_description;
 	public static String NullAnnotationsFix_add_annotation_change_name;
 	public static String NullAnnotationsRewriteOperations_change_method_parameter_nullness;
 	public static String NullAnnotationsRewriteOperations_change_target_method_parameter_nullness;
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
index fc2ce75..46c0308 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/FixMessages.properties
@@ -165,3 +165,4 @@
 TypeAnnotationFix_move=Move type annotation
 TypeAnnotationFix_remove=Remove type annotation
 ConstantsCleanUpFix_refactor=Replace system property with Java method
+DoWhileRatherThanWhileFix_description=Convert while to do/while
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AssistQuickFixTest.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AssistQuickFixTest.java
index 572fd10..27ced63 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AssistQuickFixTest.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AssistQuickFixTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2020 IBM Corporation and others.
+ * Copyright (c) 2000, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -2516,7 +2516,7 @@
 		AssistContext context= getCorrectionContext(cu, buf.toString().indexOf(str) + str.length(), 0);
 		List<IJavaCompletionProposal> proposals= collectAssists(context, false);
 
-		assertNumberOfProposals(proposals, 1);
+		assertNumberOfProposals(proposals, 2);
 		assertCorrectLabels(proposals);
 
 		CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0);
@@ -6191,7 +6191,7 @@
 		AssistContext context= getCorrectionContext(cu, buf.toString().indexOf(str), 0);
 		List<IJavaCompletionProposal> proposals= collectAssists(context, false);
 
-		assertNumberOfProposals(proposals, 1);
+		assertNumberOfProposals(proposals, 2);
 		assertCorrectLabels(proposals);
 
 		buf= new StringBuilder();
@@ -6232,7 +6232,7 @@
 		AssistContext context= getCorrectionContext(cu, buf.toString().indexOf(str), 0);
 		List<IJavaCompletionProposal> proposals= collectAssists(context, false);
 
-		assertNumberOfProposals(proposals, 2);
+		assertNumberOfProposals(proposals, 3);
 		assertCorrectLabels(proposals);
 
 		buf= new StringBuilder();
@@ -10515,4 +10515,272 @@
 		assertNumberOfProposals(proposals, 2);
 		assertProposalExists(proposals, Messages.format(CorrectionMessages.QuickAssistProcessor_create_new_impl, "E.java"));
 	}
+
+	@Test
+	public void testDoWhileRatherThanWhile1() throws Exception {
+
+		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+		StringBuilder buf= new StringBuilder();
+		buf.append("package test1;\n");
+		buf.append("public class E {\n");
+		buf.append("    public void replaceWhileByDoWhile(int i) {\n");
+		buf.append("        // Keep this comment\n");
+		buf.append("        while (true) {\n");
+		buf.append("            // Keep this comment too\n");
+		buf.append("            if (i > 100) {\n");
+		buf.append("                return;\n");
+		buf.append("            }\n");
+		buf.append("            i *= 2;\n");
+		buf.append("        }\n");
+		buf.append("    }\n");
+		buf.append("}\n");
+		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+		String str= "while (";
+		AssistContext context= getCorrectionContext(cu, buf.toString().indexOf(str), 0);
+		List<IJavaCompletionProposal> proposals= collectAssists(context, false);
+
+		assertCorrectLabels(proposals);
+
+		buf= new StringBuilder();
+		buf.append("package test1;\n");
+		buf.append("public class E {\n");
+		buf.append("    public void replaceWhileByDoWhile(int i) {\n");
+		buf.append("        // Keep this comment\n");
+		buf.append("        do {\n");
+		buf.append("            // Keep this comment too\n");
+		buf.append("            if (i > 100) {\n");
+		buf.append("                return;\n");
+		buf.append("            }\n");
+		buf.append("            i *= 2;\n");
+		buf.append("        } while (true);\n");
+		buf.append("    }\n");
+		buf.append("}\n");
+		String expected1= buf.toString();
+
+		assertExpectedExistInProposals(proposals, new String[] {expected1});
+	}
+
+	@Test
+	public void testDoWhileRatherThanWhile2() throws Exception {
+
+		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+		StringBuilder buf= new StringBuilder();
+		buf.append("package test1;\n");
+		buf.append("public class E {\n");
+		buf.append("    public void replaceWithInitedBoolean(int i) {\n");
+		buf.append("        boolean isInitedToTrue= true;\n");
+		buf.append("\n");
+		buf.append("        // Keep this comment\n");
+		buf.append("        while (isInitedToTrue) {\n");
+		buf.append("           ); Keep this comment too\n");
+		buf.append("            if (i > 100) {\n");
+		buf.append("                isInitedToTrue= false;\n");
+		buf.append("            }\n");
+		buf.append("            i *= 2;\n");
+		buf.append("        }\n");
+		buf.append("    }\n");
+		buf.append("}\n");
+		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+		String str= "while (";
+		AssistContext context= getCorrectionContext(cu, buf.toString().indexOf(str), 0);
+		List<IJavaCompletionProposal> proposals= collectAssists(context, false);
+
+		assertCorrectLabels(proposals);
+
+		buf= new StringBuilder();
+		buf.append("package test1;\n");
+		buf.append("public class E {\n");
+		buf.append("    public void replaceWithInitedBoolean(int i) {\n");
+		buf.append("        boolean isInitedToTrue= true;\n");
+		buf.append("\n");
+		buf.append("        // Keep this comment\n");
+		buf.append("        do {\n");
+		buf.append("           ); Keep this comment too\n");
+		buf.append("            if (i > 100) {\n");
+		buf.append("                isInitedToTrue= false;\n");
+		buf.append("            }\n");
+		buf.append("            i *= 2;\n");
+		buf.append("        } while (isInitedToTrue);\n");
+		buf.append("    }\n");
+		buf.append("}\n");
+		String expected1= buf.toString();
+
+		assertExpectedExistInProposals(proposals, new String[] {expected1});
+	}
+
+	@Test
+	public void testDoWhileRatherThanWhile3() throws Exception {
+
+		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+		StringBuilder buf= new StringBuilder();
+		buf.append("package test1;\n");
+		buf.append("public class E {\n");
+		buf.append("    public void replaceWithInitedBooleanAndInteger(int i) {\n");
+		buf.append("        int j= 1000;\n");
+		buf.append("        boolean isInitedToTrue= true;\n");
+		buf.append("\n");
+		buf.append("        // Keep this comment\n");
+		buf.append("        while (isInitedToTrue && j > 0) {\n");
+		buf.append("            // Keep this comment too\n");
+		buf.append("            if (i > 100) {\n");
+		buf.append("                isInitedToTrue= false;\n");
+		buf.append("            }\n");
+		buf.append("            i *= 2;\n");
+		buf.append("            j--;\n");
+		buf.append("        }\n");
+		buf.append("    }\n");
+		buf.append("}\n");
+		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+		String str= "while (";
+		AssistContext context= getCorrectionContext(cu, buf.toString().indexOf(str), 0);
+		List<IJavaCompletionProposal> proposals= collectAssists(context, false);
+
+		assertCorrectLabels(proposals);
+
+		buf= new StringBuilder();
+		buf.append("package test1;\n");
+		buf.append("public class E {\n");
+		buf.append("    public void replaceWithInitedBooleanAndInteger(int i) {\n");
+		buf.append("        int j= 1000;\n");
+		buf.append("        boolean isInitedToTrue= true;\n");
+		buf.append("\n");
+		buf.append("        // Keep this comment\n");
+		buf.append("        do {\n");
+		buf.append("            // Keep this comment too\n");
+		buf.append("            if (i > 100) {\n");
+		buf.append("                isInitedToTrue= false;\n");
+		buf.append("            }\n");
+		buf.append("            i *= 2;\n");
+		buf.append("            j--;\n");
+		buf.append("        } while (isInitedToTrue && j > 0);\n");
+		buf.append("    }\n");
+		buf.append("}\n");
+		String expected1= buf.toString();
+
+		assertExpectedExistInProposals(proposals, new String[] {expected1});
+	}
+
+	@Test
+	public void testDoWhileRatherThanWhile4() throws Exception {
+
+		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+		StringBuilder buf= new StringBuilder();
+		buf.append("package test1;\n");
+		buf.append("public class E {\n");
+		buf.append("    public void replaceWithReassignment(int i) {\n");
+		buf.append("        int j= 1000;\n");
+		buf.append("        int k= -1000;\n");
+		buf.append("        boolean isInitedToTrue= false;\n");
+		buf.append("        isInitedToTrue= k < 0;\n");
+		buf.append("\n");
+		buf.append("        // Keep this comment\n");
+		buf.append("        while (isInitedToTrue && j > 0) {\n");
+		buf.append("            // Keep this comment too\n");
+		buf.append("            if (i > 100) {\n");
+		buf.append("                isInitedToTrue= false;\n");
+		buf.append("            }\n");
+		buf.append("            i *= 2;\n");
+		buf.append("            j--;\n");
+		buf.append("        }\n");
+		buf.append("    }\n");
+		buf.append("}\n");
+		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+		String str= "while (";
+		AssistContext context= getCorrectionContext(cu, buf.toString().indexOf(str), 0);
+		List<IJavaCompletionProposal> proposals= collectAssists(context, false);
+
+		assertCorrectLabels(proposals);
+
+		buf= new StringBuilder();
+		buf.append("package test1;\n");
+		buf.append("public class E {\n");
+		buf.append("    public void replaceWithReassignment(int i) {\n");
+		buf.append("        int j= 1000;\n");
+		buf.append("        int k= -1000;\n");
+		buf.append("        boolean isInitedToTrue= false;\n");
+		buf.append("        isInitedToTrue= k < 0;\n");
+		buf.append("\n");
+		buf.append("        // Keep this comment\n");
+		buf.append("        do {\n");
+		buf.append("            // Keep this comment too\n");
+		buf.append("            if (i > 100) {\n");
+		buf.append("                isInitedToTrue= false;\n");
+		buf.append("            }\n");
+		buf.append("            i *= 2;\n");
+		buf.append("            j--;\n");
+		buf.append("        } while (isInitedToTrue && j > 0);\n");
+		buf.append("    }\n");
+		buf.append("}\n");
+		String expected1= buf.toString();
+
+		assertExpectedExistInProposals(proposals, new String[] {expected1});
+	}
+
+	@Test
+	public void testDoWhileRatherThanWhile5() throws Exception {
+
+		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
+		StringBuilder buf= new StringBuilder();
+		buf.append("package test1;\n");
+		buf.append("public class E {\n");
+		buf.append("    public void replaceWithInnerWhile(int i) {\n");
+		buf.append("        int j= 1000;\n");
+		buf.append("        int k= -1000;\n");
+		buf.append("        boolean isInitedToTrue= false;\n");
+		buf.append("        isInitedToTrue= k < 0;\n");
+		buf.append("\n");
+		buf.append("        // Keep this comment\n");
+		buf.append("        while (isInitedToTrue && j > 0) {\n");
+		buf.append("            // Keep this comment too\n");
+		buf.append("            while (i < 50 || isInitedToTrue) {\n");
+		buf.append("                ++i;\n");
+		buf.append("            }\n");
+		buf.append("            if (i > 100) {\n");
+		buf.append("                isInitedToTrue= false;\n");
+		buf.append("            }\n");
+		buf.append("            i *= 2;\n");
+		buf.append("            j--;\n");
+		buf.append("        }\n");
+		buf.append("    }\n");
+		buf.append("}\n");
+		ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null);
+
+		String str= "while (is";
+		AssistContext context= getCorrectionContext(cu, buf.toString().indexOf(str), 0);
+		List<IJavaCompletionProposal> proposals= collectAssists(context, false);
+
+		assertCorrectLabels(proposals);
+
+		buf= new StringBuilder();
+		buf.append("package test1;\n");
+		buf.append("public class E {\n");
+		buf.append("    public void replaceWithInnerWhile(int i) {\n");
+		buf.append("        int j= 1000;\n");
+		buf.append("        int k= -1000;\n");
+		buf.append("        boolean isInitedToTrue= false;\n");
+		buf.append("        isInitedToTrue= k < 0;\n");
+		buf.append("\n");
+		buf.append("        // Keep this comment\n");
+		buf.append("        do {\n");
+		buf.append("            // Keep this comment too\n");
+		buf.append("            while (i < 50 || isInitedToTrue) {\n");
+		buf.append("                ++i;\n");
+		buf.append("            }\n");
+		buf.append("            if (i > 100) {\n");
+		buf.append("                isInitedToTrue= false;\n");
+		buf.append("            }\n");
+		buf.append("            i *= 2;\n");
+		buf.append("            j--;\n");
+		buf.append("        } while (isInitedToTrue && j > 0);\n");
+		buf.append("    }\n");
+		buf.append("}\n");
+		String expected1= buf.toString();
+
+		assertExpectedExistInProposals(proposals, new String[] {expected1});
+	}
+
 }
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 218b58a..ae8e08f 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
@@ -77,6 +77,7 @@
 		options.setOption(PREFER_BOOLEAN_LITERAL, CleanUpOptions.FALSE);
 		options.setOption(SINGLE_USED_FIELD, CleanUpOptions.FALSE);
 		options.setOption(BREAK_LOOP, CleanUpOptions.FALSE);
+		options.setOption(DO_WHILE_RATHER_THAN_WHILE, CleanUpOptions.TRUE);
 		options.setOption(STATIC_INNER_CLASS, CleanUpOptions.FALSE);
 		options.setOption(STRINGBUILDER, CleanUpOptions.FALSE);
 		options.setOption(STRINGBUFFER_TO_STRINGBUILDER, CleanUpOptions.FALSE);
@@ -253,6 +254,7 @@
 		options.setOption(PREFER_BOOLEAN_LITERAL, CleanUpOptions.FALSE);
 		options.setOption(SINGLE_USED_FIELD, CleanUpOptions.FALSE);
 		options.setOption(BREAK_LOOP, CleanUpOptions.FALSE);
+		options.setOption(DO_WHILE_RATHER_THAN_WHILE, CleanUpOptions.FALSE);
 		options.setOption(STATIC_INNER_CLASS, CleanUpOptions.FALSE);
 		options.setOption(STRINGBUILDER, CleanUpOptions.FALSE);
 		options.setOption(STRINGBUFFER_TO_STRINGBUILDER, CleanUpOptions.FALSE);
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/DoWhileRatherThanWhileFix.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/DoWhileRatherThanWhileFix.java
new file mode 100644
index 0000000..48c18d5
--- /dev/null
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/DoWhileRatherThanWhileFix.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Red Hat Inc. 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:
+ *     Red Hat Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.corext.fix;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.WhileStatement;
+
+import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
+
+public class DoWhileRatherThanWhileFix extends CompilationUnitRewriteOperationsFix {
+
+	public static ICleanUpFix createCleanUp(CompilationUnit compilationUnit) {
+		List<CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation> operations= new ArrayList<>();
+		DoWhileRatherThanWhileFixCore.DoWhileRatherThanWhileFinder finder= new DoWhileRatherThanWhileFixCore.DoWhileRatherThanWhileFinder(operations);
+		compilationUnit.accept(finder);
+		if (operations.isEmpty())
+			return null;
+
+		CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] ops= operations.toArray(new CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[operations.size()]);
+		return new DoWhileRatherThanWhileFix(FixMessages.DoWhileRatherThanWhileFix_description, compilationUnit, ops);
+	}
+
+	public static DoWhileRatherThanWhileFix createDoWhileFix(WhileStatement switchStatement) {
+		CompilationUnit root= (CompilationUnit) switchStatement.getRoot();
+		List<CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation> operations= new ArrayList<>();
+		DoWhileRatherThanWhileFixCore.DoWhileRatherThanWhileFinder finder= new DoWhileRatherThanWhileFixCore.DoWhileRatherThanWhileFinder(operations);
+		switchStatement.accept(finder);
+		if (operations.isEmpty())
+			return null;
+		return new DoWhileRatherThanWhileFix(FixMessages.DoWhileRatherThanWhileFix_description, root, new CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] { operations.get(0) });
+	}
+
+	protected DoWhileRatherThanWhileFix(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] fixRewriteOperations) {
+		super(name, compilationUnit, fixRewriteOperations);
+	}
+
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/DoWhileRatherThanWhileCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/DoWhileRatherThanWhileCleanUp.java
new file mode 100644
index 0000000..d5c7fcb
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/DoWhileRatherThanWhileCleanUp.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2021 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.Map;
+
+import org.eclipse.core.runtime.CoreException;
+
+import org.eclipse.jdt.core.manipulation.ICleanUpFixCore;
+
+import org.eclipse.jdt.ui.cleanup.CleanUpContext;
+import org.eclipse.jdt.ui.cleanup.CleanUpOptions;
+import org.eclipse.jdt.ui.cleanup.CleanUpRequirements;
+import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
+
+/**
+ * A fix that replaces <code>while</code> by <code>do</code>/<code>while</code>:
+ * <ul>
+ * <li>The first evaluation must be always true.</li>
+ * <li>The first evaluation must be passive.</li>
+ * </ul>
+ */
+public class DoWhileRatherThanWhileCleanUp extends AbstractCleanUp {
+	private DoWhileRatherThanWhileCleanUpCore coreCleanUp= new DoWhileRatherThanWhileCleanUpCore();
+
+	public DoWhileRatherThanWhileCleanUp(final Map<String, String> options) {
+		setOptions(options);
+	}
+
+	public DoWhileRatherThanWhileCleanUp() {
+	}
+
+	@Override
+	public void setOptions(final CleanUpOptions options) {
+		coreCleanUp.setOptions(options);
+	}
+
+	@Override
+	public CleanUpRequirements getRequirements() {
+		return new CleanUpRequirements(coreCleanUp.getRequirementsCore());
+	}
+
+	@Override
+	public ICleanUpFix createFix(final CleanUpContext context) throws CoreException {
+		ICleanUpFixCore fixCore= coreCleanUp.createFixCore(context);
+		return fixCore != null ? new CleanUpFixWrapper(fixCore) : null;
+	}
+
+	@Override
+	public String[] getStepDescriptions() {
+		return coreCleanUp.getStepDescriptions();
+	}
+
+	@Override
+	public String getPreview() {
+		return coreCleanUp.getPreview();
+	}
+}
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 456b47b..ec6a29e 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
@@ -84,6 +84,7 @@
 
 	public static String OptimizationTabPage_CheckboxName_SingleUsedField;
 	public static String OptimizationTabPage_CheckboxName_BreakLoop;
+	public static String OptimizationTabPage_CheckboxName_DoWhileRatherThanWhile;
 	public static String OptimizationTabPage_CheckboxName_StaticInnerClass;
 	public static String OptimizationTabPage_CheckboxName_StringBuilder;
 	public static String OptimizationTabPage_CheckboxName_PlainReplacement;
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 666abf6..03f3406 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
@@ -63,6 +63,7 @@
 OptimizationTabPage_GroupName_Optimization=Optimization
 OptimizationTabPage_CheckboxName_SingleUsedField=Convert fields into local variables if the use is only local
 OptimizationTabPage_CheckboxName_BreakLoop=Exit &loop earlier
+OptimizationTabPage_CheckboxName_DoWhileRatherThanWhile=Do/while rather than while
 OptimizationTabPage_CheckboxName_StaticInnerClass=Make inner classes static where possible
 OptimizationTabPage_CheckboxName_StringBuilder=Replace &String concatenation by StringBuilder
 OptimizationTabPage_CheckboxName_PlainReplacement=Use String.replace() instead of String.replaceAll() when possible
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/IProposalRelevance.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/IProposalRelevance.java
index 795039a..35e9048 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/IProposalRelevance.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/IProposalRelevance.java
@@ -291,6 +291,7 @@
 	int INSERT_INFERRED_TYPE_ARGUMENTS_ERROR= 1;
 	int RETURN_ALLOCATED_OBJECT_VOID= 1;
 	int CONVERT_TO_IF_RETURN= 1;
+	int DO_WHILE_RATHER_THAN_WHILE= 1;
 
 	int CONVERT_TO_MESSAGE_FORMAT= 0;
 	int COPY_ANNOTATION_JAR= 0;
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickAssistProcessor.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickAssistProcessor.java
index 0347d55..b2da09c 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickAssistProcessor.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickAssistProcessor.java
@@ -165,6 +165,7 @@
 import org.eclipse.jdt.internal.corext.fix.CleanUpConstants;
 import org.eclipse.jdt.internal.corext.fix.ControlStatementsFix;
 import org.eclipse.jdt.internal.corext.fix.ConvertLoopFix;
+import org.eclipse.jdt.internal.corext.fix.DoWhileRatherThanWhileFix;
 import org.eclipse.jdt.internal.corext.fix.IProposableFix;
 import org.eclipse.jdt.internal.corext.fix.LambdaExpressionsFix;
 import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel;
@@ -201,6 +202,7 @@
 import org.eclipse.jdt.internal.ui.JavaPluginImages;
 import org.eclipse.jdt.internal.ui.fix.ControlStatementsCleanUp;
 import org.eclipse.jdt.internal.ui.fix.ConvertLoopCleanUp;
+import org.eclipse.jdt.internal.ui.fix.DoWhileRatherThanWhileCleanUp;
 import org.eclipse.jdt.internal.ui.fix.LambdaExpressionsCleanUp;
 import org.eclipse.jdt.internal.ui.fix.SwitchExpressionsCleanUp;
 import org.eclipse.jdt.internal.ui.fix.TypeParametersCleanUp;
@@ -320,6 +322,7 @@
 					|| getJUnitTestCaseProposal(context, coveringNode, null)
 					|| getNewImplementationProposal(context, coveringNode, null)
 					|| getAddStaticImportProposals(context, coveringNode, null)
+					|| getDoWhileRatherThanWhileProposal(context, coveringNode, null)
 					|| getSplitSwitchLabelProposal(context, coveringNode, null);
 		}
 		return false;
@@ -389,6 +392,7 @@
 				getConvertResolvedTypeToVarTypeProposal(context, coveringNode, resultingCollections);
 				getAddStaticImportProposals(context, coveringNode, resultingCollections);
 				getConvertToSwitchExpressionProposals(context, coveringNode, resultingCollections);
+				getDoWhileRatherThanWhileProposal(context, coveringNode, resultingCollections);
 			}
 			return resultingCollections.toArray(new IJavaCompletionProposal[resultingCollections.size()]);
 		}
@@ -4359,6 +4363,32 @@
 		return true;
 	}
 
+	private static boolean getDoWhileRatherThanWhileProposal(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections) {
+		WhileStatement whileStatement= null;
+		if (node instanceof WhileStatement) {
+			whileStatement= (WhileStatement) node;
+		} else if (node.getParent() instanceof WhileStatement) {
+			whileStatement= (WhileStatement) node.getParent();
+		}
+		if (whileStatement == null)
+			return false;
+
+		if (resultingCollections == null)
+			return true;
+
+		IProposableFix fix= DoWhileRatherThanWhileFix.createDoWhileFix(whileStatement);
+		if (fix == null)
+			return false;
+
+		Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
+		Map<String, String> options= new HashMap<>();
+		options.put(CleanUpConstants.DO_WHILE_RATHER_THAN_WHILE, CleanUpOptions.TRUE);
+		ICleanUp cleanUp= new DoWhileRatherThanWhileCleanUp(options);
+		FixCorrectionProposal proposal= new FixCorrectionProposal(fix, cleanUp, IProposalRelevance.DO_WHILE_RATHER_THAN_WHILE, image, context);
+		resultingCollections.add(proposal);
+		return true;
+	}
+
 	private static boolean getConvertForLoopProposal(IInvocationContext context, ASTNode node, Collection<ICommandAccess> resultingCollections) {
 		ForStatement forStatement= getEnclosingForStatementHeader(node);
 		if (forStatement == null)