0001: /**
0002: *******************************************************************************
0003: * Copyright (C) 2001-2006, International Business Machines Corporation and *
0004: * others. All Rights Reserved. *
0005: *******************************************************************************
0006: */package com.ibm.icu.impl;
0007:
0008: //import com.ibm.icu.text.Collator;
0009:
0010: import java.lang.ref.SoftReference;
0011: import java.util.ArrayList;
0012: import java.util.Collections;
0013: import java.util.Comparator;
0014: import java.util.EventListener;
0015: import java.util.HashMap;
0016: import java.util.HashSet;
0017: import java.util.Iterator;
0018: import java.util.List;
0019: import java.util.ListIterator;
0020: import java.util.Locale;
0021: import java.util.Map;
0022: import java.util.Map.Entry;
0023: import java.util.Set;
0024: import java.util.SortedMap;
0025: import java.util.TreeMap;
0026:
0027: import com.ibm.icu.util.ULocale;
0028:
0029: /**
0030: * <p>A Service provides access to service objects that implement a
0031: * particular service, e.g. transliterators. Users provide a String
0032: * id (for example, a locale string) to the service, and get back an
0033: * object for that id. Service objects can be any kind of object.
0034: * The service object is cached and returned for later queries, so
0035: * generally it should not be mutable, or the caller should clone the
0036: * object before modifying it.</p>
0037: *
0038: * <p>Services 'canonicalize' the query id and use the canonical id to
0039: * query for the service. The service also defines a mechanism to
0040: * 'fallback' the id multiple times. Clients can optionally request
0041: * the actual id that was matched by a query when they use an id to
0042: * retrieve a service object.</p>
0043: *
0044: * <p>Service objects are instantiated by Factory objects registered with
0045: * the service. The service queries each Factory in turn, from most recently
0046: * registered to earliest registered, until one returns a service object.
0047: * If none responds with a service object, a fallback id is generated,
0048: * and the process repeats until a service object is returned or until
0049: * the id has no further fallbacks.</p>
0050: *
0051: * <p>Factories can be dynamically registered and unregistered with the
0052: * service. When registered, a Factory is installed at the head of
0053: * the factory list, and so gets 'first crack' at any keys or fallback
0054: * keys. When unregistered, it is removed from the service and can no
0055: * longer be located through it. Service objects generated by this
0056: * factory and held by the client are unaffected.</p>
0057: *
0058: * <p>ICUService uses Keys to query factories and perform
0059: * fallback. The Key defines the canonical form of the id, and
0060: * implements the fallback strategy. Custom Keys can be defined that
0061: * parse complex IDs into components that Factories can more easily
0062: * use. The Key can cache the results of this parsing to save
0063: * repeated effort. ICUService provides convenience APIs that
0064: * take Strings and generate default Keys for use in querying.</p>
0065: *
0066: * <p>ICUService provides API to get the list of ids publicly
0067: * supported by the service (although queries aren't restricted to
0068: * this list). This list contains only 'simple' IDs, and not fully
0069: * unique ids. Factories are associated with each simple ID and
0070: * the responsible factory can also return a human-readable localized
0071: * version of the simple ID, for use in user interfaces. ICUService
0072: * can also provide a sorted collection of the all the localized visible
0073: * ids.</p>
0074: *
0075: * <p>ICUService implements ICUNotifier, so that clients can register
0076: * to receive notification when factories are added or removed from
0077: * the service. ICUService provides a default EventListener subinterface,
0078: * ServiceListener, which can be registered with the service. When
0079: * the service changes, the ServiceListener's serviceChanged method
0080: * is called, with the service as the only argument.</p>
0081: *
0082: * <p>The ICUService API is both rich and generic, and it is expected
0083: * that most implementations will statically 'wrap' ICUService to
0084: * present a more appropriate API-- for example, to declare the type
0085: * of the objects returned from get, to limit the factories that can
0086: * be registered with the service, or to define their own listener
0087: * interface with a custom callback method. They might also customize
0088: * ICUService by overriding it, for example, to customize the Key and
0089: * fallback strategy. ICULocaleService is a customized service that
0090: * uses Locale names as ids and uses Keys that implement the standard
0091: * resource bundle fallback strategy.<p>
0092: */
0093: public class ICUService extends ICUNotifier {
0094: /**
0095: * Name used for debugging.
0096: */
0097: protected final String name;
0098:
0099: /**
0100: * Constructor.
0101: */
0102: public ICUService() {
0103: name = "";
0104: }
0105:
0106: private static final boolean DEBUG = ICUDebug.enabled("service");
0107:
0108: /**
0109: * Construct with a name (useful for debugging).
0110: */
0111: public ICUService(String name) {
0112: this .name = name;
0113: }
0114:
0115: /**
0116: * Access to factories is protected by a read-write lock. This is
0117: * to allow multiple threads to read concurrently, but keep
0118: * changes to the factory list atomic with respect to all readers.
0119: */
0120: private final ICURWLock factoryLock = new ICURWLock();
0121:
0122: /**
0123: * All the factories registered with this service.
0124: */
0125: private final List factories = new ArrayList();
0126:
0127: /**
0128: * Record the default number of factories for this service.
0129: * Can be set by markDefault.
0130: */
0131: private int defaultSize = 0;
0132:
0133: /**
0134: * Keys are used to communicate with factories to generate an
0135: * instance of the service. Keys define how ids are
0136: * canonicalized, provide both a current id and a current
0137: * descriptor to use in querying the cache and factories, and
0138: * determine the fallback strategy.</p>
0139: *
0140: * <p>Keys provide both a currentDescriptor and a currentID.
0141: * The descriptor contains an optional prefix, followed by '/'
0142: * and the currentID. Factories that handle complex keys,
0143: * for example number format factories that generate multiple
0144: * kinds of formatters for the same locale, use the descriptor
0145: * to provide a fully unique identifier for the service object,
0146: * while using the currentID (in this case, the locale string),
0147: * as the visible IDs that can be localized.
0148: *
0149: * <p> The default implementation of Key has no fallbacks and
0150: * has no custom descriptors.</p>
0151: */
0152: public static class Key {
0153: private final String id;
0154:
0155: /**
0156: * Construct a key from an id.
0157: */
0158: public Key(String id) {
0159: this .id = id;
0160: }
0161:
0162: /**
0163: * Return the original ID used to construct this key.
0164: */
0165: public final String id() {
0166: return id;
0167: }
0168:
0169: /**
0170: * Return the canonical version of the original ID. This implementation
0171: * returns the original ID unchanged.
0172: */
0173: public String canonicalID() {
0174: return id;
0175: }
0176:
0177: /**
0178: * Return the (canonical) current ID. This implementation
0179: * returns the canonical ID.
0180: */
0181: public String currentID() {
0182: return canonicalID();
0183: }
0184:
0185: /**
0186: * Return the current descriptor. This implementation returns
0187: * the current ID. The current descriptor is used to fully
0188: * identify an instance of the service in the cache. A
0189: * factory may handle all descriptors for an ID, or just a
0190: * particular descriptor. The factory can either parse the
0191: * descriptor or use custom API on the key in order to
0192: * instantiate the service.
0193: */
0194: public String currentDescriptor() {
0195: return "/" + currentID();
0196: }
0197:
0198: /**
0199: * If the key has a fallback, modify the key and return true,
0200: * otherwise return false. The current ID will change if there
0201: * is a fallback. No currentIDs should be repeated, and fallback
0202: * must eventually return false. This implmentation has no fallbacks
0203: * and always returns false.
0204: */
0205: public boolean fallback() {
0206: return false;
0207: }
0208:
0209: /**
0210: * If a key created from id would eventually fallback to match the
0211: * canonical ID of this key, return true.
0212: */
0213: public boolean isFallbackOf(String id) {
0214: return canonicalID().equals(id);
0215: }
0216: }
0217:
0218: /**
0219: * Factories generate the service objects maintained by the
0220: * service. A factory generates a service object from a key,
0221: * updates id->factory mappings, and returns the display name for
0222: * a supported id.
0223: */
0224: public static interface Factory {
0225:
0226: /**
0227: * Create a service object from the key, if this factory
0228: * supports the key. Otherwise, return null.
0229: *
0230: * <p>If the factory supports the key, then it can call
0231: * the service's getKey(Key, String[], Factory) method
0232: * passing itself as the factory to get the object that
0233: * the service would have created prior to the factory's
0234: * registration with the service. This can change the
0235: * key, so any information required from the key should
0236: * be extracted before making such a callback.
0237: */
0238: public Object create(Key key, ICUService service);
0239:
0240: /**
0241: * Update the result IDs (not descriptors) to reflect the IDs
0242: * this factory handles. This function and getDisplayName are
0243: * used to support ICUService.getDisplayNames. Basically, the
0244: * factory has to determine which IDs it will permit to be
0245: * available, and of those, which it will provide localized
0246: * display names for. In most cases this reflects the IDs that
0247: * the factory directly supports.
0248: */
0249: public void updateVisibleIDs(Map result);
0250:
0251: /**
0252: * Return the display name for this id in the provided locale.
0253: * This is an localized id, not a descriptor. If the id is
0254: * not visible or not defined by the factory, return null.
0255: * If locale is null, return id unchanged.
0256: */
0257: public String getDisplayName(String id, ULocale locale);
0258: }
0259:
0260: /**
0261: * A default implementation of factory. This provides default
0262: * implementations for subclasses, and implements a singleton
0263: * factory that matches a single id and returns a single
0264: * (possibly deferred-initialized) instance. This implements
0265: * updateVisibleIDs to add a mapping from its ID to itself
0266: * if visible is true, or to remove any existing mapping
0267: * for its ID if visible is false.
0268: */
0269: public static class SimpleFactory implements Factory {
0270: protected Object instance;
0271: protected String id;
0272: protected boolean visible;
0273:
0274: /**
0275: * Convenience constructor that calls SimpleFactory(Object, String, boolean)
0276: * with visible true.
0277: */
0278: public SimpleFactory(Object instance, String id) {
0279: this (instance, id, true);
0280: }
0281:
0282: /**
0283: * Construct a simple factory that maps a single id to a single
0284: * service instance. If visible is true, the id will be visible.
0285: * Neither the instance nor the id can be null.
0286: */
0287: public SimpleFactory(Object instance, String id, boolean visible) {
0288: if (instance == null || id == null) {
0289: throw new IllegalArgumentException(
0290: "Instance or id is null");
0291: }
0292: this .instance = instance;
0293: this .id = id;
0294: this .visible = visible;
0295: }
0296:
0297: /**
0298: * Return the service instance if the factory's id is equal to
0299: * the key's currentID. Service is ignored.
0300: */
0301: public Object create(Key key, ICUService service) {
0302: if (id.equals(key.currentID())) {
0303: return instance;
0304: }
0305: return null;
0306: }
0307:
0308: /**
0309: * If visible, adds a mapping from id -> this to the result,
0310: * otherwise removes id from result.
0311: */
0312: public void updateVisibleIDs(Map result) {
0313: if (visible) {
0314: result.put(id, this );
0315: } else {
0316: result.remove(id);
0317: }
0318: }
0319:
0320: /**
0321: * If this.id equals id, returns id regardless of locale,
0322: * otherwise returns null. (This default implementation has
0323: * no localized id information.)
0324: */
0325: public String getDisplayName(String id, ULocale locale) {
0326: return (visible && this .id.equals(id)) ? id : null;
0327: }
0328:
0329: /**
0330: * For debugging.
0331: */
0332: public String toString() {
0333: StringBuffer buf = new StringBuffer(super .toString());
0334: buf.append(", id: ");
0335: buf.append(id);
0336: buf.append(", visible: ");
0337: buf.append(visible);
0338: return buf.toString();
0339: }
0340: }
0341:
0342: /**
0343: * Convenience override for get(String, String[]). This uses
0344: * createKey to create a key for the provided descriptor.
0345: */
0346: public Object get(String descriptor) {
0347: return getKey(createKey(descriptor), null);
0348: }
0349:
0350: /**
0351: * Convenience override for get(Key, String[]). This uses
0352: * createKey to create a key from the provided descriptor.
0353: */
0354: public Object get(String descriptor, String[] actualReturn) {
0355: if (descriptor == null) {
0356: throw new NullPointerException(
0357: "descriptor must not be null");
0358: }
0359: return getKey(createKey(descriptor), actualReturn);
0360: }
0361:
0362: /**
0363: * Convenience override for get(Key, String[]).
0364: */
0365: public Object getKey(Key key) {
0366: return getKey(key, null);
0367: }
0368:
0369: /**
0370: * <p>Given a key, return a service object, and, if actualReturn
0371: * is not null, the descriptor with which it was found in the
0372: * first element of actualReturn. If no service object matches
0373: * this key, return null, and leave actualReturn unchanged.</p>
0374: *
0375: * <p>This queries the cache using the key's descriptor, and if no
0376: * object in the cache matches it, tries the key on each
0377: * registered factory, in order. If none generates a service
0378: * object for the key, repeats the process with each fallback of
0379: * the key, until either one returns a service object, or the key
0380: * has no fallback.</p>
0381: *
0382: * <p>If key is null, just returns null.</p>
0383: */
0384: public Object getKey(Key key, String[] actualReturn) {
0385: return getKey(key, actualReturn, null);
0386: }
0387:
0388: // debugging
0389: // Map hardRef;
0390:
0391: public Object getKey(Key key, String[] actualReturn, Factory factory) {
0392: if (factories.size() == 0) {
0393: return handleDefault(key, actualReturn);
0394: }
0395:
0396: if (DEBUG)
0397: System.out.println("Service: " + name + " key: "
0398: + key.canonicalID());
0399:
0400: CacheEntry result = null;
0401: if (key != null) {
0402: try {
0403: // The factory list can't be modified until we're done,
0404: // otherwise we might update the cache with an invalid result.
0405: // The cache has to stay in synch with the factory list.
0406: factoryLock.acquireRead();
0407:
0408: Map cache = null;
0409: SoftReference cref = cacheref; // copy so we don't need to sync on this
0410: if (cref != null) {
0411: if (DEBUG)
0412: System.out.println("Service " + name
0413: + " ref exists");
0414: cache = (Map) cref.get();
0415: }
0416: if (cache == null) {
0417: if (DEBUG)
0418: System.out.println("Service " + name
0419: + " cache was empty");
0420: // synchronized since additions and queries on the cache must be atomic
0421: // they can be interleaved, though
0422: cache = Collections.synchronizedMap(new HashMap());
0423: // hardRef = cache; // debug
0424: cref = new SoftReference(cache);
0425: }
0426:
0427: String currentDescriptor = null;
0428: ArrayList cacheDescriptorList = null;
0429: boolean putInCache = false;
0430:
0431: int NDebug = 0;
0432:
0433: int startIndex = 0;
0434: int limit = factories.size();
0435: boolean cacheResult = true;
0436: if (factory != null) {
0437: for (int i = 0; i < limit; ++i) {
0438: if (factory == factories.get(i)) {
0439: startIndex = i + 1;
0440: break;
0441: }
0442: }
0443: if (startIndex == 0) {
0444: throw new IllegalStateException("Factory "
0445: + factory
0446: + "not registered with service: "
0447: + this );
0448: }
0449: cacheResult = false;
0450: }
0451:
0452: outer: do {
0453: currentDescriptor = key.currentDescriptor();
0454: if (DEBUG)
0455: System.out
0456: .println(name + "[" + NDebug++
0457: + "] looking for: "
0458: + currentDescriptor);
0459: result = (CacheEntry) cache.get(currentDescriptor);
0460: if (result != null) {
0461: if (DEBUG)
0462: System.out.println(name
0463: + " found with descriptor: "
0464: + currentDescriptor);
0465: break outer;
0466: } else {
0467: if (DEBUG)
0468: System.out.println("did not find: "
0469: + currentDescriptor + " in cache");
0470: }
0471:
0472: // first test of cache failed, so we'll have to update
0473: // the cache if we eventually succeed-- that is, if we're
0474: // going to update the cache at all.
0475: putInCache = cacheResult;
0476:
0477: // int n = 0;
0478: int index = startIndex;
0479: while (index < limit) {
0480: Factory f = (Factory) factories.get(index++);
0481: if (DEBUG)
0482: System.out
0483: .println("trying factory["
0484: + (index - 1) + "] "
0485: + f.toString());
0486: Object service = f.create(key, this );
0487: if (service != null) {
0488: result = new CacheEntry(currentDescriptor,
0489: service);
0490: if (DEBUG)
0491: System.out.println(name
0492: + " factory supported: "
0493: + currentDescriptor
0494: + ", caching");
0495: break outer;
0496: } else {
0497: if (DEBUG)
0498: System.out
0499: .println("factory did not support: "
0500: + currentDescriptor);
0501: }
0502: }
0503:
0504: // prepare to load the cache with all additional ids that
0505: // will resolve to result, assuming we'll succeed. We
0506: // don't want to keep querying on an id that's going to
0507: // fallback to the one that succeeded, we want to hit the
0508: // cache the first time next goaround.
0509: if (cacheDescriptorList == null) {
0510: cacheDescriptorList = new ArrayList(5);
0511: }
0512: cacheDescriptorList.add(currentDescriptor);
0513:
0514: } while (key.fallback());
0515:
0516: if (result != null) {
0517: if (putInCache) {
0518: if (DEBUG)
0519: System.out.println("caching '"
0520: + result.actualDescriptor + "'");
0521: cache.put(result.actualDescriptor, result);
0522: if (cacheDescriptorList != null) {
0523: Iterator iter = cacheDescriptorList
0524: .iterator();
0525: while (iter.hasNext()) {
0526: String desc = (String) iter.next();
0527: if (DEBUG)
0528: System.out.println(name
0529: + " adding descriptor: '"
0530: + desc + "' for actual: '"
0531: + result.actualDescriptor
0532: + "'");
0533:
0534: cache.put(desc, result);
0535: }
0536: }
0537: // Atomic update. We held the read lock all this time
0538: // so we know our cache is consistent with the factory list.
0539: // We might stomp over a cache that some other thread
0540: // rebuilt, but that's the breaks. They're both good.
0541: cacheref = cref;
0542: }
0543:
0544: if (actualReturn != null) {
0545: // strip null prefix
0546: if (result.actualDescriptor.indexOf("/") == 0) {
0547: actualReturn[0] = result.actualDescriptor
0548: .substring(1);
0549: } else {
0550: actualReturn[0] = result.actualDescriptor;
0551: }
0552: }
0553:
0554: if (DEBUG)
0555: System.out.println("found in service: " + name);
0556:
0557: return result.service;
0558: }
0559: } finally {
0560: factoryLock.releaseRead();
0561: }
0562: }
0563:
0564: if (DEBUG)
0565: System.out.println("not found in service: " + name);
0566:
0567: return handleDefault(key, actualReturn);
0568: }
0569:
0570: private SoftReference cacheref;
0571:
0572: // Record the actual id for this service in the cache, so we can return it
0573: // even if we succeed later with a different id.
0574: private static final class CacheEntry {
0575: final String actualDescriptor;
0576: final Object service;
0577:
0578: CacheEntry(String actualDescriptor, Object service) {
0579: this .actualDescriptor = actualDescriptor;
0580: this .service = service;
0581: }
0582: }
0583:
0584: /**
0585: * Default handler for this service if no factory in the list
0586: * handled the key.
0587: */
0588: protected Object handleDefault(Key key, String[] actualIDReturn) {
0589: return null;
0590: }
0591:
0592: /**
0593: * Convenience override for getVisibleIDs(String) that passes null
0594: * as the fallback, thus returning all visible IDs.
0595: */
0596: public Set getVisibleIDs() {
0597: return getVisibleIDs(null);
0598: }
0599:
0600: /**
0601: * <p>Return a snapshot of the visible IDs for this service. This
0602: * set will not change as Factories are added or removed, but the
0603: * supported ids will, so there is no guarantee that all and only
0604: * the ids in the returned set are visible and supported by the
0605: * service in subsequent calls.</p>
0606: *
0607: * <p>matchID is passed to createKey to create a key. If the
0608: * key is not null, it is used to filter out ids that don't have
0609: * the key as a fallback.
0610: */
0611: public Set getVisibleIDs(String matchID) {
0612: Set result = getVisibleIDMap().keySet();
0613:
0614: Key fallbackKey = createKey(matchID);
0615:
0616: if (fallbackKey != null) {
0617: Set temp = new HashSet(result.size());
0618: Iterator iter = result.iterator();
0619: while (iter.hasNext()) {
0620: String id = (String) iter.next();
0621: if (fallbackKey.isFallbackOf(id)) {
0622: temp.add(id);
0623: }
0624: }
0625: result = temp;
0626: }
0627: return result;
0628: }
0629:
0630: /**
0631: * Return a map from visible ids to factories.
0632: */
0633: private Map getVisibleIDMap() {
0634: Map idcache = null;
0635: SoftReference ref = idref;
0636: if (ref != null) {
0637: idcache = (Map) ref.get();
0638: }
0639: while (idcache == null) {
0640: synchronized (this ) { // or idref-only lock?
0641: if (ref == idref || idref == null) {
0642: // no other thread updated idref before we got the lock, so
0643: // grab the factory list and update it ourselves
0644: try {
0645: factoryLock.acquireRead();
0646: idcache = new HashMap();
0647: ListIterator lIter = factories
0648: .listIterator(factories.size());
0649: while (lIter.hasPrevious()) {
0650: Factory f = (Factory) lIter.previous();
0651: f.updateVisibleIDs(idcache);
0652: }
0653: idcache = Collections.unmodifiableMap(idcache);
0654: idref = new SoftReference(idcache);
0655: } finally {
0656: factoryLock.releaseRead();
0657: }
0658: } else {
0659: // another thread updated idref, but gc may have stepped
0660: // in and undone its work, leaving idcache null. If so,
0661: // retry.
0662: ref = idref;
0663: idcache = (Map) ref.get();
0664: }
0665: }
0666: }
0667:
0668: return idcache;
0669: }
0670:
0671: private SoftReference idref;
0672:
0673: /**
0674: * Convenience override for getDisplayName(String, ULocale) that
0675: * uses the current default locale.
0676: */
0677: public String getDisplayName(String id) {
0678: return getDisplayName(id, ULocale.getDefault());
0679: }
0680:
0681: /**
0682: * Given a visible id, return the display name in the requested locale.
0683: * If there is no directly supported id corresponding to this id, return
0684: * null.
0685: */
0686: public String getDisplayName(String id, ULocale locale) {
0687: Map m = getVisibleIDMap();
0688: Factory f = (Factory) m.get(id);
0689: if (f != null) {
0690: return f.getDisplayName(id, locale);
0691: }
0692:
0693: Key key = createKey(id);
0694: while (key.fallback()) {
0695: f = (Factory) m.get(key.currentID());
0696: if (f != null) {
0697: return f.getDisplayName(id, locale);
0698: }
0699: }
0700:
0701: return null;
0702: }
0703:
0704: /**
0705: * Convenience override of getDisplayNames(ULocale, Comparator, String) that
0706: * uses the current default Locale as the locale, null as
0707: * the comparator, and null for the matchID.
0708: */
0709: public SortedMap getDisplayNames() {
0710: ULocale locale = ULocale.getDefault();
0711: return getDisplayNames(locale, null, null);
0712: }
0713:
0714: /**
0715: * Convenience override of getDisplayNames(ULocale, Comparator, String) that
0716: * uses null for the comparator, and null for the matchID.
0717: */
0718: public SortedMap getDisplayNames(ULocale locale) {
0719: return getDisplayNames(locale, null, null);
0720: }
0721:
0722: /**
0723: * Convenience override of getDisplayNames(ULocale, Comparator, String) that
0724: * uses null for the matchID, thus returning all display names.
0725: */
0726: public SortedMap getDisplayNames(ULocale locale, Comparator com) {
0727: return getDisplayNames(locale, com, null);
0728: }
0729:
0730: /**
0731: * Convenience override of getDisplayNames(ULocale, Comparator, String) that
0732: * uses null for the comparator.
0733: */
0734: public SortedMap getDisplayNames(ULocale locale, String matchID) {
0735: return getDisplayNames(locale, null, matchID);
0736: }
0737:
0738: /**
0739: * Return a snapshot of the mapping from display names to visible
0740: * IDs for this service. This set will not change as factories
0741: * are added or removed, but the supported ids will, so there is
0742: * no guarantee that all and only the ids in the returned map will
0743: * be visible and supported by the service in subsequent calls,
0744: * nor is there any guarantee that the current display names match
0745: * those in the set. The display names are sorted based on the
0746: * comparator provided.
0747: */
0748: public SortedMap getDisplayNames(ULocale locale, Comparator com,
0749: String matchID) {
0750: SortedMap dncache = null;
0751: LocaleRef ref = dnref;
0752:
0753: if (ref != null) {
0754: dncache = ref.get(locale, com);
0755: }
0756:
0757: while (dncache == null) {
0758: synchronized (this ) {
0759: if (ref == dnref || dnref == null) {
0760: dncache = new TreeMap(com); // sorted
0761:
0762: Map m = getVisibleIDMap();
0763: Iterator ei = m.entrySet().iterator();
0764: while (ei.hasNext()) {
0765: Entry e = (Entry) ei.next();
0766: String id = (String) e.getKey();
0767: Factory f = (Factory) e.getValue();
0768: dncache.put(f.getDisplayName(id, locale), id);
0769: }
0770:
0771: dncache = Collections
0772: .unmodifiableSortedMap(dncache);
0773: dnref = new LocaleRef(dncache, locale, com);
0774: } else {
0775: ref = dnref;
0776: dncache = ref.get(locale, com);
0777: }
0778: }
0779: }
0780:
0781: Key matchKey = createKey(matchID);
0782: if (matchKey == null) {
0783: return dncache;
0784: }
0785:
0786: SortedMap result = new TreeMap(dncache);
0787: Iterator iter = result.entrySet().iterator();
0788: while (iter.hasNext()) {
0789: Entry e = (Entry) iter.next();
0790: if (!matchKey.isFallbackOf((String) e.getValue())) {
0791: iter.remove();
0792: }
0793: }
0794: return result;
0795: }
0796:
0797: // we define a class so we get atomic simultaneous access to the
0798: // locale, comparator, and corresponding map.
0799: private static class LocaleRef {
0800: private final ULocale locale;
0801: private SoftReference ref;
0802: private Comparator com;
0803:
0804: LocaleRef(Map dnCache, ULocale locale, Comparator com) {
0805: this .locale = locale;
0806: this .com = com;
0807: this .ref = new SoftReference(dnCache);
0808: }
0809:
0810: SortedMap get(ULocale locale, Comparator com) {
0811: SortedMap m = (SortedMap) ref.get();
0812: if (m != null
0813: && this .locale.equals(locale)
0814: && (this .com == com || (this .com != null && this .com
0815: .equals(com)))) {
0816:
0817: return m;
0818: }
0819: return null;
0820: }
0821: }
0822:
0823: private LocaleRef dnref;
0824:
0825: /**
0826: * Return a snapshot of the currently registered factories. There
0827: * is no guarantee that the list will still match the current
0828: * factory list of the service subsequent to this call.
0829: */
0830: public final List factories() {
0831: try {
0832: factoryLock.acquireRead();
0833: return new ArrayList(factories);
0834: } finally {
0835: factoryLock.releaseRead();
0836: }
0837: }
0838:
0839: /**
0840: * A convenience override of registerObject(Object, String, boolean)
0841: * that defaults visible to true.
0842: */
0843: public Factory registerObject(Object obj, String id) {
0844: return registerObject(obj, id, true);
0845: }
0846:
0847: /**
0848: * Register an object with the provided id. The id will be
0849: * canonicalized. The canonicalized ID will be returned by
0850: * getVisibleIDs if visible is true.
0851: */
0852: public Factory registerObject(Object obj, String id, boolean visible) {
0853: String canonicalID = createKey(id).canonicalID();
0854: return registerFactory(new SimpleFactory(obj, canonicalID,
0855: visible));
0856: }
0857:
0858: /**
0859: * Register a Factory. Returns the factory if the service accepts
0860: * the factory, otherwise returns null. The default implementation
0861: * accepts all factories.
0862: */
0863: public final Factory registerFactory(Factory factory) {
0864: if (factory == null) {
0865: throw new NullPointerException();
0866: }
0867: try {
0868: factoryLock.acquireWrite();
0869: factories.add(0, factory);
0870: clearCaches();
0871: } finally {
0872: factoryLock.releaseWrite();
0873: }
0874: notifyChanged();
0875: return factory;
0876: }
0877:
0878: /**
0879: * Unregister a factory. The first matching registered factory will
0880: * be removed from the list. Returns true if a matching factory was
0881: * removed.
0882: */
0883: public final boolean unregisterFactory(Factory factory) {
0884: if (factory == null) {
0885: throw new NullPointerException();
0886: }
0887:
0888: boolean result = false;
0889: try {
0890: factoryLock.acquireWrite();
0891: if (factories.remove(factory)) {
0892: result = true;
0893: clearCaches();
0894: }
0895: } finally {
0896: factoryLock.releaseWrite();
0897: }
0898:
0899: if (result) {
0900: notifyChanged();
0901: }
0902: return result;
0903: }
0904:
0905: /**
0906: * Reset the service to the default factories. The factory
0907: * lock is acquired and then reInitializeFactories is called.
0908: */
0909: public final void reset() {
0910: try {
0911: factoryLock.acquireWrite();
0912: reInitializeFactories();
0913: clearCaches();
0914: } finally {
0915: factoryLock.releaseWrite();
0916: }
0917: notifyChanged();
0918: }
0919:
0920: /**
0921: * Reinitialize the factory list to its default state. By default
0922: * this clears the list. Subclasses can override to provide other
0923: * default initialization of the factory list. Subclasses must
0924: * not call this method directly, as it must only be called while
0925: * holding write access to the factory list.
0926: */
0927: protected void reInitializeFactories() {
0928: factories.clear();
0929: }
0930:
0931: /**
0932: * Return true if the service is in its default state. The default
0933: * implementation returns true if there are no factories registered.
0934: */
0935: public boolean isDefault() {
0936: return factories.size() == defaultSize;
0937: }
0938:
0939: /**
0940: * Set the default size to the current number of registered factories.
0941: * Used by subclasses to customize the behavior of isDefault.
0942: */
0943: protected void markDefault() {
0944: defaultSize = factories.size();
0945: }
0946:
0947: /**
0948: * Create a key from an id. This creates a Key instance.
0949: * Subclasses can override to define more useful keys appropriate
0950: * to the factories they accept. If id is null, returns null.
0951: */
0952: public Key createKey(String id) {
0953: return id == null ? null : new Key(id);
0954: }
0955:
0956: /**
0957: * Clear caches maintained by this service. Subclasses can
0958: * override if they implement additional that need to be cleared
0959: * when the service changes. Subclasses should generally not call
0960: * this method directly, as it must only be called while
0961: * synchronized on this.
0962: */
0963: protected void clearCaches() {
0964: // we don't synchronize on these because methods that use them
0965: // copy before use, and check for changes if they modify the
0966: // caches.
0967: cacheref = null;
0968: idref = null;
0969: dnref = null;
0970: }
0971:
0972: /**
0973: * Clears only the service cache.
0974: * This can be called by subclasses when a change affects the service
0975: * cache but not the id caches, e.g., when the default locale changes
0976: * the resolution of ids changes, but not the visible ids themselves.
0977: */
0978: protected void clearServiceCache() {
0979: cacheref = null;
0980: }
0981:
0982: /**
0983: * ServiceListener is the listener that ICUService provides by default.
0984: * ICUService will notifiy this listener when factories are added to
0985: * or removed from the service. Subclasses can provide
0986: * different listener interfaces that extend EventListener, and modify
0987: * acceptsListener and notifyListener as appropriate.
0988: */
0989: public static interface ServiceListener extends EventListener {
0990: public void serviceChanged(ICUService service);
0991: }
0992:
0993: /**
0994: * Return true if the listener is accepted; by default this
0995: * requires a ServiceListener. Subclasses can override to accept
0996: * different listeners.
0997: */
0998: protected boolean acceptsListener(EventListener l) {
0999: return l instanceof ServiceListener;
1000: }
1001:
1002: /**
1003: * Notify the listener, which by default is a ServiceListener.
1004: * Subclasses can override to use a different listener.
1005: */
1006: protected void notifyListener(EventListener l) {
1007: ((ServiceListener) l).serviceChanged(this );
1008: }
1009:
1010: /**
1011: * Return a string describing the statistics for this service.
1012: * This also resets the statistics. Used for debugging purposes.
1013: */
1014: public String stats() {
1015: ICURWLock.Stats stats = factoryLock.resetStats();
1016: if (stats != null) {
1017: return stats.toString();
1018: }
1019: return "no stats";
1020: }
1021:
1022: /**
1023: * Return the name of this service. This will be the empty string if none was assigned.
1024: */
1025: public String getName() {
1026: return name;
1027: }
1028:
1029: /**
1030: * Returns the result of super.toString, appending the name in curly braces.
1031: */
1032: public String toString() {
1033: return super .toString() + "{" + name + "}";
1034: }
1035: }
|