[LANG-865] LocaleUtils.toLocale does not parse strings starting with an underscore.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@1428174 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 4263d9a..a8a3769 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -22,6 +22,7 @@
   <body>
 
   <release version="3.2" date="TBA" description="Next release">
+    <action issue="LANG-865" type="fix">LocaleUtils.toLocale does not parse strings starting with an underscore</action>
     <action issue="LANG-835" type="add">StrBuilder should support StringBuilder as an input parameter</action>
     <action issue="LANG-858" type="fix">StringEscapeUtils.escapeJava() and escapeEcmaScript() do not output the escaped surrogate pairs that are Java parsable</action>
     <action issue="LANG-857" type="add">StringIndexOutOfBoundsException in CharSequenceTranslator</action>
diff --git a/src/main/java/org/apache/commons/lang3/LocaleUtils.java b/src/main/java/org/apache/commons/lang3/LocaleUtils.java
index 7568f2c..a7a6051 100644
--- a/src/main/java/org/apache/commons/lang3/LocaleUtils.java
+++ b/src/main/java/org/apache/commons/lang3/LocaleUtils.java
@@ -85,41 +85,66 @@
      * @return a Locale, null if null input
      * @throws IllegalArgumentException if the string is an invalid format
      */
-    public static Locale toLocale(String str) {
+    public static Locale toLocale(final String str) {
         if (str == null) {
             return null;
         }
-        int len = str.length();
-        if (len != 2 && len != 5 && len < 7) {
+        final int len = str.length();
+        if (len < 2) {
             throw new IllegalArgumentException("Invalid locale format: " + str);
         }
-        char ch0 = str.charAt(0);
-        char ch1 = str.charAt(1);
-        if (ch0 < 'a' || ch0 > 'z' || ch1 < 'a' || ch1 > 'z') {
-            throw new IllegalArgumentException("Invalid locale format: " + str);
-        }
-        if (len == 2) {
-            return new Locale(str, "");
+        final char ch0 = str.charAt(0);
+        if (ch0 == '_') {
+            if (len < 3) {
+                throw new IllegalArgumentException("Invalid locale format: " + str);
+            }
+            final char ch1 = str.charAt(1);
+            final char ch2 = str.charAt(2);
+            if (!Character.isUpperCase(ch1) || !Character.isUpperCase(ch2)) {
+                throw new IllegalArgumentException("Invalid locale format: " + str);
+            }
+            if (len == 3) {
+                return new Locale("", str.substring(1, 3));
+            }
+            if (len < 5) {
+                throw new IllegalArgumentException("Invalid locale format: " + str);
+            }
+            if (str.charAt(3) != '_') {
+                throw new IllegalArgumentException("Invalid locale format: " + str);
+            }
+            return new Locale("", str.substring(1, 3), str.substring(4));
         } else {
+            final char ch1 = str.charAt(1);
+            if (!Character.isLowerCase(ch0) || !Character.isLowerCase(ch1)) {
+                throw new IllegalArgumentException("Invalid locale format: " + str);
+            }
+            if (len == 2) {
+                return new Locale(str);
+            }
+            if (len < 5) {
+                throw new IllegalArgumentException("Invalid locale format: " + str);
+            }
             if (str.charAt(2) != '_') {
                 throw new IllegalArgumentException("Invalid locale format: " + str);
             }
-            char ch3 = str.charAt(3);
+            final char ch3 = str.charAt(3);
             if (ch3 == '_') {
                 return new Locale(str.substring(0, 2), "", str.substring(4));
             }
-            char ch4 = str.charAt(4);
-            if (ch3 < 'A' || ch3 > 'Z' || ch4 < 'A' || ch4 > 'Z') {
+            final char ch4 = str.charAt(4);
+            if (!Character.isUpperCase(ch3) || !Character.isUpperCase(ch4)) {
                 throw new IllegalArgumentException("Invalid locale format: " + str);
             }
             if (len == 5) {
                 return new Locale(str.substring(0, 2), str.substring(3, 5));
-            } else {
-                if (str.charAt(5) != '_') {
-                    throw new IllegalArgumentException("Invalid locale format: " + str);
-                }
-                return new Locale(str.substring(0, 2), str.substring(3, 5), str.substring(6));
             }
+            if (len < 7) {
+                throw new IllegalArgumentException("Invalid locale format: " + str);
+            }
+            if (str.charAt(5) != '_') {
+                throw new IllegalArgumentException("Invalid locale format: " + str);
+            }
+            return new Locale(str.substring(0, 2), str.substring(3, 5), str.substring(6));
         }
     }
 
diff --git a/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java b/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java
index 84e3b66..b30b3e3 100644
--- a/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java
@@ -493,7 +493,53 @@
      */
     @Test
     public void testLang328() {
+        assertValidToLocale("fr__P", "fr", "", "P");
         assertValidToLocale("fr__POSIX", "fr", "", "POSIX");
     }
 
+    /**
+     * Tests #LANG-865, strings starting with an underscore.
+     */
+    @Test
+    public void testLang865() {
+        assertValidToLocale("_GB", "", "GB", "");
+        assertValidToLocale("_GB_P", "", "GB", "P");
+        assertValidToLocale("_GB_POSIX", "", "GB", "POSIX");
+        try {
+            LocaleUtils.toLocale("_G");
+            fail("Must be at least 3 chars if starts with underscore");
+        } catch (final IllegalArgumentException iae) {
+        }
+        try {
+            LocaleUtils.toLocale("_Gb");
+            fail("Must be uppercase if starts with underscore");
+        } catch (final IllegalArgumentException iae) {
+        }
+        try {
+            LocaleUtils.toLocale("_gB");
+            fail("Must be uppercase if starts with underscore");
+        } catch (final IllegalArgumentException iae) {
+        }
+        try {
+            LocaleUtils.toLocale("_1B");
+            fail("Must be letter if starts with underscore");
+        } catch (final IllegalArgumentException iae) {
+        }
+        try {
+            LocaleUtils.toLocale("_G1");
+            fail("Must be letter if starts with underscore");
+        } catch (final IllegalArgumentException iae) {
+        }
+        try {
+            LocaleUtils.toLocale("_GB_");
+            fail("Must be at least 5 chars if starts with underscore");
+        } catch (final IllegalArgumentException iae) {
+        }
+        try {
+            LocaleUtils.toLocale("_GBAP");
+            fail("Must have underscore after the country if starts with underscore and is at least 5 chars");
+        } catch (final IllegalArgumentException iae) {
+        }
+    }
+
 }