diff --git a/icu4c/source/common/common.dsp b/icu4c/source/common/common.dsp index 3451fbd2ab6..f8157019a66 100644 --- a/icu4c/source/common/common.dsp +++ b/icu4c/source/common/common.dsp @@ -203,6 +203,22 @@ SOURCE=.\filestrm.c # End Source File # Begin Source File +SOURCE=.\iculserv.cpp +# End Source File +# Begin Source File + +SOURCE=.\icunotif.cpp +# End Source File +# Begin Source File + +SOURCE=.\icurwlck.cpp +# End Source File +# Begin Source File + +SOURCE=.\icuserv.cpp +# End Source File +# Begin Source File + SOURCE=.\locid.cpp # End Source File # Begin Source File @@ -845,6 +861,26 @@ SOURCE=.\hash.h # End Source File # Begin Source File +SOURCE=.\iculdata.h +# End Source File +# Begin Source File + +SOURCE=.\iculserv.h +# End Source File +# Begin Source File + +SOURCE=.\icunotif.h +# End Source File +# Begin Source File + +SOURCE=.\icurwlck.h +# End Source File +# Begin Source File + +SOURCE=.\icuserv.h +# End Source File +# Begin Source File + SOURCE=.\unicode\locid.h !IF "$(CFG)" == "common - Win32 Release" diff --git a/icu4c/source/common/iculserv.cpp b/icu4c/source/common/iculserv.cpp new file mode 100644 index 00000000000..58a977cfdb0 --- /dev/null +++ b/icu4c/source/common/iculserv.cpp @@ -0,0 +1,748 @@ +/** + ******************************************************************************* + * Copyright (C) 2001-2002, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + * + ******************************************************************************* + */ +#include "unicode/utypes.h" + +#if !UCONFIG_NO_SERVICE + +#include "unicode/resbund.h" +#include "cmemory.h" +#include "iculserv.h" + +U_NAMESPACE_BEGIN + +/* + ****************************************************************** + */ + +UnicodeString& +LocaleUtility::canonicalLocaleString(const UnicodeString* id, UnicodeString& result) +{ + if (id == NULL) { + result.setToBogus(); + } else { + result = *id; + int32_t i = 0; + int32_t n = result.indexOf(UNDERSCORE_CHAR); + if (n < 0) { + n = result.length(); + } + for (; i < n; ++i) { + UChar c = result.charAt(i); + if (c >= 0x0041 && c <= 0x005a) { + c += 0x20; + result.setCharAt(i, c); + } + } + for (n = result.length(); i < n; ++i) { + UChar c = result.charAt(i); + if (c >= 0x0061 && c <= 0x007a) { + c -= 0x20; + result.setCharAt(i, c); + } + } + } + return result; +} + +Locale& +LocaleUtility::initLocaleFromName(const UnicodeString& id, Locale& result) +{ + if (id.isBogus()) { + result.setToBogus(); + } else { + const int32_t BUFLEN = 128; // larger than ever needed + char buffer[BUFLEN]; + int len = id.extract(0, BUFLEN, buffer); + if (len >= BUFLEN) { + result.setToBogus(); + } else { + buffer[len] = '\0'; + result = Locale::createFromName(buffer); + } + } + return result; +} + +UnicodeString& +LocaleUtility::initNameFromLocale(const Locale& locale, UnicodeString& result) +{ + if (locale.isBogus()) { + result.setToBogus(); + } else { + result.append(locale.getName()); + } + return result; +} + +const Hashtable* +LocaleUtility::getAvailableLocaleNames(const UnicodeString& bundleID) +{ + // have to ignore bundleID for the moment, since we don't have easy C++ api. + // assume it's the default bundle + + if (cache == NULL) { + Hashtable* result = new Hashtable(); + if (result) { + UErrorCode status = U_ZERO_ERROR; + int32_t count = uloc_countAvailable(); + for (int32_t i = 0; i < count; ++i) { + UnicodeString temp(uloc_getAvailable(i)); + result->put(temp, (void*)result, status); + if (U_FAILURE(status)) { + delete result; + return NULL; + } + } + { + Mutex mutex(&lock); + if (cache == NULL) { + cache = result; + return cache; + } + } + delete result; + } + } + return cache; +} + +UBool +LocaleUtility::isFallbackOf(const UnicodeString& root, const UnicodeString& child) +{ + return child.indexOf(root) == 0 && + (child.length() == root.length() || + child.charAt(root.length()) == UNDERSCORE_CHAR); +} + +Hashtable * LocaleUtility::cache = NULL; + +const UChar LocaleUtility::UNDERSCORE_CHAR = 0x005f; + +UMTX LocaleUtility::lock = 0; + +/* + ****************************************************************** + */ + +const UChar LocaleKey::UNDERSCORE_CHAR = 0x005f; + +const int32_t LocaleKey::KIND_ANY = -1; + +LocaleKey* +LocaleKey::createWithCanonicalFallback(const UnicodeString* primaryID, + const UnicodeString* canonicalFallbackID) +{ + return LocaleKey::createWithCanonicalFallback(primaryID, canonicalFallbackID, KIND_ANY); +} + +LocaleKey* +LocaleKey::createWithCanonicalFallback(const UnicodeString* primaryID, + const UnicodeString* canonicalFallbackID, + int32_t kind) +{ + if (primaryID == NULL) { + return NULL; + } + UnicodeString canonicalPrimaryID; + LocaleUtility::canonicalLocaleString(primaryID, canonicalPrimaryID); + return new LocaleKey(*primaryID, canonicalPrimaryID, canonicalFallbackID, kind); +} + +LocaleKey::LocaleKey(const UnicodeString& primaryID, + const UnicodeString& canonicalPrimaryID, + const UnicodeString* canonicalFallbackID, + int32_t kind) + : Key(primaryID) + , _kind(kind) + , _primaryID(canonicalPrimaryID) + , _fallbackID() + , _currentID() +{ + _fallbackID.setToBogus(); + if (_primaryID.length() != 0) { + if (canonicalFallbackID != NULL && _primaryID != *canonicalFallbackID) { + _fallbackID = *canonicalFallbackID; + } + } + + _currentID = _primaryID; +} + +UnicodeString& +LocaleKey::prefix(UnicodeString& result) const { + if (_kind != KIND_ANY) { + DigitList list; + list.set(_kind); + UnicodeString temp(list.fDigits, list.fCount); + result.append(temp); + } + return result; +} + +int32_t +LocaleKey::kind() const { + return _kind; +} + +UnicodeString& +LocaleKey::canonicalID(UnicodeString& result) const { + return result.append(_primaryID); +} + +UnicodeString& +LocaleKey::currentID(UnicodeString& result) const { + if (!_currentID.isBogus()) { + result.append(_currentID); + } + return result; +} + +UnicodeString& +LocaleKey::currentDescriptor(UnicodeString& result) const { + if (!_currentID.isBogus()) { + prefix(result).append(PREFIX_DELIMITER).append(_currentID); + } else { + result.setToBogus(); + } + return result; +} + +Locale& +LocaleKey::canonicalLocale(Locale& result) const { + return LocaleUtility::initLocaleFromName(_primaryID, result); +} + +Locale& +LocaleKey::currentLocale(Locale& result) const { + return LocaleUtility::initLocaleFromName(_currentID, result); +} + +UBool +LocaleKey::fallback() { + if (!_currentID.isBogus()) { + int x = _currentID.lastIndexOf(UNDERSCORE_CHAR); + if (x != -1) { + _currentID.remove(x); // truncate current or fallback, whichever we're pointing to + return TRUE; + } + + if (!_fallbackID.isBogus()) { + _currentID = _fallbackID; + _fallbackID.setToBogus(); + return TRUE; + } + + if (_currentID.length() > 0) { + _currentID.remove(0); // completely truncate + return TRUE; + } + + _currentID.setToBogus(); + } + + return FALSE; +} + +UBool +LocaleKey::isFallbackOf(const UnicodeString& id) const { + UnicodeString temp(id); + parseSuffix(temp); + return temp.indexOf(_primaryID) == 0 && + (temp.length() == _primaryID.length() || + temp.charAt(_primaryID.length()) == UNDERSCORE_CHAR); +} + +UnicodeString& +LocaleKey::debug(UnicodeString& result) const +{ + Key::debug(result); + result.append(" kind: "); + result.append(_kind); + result.append(" primaryID: "); + result.append(_primaryID); + result.append(" fallbackID: "); + result.append(_fallbackID); + result.append(" currentID: "); + result.append(_currentID); + return result; +} + +UnicodeString& +LocaleKey::debugClass(UnicodeString& result) const +{ + return result.append("LocaleKey "); +} + +const char LocaleKey::fgClassID = 0; + +/* + ****************************************************************** + */ + +LocaleKeyFactory::LocaleKeyFactory(int32_t coverage) + : _name() + , _coverage(coverage) +{ +} + +LocaleKeyFactory::LocaleKeyFactory(int32_t coverage, const UnicodeString& name) + : _name(name) + , _coverage(coverage) +{ +} + +LocaleKeyFactory::~LocaleKeyFactory() { +} + +UObject* +LocaleKeyFactory::create(const Key& key, const ICUService* service, UErrorCode& status) const { + if (handlesKey(key, status)) { + const LocaleKey& lkey = (const LocaleKey&)key; + int32_t kind = lkey.kind(); + Locale loc; + lkey.canonicalLocale(loc); + + return handleCreate(loc, kind, service, status); + } + return NULL; +} + +UBool +LocaleKeyFactory::handlesKey(const Key& key, UErrorCode& status) const { + const Hashtable* supported = getSupportedIDs(status); + if (supported) { + UnicodeString id; + key.currentID(id); + return supported->get(id) != NULL; + } + return FALSE; +} + +void +LocaleKeyFactory::updateVisibleIDs(Hashtable& result, UErrorCode& status) const { + const Hashtable* supported = getSupportedIDs(status); + if (supported) { + UBool visible = (_coverage & 0x1) == 0; + + const UHashElement* elem = NULL; + int32_t pos = 0; + while (elem = supported->nextElement(pos)) { + const UnicodeString& id = *((const UnicodeString*)elem->key.pointer); + if (!visible) { + result.remove(id); + } else { + result.put(id, (void*)this, status); // this is dummy non-void marker used for set semantics + if (U_FAILURE(status)) { + break; + } + } + } + } +} + +UnicodeString& +LocaleKeyFactory::getDisplayName(const UnicodeString& id, const Locale& locale, UnicodeString& result) const { + Locale loc; + LocaleUtility::initLocaleFromName(id, loc); + return loc.getDisplayName(locale, result); +} + +UObject* +LocaleKeyFactory::handleCreate(const Locale& loc, int32_t kind, const ICUService* service, UErrorCode& status) const { + return NULL; +} + +const Hashtable* +LocaleKeyFactory::getSupportedIDs(UErrorCode& status) const { + return NULL; +} + +UnicodeString& +LocaleKeyFactory::debug(UnicodeString& result) const +{ + debugClass(result); + result.append(", name: "); + result.append(_name); + result.append(", coverage: "); + result.append(_coverage); + return result; +} + +UnicodeString& +LocaleKeyFactory::debugClass(UnicodeString& result) const +{ + return result.append("LocaleKeyFactory"); +} + +const char LocaleKeyFactory::fgClassID = 0; + +/* + ****************************************************************** + */ + +SimpleLocaleKeyFactory::SimpleLocaleKeyFactory(UObject* objToAdopt, + const Locale& locale, + int32_t kind, + int32_t coverage) + : LocaleKeyFactory(coverage) + , _obj(objToAdopt) + , _id(locale.getName()) + , _kind(kind) +{ +} + +SimpleLocaleKeyFactory::SimpleLocaleKeyFactory(UObject* objToAdopt, + const Locale& locale, + int32_t kind, + int32_t coverage, + const UnicodeString& name) + : LocaleKeyFactory(coverage, name) + , _obj(objToAdopt) + , _id(locale.getName()) + , _kind(kind) +{ +} + +UObject* +SimpleLocaleKeyFactory::create(const Key& key, const ICUService* service, UErrorCode& status) const +{ + if (U_SUCCESS(status)) { + const LocaleKey& lkey = (const LocaleKey&)key; + if (_kind == LocaleKey::KIND_ANY || _kind == lkey.kind()) { + UnicodeString keyID; + lkey.currentID(keyID); + if (_id == keyID) { + return service->cloneInstance(_obj); + } + } + } + return NULL; +} + +void +SimpleLocaleKeyFactory::updateVisibleIDs(Hashtable& result, UErrorCode& status) const +{ + if (U_SUCCESS(status)) { + if (_coverage & 0x1) { + result.remove(_id); + } else { + result.put(_id, (void*)this, status); + } + } +} + +UnicodeString& +SimpleLocaleKeyFactory::debug(UnicodeString& result) const +{ + LocaleKeyFactory::debug(result); + result.append(", id: "); + result.append(_id); + result.append(", kind: "); + result.append(_kind); + return result; +} + +UnicodeString& +SimpleLocaleKeyFactory::debugClass(UnicodeString& result) const +{ + return result.append("SimpleLocaleKeyFactory"); +} + +const char SimpleLocaleKeyFactory::fgClassID = 0; + +/* + ****************************************************************** + */ + +ICUResourceBundleFactory::ICUResourceBundleFactory() + : LocaleKeyFactory(VISIBLE) + , _bundleName() +{ +} + +ICUResourceBundleFactory::ICUResourceBundleFactory(const UnicodeString& bundleName) + : LocaleKeyFactory(VISIBLE) + , _bundleName(bundleName) +{ +} + +const Hashtable* +ICUResourceBundleFactory::getSupportedIDs(UErrorCode& status) const +{ + if (U_SUCCESS(status)) { + return LocaleUtility::getAvailableLocaleNames(_bundleName); + } + return NULL; +} + +UObject* +ICUResourceBundleFactory::handleCreate(const Locale& loc, int32_t kind, const ICUService* service, UErrorCode& status) const +{ + if (U_SUCCESS(status)) { + return new ResourceBundle(_bundleName, loc, status); + } + return NULL; +} + +UnicodeString& +ICUResourceBundleFactory::debug(UnicodeString& result) const +{ + LocaleKeyFactory::debug(result); + result.append(", bundle: "); + return result.append(_bundleName); +} + +UnicodeString& +ICUResourceBundleFactory::debugClass(UnicodeString& result) const +{ + return result.append("ICUResourceBundleFactory"); +} + +const char ICUResourceBundleFactory::fgClassID = '\0'; + +/* + ****************************************************************** + */ + +ICULocaleService::ICULocaleService() + : fallbackLocale(Locale::getDefault()) +{ +} + +ICULocaleService::ICULocaleService(const UnicodeString& name) + : ICUService(name) + , fallbackLocale(Locale::getDefault()) +{ +} + +ICULocaleService::~ICULocaleService() +{ +} + +UObject* +ICULocaleService::get(const Locale& locale, UErrorCode& status) const +{ + return get(locale, LocaleKey::KIND_ANY, NULL, status); +} + +UObject* +ICULocaleService::get(const Locale& locale, int32_t kind, UErrorCode& status) const +{ + return get(locale, kind, NULL, status); +} + +UObject* +ICULocaleService::get(const Locale& locale, Locale* actualReturn, UErrorCode& status) const +{ + return get(locale, LocaleKey::KIND_ANY, actualReturn, status); +} + +UObject* +ICULocaleService::get(const Locale& locale, int32_t kind, Locale* actualReturn, UErrorCode& status) const +{ + UObject* result = NULL; + + UnicodeString locName(locale.getName()); + if (locName.isBogus()) { + status = U_MEMORY_ALLOCATION_ERROR; + } else { + Key* key = createKey(&locName, kind); + if (key) { + if (actualReturn == NULL) { + result = getKey(*key, status); + } else { + UnicodeString temp; + result = getKey(*key, &temp, status); + + if (result != NULL) { + key->parseSuffix(temp); + LocaleUtility::initLocaleFromName(temp, *actualReturn); + } + } + delete key; + } + } + return result; +} + +const Factory* +ICULocaleService::registerObject(UObject* objToAdopt, const Locale& locale) +{ + return registerObject(objToAdopt, locale, LocaleKey::KIND_ANY, LocaleKeyFactory::VISIBLE); +} + +const Factory* +ICULocaleService::registerObject(UObject* objToAdopt, const Locale& locale, int32_t kind) +{ + return registerObject(objToAdopt, locale, kind, LocaleKeyFactory::VISIBLE); +} + +const Factory* +ICULocaleService::registerObject(UObject* objToAdopt, const Locale& locale, int32_t kind, int32_t coverage) +{ + Factory * factory = new SimpleLocaleKeyFactory(objToAdopt, locale, kind, coverage); + if (factory != NULL) { + return registerFactory(factory); + } + delete objToAdopt; + return NULL; +} + +class ServiceEnumeration : public StringEnumeration { +private: + const ICULocaleService* _service; + int32_t _timestamp; + UVector _ids; + int32_t _pos; + void* _bufp; + int32_t _buflen; + +private: + ServiceEnumeration(const ICULocaleService* service, UErrorCode status) + : _service(service) + , _timestamp(service->getTimestamp()) + , _ids(uhash_deleteUnicodeString, NULL, status) + , _pos(0) + , _bufp(NULL) + , _buflen(0) + { + _service->getVisibleIDs(_ids, status); + } + +public: + static ServiceEnumeration* create(const ICULocaleService* service) { + UErrorCode status = U_ZERO_ERROR; + ServiceEnumeration* result = new ServiceEnumeration(service, status); + if (U_SUCCESS(status)) { + return result; + } + delete result; + return NULL; + } + + virtual ~ServiceEnumeration() { + uprv_free(_bufp); + } + + virtual int32_t count(UErrorCode& status) const { + return upToDate(status) ? _ids.size() : 0; + } + + const char* next(UErrorCode& status) { + const UnicodeString* us = snext(status); + if (us) { + int newlen; + for (newlen = us->extract((char*)_bufp, _buflen / sizeof(char), NULL, status); + status == U_STRING_NOT_TERMINATED_WARNING || status == U_BUFFER_OVERFLOW_ERROR; + resizeBuffer((newlen + 1) * sizeof(char))) + { + status = U_ZERO_ERROR; + } + + if (U_SUCCESS(status)) { + ((char*)_bufp)[newlen] = 0; + return (const char*)_bufp; + } + } + return NULL; + } + + const UChar* unext(UErrorCode& status) { + const UnicodeString* us = snext(status); + if (us) { + int newlen; + for (newlen = us->extract((UChar*)_bufp, _buflen / sizeof(UChar), status); + status == U_STRING_NOT_TERMINATED_WARNING || status == U_BUFFER_OVERFLOW_ERROR; + resizeBuffer((newlen + 1) * sizeof(UChar))) + { + status = U_ZERO_ERROR; + } + + if (U_SUCCESS(status)) { + ((UChar*)_bufp)[newlen] = 0; + return (const UChar*)_bufp; + } + } + return NULL; + } + + const UnicodeString* snext(UErrorCode& status) { + if (upToDate(status) && (_pos < _ids.size())) { + return (const UnicodeString*)_ids[_pos++]; + } + return NULL; + } + + void resizeBuffer(int32_t newlen) { + if (_bufp) { + _bufp = uprv_realloc(_bufp, newlen); + } else { + _bufp = uprv_malloc(newlen); + } + } + + UBool upToDate(UErrorCode& status) const { + if (U_SUCCESS(status)) { + if (_timestamp == _service->getTimestamp()) { + return TRUE; + } + status = U_ENUM_OUT_OF_SYNC_ERROR; + } + return FALSE; + } + + void reset(UErrorCode& status) { + if (U_SUCCESS(status)) { + _timestamp = _service->getTimestamp(); + _pos = 0; + _service->getVisibleIDs(_ids, status); + } + } +}; + +StringEnumeration* +ICULocaleService::getAvailableLocales(void) const +{ + return ServiceEnumeration::create(this); +} + +const UnicodeString& +ICULocaleService::validateFallbackLocale() const +{ + const Locale& loc = Locale::getDefault(); + if (loc != fallbackLocale) { + ICULocaleService* ncThis = (ICULocaleService*)this; + Mutex mutex(&ncThis->lock); + if (loc != fallbackLocale) { + ncThis->fallbackLocale = loc; + LocaleUtility::initNameFromLocale(loc, ncThis->fallbackLocaleName); + ncThis->clearServiceCache(); + } + } + return fallbackLocaleName; +} + +Key* +ICULocaleService::createKey(const UnicodeString* id) const +{ + return LocaleKey::createWithCanonicalFallback(id, &validateFallbackLocale()); +} + +Key* +ICULocaleService::createKey(const UnicodeString* id, int32_t kind) const +{ + return LocaleKey::createWithCanonicalFallback(id, &validateFallbackLocale(), kind); +} + +U_NAMESPACE_END + +/* !UCONFIG_NO_SERVICE */ +#endif + + diff --git a/icu4c/source/common/iculserv.h b/icu4c/source/common/iculserv.h new file mode 100644 index 00000000000..fd798d67d59 --- /dev/null +++ b/icu4c/source/common/iculserv.h @@ -0,0 +1,545 @@ +/** + ******************************************************************************* + * Copyright (C) 2001-2002, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + * + ******************************************************************************* + */ +#ifndef ICULSERV_H +#define ICULSERV_H + +#include "unicode/utypes.h" + +#if UCONFIG_NO_SERVICE + +U_NAMESPACE_BEGIN + +/* + * Allow the declaration of APIs with pointers to ICUService + * even when service is removed from the build. + */ +class ICULocaleService; + +U_NAMESPACE_END + +#else + +#include "unicode/uobject.h" +#include "unicode/unistr.h" +#include "unicode/chariter.h" +#include "unicode/locid.h" +#include "unicode/ubrk.h" +#include "unicode/strenum.h" + +#include "hash.h" +#include "uvector.h" +#include "digitlst.h" + +#include "icuserv.h" + +U_NAMESPACE_BEGIN + +class ICULocaleService; + +class LocaleKey; +class LocaleKeyFactory; +class SimpleLocaleKeyFactory; +class ServiceListener; + +/* + ****************************************************************** + */ + +/** + * A subclass of Key that implements a locale fallback mechanism. + * The first locale to search for is the locale provided by the + * client, and the fallback locale to search for is the current + * default locale. If a prefix is present, the currentDescriptor + * includes it before the locale proper, separated by "/". This + * is the default key instantiated by ICULocaleService.

+ * + *

Canonicalization adjusts the locale string so that the + * section before the first understore is in lower case, and the rest + * is in upper case, with no trailing underscores.

+ */ + +class U_COMMON_API LocaleKey : public Key { + private: + int32_t _kind; + UnicodeString _primaryID; + UnicodeString _fallbackID; + UnicodeString _currentID; + + static const UChar UNDERSCORE_CHAR; // '_' + + public: + static const int32_t KIND_ANY; // = -1; + + /** + * Create a LocaleKey with canonical primary and fallback IDs. + */ + static LocaleKey* createWithCanonicalFallback(const UnicodeString* primaryID, + const UnicodeString* canonicalFallbackID); + + /** + * Create a LocaleKey with canonical primary and fallback IDs. + */ + static LocaleKey* createWithCanonicalFallback(const UnicodeString* primaryID, + const UnicodeString* canonicalFallbackID, + int32_t kind); + + protected: + /** + * PrimaryID is the user's requested locale string, + * canonicalPrimaryID is this string in canonical form, + * fallbackID is the current default locale's string in + * canonical form. + */ + LocaleKey(const UnicodeString& primaryID, + const UnicodeString& canonicalPrimaryID, + const UnicodeString* canonicalFallbackID, + int32_t kind); + + public: + /** + * Append the prefix associated with the kind, or nothing if the kind is KIND_ANY. + */ + virtual UnicodeString& prefix(UnicodeString& result) const; + + /** + * Return the kind code associated with this key. + */ + virtual int32_t kind() const; + + /** + * Return the canonicalID. + */ + virtual UnicodeString& canonicalID(UnicodeString& result) const; + + /** + * Return the currentID. + */ + virtual UnicodeString& currentID(UnicodeString& result) const; + + /** + * Return the (canonical) current descriptor, or null if no current id. + */ + virtual UnicodeString& currentDescriptor(UnicodeString& result) const; + + /** + * Convenience method to return the locale corresponding to the (canonical) original ID. + */ + virtual Locale& canonicalLocale(Locale& result) const; + + /** + * Convenience method to return the locale corresponding to the (canonical) current ID. + */ + virtual Locale& currentLocale(Locale& result) const; + + /** + * If the key has a fallback, modify the key and return true, + * otherwise return false.

+ * + *

First falls back through the primary ID, then through + * the fallbackID. The final fallback is the empty string, + * unless the primary id was the empty string, in which case + * there is no fallback. + */ + virtual UBool fallback(); + + /** + * Return true if a key created from id matches, or would eventually + * fallback to match, the canonical ID of this key. + */ + virtual UBool isFallbackOf(const UnicodeString& id) const; + + public: + /** + * UObject boilerplate. + */ + virtual UClassID getDynamicClassID() const { + return getStaticClassID(); + } + + static UClassID getStaticClassID() { + return (UClassID)&fgClassID; + } + + public: + virtual UnicodeString& debug(UnicodeString& result) const; + virtual UnicodeString& debugClass(UnicodeString& result) const; + + private: + static const char fgClassID; +}; + +/* + ****************************************************************** + */ + +/** + * A subclass of Factory that uses LocaleKeys, and is able to + * 'cover' more specific locales with more general locales that it + * supports. + * + *

Coverage may be either of the values VISIBLE or INVISIBLE. + * + *

'Visible' indicates that the specific locale(s) supported by + * the factory are registered in getSupportedIDs, 'Invisible' + * indicates that they are not. + * + *

Localization of visible ids is handled + * by the handling factory, regardless of kind. + */ +class U_COMMON_API LocaleKeyFactory : public Factory { +protected: + const UnicodeString _name; + const int32_t _coverage; + +public: + enum { + /** + * Coverage value indicating that the factory makes + * its locales visible, and does not cover more specific + * locales. + */ + VISIBLE = 0, + + /** + * Coverage value indicating that the factory does not make + * its locales visible, and does not cover more specific + * locales. + */ + INVISIBLE = 1 + }; + +protected: + /** + * Constructor used by subclasses. + */ + LocaleKeyFactory(int32_t coverage); + + /** + * Constructor used by subclasses. + */ + LocaleKeyFactory(int32_t coverage, const UnicodeString& name); + + /** + * Destructor. + */ + virtual ~LocaleKeyFactory(); + + /** + * Implement superclass abstract method. This checks the currentID of + * the key against the supported IDs, and passes the canonicalLocale and + * kind off to handleCreate (which subclasses must implement). + */ +public: + virtual UObject* create(const Key& key, const ICUService* service, UErrorCode& status) const; + +protected: + virtual UBool handlesKey(const Key& key, UErrorCode& status) const; + +public: + /** + * Override of superclass method. This adjusts the result based + * on the coverage rule for this factory. + */ + void updateVisibleIDs(Hashtable& result, UErrorCode& status) const; + + /** + * Return a localized name for the locale represented by id. + */ + UnicodeString& getDisplayName(const UnicodeString& id, const Locale& locale, UnicodeString& result) const; + +protected: + /** + * Utility method used by create(Key, ICUService). Subclasses can implement + * this instead of create. The default returns NULL. + */ + virtual UObject* handleCreate(const Locale& loc, int32_t kind, const ICUService* service, UErrorCode& status) const; + + /** + * Return the set of ids that this factory supports (visible or + * otherwise). This can be called often and might need to be + * cached if it is expensive to create. + */ + virtual const Hashtable* getSupportedIDs(UErrorCode& status) const; + + public: + /** + * UObject boilerplate. + */ + virtual UClassID getDynamicClassID() const { + return getStaticClassID(); + } + + static UClassID getStaticClassID() { + return (UClassID)&fgClassID; + } + + public: + virtual UnicodeString& debug(UnicodeString& result) const; + virtual UnicodeString& debugClass(UnicodeString& result) const; + + private: + static const char fgClassID; +}; + +/* + ****************************************************************** + */ + +/** + * A LocaleKeyFactory that just returns a single object for a kind/locale. + */ + +class U_COMMON_API SimpleLocaleKeyFactory : public LocaleKeyFactory { + private: + UObject* _obj; + UnicodeString _id; + const int32_t _kind; + + public: + SimpleLocaleKeyFactory(UObject* objToAdopt, + const Locale& locale, + int32_t kind, + int32_t coverage); + + SimpleLocaleKeyFactory(UObject* objToAdopt, + const Locale& locale, + int32_t kind, + int32_t coverage, + const UnicodeString& name); + + /** + * Override of superclass method. Returns the service object if kind/locale match. Service is not used. + */ + UObject* create(const Key& key, const ICUService* service, UErrorCode& status) const; + + /** + * Override of superclass method. This adjusts the result based + * on the coverage rule for this factory. + */ + void updateVisibleIDs(Hashtable& result, UErrorCode& status) const; + + public: + /** + * UObject boilerplate. + */ + virtual UClassID getDynamicClassID() const { + return getStaticClassID(); + } + + static UClassID getStaticClassID() { + return (UClassID)&fgClassID; + } + + public: + virtual UnicodeString& debug(UnicodeString& result) const; + virtual UnicodeString& debugClass(UnicodeString& result) const; + + private: + static const char fgClassID; +}; + +/* + ****************************************************************** + */ + +/** + * A LocaleKeyFactory that creates a service based on the ICU locale data. + * This is a base class for most ICU factories. Subclasses instantiate it + * with a constructor that takes a bundle name, which determines the supported + * IDs. Subclasses then override handleCreate to create the actual service + * object. The default implementation returns a resource bundle. + */ +class U_COMMON_API ICUResourceBundleFactory : public LocaleKeyFactory +{ + protected: + UnicodeString _bundleName; + + public: + /** + * Convenience constructor that uses the main ICU bundle name. + */ + ICUResourceBundleFactory(); + + /** + * A service factory based on ICU resource data in resources + * with the given name. + */ + ICUResourceBundleFactory(const UnicodeString& bundleName); + +protected: + /** + * Return the supported IDs. This is the set of all locale names in ICULocaleData. + */ + virtual const Hashtable* getSupportedIDs(UErrorCode& status) const; + + /** + * Create the service. The default implementation returns the resource bundle + * for the locale, ignoring kind, and service. + */ + virtual UObject* handleCreate(const Locale& loc, int32_t kind, const ICUService* service, UErrorCode& status) const; + + public: + /** + * UObject boilerplate. + */ + virtual UClassID getDynamicClassID() const { + return getStaticClassID(); + } + + static UClassID getStaticClassID() { + return (UClassID)&fgClassID; + } + + public: + virtual UnicodeString& debug(UnicodeString& result) const; + virtual UnicodeString& debugClass(UnicodeString& result) const; + + private: + static const char fgClassID; +}; + +/* + ****************************************************************** + */ + +class U_COMMON_API ICULocaleService : public ICUService +{ + private: + Locale fallbackLocale; + UnicodeString fallbackLocaleName; + + public: + /** + * Construct an ICULocaleService. + */ + ICULocaleService(); + + /** + * Construct an ICULocaleService with a name (useful for debugging). + */ + ICULocaleService(const UnicodeString& name); + + /** + * Destructor. + */ + ~ICULocaleService(); + +#if 0 + // redeclare because of overload resolution rules? + UObject* get(const UnicodeString& descriptor, UErrorCode& status) const { + return ICUService::get(descriptor, status); + } + + UObject* get(const UnicodeString& descriptor, UnicodeString* actualReturn, UErrorCode& status) const { + return ICUService::get(descriptor, actualReturn, status); + } +#endif + + /** + * Convenience override for callers using locales. This calls + * get(Locale, int, Locale[]) with KIND_ANY for kind and null for + * actualReturn. + */ + UObject* get(const Locale& locale, UErrorCode& status) const; + + /** + * Convenience override for callers using locales. This calls + * get(Locale, int, Locale[]) with a null actualReturn. + */ + UObject* get(const Locale& locale, int32_t kind, UErrorCode& status) const; + + /** + * Convenience override for callers using locales. This calls + * get(Locale, String, Locale[]) with a null kind. + */ + UObject* get(const Locale& locale, Locale* actualReturn, UErrorCode& status) const; + + /** + * Convenience override for callers using locales. This uses + * createKey(Locale.toString(), kind) to create a key, calls getKey, and then + * if actualReturn is not null, returns the actualResult from + * getKey (stripping any prefix) into a Locale. + */ + UObject* get(const Locale& locale, int32_t kind, Locale* actualReturn, UErrorCode& status) const; + + /** + * Convenience override for callers using locales. This calls + * registerObject(Object, Locale, int32_t kind, int coverage) + * passing KIND_ANY for the kind, and VISIBLE for the coverage. + */ + const Factory* registerObject(UObject* objToAdopt, const Locale& locale); + + /** + * Convenience function for callers using locales. This calls + * registerObject(Object, Locale, int kind, int coverage) + * passing VISIBLE for the coverage. + */ + const Factory* registerObject(UObject* objToAdopt, const Locale& locale, int32_t kind); + + /** + * Convenience function for callers using locales. This instantiates + * a SimpleLocaleKeyFactory, and registers the factory. + */ + virtual const Factory* registerObject(UObject* objToAdopt, const Locale& locale, int32_t kind, int32_t coverage); + + /** + * Convenience method for callers using locales. This returns the standard + * service ID enumeration. + */ + virtual StringEnumeration* getAvailableLocales(void) const; + + /** + * Return the name of the current fallback locale. If it has changed since this was + * last accessed, the service cache is cleared. + */ + const UnicodeString& validateFallbackLocale() const; + + /** + * Override superclass createKey method. + */ + virtual Key* createKey(const UnicodeString* id) const; + + /** + * Additional createKey that takes a kind. + */ + virtual Key* createKey(const UnicodeString* id, int32_t kind) const; + + /* + virtual int32_t getTimestamp() const { + return ICUService::getTimestamp(); + } + */ + + friend class ServiceEnumeration; +}; + +// temporary utility functions, till I know where to find them +// in header so tests can also access them + +class U_COMMON_API LocaleUtility { + static Hashtable* cache; + static UMTX lock; + static const UChar UNDERSCORE_CHAR; + +public: + static UnicodeString& canonicalLocaleString(const UnicodeString* id, UnicodeString& result); + static Locale& initLocaleFromName(const UnicodeString& id, Locale& result); + static UnicodeString& initNameFromLocale(const Locale& locale, UnicodeString& result); + static const Hashtable* getAvailableLocaleNames(const UnicodeString& bundleID); + static UBool isFallbackOf(const UnicodeString& root, const UnicodeString& child); +}; + +U_NAMESPACE_END + + /* UCONFIG_NO_SERVICE */ +#endif + + /* ICULSERV_H */ +#endif + diff --git a/icu4c/source/common/icunotif.cpp b/icu4c/source/common/icunotif.cpp new file mode 100644 index 00000000000..f3f6e80af61 --- /dev/null +++ b/icu4c/source/common/icunotif.cpp @@ -0,0 +1,97 @@ +/** + ******************************************************************************* + * Copyright (C) 2001-2002, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + */ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_SERVICE + +#include "icunotif.h" +#include + +U_NAMESPACE_BEGIN + +const char EventListener::fgClassID = '\0'; + +void +ICUNotifier::addListener(const EventListener* l, UErrorCode& status) +{ + if (U_SUCCESS(status)) { + if (l == NULL) { + status = U_ILLEGAL_ARGUMENT_ERROR; + } + + if (acceptsListener(*l)) { + Mutex lmx(¬ifyLock); + if (listeners == NULL) { + listeners = new UVector(5, status); + } else { + for (int i = 0, e = listeners->size(); i < e; ++i) { + const EventListener* el = (const EventListener*)(listeners->elementAt(i)); + if (l == el) { + return; + } + } + } + + listeners->addElement((void*)l, status); // cast away const + } else { +#if DEBUG + fprintf(stderr, "Listener invalid for this notifier."); + exit(1); +#endif + } + } +} + +void +ICUNotifier::removeListener(const EventListener *l, UErrorCode& status) +{ + if (U_SUCCESS(status)) { + if (l == NULL) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + + { + Mutex lmx(¬ifyLock); + if (listeners != NULL) { + // identity equality check + for (int i = 0, e = listeners->size(); i < e; ++i) { + const EventListener* el = (const EventListener*)listeners->elementAt(i); + if (l == el) { + listeners->removeElementAt(i); + if (listeners->size() == 0) { + delete listeners; + listeners = NULL; + } + return; + } + } + } + } + } +} + +void +ICUNotifier::notifyChanged(void) +{ + if (listeners != NULL) { + Mutex lmx(¬ifyLock); + if (listeners != NULL) { + for (int i = 0, e = listeners->size(); i < e; ++i) { + EventListener* el = (EventListener*)listeners->elementAt(i); + notifyListener(*el); + } + } + } +} + +U_NAMESPACE_END; + +/* UCONFIG_NO_SERVICE */ +#endif + diff --git a/icu4c/source/common/icunotif.h b/icu4c/source/common/icunotif.h new file mode 100644 index 00000000000..4486f6786dd --- /dev/null +++ b/icu4c/source/common/icunotif.h @@ -0,0 +1,136 @@ +/** + ******************************************************************************* + * Copyright (C) 2001-2002, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + */ +#ifndef ICUNOTIF_H +#define ICUNOTIF_H + +#include "unicode/utypes.h" + +#if UCONFIG_NO_SERVICE + +U_NAMESPACE_BEGIN + +/* + * Allow the declaration of APIs with pointers to BreakIterator + * even when break iteration is removed from the build. + */ +class ICUNotifier; + +U_NAMESPACE_END + +#else + +#include "unicode/uobject.h" +#include "unicode/unistr.h" + +#include "mutex.h" +#include "uvector.h" + +U_NAMESPACE_BEGIN + +class U_COMMON_API EventListener : public UObject { + public: + virtual ~EventListener() {} + + public: + virtual UClassID getDynamicClassID() const { + return getStaticClassID(); + } + + static UClassID getStaticClassID() { + return (UClassID)&fgClassID; + } + + public: + virtual UnicodeString& debug(UnicodeString& result) const { + return debugClass(result); + } + + virtual UnicodeString& debugClass(UnicodeString& result) const { + return result.append("Key"); + } + + private: + static const char fgClassID; +}; + +/** + *

Abstract implementation of a notification facility. Clients add + * EventListeners with addListener and remove them with removeListener. + * Notifiers call notifyChanged when they wish to notify listeners. + * This queues the listener list on the notification thread, which + * eventually dequeues the list and calls notifyListener on each + * listener in the list.

+ * + *

Subclasses override acceptsListener and notifyListener + * to add type-safe notification. AcceptsListener should return + * true if the listener is of the appropriate type; ICUNotifier + * itself will ensure the listener is non-null and that the + * identical listener is not already registered with the Notifier. + * NotifyListener should cast the listener to the appropriate + * type and call the appropriate method on the listener. + */ + +class U_COMMON_API ICUNotifier : public UMemory { + private: UMTX notifyLock; + private: UVector* listeners; + + public: + ICUNotifier(void) + : notifyLock(0), listeners(NULL) + { + } + + virtual ~ICUNotifier(void) { + Mutex lmx(¬ifyLock); + delete listeners; + listeners = NULL; + } + + /** + * Add a listener to be notified when notifyChanged is called. + * The listener must not be null. AcceptsListener must return + * true for the listener. Attempts to concurrently + * register the identical listener more than once will be + * silently ignored. + */ + virtual void addListener(const EventListener* l, UErrorCode& status); + + /** + * Stop notifying this listener. The listener must + * not be null. Attemps to remove a listener that is + * not registered will be silently ignored. + */ + virtual void removeListener(const EventListener* l, UErrorCode& status); + + /** + * ICU doesn't spawn its own threads. All listeners are notified in + * the thread of the caller. Misbehaved listeners can therefore + * indefinitely block the calling thread. Callers should beware of + * deadlock situations. + */ + virtual void notifyChanged(void); + + protected: + /** + * Subclasses implement this to return TRUE if the listener is + * of the appropriate type. + */ + virtual UBool acceptsListener(const EventListener& l) const = 0; + + /** + * Subclasses implement this to notify the listener. + */ + virtual void notifyListener(EventListener& l) const = 0; +}; + +U_NAMESPACE_END + +/* UCONFIG_NO_SERVICE */ +#endif + +/* ICUNOTIF_H */ +#endif diff --git a/icu4c/source/common/icuserv.cpp b/icu4c/source/common/icuserv.cpp new file mode 100644 index 00000000000..01f534357f1 --- /dev/null +++ b/icu4c/source/common/icuserv.cpp @@ -0,0 +1,1170 @@ + /** + ******************************************************************************* + * Copyright (C) 2001-2002, International Business Machines Corporation. * + * All Rights Reserved. * + ******************************************************************************* + */ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_SERVICE + +#include "icuserv.h" + +#undef SERVICE_REFCOUNT + +// in case we use the refcount stuff + +U_NAMESPACE_BEGIN + +// A reference counted wrapper for an object. Creation and access is +// through RefHandle. + +#ifdef SERVICE_REFCOUNT + +#include "unicode/strenum.h" + +/* + ****************************************************************** + */ + +class RefCounted { +private: + int32_t _count; + UObject* _obj; + + friend class RefHandle; + + RefCounted(UObject* objectToAdopt) : _count(1), _obj(objectToAdopt) {} + ~RefCounted() { delete _obj; } + void ref() { umtx_atomic_inc(&_count); } + void unref() { if (umtx_atomic_dec(&_count) == 0) { delete this; }} +}; + +/* + ****************************************************************** + */ + +// Reference counted handle for an object +class RefHandle { + RefCounted* _ref; + +public: + RefHandle() : _ref(NULL) {} + RefHandle(UObject* obj) : _ref(new RefCounted(obj)) {} + RefHandle(const RefHandle& rhs) : _ref(NULL) { operator=(rhs); } + ~RefHandle() { if (_ref) _ref->unref(); } + RefHandle& operator=(const RefHandle& rhs) { + if (rhs._ref) rhs._ref->ref(); + if (_ref) _ref->unref(); + _ref = rhs._ref; + } + const UObject* get() const { return _ref ? _ref->_obj : NULL; } +}; + +/* + ****************************************************************** + */ + +// Generic enumeration class with fail-fast behavior. + +class MapEnumeration : public UObject, public StringEnumeration +{ + private: + UChar* _buffer; + int _buflen; + + protected: + const ICUService* _service; + uint32_t _timestamp; + RefHandle _table; + Key* _filter; + int32_t _position; + int32_t _count; + + protected: + MapEnumeration(ICUService* service, int32_t timestamp, RefHandle& table, Key* filter = NULL) + : _buffer(NULL) + , _buflen(0) + , _service(service) + , _timestamp(timestamp) + , _table(table) + , _filter(filter) + , _position(0) + , _count(((const Hashtable*)table.get())->count()) + { + } + + virtual ~MapEnumeration() + { + delete _filter; + } + + int32_t count(UErrorCode& status) const + { + return U_SUCCESS(status) ? _count : 0; + } + + const char* next(UErrorCode& status) { + const UnicodeString* us = snext(status); + if (us) { + int newlen; + for (newlen = us->extract((char*)_buffer, _buflen / sizeof(char), NULL, status); + status == U_STRING_NOT_TERMINATED_WARNING || status == U_BUFFER_OVERFLOW_ERROR; + resizeBuffer((newlen + 1) * sizeof(char))); + + if (U_SUCCESS(status)) { + ((char*)_buffer)[newlen] = 0; + return (const char*)_buffer; + } + } + return NULL; + } + + const UChar* unext(UErrorCode& status) { + const UnicodeString* us = snext(status); + if (us) { + int newlen; + for (newlen = us->extract((UChar*)_buffer, _buflen / sizeof(UChar), NULL, status); + status == U_STRING_NOT_TERMINATED_WARNING || status == U_BUFFER_OVERFLOW_ERROR; + resizeBuffer((newlen + 1) * sizeof(UChar))); + + if (U_SUCCESS(status)) { + ((UChar*)_buffer)[newlen] = 0; + return (const UChar*)_buffer; + } + } + return NULL; + } + + const UnicodeString* snext(UErrorCode& status) + { + if (U_SUCCESS(status)) { + if (_timestamp != _service->_timestamp) { + status = U_ENUM_OUT_OF_SYNCH_ERROR; + } else { + return internalNext((Hashtable*)_table.get()); + } + } + return NULL; + } + + void reset(UErrorCode& status) + { + if (U_SUCCESS(status)) { + service->reset(this); + } + } + + protected: + virtual const UnicodeString* internalNext(Hashtable* table) = 0; + + private: + void reset(RefHandle& table, int32_t timestamp) + { + _table = table; + _timestamp = timestamp; + _position = 0; + _count = ((const Hashtable*)table.get())->count(); + } + + friend class ICUService; +}; + +/* + ****************************************************************** + */ + +// An enumeration over the visible ids in a service. The ids +// are in the hashtable, which is refcounted, so it will not +// disappear as long as the enumeration exists even if the +// service itself unrefs it. For "fail-fast" behavior the +// enumeration checks the timestamp of the service, but this +// is not a guarantee that the result the client receives will +// still be valid once the function returns. + +class IDEnumeration : public MapEnumeration { +public: + IDEnumeration(ICUService* service, int32_t timestamp, RefHandle& table, Key* filter = NULL) + : MapEnumeration(service, timestamp, table, filter) + { + } + +protected: + const UnicodeString* internalNext(Hashtable* table) { + while (TRUE) { + const UnicodeString* elem = (const UnicodeString*)(table->nextElement(_position).key.pointer); + if (elem == NULL || + _filter == NULL || + _filter->isFallbackOf(*elem)) { + return elem; + } + } + return NULL; + } +}; + +/* + ****************************************************************** + */ + +class DisplayEnumeration : public MapEnumeration { +private: + Locale _locale; + UnicodeString _cache; + +public: + DisplayEnumeration(ICUService* service, int32_t timestamp, RefHandle& table, Locale& locale, Key* filter = NULL) + : MapEnumeration(service, timestamp, table, filter), _locale(locale) + { + } + +protected: + const UnicodeString* internalNext(Hashtable* table) { + while (TRUE) { + UHashElement* elem = table->nextElement(_position); + if (elem == NULL) { + return NULL; + } + const UnicodeString* id = (const UnicodeString*)elem->key.pointer; + const Factory* factory = (const Factory*)elem->value.pointer; + if (_filter == NULL || _filter->isFallbackOf(*id)) { + factory->getDisplayName(*id, cache, locale); + return &cache; + } + } + return NULL; + } +}; + +/* SERVICE_REFCOUNT */ +#endif + +/* + ****************************************************************** + */ + +const UChar Key::PREFIX_DELIMITER = '/'; + +Key::Key(const UnicodeString& id) + : _id(id) { +} + +Key::~Key() +{ +} + +const UnicodeString& +Key::getID() const +{ + return _id; +} + +UnicodeString& +Key::canonicalID(UnicodeString& result) const +{ + return result.append(_id); +} + +UnicodeString& +Key::currentID(UnicodeString& result) const +{ + return canonicalID(result); +} + +UnicodeString& +Key::currentDescriptor(UnicodeString& result) const +{ + prefix(result); + result.append(PREFIX_DELIMITER); + return currentID(result); +} + +UBool +Key::fallback() +{ + return FALSE; +} + +UBool +Key::isFallbackOf(const UnicodeString& id) const +{ + return id == _id; +} + +UnicodeString& +Key::prefix(UnicodeString& result) const +{ + return result; +} + +UnicodeString& +Key::parsePrefix(UnicodeString& result) +{ + int32_t n = result.indexOf(PREFIX_DELIMITER); + if (n < 0) { + n = 0; + } + result.remove(n); + return result; +} + +UnicodeString& +Key::parseSuffix(UnicodeString& result) +{ + int32_t n = result.indexOf(PREFIX_DELIMITER); + if (n >= 0) { + result.remove(0, n+1); + } + return result; +} + +UnicodeString& +Key::debug(UnicodeString& result) const +{ + debugClass(result); + result.append(" id: "); + result.append(_id); + return result; +} + +UnicodeString& +Key::debugClass(UnicodeString& result) const +{ + return result.append("Key"); +} + +const char Key::fgClassID = '\0'; + +/* + ****************************************************************** + */ + +SimpleFactory::SimpleFactory(UObject* instanceToAdopt, const UnicodeString& id) + : _instance(instanceToAdopt), _id(id), _visible(TRUE) +{ +} + +SimpleFactory::SimpleFactory(UObject* instanceToAdopt, const UnicodeString& id, UBool visible) + : _instance(instanceToAdopt), _id(id), _visible(visible) +{ +} + +SimpleFactory::~SimpleFactory() +{ + delete _instance; +} + +UObject* +SimpleFactory::create(const Key& 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; +} + +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"); +} + +const char SimpleFactory::fgClassID = '\0'; + +/* + ****************************************************************** + */ + +const char ServiceListener::fgClassID = '\0'; + +/* + ****************************************************************** + */ + + // 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 { +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_CAPI void U_EXPORT2 +cacheDeleter(void* obj) { + U_NAMESPACE_USE + ((CacheEntry*)obj)->unref(); +} + +/** + * Deleter for UObjects + */ +U_CAPI void U_EXPORT2 +deleteUObject(void *obj) { + U_NAMESPACE_USE + delete (UObject*) obj; +} + +/* + ****************************************************************** + */ + +class DNCache { +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 +deleteStringPair(void *obj) { + U_NAMESPACE_USE + delete (StringPair*) obj; +} + +/* + ****************************************************************** + */ + +ICUService::ICUService() + : name() + , lock(0) + , timestamp(0) + , factories(NULL) + , serviceCache(NULL) + , idCache(NULL) + , dnCache(NULL) +{ +} + +ICUService::ICUService(const UnicodeString& name) + : name(name) + , lock(0) + , timestamp(0) + , factories(NULL) + , serviceCache(NULL) + , idCache(NULL) + , dnCache(NULL) { +} + +ICUService::~ICUService() + { + Mutex mutex(&lock); + clearCaches(); + delete factories; + factories = NULL; +} + +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; + if (U_SUCCESS(status)) { + Key* key = createKey(&descriptor); + if (key) { + result = getKey(*key, actualReturn, NULL, status); + delete key; + } + } + return result; +} + +UObject* +ICUService::getKey(Key& key, UErrorCode& status) const +{ + return getKey(key, NULL, NULL, status); +} + +UObject* +ICUService::getKey(Key& key, UnicodeString* actualReturn, UErrorCode& status) const +{ + return getKey(key, actualReturn, NULL, status); +} + +UObject* +ICUService::getKey(Key& key, UnicodeString* actualReturn, const Factory* factory, UErrorCode& status) const +{ + if (U_FAILURE(status)) { + return NULL; + } + + if (isDefault()) { + return handleDefault(key, actualReturn, status); + } + +#ifdef DEBUG_SERVICE + fprintf(stderr, "Service: " + name + " key: " + key); +#endif + + 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. + + Mutex mutex(&ncthis->lock); + + if (serviceCache == NULL) { + ncthis->serviceCache = new Hashtable(FALSE, status); + if (U_FAILURE(status)) { + delete serviceCache; + return NULL; + } + serviceCache->setValueDeleter(cacheDeleter); + } + + UnicodeString currentDescriptor; + UVector* cacheDescriptorList = NULL; + UBool putInCache = FALSE; + +#ifdef DEBUG_SERVICE + int32_t NDebug = 0; +#endif + + 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 Factory*)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); +#ifdef DEBUG_SERVICE + fprintf(stderr, name + "[" + NDebug++ + "] looking for: " + currentDescriptor); +#endif + result = (CacheEntry*)serviceCache->get(currentDescriptor); + if (result != NULL) { +#ifdef DEBUG_SERVICE + fprintf(stderr, name + " found with descriptor: " + currentDescriptor); +#endif + break; + } else { +#ifdef DEBUG_SERVICE + fprintf(stderr, "did not find: " + currentDescriptor + " in cache"); +#endif + } + + // 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 = cacheResult; + + int32_t n = 0; + int32_t index = startIndex; + while (index < limit) { + Factory* f = (Factory*)factories->elementAt(index++); +#ifdef DEBUG_SERVICE + fprintf("trying factory[" + (index-1) + "] " + f.toString()); +#endif + UObject* service = f->create(key, this, status); + if (U_FAILURE(status)) { + delete cacheDescriptorList; + return NULL; + } + if (service != NULL) { + result = new CacheEntry(currentDescriptor, service); + if (result == NULL) { + delete service; + delete cacheDescriptorList; + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + +#ifdef DEBUG_SERVICE + fprintf(stderr, name + " factory supported: " + currentDescriptor + ", caching"); +#endif + goto outerEnd; + } else { +#ifdef DEBUG_SERVICE + fprintf(stderr, "factory did not support: " + currentDescriptor); +#endif + } + } + + // 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 == NULL) { + cacheDescriptorList = new UVector(uhash_deleteUnicodeString, NULL, 5, status); + if (U_FAILURE(status)) { + return NULL; + } + } + UnicodeString* idToCache = new UnicodeString(currentDescriptor); + if (idToCache == NULL || idToCache->isBogus()) { + delete cacheDescriptorList; + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + + cacheDescriptorList->addElement(idToCache, status); + if (U_FAILURE(status)) { + delete cacheDescriptorList; + return NULL; + } + } while (key.fallback()); + outerEnd: + + if (result != NULL) { + if (putInCache) { + serviceCache->put(result->actualDescriptor, result, status); + if (U_FAILURE(status)) { + delete result; + delete cacheDescriptorList; + return NULL; + } + + if (cacheDescriptorList != NULL) { + for (int32_t i = cacheDescriptorList->size(); --i >= 0;) { + UnicodeString* desc = (UnicodeString*)cacheDescriptorList->elementAt(i); +#ifdef DEBUG_SERVICE + fprintf(stderr, name + " adding descriptor: '" + desc + "' for actual: '" + result.actualDescriptor + "'"); +#endif + serviceCache->put(*desc, result, status); + if (U_FAILURE(status)) { + delete result; + delete cacheDescriptorList; + return NULL; + } + + result->ref(); + cacheDescriptorList->removeElementAt(i); + } + + delete cacheDescriptorList; + cacheDescriptorList = NULL; + } + } + + if (actualReturn != NULL) { + // strip null prefix + if (result->actualDescriptor.indexOf("/") == 0) { + actualReturn->remove(); + actualReturn->append(result->actualDescriptor, + 1, + result->actualDescriptor.length() - 1); + } else { + *actualReturn = result->actualDescriptor; + } + + if (actualReturn->isBogus()) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + } + +#ifdef DEBUG_SERVICE + fprintf(stderr, "found in service: " + name); +#endif + return cloneInstance(result->service); + } + } + +#ifdef DEBUG_SERVICE + fprintf(stderr, "not found in service: " + name); +#endif + + return handleDefault(key, actualReturn, status); +} + +UObject* +ICUService::handleDefault(const Key& 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) { + Key* fallbackKey = createKey(matchID); + + 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_SUCCESS(status) && idCache == NULL) { + ICUService* ncthis = (ICUService*)this; // cast away semantic const + ncthis->idCache = new Hashtable(); + if (idCache == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + } else if (factories != NULL) { + for (int32_t pos = factories->size(); --pos >= 0;) { + Factory* f = (Factory*)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 +{ + result.setToBogus(); + { + 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) { + Factory* f = (Factory*)map->get(id); + if (f != NULL) { + f->getDisplayName(id, locale, result); + } + } + } + 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)) { + const UnicodeString* id = (const UnicodeString*)entry->key.pointer; + Factory* f = (Factory*)entry->value.pointer; + UnicodeString name; + f->getDisplayName(*id, locale, name); + if (name.isBogus()) { + status = U_MEMORY_ALLOCATION_ERROR; + } else { + dnCache->cache.put(name, (void*)id, status); // share pointer with visibleIDMap + if (U_SUCCESS(status)) { + continue; + } + } + delete dnCache; + ncthis->dnCache = NULL; + return result; + } + } + } + } + + Key* matchKey = createKey(matchID); + int32_t pos = 0; + const UHashElement *entry = NULL; + while (entry = dnCache->cache.nextElement(pos)) { + 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; +} + +const Factory* +ICUService::registerObject(UObject* objToAdopt, const UnicodeString& id) +{ + return registerObject(objToAdopt, id, TRUE); +} + +const Factory* +ICUService::registerObject(UObject* objToAdopt, const UnicodeString& id, UBool visible) +{ + Key* key = createKey(&id); + if (key != NULL) { + UnicodeString canonicalID; + key->canonicalID(canonicalID); + delete key; + + Factory* f = createSimpleFactory(objToAdopt, canonicalID, visible); + if (f != NULL) { + return registerFactory(f); + } + } + delete objToAdopt; + return NULL; +} + +Factory* +ICUService::createSimpleFactory(UObject* objToAdopt, const UnicodeString& id, UBool visible) +{ + return new SimpleFactory(objToAdopt, id, visible); +} + +const Factory* +ICUService::registerFactory(Factory* factoryToAdopt) +{ + if (factoryToAdopt != NULL) { + Mutex mutex(&lock); + + UErrorCode status = U_ZERO_ERROR; + 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 factoryToAdopt; +} + +UBool +ICUService::unregisterFactory(Factory* factory) +{ + UBool result = FALSE; + if (factory != NULL && factories != NULL) { + Mutex mutex(&lock); + + if (factories->removeElement(factory)) { + clearCaches(); + result = TRUE; + } + } + 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 factories == NULL || factories->size() == 0; +} + +Key* +ICUService::createKey(const UnicodeString* id) const +{ + return id == NULL ? NULL : new Key(*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 diff --git a/icu4c/source/common/icuserv.h b/icu4c/source/common/icuserv.h new file mode 100644 index 00000000000..daa8ea724e7 --- /dev/null +++ b/icu4c/source/common/icuserv.h @@ -0,0 +1,719 @@ +/** + ******************************************************************************* + * Copyright (C) 2001-2002, International Business Machines Corporation. * + * All Rights Reserved. * + ******************************************************************************* + */ + +#ifndef ICUSERV_H +#define ICUSERV_H + +#include "unicode/utypes.h" + +#if UCONFIG_NO_SERVICE + +U_NAMESPACE_BEGIN + +/* + * Allow the declaration of APIs with pointers to ICUService + * even when service is removed from the build. + */ +class ICUService; + +U_NAMESPACE_END + +#else + +#include "unicode/uobject.h" +#include "unicode/unistr.h" +#include "unicode/chariter.h" +#include "unicode/locid.h" +#include "unicode/ubrk.h" + +#include "hash.h" +#include "uvector.h" +#include "icunotif.h" + +U_NAMESPACE_BEGIN + +class Key; +class Factory; +class SimpleFactory; +class ServiceListener; +class ICUServiceEnumeration; +class ICUService; +class ICUServiceTest; + +class DNCache; + +/* + ****************************************************************** + */ + + /** + * Keys are used to communicate with factories to generate an + * instance of the service. Keys define how ids are + * canonicalized, provide both a current id and a current + * descriptor to use in querying the cache and factories, and + * determine the fallback strategy.

+ * + *

Keys provide both a currentDescriptor and a currentID. + * The descriptor contains an optional prefix, followed by '/' + * and the currentID. Factories that handle complex keys, + * for example number format factories that generate multiple + * kinds of formatters for the same locale, use the descriptor + * to provide a fully unique identifier for the service object, + * while using the currentID (in this case, the locale string), + * as the visible IDs that can be localized. + * + *

The default implementation of Key has no fallbacks and + * has no custom descriptors.

+ */ +class U_COMMON_API Key : public UObject { + private: + const UnicodeString _id; + + protected: + static const UChar PREFIX_DELIMITER; + + public: + + /** + * Construct a key from an id. + */ + Key(const UnicodeString& id); + + /** + * Virtual destructor. + */ + virtual ~Key(); + + /** + * Return the original ID used to construct this key. + */ + virtual const UnicodeString& getID() const; + + /** + * Return the canonical version of the original ID. This implementation + * appends the original ID to result. It returns result as a convenience. + */ + virtual UnicodeString& canonicalID(UnicodeString& result) const; + + /** + * Return the (canonical) current ID. This implementation + * appends the canonical ID to result. It returns result as a convenience. + */ + virtual UnicodeString& currentID(UnicodeString& result) const; + + /** + * Return the current descriptor. This implementation returns + * the current ID in result, ignoring any previous contents. + * Result is returned as a convenience. + * + *

The current descriptor is used to fully + * identify an instance of the service in the cache. A + * factory may handle all descriptors for an ID, or just a + * particular descriptor. The factory can either parse the + * descriptor or use custom API on the key in order to + * instantiate the service. + */ + virtual UnicodeString& currentDescriptor(UnicodeString& result) const; + + /** + * If the key has a fallback, modify the key and return true, + * otherwise return false. The current ID will change if there + * is a fallback. No currentIDs should be repeated, and fallback + * must eventually return false. This implmentation has no fallbacks + * and always returns false. + */ + virtual UBool fallback(); + + /** + * Return true if a key created from id matches, or would eventually + * fallback to match, the canonical ID of this key. + */ + virtual UBool isFallbackOf(const UnicodeString& id) const; + + /** + * Append the prefix to result + */ + virtual UnicodeString& prefix(UnicodeString& result) const; + + /** + * Parse the prefix out of a descriptor string. + */ + static UnicodeString& parsePrefix(UnicodeString& result); + + /** + * Parse the suffix out of a descriptor string. + */ + static UnicodeString& parseSuffix(UnicodeString& result); + + public: + /** + * UObject boilerplate. + */ + virtual UClassID getDynamicClassID() const { + return getStaticClassID(); + } + + static UClassID getStaticClassID() { + return (UClassID)&fgClassID; + } + + public: + virtual UnicodeString& debug(UnicodeString& result) const; + virtual UnicodeString& debugClass(UnicodeString& result) const; + + private: + static const char fgClassID; +}; + +/* + ****************************************************************** + */ + + /** + * Factories generate the service objects maintained by the + * service. A factory generates a service object from a key, + * updates id->factory mappings, and returns the display name for + * a supported id. + */ +class U_COMMON_API Factory : public UObject { + public: + + /** + * Create a service object from the key, if this factory + * supports the key. Otherwise, return null. + * + *

If the factory supports the key, then it can call + * defaultCreate(const Key& key, const ICUService*) + * the service's getKey(Key, String[], Factory) method + * passing itself as the factory to get the object that + * the service would have created prior to the factory's + * registration with the service. This can change the + * key, so any information required from the key should + * be extracted before making such a callback. + */ + virtual UObject* create(const Key& key, const ICUService* service, UErrorCode& status) const = 0; + + /** + * Update the result IDs (not descriptors) to reflect the IDs + * this factory handles. This function and getDisplayName are + * used to support ICUService.getDisplayNames. Basically, the + * factory has to determine which IDs it will permit to be + * available, and of those, which it will provide localized + * display names for. In most cases this reflects the IDs that + * the factory directly supports. + */ + virtual void updateVisibleIDs(Hashtable& result, UErrorCode& status) const = 0; + + /** + * Return the display name for this id in the provided locale. + * This is an localized id, not a descriptor. If the id is + * not visible or not defined by the factory, return null. + * If locale is null, return id unchanged. + */ + virtual UnicodeString& getDisplayName(const UnicodeString& id, const Locale& locale, UnicodeString& result) const = 0; +}; + +/* + ****************************************************************** + */ + + /** + * A default implementation of factory. This provides default + * implementations for subclasses, and implements a singleton + * factory that matches a single id and returns a single + * (possibly deferred-initialized) instance. This implements + * updateVisibleIDs to add a mapping from its ID to itself + * if visible is true, or to remove any existing mapping + * for its ID if visible is false. + */ +class U_COMMON_API SimpleFactory : public Factory { + protected: + UObject* _instance; + const UnicodeString _id; + const UBool _visible; + + public: + /** + * Convenience constructor that calls SimpleFactory(Object, String, boolean) + * with visible true. + */ + SimpleFactory(UObject* instanceToAdopt, const UnicodeString& id); + + /** + * Construct a simple factory that maps a single id to a single + * service instance. If visible is true, the id will be visible. + * Neither the instance nor the id can be null. + */ + SimpleFactory(UObject* instanceToAdopt, const UnicodeString& id, UBool visible); + + /** + * Virtual destructor. + */ + virtual ~SimpleFactory(); + + /** + * Return the service instance if the factory's id is equal to + * the key's currentID. Service is ignored. + */ + UObject* create(const Key& key, const ICUService* service, UErrorCode& status) const; + + /** + * If visible, adds a mapping from id -> this to the result, + * otherwise removes id from result. + */ + void updateVisibleIDs(Hashtable& result, UErrorCode& status) const; + + /** + * If this.id equals id, returns id regardless of locale, + * otherwise returns the empty string. (This default implementation has + * no localized id information.) + */ + UnicodeString& getDisplayName(const UnicodeString& id, const Locale& locale, UnicodeString& result) const; + + public: + + /** + * UObject boilerplate. + */ + virtual UClassID getDynamicClassID() const { + return getStaticClassID(); + } + + static UClassID getStaticClassID() { + return (UClassID)&fgClassID; + } + + public: + + /** + * For debugging. + */ + virtual UnicodeString& debug(UnicodeString& toAppendTo) const; + virtual UnicodeString& debugClass(UnicodeString& toAppendTo) const; + + private: + static const char fgClassID; +}; + +/* + ****************************************************************** + */ + +/** + * ServiceListener is the listener that ICUService provides by default. + * ICUService will notifiy this listener when factories are added to + * or removed from the service. Subclasses can provide + * different listener interfaces that extend EventListener, and modify + * acceptsListener and notifyListener as appropriate. + */ +class U_COMMON_API ServiceListener : public EventListener { + public: + virtual UClassID getDynamicClassID() const { + return getStaticClassID(); + } + + static UClassID getStaticClassID() { + return (UClassID)&fgClassID; + } + + virtual void serviceChanged(const ICUService& service) const = 0; + + private: + static const char fgClassID; +}; + +/* + ****************************************************************** + */ + +class U_COMMON_API StringPair : public UMemory { +public: + const UnicodeString displayName; + const UnicodeString id; + + static StringPair* create(const UnicodeString& displayName, + const UnicodeString& id, + UErrorCode& status); + + UBool isBogus() const; + +private: + StringPair(const UnicodeString& displayName, const UnicodeString& id); +}; + +/** + * Deleter for StringPairs + */ +U_CAPI void U_EXPORT2 +deleteStringPair(void *obj); + +/* + ****************************************************************** + */ + + /** + *

A Service provides access to service objects that implement a + * particular service, e.g. transliterators. Users provide a String + * id (for example, a locale string) to the service, and get back an + * object for that id. Service objects can be any kind of object. + * The service object is cached and returned for later queries, so + * generally it should not be mutable, or the caller should clone the + * object before modifying it.

+ * + *

Services 'canonicalize' the query id and use the canonical id to + * query for the service. The service also defines a mechanism to + * 'fallback' the id multiple times. Clients can optionally request + * the actual id that was matched by a query when they use an id to + * retrieve a service object.

+ * + *

Service objects are instantiated by Factory objects registered with + * the service. The service queries each Factory in turn, from most recently + * registered to earliest registered, until one returns a service object. + * If none responds with a service object, a fallback id is generated, + * and the process repeats until a service object is returned or until + * the id has no further fallbacks.

+ * + *

Factories can be dynamically registered and unregistered with the + * service. When registered, a Factory is installed at the head of + * the factory list, and so gets 'first crack' at any keys or fallback + * keys. When unregistered, it is removed from the service and can no + * longer be located through it. Service objects generated by this + * factory and held by the client are unaffected.

+ * + *

ICUService uses Keys to query factories and perform + * fallback. The Key defines the canonical form of the id, and + * implements the fallback strategy. Custom Keys can be defined that + * parse complex IDs into components that Factories can more easily + * use. The Key can cache the results of this parsing to save + * repeated effort. ICUService provides convenience APIs that + * take Strings and generate default Keys for use in querying.

+ * + *

ICUService provides API to get the list of ids publicly + * supported by the service (although queries aren't restricted to + * this list). This list contains only 'simple' IDs, and not fully + * unique ids. Factories are associated with each simple ID and + * the responsible factory can also return a human-readable localized + * version of the simple ID, for use in user interfaces. ICUService + * can also provide a sorted collection of the all the localized visible + * ids.

+ * + *

ICUService implements ICUNotifier, so that clients can register + * to receive notification when factories are added or removed from + * the service. ICUService provides a default EventListener subinterface, + * ServiceListener, which can be registered with the service. When + * the service changes, the ServiceListener's serviceChanged method + * is called, with the service as the only argument.

+ * + *

The ICUService API is both rich and generic, and it is expected + * that most implementations will statically 'wrap' ICUService to + * present a more appropriate API-- for example, to declare the type + * of the objects returned from get, to limit the factories that can + * be registered with the service, or to define their own listener + * interface with a custom callback method. They might also customize + * ICUService by overriding it, for example, to customize the Key and + * fallback strategy. ICULocaleService is a customized service that + * uses Locale names as ids and uses Keys that implement the standard + * resource bundle fallback strategy.

+ */ +class U_COMMON_API ICUService : public ICUNotifier { + protected: + /** + * Name useful for debugging. + */ + const UnicodeString name; + + /** + * single lock used by this service. + */ + UMTX lock; + + private: + /** + * Timestamp so iterators can be fail-fast. + */ + uint32_t timestamp; + + /** + * All the factories registered with this service. + */ + UVector* factories; + + /** + * The service cache. + */ + Hashtable* serviceCache; + + /** + * The id cache. + */ + Hashtable* idCache; + + /** + * The name cache. + */ + DNCache* dnCache; + + /** + * Constructor. + */ + public: + ICUService(); + + /** + * Construct with a name (useful for debugging). + */ + ICUService(const UnicodeString& name); + + /** + * Destructor. + */ + virtual ~ICUService(); + + /** + * Return the name of this service. This will be the empty string if none was assigned. + */ + UnicodeString& getName(UnicodeString& result) const; + + /** + * Convenience override for get(Key&, UnicodeString*). This uses + * createKey to create a key for the provided descriptor. + */ + UObject* get(const UnicodeString& descriptor, UErrorCode& status) const; + + /** + * Convenience override for get(Key&, UnicodeString*). This uses + * createKey to create a key from the provided descriptor. + */ + UObject* get(const UnicodeString& descriptor, UnicodeString* actualReturn, UErrorCode& status) const; + + /** + * Convenience override for get(Key&, UnicodeString*). + */ + UObject* getKey(Key& key, UErrorCode& status) const; + + /** + *

Given a key, return a service object, and, if actualReturn + * is not null, the descriptor with which it was found in the + * first element of actualReturn. If no service object matches + * this key, return null, and leave actualReturn unchanged.

+ * + *

This queries the cache using the key's descriptor, and if no + * object in the cache matches it, tries the key on each + * registered factory, in order. If none generates a service + * object for the key, repeats the process with each fallback of + * the key, until either one returns a service object, or the key + * has no fallback.

+ * + *

If key is null, just returns null.

+ */ + UObject* getKey(Key& key, UnicodeString* actualReturn, UErrorCode& status) const; + + /** + *

Given a key, return a service object, and, if actualReturn + * is not null, the descriptor with which it was found in the + * first element of actualReturn. If no service object matches + * this key, return null, and leave actualReturn unchanged.

+ */ + UObject* getKey(Key& key, UnicodeString* actualReturn, const Factory* factory, UErrorCode& status) const; + + /** + * Convenience override for getVisibleIDs(String) that passes null + * as the fallback, thus returning all visible IDs. + */ + UVector& getVisibleIDs(UVector& result, UErrorCode& status) const; + + /** + *

Return a snapshot of the visible IDs for this service. This + * set will not change as Factories are added or removed, but the + * supported ids will, so there is no guarantee that all and only + * the ids in the returned set are visible and supported by the + * service in subsequent calls.

+ * + *

matchID is passed to createKey to create a key. If the + * key is not null, it is used to filter out ids that don't have + * the key as a fallback. + * + *

The IDs are returned as pointers to UnicodeStrings. The + * caller owns the IDs. + */ + UVector& getVisibleIDs(UVector& result, const UnicodeString* matchID, UErrorCode& status) const; + + /** + * Convenience override for getDisplayName(String, Locale) that + * uses the current default locale. + */ + UnicodeString& getDisplayName(const UnicodeString& id, UnicodeString& result) const; + + /** + * Given a visible id, return the display name in the requested locale. + * If there is no directly supported id corresponding to this id, set + * result to bogus. + */ + UnicodeString& getDisplayName(const UnicodeString& id, UnicodeString& result, const Locale& locale) const; + + /** + * Convenience override of getDisplayNames(Locale, String) that + * uses the current default Locale as the locale and NULL for + * the matchID. + */ + UVector& getDisplayNames(UVector& result, UErrorCode& status) const; + + /** + * Convenience override of getDisplayNames(Locale, String) that + */ + UVector& getDisplayNames(UVector& result, const Locale& locale, UErrorCode& status) const; + + /** + * Return a snapshot of the mapping from display names to visible + * IDs for this service. This set will not change as factories + * are added or removed, but the supported ids will, so there is + * no guarantee that all and only the ids in the returned map will + * be visible and supported by the service in subsequent calls, + * nor is there any guarantee that the current display names match + * those in the set. This iterates over StringPair instances. + */ + UVector& getDisplayNames(UVector& result, + const Locale& locale, + const UnicodeString* matchID, + UErrorCode& status) const; + + /** + * A convenience override of registerObject(Object, String, boolean) + * that defaults visible to true. The service adopts the object. + */ + const Factory* registerObject(UObject* objToAdopt, const UnicodeString& id); + + /** + * Register an object with the provided id. The id will be + * canonicalized. The canonicalized ID will be returned by + * getVisibleIDs if visible is true. This wraps the object + * using createSimpleFactory and calls registerFactory. + */ + virtual const Factory* registerObject(UObject* obj, const UnicodeString& id, UBool visible); + + /** + * Register a Factory. Returns the factory if the service accepts + * the factory, otherwise returns null. The default implementation + * accepts all factories. The service owns the factories. + */ + virtual const Factory* registerFactory(Factory* factoryToAdopt); + + /** + * Unregister a factory. The first matching registered factory will + * be deleted and removed from the list. Returns true if a matching factory was + * found. If not found, factory is not deleted, but this is typically an + * error. + */ + virtual UBool unregisterFactory(Factory* factory); + + /** + * Reset the service to the default factories. The factory + * lock is acquired and then reInitializeFactories is called. + */ + virtual void reset(); + + /** + * Return true if the service is in its default state. The default + * implementation returns true if there are no factories registered. + */ + virtual UBool isDefault() const; + + /** + * Create a key from an id. This creates a Key instance. + * Subclasses can override to define more useful keys appropriate + * to the factories they accept. If id is null, returns null. + */ + virtual Key* createKey(const UnicodeString* id) const; + + /** + * Clone object so that caller can own the copy. UObject doesn't yet implement + * clone so we need an instance-aware method that knows how to do this. + * This is public so factories can call it, but should really be protected. + */ + virtual UObject* cloneInstance(UObject* instance) const = 0; + + protected: + + /** + * Create a factory that wraps a singleton object. + * Default is an instance of SimpleFactory. + */ + virtual Factory* createSimpleFactory(UObject* objToAdopt, const UnicodeString& id, UBool visible); + + /** + * Reinitialize the factory list to its default state. By default + * this clears the list. Subclasses can override to provide other + * default initialization of the factory list. Subclasses must + * not call this method directly, as it must only be called while + * holding write access to the factory list. + */ + virtual void reInitializeFactories(); + + /** + * Default handler for this service if no factory in the list + * handled the key. + */ + virtual UObject* handleDefault(const Key& key, UnicodeString* actualIDReturn, UErrorCode& status) const; + + /** + * Clear caches maintained by this service. Subclasses can + * override if they implement additional that need to be cleared + * when the service changes. Subclasses should generally not call + * this method directly, as it must only be called while + * synchronized on this. + */ + virtual void clearCaches(); + + /** + * Clears only the service cache. + * This can be called by subclasses when a change affects the service + * cache but not the id caches, e.g., when the default locale changes + * the resolution of ids changes, but not the visible ids themselves. + */ + void clearServiceCache(); + + /** + * Return true if the listener is accepted; by default this + * requires a ServiceListener. Subclasses can override to accept + * different listeners. + */ + virtual UBool acceptsListener(const EventListener& l) const; + + /** + * Notify the listener, which by default is a ServiceListener. + * Subclasses can override to use a different listener. + */ + virtual void notifyListener(EventListener& l) const; + + /** + * Return a map from visible ids to factories. + * This should be only be called when the mutex is held. + */ + const Hashtable* getVisibleIDMap(UErrorCode& status) const; + + /** + * Allow subclasses to read the time stamp. + */ + virtual int32_t getTimestamp() const; + + private: + + friend class ICUServiceTest; + /** + * Return the number of factories, used for testing. + */ + int32_t countFactories() const; +}; + +U_NAMESPACE_END + + /* UCONFIG_NO_SERVICE */ +#endif + + /* ICUSERV_H */ +#endif + diff --git a/icu4c/source/common/unicode/strenum.h b/icu4c/source/common/unicode/strenum.h new file mode 100644 index 00000000000..874731690c0 --- /dev/null +++ b/icu4c/source/common/unicode/strenum.h @@ -0,0 +1,40 @@ +/* +******************************************************************************* +* +* Copyright (C) 2002, International Business Machines +* Corporation and others. All Rights Reserved. +* +******************************************************************************* +*/ + +#ifndef STRENUM_H +#define STRENUM_H + +#include "uobject.h" + +U_NAMESPACE_BEGIN + +/** + * Base class for 'pure' C++ implementations. Adds method that + * returns the next UnicodeString since in C++ this might be a + * common storage model for strings. + */ +class U_COMMON_API StringEnumeration : public UMemory { + public: + virtual ~StringEnumeration(); + virtual int32_t count(UErrorCode& status) const = 0; + + virtual const char* next(UErrorCode& status) = 0; + virtual const UChar* unext(UErrorCode& status) = 0; + virtual const UnicodeString* snext(UErrorCode& status) = 0; + + virtual void reset(UErrorCode& status) = 0; +}; + +inline StringEnumeration::~StringEnumeration() { +} + +U_NAMESPACE_END + +/* STRENUM_H */ +#endif diff --git a/icu4c/source/test/intltest/icusvtst.cpp b/icu4c/source/test/intltest/icusvtst.cpp new file mode 100644 index 00000000000..ccd44c6c5c9 --- /dev/null +++ b/icu4c/source/test/intltest/icusvtst.cpp @@ -0,0 +1,1349 @@ +/** + ******************************************************************************* + * Copyright (C) 2001-2002, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + * + ******************************************************************************* + */ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_SERVICE + +#include "icusvtst.h" + +#include "iculserv.h" + +U_NAMESPACE_BEGIN + +class MyListener : public EventListener { +}; + +class WrongListener : public EventListener { +}; + +class ICUNSubclass : public ICUNotifier { +public: + UBool acceptsListener(const EventListener& l) const { + return TRUE; + // return l instanceof MyListener; + } + + void notifyListener(const EventListener& l) const { + } +}; + +class LKFSubclass : public LocaleKeyFactory { +public: + LKFSubclass(UBool visible) + : LocaleKeyFactory(visible ? VISIBLE : INVISIBLE) + { + } + +protected: + const Hashtable* getSupportedIDs(void) const { + // return Collections.EMPTY_SET; + return NULL; + } +}; + +class Integer : public UObject { + const int32_t _val; +public: + Integer(int32_t val) : _val(val) { + } + + Integer(const Integer& rhs) : _val(rhs._val) { + } + virtual ~Integer() { + } + + virtual UBool operator==(const UObject& other) const + { + return other.getDynamicClassID() == getStaticClassID() && + _val == ((Integer&)other)._val; + } + + public: + /** + * UObject boilerplate. + */ + virtual UClassID getDynamicClassID() const { + return getStaticClassID(); + } + + static UClassID getStaticClassID() { + return (UClassID)&fgClassID; + } + + public: + virtual UnicodeString& debug(UnicodeString& result) const { + debugClass(result); + result.append(" val: "); + result.append(_val); + return result; + } + + virtual UnicodeString& debugClass(UnicodeString& result) const { + return result.append("Integer"); + } + + private: + static const char fgClassID; +}; + +const char Integer::fgClassID = '\0'; + +// use locale keys +class TestIntegerService : public ICUService { +public: + Key* createKey(const UnicodeString* id) const { + return LocaleKey::createWithCanonicalFallback(id, NULL); // no fallback locale + } + + virtual Factory* createSimpleFactory(UObject* obj, const UnicodeString& id, UBool visible) + { + if (obj && obj->getDynamicClassID() == Integer::getStaticClassID()) { + return new SimpleFactory((Integer*)obj, id, visible); + } + return NULL; + } + + virtual UObject* cloneInstance(UObject* instance) const { + return instance ? new Integer(*(Integer*)instance) : NULL; + } +}; + + +ICUServiceTest::ICUServiceTest() { +} + +ICUServiceTest::~ICUServiceTest() { +} + +void +ICUServiceTest::runIndexedTest(int32_t index, UBool exec, const char* &name, char* par) +{ + switch (index) { + TESTCASE(0,testAPI_One); + TESTCASE(1,testAPI_Two); + TESTCASE(2,testRBF); + TESTCASE(3,testNotification); + TESTCASE(4,testLocale); + TESTCASE(5,testWrapFactory); + TESTCASE(6,testCoverage); + default: name = ""; break; + } +} + +UnicodeString& +ICUServiceTest::lrmsg(UnicodeString& result, const UnicodeString& message, const UObject* lhs, const UObject* rhs) const +{ + char buffer[128]; + sprintf(buffer, ", lhs: %x rhs: %x", lhs, rhs); + result.append(message); + result.append(buffer); + return result; +} + +void +ICUServiceTest::confirmBoolean(const UnicodeString& message, UBool val) +{ + if (val) { + logln(message); + } else { + errln(message); + } +} + +#if 0 +void +ICUServiceTest::confirmEqual(const UnicodeString& message, const UObject* lhs, const UObject* rhs) +{ + UBool equ = (lhs == NULL) + ? (rhs == NULL) + : (rhs != NULL && lhs->operator==(*rhs)); + + UnicodeString temp; + lrmsg(temp, message, lhs, rhs); + + if (equ) { + logln(temp); + } else { + errln(temp); + } +} +#else +void +ICUServiceTest::confirmEqual(const UnicodeString& message, const Integer* lhs, const Integer* rhs) +{ + UBool equ = (lhs == NULL) + ? (rhs == NULL) + : (rhs != NULL && lhs->operator==(*rhs)); + + UnicodeString temp; + lrmsg(temp, message, lhs, rhs); + + if (equ) { + logln(temp); + } else { + errln(temp); + } +} + +void +ICUServiceTest::confirmEqual(const UnicodeString& message, const UnicodeString* lhs, const UnicodeString* rhs) +{ + UBool equ = (lhs == NULL) + ? (rhs == NULL) + : (rhs != NULL && lhs->operator==(*rhs)); + + UnicodeString temp; + lrmsg(temp, message, lhs, rhs); + + if (equ) { + logln(temp); + } else { + errln(temp); + } +} + +void +ICUServiceTest::confirmEqual(const UnicodeString& message, const Locale* lhs, const Locale* rhs) +{ + UBool equ = (lhs == NULL) + ? (rhs == NULL) + : (rhs != NULL && lhs->operator==(*rhs)); + + UnicodeString temp; + lrmsg(temp, message, lhs, rhs); + + if (equ) { + logln(temp); + } else { + errln(temp); + } +} +#endif + +// use these for now +void +ICUServiceTest::confirmStringsEqual(const UnicodeString& message, const UnicodeString& lhs, const UnicodeString& rhs) +{ + UBool equ = lhs == rhs; + + UnicodeString temp = message; + temp.append(" lhs: "); + temp.append(lhs); + temp.append(" rhs: "); + temp.append(rhs); + + if (equ) { + logln(temp); + } else { + errln(temp); + } +} + + +void +ICUServiceTest::confirmIdentical(const UnicodeString& message, const UObject* lhs, const UObject *rhs) +{ + UnicodeString temp; + lrmsg(temp, message, lhs, rhs); + if (lhs == rhs) { + logln(temp); + } else { + errln(temp); + } +} + +void +ICUServiceTest::confirmIdentical(const UnicodeString& message, int32_t lhs, int32_t rhs) +{ + if (lhs == rhs) { + logln(message + " lhs: " + lhs + " rhs: " + rhs); + } else { + errln(message + " lhs: " + lhs + " rhs: " + rhs); + } +} + +void +ICUServiceTest::msgstr(const UnicodeString& message, UObject* obj, UBool err) +{ + if (obj) { + UnicodeString* str = (UnicodeString*)obj; + logln(message + *str); + delete str; + } else if (err) { + errln("Error " + message + "string is NULL"); + } +} + +void +ICUServiceTest::testAPI_One() +{ + // create a service using locale keys, + TestIntegerService service; + + // register an object with one locale, + // search for an object with a more specific locale + // should return the original object + Integer* singleton0 = new Integer(0); + service.registerObject(singleton0, "en_US"); + { + UErrorCode status = U_ZERO_ERROR; + Integer* result = (Integer*)service.get("en_US_FOO", status); + confirmEqual("1) en_US_FOO -> en_US", result, singleton0); + delete result; + } + + // register a new object with the more specific locale + // search for an object with that locale + // should return the new object + Integer* singleton1 = new Integer(1); + service.registerObject(singleton1, "en_US_FOO"); + { + UErrorCode status = U_ZERO_ERROR; + Integer* result = (Integer*)service.get("en_US_FOO", status); + confirmEqual("2) en_US_FOO -> en_US_FOO", result, singleton1); + delete result; + } + + // search for an object that falls back to the first registered locale + { + UErrorCode status = U_ZERO_ERROR; + Integer* result = (Integer*)service.get("en_US_BAR", status); + confirmEqual("3) en_US_BAR -> en_US", result, singleton0); + delete result; + } + + // get a list of the factories, should be two + { + confirmIdentical("4) factory size", service.countFactories(), 2); + } + + // register a new object with yet another locale + Integer* singleton2 = new Integer(2); + service.registerObject(singleton2, "en"); + { + confirmIdentical("5) factory size", service.countFactories(), 3); + } + + // search for an object with the new locale + // stack of factories is now en, en_US_FOO, en_US + // search for en_US should still find en_US object + { + UErrorCode status = U_ZERO_ERROR; + Integer* result = (Integer*)service.get("en_US_BAR", status); + confirmEqual("6) en_US_BAR -> en_US", result, singleton0); + delete result; + } + + // register a new object with an old id, should hide earlier factory using this id, but leave it there + Integer* singleton3 = new Integer(3); + const Factory* s3factory = service.registerObject(singleton3, "en_US"); + { + confirmIdentical("9) factory size", service.countFactories(), 4); + } + + // should get data from that new factory + { + UErrorCode status = U_ZERO_ERROR; + Integer* result = (Integer*)service.get("en_US_BAR", status); + confirmEqual("10) en_US_BAR -> (3)", result, singleton3); + delete result; + } + + // remove new factory + // should have fewer factories again + // singleton3 dead! + { + service.unregisterFactory((Factory*)s3factory); + confirmIdentical("11) factory size", service.countFactories(), 3); + } + + // should get original data again after remove factory + { + UErrorCode status = U_ZERO_ERROR; + Integer* result = (Integer*)service.get("en_US_BAR", status); + confirmEqual("12) en_US_BAR -> (3)", result, singleton0); + delete result; + } + + // shouldn't find unregistered ids + { + UErrorCode status = U_ZERO_ERROR; + Integer* result = (Integer*)service.get("foo", status); + confirmIdentical("13) foo -> null", result, NULL); + delete result; + } + + // should find non-canonical strings + { + UnicodeString resultID; + UErrorCode status = U_ZERO_ERROR; + Integer* result = (Integer*)service.get("EN_us_fOo", &resultID, status); + confirmEqual("14a) find-non-canonical", result, singleton1); + confirmStringsEqual("14b) find non-canonical", resultID, "en_US_FOO"); + delete result; + } + + // should be able to register non-canonical strings and get them canonicalized + Integer* singleton4 = new Integer(4); + service.registerObject(singleton4, "eN_ca_dUde"); + { + UnicodeString resultID; + UErrorCode status = U_ZERO_ERROR; + Integer* result = (Integer*)service.get("En_Ca_DuDe", &resultID, status); + confirmEqual("15a) find-non-canonical", result, singleton4); + confirmStringsEqual("15b) register non-canonical", resultID, "en_CA_DUDE"); + delete result; + } + + // should be able to register invisible factories, these will not + // be visible by default, but if you know the secret password you + // can still access these services... + Integer* singleton5 = new Integer(5); + service.registerObject(singleton5, "en_US_BAR", FALSE); + { + UErrorCode status = U_ZERO_ERROR; + Integer* result = (Integer*)service.get("en_US_BAR", status); + confirmEqual("17) get invisible", result, singleton5); + delete result; + } + + // should not be able to locate invisible services + { + UErrorCode status = U_ZERO_ERROR; + UVector ids(uhash_deleteUnicodeString, uhash_compareUnicodeString, status); + service.getVisibleIDs(ids, status); + UnicodeString target = "en_US_BAR"; + confirmBoolean("18) find invisible", !ids.contains(&target)); + } + + // clear factory and caches + service.reset(); + confirmBoolean("19) is default", service.isDefault()); +} + +/* + ****************************************************************** + */ + +class TestStringService : public ICUService { +public: + Key* createKey(const UnicodeString* id) const { + return LocaleKey::createWithCanonicalFallback(id, NULL); // no fallback locale + } + + virtual Factory* createSimpleFactory(UObject* obj, const UnicodeString& id, UBool visible) + { + if (obj && obj->getDynamicClassID() == UnicodeString::getStaticClassID()) { + return new SimpleFactory((UnicodeString*)obj, id, visible); + } + return NULL; + } + + virtual UObject* cloneInstance(UObject* instance) const { + return instance ? new UnicodeString(*(UnicodeString*)instance) : NULL; + } +}; + +// this creates a string for any id, but doesn't report anything +class AnonymousStringFactory : public Factory +{ +public: + virtual UObject* create(const Key& key, const ICUService* service, UErrorCode& status) const { + return new UnicodeString(key.getID()); + } + + virtual void updateVisibleIDs(Hashtable& result, UErrorCode& status) const { + // do nothing + } + + virtual UnicodeString& getDisplayName(const UnicodeString& id, const Locale& locale, UnicodeString& result) const { + // do nothing + return result; + } + + virtual UClassID getDynamicClassID() const { + return getStaticClassID(); + } + + static UClassID getStaticClassID() { + return (UClassID)&fgClassID; + } + +private: + static const char fgClassID; +}; + +const char AnonymousStringFactory::fgClassID = '\0'; + +class TestMultipleKeyStringFactory : public Factory { + UErrorCode _status; + UVector _ids; + UnicodeString _factoryID; + +public: + TestMultipleKeyStringFactory(const UnicodeString ids[], int32_t count, const UnicodeString& factoryID) + : _status(U_ZERO_ERROR) + , _ids(uhash_deleteUnicodeString, uhash_compareUnicodeString, count, _status) + , _factoryID(factoryID + ": ") + { + for (int i = 0; i < count; ++i) { + _ids.addElement(new UnicodeString(ids[i]), _status); + } + } + + ~TestMultipleKeyStringFactory() { + } + + UObject* create(const Key& key, const ICUService* service, UErrorCode& status) const { + UnicodeString temp; + key.currentID(temp); + if (U_SUCCESS(_status) && _ids.contains(&temp)) { + return new UnicodeString(_factoryID + temp); + } + return NULL; + } + + void updateVisibleIDs(Hashtable& result, UErrorCode& status) const { + if (U_SUCCESS(_status)) { + for (int32_t i = 0; i < _ids.size(); ++i) { + result.put(*(UnicodeString*)_ids[i], (void*)this, status); + } + } + } + + UnicodeString& getDisplayName(const UnicodeString& id, const Locale& locale, UnicodeString& result) const { + if (U_SUCCESS(_status) && _ids.contains((void*)&id)) { + char buffer[128]; + UErrorCode status = U_ZERO_ERROR; + int32_t len = id.extract(buffer, sizeof(buffer), NULL, status); + if (U_SUCCESS(status)) { + if (len == sizeof(buffer)) { + --len; + } + buffer[len] = 0; + Locale loc = Locale::createFromName(buffer); + loc.getDisplayName(locale, result); + return result; + } + } + result.setToBogus(); // shouldn't happen + return result; + } + + virtual UClassID getDynamicClassID() const { + return getStaticClassID(); + } + + static UClassID getStaticClassID() { + return (UClassID)&fgClassID; + } + +private: + static const char fgClassID; +}; + +const char TestMultipleKeyStringFactory::fgClassID = '\0'; + +void +ICUServiceTest::testAPI_Two() +{ + TestStringService service; + service.registerFactory(new AnonymousStringFactory()); + + // anonymous factory will still handle the id + { + UErrorCode status = U_ZERO_ERROR; + const UnicodeString en_US = "en_US"; + UnicodeString* result = (UnicodeString*)service.get(en_US, status); + confirmEqual("21) locale", result, &en_US); + delete result; + } + + // still normalizes id + { + UErrorCode status = U_ZERO_ERROR; + const UnicodeString en_US_BAR = "en_US_BAR"; + UnicodeString resultID; + UnicodeString* result = (UnicodeString*)service.get("EN_us_bar", &resultID, status); + confirmEqual("22) locale", &resultID, &en_US_BAR); + delete result; + } + + // we can override for particular ids + UnicodeString* singleton0 = new UnicodeString("Zero"); + service.registerObject(singleton0, "en_US_BAR"); + { + UErrorCode status = U_ZERO_ERROR; + UnicodeString* result = (UnicodeString*)service.get("en_US_BAR", status); + confirmEqual("23) override super", result, singleton0); + delete result; + } + + // empty service should not recognize anything + service.reset(); + { + UErrorCode status = U_ZERO_ERROR; + UnicodeString* result = (UnicodeString*)service.get("en_US", status); + confirmIdentical("24) empty", result, NULL); + } + + // create a custom multiple key factory + { + UnicodeString xids[] = { + "en_US_VALLEY_GIRL", + "en_US_VALLEY_BOY", + "en_US_SURFER_GAL", + "en_US_SURFER_DUDE" + }; + int32_t count = sizeof(xids)/sizeof(UnicodeString); + + Factory* f = new TestMultipleKeyStringFactory(xids, count, "Later"); + service.registerFactory(f); + } + + + // iterate over the visual ids returned by the multiple factory + { + UErrorCode status = U_ZERO_ERROR; + UVector ids(uhash_deleteUnicodeString, uhash_compareUnicodeString, 0, status); + service.getVisibleIDs(ids, status); + for (int i = 0; i < ids.size(); ++i) { + const UnicodeString* id = (const UnicodeString*)ids[i]; + UnicodeString* result = (UnicodeString*)service.get(*id, status); + if (result) { + logln(" " + *id + " --> " + *result); + } else { + errln("could not find " + *id); + } + } + // four visible ids + confirmIdentical("25) visible ids", ids.size(), 4); + } + + // iterate over the display names + { + UErrorCode status = U_ZERO_ERROR; + UVector names(deleteStringPair, NULL, status); + service.getDisplayNames(names, Locale::getGerman(), status); + for (int i = 0; i < names.size(); ++i) { + const StringPair* pair = (const StringPair*)names[i]; + logln(" " + pair->displayName + " --> " + pair->id); + } + confirmIdentical("26) display names", names.size(), 4); + } + + // no valid display name + { + UnicodeString name; + service.getDisplayName("en_US_VALLEY_GEEK", name); + confirmBoolean("27) get display name", name.isBogus()); + } + + { + UnicodeString name; + service.getDisplayName("en_US_SURFER_DUDE", name, Locale::getEnglish()); + confirmStringsEqual("28) get display name", name, "English (United States, SURFER_DUDE)"); + } + + // register another multiple factory + { + UnicodeString xids[] = { + "en_US_SURFER", + "en_US_SURFER_GAL", + "en_US_SILICON", + "en_US_SILICON_GEEK", + }; + int32_t count = sizeof(xids)/sizeof(UnicodeString); + + Factory* f = new TestMultipleKeyStringFactory(xids, count, "Rad dude"); + service.registerFactory(f); + } + + // this time, we have seven display names + // Rad dude's surfer gal 'replaces' Later's surfer gal + { + UErrorCode status = U_ZERO_ERROR; + UVector names(deleteStringPair, NULL, status); + service.getDisplayNames(names, Locale("es"), status); + for (int i = 0; i < names.size(); ++i) { + const StringPair* pair = (const StringPair*)names[i]; + logln(" " + pair->displayName + " --> " + pair->id); + } + confirmIdentical("26) display names", names.size(), 7); + } + + // we should get the display name corresponding to the actual id + // returned by the id we used. + { + UErrorCode status = U_ZERO_ERROR; + UnicodeString actualID; + UnicodeString id = "en_us_surfer_gal"; + UnicodeString* gal = (UnicodeString*)service.get(id, &actualID, status); + if (gal != NULL) { + UnicodeString displayName; + logln("actual id: " + actualID); + service.getDisplayName(actualID, displayName, Locale::getEnglish()); + logln("found actual: " + *gal + " with display name: " + displayName); + confirmBoolean("30) found display name for actual", !displayName.isBogus()); + + service.getDisplayName(id, displayName, Locale::getEnglish()); + logln("found actual: " + *gal + " with display name: " + displayName); + confirmBoolean("31) found display name for query", displayName.isBogus()); + + delete gal; + } else { + errln("30) service could not find entry for " + id); + } + } + + // this should be handled by the 'dude' factory, since it overrides en_US_SURFER. + { + UErrorCode status = U_ZERO_ERROR; + UnicodeString actualID; + UnicodeString id = "en_US_SURFER_BOZO"; + UnicodeString* bozo = (UnicodeString*)service.get(id, &actualID, status); + if (bozo != NULL) { + UnicodeString displayName; + service.getDisplayName(actualID, displayName, Locale::getEnglish()); + logln("found actual: " + *bozo + " with display name: " + displayName); + confirmBoolean("32) found display name for actual", !displayName.isBogus()); + + service.getDisplayName(id, displayName, Locale::getEnglish()); + logln("found actual: " + *bozo + " with display name: " + displayName); + confirmBoolean("33) found display name for query", displayName.isBogus()); + + delete bozo; + } else { + errln("32) service could not find entry for " + id); + } + } + + // certainly not default... + { + confirmBoolean("34) is default ", !service.isDefault()); + } + + { + UErrorCode status = U_ZERO_ERROR; + UVector ids(uhash_deleteUnicodeString, uhash_compareUnicodeString, 0, status); + service.getVisibleIDs(ids, status); + for (int i = 0; i < ids.size(); ++i) { + const UnicodeString* id = (const UnicodeString*)ids[i]; + msgstr(*id + "? ", service.get(*id, status)); + } + + logstr("valleygirl? ", service.get("en_US_VALLEY_GIRL", status)); + logstr("valleyboy? ", service.get("en_US_VALLEY_BOY", status)); + logstr("valleydude? ", service.get("en_US_VALLEY_DUDE", status)); + logstr("surfergirl? ", service.get("en_US_SURFER_GIRL", status)); + } +} + + +class CalifornioLanguageFactory : public ICUResourceBundleFactory +{ +public: + static const char* californio; // = "en_US_CA"; + static const char* valley; // = californio ## "_VALLEY"; + static const char* surfer; // = californio ## "_SURFER"; + static const char* geek; // = californio ## "_GEEK"; + static const Hashtable* supportedIDs; // = NULL; + + const Hashtable* getSupportedIDs(UErrorCode& status) const + { + if (supportedIDs == NULL) { + Hashtable* table = new Hashtable(); + table->put(UnicodeString(californio), (void*)table, status); + table->put(UnicodeString(valley), (void*)table, status); + table->put(UnicodeString(surfer), (void*)table, status); + table->put(UnicodeString(geek), (void*)table, status); + + // not necessarily atomic, but this is a test... + supportedIDs = table; + } + return supportedIDs; + } + + UnicodeString& getDisplayName(const UnicodeString& id, const Locale& locale, UnicodeString& result) const + { + UnicodeString prefix = ""; + UnicodeString suffix = ""; + UnicodeString ls = locale.getName(); + if (LocaleUtility::isFallbackOf(californio, ls)) { + if (!ls.caseCompare(valley, 0)) { + prefix = "Like, you know, it's so totally "; + } else if (!ls.caseCompare(surfer, 0)) { + prefix = "Dude, it's "; + } else if (!ls.caseCompare(geek, 0)) { + prefix = "I'd estimate it is approximately "; + } else { + prefix = "Huh? Maybe "; + } + } + if (LocaleUtility::isFallbackOf(californio, id)) { + if (!id.caseCompare(valley, 0)) { + suffix = "like the Valley, you know? Let's go to the mall!"; + } else if (!id.caseCompare(surfer, 0)) { + suffix = "time to hit those gnarly waves, Dude!!!"; + } else if (!id.caseCompare(geek, 0)) { + suffix = "all systems go. T-Minus 9, 8, 7..."; + } else { + suffix = "No Habla Englais"; + } + } else { + suffix = ICUResourceBundleFactory::getDisplayName(id, locale, result); + } + + result = prefix + suffix; + return result; + } +}; + +const char* CalifornioLanguageFactory::californio = "en_US_CA"; +const char* CalifornioLanguageFactory::valley = "en_US_CA_VALLEY"; +const char* CalifornioLanguageFactory::surfer = "en_US_CA_SURFER"; +const char* CalifornioLanguageFactory::geek = "en_US_CA_GEEK"; +const Hashtable* CalifornioLanguageFactory::supportedIDs = NULL; + +void +ICUServiceTest::testRBF() +{ + // resource bundle factory. + TestStringService service; + service.registerFactory(new ICUResourceBundleFactory()); + + // list all of the resources + { + UErrorCode status = U_ZERO_ERROR; + UVector ids(uhash_deleteUnicodeString, uhash_compareUnicodeString, 0, status); + service.getVisibleIDs(ids, status); + logln("all visible ids:"); + for (int i = 0; i < ids.size(); ++i) { + const UnicodeString* id = (const UnicodeString*)ids[i]; + logln(*id); + } + } + + // get all the display names of these resources + // this should be fast since the display names were cached. + { + UErrorCode status = U_ZERO_ERROR; + UVector names(deleteStringPair, NULL, status); + service.getDisplayNames(names, Locale::getGermany(), status); + logln("service display names for de_DE"); + for (int i = 0; i < names.size(); ++i) { + const StringPair* pair = (const StringPair*)names[i]; + logln(" " + pair->displayName + " --> " + pair->id); + } + } + + service.registerFactory(new CalifornioLanguageFactory()); + + // get all the display names of these resources + { + logln("californio language factory:"); + const char* idNames[] = { + CalifornioLanguageFactory::californio, + CalifornioLanguageFactory::valley, + CalifornioLanguageFactory::surfer, + CalifornioLanguageFactory::geek, + }; + int32_t count = sizeof(idNames)/sizeof(idNames[0]); + + for (int i = 0; i < count; ++i) { + logln(UnicodeString("\n --- ") + idNames[i] + " ---"); + { + UErrorCode status = U_ZERO_ERROR; + UVector names(deleteStringPair, NULL, status); + service.getDisplayNames(names, idNames[i], status); + for (int i = 0; i < names.size(); ++i) { + const StringPair* pair = (const StringPair*)names[i]; + logln(" " + pair->displayName + " --> " + pair->id); + } + } + } + } +} + +class SimpleListener : public ServiceListener { + ICUServiceTest* _test; + int32_t _n; + UnicodeString _name; + +public: + SimpleListener(ICUServiceTest* test, const UnicodeString& name) : _test(test), _n(0), _name(name) {} + + virtual void serviceChanged(const ICUService& service) const { + UnicodeString serviceName = "listener "; + serviceName.append(_name); + serviceName.append(" n++"); + serviceName.append(" service changed: " ); + service.getName(serviceName); + _test->logln(serviceName); + } +}; + +void +ICUServiceTest::testNotification() +{ + SimpleListener one(this, "one"); + SimpleListener two(this, "two"); + { + UErrorCode status = U_ZERO_ERROR; + + logln("simple registration notification"); + TestStringService ls; + ls.addListener(&one, status); + ls.addListener(&two, status); + + logln("registering foo... "); + ls.registerObject(new UnicodeString("Foo"), "en_FOO"); + logln("registering bar... "); + ls.registerObject(new UnicodeString("Bar"), "en_BAR"); + logln("getting foo..."); + UnicodeString* result = (UnicodeString*)ls.get("en_FOO", status); + logln(*result); + delete result; + + logln("removing listener 2..."); + ls.removeListener(&two, status); + logln("registering baz..."); + ls.registerObject(new UnicodeString("Baz"), "en_BAZ"); + logln("removing listener 1"); + ls.removeListener(&one, status); + logln("registering burp..."); + ls.registerObject(new UnicodeString("Burp"), "en_BURP"); + + // should only get one notification even if register multiple times + logln("... trying multiple registration"); + ls.addListener(&one, status); + ls.addListener(&one, status); + ls.addListener(&one, status); + ls.addListener(&two, status); + ls.registerObject(new UnicodeString("Foo"), "en_FOO"); + logln("... registered foo"); + } +#if 0 + // same thread, so we can't callback within notification, unlike Java + ServiceListener l3 = new ServiceListener() { +private int n; +public void serviceChanged(ICUService s) { + logln("listener 3 report " + n++ + " service changed..."); + if (s.get("en_BOINK") == null) { // don't recurse on ourselves!!! + logln("registering boink..."); + s.registerObject("boink", "en_BOINK"); + } +} + }; + ls.addListener(l3); + logln("registering boo..."); + ls.registerObject("Boo", "en_BOO"); +#endif + + logln("...done"); +} + +class TestStringLocaleService : public ICULocaleService { +public: + virtual UObject* cloneInstance(UObject* instance) const { + return instance ? new UnicodeString(*(UnicodeString*)instance) : NULL; + } +}; + +void ICUServiceTest::testLocale() { + TestStringLocaleService service; + + UnicodeString* root = new UnicodeString("root"); + UnicodeString* german = new UnicodeString("german"); + UnicodeString* germany = new UnicodeString("german_Germany"); + UnicodeString* japanese = new UnicodeString("japanese"); + UnicodeString* japan = new UnicodeString("japanese_Japan"); + + service.registerObject(root, ""); + service.registerObject(german, "de"); + service.registerObject(germany, Locale::getGermany()); + service.registerObject(japanese, "ja"); + service.registerObject(japan, Locale::getJapan()); + + { + UErrorCode status = U_ZERO_ERROR; + UnicodeString* target = (UnicodeString*)service.get("de_US", status); + confirmEqual("test de_US", german, target); + delete target; + } + + { + UErrorCode status = U_ZERO_ERROR; + UnicodeString* target = (UnicodeString*)service.get("de_US", LocaleKey::KIND_ANY, status); + confirmEqual("test de_US 2", german, target); + delete target; + } + + { + UErrorCode status = U_ZERO_ERROR; + UnicodeString* target = (UnicodeString*)service.get("de_US", 1234, status); + confirmEqual("test de_US 3", german, target); + delete target; + } + + { + UErrorCode status = U_ZERO_ERROR; + Locale actualReturn; + UnicodeString* target = (UnicodeString*)service.get("de_US", &actualReturn, status); + confirmEqual("test de_US 5", german, target); + confirmEqual("test de_US 6", &actualReturn, &Locale::getGerman()); + delete target; + } + + { + UErrorCode status = U_ZERO_ERROR; + Locale actualReturn; + UnicodeString* target = (UnicodeString*)service.get("de_US", LocaleKey::KIND_ANY, &actualReturn, status); + confirmEqual("test de_US 7", &actualReturn, &Locale::getGerman()); + delete target; + } + + { + UErrorCode status = U_ZERO_ERROR; + Locale actualReturn; + UnicodeString* target = (UnicodeString*)service.get("de_US", 1234, &actualReturn, status); + confirmEqual("test de_US 8", german, target); + confirmEqual("test de_US 9", &actualReturn, &Locale::getGerman()); + delete target; + } + + UnicodeString* one = new UnicodeString("one/de_US"); + UnicodeString* two = new UnicodeString("two/de_US"); + + service.registerObject(one, "de_US", 1); + service.registerObject(two, "de_US", 2); + + { + UErrorCode status = U_ZERO_ERROR; + UnicodeString* target = (UnicodeString*)service.get("de_US", 1, status); + confirmEqual("test de_US kind 1", one, target); + delete target; + } + + { + UErrorCode status = U_ZERO_ERROR; + UnicodeString* target = (UnicodeString*)service.get("de_US", 2, status); + confirmEqual("test de_US kind 2", two, target); + delete target; + } + + { + UErrorCode status = U_ZERO_ERROR; + UnicodeString* target = (UnicodeString*)service.get("de_US", status); + confirmEqual("test de_US kind 3", german, target); + delete target; + } + + { + UnicodeString english = "en"; + Locale localeResult; + UnicodeString result; + LocaleKey* lkey = LocaleKey::createWithCanonicalFallback(&english, NULL, 1234); + logln("lkey prefix: " + lkey->prefix(result)); + result.remove(); + logln("lkey descriptor: " + lkey->currentDescriptor(result)); + result.remove(); + logln(UnicodeString("lkey current locale: ") + lkey->currentLocale(localeResult).getName()); + result.remove(); + + lkey->fallback(); + logln("lkey descriptor 2: " + lkey->currentDescriptor(result)); + result.remove(); + + lkey->fallback(); + logln("lkey descriptor 3: " + lkey->currentDescriptor(result)); + result.remove(); + } + + { + UErrorCode status = U_ZERO_ERROR; + UnicodeString* target = (UnicodeString*)service.get("za_PPP", status); + confirmEqual("test zappp", root, target); + delete target; + } + + Locale loc = Locale::getDefault(); + UErrorCode status = U_ZERO_ERROR; + Locale::setDefault(Locale::getJapanese(), status); + { + UErrorCode status = U_ZERO_ERROR; + UnicodeString* target = (UnicodeString*)service.get("za_PPP", status); + confirmEqual("test with ja locale", japanese, target); + delete target; + } + + { + UErrorCode status = U_ZERO_ERROR; + UVector ids(uhash_deleteUnicodeString, uhash_compareUnicodeString, 0, status); + service.getVisibleIDs(ids, status); + logln("all visible ids:"); + for (int i = 0; i < ids.size(); ++i) { + const UnicodeString* id = (const UnicodeString*)ids[i]; + logln(*id); + } + } + + Locale::setDefault(loc, status); + { + UErrorCode status = U_ZERO_ERROR; + UVector ids(uhash_deleteUnicodeString, uhash_compareUnicodeString, 0, status); + service.getVisibleIDs(ids, status); + logln("all visible ids:"); + for (int i = 0; i < ids.size(); ++i) { + const UnicodeString* id = (const UnicodeString*)ids[i]; + logln(*id); + } + } + +#if 0 + { + target = service.get("za_PPP"); + confirmEqual("test with en locale", root, target); + } + + Locale[] locales = service.getAvailableLocales(); + confirmIdentical("test available locales", locales.length, 6); + logln("locales: "); + for (int i = 0; i < locales.length; ++i) { + log("\n [" + i + "] " + locales[i]); + } + logln(" "); + + service.registerFactory(new ICUResourceBundleFactory()); + target = service.get(Locale.JAPAN); + + { + int n = 0; + List factories = service.factories(); + Iterator iter = factories.iterator(); + while (iter.hasNext()) { + logln("[" + n++ + "] " + iter.next()); + } + } +#endif + } + +void +ICUServiceTest::testWrapFactory() { +#if 0 + final String greeting = "Hello There"; + final String greetingID = "greeting"; + + ICUService service = new ICUService("wrap"); + service.registerObject(greeting, greetingID); + + logln("test one: " + service.get(greetingID)); + + class WrapFactory implements Factory { + public Object create(Key key, ICUService service) { + if (key.currentID().equals(greetingID)) { + Object previous = service.getKey(key, null, this); + return "A different greeting: \"" + previous + "\""; + } + return null; + } + + public void updateVisibleIDs(Map result) { + result.put("greeting", this); + } + + public String getDisplayName(String id, Locale locale) { + return "wrap '" + id + "'"; + } + } + service.registerFactory(new WrapFactory()); + + confirmEqual("wrap test: ", service.get(greetingID), "A different greeting: \"" + greeting + "\""); +#endif + +} + + // misc coverage tests +void ICUServiceTest::testCoverage() { +#if 0 + // Key + Key key = new Key("foobar"); + logln("ID: " + key.id()); + logln("canonicalID: " + key.canonicalID()); + logln("currentID: " + key.currentID()); + logln("has fallback: " + key.fallback()); + + // SimpleFactory + Object obj = new Object(); + SimpleFactory sf = new SimpleFactory(obj, "object"); + try { + sf = new SimpleFactory(null, null); + errln("didn't throw exception"); + } + catch (IllegalArgumentException e) { + logln("OK: " + e.getMessage()); + } + catch (Exception e) { + errln("threw wrong exception" + e); + } + logln(sf.getDisplayName("object", null)); + + // ICUService + ICUService service = new ICUService(); + service.registerFactory(sf); + + try { + service.get(null, null); + errln("didn't throw exception"); + } + catch (NullPointerException e) { + logln("OK: " + e.getMessage()); + } + /* + catch (Exception e) { + errln("threw wrong exception" + e); + } + */ + try { + service.registerFactory(null); + errln("didn't throw exception"); + } + catch (NullPointerException e) { + logln("OK: " + e.getMessage()); + } + catch (Exception e) { + errln("threw wrong exception" + e); + } + + try { + service.unregisterFactory(null); + errln("didn't throw exception"); + } + catch (NullPointerException e) { + logln("OK: " + e.getMessage()); + } + catch (Exception e) { + errln("threw wrong exception" + e); + } + + logln("object is: " + service.get("object")); + + logln("stats: " + service.stats()); + + // ICURWLock + + ICURWLock rwlock = new ICURWLock(); + rwlock.acquireRead(); + rwlock.releaseRead(); + + rwlock.acquireWrite(); + rwlock.releaseWrite(); + logln("stats: " + rwlock.getStats()); + logln("stats: " + rwlock.clearStats()); + rwlock.acquireRead(); + rwlock.releaseRead(); + rwlock.acquireWrite(); + rwlock.releaseWrite(); + logln("stats: " + rwlock.getStats()); + + try { + rwlock.releaseRead(); + errln("no error thrown"); + } + catch (InternalError e) { + logln("OK: " + e.getMessage()); + } + + try { + rwlock.releaseWrite(); + errln("no error thrown"); + } + catch (InternalError e) { + logln("OK: " + e.getMessage()); + } + + // ICULocaleService + + // LocaleKey + + // LocaleKey lkey = LocaleKey.create("en_US", "ja_JP"); + // lkey = LocaleKey.create(null, null); + LocaleKey lkey = LocaleKey.createWithCanonicalFallback("en_US", "ja_JP"); + logln("lkey: " + lkey); + + lkey = LocaleKey.createWithCanonicalFallback(null, null); + logln("lkey from null,null: " + lkey); + + // LocaleKeyFactory + LocaleKeyFactory lkf = new LKFSubclass(false); + logln("lkf: " + lkf); + logln("obj: " + lkf.create(lkey, null)); + logln(lkf.getDisplayName("foo", null)); + logln(lkf.getDisplayName("bar", null)); + lkf.updateVisibleIDs(new HashMap()); + + LocaleKeyFactory invisibleLKF = new LKFSubclass(false); + logln("obj: " + invisibleLKF.create(lkey, null)); + logln(invisibleLKF.getDisplayName("foo", null)); + logln(invisibleLKF.getDisplayName("bar", null)); + invisibleLKF.updateVisibleIDs(new HashMap()); + + // ResourceBundleFactory + ICUResourceBundleFactory rbf = new ICUResourceBundleFactory(); + logln("RB: " + rbf.create(lkey, null)); + + // ICUNotifier + ICUNotifier nf = new ICUNSubclass(); + try { + nf.addListener(null); + errln("added null listener"); + } + catch (NullPointerException e) { + logln(e.getMessage()); + } + catch (Exception e) { + errln("got wrong exception"); + } + + try { + nf.addListener(new WrongListener()); + errln("added wrong listener"); + } + catch (InternalError e) { + logln(e.getMessage()); + } + catch (Exception e) { + errln("got wrong exception"); + } + + try { + nf.removeListener(null); + errln("removed null listener"); + } + catch (NullPointerException e) { + logln(e.getMessage()); + } + catch (Exception e) { + errln("got wrong exception"); + } + + nf.removeListener(new MyListener()); + nf.notifyChanged(); + nf.addListener(new MyListener()); + nf.removeListener(new MyListener()); +#endif + } + +U_NAMESPACE_END + +/* !UCONFIG_NO_SERVICE */ +#endif + + diff --git a/icu4c/source/test/intltest/icusvtst.h b/icu4c/source/test/intltest/icusvtst.h new file mode 100644 index 00000000000..01b32059e9c --- /dev/null +++ b/icu4c/source/test/intltest/icusvtst.h @@ -0,0 +1,65 @@ +/** + ******************************************************************************* + * Copyright (C) 2001-2002, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + * + ******************************************************************************* + */ + +#ifndef ICUSVTST_H +#define ICUSVTST_H + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_SERVICE + +#include "intltest.h" + +U_NAMESPACE_BEGIN + +class Integer; + +class ICUServiceTest : public IntlTest +{ + public: + ICUServiceTest(); + ~ICUServiceTest(); + + void runIndexedTest(int32_t index, UBool exec, const char* &name, char* par = NULL); + + void testAPI_One(void); + void testAPI_Two(void); + void testRBF(void); + void testNotification(void); + void testLocale(void); + void testWrapFactory(void); + void testCoverage(void); + + private: + UnicodeString& lrmsg(UnicodeString& result, const UnicodeString& message, const UObject* lhs, const UObject* rhs) const; + void confirmBoolean(const UnicodeString& message, UBool val); +#if 0 + void confirmEqual(const UnicodeString& message, const UObject* lhs, const UObject* rhs); +#else + void confirmEqual(const UnicodeString& message, const Integer* lhs, const Integer* rhs); + void confirmEqual(const UnicodeString& message, const UnicodeString* lhs, const UnicodeString* rhs); + void confirmEqual(const UnicodeString& message, const Locale* lhs, const Locale* rhs); +#endif + void confirmStringsEqual(const UnicodeString& message, const UnicodeString& lhs, const UnicodeString& rhs); + void confirmIdentical(const UnicodeString& message, const UObject* lhs, const UObject* rhs); + void confirmIdentical(const UnicodeString& message, int32_t lhs, int32_t rhs); + + void msgstr(const UnicodeString& message, UObject* obj, UBool err = TRUE); + void logstr(const UnicodeString& message, UObject* obj) { + msgstr(message, obj, FALSE); + } +}; + +U_NAMESPACE_END + +/* UCONFIG_NO_SERVICE */ +#endif + +/* ICUSVTST_H */ +#endif diff --git a/icu4c/source/test/intltest/intltest.cpp b/icu4c/source/test/intltest/intltest.cpp index d56a3609446..9e9e37c67fb 100644 --- a/icu4c/source/test/intltest/intltest.cpp +++ b/icu4c/source/test/intltest/intltest.cpp @@ -1154,7 +1154,7 @@ main(int argc, char* argv[]) /* Call it twice to make sure that the defaults were reset. */ /* Call it before the OK message to verify proper cleanup. */ u_cleanup(); - u_cleanup(); + u_cleanup(); fprintf(stdout, "OK: All tests passed without error.\n"); }else{ diff --git a/icu4c/source/test/intltest/intltest.dsp b/icu4c/source/test/intltest/intltest.dsp index 36e598a4ec0..4138e294002 100644 --- a/icu4c/source/test/intltest/intltest.dsp +++ b/icu4c/source/test/intltest/intltest.dsp @@ -270,6 +270,10 @@ SOURCE=.\hxuntrts.cpp # End Source File # Begin Source File +SOURCE=.\icusvtst.cpp +# End Source File +# Begin Source File + SOURCE=.\intltest.cpp # End Source File # Begin Source File @@ -647,6 +651,10 @@ SOURCE=.\hxuntrts.h # End Source File # Begin Source File +SOURCE=.\icusvtst.h +# End Source File +# Begin Source File + SOURCE=.\intltest.h # End Source File # Begin Source File diff --git a/icu4c/source/test/intltest/itmajor.cpp b/icu4c/source/test/intltest/itmajor.cpp index d1f6ee8b711..08e009bb878 100644 --- a/icu4c/source/test/intltest/itmajor.cpp +++ b/icu4c/source/test/intltest/itmajor.cpp @@ -29,6 +29,7 @@ #include "regextst.h" #include "tstnorm.h" #include "canittst.h" +#include "icusvtst.h" #define CASE_SUITE(id, suite) case id: \ name = #suite; \ @@ -135,6 +136,15 @@ void MajorTestLevel::runIndexedTest( int32_t index, UBool exec, const char* &nam #endif /* ICU_UNICODECONVERTER_USE_DEPRECATES */ break; + case 10: name = "icuserv"; +#if !UCONFIG_NO_SERVICE + if (exec) { + logln("TestSuite ICUService---"); logln(); + ICUServiceTest test; + callTest(test, par); + } +#endif + break; default: name = ""; break; } }