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