001: /**
002: *******************************************************************************
003: * Copyright (C) 2001-2006, International Business Machines Corporation and *
004: * others. All Rights Reserved. *
005: *******************************************************************************
006: */package com.ibm.icu.impl;
007:
008: import java.util.Collections;
009: import java.util.Iterator;
010: import java.util.Locale;
011: import java.util.Map;
012: import java.util.Set;
013:
014: import com.ibm.icu.util.ULocale;
015:
016: public class ICULocaleService extends ICUService {
017: private ULocale fallbackLocale;
018: private String fallbackLocaleName;
019:
020: /**
021: * Construct an ICULocaleService.
022: */
023: public ICULocaleService() {
024: }
025:
026: /**
027: * Construct an ICULocaleService with a name (useful for debugging).
028: */
029: public ICULocaleService(String name) {
030: super (name);
031: }
032:
033: /**
034: * Convenience override for callers using locales. This calls
035: * get(ULocale, int, ULocale[]) with KIND_ANY for kind and null for
036: * actualReturn.
037: */
038: public Object get(ULocale locale) {
039: return get(locale, LocaleKey.KIND_ANY, null);
040: }
041:
042: /**
043: * Convenience override for callers using locales. This calls
044: * get(ULocale, int, ULocale[]) with a null actualReturn.
045: */
046: public Object get(ULocale locale, int kind) {
047: return get(locale, kind, null);
048: }
049:
050: /**
051: * Convenience override for callers using locales. This calls
052: * get(ULocale, int, ULocale[]) with KIND_ANY for kind.
053: */
054: public Object get(ULocale locale, ULocale[] actualReturn) {
055: return get(locale, LocaleKey.KIND_ANY, actualReturn);
056: }
057:
058: /**
059: * Convenience override for callers using locales. This uses
060: * createKey(ULocale.toString(), kind) to create a key, calls getKey, and then
061: * if actualReturn is not null, returns the actualResult from
062: * getKey (stripping any prefix) into a ULocale.
063: */
064: public Object get(ULocale locale, int kind, ULocale[] actualReturn) {
065: Key key = createKey(locale, kind);
066: if (actualReturn == null) {
067: return getKey(key);
068: }
069:
070: String[] temp = new String[1];
071: Object result = getKey(key, temp);
072: if (result != null) {
073: int n = temp[0].indexOf("/");
074: if (n >= 0) {
075: temp[0] = temp[0].substring(n + 1);
076: }
077: actualReturn[0] = new ULocale(temp[0]);
078: }
079: return result;
080: }
081:
082: /**
083: * Convenience override for callers using locales. This calls
084: * registerObject(Object, ULocale, int kind, boolean visible)
085: * passing KIND_ANY for the kind, and true for the visibility.
086: */
087: public Factory registerObject(Object obj, ULocale locale) {
088: return registerObject(obj, locale, LocaleKey.KIND_ANY, true);
089: }
090:
091: /**
092: * Convenience override for callers using locales. This calls
093: * registerObject(Object, ULocale, int kind, boolean visible)
094: * passing KIND_ANY for the kind.
095: */
096: public Factory registerObject(Object obj, ULocale locale,
097: boolean visible) {
098: return registerObject(obj, locale, LocaleKey.KIND_ANY, visible);
099: }
100:
101: /**
102: * Convenience function for callers using locales. This calls
103: * registerObject(Object, ULocale, int kind, boolean visible)
104: * passing true for the visibility.
105: */
106: public Factory registerObject(Object obj, ULocale locale, int kind) {
107: return registerObject(obj, locale, kind, true);
108: }
109:
110: /**
111: * Convenience function for callers using locales. This instantiates
112: * a SimpleLocaleKeyFactory, and registers the factory.
113: */
114: public Factory registerObject(Object obj, ULocale locale, int kind,
115: boolean visible) {
116: Factory factory = new SimpleLocaleKeyFactory(obj, locale, kind,
117: visible);
118: return registerFactory(factory);
119: }
120:
121: /**
122: * Convenience method for callers using locales. This returns the standard
123: * Locale list, built from the Set of visible ids.
124: */
125: public Locale[] getAvailableLocales() {
126: // TODO make this wrap getAvailableULocales later
127: Set visIDs = getVisibleIDs();
128: Iterator iter = visIDs.iterator();
129: Locale[] locales = new Locale[visIDs.size()];
130: int n = 0;
131: while (iter.hasNext()) {
132: Locale loc = LocaleUtility.getLocaleFromName((String) iter
133: .next());
134: locales[n++] = loc;
135: }
136: return locales;
137: }
138:
139: /**
140: * Convenience method for callers using locales. This returns the standard
141: * ULocale list, built from the Set of visible ids.
142: */
143: public ULocale[] getAvailableULocales() {
144: Set visIDs = getVisibleIDs();
145: Iterator iter = visIDs.iterator();
146: ULocale[] locales = new ULocale[visIDs.size()];
147: int n = 0;
148: while (iter.hasNext()) {
149: locales[n++] = new ULocale((String) iter.next());
150: }
151: return locales;
152: }
153:
154: /**
155: * A subclass of Key that implements a locale fallback mechanism.
156: * The first locale to search for is the locale provided by the
157: * client, and the fallback locale to search for is the current
158: * default locale. If a prefix is present, the currentDescriptor
159: * includes it before the locale proper, separated by "/". This
160: * is the default key instantiated by ICULocaleService.</p>
161: *
162: * <p>Canonicalization adjusts the locale string so that the
163: * section before the first understore is in lower case, and the rest
164: * is in upper case, with no trailing underscores.</p>
165: */
166: public static class LocaleKey extends ICUService.Key {
167: private int kind;
168: private int varstart;
169: private String primaryID;
170: private String fallbackID;
171: private String currentID;
172:
173: public static final int KIND_ANY = -1;
174:
175: /**
176: * Create a LocaleKey with canonical primary and fallback IDs.
177: */
178: public static LocaleKey createWithCanonicalFallback(
179: String primaryID, String canonicalFallbackID) {
180: return createWithCanonicalFallback(primaryID,
181: canonicalFallbackID, KIND_ANY);
182: }
183:
184: /**
185: * Create a LocaleKey with canonical primary and fallback IDs.
186: */
187: public static LocaleKey createWithCanonicalFallback(
188: String primaryID, String canonicalFallbackID, int kind) {
189: if (primaryID == null) {
190: return null;
191: }
192: if (primaryID.length() == 0) {
193: primaryID = "root";
194: }
195: String canonicalPrimaryID = ULocale.getName(primaryID);
196: return new LocaleKey(primaryID, canonicalPrimaryID,
197: canonicalFallbackID, kind);
198: }
199:
200: /**
201: * Create a LocaleKey with canonical primary and fallback IDs.
202: */
203: public static LocaleKey createWithCanonical(ULocale locale,
204: String canonicalFallbackID, int kind) {
205: if (locale == null) {
206: return null;
207: }
208: String canonicalPrimaryID = locale.getName();
209: return new LocaleKey(canonicalPrimaryID,
210: canonicalPrimaryID, canonicalFallbackID, kind);
211: }
212:
213: /**
214: * PrimaryID is the user's requested locale string,
215: * canonicalPrimaryID is this string in canonical form,
216: * fallbackID is the current default locale's string in
217: * canonical form.
218: */
219: protected LocaleKey(String primaryID,
220: String canonicalPrimaryID, String canonicalFallbackID,
221: int kind) {
222: super (primaryID);
223:
224: this .kind = kind;
225: if (canonicalPrimaryID == null) {
226: this .primaryID = "";
227: } else {
228: this .primaryID = canonicalPrimaryID;
229: this .varstart = this .primaryID.indexOf('@');
230: }
231: if (this .primaryID == "") {
232: this .fallbackID = null;
233: } else {
234: if (canonicalFallbackID == null
235: || this .primaryID.equals(canonicalFallbackID)) {
236: this .fallbackID = "";
237: } else {
238: this .fallbackID = canonicalFallbackID;
239: }
240: }
241:
242: this .currentID = varstart == -1 ? this .primaryID
243: : this .primaryID.substring(0, varstart);
244: }
245:
246: /**
247: * Return the prefix associated with the kind, or null if the kind is KIND_ANY.
248: */
249: public String prefix() {
250: return kind == KIND_ANY ? null : Integer.toString(kind());
251: }
252:
253: /**
254: * Return the kind code associated with this key.
255: */
256: public int kind() {
257: return kind;
258: }
259:
260: /**
261: * Return the (canonical) original ID.
262: */
263: public String canonicalID() {
264: return primaryID;
265: }
266:
267: /**
268: * Return the (canonical) current ID, or null if no current id.
269: */
270: public String currentID() {
271: return currentID;
272: }
273:
274: /**
275: * Return the (canonical) current descriptor, or null if no current id.
276: * Includes the keywords, whereas the ID does not include keywords.
277: */
278: public String currentDescriptor() {
279: String result = currentID();
280: if (result != null) {
281: result = "/" + result;
282: if (varstart != -1) {
283: result += primaryID.substring(varstart);
284: }
285: if (kind != KIND_ANY) {
286: result = prefix() + result;
287: }
288: }
289: return result;
290: }
291:
292: /**
293: * Convenience method to return the locale corresponding to the (canonical) original ID.
294: */
295: public ULocale canonicalLocale() {
296: return new ULocale(primaryID);
297: }
298:
299: /**
300: * Convenience method to return the ulocale corresponding to the (canonical) currentID.
301: */
302: public ULocale currentLocale() {
303: if (varstart == -1) {
304: return new ULocale(currentID);
305: } else {
306: return new ULocale(currentID
307: + primaryID.substring(varstart));
308: }
309: }
310:
311: /**
312: * If the key has a fallback, modify the key and return true,
313: * otherwise return false.</p>
314: *
315: * <p>First falls back through the primary ID, then through
316: * the fallbackID. The final fallback is "root"
317: * unless the primary id was "root", in which case
318: * there is no fallback.
319: */
320: public boolean fallback() {
321: int x = currentID.lastIndexOf('_');
322: if (x != -1) {
323: while (--x >= 0 && currentID.charAt(x) == '_') { // handle zh__PINYIN
324: }
325: currentID = currentID.substring(0, x + 1);
326: return true;
327: }
328: if (fallbackID != null) {
329: if (fallbackID.length() == 0) {
330: currentID = "root";
331: fallbackID = null;
332: } else {
333: currentID = fallbackID;
334: fallbackID = "";
335: }
336: return true;
337: }
338: currentID = null;
339: return false;
340: }
341:
342: /**
343: * If a key created from id would eventually fallback to match the
344: * canonical ID of this key, return true.
345: */
346: public boolean isFallbackOf(String id) {
347: return LocaleUtility.isFallbackOf(canonicalID(), id);
348: }
349: }
350:
351: /**
352: * A subclass of Factory that uses LocaleKeys. If 'visible' the
353: * factory reports its IDs.
354: */
355: public static abstract class LocaleKeyFactory implements Factory {
356: protected final String name;
357: protected final boolean visible;
358:
359: public static final boolean VISIBLE = true;
360: public static final boolean INVISIBLE = false;
361:
362: /**
363: * Constructor used by subclasses.
364: */
365: protected LocaleKeyFactory(boolean visible) {
366: this .visible = visible;
367: this .name = null;
368: }
369:
370: /**
371: * Constructor used by subclasses.
372: */
373: protected LocaleKeyFactory(boolean visible, String name) {
374: this .visible = visible;
375: this .name = name;
376: }
377:
378: /**
379: * Implement superclass abstract method. This checks the currentID of
380: * the key against the supported IDs, and passes the canonicalLocale and
381: * kind off to handleCreate (which subclasses must implement).
382: */
383: public Object create(Key key, ICUService service) {
384: if (handlesKey(key)) {
385: LocaleKey lkey = (LocaleKey) key;
386: int kind = lkey.kind();
387:
388: ULocale uloc = lkey.currentLocale();
389: return handleCreate(uloc, kind, service);
390: } else {
391: // System.out.println("factory: " + this + " did not support id: " + key.currentID());
392: // System.out.println("supported ids: " + getSupportedIDs());
393: }
394: return null;
395: }
396:
397: protected boolean handlesKey(Key key) {
398: if (key != null) {
399: String id = key.currentID();
400: Set supported = getSupportedIDs();
401: return supported.contains(id);
402: }
403: return false;
404: }
405:
406: /**
407: * Override of superclass method.
408: */
409: public void updateVisibleIDs(Map result) {
410: Set cache = getSupportedIDs();
411: Iterator iter = cache.iterator();
412: while (iter.hasNext()) {
413: String id = (String) iter.next();
414: if (visible) {
415: result.put(id, this );
416: } else {
417: result.remove(id);
418: }
419: }
420: }
421:
422: /**
423: * Return a localized name for the locale represented by id.
424: */
425: public String getDisplayName(String id, ULocale locale) {
426: // assume if the user called this on us, we must have handled some fallback of this id
427: // if (isSupportedID(id)) {
428: if (locale == null) {
429: return id;
430: }
431: ULocale loc = new ULocale(id);
432: return loc.getDisplayName(locale);
433: // }
434: // return null;
435: }
436:
437: ///CLOVER:OFF
438: /**
439: * Utility method used by create(Key, ICUService). Subclasses can
440: * implement this instead of create.
441: */
442: protected Object handleCreate(ULocale loc, int kind,
443: ICUService service) {
444: return null;
445: }
446:
447: ///CLOVER:ON
448:
449: /**
450: * Return true if this id is one the factory supports (visible or
451: * otherwise).
452: */
453: protected boolean isSupportedID(String id) {
454: return getSupportedIDs().contains(id);
455: }
456:
457: /**
458: * Return the set of ids that this factory supports (visible or
459: * otherwise). This can be called often and might need to be
460: * cached if it is expensive to create.
461: */
462: protected Set getSupportedIDs() {
463: return Collections.EMPTY_SET;
464: }
465:
466: /**
467: * For debugging.
468: */
469: public String toString() {
470: StringBuffer buf = new StringBuffer(super .toString());
471: if (name != null) {
472: buf.append(", name: ");
473: buf.append(name);
474: }
475: buf.append(", visible: ");
476: buf.append(visible);
477: return buf.toString();
478: }
479: }
480:
481: /**
482: * A LocaleKeyFactory that just returns a single object for a kind/locale.
483: */
484: public static class SimpleLocaleKeyFactory extends LocaleKeyFactory {
485: private final Object obj;
486: private final String id;
487: private final int kind;
488:
489: // TODO: remove when we no longer need this
490: public SimpleLocaleKeyFactory(Object obj, ULocale locale,
491: int kind, boolean visible) {
492: this (obj, locale, kind, visible, null);
493: }
494:
495: public SimpleLocaleKeyFactory(Object obj, ULocale locale,
496: int kind, boolean visible, String name) {
497: super (visible, name);
498:
499: this .obj = obj;
500: this .id = locale.getBaseName();
501: this .kind = kind;
502: }
503:
504: /**
505: * Returns the service object if kind/locale match. Service is not used.
506: */
507: public Object create(Key key, ICUService service) {
508: LocaleKey lkey = (LocaleKey) key;
509: if (kind == LocaleKey.KIND_ANY || kind == lkey.kind()) {
510: String keyID = lkey.currentID();
511: if (id.equals(keyID)) {
512: return obj;
513: }
514: }
515: return null;
516: }
517:
518: protected boolean isSupportedID(String id) {
519: return this .id.equals(id);
520: }
521:
522: public void updateVisibleIDs(Map result) {
523: if (visible) {
524: result.put(id, this );
525: } else {
526: result.remove(id);
527: }
528: }
529:
530: public String toString() {
531: StringBuffer buf = new StringBuffer(super .toString());
532: buf.append(", id: ");
533: buf.append(id);
534: buf.append(", kind: ");
535: buf.append(kind);
536: return buf.toString();
537: }
538: }
539:
540: /**
541: * A LocaleKeyFactory that creates a service based on the ICU locale data.
542: * This is a base class for most ICU factories. Subclasses instantiate it
543: * with a constructor that takes a bundle name, which determines the supported
544: * IDs. Subclasses then override handleCreate to create the actual service
545: * object. The default implementation returns a resource bundle.
546: */
547: public static class ICUResourceBundleFactory extends
548: LocaleKeyFactory {
549: protected final String bundleName;
550:
551: /**
552: * Convenience constructor that uses the main ICU bundle name.
553: */
554: public ICUResourceBundleFactory() {
555: this (ICUResourceBundle.ICU_BASE_NAME);
556: }
557:
558: /**
559: * A service factory based on ICU resource data in resources
560: * with the given name.
561: */
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: protected Set getSupportedIDs() {
572: // note: "root" is one of the ids, but "" is not. Must convert ULocale.ROOT.
573: return ICUResourceBundle.getFullLocaleNameSet(bundleName);
574: }
575:
576: /**
577: * Override of superclass method.
578: */
579: public void updateVisibleIDs(Map result) {
580: Set visibleIDs = ICUResourceBundle
581: .getAvailableLocaleNameSet(bundleName); // only visible ids
582: Iterator iter = visibleIDs.iterator();
583: while (iter.hasNext()) {
584: String id = (String) iter.next();
585: result.put(id, this );
586: }
587: }
588:
589: /**
590: * Create the service. The default implementation returns the resource bundle
591: * for the locale, ignoring kind, and service.
592: */
593: protected Object handleCreate(ULocale loc, int kind,
594: ICUService service) {
595: return ICUResourceBundle.getBundleInstance(bundleName, loc);
596: }
597:
598: public String toString() {
599: return super .toString() + ", bundle: " + bundleName;
600: }
601: }
602:
603: /**
604: * Return the name of the current fallback locale. If it has changed since this was
605: * last accessed, the service cache is cleared.
606: */
607: public String validateFallbackLocale() {
608: ULocale loc = ULocale.getDefault();
609: if (loc != fallbackLocale) {
610: synchronized (this ) {
611: if (loc != fallbackLocale) {
612: fallbackLocale = loc;
613: fallbackLocaleName = loc.getBaseName();
614: clearServiceCache();
615: }
616: }
617: }
618: return fallbackLocaleName;
619: }
620:
621: public Key createKey(String id) {
622: return LocaleKey.createWithCanonicalFallback(id,
623: validateFallbackLocale());
624: }
625:
626: public Key createKey(String id, int kind) {
627: return LocaleKey.createWithCanonicalFallback(id,
628: validateFallbackLocale(), kind);
629: }
630:
631: public Key createKey(ULocale l, int kind) {
632: return LocaleKey.createWithCanonical(l,
633: validateFallbackLocale(), kind);
634: }
635: }
|