Bug 565695 - Cleanup for regex should not change 1-char non-regexps

- change PatternCleanUp to not create a pattern for a String split
  if the pattern is a single-char non-meta character or is an
  escaped character that is not alpha-numeric since the String
  split() method fast-paths these without using patterns

Change-Id: I87af41904bc87f02cd8e4d4349221cb87f8cc058
Signed-off-by: Fabrice Tiercelin <fabrice.tiercelin@yahoo.fr>
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 f549de6..8bf62be 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
@@ -7597,7 +7597,7 @@
 				+ "       return dateText1 + dateText2;\n" //
 				+ "    }\n" //
 				+ "\n" //
-				+ "    public String usePatternForSplit(String speech1, String speech2) {\n" //
+				+ "    public String usePatternForSplit1(String speech1, String speech2) {\n" //
 				+ "       // Keep this comment\n" //
 				+ "       String line= \"\\\\r?\\\\n\";\n" //
 				+ "\n" //
@@ -7609,6 +7609,30 @@
 				+ "       return Arrays.toString(phrases1) + Arrays.toString(phrases2);\n" //
 				+ "    }\n" //
 				+ "\n" //
+				+ "    public String usePatternForSplit2(String speech1, String speech2) {\n" //
+				+ "       // Keep this comment\n" //
+				+ "       String line= \".\";\n" //
+				+ "\n" //
+				+ "       // Keep this comment too\n" //
+				+ "       String[] phrases1= speech1.split(line);\n" //
+				+ "       // Keep this comment also\n" //
+				+ "       String[] phrases2= speech2.split(line, 123);\n" //
+				+ "\n" //
+				+ "       return Arrays.toString(phrases1) + Arrays.toString(phrases2);\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String usePatternForSplit3(String speech1, String speech2) {\n" //
+				+ "       // Keep this comment\n" //
+				+ "       String line= \"\\\\a\";\n" //
+				+ "\n" //
+				+ "       // Keep this comment too\n" //
+				+ "       String[] phrases1= speech1.split(line);\n" //
+				+ "       // Keep this comment also\n" //
+				+ "       String[] phrases2= speech2.split(line, 123);\n" //
+				+ "\n" //
+				+ "       return Arrays.toString(phrases1) + Arrays.toString(phrases2);\n" //
+				+ "    }\n" //
+				+ "\n" //
 				+ "    public String usePatternForLocalVariableOnly(String date1, String date2, String date3) {\n" //
 				+ "       String dateText1= date1.replaceFirst(dateValidation, \"0000-00-00\");\n" //
 				+ "       // Keep this comment\n" //
@@ -7672,7 +7696,7 @@
 				+ "       return dateText1 + dateText2;\n" //
 				+ "    }\n" //
 				+ "\n" //
-				+ "    public String usePatternForSplit(String speech1, String speech2) {\n" //
+				+ "    public String usePatternForSplit1(String speech1, String speech2) {\n" //
 				+ "       // Keep this comment\n" //
 				+ "       Pattern line= Pattern.compile(\"\\\\r?\\\\n\");\n" //
 				+ "\n" //
@@ -7684,6 +7708,30 @@
 				+ "       return Arrays.toString(phrases1) + Arrays.toString(phrases2);\n" //
 				+ "    }\n" //
 				+ "\n" //
+				+ "    public String usePatternForSplit2(String speech1, String speech2) {\n" //
+				+ "       // Keep this comment\n" //
+				+ "       Pattern line= Pattern.compile(\".\");\n" //
+				+ "\n" //
+				+ "       // Keep this comment too\n" //
+				+ "       String[] phrases1= line.split(speech1);\n" //
+				+ "       // Keep this comment also\n" //
+				+ "       String[] phrases2= line.split(speech2, 123);\n" //
+				+ "\n" //
+				+ "       return Arrays.toString(phrases1) + Arrays.toString(phrases2);\n" //
+				+ "    }\n" //
+				+ "\n" //
+				+ "    public String usePatternForSplit3(String speech1, String speech2) {\n" //
+				+ "       // Keep this comment\n" //
+				+ "       Pattern line= Pattern.compile(\"\\\\a\");\n" //
+				+ "\n" //
+				+ "       // Keep this comment too\n" //
+				+ "       String[] phrases1= line.split(speech1);\n" //
+				+ "       // Keep this comment also\n" //
+				+ "       String[] phrases2= line.split(speech2, 123);\n" //
+				+ "\n" //
+				+ "       return Arrays.toString(phrases1) + Arrays.toString(phrases2);\n" //
+				+ "    }\n" //
+				+ "\n" //
 				+ "    public String usePatternForLocalVariableOnly(String date1, String date2, String date3) {\n" //
 				+ "       String dateText1= date1.replaceFirst(dateValidation, \"0000-00-00\");\n" //
 				+ "       // Keep this comment\n" //
@@ -7758,6 +7806,20 @@
 				+ "\n" //
 				+ "       return dateText1 + dateText2;\n" //
 				+ "    }\n" //
+				+ "    public String doNotUsePatternOnSimpleSplit1(String speech1, String speech2) {\n" //
+				+ "       String line= \"a\";\n" //
+				+ "\n" //
+				+ "       String[] phrases1= speech1.split(line);\n" //
+				+ "       String[] phrases2= speech2.split(line, 1);\n" //
+				+ "       return phrases1[0] + phrases2[0];\n" //
+				+ "    }\n" //
+				+ "    public String doNotUsePatternOnSimpleSplit2(String speech1, String speech2) {\n" //
+				+ "       String line= \"\\\\;\";\n" //
+				+ "\n" //
+				+ "       String[] phrases1= speech1.split(line1);\n" //
+				+ "       String[] phrases2= speech2.split(line1, 1);\n" //
+				+ "       return phrases1[0] + phrases2[0];\n" //
+				+ "    }\n" //
 				+ "}\n";
 		ICompilationUnit cu= pack1.createCompilationUnit("E1.java", sample, false, null);
 
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/PatternCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/PatternCleanUp.java
index 04e54ad..fb961c5 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/PatternCleanUp.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/PatternCleanUp.java
@@ -25,15 +25,18 @@
 
 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.Block;
 import org.eclipse.jdt.core.dom.CompilationUnit;
 import org.eclipse.jdt.core.dom.Expression;
+import org.eclipse.jdt.core.dom.IBinding;
 import org.eclipse.jdt.core.dom.IVariableBinding;
 import org.eclipse.jdt.core.dom.MethodInvocation;
 import org.eclipse.jdt.core.dom.Name;
 import org.eclipse.jdt.core.dom.SimpleName;
 import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
+import org.eclipse.jdt.core.dom.StringLiteral;
 import org.eclipse.jdt.core.dom.Type;
 import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
 import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
@@ -186,7 +189,7 @@
 
 						if (writes.size() == 1 && reads.size() > 1) {
 							for (SimpleName simpleName : reads) {
-								if (!isRegExUse(simpleName)) {
+								if (!isRegExUse(simpleName, initializer)) {
 									return true;
 								}
 							}
@@ -200,7 +203,7 @@
 					return true;
 				}
 
-				private boolean isRegExUse(final SimpleName simpleName) {
+				private boolean isRegExUse(final SimpleName simpleName, final Expression initializer) {
 					if (simpleName.getParent() instanceof MethodInvocation) {
 						MethodInvocation methodInvocation= (MethodInvocation) simpleName.getParent();
 
@@ -209,9 +212,48 @@
 								&& simpleName.equals(methodInvocation.arguments().get(0))) {
 							if (ASTNodes.usesGivenSignature(methodInvocation, STRING_CLASS_NAME, MATCHES_METHOD, STRING_CLASS_NAME)
 									|| ASTNodes.usesGivenSignature(methodInvocation, STRING_CLASS_NAME, REPLACE_ALL_METHOD, STRING_CLASS_NAME, STRING_CLASS_NAME)
-									|| ASTNodes.usesGivenSignature(methodInvocation, STRING_CLASS_NAME, REPLACE_FIRST_METHOD, STRING_CLASS_NAME, STRING_CLASS_NAME)
-									|| ASTNodes.usesGivenSignature(methodInvocation, STRING_CLASS_NAME, SPLIT_METHOD, STRING_CLASS_NAME)
+									|| ASTNodes.usesGivenSignature(methodInvocation, STRING_CLASS_NAME, REPLACE_FIRST_METHOD, STRING_CLASS_NAME, STRING_CLASS_NAME)) {
+								return true;
+							} else if (ASTNodes.usesGivenSignature(methodInvocation, STRING_CLASS_NAME, SPLIT_METHOD, STRING_CLASS_NAME)
 									|| ASTNodes.usesGivenSignature(methodInvocation, STRING_CLASS_NAME, SPLIT_METHOD, STRING_CLASS_NAME, int.class.getCanonicalName())) {
+								String value= null;
+								Expression expression= ASTNodes.getUnparenthesedExpression(initializer);
+
+								if (expression instanceof StringLiteral) {
+									StringLiteral literal= (StringLiteral) expression;
+									value= literal.getLiteralValue();
+								} else if (expression instanceof Name) {
+									Name name= (Name) expression;
+									IBinding binding= name.resolveBinding();
+
+									if (binding instanceof IVariableBinding) {
+										ASTNode aSTNode= ASTNodes.findDeclaration(binding, startNode);
+
+										if (aSTNode instanceof VariableDeclarationFragment) {
+											Expression exp= ((VariableDeclarationFragment) aSTNode).getInitializer();
+
+											if (exp != null) {
+												exp= ASTNodes.getUnparenthesedExpression(exp);
+
+												if (exp instanceof StringLiteral) {
+													value= ((StringLiteral) exp).getLiteralValue();
+												}
+											}
+										}
+									}
+								}
+
+								if (value != null) {
+									// Don't compile pattern for single character that doesn't use regex meta-chars
+									// and don't compile pattern for double character where first char is back-slash
+									// and second character isn't alpha-numeric
+									if (value.length() == 1) {
+										return ".$|()[{^?*+\\".contains(value); //$NON-NLS-1$
+									} else if (value.length() == 2 && value.charAt(0) == '\\') {
+										return Character.isLetterOrDigit(value.charAt(1));
+									}
+								}
+
 								return true;
 							}
 						}