• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html#License
3 /**
4  *******************************************************************************
5  * Copyright (C) 2001-2016, International Business Machines Corporation and
6  * others. All Rights Reserved.
7  *******************************************************************************
8  */
9 package com.ibm.icu.impl;
10 
11 import java.util.Collections;
12 import java.util.Locale;
13 import java.util.Map;
14 import java.util.Set;
15 
16 import com.ibm.icu.util.ULocale;
17 
18 public class ICULocaleService extends ICUService {
19     private ULocale fallbackLocale;
20     private String fallbackLocaleName;
21 
22     /**
23      * Construct an ICULocaleService.
24      */
ICULocaleService()25     public ICULocaleService() {
26     }
27 
28     /**
29      * Construct an ICULocaleService with a name (useful for debugging).
30      */
ICULocaleService(String name)31     public ICULocaleService(String name) {
32         super(name);
33     }
34 
35     /**
36      * Convenience override for callers using locales.  This calls
37      * get(ULocale, int, ULocale[]) with KIND_ANY for kind and null for
38      * actualReturn.
39      */
get(ULocale locale)40     public Object get(ULocale locale) {
41         return get(locale, LocaleKey.KIND_ANY, null);
42     }
43 
44     /**
45      * Convenience override for callers using locales.  This calls
46      * get(ULocale, int, ULocale[]) with a null actualReturn.
47      */
get(ULocale locale, int kind)48     public Object get(ULocale locale, int kind) {
49         return get(locale, kind, null);
50     }
51 
52     /**
53      * Convenience override for callers using locales.  This calls
54      * get(ULocale, int, ULocale[]) with KIND_ANY for kind.
55      */
get(ULocale locale, ULocale[] actualReturn)56     public Object get(ULocale locale, ULocale[] actualReturn) {
57         return get(locale, LocaleKey.KIND_ANY, actualReturn);
58     }
59 
60     /**
61      * Convenience override for callers using locales.  This uses
62      * createKey(ULocale.toString(), kind) to create a key, calls getKey, and then
63      * if actualReturn is not null, returns the actualResult from
64      * getKey (stripping any prefix) into a ULocale.
65      */
get(ULocale locale, int kind, ULocale[] actualReturn)66     public Object get(ULocale locale, int kind, ULocale[] actualReturn) {
67         Key key = createKey(locale, kind);
68         if (actualReturn == null) {
69             return getKey(key);
70         }
71 
72         String[] temp = new String[1];
73         Object result = getKey(key, temp);
74         if (result != null) {
75             int n = temp[0].indexOf("/");
76             if (n >= 0) {
77                 temp[0] = temp[0].substring(n+1);
78             }
79             actualReturn[0] = new ULocale(temp[0]);
80         }
81         return result;
82     }
83 
84     /**
85      * Convenience override for callers using locales.  This calls
86      * registerObject(Object, ULocale, int kind, boolean visible)
87      * passing KIND_ANY for the kind, and true for the visibility.
88      */
registerObject(Object obj, ULocale locale)89     public Factory registerObject(Object obj, ULocale locale) {
90         return registerObject(obj, locale, LocaleKey.KIND_ANY, true);
91     }
92 
93     /**
94      * Convenience override for callers using locales.  This calls
95      * registerObject(Object, ULocale, int kind, boolean visible)
96      * passing KIND_ANY for the kind.
97      */
registerObject(Object obj, ULocale locale, boolean visible)98     public Factory registerObject(Object obj, ULocale locale, boolean visible) {
99         return registerObject(obj, locale, LocaleKey.KIND_ANY, visible);
100     }
101 
102     /**
103      * Convenience function for callers using locales.  This calls
104      * registerObject(Object, ULocale, int kind, boolean visible)
105      * passing true for the visibility.
106      */
registerObject(Object obj, ULocale locale, int kind)107     public Factory registerObject(Object obj, ULocale locale, int kind) {
108         return registerObject(obj, locale, kind, true);
109     }
110 
111     /**
112      * Convenience function for callers using locales.  This  instantiates
113      * a SimpleLocaleKeyFactory, and registers the factory.
114      */
registerObject(Object obj, ULocale locale, int kind, boolean visible)115     public Factory registerObject(Object obj, ULocale locale, int kind, boolean visible) {
116         Factory factory = new SimpleLocaleKeyFactory(obj, locale, kind, visible);
117         return registerFactory(factory);
118     }
119 
120     /**
121      * Convenience method for callers using locales.  This returns the standard
122      * Locale list, built from the Set of visible ids.
123      */
getAvailableLocales()124     public Locale[] getAvailableLocales() {
125         // TODO make this wrap getAvailableULocales later
126         Set<String> visIDs = getVisibleIDs();
127         Locale[] locales = new Locale[visIDs.size()];
128         int n = 0;
129         for (String id : visIDs) {
130             Locale loc = LocaleUtility.getLocaleFromName(id);
131             locales[n++] = loc;
132         }
133         return locales;
134     }
135 
136     /**
137      * Convenience method for callers using locales.  This returns the standard
138      * ULocale list, built from the Set of visible ids.
139      */
getAvailableULocales()140     public ULocale[] getAvailableULocales() {
141         Set<String> visIDs = getVisibleIDs();
142         ULocale[] locales = new ULocale[visIDs.size()];
143         int n = 0;
144         for (String id : visIDs) {
145             locales[n++] = new ULocale(id);
146         }
147         return locales;
148     }
149 
150     /**
151      * A subclass of Key that implements a locale fallback mechanism.
152      * The first locale to search for is the locale provided by the
153      * client, and the fallback locale to search for is the current
154      * default locale.  If a prefix is present, the currentDescriptor
155      * includes it before the locale proper, separated by "/".  This
156      * is the default key instantiated by ICULocaleService.</p>
157      *
158      * <p>Canonicalization adjusts the locale string so that the
159      * section before the first understore is in lower case, and the rest
160      * is in upper case, with no trailing underscores.</p>
161      */
162     public static class LocaleKey extends ICUService.Key {
163         private int kind;
164         private int varstart;
165         private String primaryID;
166         private String fallbackID;
167         private String currentID;
168 
169         public static final int KIND_ANY = -1;
170 
171         /**
172          * Create a LocaleKey with canonical primary and fallback IDs.
173          */
createWithCanonicalFallback(String primaryID, String canonicalFallbackID)174         public static LocaleKey createWithCanonicalFallback(String primaryID, String canonicalFallbackID) {
175             return createWithCanonicalFallback(primaryID, canonicalFallbackID, KIND_ANY);
176         }
177 
178         /**
179          * Create a LocaleKey with canonical primary and fallback IDs.
180          */
createWithCanonicalFallback(String primaryID, String canonicalFallbackID, int kind)181         public static LocaleKey createWithCanonicalFallback(String primaryID, String canonicalFallbackID, int kind) {
182             if (primaryID == null) {
183                 return null;
184             }
185             String canonicalPrimaryID = ULocale.getName(primaryID);
186             return new LocaleKey(primaryID, canonicalPrimaryID, canonicalFallbackID, kind);
187         }
188 
189         /**
190          * Create a LocaleKey with canonical primary and fallback IDs.
191          */
createWithCanonical(ULocale locale, String canonicalFallbackID, int kind)192         public static LocaleKey createWithCanonical(ULocale locale, String canonicalFallbackID, int kind) {
193             if (locale == null) {
194                 return null;
195             }
196             String canonicalPrimaryID = locale.getName();
197             return new LocaleKey(canonicalPrimaryID, canonicalPrimaryID, canonicalFallbackID, kind);
198         }
199 
200         /**
201          * PrimaryID is the user's requested locale string,
202          * canonicalPrimaryID is this string in canonical form,
203          * fallbackID is the current default locale's string in
204          * canonical form.
205          */
LocaleKey(String primaryID, String canonicalPrimaryID, String canonicalFallbackID, int kind)206         protected LocaleKey(String primaryID, String canonicalPrimaryID, String canonicalFallbackID, int kind) {
207             super(primaryID);
208             this.kind = kind;
209 
210             if (canonicalPrimaryID == null || canonicalPrimaryID.equalsIgnoreCase("root")) {
211                 this.primaryID = "";
212                 this.fallbackID = null;
213             } else {
214                 int idx = canonicalPrimaryID.indexOf('@');
215                 if (idx == 4 && canonicalPrimaryID.regionMatches(true, 0, "root", 0, 4)) {
216                     this.primaryID = canonicalPrimaryID.substring(4);
217                     this.varstart = 0;
218                     this.fallbackID = null;
219                 } else {
220                     this.primaryID = canonicalPrimaryID;
221                     this.varstart = idx;
222 
223                     if (canonicalFallbackID == null || this.primaryID.equals(canonicalFallbackID)) {
224                         this.fallbackID = "";
225                     } else {
226                         this.fallbackID = canonicalFallbackID;
227                     }
228                 }
229             }
230 
231             this.currentID = varstart == -1 ? this.primaryID : this.primaryID.substring(0, varstart);
232         }
233 
234         /**
235          * Return the prefix associated with the kind, or null if the kind is KIND_ANY.
236          */
prefix()237         public String prefix() {
238             return kind == KIND_ANY ? null : Integer.toString(kind());
239         }
240 
241         /**
242          * Return the kind code associated with this key.
243          */
kind()244         public int kind() {
245             return kind;
246         }
247 
248         /**
249          * Return the (canonical) original ID.
250          */
251         @Override
canonicalID()252         public String canonicalID() {
253             return primaryID;
254         }
255 
256         /**
257          * Return the (canonical) current ID, or null if no current id.
258          */
259         @Override
currentID()260         public String currentID() {
261             return currentID;
262         }
263 
264         /**
265          * Return the (canonical) current descriptor, or null if no current id.
266          * Includes the keywords, whereas the ID does not include keywords.
267          */
268         @Override
currentDescriptor()269         public String currentDescriptor() {
270             String result = currentID();
271             if (result != null) {
272                 StringBuilder buf = new StringBuilder(); // default capacity 16 is usually good enough
273                 if (kind != KIND_ANY) {
274                     buf.append(prefix());
275                 }
276                 buf.append('/');
277                 buf.append(result);
278                 if (varstart != -1) {
279                     buf.append(primaryID.substring(varstart, primaryID.length()));
280                 }
281                 result = buf.toString();
282             }
283             return result;
284         }
285 
286         /**
287          * Convenience method to return the locale corresponding to the (canonical) original ID.
288          */
canonicalLocale()289         public ULocale canonicalLocale() {
290             return new ULocale(primaryID);
291         }
292 
293         /**
294          * Convenience method to return the ulocale corresponding to the (canonical) currentID.
295          */
currentLocale()296         public ULocale currentLocale() {
297             if (varstart == -1) {
298                 return new ULocale(currentID);
299             } else {
300                 return new ULocale(currentID + primaryID.substring(varstart));
301             }
302         }
303 
304         /**
305          * If the key has a fallback, modify the key and return true,
306          * otherwise return false.</p>
307          *
308          * <p>First falls back through the primary ID, then through
309          * the fallbackID.  The final fallback is "" (root)
310          * unless the primary id was "" (root), in which case
311          * there is no fallback.
312          */
313         @Override
fallback()314         public boolean fallback() {
315             int x = currentID.lastIndexOf('_');
316             if (x != -1) {
317                 while (--x >= 0 && currentID.charAt(x) == '_') { // handle zh__PINYIN
318                 }
319                 currentID = currentID.substring(0, x+1);
320                 return true;
321             }
322             if (fallbackID != null) {
323                 currentID = fallbackID;
324                 if (fallbackID.length() == 0) {
325                     fallbackID = null;
326                 } else {
327                     fallbackID = "";
328                 }
329                 return true;
330             }
331             currentID = null;
332             return false;
333         }
334 
335         /**
336          * If a key created from id would eventually fallback to match the
337          * canonical ID of this key, return true.
338          */
339         @Override
isFallbackOf(String id)340         public boolean isFallbackOf(String id) {
341             return LocaleUtility.isFallbackOf(canonicalID(), id);
342         }
343     }
344 
345     /**
346      * A subclass of Factory that uses LocaleKeys.  If 'visible' the
347      * factory reports its IDs.
348      */
349     public static abstract class LocaleKeyFactory implements Factory {
350         protected final String name;
351         protected final boolean visible;
352 
353         public static final boolean VISIBLE = true;
354         public static final boolean INVISIBLE = false;
355 
356         /**
357          * Constructor used by subclasses.
358          */
LocaleKeyFactory(boolean visible)359         protected LocaleKeyFactory(boolean visible) {
360             this.visible = visible;
361             this.name = null;
362         }
363 
364         /**
365          * Constructor used by subclasses.
366          */
LocaleKeyFactory(boolean visible, String name)367         protected LocaleKeyFactory(boolean visible, String name) {
368             this.visible = visible;
369             this.name = name;
370         }
371 
372         /**
373          * Implement superclass abstract method.  This checks the currentID of
374          * the key against the supported IDs, and passes the canonicalLocale and
375          * kind off to handleCreate (which subclasses must implement).
376          */
377         @Override
create(Key key, ICUService service)378         public Object create(Key key, ICUService service) {
379             if (handlesKey(key)) {
380                 LocaleKey lkey = (LocaleKey)key;
381                 int kind = lkey.kind();
382 
383                 ULocale uloc = lkey.currentLocale();
384                 return handleCreate(uloc, kind, service);
385             } else {
386                 // System.out.println("factory: " + this + " did not support id: " + key.currentID());
387                 // System.out.println("supported ids: " + getSupportedIDs());
388             }
389             return null;
390         }
391 
handlesKey(Key key)392         protected boolean handlesKey(Key key) {
393             if (key != null) {
394                 String id = key.currentID();
395                 Set<String> supported = getSupportedIDs();
396                 return supported.contains(id);
397             }
398             return false;
399         }
400 
401         /**
402          * Override of superclass method.
403          */
404         @Override
updateVisibleIDs(Map<String, Factory> result)405         public void updateVisibleIDs(Map<String, Factory> result) {
406             Set<String> cache = getSupportedIDs();
407             for (String id : cache) {
408                 if (visible) {
409                     result.put(id, this);
410                 } else {
411                     result.remove(id);
412                 }
413             }
414        }
415 
416         /**
417          * Return a localized name for the locale represented by id.
418          */
419         @Override
getDisplayName(String id, ULocale locale)420         public String getDisplayName(String id, ULocale locale) {
421             // assume if the user called this on us, we must have handled some fallback of this id
422             //          if (isSupportedID(id)) {
423             if (locale == null) {
424                 return id;
425             }
426             ULocale loc = new ULocale(id);
427             return loc.getDisplayName(locale);
428             //              }
429             //          return null;
430         }
431 
432         ///CLOVER:OFF
433         /**
434          * Utility method used by create(Key, ICUService).  Subclasses can
435          * implement this instead of create.
436          */
handleCreate(ULocale loc, int kind, ICUService service)437         protected Object handleCreate(ULocale loc, int kind, ICUService service) {
438             return null;
439         }
440         ///CLOVER:ON
441 
442         /**
443          * Return true if this id is one the factory supports (visible or
444          * otherwise).
445          */
isSupportedID(String id)446         protected boolean isSupportedID(String id) {
447             return getSupportedIDs().contains(id);
448         }
449 
450         /**
451          * Return the set of ids that this factory supports (visible or
452          * otherwise).  This can be called often and might need to be
453          * cached if it is expensive to create.
454          */
getSupportedIDs()455         protected Set<String> getSupportedIDs() {
456             return Collections.emptySet();
457         }
458 
459         /**
460          * For debugging.
461          */
462         @Override
toString()463         public String toString() {
464             StringBuilder buf = new StringBuilder(super.toString());
465             if (name != null) {
466                 buf.append(", name: ");
467                 buf.append(name);
468             }
469             buf.append(", visible: ");
470             buf.append(visible);
471             return buf.toString();
472         }
473     }
474 
475     /**
476      * A LocaleKeyFactory that just returns a single object for a kind/locale.
477      */
478     public static class SimpleLocaleKeyFactory extends LocaleKeyFactory {
479         private final Object obj;
480         private final String id;
481         private final int kind;
482 
483         // TODO: remove when we no longer need this
SimpleLocaleKeyFactory(Object obj, ULocale locale, int kind, boolean visible)484         public SimpleLocaleKeyFactory(Object obj, ULocale locale, int kind, boolean visible) {
485             this(obj, locale, kind, visible, null);
486         }
487 
SimpleLocaleKeyFactory(Object obj, ULocale locale, int kind, boolean visible, String name)488         public SimpleLocaleKeyFactory(Object obj, ULocale locale, int kind, boolean visible, String name) {
489             super(visible, name);
490 
491             this.obj = obj;
492             this.id = locale.getBaseName();
493             this.kind = kind;
494         }
495 
496         /**
497          * Returns the service object if kind/locale match.  Service is not used.
498          */
499         @Override
create(Key key, ICUService service)500         public Object create(Key key, ICUService service) {
501             if (!(key instanceof LocaleKey)) {
502                 return null;
503             }
504 
505             LocaleKey lkey = (LocaleKey)key;
506             if (kind != LocaleKey.KIND_ANY && kind != lkey.kind()) {
507                 return null;
508             }
509             if (!id.equals(lkey.currentID())) {
510                 return null;
511             }
512 
513             return obj;
514         }
515 
516         @Override
isSupportedID(String idToCheck)517         protected boolean isSupportedID(String idToCheck) {
518             return this.id.equals(idToCheck);
519         }
520 
521         @Override
updateVisibleIDs(Map<String, Factory> result)522         public void updateVisibleIDs(Map<String, Factory> result) {
523             if (visible) {
524                 result.put(id, this);
525             } else {
526                 result.remove(id);
527             }
528         }
529 
530         @Override
toString()531         public String toString() {
532             StringBuilder buf = new StringBuilder(super.toString());
533             buf.append(", id: ");
534             buf.append(id);
535             buf.append(", kind: ");
536             buf.append(kind);
537             return buf.toString();
538         }
539     }
540 
541     /**
542      * A LocaleKeyFactory that creates a service based on the ICU locale data.
543      * This is a base class for most ICU factories.  Subclasses instantiate it
544      * with a constructor that takes a bundle name, which determines the supported
545      * IDs.  Subclasses then override handleCreate to create the actual service
546      * object.  The default implementation returns a resource bundle.
547      */
548     public static class ICUResourceBundleFactory extends LocaleKeyFactory {
549         protected final String bundleName;
550 
551         /**
552          * Convenience constructor that uses the main ICU bundle name.
553          */
ICUResourceBundleFactory()554         public ICUResourceBundleFactory() {
555             this(ICUData.ICU_BASE_NAME);
556         }
557 
558         /**
559          * A service factory based on ICU resource data in resources
560          * with the given name.
561          */
ICUResourceBundleFactory(String bundleName)562         public ICUResourceBundleFactory(String bundleName) {
563             super(true);
564 
565             this.bundleName = bundleName;
566         }
567 
568         /**
569          * Return the supported IDs.  This is the set of all locale names for the bundleName.
570          */
571         @Override
getSupportedIDs()572         protected Set<String> getSupportedIDs() {
573             return ICUResourceBundle.getFullLocaleNameSet(bundleName, loader());
574         }
575 
576         /**
577          * Override of superclass method.
578          */
579         @Override
updateVisibleIDs(Map<String, Factory> result)580         public void updateVisibleIDs(Map<String, Factory> result) {
581           Set<String> visibleIDs = ICUResourceBundle.getAvailableLocaleNameSet(bundleName, loader()); // only visible ids
582             for (String id : visibleIDs) {
583                 result.put(id, this);
584             }
585         }
586 
587         /**
588          * Create the service.  The default implementation returns the resource bundle
589          * for the locale, ignoring kind, and service.
590          */
591         @Override
handleCreate(ULocale loc, int kind, ICUService service)592         protected Object handleCreate(ULocale loc, int kind, ICUService service) {
593             return ICUResourceBundle.getBundleInstance(bundleName, loc, loader());
594         }
595 
loader()596         protected ClassLoader loader() {
597             return ClassLoaderUtil.getClassLoader(getClass());
598         }
599 
600         @Override
toString()601         public String toString() {
602             return super.toString() + ", bundle: " + bundleName;
603         }
604     }
605 
606     /**
607      * Return the name of the current fallback locale.  If it has changed since this was
608      * last accessed, the service cache is cleared.
609      */
validateFallbackLocale()610     public String validateFallbackLocale() {
611         ULocale loc = ULocale.getDefault();
612         if (loc != fallbackLocale) {
613             synchronized (this) {
614                 if (loc != fallbackLocale) {
615                     fallbackLocale = loc;
616                     fallbackLocaleName = loc.getBaseName();
617                     clearServiceCache();
618                 }
619             }
620         }
621         return fallbackLocaleName;
622     }
623 
624     @Override
createKey(String id)625     public Key createKey(String id) {
626         return LocaleKey.createWithCanonicalFallback(id, validateFallbackLocale());
627     }
628 
createKey(String id, int kind)629     public Key createKey(String id, int kind) {
630         return LocaleKey.createWithCanonicalFallback(id, validateFallbackLocale(), kind);
631     }
632 
createKey(ULocale l, int kind)633     public Key createKey(ULocale l, int kind) {
634         return LocaleKey.createWithCanonical(l, validateFallbackLocale(), kind);
635     }
636 }
637