Better regular expression exception detail messages.

Before:
    Caused by: java.util.regex.PatternSyntaxException: Syntax error U_REGEX_MISSING_CLOSE_BRACKET near index 6:
    hello[
          ^
    	at java.util.regex.Pattern.compileImpl(Native Method)
    	at java.util.regex.Pattern.compile(Pattern.java:400)
    	at java.util.regex.Pattern.<init>(Pattern.java:383)
    	at java.util.regex.Pattern.compile(Pattern.java:374)

After:
    Caused by: java.util.regex.PatternSyntaxException: Missing closing bracket in character class near index 6:
    hello[
          ^
    	at java.util.regex.Pattern.compileImpl(Native Method)
    	at java.util.regex.Pattern.compile(Pattern.java:400)
    	at java.util.regex.Pattern.<init>(Pattern.java:383)
    	at java.util.regex.Pattern.compile(Pattern.java:374)

Some of the messages might still want some tuning, but we can improve them
over time.

Bug: 2974820
Change-Id: I4f41582030168847d22c5323125fde6928d0fc40
diff --git a/luni/src/main/java/java/util/regex/PatternSyntaxException.java b/luni/src/main/java/java/util/regex/PatternSyntaxException.java
index a8dec47..0c1de4b 100644
--- a/luni/src/main/java/java/util/regex/PatternSyntaxException.java
+++ b/luni/src/main/java/java/util/regex/PatternSyntaxException.java
@@ -88,31 +88,34 @@
      */
     @Override
     public String getMessage() {
-        StringBuilder builder = new StringBuilder("Syntax error");
-
+        StringBuilder sb = new StringBuilder();
         if (desc != null) {
-            builder.append(' ');
-            builder.append(desc);
+            sb.append(desc);
         }
 
         if (index >= 0) {
-            builder.append(" near index " + index + ":");
+            if (desc != null) {
+                sb.append(' ');
+            }
+            sb.append("near index ");
+            sb.append(index);
+            sb.append(':');
         }
 
         if (pattern != null) {
-            builder.append('\n');
-            builder.append(pattern);
+            sb.append('\n');
+            sb.append(pattern);
 
             if (index >= 0) {
                 char[] spaces = new char[index];
                 Arrays.fill(spaces, ' ');
-                builder.append('\n');
-                builder.append(spaces);
-                builder.append('^');
+                sb.append('\n');
+                sb.append(spaces);
+                sb.append('^');
             }
         }
 
-        return builder.toString();
+        return sb.toString();
     }
 
     /**
diff --git a/luni/src/main/native/java_util_regex_Pattern.cpp b/luni/src/main/native/java_util_regex_Pattern.cpp
index 3a1a840..1e0e1e3 100644
--- a/luni/src/main/native/java_util_regex_Pattern.cpp
+++ b/luni/src/main/native/java_util_regex_Pattern.cpp
@@ -31,10 +31,41 @@
     return reinterpret_cast<RegexPattern*>(static_cast<uintptr_t>(addr));
 }
 
+static const char* regexDetailMessage(UErrorCode status) {
+    // These human-readable error messages were culled from "utypes.h", and then slightly tuned
+    // to make more sense in context.
+    // If we don't have a special-case, we'll just return the textual name of
+    // the enum value (such as U_REGEX_RULE_SYNTAX), which is better than nothing.
+    switch (status) {
+    case U_REGEX_INTERNAL_ERROR: return "An internal error was detected";
+    case U_REGEX_RULE_SYNTAX: return "Syntax error in regexp pattern";
+    case U_REGEX_INVALID_STATE: return "Matcher in invalid state for requested operation";
+    case U_REGEX_BAD_ESCAPE_SEQUENCE: return "Unrecognized backslash escape sequence in pattern";
+    case U_REGEX_PROPERTY_SYNTAX: return "Incorrect Unicode property";
+    case U_REGEX_UNIMPLEMENTED: return "Use of unimplemented feature";
+    case U_REGEX_MISMATCHED_PAREN: return "Incorrectly nested parentheses in regexp pattern";
+    case U_REGEX_NUMBER_TOO_BIG: return "Decimal number is too large";
+    case U_REGEX_BAD_INTERVAL: return "Error in {min,max} interval";
+    case U_REGEX_MAX_LT_MIN: return "In {min,max}, max is less than min";
+    case U_REGEX_INVALID_BACK_REF: return "Back-reference to a non-existent capture group";
+    case U_REGEX_INVALID_FLAG: return "Invalid value for match mode flags";
+    case U_REGEX_LOOK_BEHIND_LIMIT: return "Look-behind pattern matches must have a bounded maximum length";
+    case U_REGEX_SET_CONTAINS_STRING: return "Regular expressions cannot have UnicodeSets containing strings";
+    case U_REGEX_OCTAL_TOO_BIG: return "Octal character constants must be <= 0377.";
+    case U_REGEX_MISSING_CLOSE_BRACKET: return "Missing closing bracket in character class";
+    case U_REGEX_INVALID_RANGE: return "In a character range [x-y], x is greater than y";
+    case U_REGEX_STACK_OVERFLOW: return "Regular expression backtrack stack overflow";
+    case U_REGEX_TIME_OUT: return "Maximum allowed match time exceeded";
+    case U_REGEX_STOPPED_BY_CALLER: return "Matching operation aborted by user callback function";
+    default:
+        return u_errorName(status);
+    }
+}
+
 static void throwPatternSyntaxException(JNIEnv* env, UErrorCode status, jstring pattern, UParseError error) {
     static jmethodID method = env->GetMethodID(JniConstants::patternSyntaxExceptionClass,
             "<init>", "(Ljava/lang/String;Ljava/lang/String;I)V");
-    jstring message = env->NewStringUTF(u_errorName(status));
+    jstring message = env->NewStringUTF(regexDetailMessage(status));
     jclass exceptionClass = JniConstants::patternSyntaxExceptionClass;
     jobject exception = env->NewObject(exceptionClass, method, message, pattern, error.offset);
     env->Throw(reinterpret_cast<jthrowable>(exception));