/** ******************************************************************************* * Copyright (C) 2001-2004, International Business Machines Corporation. * * All Rights Reserved. * ******************************************************************************* */ #include "unicode/utypes.h" #if !UCONFIG_NO_SERVICE #include "icuserv.h" #include "umutex.h" #undef SERVICE_REFCOUNT // in case we use the refcount stuff U_NAMESPACE_BEGIN /* ****************************************************************** */ const UChar ICUServiceKey::PREFIX_DELIMITER = 0x002F; /* '/' */ ICUServiceKey::ICUServiceKey(const UnicodeString& id) : _id(id) { } ICUServiceKey::~ICUServiceKey() { } const UnicodeString& ICUServiceKey::getID() const { return _id; } UnicodeString& ICUServiceKey::canonicalID(UnicodeString& result) const { return result.append(_id); } UnicodeString& ICUServiceKey::currentID(UnicodeString& result) const { return canonicalID(result); } UnicodeString& ICUServiceKey::currentDescriptor(UnicodeString& result) const { prefix(result); result.append(PREFIX_DELIMITER); return currentID(result); } UBool ICUServiceKey::fallback() { return FALSE; } UBool ICUServiceKey::isFallbackOf(const UnicodeString& id) const { return id == _id; } UnicodeString& ICUServiceKey::prefix(UnicodeString& result) const { return result; } UnicodeString& ICUServiceKey::parsePrefix(UnicodeString& result) { int32_t n = result.indexOf(PREFIX_DELIMITER); if (n < 0) { n = 0; } result.remove(n); return result; } UnicodeString& ICUServiceKey::parseSuffix(UnicodeString& result) { int32_t n = result.indexOf(PREFIX_DELIMITER); if (n >= 0) { result.remove(0, n+1); } return result; } #ifdef SERVICE_DEBUG UnicodeString& ICUServiceKey::debug(UnicodeString& result) const { debugClass(result); result.append(" id: "); result.append(_id); return result; } UnicodeString& ICUServiceKey::debugClass(UnicodeString& result) const { return result.append("ICUServiceKey"); } #endif UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ICUServiceKey) /* ****************************************************************** */ SimpleFactory::SimpleFactory(UObject* instanceToAdopt, const UnicodeString& id, UBool visible) : _instance(instanceToAdopt), _id(id), _visible(visible) { } SimpleFactory::~SimpleFactory() { delete _instance; } UObject* SimpleFactory::create(const ICUServiceKey& key, const ICUService* service, UErrorCode& status) const { if (U_SUCCESS(status)) { UnicodeString temp; if (_id == key.currentID(temp)) { return service->cloneInstance(_instance); } } return NULL; } void SimpleFactory::updateVisibleIDs(Hashtable& result, UErrorCode& status) const { if (_visible) { result.put(_id, (void*)this, status); // cast away const } else { result.remove(_id); } } UnicodeString& SimpleFactory::getDisplayName(const UnicodeString& id, const Locale& /* locale */, UnicodeString& result) const { if (_visible && _id == id) { result = _id; } else { result.setToBogus(); } return result; } #ifdef SERVICE_DEBUG UnicodeString& SimpleFactory::debug(UnicodeString& toAppendTo) const { debugClass(toAppendTo); toAppendTo.append(" id: "); toAppendTo.append(_id); toAppendTo.append(", visible: "); toAppendTo.append(_visible ? "T" : "F"); return toAppendTo; } UnicodeString& SimpleFactory::debugClass(UnicodeString& toAppendTo) const { return toAppendTo.append("SimpleFactory"); } #endif UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleFactory) /* ****************************************************************** */ UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ServiceListener) /* ****************************************************************** */ // Record the actual id for this service in the cache, so we can return it // even if we succeed later with a different id. class CacheEntry : public UMemory { private: int32_t refcount; public: UnicodeString actualDescriptor; UObject* service; /** * Releases a reference to the shared resource. */ ~CacheEntry() { delete service; } CacheEntry(const UnicodeString& _actualDescriptor, UObject* _service) : refcount(1), actualDescriptor(_actualDescriptor), service(_service) { } /** * Instantiation creates an initial reference, so don't call this * unless you're creating a new pointer to this. Management of * that pointer will have to know how to deal with refcounts. * Return true if the resource has not already been released. */ CacheEntry* ref() { ++refcount; return this; } /** * Destructions removes a reference, so don't call this unless * you're removing pointer to this somewhere. Management of that * pointer will have to know how to deal with refcounts. Once * the refcount drops to zero, the resource is released. Return * false if the resouce has been released. */ CacheEntry* unref() { if ((--refcount) == 0) { delete this; return NULL; } return this; } /** * Return TRUE if there is at least one reference to this and the * resource has not been released. */ UBool isShared() const { return refcount > 1; } }; // UObjectDeleter for serviceCache U_CDECL_BEGIN static void U_CALLCONV cacheDeleter(void* obj) { U_NAMESPACE_USE ((CacheEntry*)obj)->unref(); } /** * Deleter for UObjects */ static void U_CALLCONV deleteUObject(void *obj) { U_NAMESPACE_USE delete (UObject*) obj; } U_CDECL_END /* ****************************************************************** */ class DNCache : public UMemory { public: Hashtable cache; const Locale locale; DNCache(const Locale& _locale) : cache(FALSE), locale(_locale) { // cache.setKeyDeleter(uhash_deleteUnicodeString); } }; /* ****************************************************************** */ StringPair* StringPair::create(const UnicodeString& displayName, const UnicodeString& id, UErrorCode& status) { if (U_SUCCESS(status)) { StringPair* sp = new StringPair(displayName, id); if (sp == NULL || sp->isBogus()) { status = U_MEMORY_ALLOCATION_ERROR; delete sp; return NULL; } return sp; } return NULL; } UBool StringPair::isBogus() const { return displayName.isBogus() || id.isBogus(); } StringPair::StringPair(const UnicodeString& _displayName, const UnicodeString& _id) : displayName(_displayName) , id(_id) { } U_CAPI void U_EXPORT2 userv_deleteStringPair(void *obj) { U_NAMESPACE_USE delete (StringPair*) obj; } /* ****************************************************************** */ ICUService::ICUService() : name() , lock(0) , timestamp(0) , factories(NULL) , serviceCache(NULL) , idCache(NULL) , dnCache(NULL) { umtx_init(&lock); } ICUService::ICUService(const UnicodeString& newName) : name(newName) , lock(0) , timestamp(0) , factories(NULL) , serviceCache(NULL) , idCache(NULL) , dnCache(NULL) { umtx_init(&lock); } ICUService::~ICUService() { { Mutex mutex(&lock); clearCaches(); delete factories; factories = NULL; } umtx_destroy(&lock); } UObject* ICUService::get(const UnicodeString& descriptor, UErrorCode& status) const { return get(descriptor, NULL, status); } UObject* ICUService::get(const UnicodeString& descriptor, UnicodeString* actualReturn, UErrorCode& status) const { UObject* result = NULL; ICUServiceKey* key = createKey(&descriptor, status); if (key) { result = getKey(*key, actualReturn, status); delete key; } return result; } UObject* ICUService::getKey(ICUServiceKey& key, UErrorCode& status) const { return getKey(key, NULL, status); } // this is a vector that subclasses of ICUService can override to further customize the result object // before returning it. All other public get functions should call this one. UObject* ICUService::getKey(ICUServiceKey& key, UnicodeString* actualReturn, UErrorCode& status) const { return getKey(key, actualReturn, NULL, status); } // make it possible to call reentrantly on systems that don't have reentrant mutexes. // we can use this simple approach since we know the situation where we're calling // reentrantly even without knowing the thread. class XMutex : public UMemory { public: inline XMutex(UMTX *mutex, UBool reentering) : fMutex(mutex) , fActive(!reentering) { if (fActive) umtx_lock(fMutex); } inline ~XMutex() { if (fActive) umtx_unlock(fMutex); } private: UMTX *fMutex; UBool fActive; }; struct UVectorDeleter { UVector* _obj; UVectorDeleter() : _obj(NULL) {} ~UVectorDeleter() { delete _obj; } }; // called only by factories, treat as private UObject* ICUService::getKey(ICUServiceKey& key, UnicodeString* actualReturn, const ICUServiceFactory* factory, UErrorCode& status) const { if (U_FAILURE(status)) { return NULL; } if (isDefault()) { return handleDefault(key, actualReturn, status); } ICUService* ncthis = (ICUService*)this; // cast away semantic const CacheEntry* result = NULL; { // The factory list can't be modified until we're done, // otherwise we might update the cache with an invalid result. // The cache has to stay in synch with the factory list. // ICU doesn't have monitors so we can't use rw locks, so // we single-thread everything using this service, for now. // if factory is not null, we're calling from within the mutex, // and since some unix machines don't have reentrant mutexes we // need to make sure not to try to lock it again. XMutex(&ncthis->lock, factory != NULL); if (serviceCache == NULL) { ncthis->serviceCache = new Hashtable(FALSE, status); if (U_FAILURE(status)) { delete serviceCache; return NULL; } serviceCache->setValueDeleter(cacheDeleter); } UnicodeString currentDescriptor; UVectorDeleter cacheDescriptorList; UBool putInCache = FALSE; int32_t startIndex = 0; int32_t limit = factories->size(); UBool cacheResult = TRUE; if (factory != NULL) { for (int32_t i = 0; i < limit; ++i) { if (factory == (const ICUServiceFactory*)factories->elementAt(i)) { startIndex = i + 1; break; } } if (startIndex == 0) { // throw new InternalError("Factory " + factory + "not registered with service: " + this); status = U_ILLEGAL_ARGUMENT_ERROR; return NULL; } cacheResult = FALSE; } do { currentDescriptor.remove(); key.currentDescriptor(currentDescriptor); result = (CacheEntry*)serviceCache->get(currentDescriptor); if (result != NULL) { break; } // first test of cache failed, so we'll have to update // the cache if we eventually succeed-- that is, if we're // going to update the cache at all. putInCache = TRUE; int32_t index = startIndex; while (index < limit) { ICUServiceFactory* f = (ICUServiceFactory*)factories->elementAt(index++); UObject* service = f->create(key, this, status); if (U_FAILURE(status)) { delete service; return NULL; } if (service != NULL) { result = new CacheEntry(currentDescriptor, service); if (result == NULL) { delete service; status = U_MEMORY_ALLOCATION_ERROR; return NULL; } goto outerEnd; } } // prepare to load the cache with all additional ids that // will resolve to result, assuming we'll succeed. We // don't want to keep querying on an id that's going to // fallback to the one that succeeded, we want to hit the // cache the first time next goaround. if (cacheDescriptorList._obj == NULL) { cacheDescriptorList._obj = new UVector(uhash_deleteUnicodeString, NULL, 5, status); if (U_FAILURE(status)) { return NULL; } } UnicodeString* idToCache = new UnicodeString(currentDescriptor); if (idToCache == NULL || idToCache->isBogus()) { status = U_MEMORY_ALLOCATION_ERROR; return NULL; } cacheDescriptorList._obj->addElement(idToCache, status); if (U_FAILURE(status)) { return NULL; } } while (key.fallback()); outerEnd: if (result != NULL) { if (putInCache && cacheResult) { serviceCache->put(result->actualDescriptor, result, status); if (U_FAILURE(status)) { delete result; return NULL; } if (cacheDescriptorList._obj != NULL) { for (int32_t i = cacheDescriptorList._obj->size(); --i >= 0;) { UnicodeString* desc = (UnicodeString*)cacheDescriptorList._obj->elementAt(i); serviceCache->put(*desc, result, status); if (U_FAILURE(status)) { delete result; return NULL; } result->ref(); cacheDescriptorList._obj->removeElementAt(i); } } } if (actualReturn != NULL) { // strip null prefix if (result->actualDescriptor.indexOf((UChar)0x2f) == 0) { // U+002f=slash (/) actualReturn->remove(); actualReturn->append(result->actualDescriptor, 1, result->actualDescriptor.length() - 1); } else { *actualReturn = result->actualDescriptor; } if (actualReturn->isBogus()) { status = U_MEMORY_ALLOCATION_ERROR; delete result; return NULL; } } UObject* service = cloneInstance(result->service); if (putInCache && !cacheResult) { delete result; } return service; } } return handleDefault(key, actualReturn, status); } UObject* ICUService::handleDefault(const ICUServiceKey& /* key */, UnicodeString* /* actualIDReturn */, UErrorCode& /* status */) const { return NULL; } UVector& ICUService::getVisibleIDs(UVector& result, UErrorCode& status) const { return getVisibleIDs(result, NULL, status); } UVector& ICUService::getVisibleIDs(UVector& result, const UnicodeString* matchID, UErrorCode& status) const { result.removeAllElements(); if (U_FAILURE(status)) { return result; } ICUService * ncthis = (ICUService*)this; // cast away semantic const { Mutex mutex(&ncthis->lock); const Hashtable* map = getVisibleIDMap(status); if (map != NULL) { ICUServiceKey* fallbackKey = createKey(matchID, status); for (int32_t pos = 0;;) { const UHashElement* e = map->nextElement(pos); if (e == NULL) { break; } const UnicodeString* id = (const UnicodeString*)e->key.pointer; if (fallbackKey != NULL) { if (!fallbackKey->isFallbackOf(*id)) { continue; } } UnicodeString* idClone = new UnicodeString(*id); if (idClone == NULL || idClone->isBogus()) { delete idClone; status = U_MEMORY_ALLOCATION_ERROR; break; } result.addElement(idClone, status); if (U_FAILURE(status)) { delete idClone; break; } } delete fallbackKey; } } if (U_FAILURE(status)) { result.removeAllElements(); } return result; } const Hashtable* ICUService::getVisibleIDMap(UErrorCode& status) const { if (U_FAILURE(status)) return NULL; // must only be called when lock is already held ICUService* ncthis = (ICUService*)this; // cast away semantic const if (idCache == NULL) { ncthis->idCache = new Hashtable(); if (idCache == NULL) { status = U_MEMORY_ALLOCATION_ERROR; } else if (factories != NULL) { for (int32_t pos = factories->size(); --pos >= 0;) { ICUServiceFactory* f = (ICUServiceFactory*)factories->elementAt(pos); f->updateVisibleIDs(*idCache, status); } if (U_FAILURE(status)) { delete idCache; ncthis->idCache = NULL; } } } return idCache; } UnicodeString& ICUService::getDisplayName(const UnicodeString& id, UnicodeString& result) const { return getDisplayName(id, result, Locale::getDefault()); } UnicodeString& ICUService::getDisplayName(const UnicodeString& id, UnicodeString& result, const Locale& locale) const { { ICUService* ncthis = (ICUService*)this; // cast away semantic const UErrorCode status = U_ZERO_ERROR; Mutex mutex(&ncthis->lock); const Hashtable* map = getVisibleIDMap(status); if (map != NULL) { ICUServiceFactory* f = (ICUServiceFactory*)map->get(id); if (f != NULL) { f->getDisplayName(id, locale, result); return result; } // fallback UErrorCode status = U_ZERO_ERROR; ICUServiceKey* fallbackKey = createKey(&id, status); while (fallbackKey->fallback()) { UnicodeString us; fallbackKey->currentID(us); f = (ICUServiceFactory*)map->get(us); if (f != NULL) { f->getDisplayName(id, locale, result); delete fallbackKey; return result; } } delete fallbackKey; } } result.setToBogus(); return result; } UVector& ICUService::getDisplayNames(UVector& result, UErrorCode& status) const { return getDisplayNames(result, Locale::getDefault(), NULL, status); } UVector& ICUService::getDisplayNames(UVector& result, const Locale& locale, UErrorCode& status) const { return getDisplayNames(result, locale, NULL, status); } UVector& ICUService::getDisplayNames(UVector& result, const Locale& locale, const UnicodeString* matchID, UErrorCode& status) const { result.removeAllElements(); if (U_SUCCESS(status)) { ICUService* ncthis = (ICUService*)this; // cast away semantic const Mutex mutex(&ncthis->lock); if (dnCache != NULL && dnCache->locale != locale) { delete dnCache; ncthis->dnCache = NULL; } if (dnCache == NULL) { const Hashtable* m = getVisibleIDMap(status); if (m != NULL) { ncthis->dnCache = new DNCache(locale); if (dnCache == NULL) { status = U_MEMORY_ALLOCATION_ERROR; return result; } int32_t pos = 0; const UHashElement* entry = NULL; while ((entry = m->nextElement(pos)) != NULL) { const UnicodeString* id = (const UnicodeString*)entry->key.pointer; ICUServiceFactory* f = (ICUServiceFactory*)entry->value.pointer; UnicodeString dname; f->getDisplayName(*id, locale, dname); if (dname.isBogus()) { status = U_MEMORY_ALLOCATION_ERROR; } else { dnCache->cache.put(dname, (void*)id, status); // share pointer with visibleIDMap if (U_SUCCESS(status)) { continue; } } delete dnCache; ncthis->dnCache = NULL; return result; } } } } ICUServiceKey* matchKey = createKey(matchID, status); int32_t pos = 0; const UHashElement *entry = NULL; while ((entry = dnCache->cache.nextElement(pos)) != NULL) { const UnicodeString* id = (const UnicodeString*)entry->value.pointer; if (matchKey != NULL && !matchKey->isFallbackOf(*id)) { continue; } const UnicodeString* dn = (const UnicodeString*)entry->key.pointer; StringPair* sp = StringPair::create(*id, *dn, status); result.addElement(sp, status); if (U_FAILURE(status)) { result.removeAllElements(); break; } } delete matchKey; return result; } URegistryKey ICUService::registerInstance(UObject* objToAdopt, const UnicodeString& id, UErrorCode& status) { return registerInstance(objToAdopt, id, TRUE, status); } URegistryKey ICUService::registerInstance(UObject* objToAdopt, const UnicodeString& id, UBool visible, UErrorCode& status) { ICUServiceKey* key = createKey(&id, status); if (key != NULL) { UnicodeString canonicalID; key->canonicalID(canonicalID); delete key; ICUServiceFactory* f = createSimpleFactory(objToAdopt, canonicalID, visible, status); if (f != NULL) { return registerFactory(f, status); } } delete objToAdopt; return NULL; } ICUServiceFactory* ICUService::createSimpleFactory(UObject* objToAdopt, const UnicodeString& id, UBool visible, UErrorCode& status) { if (U_SUCCESS(status)) { if ((objToAdopt != NULL) && (!id.isBogus())) { return new SimpleFactory(objToAdopt, id, visible); } status = U_ILLEGAL_ARGUMENT_ERROR; } return NULL; } URegistryKey ICUService::registerFactory(ICUServiceFactory* factoryToAdopt, UErrorCode& status) { if (U_SUCCESS(status) && factoryToAdopt != NULL) { Mutex mutex(&lock); if (factories == NULL) { factories = new UVector(deleteUObject, NULL, status); if (U_FAILURE(status)) { delete factories; return NULL; } } factories->insertElementAt(factoryToAdopt, 0, status); if (U_SUCCESS(status)) { clearCaches(); } else { delete factoryToAdopt; factoryToAdopt = NULL; } } if (factoryToAdopt != NULL) { notifyChanged(); } return (URegistryKey)factoryToAdopt; } UBool ICUService::unregister(URegistryKey rkey, UErrorCode& status) { ICUServiceFactory *factory = (ICUServiceFactory*)rkey; UBool result = FALSE; if (factory != NULL && factories != NULL) { Mutex mutex(&lock); if (factories->removeElement(factory)) { clearCaches(); result = TRUE; } else { status = U_ILLEGAL_ARGUMENT_ERROR; delete factory; } } if (result) { notifyChanged(); } return result; } void ICUService::reset() { { Mutex mutex(&lock); reInitializeFactories(); clearCaches(); } notifyChanged(); } void ICUService::reInitializeFactories() { if (factories != NULL) { factories->removeAllElements(); } } UBool ICUService::isDefault() const { return countFactories() == 0; } ICUServiceKey* ICUService::createKey(const UnicodeString* id, UErrorCode& status) const { return (U_FAILURE(status) || id == NULL) ? NULL : new ICUServiceKey(*id); } void ICUService::clearCaches() { // callers synchronize before use ++timestamp; delete dnCache; dnCache = NULL; delete idCache; idCache = NULL; delete serviceCache; serviceCache = NULL; } void ICUService::clearServiceCache() { // callers synchronize before use delete serviceCache; serviceCache = NULL; } UBool ICUService::acceptsListener(const EventListener& l) const { return l.getDynamicClassID() == ServiceListener::getStaticClassID(); } void ICUService::notifyListener(EventListener& l) const { ((ServiceListener&)l).serviceChanged(*this); } UnicodeString& ICUService::getName(UnicodeString& result) const { return result.append(name); } int32_t ICUService::countFactories() const { return factories == NULL ? 0 : factories->size(); } int32_t ICUService::getTimestamp() const { return timestamp; } U_NAMESPACE_END /* UCONFIG_NO_SERVICE */ #endif