// 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 { private static final boolean DEBUG = false; public interface NameFormatter { String getDisplayName(CLDRLocale cldrLocale); String getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform 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 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 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 + " [" + fromLst + "]"; } } } 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 stringToLoc = new Hashtable(); private static Hashtable ulocToLoc = new Hashtable(); /** * 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 getParentIterator() { final CLDRLocale newThis = this; return new Iterable() { public Iterator iterator() { return new Iterator() { 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 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 defaultFormatters = new LruMap(1); private static Cache 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() { @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 getInstance(Iterable available) { Set s = new TreeSet(); for (String str : available) { s.add(CLDRLocale.getInstance(str)); } return s; } public interface SublocaleProvider { public Set subLocalesOf(CLDRLocale forLocale); } public String getDisplayName(NameFormatter engFormat, boolean combined, Transform 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; } }