panda.lang.Locales Maven / Gradle / Ivy
Show all versions of panda-core Show documentation
package panda.lang;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import panda.io.FileNames;
/**
* utility class for Locale.
*/
public abstract class Locales {
/** Concurrent map of language locales by country. */
private static final ConcurrentMap> cLanguagesByCountry = new ConcurrentHashMap>();
/** Concurrent map of country locales by language. */
private static final ConcurrentMap> cCountriesByLanguage = new ConcurrentHashMap>();
/**
* Builds a {@link java.util.Locale} from a String of the form en_US_foo into a Locale with
* language "en", country "US" and variant "foo". This will parse the output of
* {@link java.util.Locale#toString()}.
*
* @param localeStr The locale String to parse.
* @return requested Locale
*/
public static Locale toLocale(String localeStr) {
return toLocale(localeStr, null);
}
/**
* Builds a {@link java.util.Locale} from a String of the form en_US_foo into a Locale with
* language "en", country "US" and variant "foo". This will parse the output of
* {@link java.util.Locale#toString()}.
*
* @param localeStr The locale String to parse.
* @param defaultLocale The locale to use if localeStr is null.
* @return requested Locale
* @see #toLocale(String)
*/
public static Locale toLocale(String localeStr, Locale defaultLocale) {
try {
return parseLocale(localeStr);
}
catch (Exception e) {
return defaultLocale;
}
}
// -----------------------------------------------------------------------
/**
*
* Converts a String to a Locale.
*
*
* This method takes the string format of a locale and creates the locale object from it.
*
*
*
* parseLocale("en") = new Locale("en", "")
* parseLocale("en_GB") = new Locale("en", "GB")
* parseLocale("en_GB_xxx") = new Locale("en", "GB", "xxx") (#)
*
*
* (#) The behaviour of the JDK variant constructor changed between JDK1.3 and JDK1.4. In
* JDK1.3, the constructor upper cases the variant, in JDK1.4, it doesn't. Thus, the result from
* getVariant() may vary depending on your JDK.
*
*
* This method validates the input strictly. The language code must be lowercase. The country
* code must be uppercase. The separator must be an underscore. The length must be correct.
*
*
* @param str the locale String to convert, null returns null
* @return a Locale, null if null input
* @throws IllegalArgumentException if the string is an invalid format
*/
public static Locale parseLocale(final String str) {
if (str == null) {
return null;
}
if (str.contains("#")) { // LANG-879 - Cannot handle Java 7 script & extensions
throw new IllegalArgumentException("Invalid locale format: " + str);
}
final int len = str.length();
if (len < 2) {
throw new IllegalArgumentException("Invalid locale format: " + str);
}
final char ch0 = str.charAt(0);
if (ch0 == '_') {
if (len < 3) {
throw new IllegalArgumentException("Invalid locale format: " + str);
}
if (len == 3) {
return new Locale("", Strings.upperCase(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("", Strings.upperCase(str.substring(1, 3)), str.substring(4));
}
if (len == 2) {
return new Locale(Strings.lowerCase(str));
}
if (len < 5) {
throw new IllegalArgumentException("Invalid locale format: " + str);
}
if (str.charAt(2) != '_') {
throw new IllegalArgumentException("Invalid locale format: " + str);
}
if (str.charAt(3) == '_') {
return new Locale(Strings.lowerCase(str.substring(0, 2)), "", str.substring(4));
}
if (len == 5) {
return new Locale(Strings.lowerCase(str.substring(0, 2)), Strings.upperCase(str.substring(3, 5)));
}
if (len < 7) {
throw new IllegalArgumentException("Invalid locale format: " + str);
}
if (str.charAt(5) != '_') {
throw new IllegalArgumentException("Invalid locale format: " + str);
}
return new Locale(Strings.lowerCase(str.substring(0, 2)), Strings.upperCase(str.substring(3, 5)), str.substring(6));
}
// -----------------------------------------------------------------------
/**
*
* 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().length() > 0) {
list.add(new Locale(locale.getLanguage(), locale.getCountry()));
}
if (locale.getCountry().length() > 0) {
list.add(new Locale(locale.getLanguage(), ""));
}
if (list.contains(defaultLocale) == false) {
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 (int i = 0; i < locales.size(); i++) {
final Locale locale = locales.get(i);
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 (int i = 0; i < locales.size(); i++) {
final Locale locale = locales.get(i);
if (languageCode.equals(locale.getLanguage()) && locale.getCountry().length() != 0
&& 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));
}
}
/**
* Builds a {@link java.util.Locale} from a filename String of the form en_US_foo into a Locale
* with language "en", country "US" and variant "foo". This will parse the output of
* {@link java.util.Locale#toString()}.
*
* @param filename filename
* @return Locale
*/
public static Locale localeFromFileName(String filename) {
return localeFromFileName(filename, null);
}
/**
* Builds a {@link java.util.Locale} from a filename String of the form en_US_foo into a Locale
* with language "en", country "US" and variant "foo". This will parse the output of
* {@link java.util.Locale#toString()}.
*
* @param filename filename
* @param defaultLocale The locale to use
* @return Locale
*/
public static Locale localeFromFileName(String filename, Locale defaultLocale) {
return localeFromFileName(new File(filename), null);
}
/**
* Builds a {@link java.util.Locale} from a filename String of the form en_US_foo into a Locale
* with language "en", country "US" and variant "foo". This will parse the output of
* {@link java.util.Locale#toString()}.
*
* @param file file
* @return Locale
*/
public static Locale localeFromFileName(File file) {
return localeFromFileName(file, null);
}
/**
* Builds a {@link java.util.Locale} from a filename String of the form en_US_foo into a Locale
* with language "en", country "US" and variant "foo". This will parse the output of
* {@link java.util.Locale#toString()}.
*
* @param file file
* @param defaultLocale The locale to use
* @return Locale
*/
public static Locale localeFromFileName(File file, Locale defaultLocale) {
String b = FileNames.getBaseName(file);
String[] sa = b.split("\\_");
if (sa.length > 3) {
if (sa[sa.length - 3].length() == 2 && sa[sa.length - 2].length() == 2) {
return new Locale(sa[sa.length - 3], sa[sa.length - 2], sa[sa.length - 1]);
}
else if (sa[sa.length - 2].length() == 2 && sa[sa.length - 1].length() == 2) {
return new Locale(sa[sa.length - 2], sa[sa.length - 1]);
}
else if (sa[sa.length - 1].length() == 2) {
return new Locale(sa[sa.length - 1]);
}
}
else if (sa.length == 3) {
if (sa[sa.length - 2].length() == 2 && sa[sa.length - 1].length() == 2) {
return new Locale(sa[sa.length - 2], sa[sa.length - 1]);
}
else if (sa[sa.length - 1].length() == 2) {
return new Locale(sa[sa.length - 1]);
}
}
else if (sa.length == 2) {
if (sa[sa.length - 1].length() == 2) {
return new Locale(sa[sa.length - 1]);
}
}
return defaultLocale;
}
}