| // Copyright (C) 2008-2012 IBM Corporation and Others. All Rights Reserved. |
| |
| package org.unicode.cldr.util; |
| |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ExecutionException; |
| |
| import com.google.common.cache.Cache; |
| import com.google.common.cache.CacheBuilder; |
| import com.ibm.icu.text.LocaleDisplayNames; |
| import com.ibm.icu.text.Transform; |
| import com.ibm.icu.util.ULocale; |
| |
| /** |
| * This class implements a CLDR UTS#35 compliant locale. |
| * It differs from ICU and Java locales in that it is singleton based, and that it is Comparable. |
| * It uses LocaleIDParser to do the heavy lifting of parsing. |
| * |
| * @author srl |
| * @see LocaleIDParser |
| * @see ULocale |
| */ |
| public final class CLDRLocale implements Comparable<CLDRLocale> { |
| private static final boolean DEBUG = false; |
| |
| public interface NameFormatter { |
| String getDisplayName(CLDRLocale cldrLocale); |
| |
| String getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker); |
| |
| String getDisplayLanguage(CLDRLocale cldrLocale); |
| |
| String getDisplayScript(CLDRLocale cldrLocale); |
| |
| String getDisplayVariant(CLDRLocale cldrLocale); |
| |
| String getDisplayCountry(CLDRLocale cldrLocale); |
| } |
| |
| public static class SimpleFormatter implements NameFormatter { |
| private LocaleDisplayNames ldn; |
| |
| public SimpleFormatter(ULocale displayLocale) { |
| this.ldn = LocaleDisplayNames.getInstance(displayLocale); |
| } |
| |
| public LocaleDisplayNames getDisplayNames() { |
| return ldn; |
| } |
| |
| public LocaleDisplayNames setDisplayNames(LocaleDisplayNames ldn) { |
| return this.ldn = ldn; |
| } |
| |
| @Override |
| public String getDisplayVariant(CLDRLocale cldrLocale) { |
| return ldn.variantDisplayName(cldrLocale.getVariant()); |
| } |
| |
| @Override |
| public String getDisplayCountry(CLDRLocale cldrLocale) { |
| return ldn.regionDisplayName(cldrLocale.getCountry()); |
| } |
| |
| @Override |
| public String getDisplayName(CLDRLocale cldrLocale) { |
| StringBuffer sb = new StringBuffer(); |
| String l = cldrLocale.getLanguage(); |
| String s = cldrLocale.getScript(); |
| String r = cldrLocale.getCountry(); |
| String v = cldrLocale.getVariant(); |
| |
| if (l != null && !l.isEmpty()) { |
| sb.append(getDisplayLanguage(cldrLocale)); |
| } else { |
| sb.append("?"); |
| } |
| if ((s != null && !s.isEmpty()) || |
| (r != null && !r.isEmpty()) || |
| (v != null && !v.isEmpty())) { |
| sb.append(" ("); |
| if (s != null && !s.isEmpty()) { |
| sb.append(getDisplayScript(cldrLocale)).append(","); |
| } |
| if (r != null && !r.isEmpty()) { |
| sb.append(getDisplayCountry(cldrLocale)).append(","); |
| } |
| if (v != null && !v.isEmpty()) { |
| sb.append(getDisplayVariant(cldrLocale)).append(","); |
| } |
| sb.replace(sb.length() - 1, sb.length(), ")"); |
| } |
| return sb.toString(); |
| } |
| |
| @Override |
| public String getDisplayScript(CLDRLocale cldrLocale) { |
| return ldn.scriptDisplayName(cldrLocale.getScript()); |
| } |
| |
| @Override |
| public String getDisplayLanguage(CLDRLocale cldrLocale) { |
| return ldn.languageDisplayName(cldrLocale.getLanguage()); |
| } |
| |
| @Override |
| public String getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker) { |
| return getDisplayName(cldrLocale); |
| } |
| } |
| |
| /** |
| * @author srl |
| * |
| * This formatter will delegate to CLDRFile.getName if a CLDRFile is given, otherwise StandardCodes |
| */ |
| public static class CLDRFormatter extends SimpleFormatter { |
| private FormatBehavior behavior = FormatBehavior.extend; |
| |
| private CLDRFile file = null; |
| |
| public CLDRFormatter(CLDRFile fromFile) { |
| super(CLDRLocale.getInstance(fromFile.getLocaleID()).toULocale()); |
| file = fromFile; |
| } |
| |
| public CLDRFormatter(CLDRFile fromFile, FormatBehavior behavior) { |
| super(CLDRLocale.getInstance(fromFile.getLocaleID()).toULocale()); |
| this.behavior = behavior; |
| file = fromFile; |
| } |
| |
| public CLDRFormatter() { |
| super(ULocale.ROOT); |
| } |
| |
| public CLDRFormatter(FormatBehavior behavior) { |
| super(ULocale.ROOT); |
| this.behavior = behavior; |
| } |
| |
| @Override |
| public String getDisplayVariant(CLDRLocale cldrLocale) { |
| if (file != null) return file.getName("variant", cldrLocale.getVariant()); |
| return tryForBetter(super.getDisplayVariant(cldrLocale), |
| cldrLocale.getVariant(), |
| "variant"); |
| } |
| |
| @Override |
| public String getDisplayName(CLDRLocale cldrLocale) { |
| if (file != null) return file.getName(cldrLocale.toDisplayLanguageTag(), true, null); |
| return super.getDisplayName(cldrLocale); |
| } |
| |
| @Override |
| public String getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker) { |
| if (file != null) return file.getName(cldrLocale.toDisplayLanguageTag(), onlyConstructCompound, altPicker); |
| return super.getDisplayName(cldrLocale); |
| } |
| |
| @Override |
| public String getDisplayScript(CLDRLocale cldrLocale) { |
| if (file != null) return file.getName("script", cldrLocale.getScript()); |
| return tryForBetter(super.getDisplayScript(cldrLocale), |
| cldrLocale.getScript(), |
| "language"); |
| } |
| |
| @Override |
| public String getDisplayLanguage(CLDRLocale cldrLocale) { |
| if (file != null) return file.getName("language", cldrLocale.getLanguage()); |
| return tryForBetter(super.getDisplayLanguage(cldrLocale), |
| cldrLocale.getLanguage(), |
| "language"); |
| } |
| |
| @Override |
| public String getDisplayCountry(CLDRLocale cldrLocale) { |
| if (file != null) return file.getName("territory", cldrLocale.getCountry()); |
| return tryForBetter(super.getDisplayLanguage(cldrLocale), |
| cldrLocale.getLanguage(), |
| "territory"); |
| } |
| |
| private String tryForBetter(String superString, String code, String type) { |
| if (superString.equals(code)) { |
| String fromLst = StandardCodes.make().getData("language", code); |
| if (fromLst != null && !fromLst.equals(code)) { |
| switch (behavior) { |
| case replace: |
| return fromLst; |
| case extend: |
| return superString + " [" + fromLst + "]"; |
| case extendHtml: |
| return superString + " [<i>" + fromLst + "</i>]"; |
| } |
| } |
| } |
| return superString; |
| } |
| } |
| |
| public enum FormatBehavior { |
| replace, extend, extendHtml |
| }; |
| |
| /** |
| * Reference to the parent CLDRLocale |
| */ |
| private CLDRLocale parent = null; |
| /** |
| * Cached ICU format locale |
| */ |
| private ULocale ulocale; |
| /** |
| * base name, 'without parameters'. Currently same as fullname. |
| */ |
| private String basename; |
| /** |
| * Full name |
| */ |
| private String fullname; |
| /** |
| * The LocaleIDParser interprets the various parts (language, country, script, etc). |
| */ |
| private LocaleIDParser parts = null; |
| |
| /** |
| * Construct a CLDRLocale from an ICU ULocale. |
| * Internal, called by the factory function. |
| */ |
| private CLDRLocale(ULocale loc) { |
| init(loc); |
| } |
| |
| /** |
| * Returns the BCP47 langauge tag for all except root. For root, returns "root". |
| * @return |
| */ |
| private String toDisplayLanguageTag() { |
| if (getBaseName().equals("root")) { |
| return "root"; |
| } else { |
| return toLanguageTag(); |
| } |
| } |
| |
| /** |
| * Return BCP47 language tag |
| * @return |
| */ |
| public String toLanguageTag() { |
| return ulocale.toLanguageTag(); |
| } |
| |
| /** |
| * Construct a CLDRLocale from a string with the full locale ID. |
| * Internal, called by the factory function. |
| * |
| * @param str |
| */ |
| private CLDRLocale(String str) { |
| init(str); |
| } |
| |
| /** |
| * Initialize a CLDRLocale from a ULocale |
| * |
| * @param loc |
| */ |
| private void init(ULocale loc) { |
| ulocale = loc; |
| init(loc.getBaseName()); |
| } |
| |
| /** |
| * Initialize a CLDRLocale from a string. |
| * |
| * @param str |
| */ |
| private void init(String str) { |
| // if(str.length()==0) { |
| // str = "root"; |
| // } |
| str = process(str); |
| // System.err.println("bn: " + str); |
| if (str.equals(ULocale.ROOT.getBaseName()) || str.equalsIgnoreCase("root")) { |
| fullname = "root"; |
| parent = null; |
| } else { |
| parts = new LocaleIDParser(); |
| parts.set(str); |
| fullname = parts.toString(); |
| String parentId = LocaleIDParser.getParent(str); |
| if (DEBUG) System.out.println(str + " par = " + parentId); |
| if (parentId != null) { |
| parent = CLDRLocale.getInstance(parentId); |
| } else { |
| parent = null; // probably, we are root or we are supplemental |
| } |
| } |
| basename = fullname; |
| if (ulocale == null) { |
| ulocale = new ULocale(fullname); |
| } |
| } |
| |
| /** |
| * Return the full locale name, in CLDR format. |
| */ |
| public String toString() { |
| return fullname; |
| } |
| |
| /** |
| * Return the base locale name, in CLDR format, without any @keywords |
| * |
| * @return |
| */ |
| public String getBaseName() { |
| return basename; |
| } |
| |
| /** |
| * internal: process a string from ICU to CLDR form. For now, just collapse double underscores. |
| * |
| * @param baseName |
| * @return |
| * @internal |
| */ |
| private String process(String baseName) { |
| return baseName.replaceAll("__", "_"); |
| } |
| |
| /** |
| * Compare to another CLDRLocale. Uses string order of toString(). |
| */ |
| public int compareTo(CLDRLocale o) { |
| if (o == this) return 0; |
| return fullname.compareTo(o.fullname); |
| } |
| |
| /** |
| * Hashcode - is the hashcode of the full string |
| */ |
| public int hashCode() |
| { |
| return fullname.hashCode(); |
| } |
| |
| /** |
| * Convert to an ICU compatible ULocale. |
| * |
| * @return |
| */ |
| public ULocale toULocale() { |
| return ulocale; |
| } |
| |
| /** |
| * Allocate a CLDRLocale (could be a singleton). If null is passed in, null will be returned. |
| * |
| * @param s |
| * @return |
| */ |
| public static CLDRLocale getInstance(String s) { |
| if (s == null) return null; |
| CLDRLocale loc = stringToLoc.get(s); |
| if (loc == null) { |
| loc = new CLDRLocale(s); |
| loc.register(); |
| } |
| return loc; |
| } |
| |
| /** |
| * Public factory function. Allocate a CLDRLocale (could be a singleton). If null is passed in, null will be |
| * returned. |
| * |
| * @param u |
| * @return |
| */ |
| public static CLDRLocale getInstance(ULocale u) { |
| if (u == null) return null; |
| CLDRLocale loc = ulocToLoc.get(u); |
| if (loc == null) { |
| loc = new CLDRLocale(u); |
| loc.register(); |
| } |
| return loc; |
| } |
| |
| /** |
| * Register the singleton instance. |
| */ |
| private void register() { |
| stringToLoc.put(this.toString(), this); |
| ulocToLoc.put(this.toULocale(), this); |
| } |
| |
| private static Hashtable<String, CLDRLocale> stringToLoc = new Hashtable<String, CLDRLocale>(); |
| private static Hashtable<ULocale, CLDRLocale> ulocToLoc = new Hashtable<ULocale, CLDRLocale>(); |
| |
| /** |
| * Return the parent locale of this item. Null if no parent (root has no parent) |
| * |
| * @return |
| */ |
| public CLDRLocale getParent() { |
| return parent; |
| } |
| |
| /** |
| * Returns true if other is equal to or is an ancestor of this, false otherwise |
| */ |
| public boolean childOf(CLDRLocale other) { |
| if (other == null) return false; |
| if (other == this) return true; |
| if (parent == null) return false; // end |
| return parent.childOf(other); |
| } |
| |
| /** |
| * Return an iterator that will iterate over locale, parent, parent etc, finally reaching root. |
| * |
| * @return |
| */ |
| public Iterable<CLDRLocale> getParentIterator() { |
| final CLDRLocale newThis = this; |
| return new Iterable<CLDRLocale>() { |
| public Iterator<CLDRLocale> iterator() { |
| return new Iterator<CLDRLocale>() { |
| CLDRLocale what = newThis; |
| |
| public boolean hasNext() { |
| // TODO Auto-generated method stub |
| return what.getParent() != null; |
| } |
| |
| public CLDRLocale next() { |
| // TODO Auto-generated method stub |
| CLDRLocale curr = what; |
| if (what != null) { |
| what = what.getParent(); |
| } |
| return curr; |
| } |
| |
| public void remove() { |
| throw new InternalError("unmodifiable iterator"); |
| } |
| |
| }; |
| } |
| }; |
| } |
| |
| /** |
| * Get the 'language' locale, as an object. Might be 'this'. |
| * @return |
| */ |
| public CLDRLocale getLanguageLocale() { |
| return getInstance(getLanguage()); |
| } |
| |
| public String getLanguage() { |
| return parts == null ? fullname : parts.getLanguage(); |
| } |
| |
| public String getScript() { |
| return parts == null ? null : parts.getScript(); |
| } |
| |
| public boolean isLanguageLocale() { |
| return this.equals(getLanguageLocale()); |
| } |
| |
| /** |
| * Return the region |
| * |
| * @return |
| */ |
| public String getCountry() { |
| return parts == null ? null : parts.getRegion(); |
| } |
| |
| /** |
| * Return "the" variant. |
| * |
| * @return |
| */ |
| public String getVariant() { |
| return toULocale().getVariant(); // TODO: replace with parts? |
| } |
| |
| /** |
| * Most objects should be singletons, and so equality/inequality comparison is done first. |
| */ |
| public boolean equals(Object o) { |
| if (o == this) return true; |
| if (!(o instanceof CLDRLocale)) return false; |
| return (0 == compareTo((CLDRLocale) o)); |
| } |
| |
| /** |
| * The root locale, a singleton. |
| */ |
| public static final CLDRLocale ROOT = getInstance(ULocale.ROOT); |
| |
| public String getDisplayName() { |
| return getDisplayName(getDefaultFormatter()); |
| } |
| |
| public String getDisplayRegion() { |
| return getDisplayCountry(getDefaultFormatter()); |
| } |
| |
| public String getDisplayVariant() { |
| return getDisplayVariant(getDefaultFormatter()); |
| } |
| |
| public String getDisplayName(boolean combined, Transform<String, String> picker) { |
| return getDisplayName(getDefaultFormatter(), combined, picker); |
| } |
| |
| /** |
| * These functions wrap calls to the displayLocale, but are provided to supply an interface that looks similar to |
| * ULocale.getDisplay___(displayLocale) |
| * |
| * @param displayLocale |
| * @return |
| */ |
| public String getDisplayName(NameFormatter displayLocale) { |
| if (displayLocale == null) displayLocale = getDefaultFormatter(); |
| return displayLocale.getDisplayName(this); |
| } |
| |
| // private static LruMap<ULocale, NameFormatter> defaultFormatters = new LruMap<ULocale, NameFormatter>(1); |
| private static Cache<ULocale, NameFormatter> defaultFormatters = CacheBuilder.newBuilder().initialCapacity(1).build(); |
| private static NameFormatter gDefaultFormatter = getSimpleFormatterFor(ULocale.getDefault()); |
| |
| public static NameFormatter getSimpleFormatterFor(ULocale loc) { |
| // NameFormatter nf = defaultFormatters.get(loc); |
| // if (nf == null) { |
| // nf = new SimpleFormatter(loc); |
| // defaultFormatters.put(loc, nf); |
| // } |
| // return nf; |
| // return defaultFormatters.getIfPresent(loc); |
| final ULocale uLocFinal = loc; |
| try { |
| return defaultFormatters.get(loc, new Callable<NameFormatter>() { |
| |
| @Override |
| public NameFormatter call() throws Exception { |
| return new SimpleFormatter(uLocFinal); |
| } |
| }); |
| } catch (ExecutionException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| return null; |
| } |
| } |
| |
| public String getDisplayName(ULocale displayLocale) { |
| return getSimpleFormatterFor(displayLocale).getDisplayName(this); |
| } |
| |
| public static NameFormatter getDefaultFormatter() { |
| return gDefaultFormatter; |
| } |
| |
| public static NameFormatter setDefaultFormatter(NameFormatter nf) { |
| return gDefaultFormatter = nf; |
| } |
| |
| /** |
| * These functions wrap calls to the displayLocale, but are provided to supply an interface that looks similar to |
| * ULocale.getDisplay___(displayLocale) |
| * |
| * @param displayLocale |
| * @return |
| */ |
| public String getDisplayCountry(NameFormatter displayLocale) { |
| if (displayLocale == null) displayLocale = getDefaultFormatter(); |
| return displayLocale.getDisplayCountry(this); |
| } |
| |
| /** |
| * These functions wrap calls to the displayLocale, but are provided to supply an interface that looks similar to |
| * ULocale.getDisplay___(displayLocale) |
| * |
| * @param displayLocale |
| * @return |
| */ |
| public String getDisplayVariant(NameFormatter displayLocale) { |
| if (displayLocale == null) displayLocale = getDefaultFormatter(); |
| return displayLocale.getDisplayVariant(this); |
| } |
| |
| /** |
| * Construct an instance from an array |
| * |
| * @param available |
| * @return |
| */ |
| public static Set<CLDRLocale> getInstance(Iterable<String> available) { |
| Set<CLDRLocale> s = new TreeSet<CLDRLocale>(); |
| for (String str : available) { |
| s.add(CLDRLocale.getInstance(str)); |
| } |
| return s; |
| } |
| |
| public interface SublocaleProvider { |
| public Set<CLDRLocale> subLocalesOf(CLDRLocale forLocale); |
| } |
| |
| public String getDisplayName(NameFormatter engFormat, boolean combined, Transform<String, String> picker) { |
| return engFormat.getDisplayName(this, combined, picker); |
| } |
| |
| /** |
| * Return the highest parent that is a child of root, or null. |
| * @return highest parent, or null. ROOT.getHighestNonrootParent() also returns null. |
| */ |
| public CLDRLocale getHighestNonrootParent() { |
| CLDRLocale res; |
| if (this == ROOT) { |
| res = null; |
| ; |
| } else if (this.parent == ROOT) { |
| res = this; |
| } else if (this.parent == null) { |
| res = this; |
| } else { |
| res = parent.getHighestNonrootParent(); |
| } |
| if (DEBUG) System.out.println(this + ".HNRP=" + res); |
| return res; |
| } |
| } |