diff --git a/icu4c/source/common/iculserv.cpp b/icu4c/source/common/iculserv.cpp index 6e044b1b24e..82e4f1d6f8b 100644 --- a/icu4c/source/common/iculserv.cpp +++ b/icu4c/source/common/iculserv.cpp @@ -767,6 +767,9 @@ public: } void reset(UErrorCode& status) { + if (status == U_ENUM_OUT_OF_SYNC_ERROR) { + status = U_ZERO_ERROR; + } if (U_SUCCESS(status)) { _timestamp = _service->getTimestamp(); _pos = 0; diff --git a/icu4c/source/i18n/numfmt.cpp b/icu4c/source/i18n/numfmt.cpp index 28dead4dde2..9c69e12a0ed 100644 --- a/icu4c/source/i18n/numfmt.cpp +++ b/icu4c/source/i18n/numfmt.cpp @@ -31,6 +31,7 @@ #include "unicode/resbund.h" #include "unicode/dcfmtsym.h" #include "unicode/decimfmt.h" +#include "iculserv.h" #include // If no number pattern can be located for a locale, this is the last @@ -300,6 +301,155 @@ NumberFormat::getAvailableLocales(int32_t& count) return Locale::getAvailableLocales(count); } +// ------------------------------------------ +// +// Registration +// +//------------------------------------------- + +static ICULocaleService* gService = NULL; + +// ------------------------------------- + +class ICUNumberFormatFactory : public ICUResourceBundleFactory { +protected: + virtual UObject* handleCreate(const Locale& loc, int32_t kind, const ICUService* service, UErrorCode& status) const { +// !!! kind is not an EStyles, need to determine how to handle this + return NumberFormat::makeInstance(loc, (NumberFormat::EStyles)kind, status); + } +}; + +// ------------------------------------- + +class NFFactory : public LocaleKeyFactory { +private: + NumberFormatFactory* _delegate; + +public: + NFFactory(NumberFormatFactory* delegate) + : LocaleKeyFactory(delegate->visible() ? VISIBLE : INVISIBLE) + , _delegate(delegate) + { + } + + virtual ~NFFactory() + { + delete _delegate; + } + + virtual UObject* create(const ICUServiceKey& key, const ICUService* service, UErrorCode& status) const + { + if (handlesKey(key, status)) { + const LocaleKey& lkey = (const LocaleKey&)key; + Locale loc; + lkey.canonicalLocale(loc); + int32_t kind = lkey.kind(); + + UObject* result = _delegate->createFormat(loc, (UNumberFormatStyle)(kind+1)); + if (result == NULL) { + result = service->getKey((ICUServiceKey&)key /* cast away const */, NULL, this, status); + } + return result; + } + return NULL; + } + +protected: + /** + * 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 + { + return _delegate->getSupportedIDs(status); + } +}; + +class ICUNumberFormatService : public ICULocaleService { +public: + ICUNumberFormatService() + : ICULocaleService("Number Format") + { + UErrorCode status = U_ZERO_ERROR; + registerFactory(new ICUNumberFormatFactory(), status); + } + + virtual UObject* cloneInstance(UObject* instance) const { + return ((NumberFormat*)instance)->clone(); + } + + virtual UObject* handleDefault(const ICUServiceKey& key, UnicodeString* actualID, UErrorCode& status) const { + LocaleKey& lkey = (LocaleKey&)key; + int32_t kind = lkey.kind(); + Locale loc; + lkey.currentLocale(loc); + return NumberFormat::makeInstance(loc, (NumberFormat::EStyles)kind, status); + } + + virtual UBool isDefault() const { + return countFactories() == 1; + } +}; + +// ------------------------------------- + +static UMTX gLock = 0; + +static ICULocaleService* +getService(void) +{ + if (gService == NULL) { + Mutex mutex(&gLock); + if (gService == NULL) { + gService = new ICUNumberFormatService(); + } + } + return gService; +} + +// ------------------------------------- + +NumberFormat* +NumberFormat::createInstance(const Locale& loc, EStyles kind, UErrorCode& status) +{ + if (gService != NULL) { + return (NumberFormat*)gService->get(loc, kind, status); + } else { + return makeInstance(loc, kind, status); + } +} + + +// ------------------------------------- + +URegistryKey +NumberFormat::registerFactory(NumberFormatFactory* toAdopt, UErrorCode& status) +{ + return getService()->registerFactory(new NFFactory(toAdopt), status); +} + +// ------------------------------------- + +UBool +NumberFormat::unregister(URegistryKey key, UErrorCode& status) +{ + if (U_SUCCESS(status)) { + if (gService != NULL) { + return gService->unregister(key, status); + } + status = U_ILLEGAL_ARGUMENT_ERROR; + } + return FALSE; +} + +// ------------------------------------- +StringEnumeration* +NumberFormat::getAvailableLocales(void) +{ + return getService()->getAvailableLocales(); +} + // ------------------------------------- // Checks if the thousand/10 thousand grouping is used in the // NumberFormat instance. @@ -412,9 +562,9 @@ NumberFormat::setMinimumFractionDigits(int32_t newValue) // or percent) for the desired locale. NumberFormat* -NumberFormat::createInstance(const Locale& desiredLocale, - EStyles style, - UErrorCode& status) +NumberFormat::makeInstance(const Locale& desiredLocale, + EStyles style, + UErrorCode& status) { if (U_FAILURE(status)) return NULL; @@ -505,6 +655,20 @@ NumberFormat::createInstance(const Locale& desiredLocale, U_NAMESPACE_END +// defined in ucln_cmn.h + +/** + * Release all static memory held by numberformat. + */ +U_CFUNC UBool numfmt_cleanup(void) { + if (gService) { + delete gService; + gService = NULL; + } + umtx_destroy(&gLock); + return TRUE; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ //eof diff --git a/icu4c/source/i18n/ucln_in.c b/icu4c/source/i18n/ucln_in.c index 4be59926595..74b353eb226 100644 --- a/icu4c/source/i18n/ucln_in.c +++ b/icu4c/source/i18n/ucln_in.c @@ -25,6 +25,7 @@ static UBool i18n_cleanup(void) #if !UCONFIG_NO_FORMATTING timeZone_cleanup(); + numfmt_cleanup(); #endif #if !UCONFIG_NO_COLLATION diff --git a/icu4c/source/i18n/ucln_in.h b/icu4c/source/i18n/ucln_in.h index 83e849590a5..e12b23501f7 100644 --- a/icu4c/source/i18n/ucln_in.h +++ b/icu4c/source/i18n/ucln_in.h @@ -34,4 +34,6 @@ U_CFUNC UBool ucol_bld_cleanup(void); U_CFUNC UBool regex_cleanup(void); +U_CFUNC UBool numfmt_cleanup(void); + #endif diff --git a/icu4c/source/i18n/unicode/numfmt.h b/icu4c/source/i18n/unicode/numfmt.h index d0db15293a3..30c0d1536cf 100644 --- a/icu4c/source/i18n/unicode/numfmt.h +++ b/icu4c/source/i18n/unicode/numfmt.h @@ -26,10 +26,16 @@ #include "unicode/unistr.h" #include "unicode/format.h" +#include "unicode/unum.h" // UNumberFormatStyle +#include "hash.h" +#include "unicode/locid.h" U_NAMESPACE_BEGIN -class Locale; +class NumberFormatFactory; +class StringEnumeration; + +typedef const void* URegistryKey; /** * Abstract base class for all number formats. Provides interface for @@ -420,6 +426,34 @@ public: */ static const Locale* getAvailableLocales(int32_t& count); + /** + * Register a new NumberFormatFactory. The factory will be adopted. + * @param toAdopt the NumberFormatFactory instance to be adopted + * @param status the in/out status code, no special meanings are assigned + * @return a registry key that can be used to unregister this factory + * @draft ICU 2.6 + */ + static URegistryKey registerFactory(NumberFormatFactory* toAdopt, UErrorCode& status); + + /** + * Unregister a previously-registered NumberFormatFactory using the key returned from the + * register call. Key becomes invalid after a successful call and should not be used again. + * The NumberFormatFactory corresponding to the key will be deleted. + * @param key the registry key returned by a previous call to registerFactory + * @param status the in/out status code, no special meanings are assigned + * @return TRUE if the factory for the key was successfully unregistered + * @draft ICU 2.6 + */ + static UBool unregister(URegistryKey key, UErrorCode& status); + + /** + * Return a StringEnumeration over the locales available at the time of the call, + * including registered locales. + * @return a StringEnumeration over the locales available at the time of the call + * @draft ICU 2.6 + */ + static StringEnumeration* getAvailableLocales(void); + /** * Returns true if grouping is used in this format. For example, * in the English locale, with grouping on, the number 1234567 @@ -545,7 +579,6 @@ public: static inline UClassID getStaticClassID(void); /** - * Override Calendar * Returns a unique class ID POLYMORPHICALLY. Pure virtual override. * This method is to implement a simple version of RTTI, since not all * C++ compilers support genuine RTTI. Polymorphic operator==() and @@ -595,6 +628,8 @@ private: /** * Creates the specified decimal format style of the desired locale. + * Hook for service registration, uses makeInstance directly if no services + * registered. * @param desiredLocale the given locale. * @param choice the given style. * @param success Output param filled with success/failure status. @@ -602,6 +637,14 @@ private: */ static NumberFormat* createInstance(const Locale& desiredLocale, EStyles choice, UErrorCode& success); + /** + * Creates the specified decimal format style of the desired locale. + * @param desiredLocale the given locale. + * @param choice the given style. + * @param success Output param filled with success/failure status. + * @return A new NumberFormat instance. + */ + static NumberFormat* makeInstance(const Locale& desiredLocale, EStyles choice, UErrorCode& success); static const int32_t fgNumberPatternsCount; static const UChar* const fgLastResortNumberPatterns[]; @@ -611,8 +654,75 @@ private: int32_t fMaxFractionDigits; int32_t fMinFractionDigits; UBool fParseIntegerOnly; + + friend class ICUNumberFormatFactory; // access to makeInstance, EStyles + friend class ICUNumberFormatService; }; +/** + * A NumberFormatFactory is used to register new number formats. The factory + * should be able to create any of the predefined formats for each locale it + * supports. When registered, the locales it supports extend or override the + * locale already supported by ICU. + * + * @prototype + */ +class U_I18N_API NumberFormatFactory : public UObject { +public: + + /** + * Return true if this factory will be visible. Default is true. + * If not visible, the locales supported by this factory will not + * be listed by getAvailableLocales. + */ + virtual UBool visible(void) const = 0; + + /** + * Return an unmodifiable collection of the locale names directly + * supported by this factory. + */ + virtual const Hashtable* getSupportedIDs(UErrorCode& status) const = 0; + + /** + * Return a number format of the appropriate type. If the locale + * is not supported, return null. If the locale is supported, but + * the type is not provided by this service, return null. Otherwise + * return an appropriate instance of NumberFormat. + */ + virtual NumberFormat* createFormat(const Locale& loc, UNumberFormatStyle formatType) = 0; +}; + + /** + * A NumberFormatFactory that supports a single locale. It can be visible or invisible. + * @prototype + */ +class U_I18N_API SimpleNumberFormatFactory : public NumberFormatFactory { +protected: + const UBool _visible; + Hashtable _ids; + +public: + SimpleNumberFormatFactory(const Locale& locale, UBool visible = TRUE) + : _visible(visible) + { + UErrorCode status = U_ZERO_ERROR; + _ids.put(locale.getName(), this, status); // um, ignore error code... + } + + virtual UBool visible(void) const { + return _visible; + } + + virtual const Hashtable* getSupportedIDs(UErrorCode& status) const + { + if (U_SUCCESS(status)) { + return &_ids; + } + return NULL; + } +}; + + // ------------------------------------- inline UClassID diff --git a/icu4c/source/test/intltest/nmfmapts.cpp b/icu4c/source/test/intltest/nmfmapts.cpp index 6bfa04d3297..7a34aa0c8a7 100644 --- a/icu4c/source/test/intltest/nmfmapts.cpp +++ b/icu4c/source/test/intltest/nmfmapts.cpp @@ -13,6 +13,8 @@ #include "unicode/numfmt.h" #include "unicode/decimfmt.h" #include "unicode/locid.h" +#include "unicode/unum.h" +#include "unicode/strenum.h" // This is an API test, not a unit test. It doesn't test very many cases, and doesn't // try to test the full functionality. It just calls each function in the class and @@ -33,7 +35,17 @@ void IntlTestNumberFormatAPI::runIndexedTest( int32_t index, UBool exec, const c testAPI(/* par */); } break; - + case 1: name = "NumberFormatRegistration"; + if (exec) { + logln("NumberFormat Registration test---"); logln(""); + UErrorCode status = U_ZERO_ERROR; + Locale::setDefault(Locale::getEnglish(), status); + if(U_FAILURE(status)) { + errln("ERROR: Could not set default locale, test may not give correct results"); + } + testRegistration(); + } + break; default: name = ""; break; } } @@ -226,4 +238,152 @@ void IntlTestNumberFormatAPI::testAPI(/* char* par */) delete per_fr; } +#define SRC_LOC Locale::getFrance() +#define SWAP_LOC Locale::getUS() + +class TestFactory : public SimpleNumberFormatFactory { + NumberFormat* currencyStyle; + +public: + TestFactory() + : SimpleNumberFormatFactory(SRC_LOC, TRUE) + { + UErrorCode status = U_ZERO_ERROR; + currencyStyle = NumberFormat::createInstance(SWAP_LOC, status); + } + + virtual ~TestFactory() + { + delete currencyStyle; + } + + virtual NumberFormat* createFormat(const Locale& loc, UNumberFormatStyle formatType) + { + if (formatType == UNUM_CURRENCY) { + return (NumberFormat*)currencyStyle->clone(); + } + return NULL; + } + + virtual inline UClassID getDynamicClassID() const + { + return (UClassID)gID; + } + + static inline UClassID getStaticClassID() + { + return (UClassID)gID; + } + +private: + static char gID; +}; + +char TestFactory::gID = 0; + +void +IntlTestNumberFormatAPI::testRegistration() +{ + + UErrorCode status = U_ZERO_ERROR; + + NumberFormat* f0 = NumberFormat::createInstance(SWAP_LOC, status); + NumberFormat* f1 = NumberFormat::createInstance(SRC_LOC, status); + NumberFormat* f2 = NumberFormat::createCurrencyInstance(SRC_LOC, status); + URegistryKey key = NumberFormat::registerFactory(new TestFactory(), status); + NumberFormat* f3 = NumberFormat::createCurrencyInstance(SRC_LOC, status); + NumberFormat* f3a = NumberFormat::createCurrencyInstance(SRC_LOC, status); + NumberFormat* f4 = NumberFormat::createInstance(SRC_LOC, status); + + StringEnumeration* locs = NumberFormat::getAvailableLocales(); + + UNumberFormat* uf3 = unum_open(UNUM_CURRENCY, NULL, 0, SRC_LOC.getName(),NULL, &status); + UNumberFormat* uf4 = unum_open(UNUM_DEFAULT, NULL, 0, SRC_LOC.getName(), NULL, &status); + + for (const UnicodeString* res = locs->snext(status); res; res = locs->snext(status)) { + logln(*res); // service is still in synch + } + + NumberFormat::unregister(key, status); // restore for other tests + NumberFormat* f5 = NumberFormat::createCurrencyInstance(SRC_LOC, status); + UNumberFormat* uf5 = unum_open(UNUM_CURRENCY, NULL, 0, SRC_LOC.getName(),NULL, &status); + + float n = 1234.567f; + UnicodeString res0, res1, res2, res3, res4, res5; + UChar ures3[50]; + UChar ures4[50]; + UChar ures5[50]; + + f0->format(n, res0); + f1->format(n, res1); + f2->format(n, res2); + f3->format(n, res3); + f4->format(n, res4); + f5->format(n, res5); + + unum_formatDouble(uf3, n, ures3, 50, NULL, &status); + unum_formatDouble(uf4, n, ures4, 50, NULL, &status); + unum_formatDouble(uf5, n, ures5, 50, NULL, &status); + + logln((UnicodeString)"f0 swap int: " + res0); + logln((UnicodeString)"f1 src int: " + res1); + logln((UnicodeString)"f2 src cur: " + res2); + logln((UnicodeString)"f3 reg cur: " + res3); + logln((UnicodeString)"f4 reg int: " + res4); + logln((UnicodeString)"f5 unreg cur: " + res5); + log("uf3 reg cur: "); + logln(ures3); + log("uf4 reg int: "); + logln(ures4); + log("uf5 ureg cur: "); + logln(ures5); + + if (f3 == f3a) { + errln("did not get new instance from service"); + } else { + delete f3a; + } + if (res3 != res0) { + errln("registered service did not match"); + } + if (res4 != res1) { + errln("registered service did not inherit"); + } + if (res5 != res2) { + errln("unregistered service did not match original"); + } + + if (res0 != ures3) { + errln("registered service did not match / unum"); + } + if (res1 != ures4) { + errln("registered service did not inherit / unum"); + } + if (res2 != ures5) { + errln("unregistered service did not match original / unum"); + } + + unum_close(uf5); + delete f5; + unum_close(uf4); + unum_close(uf3); + delete f4; + delete f3; + delete f2; + delete f1; + delete f0; + + for (const UnicodeString* res = locs->snext(status); res; res = locs->snext(status)) { + errln(*res); // service should be out of synch + } + + locs->reset(status); // now in synch again, we hope + for (const UnicodeString* res = locs->snext(status); res; res = locs->snext(status)) { + logln(*res); + } + + delete locs; +} + + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/intltest/nmfmapts.h b/icu4c/source/test/intltest/nmfmapts.h index 25ac6e20dc8..07f855cdab5 100644 --- a/icu4c/source/test/intltest/nmfmapts.h +++ b/icu4c/source/test/intltest/nmfmapts.h @@ -25,6 +25,7 @@ private: * executes tests of API functions, see detailed comments in source code **/ void testAPI(/* char* par */); + void testRegistration(); }; #endif /* #if !UCONFIG_NO_FORMATTING */