// © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html /** ******************************************************************************* * Copyright (C) 2001-2014, International Business Machines Corporation. * All Rights Reserved. ******************************************************************************* */ #include "unicode/utypes.h" #include "unicode/localpointer.h" #if !UCONFIG_NO_SERVICE #include "serv.h" #include "umutex.h" #undef SERVICE_REFCOUNT // in case we use the refcount stuff U_NAMESPACE_BEGIN /* ****************************************************************** */ const char16_t 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((UnicodeString)" id: "); result.append(_id); return result; } UnicodeString& ICUServiceKey::debugClass(UnicodeString& result) const { return result.append((UnicodeString)"ICUServiceKey"); } #endif UOBJECT_DEFINE_RTTI_IMPLEMENTATION(ICUServiceKey) /* ****************************************************************** */ ICUServiceFactory::~ICUServiceFactory() {} 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 nullptr; } 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((UnicodeString)" id: "); toAppendTo.append(_id); toAppendTo.append((UnicodeString)", visible: "); toAppendTo.append(_visible ? (UnicodeString)"T" : (UnicodeString)"F"); return toAppendTo; } UnicodeString& SimpleFactory::debugClass(UnicodeString& toAppendTo) const { return toAppendTo.append((UnicodeString)"SimpleFactory"); } #endif UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleFactory) /* ****************************************************************** */ ServiceListener::~ServiceListener() {} 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 resource has been released. */ CacheEntry* unref() { if ((--refcount) == 0) { delete this; return nullptr; } 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; } }; // Deleter for serviceCache U_CDECL_BEGIN static void U_CALLCONV cacheDeleter(void* obj) { U_NAMESPACE_USE ((CacheEntry*)obj)->unref(); } U_CDECL_END /* ****************************************************************** */ class DNCache : public UMemory { public: Hashtable cache; const Locale locale; DNCache(const Locale& _locale) : cache(), locale(_locale) { // cache.setKeyDeleter(uprv_deleteUObject); } }; /* ****************************************************************** */ StringPair* StringPair::create(const UnicodeString& displayName, const UnicodeString& id, UErrorCode& status) { if (U_SUCCESS(status)) { StringPair* sp = new StringPair(displayName, id); if (sp == nullptr || sp->isBogus()) { status = U_MEMORY_ALLOCATION_ERROR; delete sp; return nullptr; } return sp; } return nullptr; } UBool StringPair::isBogus() const { return displayName.isBogus() || id.isBogus(); } StringPair::StringPair(const UnicodeString& _displayName, const UnicodeString& _id) : displayName(_displayName) , id(_id) { } U_CDECL_BEGIN static void U_CALLCONV userv_deleteStringPair(void *obj) { U_NAMESPACE_USE delete (StringPair*) obj; } U_CDECL_END /* ****************************************************************** */ static UMutex lock; ICUService::ICUService() : name() , timestamp(0) , factories(nullptr) , serviceCache(nullptr) , idCache(nullptr) , dnCache(nullptr) { } ICUService::ICUService(const UnicodeString& newName) : name(newName) , timestamp(0) , factories(nullptr) , serviceCache(nullptr) , idCache(nullptr) , dnCache(nullptr) { } ICUService::~ICUService() { { Mutex mutex(&lock); clearCaches(); delete factories; factories = nullptr; } } UObject* ICUService::get(const UnicodeString& descriptor, UErrorCode& status) const { return get(descriptor, nullptr, status); } UObject* ICUService::get(const UnicodeString& descriptor, UnicodeString* actualReturn, UErrorCode& status) const { UObject* result = nullptr; 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, nullptr, 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, nullptr, 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(UMutex *mutex, UBool reentering) : fMutex(mutex) , fActive(!reentering) { if (fActive) umtx_lock(fMutex); } inline ~XMutex() { if (fActive) umtx_unlock(fMutex); } private: UMutex *fMutex; UBool fActive; }; // 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 nullptr; } if (isDefault()) { return handleDefault(key, actualReturn, status); } ICUService* ncthis = (ICUService*)this; // cast away semantic const CacheEntry* result = nullptr; { // 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 mutex(&lock, factory != nullptr); if (serviceCache == nullptr) { ncthis->serviceCache = new Hashtable(status); if (ncthis->serviceCache == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return nullptr; } if (U_FAILURE(status)) { delete serviceCache; return nullptr; } serviceCache->setValueDeleter(cacheDeleter); } UnicodeString currentDescriptor; LocalPointer cacheDescriptorList; UBool putInCache = false; int32_t startIndex = 0; int32_t limit = factories->size(); UBool cacheResult = true; if (factory != nullptr) { 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 nullptr; } cacheResult = false; } do { currentDescriptor.remove(); key.currentDescriptor(currentDescriptor); result = (CacheEntry*)serviceCache->get(currentDescriptor); if (result != nullptr) { 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++); LocalPointer service(f->create(key, this, status)); if (U_FAILURE(status)) { return nullptr; } if (service.isValid()) { result = new CacheEntry(currentDescriptor, service.getAlias()); if (result == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return nullptr; } service.orphan(); // result now owns service. 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.isNull()) { cacheDescriptorList.adoptInsteadAndCheckErrorCode(new UVector(uprv_deleteUObject, nullptr, 5, status), status); if (U_FAILURE(status)) { return nullptr; } } LocalPointer idToCache(new UnicodeString(currentDescriptor), status); if (U_FAILURE(status)) { return nullptr; } if (idToCache->isBogus()) { status = U_MEMORY_ALLOCATION_ERROR; return nullptr; } cacheDescriptorList->adoptElement(idToCache.orphan(), status); if (U_FAILURE(status)) { return nullptr; } } while (key.fallback()); outerEnd: if (result != nullptr) { if (putInCache && cacheResult) { serviceCache->put(result->actualDescriptor, result, status); if (U_FAILURE(status)) { return nullptr; } if (cacheDescriptorList.isValid()) { for (int32_t i = cacheDescriptorList->size(); --i >= 0;) { UnicodeString* desc = (UnicodeString*)cacheDescriptorList->elementAt(i); serviceCache->put(*desc, result, status); if (U_FAILURE(status)) { return nullptr; } result->ref(); cacheDescriptorList->removeElementAt(i); } } } if (actualReturn != nullptr) { // strip null prefix if (result->actualDescriptor.indexOf((char16_t)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 nullptr; } } 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 nullptr; } UVector& ICUService::getVisibleIDs(UVector& result, UErrorCode& status) const { return getVisibleIDs(result, nullptr, status); } UVector& ICUService::getVisibleIDs(UVector& result, const UnicodeString* matchID, UErrorCode& status) const { result.removeAllElements(); if (U_FAILURE(status)) { return result; } UObjectDeleter *savedDeleter = result.setDeleter(uprv_deleteUObject); { Mutex mutex(&lock); const Hashtable* map = getVisibleIDMap(status); if (map != nullptr) { ICUServiceKey* fallbackKey = createKey(matchID, status); for (int32_t pos = UHASH_FIRST; U_SUCCESS(status); ) { const UHashElement* e = map->nextElement(pos); if (e == nullptr) { break; } const UnicodeString* id = (const UnicodeString*)e->key.pointer; if (fallbackKey != nullptr) { if (!fallbackKey->isFallbackOf(*id)) { continue; } } LocalPointer idClone(id->clone(), status); result.adoptElement(idClone.orphan(), status); } delete fallbackKey; } } if (U_FAILURE(status)) { result.removeAllElements(); } result.setDeleter(savedDeleter); return result; } const Hashtable* ICUService::getVisibleIDMap(UErrorCode& status) const { if (U_FAILURE(status)) return nullptr; // must only be called when lock is already held ICUService* ncthis = (ICUService*)this; // cast away semantic const if (idCache == nullptr) { ncthis->idCache = new Hashtable(status); if (idCache == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; } else if (factories != nullptr) { 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 = nullptr; } } } 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 { { UErrorCode status = U_ZERO_ERROR; Mutex mutex(&lock); const Hashtable* map = getVisibleIDMap(status); if (map != nullptr) { ICUServiceFactory* f = (ICUServiceFactory*)map->get(id); if (f != nullptr) { f->getDisplayName(id, locale, result); return result; } // fallback status = U_ZERO_ERROR; ICUServiceKey* fallbackKey = createKey(&id, status); while (fallbackKey != nullptr && fallbackKey->fallback()) { UnicodeString us; fallbackKey->currentID(us); f = (ICUServiceFactory*)map->get(us); if (f != nullptr) { 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(), nullptr, status); } UVector& ICUService::getDisplayNames(UVector& result, const Locale& locale, UErrorCode& status) const { return getDisplayNames(result, locale, nullptr, status); } UVector& ICUService::getDisplayNames(UVector& result, const Locale& locale, const UnicodeString* matchID, UErrorCode& status) const { result.removeAllElements(); result.setDeleter(userv_deleteStringPair); if (U_SUCCESS(status)) { ICUService* ncthis = (ICUService*)this; // cast away semantic const Mutex mutex(&lock); if (dnCache != nullptr && dnCache->locale != locale) { delete dnCache; ncthis->dnCache = nullptr; } if (dnCache == nullptr) { const Hashtable* m = getVisibleIDMap(status); if (U_FAILURE(status)) { return result; } ncthis->dnCache = new DNCache(locale); if (dnCache == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return result; } int32_t pos = UHASH_FIRST; const UHashElement* entry = nullptr; while ((entry = m->nextElement(pos)) != nullptr) { 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 = nullptr; return result; } } } ICUServiceKey* matchKey = createKey(matchID, status); /* To ensure that all elements in the hashtable are iterated, set pos to -1. * nextElement(pos) will skip the position at pos and begin the iteration * at the next position, which in this case will be 0. */ int32_t pos = UHASH_FIRST; const UHashElement *entry = nullptr; while ((entry = dnCache->cache.nextElement(pos)) != nullptr) { const UnicodeString* id = (const UnicodeString*)entry->value.pointer; if (matchKey != nullptr && !matchKey->isFallbackOf(*id)) { continue; } const UnicodeString* dn = (const UnicodeString*)entry->key.pointer; StringPair* sp = StringPair::create(*id, *dn, status); result.adoptElement(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 != nullptr) { UnicodeString canonicalID; key->canonicalID(canonicalID); delete key; ICUServiceFactory* f = createSimpleFactory(objToAdopt, canonicalID, visible, status); if (f != nullptr) { return registerFactory(f, status); } } delete objToAdopt; return nullptr; } ICUServiceFactory* ICUService::createSimpleFactory(UObject* objToAdopt, const UnicodeString& id, UBool visible, UErrorCode& status) { if (U_SUCCESS(status)) { if ((objToAdopt != nullptr) && (!id.isBogus())) { return new SimpleFactory(objToAdopt, id, visible); } status = U_ILLEGAL_ARGUMENT_ERROR; } return nullptr; } URegistryKey ICUService::registerFactory(ICUServiceFactory* factoryToAdopt, UErrorCode& status) { LocalPointerlpFactoryToAdopt(factoryToAdopt); if (U_FAILURE(status) || factoryToAdopt == nullptr) { return nullptr; } { Mutex mutex(&lock); if (factories == nullptr) { LocalPointer lpFactories(new UVector(uprv_deleteUObject, nullptr, status), status); if (U_FAILURE(status)) { return nullptr; } factories = lpFactories.orphan(); } factories->insertElementAt(lpFactoryToAdopt.orphan(), 0, status); if (U_SUCCESS(status)) { clearCaches(); } } // Close of mutex lock block. if (U_SUCCESS(status)) { notifyChanged(); return (URegistryKey)factoryToAdopt; } else { return nullptr; } } UBool ICUService::unregister(URegistryKey rkey, UErrorCode& status) { ICUServiceFactory *factory = (ICUServiceFactory*)rkey; UBool result = false; if (factory != nullptr && factories != nullptr) { 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 != nullptr) { factories->removeAllElements(); } } UBool ICUService::isDefault() const { return countFactories() == 0; } ICUServiceKey* ICUService::createKey(const UnicodeString* id, UErrorCode& status) const { return (U_FAILURE(status) || id == nullptr) ? nullptr : new ICUServiceKey(*id); } void ICUService::clearCaches() { // callers synchronize before use ++timestamp; delete dnCache; dnCache = nullptr; delete idCache; idCache = nullptr; delete serviceCache; serviceCache = nullptr; } void ICUService::clearServiceCache() { // callers synchronize before use delete serviceCache; serviceCache = nullptr; } UBool ICUService::acceptsListener(const EventListener& l) const { return dynamic_cast(&l) != nullptr; } void ICUService::notifyListener(EventListener& l) const { (static_cast(l)).serviceChanged(*this); } UnicodeString& ICUService::getName(UnicodeString& result) const { return result.append(name); } int32_t ICUService::countFactories() const { return factories == nullptr ? 0 : factories->size(); } int32_t ICUService::getTimestamp() const { return timestamp; } U_NAMESPACE_END /* UCONFIG_NO_SERVICE */ #endif