michael@0: /* michael@0: ******************************************************************************* michael@0: * Copyright (C) 2011-2013, International Business Machines Corporation and * michael@0: * others. All Rights Reserved. * michael@0: ******************************************************************************* michael@0: */ michael@0: michael@0: #include "unicode/utypes.h" michael@0: michael@0: #if !UCONFIG_NO_FORMATTING michael@0: michael@0: #include "unicode/locid.h" michael@0: #include "unicode/tznames.h" michael@0: #include "unicode/uenum.h" michael@0: #include "cmemory.h" michael@0: #include "cstring.h" michael@0: #include "mutex.h" michael@0: #include "putilimp.h" michael@0: #include "tznames_impl.h" michael@0: #include "uassert.h" michael@0: #include "ucln_in.h" michael@0: #include "uhash.h" michael@0: #include "umutex.h" michael@0: #include "uvector.h" michael@0: michael@0: michael@0: U_NAMESPACE_BEGIN michael@0: michael@0: // TimeZoneNames object cache handling michael@0: static UMutex gTimeZoneNamesLock = U_MUTEX_INITIALIZER; michael@0: static UHashtable *gTimeZoneNamesCache = NULL; michael@0: static UBool gTimeZoneNamesCacheInitialized = FALSE; michael@0: michael@0: // Access count - incremented every time up to SWEEP_INTERVAL, michael@0: // then reset to 0 michael@0: static int32_t gAccessCount = 0; michael@0: michael@0: // Interval for calling the cache sweep function - every 100 times michael@0: #define SWEEP_INTERVAL 100 michael@0: michael@0: // Cache expiration in millisecond. When a cached entry is no michael@0: // longer referenced and exceeding this threshold since last michael@0: // access time, then the cache entry will be deleted by the sweep michael@0: // function. For now, 3 minutes. michael@0: #define CACHE_EXPIRATION 180000.0 michael@0: michael@0: typedef struct TimeZoneNamesCacheEntry { michael@0: TimeZoneNames* names; michael@0: int32_t refCount; michael@0: double lastAccess; michael@0: } TimeZoneNamesCacheEntry; michael@0: michael@0: U_CDECL_BEGIN michael@0: /** michael@0: * Cleanup callback func michael@0: */ michael@0: static UBool U_CALLCONV timeZoneNames_cleanup(void) michael@0: { michael@0: if (gTimeZoneNamesCache != NULL) { michael@0: uhash_close(gTimeZoneNamesCache); michael@0: gTimeZoneNamesCache = NULL; michael@0: } michael@0: gTimeZoneNamesCacheInitialized = FALSE; michael@0: return TRUE; michael@0: } michael@0: michael@0: /** michael@0: * Deleter for TimeZoneNamesCacheEntry michael@0: */ michael@0: static void U_CALLCONV michael@0: deleteTimeZoneNamesCacheEntry(void *obj) { michael@0: icu::TimeZoneNamesCacheEntry *entry = (icu::TimeZoneNamesCacheEntry*)obj; michael@0: delete (icu::TimeZoneNamesImpl*) entry->names; michael@0: uprv_free(entry); michael@0: } michael@0: U_CDECL_END michael@0: michael@0: /** michael@0: * Function used for removing unreferrenced cache entries exceeding michael@0: * the expiration time. This function must be called with in the mutex michael@0: * block. michael@0: */ michael@0: static void sweepCache() { michael@0: int32_t pos = -1; michael@0: const UHashElement* elem; michael@0: double now = (double)uprv_getUTCtime(); michael@0: michael@0: while ((elem = uhash_nextElement(gTimeZoneNamesCache, &pos))) { michael@0: TimeZoneNamesCacheEntry *entry = (TimeZoneNamesCacheEntry *)elem->value.pointer; michael@0: if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) { michael@0: // delete this entry michael@0: uhash_removeElement(gTimeZoneNamesCache, elem); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // --------------------------------------------------- michael@0: // TimeZoneNamesDelegate michael@0: // --------------------------------------------------- michael@0: class TimeZoneNamesDelegate : public TimeZoneNames { michael@0: public: michael@0: TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status); michael@0: virtual ~TimeZoneNamesDelegate(); michael@0: michael@0: virtual UBool operator==(const TimeZoneNames& other) const; michael@0: virtual UBool operator!=(const TimeZoneNames& other) const {return !operator==(other);}; michael@0: virtual TimeZoneNames* clone() const; michael@0: michael@0: StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const; michael@0: StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const; michael@0: UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const; michael@0: UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const; michael@0: michael@0: UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const; michael@0: UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const; michael@0: michael@0: UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const; michael@0: michael@0: MatchInfoCollection* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const; michael@0: private: michael@0: TimeZoneNamesDelegate(); michael@0: TimeZoneNamesCacheEntry* fTZnamesCacheEntry; michael@0: }; michael@0: michael@0: TimeZoneNamesDelegate::TimeZoneNamesDelegate() michael@0: : fTZnamesCacheEntry(0) { michael@0: } michael@0: michael@0: TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status) { michael@0: Mutex lock(&gTimeZoneNamesLock); michael@0: if (!gTimeZoneNamesCacheInitialized) { michael@0: // Create empty hashtable if it is not already initialized. michael@0: gTimeZoneNamesCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); michael@0: if (U_SUCCESS(status)) { michael@0: uhash_setKeyDeleter(gTimeZoneNamesCache, uprv_free); michael@0: uhash_setValueDeleter(gTimeZoneNamesCache, deleteTimeZoneNamesCacheEntry); michael@0: gTimeZoneNamesCacheInitialized = TRUE; michael@0: ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONENAMES, timeZoneNames_cleanup); michael@0: } michael@0: } michael@0: michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: michael@0: // Check the cache, if not available, create new one and cache michael@0: TimeZoneNamesCacheEntry *cacheEntry = NULL; michael@0: michael@0: const char *key = locale.getName(); michael@0: cacheEntry = (TimeZoneNamesCacheEntry *)uhash_get(gTimeZoneNamesCache, key); michael@0: if (cacheEntry == NULL) { michael@0: TimeZoneNames *tznames = NULL; michael@0: char *newKey = NULL; michael@0: michael@0: tznames = new TimeZoneNamesImpl(locale, status); michael@0: if (tznames == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: } michael@0: if (U_SUCCESS(status)) { michael@0: newKey = (char *)uprv_malloc(uprv_strlen(key) + 1); michael@0: if (newKey == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: } else { michael@0: uprv_strcpy(newKey, key); michael@0: } michael@0: } michael@0: if (U_SUCCESS(status)) { michael@0: cacheEntry = (TimeZoneNamesCacheEntry *)uprv_malloc(sizeof(TimeZoneNamesCacheEntry)); michael@0: if (cacheEntry == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: } else { michael@0: cacheEntry->names = tznames; michael@0: cacheEntry->refCount = 1; michael@0: cacheEntry->lastAccess = (double)uprv_getUTCtime(); michael@0: michael@0: uhash_put(gTimeZoneNamesCache, newKey, cacheEntry, &status); michael@0: } michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: if (tznames != NULL) { michael@0: delete tznames; michael@0: } michael@0: if (newKey != NULL) { michael@0: uprv_free(newKey); michael@0: } michael@0: if (cacheEntry != NULL) { michael@0: uprv_free(cacheEntry); michael@0: } michael@0: cacheEntry = NULL; michael@0: } michael@0: } else { michael@0: // Update the reference count michael@0: cacheEntry->refCount++; michael@0: cacheEntry->lastAccess = (double)uprv_getUTCtime(); michael@0: } michael@0: gAccessCount++; michael@0: if (gAccessCount >= SWEEP_INTERVAL) { michael@0: // sweep michael@0: sweepCache(); michael@0: gAccessCount = 0; michael@0: } michael@0: fTZnamesCacheEntry = cacheEntry; michael@0: } michael@0: michael@0: TimeZoneNamesDelegate::~TimeZoneNamesDelegate() { michael@0: umtx_lock(&gTimeZoneNamesLock); michael@0: { michael@0: if (fTZnamesCacheEntry) { michael@0: U_ASSERT(fTZnamesCacheEntry->refCount > 0); michael@0: // Just decrement the reference count michael@0: fTZnamesCacheEntry->refCount--; michael@0: } michael@0: } michael@0: umtx_unlock(&gTimeZoneNamesLock); michael@0: } michael@0: michael@0: UBool michael@0: TimeZoneNamesDelegate::operator==(const TimeZoneNames& other) const { michael@0: if (this == &other) { michael@0: return TRUE; michael@0: } michael@0: // Just compare if the other object also use the same michael@0: // cache entry michael@0: const TimeZoneNamesDelegate* rhs = dynamic_cast(&other); michael@0: if (rhs) { michael@0: return fTZnamesCacheEntry == rhs->fTZnamesCacheEntry; michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: TimeZoneNames* michael@0: TimeZoneNamesDelegate::clone() const { michael@0: TimeZoneNamesDelegate* other = new TimeZoneNamesDelegate(); michael@0: if (other != NULL) { michael@0: umtx_lock(&gTimeZoneNamesLock); michael@0: { michael@0: // Just increment the reference count michael@0: fTZnamesCacheEntry->refCount++; michael@0: other->fTZnamesCacheEntry = fTZnamesCacheEntry; michael@0: } michael@0: umtx_unlock(&gTimeZoneNamesLock); michael@0: } michael@0: return other; michael@0: } michael@0: michael@0: StringEnumeration* michael@0: TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const { michael@0: return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status); michael@0: } michael@0: michael@0: StringEnumeration* michael@0: TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const { michael@0: return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status); michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const { michael@0: return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID); michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const { michael@0: return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID); michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const { michael@0: return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name); michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const { michael@0: return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name); michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const { michael@0: return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name); michael@0: } michael@0: michael@0: TimeZoneNames::MatchInfoCollection* michael@0: TimeZoneNamesDelegate::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { michael@0: return fTZnamesCacheEntry->names->find(text, start, types, status); michael@0: } michael@0: michael@0: // --------------------------------------------------- michael@0: // TimeZoneNames base class michael@0: // --------------------------------------------------- michael@0: TimeZoneNames::~TimeZoneNames() { michael@0: } michael@0: michael@0: TimeZoneNames* michael@0: TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) { michael@0: return new TimeZoneNamesDelegate(locale, status); michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZoneNames::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const { michael@0: return TimeZoneNamesImpl::getDefaultExemplarLocationName(tzID, name); michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZoneNames::getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const { michael@0: getTimeZoneDisplayName(tzID, type, name); michael@0: if (name.isEmpty()) { michael@0: UnicodeString mzID; michael@0: getMetaZoneID(tzID, date, mzID); michael@0: getMetaZoneDisplayName(mzID, type, name); michael@0: } michael@0: return name; michael@0: } michael@0: michael@0: michael@0: struct MatchInfo : UMemory { michael@0: UTimeZoneNameType nameType; michael@0: UnicodeString id; michael@0: int32_t matchLength; michael@0: UBool isTZID; michael@0: michael@0: MatchInfo(UTimeZoneNameType nameType, int32_t matchLength, const UnicodeString* tzID, const UnicodeString* mzID) { michael@0: this->nameType = nameType; michael@0: this->matchLength = matchLength; michael@0: if (tzID != NULL) { michael@0: this->id.setTo(*tzID); michael@0: this->isTZID = TRUE; michael@0: } else { michael@0: this->id.setTo(*mzID); michael@0: this->isTZID = FALSE; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: U_CDECL_BEGIN michael@0: static void U_CALLCONV michael@0: deleteMatchInfo(void *obj) { michael@0: delete static_cast(obj); michael@0: } michael@0: U_CDECL_END michael@0: michael@0: // --------------------------------------------------- michael@0: // MatchInfoCollection class michael@0: // --------------------------------------------------- michael@0: TimeZoneNames::MatchInfoCollection::MatchInfoCollection() michael@0: : fMatches(NULL) { michael@0: } michael@0: michael@0: TimeZoneNames::MatchInfoCollection::~MatchInfoCollection() { michael@0: if (fMatches != NULL) { michael@0: delete fMatches; michael@0: } michael@0: } michael@0: michael@0: void michael@0: TimeZoneNames::MatchInfoCollection::addZone(UTimeZoneNameType nameType, int32_t matchLength, michael@0: const UnicodeString& tzID, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: MatchInfo* matchInfo = new MatchInfo(nameType, matchLength, &tzID, NULL); michael@0: if (matchInfo == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: matches(status)->addElement(matchInfo, status); michael@0: if (U_FAILURE(status)) { michael@0: delete matchInfo; michael@0: } michael@0: } michael@0: michael@0: void michael@0: TimeZoneNames::MatchInfoCollection::addMetaZone(UTimeZoneNameType nameType, int32_t matchLength, michael@0: const UnicodeString& mzID, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: MatchInfo* matchInfo = new MatchInfo(nameType, matchLength, NULL, &mzID); michael@0: if (matchInfo == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: matches(status)->addElement(matchInfo, status); michael@0: if (U_FAILURE(status)) { michael@0: delete matchInfo; michael@0: } michael@0: } michael@0: michael@0: int32_t michael@0: TimeZoneNames::MatchInfoCollection::size() const { michael@0: if (fMatches == NULL) { michael@0: return 0; michael@0: } michael@0: return fMatches->size(); michael@0: } michael@0: michael@0: UTimeZoneNameType michael@0: TimeZoneNames::MatchInfoCollection::getNameTypeAt(int32_t idx) const { michael@0: const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx); michael@0: if (match) { michael@0: return match->nameType; michael@0: } michael@0: return UTZNM_UNKNOWN; michael@0: } michael@0: michael@0: int32_t michael@0: TimeZoneNames::MatchInfoCollection::getMatchLengthAt(int32_t idx) const { michael@0: const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx); michael@0: if (match) { michael@0: return match->matchLength; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: UBool michael@0: TimeZoneNames::MatchInfoCollection::getTimeZoneIDAt(int32_t idx, UnicodeString& tzID) const { michael@0: tzID.remove(); michael@0: const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx); michael@0: if (match && match->isTZID) { michael@0: tzID.setTo(match->id); michael@0: return TRUE; michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: UBool michael@0: TimeZoneNames::MatchInfoCollection::getMetaZoneIDAt(int32_t idx, UnicodeString& mzID) const { michael@0: mzID.remove(); michael@0: const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx); michael@0: if (match && !match->isTZID) { michael@0: mzID.setTo(match->id); michael@0: return TRUE; michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: UVector* michael@0: TimeZoneNames::MatchInfoCollection::matches(UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: if (fMatches != NULL) { michael@0: return fMatches; michael@0: } michael@0: fMatches = new UVector(deleteMatchInfo, NULL, status); michael@0: if (fMatches == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: } else if (U_FAILURE(status)) { michael@0: delete fMatches; michael@0: fMatches = NULL; michael@0: } michael@0: return fMatches; michael@0: } michael@0: michael@0: michael@0: U_NAMESPACE_END michael@0: #endif