diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java index 7f4daf49a4..379b86f4c6 100644 --- a/spring-core/src/main/java/org/springframework/util/StringUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java @@ -772,19 +772,23 @@ public abstract class StringUtils { public static Locale parseLocale(String localeValue) { String[] tokens = tokenizeLocaleSource(localeValue); if (tokens.length == 1) { - return Locale.forLanguageTag(localeValue); + Locale resolved = Locale.forLanguageTag(localeValue); + return (resolved.getLanguage().length() > 0 ? resolved : null); } return parseLocaleTokens(localeValue, tokens); } /** * Parse the given {@code String} representation into a {@link Locale}. - *

This is the inverse operation of {@link Locale#toString Locale's toString}. + *

For many parsing scenarios, this is an inverse operation of + * {@link Locale#toString Locale's toString}, in a lenient sense. + * This method does not aim for strict {@code Locale} design compliance; + * it is rather specifically tailored for typical Spring parsing needs. + *

Note: This delegate does not accept the BCP 47 language tag format. + * Please use {@link #parseLocale} for lenient parsing of both formats. * @param localeString the locale {@code String}: following {@code Locale's} * {@code toString()} format ("en", "en_UK", etc), also accepting spaces as * separators (as an alternative to underscores) - *

Note: This variant does not accept the BCP 47 language tag format. - * Please use {@link #parseLocale} for lenient parsing of both formats. * @return a corresponding {@code Locale} instance, or {@code null} if none * @throws IllegalArgumentException in case of an invalid locale specification */ @@ -815,6 +819,12 @@ public abstract class StringUtils { variant = trimLeadingCharacter(variant, '_'); } } + + if ("".equals(variant) && country.startsWith("#")) { + variant = country; + country = ""; + } + return (language.length() > 0 ? new Locale(language, country, variant) : null); } diff --git a/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java b/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java index 80e83aba88..9e382c1849 100644 --- a/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java @@ -734,9 +734,35 @@ public class StringUtilsTests { assertEquals("Variant containing country code not extracted correctly", variant, locale.getVariant()); } - @Test // SPR-14718 + @Test // SPR-14718, SPR-7598 public void testParseJava7Variant() { - assertEquals("sr_#LATN", StringUtils.parseLocaleString("sr_#LATN").toString()); + assertEquals("sr__#LATN", StringUtils.parseLocaleString("sr__#LATN").toString()); + } + + @Test // SPR-16651 + public void testAvailableLocalesWithLocaleString() { + for (Locale locale : Locale.getAvailableLocales()) { + Locale parsedLocale = StringUtils.parseLocaleString(locale.toString()); + if (parsedLocale == null) { + assertEquals("", locale.getLanguage()); + } + else { + assertEquals(parsedLocale.toString(), locale.toString()); + } + } + } + + @Test // SPR-16651 + public void testAvailableLocalesWithLanguageTag() { + for (Locale locale : Locale.getAvailableLocales()) { + Locale parsedLocale = StringUtils.parseLocale(locale.toLanguageTag()); + if (parsedLocale == null) { + assertEquals("", locale.getLanguage()); + } + else { + assertEquals(parsedLocale.toLanguageTag(), locale.toLanguageTag()); + } + } } }