Bug 568050 - [AutoRefactor immigration #36/141] [cleanup & saveaction]
Break loop

Change-Id: I468c93096ae66230d97e6d96ef013e6395e42097
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 11e3067..55959ff 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
@@ -121,6 +121,7 @@
 	public static String NullAnnotationsCleanUp_add_nonnullbydefault_annotation;
 	public static String NullAnnotationsCleanUp_remove_redundant_nullness_annotation;
 
+	public static String BreakLoopCleanUp_description;
 	public static String CodeStyleCleanUp_LazyLogical_description;
 	public static String PrimitiveSerializationCleanUp_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 1837874..0623f6c 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
@@ -103,6 +103,7 @@
 NullAnnotationsCleanUp_add_nonnullbydefault_annotation=Add missing @NonNullByDefault annotation
 NullAnnotationsCleanUp_remove_redundant_nullness_annotation=Remove redundant nullness annotation
 
+BreakLoopCleanUp_description=Exit loop earlier
 CodeStyleCleanUp_LazyLogical_description= Use lazy logical operator (&& and ||)
 PrimitiveSerializationCleanUp_description=Primitive serialization
 
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 29aaa9e..347d8e9 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
@@ -31,6 +31,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.eclipse.core.runtime.Assert;
 import org.eclipse.core.runtime.CoreException;
@@ -804,6 +805,24 @@
 	}
 
 	/**
+	 * Return the identifiers of variables declared inside the given statement.
+	 *
+	 * @param node               The node to visit
+	 * @param includeInnerScopes True if blocks are visited too.
+	 *
+	 * @return The ids of the declared variables.
+	 */
+	public static Set<SimpleName> getLocalVariableIdentifiers(final ASTNode node, final boolean includeInnerScopes) {
+		if (node == null) {
+			return Collections.emptySet();
+		}
+
+		VarDeclarationIdentifierVisitor visitor= new VarDeclarationIdentifierVisitor(node, includeInnerScopes);
+		node.accept(visitor);
+		return visitor.getVariableNames();
+	}
+
+	/**
 	 * Return true if the node changes nothing and throws no exceptions.
 	 *
 	 * @param node The node to visit.
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/VarDeclarationIdentifierVisitor.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/VarDeclarationIdentifierVisitor.java
new file mode 100644
index 0000000..86747ab
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/VarDeclarationIdentifierVisitor.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * 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.corext.dom;
+
+import java.util.HashSet;
+import java.util.Set;
+
+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.SimpleName;
+import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
+import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
+
+/**
+ * Visitor collecting all definitions of any variable.
+ */
+public class VarDeclarationIdentifierVisitor extends ASTVisitor {
+	private final Set<SimpleName> variableNames= new HashSet<>();
+	private final ASTNode startNode;
+	private final boolean includeInnerScopes;
+
+	/**
+	 * The constructor.
+	 *
+	 * @param startNode       the {@link ASTNode} which is the scope of the search
+	 * @param includeInnerScopes True if the sub blocks should be analyzed
+	 */
+	public VarDeclarationIdentifierVisitor(final ASTNode startNode, final boolean includeInnerScopes) {
+		this.startNode= startNode;
+		this.includeInnerScopes= includeInnerScopes;
+	}
+
+	/**
+	 * Get the variable names.
+	 *
+	 * @return the variable names.
+	 */
+	public Set<SimpleName> getVariableNames() {
+		return variableNames;
+	}
+
+	@Override
+	public boolean visit(final SingleVariableDeclaration node) {
+		variableNames.add(node.getName());
+		return true;
+	}
+
+	@Override
+	public boolean visit(final VariableDeclarationFragment node) {
+		variableNames.add(node.getName());
+		return true;
+	}
+
+	@Override
+	public boolean visit(final Block node) {
+		return startNode == node || includeInnerScopes;
+	}
+}
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/VarOccurrenceVisitor.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/VarOccurrenceVisitor.java
new file mode 100644
index 0000000..b7fcde0
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/dom/VarOccurrenceVisitor.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * 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.corext.dom;
+
+import java.util.Set;
+
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.Block;
+import org.eclipse.jdt.core.dom.SimpleName;
+
+/**
+ * The variable occurrence visitor.
+ */
+public class VarOccurrenceVisitor extends InterruptibleVisitor {
+	private final Set<SimpleName> localVarIds;
+	private boolean varUsed;
+	private ASTNode startNode;
+	private final boolean includeInnerScopes;
+
+	/**
+	 * Returns true if at least one variable is used.
+	 *
+	 * @return True if at least one variable is used
+	 */
+	public boolean isVarUsed() {
+		return varUsed;
+	}
+
+	/**
+	 * The constructor.
+	 *
+	 * @param localVarIds The ids of the variable to search
+	 * @param includeInnerScopes True if the sub blocks should be analyzed
+	 */
+	public VarOccurrenceVisitor(final Set<SimpleName> localVarIds, final boolean includeInnerScopes) {
+		this.localVarIds= localVarIds;
+		this.includeInnerScopes= includeInnerScopes;
+	}
+
+	@Override
+	public void traverseNodeInterruptibly(final ASTNode aStartNode) {
+		this.startNode= aStartNode;
+		super.traverseNodeInterruptibly(this.startNode);
+	}
+
+	@Override
+	public boolean visit(final SimpleName aVariable) {
+		for (SimpleName localVarId : localVarIds) {
+			if (localVarId.getIdentifier() != null && localVarId.getIdentifier().equals(aVariable.getIdentifier())) {
+				varUsed= true;
+				return interruptVisit();
+			}
+		}
+
+		return true;
+	}
+
+	@Override
+	public boolean visit(final Block node) {
+		return startNode == node || includeInnerScopes;
+	}
+}
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 056cbd9..3eb860d 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
@@ -961,6 +961,18 @@
 	public static final String VARIABLE_DECLARATION_USE_TYPE_ARGUMENTS_FOR_RAW_TYPE_REFERENCES= "cleanup.use_arguments_for_raw_type_references"; //$NON-NLS-1$
 
 	/**
+	 * Add a break to avoid passive for loop iterations.
+	 * <p>
+	 * Possible values: {TRUE, FALSE}
+	 * <p>
+	 *
+	 * @see CleanUpOptionsCore#TRUE
+	 * @see CleanUpOptionsCore#FALSE
+	 * @since 4.18
+	 */
+	public static final String BREAK_LOOP= "cleanup.break_loop"; //$NON-NLS-1$
+
+	/**
 	 * Removes unused imports. <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 4aa3d26..fc37f82 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
@@ -11365,6 +11365,456 @@
 	}
 
 	@Test
+	public void testBreakLoop() throws Exception {
+		IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+		String input= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    private int[] innerArray = new int[10];\n" //
+				+ "\n" //
+				+ "    public String addBreak(int number) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i = 0; i < number; i++) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound = true;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : \"The result has not been found\";\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakInForeachLoop(int[] array) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i : array) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound = true;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : \"The result has not been found\";\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakWithField() {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i = 0; i < this.innerArray.length; i++) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound = true;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : \"The result has not been found\";\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakWithoutBlock(int[] array) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i : array) {\n" //
+				+ "            // Keep this comment\n" //
+				+ "            if (i == 42)\n" //
+				+ "                isFound = true;\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : \"The result has not been found\";\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakAfterSeveralAssignments(String[] array, boolean isFound, int count) {\n" //
+				+ "        for (String text : array) {\n" //
+				+ "            if (text == null) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound = true;\n" //
+				+ "                count = 1;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isFound) {\n" //
+				+ "            return \"We have found \" + count + \" result(s)\";\n" //
+				+ "        } else {\n" //
+				+ "            return \"The result has not been found\";\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakAfterComplexAssignment(int[] array) {\n" //
+				+ "        int hourNumber = 0;\n" //
+				+ "\n" //
+				+ "        for (int dayNumber : array) {\n" //
+				+ "            if (dayNumber == 7) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                hourNumber = 7 * 24;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return \"Hour number: \" + hourNumber;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakWithTemporaryVariable(int number) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i = 0; i < number; i++) {\n" //
+				+ "            int temporaryInteger = i * 3;\n" //
+				+ "\n" //
+				+ "            if (temporaryInteger == 42) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound = true;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : \"The result has not been found\";\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public boolean[] addBreakWithFixedAssignment(int number, int index) {\n" //
+				+ "        boolean[] isFound = new boolean[number];\n" //
+				+ "\n" //
+				+ "        for (int i = 0; i < number; i++) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound[index] = true;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakWithUpdatedIterator(int number) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i = 0; i < number; i++) {\n" //
+				+ "            if (i++ == 42) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound = true;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : \"The result has not been found\";\n" //
+				+ "    }\n" //
+				+ "}\n";
+		ICompilationUnit cu= pack.createCompilationUnit("E.java", input, false, null);
+
+		enable(CleanUpConstants.BREAK_LOOP);
+
+		String output= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    private int[] innerArray = new int[10];\n" //
+				+ "\n" //
+				+ "    public String addBreak(int number) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i = 0; i < number; i++) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound = true;\n" //
+				+ "                break;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : \"The result has not been found\";\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakInForeachLoop(int[] array) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i : array) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound = true;\n" //
+				+ "                break;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : \"The result has not been found\";\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakWithField() {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i = 0; i < this.innerArray.length; i++) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound = true;\n" //
+				+ "                break;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : \"The result has not been found\";\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakWithoutBlock(int[] array) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i : array) {\n" //
+				+ "            // Keep this comment\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                isFound = true;\n" //
+				+ "                break;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : \"The result has not been found\";\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakAfterSeveralAssignments(String[] array, boolean isFound, int count) {\n" //
+				+ "        for (String text : array) {\n" //
+				+ "            if (text == null) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound = true;\n" //
+				+ "                count = 1;\n" //
+				+ "                break;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        if (isFound) {\n" //
+				+ "            return \"We have found \" + count + \" result(s)\";\n" //
+				+ "        } else {\n" //
+				+ "            return \"The result has not been found\";\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakAfterComplexAssignment(int[] array) {\n" //
+				+ "        int hourNumber = 0;\n" //
+				+ "\n" //
+				+ "        for (int dayNumber : array) {\n" //
+				+ "            if (dayNumber == 7) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                hourNumber = 7 * 24;\n" //
+				+ "                break;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return \"Hour number: \" + hourNumber;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakWithTemporaryVariable(int number) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i = 0; i < number; i++) {\n" //
+				+ "            int temporaryInteger = i * 3;\n" //
+				+ "\n" //
+				+ "            if (temporaryInteger == 42) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound = true;\n" //
+				+ "                break;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : \"The result has not been found\";\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public boolean[] addBreakWithFixedAssignment(int number, int index) {\n" //
+				+ "        boolean[] isFound = new boolean[number];\n" //
+				+ "\n" //
+				+ "        for (int i = 0; i < number; i++) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound[index] = true;\n" //
+				+ "                break;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String addBreakWithUpdatedIterator(int number) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i = 0; i < number; i++) {\n" //
+				+ "            if (i++ == 42) {\n" //
+				+ "                // Keep this comment\n" //
+				+ "                isFound = true;\n" //
+				+ "                break;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : \"The result has not been found\";\n" //
+				+ "    }\n" //
+				+ "}\n";
+
+		assertNotEquals("The class must be changed", input, output);
+		assertGroupCategoryUsed(new ICompilationUnit[] { cu }, new HashSet<>(Arrays.asList(MultiFixMessages.BreakLoopCleanUp_description)));
+		assertRefactoringResultAsExpected(new ICompilationUnit[] { cu }, new String[] { output });
+	}
+
+	@Test
+	public void testDoNotBreakLoop() throws Exception {
+		IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+		String sample= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    private int crazyInteger = 0;\n" //
+				+ "\n" //
+				+ "    public String doNotBreakWithoutAssignment(int number) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "        for (int i = 0; i < number; i++) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "        return isFound ? \"The result has been found\" : (\"The result has not been found\");\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String doNotBreakWithExternalIterator(int number) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "        int i;\n" //
+				+ "        for (i = 0; i < number; i++) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                isFound = true;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "        return isFound ? \"The result has been found\" : (\"The result has not been found on \" + i + \" iteration(s)\");\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String doNotBreakWithActiveConditions(int number) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "        for (int i = 0; i < number--; i++) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                isFound = true;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : (\"The result has not been found on \" + number + \" iteration(s)\");\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public boolean[] doNotBreakWithChangingAssignment(int number) {\n" //
+				+ "        boolean[] hasNumber42 = new boolean[number];\n" //
+				+ "\n" //
+				+ "        for (int i = 0; i < number; i++) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                hasNumber42[i] = true;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return hasNumber42;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public int[] doNotBreakForeachLoopWithChangingAssignment(int[] input, int[] output) {\n" //
+				+ "        for (int i : input) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                output[i] = 123456;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return output;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public boolean[] doNotBreakWithActiveAssignment(int number, int index) {\n" //
+				+ "        boolean[] isFound = new boolean[number];\n" //
+				+ "\n" //
+				+ "        for (int i = 0; i < number; i++) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                isFound[index++] = true;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String doNotBreakWithActiveUpdater(int number) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i = 0; i < number; i++, number--) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                isFound = true;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? \"The result has been found\" : (\"The result has not been found on \" + number + \" iteration(s)\");\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String doNotBreakWithSeveralConditions(int[] array) {\n" //
+				+ "        int tenFactor = 0;\n" //
+				+ "\n" //
+				+ "        for (int i : array) {\n" //
+				+ "            if (i == 10) {\n" //
+				+ "                tenFactor = 1;\n" //
+				+ "            }\n" //
+				+ "            if (i == 100) {\n" //
+				+ "                tenFactor = 2;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return \"The result: \" + tenFactor;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public int doNotBreakWithActiveCondition(int[] array, int modifiedInteger) {\n" //
+				+ "        boolean isFound = false;\n" //
+				+ "\n" //
+				+ "        for (int i : array) {\n" //
+				+ "            if (i == modifiedInteger++) {\n" //
+				+ "                isFound = true;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return isFound ? 0 : modifiedInteger;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public int doNotBreakWithActiveAssignment(int[] array, int modifiedInteger) {\n" //
+				+ "        int result = 0;\n" //
+				+ "\n" //
+				+ "        for (int i : array) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                result = modifiedInteger++;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return result;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public int doNotBreakWithVariableAssignment(int[] array) {\n" //
+				+ "        int result = 0;\n" //
+				+ "\n" //
+				+ "        new Thread() {\n" //
+				+ "            @Override\n" //
+				+ "            public void run() {\n" //
+				+ "                while (crazyInteger++ < 10000) {}\n" //
+				+ "            }\n" //
+				+ "        }.start();\n" //
+				+ "\n" //
+				+ "        for (int i : array) {\n" //
+				+ "            if (i == 42) {\n" //
+				+ "                result = crazyInteger;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return result;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String doNotRefactorWithSpecialAssignment(int[] array) {\n" //
+				+ "        int tenFactor = 0;\n" //
+				+ "\n" //
+				+ "        for (int i : array) {\n" //
+				+ "            if (i == 10) {\n" //
+				+ "                tenFactor += 1;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "\n" //
+				+ "        return \"The result: \" + tenFactor;\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public void doNotBreakInfiniteLoop(int[] array) {\n" //
+				+ "        int tenFactor = 0;\n" //
+				+ "\n" //
+				+ "        for (;;) {\n" //
+				+ "            if (crazyInteger == 10) {\n" //
+				+ "                tenFactor = 1;\n" //
+				+ "            }\n" //
+				+ "        }\n" //
+				+ "    }\n" //
+				+ "}\n";
+		ICompilationUnit cu= pack.createCompilationUnit("E.java", sample, false, null);
+
+		enable(CleanUpConstants.BREAK_LOOP);
+
+		assertRefactoringHasNoChange(new ICompilationUnit[] { cu });
+	}
+
+	@Test
 	public void testRegExPrecompilationInLambda() throws Exception {
 		IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null);
 		String sample= "" //
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 e107d5d..52739ab 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
@@ -82,6 +82,7 @@
 		options.setOption(PREFER_BOOLEAN_LITERAL, CleanUpOptions.FALSE);
 
 		// Optimization
+		options.setOption(BREAK_LOOP, CleanUpOptions.FALSE);
 		options.setOption(USE_LAZY_LOGICAL_OPERATOR, CleanUpOptions.FALSE);
 		options.setOption(PRIMITIVE_SERIALIZATION, CleanUpOptions.FALSE);
 
@@ -210,6 +211,7 @@
 		options.setOption(PREFER_BOOLEAN_LITERAL, CleanUpOptions.FALSE);
 
 		// Optimization
+		options.setOption(BREAK_LOOP, CleanUpOptions.FALSE);
 		options.setOption(USE_LAZY_LOGICAL_OPERATOR, CleanUpOptions.FALSE);
 		options.setOption(PRIMITIVE_SERIALIZATION, CleanUpOptions.FALSE);
 
diff --git a/org.eclipse.jdt.ui/plugin.xml b/org.eclipse.jdt.ui/plugin.xml
index 61bf154..fda1403 100644
--- a/org.eclipse.jdt.ui/plugin.xml
+++ b/org.eclipse.jdt.ui/plugin.xml
@@ -7116,9 +7116,14 @@
             runAfter="org.eclipse.jdt.ui.cleanup.pull_up_assignment">
       </cleanUp>
       <cleanUp
+            class="org.eclipse.jdt.internal.ui.fix.BreakLoopCleanUp"
+            id="org.eclipse.jdt.ui.cleanup.break_loop"
+            runAfter="org.eclipse.jdt.ui.cleanup.number_suffix">
+      </cleanUp>
+      <cleanUp
             class="org.eclipse.jdt.internal.ui.fix.LazyLogicalCleanUp"
             id="org.eclipse.jdt.ui.cleanup.lazy_logical"
-            runAfter="org.eclipse.jdt.ui.cleanup.number_suffix">
+            runAfter="org.eclipse.jdt.ui.cleanup.break_loop">
       </cleanUp>
       <cleanUp
             class="org.eclipse.jdt.internal.ui.fix.PrimitiveSerializationCleanUp"
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/BreakLoopCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/BreakLoopCleanUp.java
new file mode 100644
index 0000000..2ed3242
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/BreakLoopCleanUp.java
@@ -0,0 +1,398 @@
+/*******************************************************************************
+ * 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.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.runtime.CoreException;
+
+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.Assignment;
+import org.eclipse.jdt.core.dom.Block;
+import org.eclipse.jdt.core.dom.ClassInstanceCreation;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+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.IfStatement;
+import org.eclipse.jdt.core.dom.InfixExpression;
+import org.eclipse.jdt.core.dom.MethodInvocation;
+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.Statement;
+import org.eclipse.jdt.core.dom.SuperMethodInvocation;
+import org.eclipse.jdt.core.dom.ThrowStatement;
+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.dom.rewrite.ListRewrite;
+
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
+import org.eclipse.jdt.internal.corext.dom.InterruptibleVisitor;
+import org.eclipse.jdt.internal.corext.dom.VarOccurrenceVisitor;
+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 adds a break to avoid passive for loop iterations.
+ *
+ * The conditions of triggering are:
+ * <ul>
+ *  <li>The structure of the code (for loop, including an if, including assignments)</li>
+ *  <li>Two cases of reject:
+ *   <ul>
+ *    <li>The inner assignments should not do other different assignments in the future (assign other values or assign into other variables),</li>
+ *    <li>No side effects after the first assignments.</li>
+ *   </ul>
+ *  </li>
+ * </ul>
+ */
+public class BreakLoopCleanUp extends AbstractMultiFix {
+	public BreakLoopCleanUp() {
+		this(Collections.emptyMap());
+	}
+
+	public BreakLoopCleanUp(Map<String, String> options) {
+		super(options);
+	}
+
+	@Override
+	public CleanUpRequirements getRequirements() {
+		boolean requireAST= isEnabled(CleanUpConstants.BREAK_LOOP);
+		return new CleanUpRequirements(requireAST, false, false, null);
+	}
+
+	@Override
+	public String[] getStepDescriptions() {
+		if (isEnabled(CleanUpConstants.BREAK_LOOP)) {
+			return new String[] { MultiFixMessages.BreakLoopCleanUp_description };
+		}
+
+		return new String[0];
+	}
+
+	@Override
+	public String getPreview() {
+		StringBuilder bld= new StringBuilder();
+		bld.append("boolean isFound = false;\n"); //$NON-NLS-1$
+		bld.append("for (int i = 0; i < number; i++) {\n"); //$NON-NLS-1$
+		bld.append("    if (i == 42) {\n"); //$NON-NLS-1$
+		bld.append("        isFound = true;\n"); //$NON-NLS-1$
+
+		if (isEnabled(CleanUpConstants.BREAK_LOOP)) {
+			bld.append("        break;\n"); //$NON-NLS-1$
+		}
+
+		bld.append("    }\n"); //$NON-NLS-1$
+		bld.append("}\n"); //$NON-NLS-1$
+
+		if (!isEnabled(CleanUpConstants.BREAK_LOOP)) {
+			bld.append("\n"); //$NON-NLS-1$
+		}
+
+		return bld.toString();
+	}
+
+	@Override
+	protected ICleanUpFix createFix(CompilationUnit unit) throws CoreException {
+		if (!isEnabled(CleanUpConstants.BREAK_LOOP)) {
+			return null;
+		}
+
+		final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>();
+
+		unit.accept(new ASTVisitor() {
+			final class SideEffectVisitor extends InterruptibleVisitor {
+				private final Set<SimpleName> localVariableNames;
+				private boolean hasSideEffect;
+
+				private SideEffectVisitor(final Set<SimpleName> localVariableNames) {
+					this.localVariableNames= localVariableNames;
+				}
+
+				private boolean hasSideEffect() {
+					return hasSideEffect;
+				}
+
+				@Override
+				public boolean visit(final Assignment node) {
+					if (!ASTNodes.hasOperator(node, Assignment.Operator.ASSIGN)) {
+						hasSideEffect= true;
+						return interruptVisit();
+					}
+
+					return visitVar(node.getLeftHandSide());
+				}
+
+				private boolean visitVar(final Expression modifiedVar) {
+					if (!(modifiedVar instanceof SimpleName)) {
+						hasSideEffect= true;
+						return interruptVisit();
+					}
+
+					boolean isFound= false;
+
+					for (SimpleName localVariableName : localVariableNames) {
+						if (ASTNodes.isSameVariable(localVariableName, modifiedVar)) {
+							isFound= true;
+							break;
+						}
+					}
+
+					if (!isFound) {
+						hasSideEffect= true;
+						return interruptVisit();
+					}
+
+					return true;
+				}
+
+				@Override
+				public boolean visit(final PrefixExpression node) {
+					if (ASTNodes.hasOperator(node, PrefixExpression.Operator.INCREMENT, PrefixExpression.Operator.DECREMENT)) {
+						return visitVar(node.getOperand());
+					}
+
+					return true;
+				}
+
+				@Override
+				public boolean visit(final PostfixExpression node) {
+					return visitVar(node.getOperand());
+				}
+
+				@Override
+				public boolean visit(final InfixExpression node) {
+					if (ASTNodes.hasOperator(node, InfixExpression.Operator.PLUS) && ASTNodes.hasType(node, String.class.getCanonicalName())
+							&& (mayCallImplicitToString(node.getLeftOperand())
+									|| mayCallImplicitToString(node.getRightOperand())
+									|| mayCallImplicitToString(node.extendedOperands()))) {
+						hasSideEffect= true;
+						return interruptVisit();
+					}
+
+					return true;
+				}
+
+				private boolean mayCallImplicitToString(final List<Expression> extendedOperands) {
+					if (extendedOperands != null) {
+						for (Expression expression : extendedOperands) {
+							if (mayCallImplicitToString(expression)) {
+								return true;
+							}
+						}
+					}
+
+					return false;
+				}
+
+				private boolean mayCallImplicitToString(final Expression expression) {
+					return !ASTNodes.hasType(expression, String.class.getCanonicalName(), boolean.class.getSimpleName(), short.class.getSimpleName(), int.class.getSimpleName(), long.class.getSimpleName(), float.class.getSimpleName(), double.class.getSimpleName(),
+							Short.class.getCanonicalName(), Boolean.class.getCanonicalName(), Integer.class.getCanonicalName(), Long.class.getCanonicalName(), Float.class.getCanonicalName(),
+							Double.class.getCanonicalName()) && !(expression instanceof PrefixExpression) && !(expression instanceof InfixExpression)
+							&& !(expression instanceof PostfixExpression);
+				}
+
+				@Override
+				public boolean visit(final SuperMethodInvocation node) {
+					hasSideEffect= true;
+					return interruptVisit();
+				}
+
+				@Override
+				public boolean visit(final MethodInvocation node) {
+					hasSideEffect= true;
+					return interruptVisit();
+				}
+
+				@Override
+				public boolean visit(final ClassInstanceCreation node) {
+					hasSideEffect= true;
+					return interruptVisit();
+				}
+
+				@Override
+				public boolean visit(final ThrowStatement node) {
+					hasSideEffect= true;
+					return interruptVisit();
+				}
+			}
+
+			@Override
+			public boolean visit(final ForStatement node) {
+				Set<SimpleName> vars= new HashSet<>();
+
+				for (Expression initializer : (List<Expression>) node.initializers()) {
+					vars.addAll(ASTNodes.getLocalVariableIdentifiers(initializer, true));
+				}
+
+				if (node.getExpression() == null
+						|| hasSideEffect(node.getExpression(), vars)
+						|| node.updaters().isEmpty()) {
+					return true;
+				}
+
+				for (Expression updater : (List<Expression>) node.updaters()) {
+					if (hasSideEffect(updater, vars)) {
+						return true;
+					}
+				}
+
+				return visitLoopBody(node.getBody(), vars);
+			}
+
+			private boolean hasSideEffect(final ASTNode node, final Set<SimpleName> allowedVars) {
+				SideEffectVisitor variableUseVisitor= new SideEffectVisitor(allowedVars);
+				variableUseVisitor.traverseNodeInterruptibly(node);
+				return variableUseVisitor.hasSideEffect();
+			}
+
+			@Override
+			public boolean visit(final EnhancedForStatement node) {
+				if (ASTNodes.isArray(node.getExpression())) {
+					Set<SimpleName> vars= new HashSet<>();
+					vars.add(node.getParameter().getName());
+					return visitLoopBody(node.getBody(), vars);
+				}
+
+				return true;
+			}
+
+			private boolean visitLoopBody(final Statement body, final Set<SimpleName> allowedVars) {
+				List<Statement> statements= ASTNodes.asList(body);
+
+				if (statements == null || statements.isEmpty()) {
+					return true;
+				}
+
+				for (int i= 0; i < statements.size() - 1; i++) {
+					Statement statement= statements.get(i);
+					allowedVars.addAll(ASTNodes.getLocalVariableIdentifiers(statement, true));
+
+					if (hasSideEffect(statement, allowedVars)) {
+						return true;
+					}
+				}
+
+				IfStatement ifStatement= ASTNodes.as(statements.get(statements.size() - 1), IfStatement.class);
+
+				if (ifStatement != null && ifStatement.getElseStatement() == null && !hasSideEffect(ifStatement.getExpression(), allowedVars)) {
+					List<Statement> assignments= ASTNodes.asList(ifStatement.getThenStatement());
+
+					if (areAssignmentsValid(allowedVars, assignments)) {
+						rewriteOperations.add(new BreakLoopOperation(ifStatement));
+						return false;
+					}
+				}
+
+				return true;
+			}
+
+			private boolean areAssignmentsValid(final Set<SimpleName> allowedVars, final List<Statement> assignments) {
+				if (assignments.isEmpty()) {
+					return false;
+				}
+
+				for (Statement statement : assignments) {
+					VariableDeclarationStatement variableDeclaration= ASTNodes.as(statement, VariableDeclarationStatement.class);
+					Assignment assignment= ASTNodes.asExpression(statement, Assignment.class);
+
+					if (variableDeclaration != null) {
+						for (Object obj : variableDeclaration.fragments()) {
+							VariableDeclarationFragment fragment= (VariableDeclarationFragment) obj;
+
+							if (!ASTNodes.isHardCoded(fragment.getInitializer())) {
+								return false;
+							}
+						}
+					} else if (assignment != null
+							&& ASTNodes.hasOperator(assignment, Assignment.Operator.ASSIGN)
+							&& ASTNodes.isHardCoded(assignment.getRightHandSide())
+							&& ASTNodes.isPassive(assignment.getLeftHandSide())) {
+						VarOccurrenceVisitor varOccurrenceVisitor= new VarOccurrenceVisitor(allowedVars, true);
+						varOccurrenceVisitor.traverseNodeInterruptibly(assignment.getLeftHandSide());
+
+						if (varOccurrenceVisitor.isVarUsed()) {
+							return false;
+						}
+					} else {
+						return false;
+					}
+				}
+
+				return true;
+			}
+		});
+
+		if (rewriteOperations.isEmpty()) {
+			return null;
+		}
+
+		return new CompilationUnitRewriteOperationsFix(MultiFixMessages.BreakLoopCleanUp_description, unit,
+				rewriteOperations.toArray(new CompilationUnitRewriteOperation[0]));
+	}
+
+	@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 BreakLoopOperation extends CompilationUnitRewriteOperation {
+		private final IfStatement ifStatement;
+
+		public BreakLoopOperation(final IfStatement ifStatement) {
+			this.ifStatement= ifStatement;
+		}
+
+		@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.BreakLoopCleanUp_description, cuRewrite);
+
+			if (ifStatement.getThenStatement() instanceof Block) {
+				ListRewrite listRewrite= rewrite.getListRewrite(ifStatement.getThenStatement(), Block.STATEMENTS_PROPERTY);
+				listRewrite.insertLast(ast.newBreakStatement(), group);
+			} else {
+				Block newBlock= ast.newBlock();
+				newBlock.statements().add(ASTNodes.createMoveTarget(rewrite, ifStatement.getThenStatement()));
+				newBlock.statements().add(ast.newBreakStatement());
+				ASTNodes.replaceButKeepComment(rewrite, ifStatement.getThenStatement(), 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 88cf1e2..7736259 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
@@ -88,8 +88,9 @@
 
 	public static String OptimizationTabPage_GroupName_Optimization;
 
-	public static String OptimizationTabPage_CheckboxName_PrimitiveSerialization;
+	public static String OptimizationTabPage_CheckboxName_BreakLoop;
 	public static String OptimizationTabPage_CheckboxName_UseLazyLogicalOperator;
+	public static String OptimizationTabPage_CheckboxName_PrimitiveSerialization;
 	public static String OptimizationTabPage_CheckboxName_PrecompileRegEx;
 	public static String OptimizationTabPage_CheckboxName_NoStringCreation;
 	public static String OptimizationTabPage_CheckboxName_BooleanLiteral;
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 db8b091..16c6aa0 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
@@ -66,6 +66,7 @@
 CodeStyleTabPage_RadioName_UseAnonymous=Use anonymous class
 
 OptimizationTabPage_GroupName_Optimization=Optimization
+OptimizationTabPage_CheckboxName_BreakLoop=Exit &loop earlier
 OptimizationTabPage_CheckboxName_UseLazyLogicalOperator=Use la&zy logical operator
 OptimizationTabPage_CheckboxName_PrimitiveSerialization=&Primitive serialization
 OptimizationTabPage_CheckboxName_PrecompileRegEx=Precompile reused regular e&xpressions
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/OptimizationTabPage.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/OptimizationTabPage.java
index 01de826..e28c67b 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/OptimizationTabPage.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/OptimizationTabPage.java
@@ -22,6 +22,7 @@
 
 import org.eclipse.jdt.internal.ui.fix.AbstractCleanUp;
 import org.eclipse.jdt.internal.ui.fix.BooleanLiteralCleanUp;
+import org.eclipse.jdt.internal.ui.fix.BreakLoopCleanUp;
 import org.eclipse.jdt.internal.ui.fix.LazyLogicalCleanUp;
 import org.eclipse.jdt.internal.ui.fix.NoStringCreationCleanUp;
 import org.eclipse.jdt.internal.ui.fix.PatternCleanUp;
@@ -33,6 +34,7 @@
 	@Override
 	protected AbstractCleanUp[] createPreviewCleanUps(Map<String, String> values) {
 		return new AbstractCleanUp[] {
+				new BreakLoopCleanUp(values),
 				new LazyLogicalCleanUp(values),
 				new PrimitiveSerializationCleanUp(values),
 				new PatternCleanUp(values),
@@ -45,6 +47,9 @@
 	protected void doCreatePreferences(Composite composite, int numColumns) {
 		Group optimizationGroup= createGroup(numColumns, composite, CleanUpMessages.OptimizationTabPage_GroupName_Optimization);
 
+		final CheckboxPreference breakLoopPref= createCheckboxPref(optimizationGroup, numColumns, CleanUpMessages.OptimizationTabPage_CheckboxName_BreakLoop, CleanUpConstants.BREAK_LOOP, CleanUpModifyDialog.FALSE_TRUE);
+		registerPreference(breakLoopPref);
+
 		final CheckboxPreference useLazyLogicalPref= createCheckboxPref(optimizationGroup, numColumns, CleanUpMessages.OptimizationTabPage_CheckboxName_UseLazyLogicalOperator,
 				CleanUpConstants.USE_LAZY_LOGICAL_OPERATOR, CleanUpModifyDialog.FALSE_TRUE);
 		registerPreference(useLazyLogicalPref);