diff --git a/src/main/java/org/apache/commons/lang3/LocaleUtils.java b/src/main/java/org/apache/commons/lang3/LocaleUtils.java index d990747c5..24b7f3e52 100644 --- a/src/main/java/org/apache/commons/lang3/LocaleUtils.java +++ b/src/main/java/org/apache/commons/lang3/LocaleUtils.java @@ -37,6 +37,21 @@ */ public class LocaleUtils { + //----------------------------------------------------------------------- + // class to avoid synchronization (Init on demand) + static class SyncAvoid { + /** Unmodifiable list of available locales. */ + private static final List AVAILABLE_LOCALE_LIST; + /** Unmodifiable set of available locales. */ + private static final Set AVAILABLE_LOCALE_SET; + + static { + final List list = new ArrayList<>(Arrays.asList(Locale.getAvailableLocales())); // extra safe + AVAILABLE_LOCALE_LIST = Collections.unmodifiableList(list); + AVAILABLE_LOCALE_SET = Collections.unmodifiableSet(new HashSet<>(list)); + } + } + /** Concurrent map of language locales by country. */ private static final ConcurrentMap> cLanguagesByCountry = new ConcurrentHashMap<>(); @@ -45,14 +60,220 @@ public class LocaleUtils { private static final ConcurrentMap> cCountriesByLanguage = new ConcurrentHashMap<>(); + //----------------------------------------------------------------------- /** - *

{@code LocaleUtils} instances should NOT be constructed in standard programming. - * Instead, the class should be used as {@code LocaleUtils.toLocale("en_GB");}.

+ *

Obtains an unmodifiable list of installed locales.

* - *

This constructor is public to permit tools that require a JavaBean instance - * to operate.

+ *

This method is a wrapper around {@link Locale#getAvailableLocales()}. + * It is more efficient, as the JDK method must create a new array each + * time it is called.

+ * + * @return the unmodifiable list of available locales */ - public LocaleUtils() { + public static List availableLocaleList() { + return SyncAvoid.AVAILABLE_LOCALE_LIST; + } + + //----------------------------------------------------------------------- + /** + *

Obtains an unmodifiable set of installed locales.

+ * + *

This method is a wrapper around {@link Locale#getAvailableLocales()}. + * It is more efficient, as the JDK method must create a new array each + * time it is called.

+ * + * @return the unmodifiable set of available locales + */ + public static Set availableLocaleSet() { + return SyncAvoid.AVAILABLE_LOCALE_SET; + } + + //----------------------------------------------------------------------- + /** + *

Obtains the list of countries supported for a given language.

+ * + *

This method takes a language code and searches to find the + * countries available for that language. Variant locales are removed.

+ * + * @param languageCode the 2 letter language code, null returns empty + * @return an unmodifiable List of Locale objects, not null + */ + public static List countriesByLanguage(final String languageCode) { + if (languageCode == null) { + return Collections.emptyList(); + } + List countries = cCountriesByLanguage.get(languageCode); + if (countries == null) { + countries = new ArrayList<>(); + final List locales = availableLocaleList(); + for (final Locale locale : locales) { + if (languageCode.equals(locale.getLanguage()) && + !locale.getCountry().isEmpty() && + locale.getVariant().isEmpty()) { + countries.add(locale); + } + } + countries = Collections.unmodifiableList(countries); + cCountriesByLanguage.putIfAbsent(languageCode, countries); + countries = cCountriesByLanguage.get(languageCode); + } + return countries; + } + + //----------------------------------------------------------------------- + /** + *

Checks if the locale specified is in the list of available locales.

+ * + * @param locale the Locale object to check if it is available + * @return true if the locale is a known locale + */ + public static boolean isAvailableLocale(final Locale locale) { + return availableLocaleList().contains(locale); + } + + /** + * Checks whether the given String is a ISO 3166 alpha-2 country code. + * + * @param str the String to check + * @return true, is the given String is a ISO 3166 compliant country code. + */ + private static boolean isISO3166CountryCode(final String str) { + return StringUtils.isAllUpperCase(str) && str.length() == 2; + } + + /** + * Checks whether the given String is a ISO 639 compliant language code. + * + * @param str the String to check. + * @return true, if the given String is a ISO 639 compliant language code. + */ + private static boolean isISO639LanguageCode(final String str) { + return StringUtils.isAllLowerCase(str) && (str.length() == 2 || str.length() == 3); + } + + /** + * Checks whether the given String is a UN M.49 numeric area code. + * + * @param str the String to check + * @return true, is the given String is a UN M.49 numeric area code. + */ + private static boolean isNumericAreaCode(final String str) { + return StringUtils.isNumeric(str) && str.length() == 3; + } + + //----------------------------------------------------------------------- + /** + *

Obtains the list of languages supported for a given country.

+ * + *

This method takes a country code and searches to find the + * languages available for that country. Variant locales are removed.

+ * + * @param countryCode the 2 letter country code, null returns empty + * @return an unmodifiable List of Locale objects, not null + */ + public static List languagesByCountry(final String countryCode) { + if (countryCode == null) { + return Collections.emptyList(); + } + List langs = cLanguagesByCountry.get(countryCode); + if (langs == null) { + langs = new ArrayList<>(); + final List locales = availableLocaleList(); + for (final Locale locale : locales) { + if (countryCode.equals(locale.getCountry()) && + locale.getVariant().isEmpty()) { + langs.add(locale); + } + } + langs = Collections.unmodifiableList(langs); + cLanguagesByCountry.putIfAbsent(countryCode, langs); + langs = cLanguagesByCountry.get(countryCode); + } + return langs; + } + + //----------------------------------------------------------------------- + /** + *

Obtains the list of locales to search through when performing + * a locale search.

+ * + *
+     * localeLookupList(Locale("fr", "CA", "xxx"))
+     *   = [Locale("fr", "CA", "xxx"), Locale("fr", "CA"), Locale("fr")]
+     * 
+ * + * @param locale the locale to start from + * @return the unmodifiable list of Locale objects, 0 being locale, not null + */ + public static List localeLookupList(final Locale locale) { + return localeLookupList(locale, locale); + } + + //----------------------------------------------------------------------- + /** + *

Obtains the list of locales to search through when performing + * a locale search.

+ * + *
+     * localeLookupList(Locale("fr", "CA", "xxx"), Locale("en"))
+     *   = [Locale("fr", "CA", "xxx"), Locale("fr", "CA"), Locale("fr"), Locale("en"]
+     * 
+ * + *

The result list begins with the most specific locale, then the + * next more general and so on, finishing with the default locale. + * The list will never contain the same locale twice.

+ * + * @param locale the locale to start from, null returns empty list + * @param defaultLocale the default locale to use if no other is found + * @return the unmodifiable list of Locale objects, 0 being locale, not null + */ + public static List localeLookupList(final Locale locale, final Locale defaultLocale) { + final List list = new ArrayList<>(4); + if (locale != null) { + list.add(locale); + if (!locale.getVariant().isEmpty()) { + list.add(new Locale(locale.getLanguage(), locale.getCountry())); + } + if (!locale.getCountry().isEmpty()) { + list.add(new Locale(locale.getLanguage(), StringUtils.EMPTY)); + } + if (!list.contains(defaultLocale)) { + list.add(defaultLocale); + } + } + return Collections.unmodifiableList(list); + } + + /** + * Tries to parse a locale from the given String. + * + * @param str the String to parse a locale from. + * @return a Locale instance parsed from the given String. + * @throws IllegalArgumentException if the given String can not be parsed. + */ + private static Locale parseLocale(final String str) { + if (isISO639LanguageCode(str)) { + return new Locale(str); + } + + final String[] segments = str.split("_", -1); + final String language = segments[0]; + if (segments.length == 2) { + final String country = segments[1]; + if (isISO639LanguageCode(language) && isISO3166CountryCode(country) || + isNumericAreaCode(country)) { + return new Locale(language, country); + } + } else if (segments.length == 3) { + final String country = segments[1]; + final String variant = segments[2]; + if (isISO639LanguageCode(language) && + (country.isEmpty() || isISO3166CountryCode(country) || isNumericAreaCode(country)) && + !variant.isEmpty()) { + return new Locale(language, country, variant); + } + } + throw new IllegalArgumentException("Invalid locale format: " + str); } //----------------------------------------------------------------------- @@ -126,234 +347,13 @@ public static Locale toLocale(final String str) { } /** - * Tries to parse a locale from the given String. + *

{@code LocaleUtils} instances should NOT be constructed in standard programming. + * Instead, the class should be used as {@code LocaleUtils.toLocale("en_GB");}.

* - * @param str the String to parse a locale from. - * @return a Locale instance parsed from the given String. - * @throws IllegalArgumentException if the given String can not be parsed. + *

This constructor is public to permit tools that require a JavaBean instance + * to operate.

*/ - private static Locale parseLocale(final String str) { - if (isISO639LanguageCode(str)) { - return new Locale(str); - } - - final String[] segments = str.split("_", -1); - final String language = segments[0]; - if (segments.length == 2) { - final String country = segments[1]; - if (isISO639LanguageCode(language) && isISO3166CountryCode(country) || - isNumericAreaCode(country)) { - return new Locale(language, country); - } - } else if (segments.length == 3) { - final String country = segments[1]; - final String variant = segments[2]; - if (isISO639LanguageCode(language) && - (country.isEmpty() || isISO3166CountryCode(country) || isNumericAreaCode(country)) && - !variant.isEmpty()) { - return new Locale(language, country, variant); - } - } - throw new IllegalArgumentException("Invalid locale format: " + str); - } - - /** - * Checks whether the given String is a ISO 639 compliant language code. - * - * @param str the String to check. - * @return true, if the given String is a ISO 639 compliant language code. - */ - private static boolean isISO639LanguageCode(final String str) { - return StringUtils.isAllLowerCase(str) && (str.length() == 2 || str.length() == 3); - } - - /** - * Checks whether the given String is a ISO 3166 alpha-2 country code. - * - * @param str the String to check - * @return true, is the given String is a ISO 3166 compliant country code. - */ - private static boolean isISO3166CountryCode(final String str) { - return StringUtils.isAllUpperCase(str) && str.length() == 2; - } - - /** - * Checks whether the given String is a UN M.49 numeric area code. - * - * @param str the String to check - * @return true, is the given String is a UN M.49 numeric area code. - */ - private static boolean isNumericAreaCode(final String str) { - return StringUtils.isNumeric(str) && str.length() == 3; - } - - //----------------------------------------------------------------------- - /** - *

Obtains the list of locales to search through when performing - * a locale search.

- * - *
-     * localeLookupList(Locale("fr", "CA", "xxx"))
-     *   = [Locale("fr", "CA", "xxx"), Locale("fr", "CA"), Locale("fr")]
-     * 
- * - * @param locale the locale to start from - * @return the unmodifiable list of Locale objects, 0 being locale, not null - */ - public static List localeLookupList(final Locale locale) { - return localeLookupList(locale, locale); - } - - //----------------------------------------------------------------------- - /** - *

Obtains the list of locales to search through when performing - * a locale search.

- * - *
-     * localeLookupList(Locale("fr", "CA", "xxx"), Locale("en"))
-     *   = [Locale("fr", "CA", "xxx"), Locale("fr", "CA"), Locale("fr"), Locale("en"]
-     * 
- * - *

The result list begins with the most specific locale, then the - * next more general and so on, finishing with the default locale. - * The list will never contain the same locale twice.

- * - * @param locale the locale to start from, null returns empty list - * @param defaultLocale the default locale to use if no other is found - * @return the unmodifiable list of Locale objects, 0 being locale, not null - */ - public static List localeLookupList(final Locale locale, final Locale defaultLocale) { - final List list = new ArrayList<>(4); - if (locale != null) { - list.add(locale); - if (!locale.getVariant().isEmpty()) { - list.add(new Locale(locale.getLanguage(), locale.getCountry())); - } - if (!locale.getCountry().isEmpty()) { - list.add(new Locale(locale.getLanguage(), StringUtils.EMPTY)); - } - if (!list.contains(defaultLocale)) { - list.add(defaultLocale); - } - } - return Collections.unmodifiableList(list); - } - - //----------------------------------------------------------------------- - /** - *

Obtains an unmodifiable list of installed locales.

- * - *

This method is a wrapper around {@link Locale#getAvailableLocales()}. - * It is more efficient, as the JDK method must create a new array each - * time it is called.

- * - * @return the unmodifiable list of available locales - */ - public static List availableLocaleList() { - return SyncAvoid.AVAILABLE_LOCALE_LIST; - } - - //----------------------------------------------------------------------- - /** - *

Obtains an unmodifiable set of installed locales.

- * - *

This method is a wrapper around {@link Locale#getAvailableLocales()}. - * It is more efficient, as the JDK method must create a new array each - * time it is called.

- * - * @return the unmodifiable set of available locales - */ - public static Set availableLocaleSet() { - return SyncAvoid.AVAILABLE_LOCALE_SET; - } - - //----------------------------------------------------------------------- - /** - *

Checks if the locale specified is in the list of available locales.

- * - * @param locale the Locale object to check if it is available - * @return true if the locale is a known locale - */ - public static boolean isAvailableLocale(final Locale locale) { - return availableLocaleList().contains(locale); - } - - //----------------------------------------------------------------------- - /** - *

Obtains the list of languages supported for a given country.

- * - *

This method takes a country code and searches to find the - * languages available for that country. Variant locales are removed.

- * - * @param countryCode the 2 letter country code, null returns empty - * @return an unmodifiable List of Locale objects, not null - */ - public static List languagesByCountry(final String countryCode) { - if (countryCode == null) { - return Collections.emptyList(); - } - List langs = cLanguagesByCountry.get(countryCode); - if (langs == null) { - langs = new ArrayList<>(); - final List locales = availableLocaleList(); - for (final Locale locale : locales) { - if (countryCode.equals(locale.getCountry()) && - locale.getVariant().isEmpty()) { - langs.add(locale); - } - } - langs = Collections.unmodifiableList(langs); - cLanguagesByCountry.putIfAbsent(countryCode, langs); - langs = cLanguagesByCountry.get(countryCode); - } - return langs; - } - - //----------------------------------------------------------------------- - /** - *

Obtains the list of countries supported for a given language.

- * - *

This method takes a language code and searches to find the - * countries available for that language. Variant locales are removed.

- * - * @param languageCode the 2 letter language code, null returns empty - * @return an unmodifiable List of Locale objects, not null - */ - public static List countriesByLanguage(final String languageCode) { - if (languageCode == null) { - return Collections.emptyList(); - } - List countries = cCountriesByLanguage.get(languageCode); - if (countries == null) { - countries = new ArrayList<>(); - final List locales = availableLocaleList(); - for (final Locale locale : locales) { - if (languageCode.equals(locale.getLanguage()) && - !locale.getCountry().isEmpty() && - locale.getVariant().isEmpty()) { - countries.add(locale); - } - } - countries = Collections.unmodifiableList(countries); - cCountriesByLanguage.putIfAbsent(languageCode, countries); - countries = cCountriesByLanguage.get(languageCode); - } - return countries; - } - - //----------------------------------------------------------------------- - // class to avoid synchronization (Init on demand) - static class SyncAvoid { - /** Unmodifiable list of available locales. */ - private static final List AVAILABLE_LOCALE_LIST; - /** Unmodifiable set of available locales. */ - private static final Set AVAILABLE_LOCALE_SET; - - static { - final List list = new ArrayList<>(Arrays.asList(Locale.getAvailableLocales())); // extra safe - AVAILABLE_LOCALE_LIST = Collections.unmodifiableList(list); - AVAILABLE_LOCALE_SET = Collections.unmodifiableSet(new HashSet<>(list)); - } + public LocaleUtils() { } }