1 // Copyright (C) 2008-2012 IBM Corporation and Others. All Rights Reserved.
2 
3 package org.unicode.cldr.util;
4 
5 import java.util.Hashtable;
6 import java.util.Iterator;
7 import java.util.Set;
8 import java.util.TreeSet;
9 import java.util.concurrent.Callable;
10 import java.util.concurrent.ExecutionException;
11 
12 import com.google.common.cache.Cache;
13 import com.google.common.cache.CacheBuilder;
14 import com.ibm.icu.text.LocaleDisplayNames;
15 import com.ibm.icu.text.Transform;
16 import com.ibm.icu.util.ULocale;
17 
18 /**
19  * This class implements a CLDR UTS#35 compliant locale.
20  * It differs from ICU and Java locales in that it is singleton based, and that it is Comparable.
21  * It uses LocaleIDParser to do the heavy lifting of parsing.
22  *
23  * @author srl
24  * @see LocaleIDParser
25  * @see ULocale
26  */
27 public final class CLDRLocale implements Comparable<CLDRLocale> {
28     private static final boolean DEBUG = false;
29 
30     public interface NameFormatter {
getDisplayName(CLDRLocale cldrLocale)31         String getDisplayName(CLDRLocale cldrLocale);
32 
getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker)33         String getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker);
34 
getDisplayLanguage(CLDRLocale cldrLocale)35         String getDisplayLanguage(CLDRLocale cldrLocale);
36 
getDisplayScript(CLDRLocale cldrLocale)37         String getDisplayScript(CLDRLocale cldrLocale);
38 
getDisplayVariant(CLDRLocale cldrLocale)39         String getDisplayVariant(CLDRLocale cldrLocale);
40 
getDisplayCountry(CLDRLocale cldrLocale)41         String getDisplayCountry(CLDRLocale cldrLocale);
42     }
43 
44     public static class SimpleFormatter implements NameFormatter {
45         private LocaleDisplayNames ldn;
46 
SimpleFormatter(ULocale displayLocale)47         public SimpleFormatter(ULocale displayLocale) {
48             this.ldn = LocaleDisplayNames.getInstance(displayLocale);
49         }
50 
getDisplayNames()51         public LocaleDisplayNames getDisplayNames() {
52             return ldn;
53         }
54 
setDisplayNames(LocaleDisplayNames ldn)55         public LocaleDisplayNames setDisplayNames(LocaleDisplayNames ldn) {
56             return this.ldn = ldn;
57         }
58 
59         @Override
getDisplayVariant(CLDRLocale cldrLocale)60         public String getDisplayVariant(CLDRLocale cldrLocale) {
61             return ldn.variantDisplayName(cldrLocale.getVariant());
62         }
63 
64         @Override
getDisplayCountry(CLDRLocale cldrLocale)65         public String getDisplayCountry(CLDRLocale cldrLocale) {
66             return ldn.regionDisplayName(cldrLocale.getCountry());
67         }
68 
69         @Override
getDisplayName(CLDRLocale cldrLocale)70         public String getDisplayName(CLDRLocale cldrLocale) {
71             StringBuffer sb = new StringBuffer();
72             String l = cldrLocale.getLanguage();
73             String s = cldrLocale.getScript();
74             String r = cldrLocale.getCountry();
75             String v = cldrLocale.getVariant();
76 
77             if (l != null && !l.isEmpty()) {
78                 sb.append(getDisplayLanguage(cldrLocale));
79             } else {
80                 sb.append("?");
81             }
82             if ((s != null && !s.isEmpty()) ||
83                 (r != null && !r.isEmpty()) ||
84                 (v != null && !v.isEmpty())) {
85                 sb.append(" (");
86                 if (s != null && !s.isEmpty()) {
87                     sb.append(getDisplayScript(cldrLocale)).append(",");
88                 }
89                 if (r != null && !r.isEmpty()) {
90                     sb.append(getDisplayCountry(cldrLocale)).append(",");
91                 }
92                 if (v != null && !v.isEmpty()) {
93                     sb.append(getDisplayVariant(cldrLocale)).append(",");
94                 }
95                 sb.replace(sb.length() - 1, sb.length(), ")");
96             }
97             return sb.toString();
98         }
99 
100         @Override
getDisplayScript(CLDRLocale cldrLocale)101         public String getDisplayScript(CLDRLocale cldrLocale) {
102             return ldn.scriptDisplayName(cldrLocale.getScript());
103         }
104 
105         @Override
getDisplayLanguage(CLDRLocale cldrLocale)106         public String getDisplayLanguage(CLDRLocale cldrLocale) {
107             return ldn.languageDisplayName(cldrLocale.getLanguage());
108         }
109 
110         @Override
getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker)111         public String getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker) {
112             return getDisplayName(cldrLocale);
113         }
114     }
115 
116     /**
117      * @author srl
118      *
119      * This formatter will delegate to CLDRFile.getName if a CLDRFile is given, otherwise StandardCodes
120      */
121     public static class CLDRFormatter extends SimpleFormatter {
122         private FormatBehavior behavior = FormatBehavior.extend;
123 
124         private CLDRFile file = null;
125 
CLDRFormatter(CLDRFile fromFile)126         public CLDRFormatter(CLDRFile fromFile) {
127             super(CLDRLocale.getInstance(fromFile.getLocaleID()).toULocale());
128             file = fromFile;
129         }
130 
CLDRFormatter(CLDRFile fromFile, FormatBehavior behavior)131         public CLDRFormatter(CLDRFile fromFile, FormatBehavior behavior) {
132             super(CLDRLocale.getInstance(fromFile.getLocaleID()).toULocale());
133             this.behavior = behavior;
134             file = fromFile;
135         }
136 
CLDRFormatter()137         public CLDRFormatter() {
138             super(ULocale.ROOT);
139         }
140 
CLDRFormatter(FormatBehavior behavior)141         public CLDRFormatter(FormatBehavior behavior) {
142             super(ULocale.ROOT);
143             this.behavior = behavior;
144         }
145 
146         @Override
getDisplayVariant(CLDRLocale cldrLocale)147         public String getDisplayVariant(CLDRLocale cldrLocale) {
148             if (file != null) return file.getName("variant", cldrLocale.getVariant());
149             return tryForBetter(super.getDisplayVariant(cldrLocale),
150                 cldrLocale.getVariant(),
151                 "variant");
152         }
153 
154         @Override
getDisplayName(CLDRLocale cldrLocale)155         public String getDisplayName(CLDRLocale cldrLocale) {
156             if (file != null) return file.getName(cldrLocale.toDisplayLanguageTag(), true, null);
157             return super.getDisplayName(cldrLocale);
158         }
159 
160         @Override
getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker)161         public String getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker) {
162             if (file != null) return file.getName(cldrLocale.toDisplayLanguageTag(), onlyConstructCompound, altPicker);
163             return super.getDisplayName(cldrLocale);
164         }
165 
166         @Override
getDisplayScript(CLDRLocale cldrLocale)167         public String getDisplayScript(CLDRLocale cldrLocale) {
168             if (file != null) return file.getName("script", cldrLocale.getScript());
169             return tryForBetter(super.getDisplayScript(cldrLocale),
170                 cldrLocale.getScript(),
171                 "language");
172         }
173 
174         @Override
getDisplayLanguage(CLDRLocale cldrLocale)175         public String getDisplayLanguage(CLDRLocale cldrLocale) {
176             if (file != null) return file.getName("language", cldrLocale.getLanguage());
177             return tryForBetter(super.getDisplayLanguage(cldrLocale),
178                 cldrLocale.getLanguage(),
179                 "language");
180         }
181 
182         @Override
getDisplayCountry(CLDRLocale cldrLocale)183         public String getDisplayCountry(CLDRLocale cldrLocale) {
184             if (file != null) return file.getName("territory", cldrLocale.getCountry());
185             return tryForBetter(super.getDisplayLanguage(cldrLocale),
186                 cldrLocale.getLanguage(),
187                 "territory");
188         }
189 
tryForBetter(String superString, String code, String type)190         private String tryForBetter(String superString, String code, String type) {
191             if (superString.equals(code)) {
192                 String fromLst = StandardCodes.make().getData("language", code);
193                 if (fromLst != null && !fromLst.equals(code)) {
194                     switch (behavior) {
195                     case replace:
196                         return fromLst;
197                     case extend:
198                         return superString + " [" + fromLst + "]";
199                     case extendHtml:
200                         return superString + " [<i>" + fromLst + "</i>]";
201                     }
202                 }
203             }
204             return superString;
205         }
206     }
207 
208     public enum FormatBehavior {
209         replace, extend, extendHtml
210     };
211 
212     /**
213      * Reference to the parent CLDRLocale
214      */
215     private CLDRLocale parent = null;
216     /**
217      * Cached ICU format locale
218      */
219     private ULocale ulocale;
220     /**
221      * base name, 'without parameters'. Currently same as fullname.
222      */
223     private String basename;
224     /**
225      * Full name
226      */
227     private String fullname;
228     /**
229      * The LocaleIDParser interprets the various parts (language, country, script, etc).
230      */
231     private LocaleIDParser parts = null;
232 
233     /**
234      * Construct a CLDRLocale from an ICU ULocale.
235      * Internal, called by the factory function.
236      */
CLDRLocale(ULocale loc)237     private CLDRLocale(ULocale loc) {
238         init(loc);
239     }
240 
241     /**
242      * Returns the BCP47 langauge tag for all except root. For root, returns "root".
243      * @return
244      */
toDisplayLanguageTag()245     private String toDisplayLanguageTag() {
246         if (getBaseName().equals("root")) {
247             return "root";
248         } else {
249             return toLanguageTag();
250         }
251     }
252 
253     /**
254      * Return BCP47 language tag
255      * @return
256      */
toLanguageTag()257     public String toLanguageTag() {
258         return ulocale.toLanguageTag();
259     }
260 
261     /**
262      * Construct a CLDRLocale from a string with the full locale ID.
263      * Internal, called by the factory function.
264      *
265      * @param str
266      */
CLDRLocale(String str)267     private CLDRLocale(String str) {
268         init(str);
269     }
270 
271     /**
272      * Initialize a CLDRLocale from a ULocale
273      *
274      * @param loc
275      */
init(ULocale loc)276     private void init(ULocale loc) {
277         ulocale = loc;
278         init(loc.getBaseName());
279     }
280 
281     /**
282      * Initialize a CLDRLocale from a string.
283      *
284      * @param str
285      */
init(String str)286     private void init(String str) {
287         // if(str.length()==0) {
288         // str = "root";
289         // }
290         str = process(str);
291         // System.err.println("bn: " + str);
292         if (str.equals(ULocale.ROOT.getBaseName()) || str.equalsIgnoreCase("root")) {
293             fullname = "root";
294             parent = null;
295         } else {
296             parts = new LocaleIDParser();
297             parts.set(str);
298             fullname = parts.toString();
299             String parentId = LocaleIDParser.getParent(str);
300             if (DEBUG) System.out.println(str + " par = " + parentId);
301             if (parentId != null) {
302                 parent = CLDRLocale.getInstance(parentId);
303             } else {
304                 parent = null; // probably, we are root or we are supplemental
305             }
306         }
307         basename = fullname;
308         if (ulocale == null) {
309             ulocale = new ULocale(fullname);
310         }
311     }
312 
313     /**
314      * Return the full locale name, in CLDR format.
315      */
toString()316     public String toString() {
317         return fullname;
318     }
319 
320     /**
321      * Return the base locale name, in CLDR format, without any @keywords
322      *
323      * @return
324      */
getBaseName()325     public String getBaseName() {
326         return basename;
327     }
328 
329     /**
330      * internal: process a string from ICU to CLDR form. For now, just collapse double underscores.
331      *
332      * @param baseName
333      * @return
334      * @internal
335      */
process(String baseName)336     private String process(String baseName) {
337         return baseName.replaceAll("__", "_");
338     }
339 
340     /**
341      * Compare to another CLDRLocale. Uses string order of toString().
342      */
compareTo(CLDRLocale o)343     public int compareTo(CLDRLocale o) {
344         if (o == this) return 0;
345         return fullname.compareTo(o.fullname);
346     }
347 
348     /**
349      * Hashcode - is the hashcode of the full string
350      */
hashCode()351     public int hashCode() {
352         return fullname.hashCode();
353     }
354 
355     /**
356      * Convert to an ICU compatible ULocale.
357      *
358      * @return
359      */
toULocale()360     public ULocale toULocale() {
361         return ulocale;
362     }
363 
364     /**
365      * Allocate a CLDRLocale (could be a singleton). If null is passed in, null will be returned.
366      *
367      * @param s
368      * @return
369      */
getInstance(String s)370     public static CLDRLocale getInstance(String s) {
371         if (s == null) return null;
372         CLDRLocale loc = stringToLoc.get(s);
373         if (loc == null) {
374             loc = new CLDRLocale(s);
375             loc.register();
376         }
377         return loc;
378     }
379 
380     /**
381      * Public factory function. Allocate a CLDRLocale (could be a singleton). If null is passed in, null will be
382      * returned.
383      *
384      * @param u
385      * @return
386      */
getInstance(ULocale u)387     public static CLDRLocale getInstance(ULocale u) {
388         if (u == null) return null;
389         CLDRLocale loc = ulocToLoc.get(u);
390         if (loc == null) {
391             loc = new CLDRLocale(u);
392             loc.register();
393         }
394         return loc;
395     }
396 
397     /**
398      * Register the singleton instance.
399      */
register()400     private void register() {
401         stringToLoc.put(this.toString(), this);
402         ulocToLoc.put(this.toULocale(), this);
403     }
404 
405     private static Hashtable<String, CLDRLocale> stringToLoc = new Hashtable<String, CLDRLocale>();
406     private static Hashtable<ULocale, CLDRLocale> ulocToLoc = new Hashtable<ULocale, CLDRLocale>();
407 
408     /**
409      * Return the parent locale of this item. Null if no parent (root has no parent)
410      *
411      * @return
412      */
getParent()413     public CLDRLocale getParent() {
414         return parent;
415     }
416 
417     /**
418      * Returns true if other is equal to or is an ancestor of this, false otherwise
419      */
childOf(CLDRLocale other)420     public boolean childOf(CLDRLocale other) {
421         if (other == null) return false;
422         if (other == this) return true;
423         if (parent == null) return false; // end
424         return parent.childOf(other);
425     }
426 
427     /**
428      * Return an iterator that will iterate over locale, parent, parent etc, finally reaching root.
429      *
430      * @return
431      */
getParentIterator()432     public Iterable<CLDRLocale> getParentIterator() {
433         final CLDRLocale newThis = this;
434         return new Iterable<CLDRLocale>() {
435             public Iterator<CLDRLocale> iterator() {
436                 return new Iterator<CLDRLocale>() {
437                     CLDRLocale what = newThis;
438 
439                     public boolean hasNext() {
440                         // TODO Auto-generated method stub
441                         return what.getParent() != null;
442                     }
443 
444                     public CLDRLocale next() {
445                         // TODO Auto-generated method stub
446                         CLDRLocale curr = what;
447                         if (what != null) {
448                             what = what.getParent();
449                         }
450                         return curr;
451                     }
452 
453                     public void remove() {
454                         throw new InternalError("unmodifiable iterator");
455                     }
456 
457                 };
458             }
459         };
460     }
461 
462     /**
463      * Get the 'language' locale, as an object. Might be 'this'.
464      * @return
465      */
466     public CLDRLocale getLanguageLocale() {
467         return getInstance(getLanguage());
468     }
469 
470     public String getLanguage() {
471         return parts == null ? fullname : parts.getLanguage();
472     }
473 
474     public String getScript() {
475         return parts == null ? null : parts.getScript();
476     }
477 
478     public boolean isLanguageLocale() {
479         return this.equals(getLanguageLocale());
480     }
481 
482     /**
483      * Return the region
484      *
485      * @return
486      */
487     public String getCountry() {
488         return parts == null ? null : parts.getRegion();
489     }
490 
491     /**
492      * Return "the" variant.
493      *
494      * @return
495      */
496     public String getVariant() {
497         return toULocale().getVariant(); // TODO: replace with parts?
498     }
499 
500     /**
501      * Most objects should be singletons, and so equality/inequality comparison is done first.
502      */
503     public boolean equals(Object o) {
504         if (o == this) return true;
505         if (!(o instanceof CLDRLocale)) return false;
506         return (0 == compareTo((CLDRLocale) o));
507     }
508 
509     /**
510      * The root locale, a singleton.
511      */
512     public static final CLDRLocale ROOT = getInstance(ULocale.ROOT);
513 
514     public String getDisplayName() {
515         return getDisplayName(getDefaultFormatter());
516     }
517 
518     public String getDisplayRegion() {
519         return getDisplayCountry(getDefaultFormatter());
520     }
521 
522     public String getDisplayVariant() {
523         return getDisplayVariant(getDefaultFormatter());
524     }
525 
526     public String getDisplayName(boolean combined, Transform<String, String> picker) {
527         return getDisplayName(getDefaultFormatter(), combined, picker);
528     }
529 
530     /**
531      * These functions wrap calls to the displayLocale, but are provided to supply an interface that looks similar to
532      * ULocale.getDisplay___(displayLocale)
533      *
534      * @param displayLocale
535      * @return
536      */
537     public String getDisplayName(NameFormatter displayLocale) {
538         if (displayLocale == null) displayLocale = getDefaultFormatter();
539         return displayLocale.getDisplayName(this);
540     }
541 
542 //    private static LruMap<ULocale, NameFormatter> defaultFormatters = new LruMap<ULocale, NameFormatter>(1);
543     private static Cache<ULocale, NameFormatter> defaultFormatters = CacheBuilder.newBuilder().initialCapacity(1).build();
544     private static NameFormatter gDefaultFormatter = getSimpleFormatterFor(ULocale.getDefault());
545 
546     public static NameFormatter getSimpleFormatterFor(ULocale loc) {
547 //        NameFormatter nf = defaultFormatters.get(loc);
548 //        if (nf == null) {
549 //            nf = new SimpleFormatter(loc);
550 //            defaultFormatters.put(loc, nf);
551 //        }
552 //        return nf;
553 //        return defaultFormatters.getIfPresent(loc);
554         final ULocale uLocFinal = loc;
555         try {
556             return defaultFormatters.get(loc, new Callable<NameFormatter>() {
557 
558                 @Override
559                 public NameFormatter call() throws Exception {
560                     return new SimpleFormatter(uLocFinal);
561                 }
562             });
563         } catch (ExecutionException e) {
564             // TODO Auto-generated catch block
565             e.printStackTrace();
566             return null;
567         }
568     }
569 
570     public String getDisplayName(ULocale displayLocale) {
571         return getSimpleFormatterFor(displayLocale).getDisplayName(this);
572     }
573 
574     public static NameFormatter getDefaultFormatter() {
575         return gDefaultFormatter;
576     }
577 
578     public static NameFormatter setDefaultFormatter(NameFormatter nf) {
579         return gDefaultFormatter = nf;
580     }
581 
582     /**
583      * These functions wrap calls to the displayLocale, but are provided to supply an interface that looks similar to
584      * ULocale.getDisplay___(displayLocale)
585      *
586      * @param displayLocale
587      * @return
588      */
589     public String getDisplayCountry(NameFormatter displayLocale) {
590         if (displayLocale == null) displayLocale = getDefaultFormatter();
591         return displayLocale.getDisplayCountry(this);
592     }
593 
594     /**
595      * These functions wrap calls to the displayLocale, but are provided to supply an interface that looks similar to
596      * ULocale.getDisplay___(displayLocale)
597      *
598      * @param displayLocale
599      * @return
600      */
601     public String getDisplayVariant(NameFormatter displayLocale) {
602         if (displayLocale == null) displayLocale = getDefaultFormatter();
603         return displayLocale.getDisplayVariant(this);
604     }
605 
606     /**
607      * Construct an instance from an array
608      *
609      * @param available
610      * @return
611      */
612     public static Set<CLDRLocale> getInstance(Iterable<String> available) {
613         Set<CLDRLocale> s = new TreeSet<CLDRLocale>();
614         for (String str : available) {
615             s.add(CLDRLocale.getInstance(str));
616         }
617         return s;
618     }
619 
620     public interface SublocaleProvider {
621         public Set<CLDRLocale> subLocalesOf(CLDRLocale forLocale);
622     }
623 
624     public String getDisplayName(NameFormatter engFormat, boolean combined, Transform<String, String> picker) {
625         return engFormat.getDisplayName(this, combined, picker);
626     }
627 
628     /**
629      * Return the highest parent that is a child of root, or null.
630      * @return highest parent, or null.  ROOT.getHighestNonrootParent() also returns null.
631      */
632     public CLDRLocale getHighestNonrootParent() {
633         CLDRLocale res;
634         if (this == ROOT) {
635             res = null;
636             ;
637         } else if (this.parent == ROOT) {
638             res = this;
639         } else if (this.parent == null) {
640             res = this;
641         } else {
642             res = parent.getHighestNonrootParent();
643         }
644         if (DEBUG) System.out.println(this + ".HNRP=" + res);
645         return res;
646     }
647 }
648