Bug 571040 - [cleanup] Use String::replace instead of String::replaceAll

A cleanup to use String::replace instead of String::replaceAll where
only ordinary Strings are replaced and regular expressions are not used.

Change-Id: I19c988cbc21df87324d01f62264e70d23df16741
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 2ed3b55..4f4c332 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
@@ -131,6 +131,7 @@
 	public static String BreakLoopCleanUp_description;
 	public static String StaticInnerClassCleanUp_description;
 	public static String StringBuilderCleanUp_description;
+	public static String PlainReplacementCleanUp_description;
 	public static String CodeStyleCleanUp_LazyLogical_description;
 	public static String PrimitiveComparisonCleanUp_description;
 	public static String PrimitiveParsingCleanUp_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 8863cac..1b336b4 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
@@ -113,6 +113,7 @@
 BreakLoopCleanUp_description=Exit loop earlier
 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 no regex used
 CodeStyleCleanUp_LazyLogical_description=Use lazy logical operator (&& and ||)
 PrimitiveComparisonCleanUp_description=Primitive comparison
 PrimitiveParsingCleanUp_description=Primitive parsing
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/PlainReplacementCleanUpCore.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/PlainReplacementCleanUpCore.java
new file mode 100644
index 0000000..181cbd4
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/PlainReplacementCleanUpCore.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * 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.ArrayList;
+import java.util.List;
+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.PlainReplacementFixCore;
+
+public class PlainReplacementCleanUpCore extends AbstractCleanUpCore {
+	public PlainReplacementCleanUpCore(final Map<String, String> options) {
+		super(options);
+	}
+
+	public PlainReplacementCleanUpCore() {
+	}
+
+	@Override
+	public CleanUpRequirementsCore getRequirementsCore() {
+		return new CleanUpRequirementsCore(requireAST(), false, false, null);
+	}
+
+	public boolean requireAST() {
+		return isEnabled(CleanUpConstants.PLAIN_REPLACEMENT);
+	}
+
+	@Override
+	public ICleanUpFixCore createFixCore(final CleanUpContextCore context) throws CoreException {
+		CompilationUnit compilationUnit= context.getAST();
+
+		if (compilationUnit == null || !isEnabled(CleanUpConstants.PLAIN_REPLACEMENT)) {
+			return null;
+		}
+
+		return PlainReplacementFixCore.createCleanUp(compilationUnit);
+	}
+
+	@Override
+	public String[] getStepDescriptions() {
+		List<String> result= new ArrayList<>();
+
+		if (isEnabled(CleanUpConstants.PLAIN_REPLACEMENT)) {
+			result.add(MultiFixMessages.PlainReplacementCleanUp_description);
+		}
+
+		return result.toArray(new String[0]);
+	}
+
+	@Override
+	public String getPreview() {
+		if (isEnabled(CleanUpConstants.PLAIN_REPLACEMENT)) {
+			return "" //$NON-NLS-1$
+					+ "String result = text.replace(\"foo\", \"bar\");\n" //$NON-NLS-1$
+					+ "String result2 = text.replace(\"&\", \"&amp;\");\n" //$NON-NLS-1$
+					+ "String result3 = text.replaceAll(\"[ab]\", \"c\");\n" //$NON-NLS-1$
+					+ "String result4 = text.replaceAll(\"\\s\". \"\");\n"; //$NON-NLS-1$
+		}
+
+		return "" //$NON-NLS-1$
+				+ "String result = text.replaceAll(\"foo\", \"bar\");\n" //$NON-NLS-1$
+				+ "String result2 = text.replaceAll(\"&\", \"&amp;\");\n" //$NON-NLS-1$
+				+ "String result3 = text.replaceAll(\"[ab]\", \"c\");\n" //$NON-NLS-1$
+				+ "String result4 = text.replaceAll(\"\\s\". \"\");\n"; //$NON-NLS-1$
+	}
+}
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 6e8b13c..54b8516 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
@@ -1083,6 +1083,18 @@
 	public static final String STRINGBUILDER= "cleanup.stringbuilder"; //$NON-NLS-1$
 
 	/**
+	 * Replaces <code>String.replaceAll()</code> by <code>String.replace()</code>.
+	 * <p>
+	 * Possible values: {TRUE, FALSE}
+	 * <p>
+	 *
+	 * @see CleanUpOptionsCore#TRUE
+	 * @see CleanUpOptionsCore#FALSE
+	 * @since 4.19
+	 */
+	public static final String PLAIN_REPLACEMENT= "cleanup.plain_replacement"; //$NON-NLS-1$
+
+	/**
 	 * Removes unused imports. <br>
 	 * <br>
 	 * Possible values: {TRUE, FALSE}<br>
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 8c2070f..bef87cf 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
@@ -131,6 +131,8 @@
 	public static String VariableDeclarationFix_changeModifierOfUnknownToFinal_description;
 	public static String VariableDeclarationFix_ChangeMidifiersToFinalWherPossible_description;
 
+	public static String PlainReplacementFix_use_plain_text;
+
 	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 7cafc29..9948059 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
@@ -118,6 +118,8 @@
 SortMembersFix_Change_description=Sort Members
 SortMembersFix_Fix_description=Sort Members
 
+PlainReplacementFix_use_plain_text=Use String::replace instead of String::replaceAll when no regex used
+
 NullAnnotationsFix_add_annotation_change_name=Add Annotations
 NullAnnotationsRewriteOperations_change_method_parameter_nullness=Change parameter ''{0}'' to ''@{1}''
 NullAnnotationsRewriteOperations_change_target_method_parameter_nullness=Change parameter of ''{0}(..)'' to ''@{1}''
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/PlainReplacementFixCore.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/PlainReplacementFixCore.java
new file mode 100644
index 0000000..753de43
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/PlainReplacementFixCore.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * 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.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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.CompilationUnit;
+import org.eclipse.jdt.core.dom.Expression;
+import org.eclipse.jdt.core.dom.MethodInvocation;
+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.refactoring.structure.CompilationUnitRewrite;
+
+import org.eclipse.jdt.internal.ui.fix.MultiFixMessages;
+
+public class PlainReplacementFixCore extends CompilationUnitRewriteOperationsFixCore {
+	public static final class PlainReplacementFinder extends ASTVisitor {
+		private Pattern HAS_REGEX_CHARACTER= Pattern.compile(".*[\\\\\\[\\]\\{\\}\\(\\)\\*\\+\\?\\.\\^\\$\\|].*"); //$NON-NLS-1$
+
+		private List<PlainReplacementFixOperation> fResult;
+
+		public PlainReplacementFinder(List<PlainReplacementFixOperation> ops) {
+			fResult= ops;
+		}
+
+		@Override
+		public boolean visit(final MethodInvocation visited) {
+			if (ASTNodes.usesGivenSignature(visited, String.class.getCanonicalName(), "replaceAll", String.class.getCanonicalName(), String.class.getCanonicalName())) { //$NON-NLS-1$
+				Object pattern= ((Expression) visited.arguments().get(0)).resolveConstantExpressionValue();
+				Object replacement= ((Expression) visited.arguments().get(1)).resolveConstantExpressionValue();
+
+				if (pattern instanceof String
+						&& !HAS_REGEX_CHARACTER.matcher((String) pattern).matches()
+						&& replacement instanceof String
+						&& ((String) replacement).equals(Matcher.quoteReplacement((String) replacement))) {
+					fResult.add(new PlainReplacementFixOperation(visited));
+					return false;
+				}
+			}
+
+			return true;
+		}
+	}
+
+	public static class PlainReplacementFixOperation extends CompilationUnitRewriteOperation {
+		private final MethodInvocation visited;
+
+		public PlainReplacementFixOperation(final MethodInvocation 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.PlainReplacementCleanUp_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);
+				}
+			});
+
+			rewrite.set(visited, MethodInvocation.NAME_PROPERTY, ast.newSimpleName("replace"), group); //$NON-NLS-1$
+		}
+	}
+
+
+	public static ICleanUpFixCore createCleanUp(final CompilationUnit compilationUnit) {
+		List<PlainReplacementFixOperation> operations= new ArrayList<>();
+		PlainReplacementFinder finder= new PlainReplacementFinder(operations);
+		compilationUnit.accept(finder);
+
+		if (operations.isEmpty()) {
+			return null;
+		}
+
+		CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] ops= operations.toArray(new CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[0]);
+		return new PlainReplacementFixCore(FixMessages.PlainReplacementFix_use_plain_text, compilationUnit, ops);
+	}
+
+	protected PlainReplacementFixCore(final String name, final CompilationUnit compilationUnit, final CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation[] fixRewriteOperations) {
+		super(name, compilationUnit, fixRewriteOperations);
+	}
+}
diff --git a/org.eclipse.jdt.ui.tests/performance/org/eclipse/jdt/ui/tests/performance/views/CleanUpPerfTest.java b/org.eclipse.jdt.ui.tests/performance/org/eclipse/jdt/ui/tests/performance/views/CleanUpPerfTest.java
index 3457153..c0613cb 100644
--- a/org.eclipse.jdt.ui.tests/performance/org/eclipse/jdt/ui/tests/performance/views/CleanUpPerfTest.java
+++ b/org.eclipse.jdt.ui.tests/performance/org/eclipse/jdt/ui/tests/performance/views/CleanUpPerfTest.java
@@ -86,6 +86,7 @@
 import org.eclipse.jdt.internal.ui.fix.ReduceIndentationCleanUp;
 import org.eclipse.jdt.internal.ui.fix.SingleUsedFieldCleanUp;
 import org.eclipse.jdt.internal.ui.fix.SortMembersCleanUp;
+import org.eclipse.jdt.internal.ui.fix.PlainReplacementCleanUp;
 import org.eclipse.jdt.internal.ui.fix.StandardComparisonCleanUp;
 import org.eclipse.jdt.internal.ui.fix.StringCleanUp;
 import org.eclipse.jdt.internal.ui.fix.SwitchExpressionsCleanUp;
@@ -606,6 +607,22 @@
 	}
 
 	@Test
+	public void testPlainReplacementCleanUp() throws Exception {
+		CleanUpRefactoring cleanUpRefactoring= new CleanUpRefactoring();
+		addAllCUs(cleanUpRefactoring, MyTestSetup.fJProject1.getChildren());
+
+		Map<String, String> node= getNullSettings();
+
+		node.put(CleanUpConstants.PLAIN_REPLACEMENT, CleanUpOptions.TRUE);
+
+		storeSettings(node);
+
+		cleanUpRefactoring.addCleanUp(new PlainReplacementCleanUp());
+
+		doCleanUp(cleanUpRefactoring);
+	}
+
+	@Test
 	public void testCollectionCloningCleanUp() throws Exception {
 		CleanUpRefactoring cleanUpRefactoring= new CleanUpRefactoring();
 		addAllCUs(cleanUpRefactoring, MyTestSetup.fJProject1.getChildren());
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 f39fd9b..acc96c8 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
@@ -15286,6 +15286,95 @@
 	}
 
 	@Test
+	public void testPlainReplacement() throws Exception {
+		// Given
+		IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+		String given= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    private static final String CONSTANT = \"&\";\n" //
+				+ "\n" //
+				+ "    public void refactor(String text) {\n" //
+				+ "        String s1 = text.replaceAll(\"&\", \"&amp;\");\n" //
+				+ "        String s2 = text.replaceAll(\",:#\", \"/\");\n" //
+				+ "        String s3 = text.replaceAll(\"/\", \".\");\n" //
+				+ "        String s4 = text.replaceAll(CONSTANT, \"&amp;\");\n" //
+				+ "    }\n" //
+				+ "}\n";
+
+		String expected= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    private static final String CONSTANT = \"&\";\n" //
+				+ "\n" //
+				+ "    public void refactor(String text) {\n" //
+				+ "        String s1 = text.replace(\"&\", \"&amp;\");\n" //
+				+ "        String s2 = text.replace(\",:#\", \"/\");\n" //
+				+ "        String s3 = text.replace(\"/\", \".\");\n" //
+				+ "        String s4 = text.replace(CONSTANT, \"&amp;\");\n" //
+				+ "    }\n" //
+				+ "}\n";
+
+		// When
+		ICompilationUnit cu= pack.createCompilationUnit("E.java", given, false, null);
+		enable(CleanUpConstants.PLAIN_REPLACEMENT);
+
+		// Then
+		assertNotEquals("The class must be changed", given, expected);
+		assertGroupCategoryUsed(new ICompilationUnit[] { cu }, new HashSet<>(Arrays.asList(MultiFixMessages.PlainReplacementCleanUp_description)));
+		assertRefactoringResultAsExpected(new ICompilationUnit[] { cu }, new String[] { expected });
+	}
+
+	@Test
+	public void testDoNotUsePlainReplacement() throws Exception {
+		IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+		String sample= "" //
+				+ "package test1;\n" //
+				+ "\n" //
+				+ "import java.util.regex.Matcher;\n" //
+				+ "\n" //
+				+ "public class E {\n" //
+				+ "    public void doNotRefactorEscapableCharacters(String text) {\n" //
+				+ "        String s1 = text.replaceAll(\"[ab]\", \"c\");\n" //
+				+ "        String s2 = text.replaceAll(\"a.b\", \"foo\");\n" //
+				+ "        String s3 = text.replaceAll(\"a?\", \"foo\");\n" //
+				+ "        String s4 = text.replaceAll(\"a+\", \"foo\");\n" //
+				+ "        String s5 = text.replaceAll(\"a*\", \"foo\");\n" //
+				+ "        String s6 = text.replaceAll(\"a{42}\", \"foo\");\n" //
+				+ "        String s7 = text.replaceAll(\"a{1,42}\", \"foo\");\n" //
+				+ "        String s8 = text.replaceAll(\"(a)\", \"foo\");\n" //
+				+ "        String s9 = text.replaceAll(\"^a\", \"foo\");\n" //
+				+ "        String s10 = text.replaceAll(\"a$\", \"foo\");\n" //
+				+ "        String s11 = text.replaceAll(\"\\\\s\", \"\");\n" //
+				+ "        String s12 = text.replaceAll(\"a|b\", \"foo\");\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String doNotRefactorReplacementWithCapturedGroup(String text) {\n" //
+				+ "        return text.replaceAll(\"foo\", \"$0\");\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String doNotRefactorWithEscapedReplacement(String text) {\n" //
+				+ "        return text.replaceAll(\"foo\", \"\\\\$1\");\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String doNotRefactorUnknownPattern(String text, String pattern) {\n" //
+				+ "        return text.replaceAll(pattern, \"c\");\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String doNotRefactorOtherMethod(Matcher matcher, String text) {\n" //
+				+ "        return matcher.replaceAll(text);\n" //
+				+ "    }\n" //
+				+ "}\n";
+		ICompilationUnit cu= pack.createCompilationUnit("E.java", sample, false, null);
+
+		enable(CleanUpConstants.PLAIN_REPLACEMENT);
+
+		assertRefactoringHasNoChange(new ICompilationUnit[] { cu });
+	}
+
+	@Test
 	public void testControlFlowMerge() throws Exception {
 		// Given
 		IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
index 41c5716..76a3ed1 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
@@ -78,6 +78,7 @@
 		options.setOption(BREAK_LOOP, CleanUpOptions.FALSE);
 		options.setOption(STATIC_INNER_CLASS, CleanUpOptions.FALSE);
 		options.setOption(STRINGBUILDER, CleanUpOptions.FALSE);
+		options.setOption(PLAIN_REPLACEMENT, CleanUpOptions.FALSE);
 		options.setOption(USE_LAZY_LOGICAL_OPERATOR, CleanUpOptions.FALSE);
 		options.setOption(PRIMITIVE_COMPARISON, CleanUpOptions.FALSE);
 		options.setOption(PRIMITIVE_PARSING, CleanUpOptions.FALSE);
@@ -160,6 +161,8 @@
 		options.setOption(MERGE_CONDITIONAL_BLOCKS, CleanUpOptions.FALSE);
 		options.setOption(CONTROLFLOW_MERGE, CleanUpOptions.FALSE);
 
+		// Code fixing
+
 		// Java Features
 		options.setOption(USE_PATTERN_MATCHING_FOR_INSTANCEOF, CleanUpOptions.FALSE);
 		options.setOption(CONTROL_STATEMENTS_CONVERT_TO_SWITCH_EXPRESSIONS, CleanUpOptions.FALSE);
@@ -233,6 +236,7 @@
 		options.setOption(BREAK_LOOP, CleanUpOptions.FALSE);
 		options.setOption(STATIC_INNER_CLASS, CleanUpOptions.FALSE);
 		options.setOption(STRINGBUILDER, CleanUpOptions.FALSE);
+		options.setOption(PLAIN_REPLACEMENT, CleanUpOptions.FALSE);
 		options.setOption(USE_LAZY_LOGICAL_OPERATOR, CleanUpOptions.FALSE);
 		options.setOption(PRIMITIVE_COMPARISON, CleanUpOptions.FALSE);
 		options.setOption(PRIMITIVE_PARSING, CleanUpOptions.FALSE);
@@ -273,6 +277,8 @@
 		options.setOption(REMOVE_USELESS_CONTINUE, CleanUpOptions.FALSE);
 		options.setOption(UNLOOPED_WHILE, CleanUpOptions.FALSE);
 
+		// Code fixing
+
 		//Missing Code
 		options.setOption(ADD_MISSING_ANNOTATIONS, CleanUpOptions.TRUE);
 		options.setOption(ADD_MISSING_ANNOTATIONS_OVERRIDE, CleanUpOptions.TRUE);
diff --git a/org.eclipse.jdt.ui/plugin.xml b/org.eclipse.jdt.ui/plugin.xml
index cc1e418..dbf508f 100644
--- a/org.eclipse.jdt.ui/plugin.xml
+++ b/org.eclipse.jdt.ui/plugin.xml
@@ -7138,9 +7138,14 @@
             runAfter="org.eclipse.jdt.ui.cleanup.static_inner_class">
       </cleanUp>
       <cleanUp
+            class="org.eclipse.jdt.internal.ui.fix.PlainReplacementCleanUp"
+            id="org.eclipse.jdt.ui.cleanup.plain_replacement"
+            runAfter="org.eclipse.jdt.ui.cleanup.stringbuilder">
+      </cleanUp>
+      <cleanUp
             class="org.eclipse.jdt.internal.ui.fix.LazyLogicalCleanUp"
             id="org.eclipse.jdt.ui.cleanup.lazy_logical"
-            runAfter="org.eclipse.jdt.ui.cleanup.stringbuilder">
+            runAfter="org.eclipse.jdt.ui.cleanup.plain_replacement">
       </cleanUp>
       <cleanUp
             class="org.eclipse.jdt.internal.ui.fix.PrimitiveComparisonCleanUp"
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/PlainReplacementCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/PlainReplacementCleanUp.java
new file mode 100644
index 0000000..9d8f114
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/PlainReplacementCleanUp.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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>String.replaceAll()</code> by <code>String.replace()</code> when the pattern is a plain text:
+ * <ul>
+ * <li>The pattern must be a constant.</li>
+ * </ul>
+ */
+public class PlainReplacementCleanUp extends AbstractCleanUp {
+	private PlainReplacementCleanUpCore coreCleanUp= new PlainReplacementCleanUpCore();
+
+	public PlainReplacementCleanUp(final Map<String, String> options) {
+		setOptions(options);
+	}
+
+	public PlainReplacementCleanUp() {
+	}
+
+	@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 c581eb8..952348e 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
@@ -85,6 +85,7 @@
 	public static String OptimizationTabPage_CheckboxName_BreakLoop;
 	public static String OptimizationTabPage_CheckboxName_StaticInnerClass;
 	public static String OptimizationTabPage_CheckboxName_StringBuilder;
+	public static String OptimizationTabPage_CheckboxName_PlainReplacement;
 	public static String OptimizationTabPage_CheckboxName_UseLazyLogicalOperator;
 	public static String OptimizationTabPage_CheckboxName_PrimitiveComparison;
 	public static String OptimizationTabPage_CheckboxName_PrimitiveParsing;
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 46b5119..dec0307 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
@@ -64,6 +64,7 @@
 OptimizationTabPage_CheckboxName_BreakLoop=Exit &loop earlier
 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 no regex used
 OptimizationTabPage_CheckboxName_UseLazyLogicalOperator=Use la&zy logical operator
 OptimizationTabPage_CheckboxName_PrimitiveComparison=Primitive comparison
 OptimizationTabPage_CheckboxName_PrimitiveParsing=Primitive parsing
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 ca79c45..0abce49 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
@@ -26,6 +26,7 @@
 import org.eclipse.jdt.internal.ui.fix.LazyLogicalCleanUp;
 import org.eclipse.jdt.internal.ui.fix.NoStringCreationCleanUp;
 import org.eclipse.jdt.internal.ui.fix.PatternCleanUp;
+import org.eclipse.jdt.internal.ui.fix.PlainReplacementCleanUp;
 import org.eclipse.jdt.internal.ui.fix.PrimitiveComparisonCleanUp;
 import org.eclipse.jdt.internal.ui.fix.PrimitiveParsingCleanUp;
 import org.eclipse.jdt.internal.ui.fix.PrimitiveSerializationCleanUp;
@@ -43,6 +44,7 @@
 				new BreakLoopCleanUp(values),
 				new StaticInnerClassCleanUp(values),
 				new StringBuilderCleanUp(values),
+				new PlainReplacementCleanUp(values),
 				new LazyLogicalCleanUp(values),
 				new PrimitiveComparisonCleanUp(values),
 				new PrimitiveParsingCleanUp(values),
@@ -69,6 +71,9 @@
 		final CheckboxPreference stringBuilderPref= createCheckboxPref(optimizationGroup, numColumns, CleanUpMessages.OptimizationTabPage_CheckboxName_StringBuilder, CleanUpConstants.STRINGBUILDER, CleanUpModifyDialog.FALSE_TRUE);
 		registerPreference(stringBuilderPref);
 
+		final CheckboxPreference plainReplacementPref= createCheckboxPref(optimizationGroup, numColumns, CleanUpMessages.OptimizationTabPage_CheckboxName_PlainReplacement, CleanUpConstants.PLAIN_REPLACEMENT, CleanUpModifyDialog.FALSE_TRUE);
+		registerPreference(plainReplacementPref);
+
 		final CheckboxPreference useLazyLogicalPref= createCheckboxPref(optimizationGroup, numColumns, CleanUpMessages.OptimizationTabPage_CheckboxName_UseLazyLogicalOperator,
 				CleanUpConstants.USE_LAZY_LOGICAL_OPERATOR, CleanUpModifyDialog.FALSE_TRUE);
 		registerPreference(useLazyLogicalPref);