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