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 "tzgnames.h" michael@0: michael@0: #include "unicode/basictz.h" michael@0: #include "unicode/locdspnm.h" michael@0: #include "unicode/msgfmt.h" michael@0: #include "unicode/rbtz.h" michael@0: #include "unicode/simpletz.h" michael@0: #include "unicode/vtzone.h" michael@0: michael@0: #include "cmemory.h" michael@0: #include "cstring.h" michael@0: #include "mutex.h" michael@0: #include "uhash.h" michael@0: #include "uassert.h" michael@0: #include "umutex.h" michael@0: #include "uresimp.h" michael@0: #include "ureslocs.h" michael@0: #include "zonemeta.h" michael@0: #include "tznames_impl.h" michael@0: #include "olsontz.h" michael@0: #include "ucln_in.h" michael@0: michael@0: U_NAMESPACE_BEGIN michael@0: michael@0: #define ZID_KEY_MAX 128 michael@0: michael@0: static const char gZoneStrings[] = "zoneStrings"; michael@0: michael@0: static const char gRegionFormatTag[] = "regionFormat"; michael@0: static const char gFallbackFormatTag[] = "fallbackFormat"; michael@0: michael@0: static const UChar gEmpty[] = {0x00}; michael@0: michael@0: static const UChar gDefRegionPattern[] = {0x7B, 0x30, 0x7D, 0x00}; // "{0}" michael@0: static const UChar gDefFallbackPattern[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})" michael@0: michael@0: static const double kDstCheckRange = (double)184*U_MILLIS_PER_DAY; michael@0: michael@0: michael@0: michael@0: U_CDECL_BEGIN michael@0: michael@0: typedef struct PartialLocationKey { michael@0: const UChar* tzID; michael@0: const UChar* mzID; michael@0: UBool isLong; michael@0: } PartialLocationKey; michael@0: michael@0: /** michael@0: * Hash function for partial location name hash key michael@0: */ michael@0: static int32_t U_CALLCONV michael@0: hashPartialLocationKey(const UHashTok key) { michael@0: // &#[L|S] michael@0: PartialLocationKey *p = (PartialLocationKey *)key.pointer; michael@0: UnicodeString str(p->tzID); michael@0: str.append((UChar)0x26) michael@0: .append(p->mzID, -1) michael@0: .append((UChar)0x23) michael@0: .append((UChar)(p->isLong ? 0x4C : 0x53)); michael@0: return str.hashCode(); michael@0: } michael@0: michael@0: /** michael@0: * Comparer for partial location name hash key michael@0: */ michael@0: static UBool U_CALLCONV michael@0: comparePartialLocationKey(const UHashTok key1, const UHashTok key2) { michael@0: PartialLocationKey *p1 = (PartialLocationKey *)key1.pointer; michael@0: PartialLocationKey *p2 = (PartialLocationKey *)key2.pointer; michael@0: michael@0: if (p1 == p2) { michael@0: return TRUE; michael@0: } michael@0: if (p1 == NULL || p2 == NULL) { michael@0: return FALSE; michael@0: } michael@0: // We just check identity of tzID/mzID michael@0: return (p1->tzID == p2->tzID && p1->mzID == p2->mzID && p1->isLong == p2->isLong); michael@0: } michael@0: michael@0: /** michael@0: * Deleter for GNameInfo michael@0: */ michael@0: static void U_CALLCONV michael@0: deleteGNameInfo(void *obj) { michael@0: uprv_free(obj); michael@0: } michael@0: michael@0: /** michael@0: * GNameInfo stores zone name information in the local trie michael@0: */ michael@0: typedef struct GNameInfo { michael@0: UTimeZoneGenericNameType type; michael@0: const UChar* tzID; michael@0: } ZNameInfo; michael@0: michael@0: /** michael@0: * GMatchInfo stores zone name match information used by find method michael@0: */ michael@0: typedef struct GMatchInfo { michael@0: const GNameInfo* gnameInfo; michael@0: int32_t matchLength; michael@0: UTimeZoneFormatTimeType timeType; michael@0: } ZMatchInfo; michael@0: michael@0: U_CDECL_END michael@0: michael@0: // --------------------------------------------------- michael@0: // The class stores time zone generic name match information michael@0: // --------------------------------------------------- michael@0: class TimeZoneGenericNameMatchInfo : public UMemory { michael@0: public: michael@0: TimeZoneGenericNameMatchInfo(UVector* matches); michael@0: ~TimeZoneGenericNameMatchInfo(); michael@0: michael@0: int32_t size() const; michael@0: UTimeZoneGenericNameType getGenericNameType(int32_t index) const; michael@0: int32_t getMatchLength(int32_t index) const; michael@0: UnicodeString& getTimeZoneID(int32_t index, UnicodeString& tzID) const; michael@0: michael@0: private: michael@0: UVector* fMatches; // vector of MatchEntry michael@0: }; michael@0: michael@0: TimeZoneGenericNameMatchInfo::TimeZoneGenericNameMatchInfo(UVector* matches) michael@0: : fMatches(matches) { michael@0: } michael@0: michael@0: TimeZoneGenericNameMatchInfo::~TimeZoneGenericNameMatchInfo() { michael@0: if (fMatches != NULL) { michael@0: delete fMatches; michael@0: } michael@0: } michael@0: michael@0: int32_t michael@0: TimeZoneGenericNameMatchInfo::size() const { michael@0: if (fMatches == NULL) { michael@0: return 0; michael@0: } michael@0: return fMatches->size(); michael@0: } michael@0: michael@0: UTimeZoneGenericNameType michael@0: TimeZoneGenericNameMatchInfo::getGenericNameType(int32_t index) const { michael@0: GMatchInfo *minfo = (GMatchInfo *)fMatches->elementAt(index); michael@0: if (minfo != NULL) { michael@0: return static_cast(minfo->gnameInfo->type); michael@0: } michael@0: return UTZGNM_UNKNOWN; michael@0: } michael@0: michael@0: int32_t michael@0: TimeZoneGenericNameMatchInfo::getMatchLength(int32_t index) const { michael@0: ZMatchInfo *minfo = (ZMatchInfo *)fMatches->elementAt(index); michael@0: if (minfo != NULL) { michael@0: return minfo->matchLength; michael@0: } michael@0: return -1; michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZoneGenericNameMatchInfo::getTimeZoneID(int32_t index, UnicodeString& tzID) const { michael@0: GMatchInfo *minfo = (GMatchInfo *)fMatches->elementAt(index); michael@0: if (minfo != NULL && minfo->gnameInfo->tzID != NULL) { michael@0: tzID.setTo(TRUE, minfo->gnameInfo->tzID, -1); michael@0: } else { michael@0: tzID.setToBogus(); michael@0: } michael@0: return tzID; michael@0: } michael@0: michael@0: // --------------------------------------------------- michael@0: // GNameSearchHandler michael@0: // --------------------------------------------------- michael@0: class GNameSearchHandler : public TextTrieMapSearchResultHandler { michael@0: public: michael@0: GNameSearchHandler(uint32_t types); michael@0: virtual ~GNameSearchHandler(); michael@0: michael@0: UBool handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status); michael@0: UVector* getMatches(int32_t& maxMatchLen); michael@0: michael@0: private: michael@0: uint32_t fTypes; michael@0: UVector* fResults; michael@0: int32_t fMaxMatchLen; michael@0: }; michael@0: michael@0: GNameSearchHandler::GNameSearchHandler(uint32_t types) michael@0: : fTypes(types), fResults(NULL), fMaxMatchLen(0) { michael@0: } michael@0: michael@0: GNameSearchHandler::~GNameSearchHandler() { michael@0: if (fResults != NULL) { michael@0: delete fResults; michael@0: } michael@0: } michael@0: michael@0: UBool michael@0: GNameSearchHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) { michael@0: if (U_FAILURE(status)) { michael@0: return FALSE; michael@0: } michael@0: if (node->hasValues()) { michael@0: int32_t valuesCount = node->countValues(); michael@0: for (int32_t i = 0; i < valuesCount; i++) { michael@0: GNameInfo *nameinfo = (ZNameInfo *)node->getValue(i); michael@0: if (nameinfo == NULL) { michael@0: break; michael@0: } michael@0: if ((nameinfo->type & fTypes) != 0) { michael@0: // matches a requested type michael@0: if (fResults == NULL) { michael@0: fResults = new UVector(uprv_free, NULL, status); michael@0: if (fResults == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: } michael@0: } michael@0: if (U_SUCCESS(status)) { michael@0: U_ASSERT(fResults != NULL); michael@0: GMatchInfo *gmatch = (GMatchInfo *)uprv_malloc(sizeof(GMatchInfo)); michael@0: if (gmatch == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: } else { michael@0: // add the match to the vector michael@0: gmatch->gnameInfo = nameinfo; michael@0: gmatch->matchLength = matchLength; michael@0: gmatch->timeType = UTZFMT_TIME_TYPE_UNKNOWN; michael@0: fResults->addElement(gmatch, status); michael@0: if (U_FAILURE(status)) { michael@0: uprv_free(gmatch); michael@0: } else { michael@0: if (matchLength > fMaxMatchLen) { michael@0: fMaxMatchLen = matchLength; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return TRUE; michael@0: } michael@0: michael@0: UVector* michael@0: GNameSearchHandler::getMatches(int32_t& maxMatchLen) { michael@0: // give the ownership to the caller michael@0: UVector *results = fResults; michael@0: maxMatchLen = fMaxMatchLen; michael@0: michael@0: // reset michael@0: fResults = NULL; michael@0: fMaxMatchLen = 0; michael@0: return results; michael@0: } michael@0: michael@0: static UMutex gLock = U_MUTEX_INITIALIZER; michael@0: michael@0: class TZGNCore : public UMemory { michael@0: public: michael@0: TZGNCore(const Locale& locale, UErrorCode& status); michael@0: virtual ~TZGNCore(); michael@0: michael@0: UnicodeString& getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, michael@0: UDate date, UnicodeString& name) const; michael@0: michael@0: UnicodeString& getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const; michael@0: michael@0: int32_t findBestMatch(const UnicodeString& text, int32_t start, uint32_t types, michael@0: UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const; michael@0: michael@0: private: michael@0: Locale fLocale; michael@0: const TimeZoneNames* fTimeZoneNames; michael@0: UHashtable* fLocationNamesMap; michael@0: UHashtable* fPartialLocationNamesMap; michael@0: michael@0: MessageFormat* fRegionFormat; michael@0: MessageFormat* fFallbackFormat; michael@0: michael@0: LocaleDisplayNames* fLocaleDisplayNames; michael@0: ZNStringPool fStringPool; michael@0: michael@0: TextTrieMap fGNamesTrie; michael@0: UBool fGNamesTrieFullyLoaded; michael@0: michael@0: char fTargetRegion[ULOC_COUNTRY_CAPACITY]; michael@0: michael@0: void initialize(const Locale& locale, UErrorCode& status); michael@0: void cleanup(); michael@0: michael@0: void loadStrings(const UnicodeString& tzCanonicalID); michael@0: michael@0: const UChar* getGenericLocationName(const UnicodeString& tzCanonicalID); michael@0: michael@0: UnicodeString& formatGenericNonLocationName(const TimeZone& tz, UTimeZoneGenericNameType type, michael@0: UDate date, UnicodeString& name) const; michael@0: michael@0: UnicodeString& getPartialLocationName(const UnicodeString& tzCanonicalID, michael@0: const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName, michael@0: UnicodeString& name) const; michael@0: michael@0: const UChar* getPartialLocationName(const UnicodeString& tzCanonicalID, michael@0: const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName); michael@0: michael@0: TimeZoneGenericNameMatchInfo* findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const; michael@0: michael@0: TimeZoneNames::MatchInfoCollection* findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const; michael@0: }; michael@0: michael@0: michael@0: // --------------------------------------------------- michael@0: // TZGNCore - core implmentation of TimeZoneGenericNames michael@0: // michael@0: // TimeZoneGenericNames is parallel to TimeZoneNames, michael@0: // but handles run-time generated time zone names. michael@0: // This is the main part of this module. michael@0: // --------------------------------------------------- michael@0: TZGNCore::TZGNCore(const Locale& locale, UErrorCode& status) michael@0: : fLocale(locale), michael@0: fTimeZoneNames(NULL), michael@0: fLocationNamesMap(NULL), michael@0: fPartialLocationNamesMap(NULL), michael@0: fRegionFormat(NULL), michael@0: fFallbackFormat(NULL), michael@0: fLocaleDisplayNames(NULL), michael@0: fStringPool(status), michael@0: fGNamesTrie(TRUE, deleteGNameInfo), michael@0: fGNamesTrieFullyLoaded(FALSE) { michael@0: initialize(locale, status); michael@0: } michael@0: michael@0: TZGNCore::~TZGNCore() { michael@0: cleanup(); michael@0: } michael@0: michael@0: void michael@0: TZGNCore::initialize(const Locale& locale, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: michael@0: // TimeZoneNames michael@0: fTimeZoneNames = TimeZoneNames::createInstance(locale, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: michael@0: // Initialize format patterns michael@0: UnicodeString rpat(TRUE, gDefRegionPattern, -1); michael@0: UnicodeString fpat(TRUE, gDefFallbackPattern, -1); michael@0: michael@0: UErrorCode tmpsts = U_ZERO_ERROR; // OK with fallback warning.. michael@0: UResourceBundle *zoneStrings = ures_open(U_ICUDATA_ZONE, locale.getName(), &tmpsts); michael@0: zoneStrings = ures_getByKeyWithFallback(zoneStrings, gZoneStrings, zoneStrings, &tmpsts); michael@0: michael@0: if (U_SUCCESS(tmpsts)) { michael@0: const UChar *regionPattern = ures_getStringByKeyWithFallback(zoneStrings, gRegionFormatTag, NULL, &tmpsts); michael@0: if (U_SUCCESS(tmpsts) && u_strlen(regionPattern) > 0) { michael@0: rpat.setTo(regionPattern, -1); michael@0: } michael@0: tmpsts = U_ZERO_ERROR; michael@0: const UChar *fallbackPattern = ures_getStringByKeyWithFallback(zoneStrings, gFallbackFormatTag, NULL, &tmpsts); michael@0: if (U_SUCCESS(tmpsts) && u_strlen(fallbackPattern) > 0) { michael@0: fpat.setTo(fallbackPattern, -1); michael@0: } michael@0: } michael@0: ures_close(zoneStrings); michael@0: michael@0: fRegionFormat = new MessageFormat(rpat, status); michael@0: if (fRegionFormat == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: } michael@0: fFallbackFormat = new MessageFormat(fpat, status); michael@0: if (fFallbackFormat == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: cleanup(); michael@0: return; michael@0: } michael@0: michael@0: // locale display names michael@0: fLocaleDisplayNames = LocaleDisplayNames::createInstance(locale); michael@0: michael@0: // hash table for names - no key/value deleters michael@0: fLocationNamesMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); michael@0: if (U_FAILURE(status)) { michael@0: cleanup(); michael@0: return; michael@0: } michael@0: michael@0: fPartialLocationNamesMap = uhash_open(hashPartialLocationKey, comparePartialLocationKey, NULL, &status); michael@0: if (U_FAILURE(status)) { michael@0: cleanup(); michael@0: return; michael@0: } michael@0: uhash_setKeyDeleter(fPartialLocationNamesMap, uprv_free); michael@0: // no value deleter michael@0: michael@0: // target region michael@0: const char* region = fLocale.getCountry(); michael@0: int32_t regionLen = uprv_strlen(region); michael@0: if (regionLen == 0) { michael@0: char loc[ULOC_FULLNAME_CAPACITY]; michael@0: uloc_addLikelySubtags(fLocale.getName(), loc, sizeof(loc), &status); michael@0: michael@0: regionLen = uloc_getCountry(loc, fTargetRegion, sizeof(fTargetRegion), &status); michael@0: if (U_SUCCESS(status)) { michael@0: fTargetRegion[regionLen] = 0; michael@0: } else { michael@0: cleanup(); michael@0: return; michael@0: } michael@0: } else if (regionLen < (int32_t)sizeof(fTargetRegion)) { michael@0: uprv_strcpy(fTargetRegion, region); michael@0: } else { michael@0: fTargetRegion[0] = 0; michael@0: } michael@0: michael@0: // preload generic names for the default zone michael@0: TimeZone *tz = TimeZone::createDefault(); michael@0: const UChar *tzID = ZoneMeta::getCanonicalCLDRID(*tz); michael@0: if (tzID != NULL) { michael@0: loadStrings(UnicodeString(tzID)); michael@0: } michael@0: delete tz; michael@0: } michael@0: michael@0: void michael@0: TZGNCore::cleanup() { michael@0: if (fRegionFormat != NULL) { michael@0: delete fRegionFormat; michael@0: } michael@0: if (fFallbackFormat != NULL) { michael@0: delete fFallbackFormat; michael@0: } michael@0: if (fLocaleDisplayNames != NULL) { michael@0: delete fLocaleDisplayNames; michael@0: } michael@0: if (fTimeZoneNames != NULL) { michael@0: delete fTimeZoneNames; michael@0: } michael@0: michael@0: uhash_close(fLocationNamesMap); michael@0: uhash_close(fPartialLocationNamesMap); michael@0: } michael@0: michael@0: michael@0: UnicodeString& michael@0: TZGNCore::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const { michael@0: name.setToBogus(); michael@0: switch (type) { michael@0: case UTZGNM_LOCATION: michael@0: { michael@0: const UChar* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz); michael@0: if (tzCanonicalID != NULL) { michael@0: getGenericLocationName(UnicodeString(tzCanonicalID), name); michael@0: } michael@0: } michael@0: break; michael@0: case UTZGNM_LONG: michael@0: case UTZGNM_SHORT: michael@0: formatGenericNonLocationName(tz, type, date, name); michael@0: if (name.isEmpty()) { michael@0: const UChar* tzCanonicalID = ZoneMeta::getCanonicalCLDRID(tz); michael@0: if (tzCanonicalID != NULL) { michael@0: getGenericLocationName(UnicodeString(tzCanonicalID), name); michael@0: } michael@0: } michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: return name; michael@0: } michael@0: michael@0: UnicodeString& michael@0: TZGNCore::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const { michael@0: if (tzCanonicalID.isEmpty()) { michael@0: name.setToBogus(); michael@0: return name; michael@0: } michael@0: michael@0: const UChar *locname = NULL; michael@0: TZGNCore *nonConstThis = const_cast(this); michael@0: umtx_lock(&gLock); michael@0: { michael@0: locname = nonConstThis->getGenericLocationName(tzCanonicalID); michael@0: } michael@0: umtx_unlock(&gLock); michael@0: michael@0: if (locname == NULL) { michael@0: name.setToBogus(); michael@0: } else { michael@0: name.setTo(locname, u_strlen(locname)); michael@0: } michael@0: michael@0: return name; michael@0: } michael@0: michael@0: /* michael@0: * This method updates the cache and must be called with a lock michael@0: */ michael@0: const UChar* michael@0: TZGNCore::getGenericLocationName(const UnicodeString& tzCanonicalID) { michael@0: U_ASSERT(!tzCanonicalID.isEmpty()); michael@0: if (tzCanonicalID.length() > ZID_KEY_MAX) { michael@0: return NULL; michael@0: } michael@0: michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UChar tzIDKey[ZID_KEY_MAX + 1]; michael@0: int32_t tzIDKeyLen = tzCanonicalID.extract(tzIDKey, ZID_KEY_MAX + 1, status); michael@0: U_ASSERT(status == U_ZERO_ERROR); // already checked length above michael@0: tzIDKey[tzIDKeyLen] = 0; michael@0: michael@0: const UChar *locname = (const UChar *)uhash_get(fLocationNamesMap, tzIDKey); michael@0: michael@0: if (locname != NULL) { michael@0: // gEmpty indicate the name is not available michael@0: if (locname == gEmpty) { michael@0: return NULL; michael@0: } michael@0: return locname; michael@0: } michael@0: michael@0: // Construct location name michael@0: UnicodeString name; michael@0: UnicodeString usCountryCode; michael@0: UBool isPrimary = FALSE; michael@0: michael@0: ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode, &isPrimary); michael@0: michael@0: if (!usCountryCode.isEmpty()) { michael@0: FieldPosition fpos; michael@0: michael@0: if (isPrimary) { michael@0: // If this is the primary zone in the country, use the country name. michael@0: char countryCode[ULOC_COUNTRY_CAPACITY]; michael@0: U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY); michael@0: int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INV); michael@0: countryCode[ccLen] = 0; michael@0: michael@0: UnicodeString country; michael@0: fLocaleDisplayNames->regionDisplayName(countryCode, country); michael@0: michael@0: Formattable param[] = { michael@0: Formattable(country) michael@0: }; michael@0: michael@0: fRegionFormat->format(param, 1, name, fpos, status); michael@0: } else { michael@0: // If this is not the primary zone in the country, michael@0: // use the exemplar city name. michael@0: michael@0: // getExemplarLocationName should retur non-empty string michael@0: // if the time zone is associated with a region michael@0: michael@0: UnicodeString city; michael@0: fTimeZoneNames->getExemplarLocationName(tzCanonicalID, city); michael@0: michael@0: Formattable param[] = { michael@0: Formattable(city), michael@0: }; michael@0: michael@0: fRegionFormat->format(param, 1, name, fpos, status); michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: } michael@0: michael@0: locname = name.isEmpty() ? NULL : fStringPool.get(name, status); michael@0: if (U_SUCCESS(status)) { michael@0: // Cache the result michael@0: const UChar* cacheID = ZoneMeta::findTimeZoneID(tzCanonicalID); michael@0: U_ASSERT(cacheID != NULL); michael@0: if (locname == NULL) { michael@0: // gEmpty to indicate - no location name available michael@0: uhash_put(fLocationNamesMap, (void *)cacheID, (void *)gEmpty, &status); michael@0: } else { michael@0: uhash_put(fLocationNamesMap, (void *)cacheID, (void *)locname, &status); michael@0: if (U_FAILURE(status)) { michael@0: locname = NULL; michael@0: } else { michael@0: // put the name info into the trie michael@0: GNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(GNameInfo)); michael@0: if (nameinfo != NULL) { michael@0: nameinfo->type = UTZGNM_LOCATION; michael@0: nameinfo->tzID = cacheID; michael@0: fGNamesTrie.put(locname, nameinfo, status); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return locname; michael@0: } michael@0: michael@0: UnicodeString& michael@0: TZGNCore::formatGenericNonLocationName(const TimeZone& tz, UTimeZoneGenericNameType type, UDate date, UnicodeString& name) const { michael@0: U_ASSERT(type == UTZGNM_LONG || type == UTZGNM_SHORT); michael@0: name.setToBogus(); michael@0: michael@0: const UChar* uID = ZoneMeta::getCanonicalCLDRID(tz); michael@0: if (uID == NULL) { michael@0: return name; michael@0: } michael@0: michael@0: UnicodeString tzID(uID); michael@0: michael@0: // Try to get a name from time zone first michael@0: UTimeZoneNameType nameType = (type == UTZGNM_LONG) ? UTZNM_LONG_GENERIC : UTZNM_SHORT_GENERIC; michael@0: fTimeZoneNames->getTimeZoneDisplayName(tzID, nameType, name); michael@0: michael@0: if (!name.isEmpty()) { michael@0: return name; michael@0: } michael@0: michael@0: // Try meta zone michael@0: UnicodeString mzID; michael@0: fTimeZoneNames->getMetaZoneID(tzID, date, mzID); michael@0: if (!mzID.isEmpty()) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UBool useStandard = FALSE; michael@0: int32_t raw, sav; michael@0: michael@0: tz.getOffset(date, FALSE, raw, sav, status); michael@0: if (U_FAILURE(status)) { michael@0: return name; michael@0: } michael@0: michael@0: if (sav == 0) { michael@0: useStandard = TRUE; michael@0: michael@0: TimeZone *tmptz = tz.clone(); michael@0: // Check if the zone actually uses daylight saving time around the time michael@0: BasicTimeZone *btz = NULL; michael@0: if (dynamic_cast(tmptz) != NULL michael@0: || dynamic_cast(tmptz) != NULL michael@0: || dynamic_cast(tmptz) != NULL michael@0: || dynamic_cast(tmptz) != NULL) { michael@0: btz = (BasicTimeZone*)tmptz; michael@0: } michael@0: michael@0: if (btz != NULL) { michael@0: TimeZoneTransition before; michael@0: UBool beforTrs = btz->getPreviousTransition(date, TRUE, before); michael@0: if (beforTrs michael@0: && (date - before.getTime() < kDstCheckRange) michael@0: && before.getFrom()->getDSTSavings() != 0) { michael@0: useStandard = FALSE; michael@0: } else { michael@0: TimeZoneTransition after; michael@0: UBool afterTrs = btz->getNextTransition(date, FALSE, after); michael@0: if (afterTrs michael@0: && (after.getTime() - date < kDstCheckRange) michael@0: && after.getTo()->getDSTSavings() != 0) { michael@0: useStandard = FALSE; michael@0: } michael@0: } michael@0: } else { michael@0: // If not BasicTimeZone... only if the instance is not an ICU's implementation. michael@0: // We may get a wrong answer in edge case, but it should practically work OK. michael@0: tmptz->getOffset(date - kDstCheckRange, FALSE, raw, sav, status); michael@0: if (sav != 0) { michael@0: useStandard = FALSE; michael@0: } else { michael@0: tmptz->getOffset(date + kDstCheckRange, FALSE, raw, sav, status); michael@0: if (sav != 0){ michael@0: useStandard = FALSE; michael@0: } michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: delete tmptz; michael@0: return name; michael@0: } michael@0: } michael@0: delete tmptz; michael@0: } michael@0: if (useStandard) { michael@0: UTimeZoneNameType stdNameType = (nameType == UTZNM_LONG_GENERIC) michael@0: ? UTZNM_LONG_STANDARD : UTZNM_SHORT_STANDARD; michael@0: UnicodeString stdName; michael@0: fTimeZoneNames->getDisplayName(tzID, stdNameType, date, stdName); michael@0: if (!stdName.isEmpty()) { michael@0: name.setTo(stdName); michael@0: michael@0: // TODO: revisit this issue later michael@0: // In CLDR, a same display name is used for both generic and standard michael@0: // for some meta zones in some locales. This looks like a data bugs. michael@0: // For now, we check if the standard name is different from its generic michael@0: // name below. michael@0: UnicodeString mzGenericName; michael@0: fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzGenericName); michael@0: if (stdName.caseCompare(mzGenericName, 0) == 0) { michael@0: name.setToBogus(); michael@0: } michael@0: } michael@0: } michael@0: if (name.isEmpty()) { michael@0: // Get a name from meta zone michael@0: UnicodeString mzName; michael@0: fTimeZoneNames->getMetaZoneDisplayName(mzID, nameType, mzName); michael@0: if (!mzName.isEmpty()) { michael@0: // Check if we need to use a partial location format. michael@0: // This check is done by comparing offset with the meta zone's michael@0: // golden zone at the given date. michael@0: UnicodeString goldenID; michael@0: fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, goldenID); michael@0: if (!goldenID.isEmpty() && goldenID != tzID) { michael@0: TimeZone *goldenZone = TimeZone::createTimeZone(goldenID); michael@0: int32_t raw1, sav1; michael@0: michael@0: // Check offset in the golden zone with wall time. michael@0: // With getOffset(date, false, offsets1), michael@0: // you may get incorrect results because of time overlap at DST->STD michael@0: // transition. michael@0: goldenZone->getOffset(date + raw + sav, TRUE, raw1, sav1, status); michael@0: delete goldenZone; michael@0: if (U_SUCCESS(status)) { michael@0: if (raw != raw1 || sav != sav1) { michael@0: // Now we need to use a partial location format michael@0: getPartialLocationName(tzID, mzID, (nameType == UTZNM_LONG_GENERIC), mzName, name); michael@0: } else { michael@0: name.setTo(mzName); michael@0: } michael@0: } michael@0: } else { michael@0: name.setTo(mzName); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return name; michael@0: } michael@0: michael@0: UnicodeString& michael@0: TZGNCore::getPartialLocationName(const UnicodeString& tzCanonicalID, michael@0: const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName, michael@0: UnicodeString& name) const { michael@0: name.setToBogus(); michael@0: if (tzCanonicalID.isEmpty() || mzID.isEmpty() || mzDisplayName.isEmpty()) { michael@0: return name; michael@0: } michael@0: michael@0: const UChar *uplname = NULL; michael@0: TZGNCore *nonConstThis = const_cast(this); michael@0: umtx_lock(&gLock); michael@0: { michael@0: uplname = nonConstThis->getPartialLocationName(tzCanonicalID, mzID, isLong, mzDisplayName); michael@0: } michael@0: umtx_unlock(&gLock); michael@0: michael@0: if (uplname == NULL) { michael@0: name.setToBogus(); michael@0: } else { michael@0: name.setTo(TRUE, uplname, -1); michael@0: } michael@0: return name; michael@0: } michael@0: michael@0: /* michael@0: * This method updates the cache and must be called with a lock michael@0: */ michael@0: const UChar* michael@0: TZGNCore::getPartialLocationName(const UnicodeString& tzCanonicalID, michael@0: const UnicodeString& mzID, UBool isLong, const UnicodeString& mzDisplayName) { michael@0: U_ASSERT(!tzCanonicalID.isEmpty()); michael@0: U_ASSERT(!mzID.isEmpty()); michael@0: U_ASSERT(!mzDisplayName.isEmpty()); michael@0: michael@0: PartialLocationKey key; michael@0: key.tzID = ZoneMeta::findTimeZoneID(tzCanonicalID); michael@0: key.mzID = ZoneMeta::findMetaZoneID(mzID); michael@0: key.isLong = isLong; michael@0: U_ASSERT(key.tzID != NULL && key.mzID != NULL); michael@0: michael@0: const UChar* uplname = (const UChar*)uhash_get(fPartialLocationNamesMap, (void *)&key); michael@0: if (uplname != NULL) { michael@0: return uplname; michael@0: } michael@0: michael@0: UnicodeString location; michael@0: UnicodeString usCountryCode; michael@0: ZoneMeta::getCanonicalCountry(tzCanonicalID, usCountryCode); michael@0: if (!usCountryCode.isEmpty()) { michael@0: char countryCode[ULOC_COUNTRY_CAPACITY]; michael@0: U_ASSERT(usCountryCode.length() < ULOC_COUNTRY_CAPACITY); michael@0: int32_t ccLen = usCountryCode.extract(0, usCountryCode.length(), countryCode, sizeof(countryCode), US_INV); michael@0: countryCode[ccLen] = 0; michael@0: michael@0: UnicodeString regionalGolden; michael@0: fTimeZoneNames->getReferenceZoneID(mzID, countryCode, regionalGolden); michael@0: if (tzCanonicalID == regionalGolden) { michael@0: // Use country name michael@0: fLocaleDisplayNames->regionDisplayName(countryCode, location); michael@0: } else { michael@0: // Otherwise, use exemplar city name michael@0: fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location); michael@0: } michael@0: } else { michael@0: fTimeZoneNames->getExemplarLocationName(tzCanonicalID, location); michael@0: if (location.isEmpty()) { michael@0: // This could happen when the time zone is not associated with a country, michael@0: // and its ID is not hierarchical, for example, CST6CDT. michael@0: // We use the canonical ID itself as the location for this case. michael@0: location.setTo(tzCanonicalID); michael@0: } michael@0: } michael@0: michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UnicodeString name; michael@0: michael@0: FieldPosition fpos; michael@0: Formattable param[] = { michael@0: Formattable(location), michael@0: Formattable(mzDisplayName) michael@0: }; michael@0: fFallbackFormat->format(param, 2, name, fpos, status); michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: michael@0: uplname = fStringPool.get(name, status); michael@0: if (U_SUCCESS(status)) { michael@0: // Add the name to cache michael@0: PartialLocationKey* cacheKey = (PartialLocationKey *)uprv_malloc(sizeof(PartialLocationKey)); michael@0: if (cacheKey != NULL) { michael@0: cacheKey->tzID = key.tzID; michael@0: cacheKey->mzID = key.mzID; michael@0: cacheKey->isLong = key.isLong; michael@0: uhash_put(fPartialLocationNamesMap, (void *)cacheKey, (void *)uplname, &status); michael@0: if (U_FAILURE(status)) { michael@0: uprv_free(cacheKey); michael@0: } else { michael@0: // put the name to the local trie as well michael@0: GNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(GNameInfo)); michael@0: if (nameinfo != NULL) { michael@0: nameinfo->type = isLong ? UTZGNM_LONG : UTZGNM_SHORT; michael@0: nameinfo->tzID = key.tzID; michael@0: fGNamesTrie.put(uplname, nameinfo, status); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return uplname; michael@0: } michael@0: michael@0: /* michael@0: * This method updates the cache and must be called with a lock, michael@0: * except initializer. michael@0: */ michael@0: void michael@0: TZGNCore::loadStrings(const UnicodeString& tzCanonicalID) { michael@0: // load the generic location name michael@0: getGenericLocationName(tzCanonicalID); michael@0: michael@0: // partial location names michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: michael@0: const UnicodeString *mzID; michael@0: UnicodeString goldenID; michael@0: UnicodeString mzGenName; michael@0: UTimeZoneNameType genNonLocTypes[] = { michael@0: UTZNM_LONG_GENERIC, UTZNM_SHORT_GENERIC, michael@0: UTZNM_UNKNOWN /*terminator*/ michael@0: }; michael@0: michael@0: StringEnumeration *mzIDs = fTimeZoneNames->getAvailableMetaZoneIDs(tzCanonicalID, status); michael@0: while ((mzID = mzIDs->snext(status))) { michael@0: if (U_FAILURE(status)) { michael@0: break; michael@0: } michael@0: // if this time zone is not the golden zone of the meta zone, michael@0: // partial location name (such as "PT (Los Angeles)") might be michael@0: // available. michael@0: fTimeZoneNames->getReferenceZoneID(*mzID, fTargetRegion, goldenID); michael@0: if (tzCanonicalID != goldenID) { michael@0: for (int32_t i = 0; genNonLocTypes[i] != UTZNM_UNKNOWN; i++) { michael@0: fTimeZoneNames->getMetaZoneDisplayName(*mzID, genNonLocTypes[i], mzGenName); michael@0: if (!mzGenName.isEmpty()) { michael@0: // getPartialLocationName formats a name and put it into the trie michael@0: getPartialLocationName(tzCanonicalID, *mzID, michael@0: (genNonLocTypes[i] == UTZNM_LONG_GENERIC), mzGenName); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (mzIDs != NULL) { michael@0: delete mzIDs; michael@0: } michael@0: } michael@0: michael@0: int32_t michael@0: TZGNCore::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types, michael@0: UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const { michael@0: timeType = UTZFMT_TIME_TYPE_UNKNOWN; michael@0: tzID.setToBogus(); michael@0: michael@0: if (U_FAILURE(status)) { michael@0: return 0; michael@0: } michael@0: michael@0: // Find matches in the TimeZoneNames first michael@0: TimeZoneNames::MatchInfoCollection *tznamesMatches = findTimeZoneNames(text, start, types, status); michael@0: if (U_FAILURE(status)) { michael@0: return 0; michael@0: } michael@0: michael@0: int32_t bestMatchLen = 0; michael@0: UTimeZoneFormatTimeType bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; michael@0: UnicodeString bestMatchTzID; michael@0: // UBool isLongStandard = FALSE; // workaround - see the comments below michael@0: UBool isStandard = FALSE; // TODO: Temporary hack (on hack) for short standard name/location name conflict (found in zh_Hant), should be removed after CLDR 21m1 integration michael@0: michael@0: if (tznamesMatches != NULL) { michael@0: UnicodeString mzID; michael@0: for (int32_t i = 0; i < tznamesMatches->size(); i++) { michael@0: int32_t len = tznamesMatches->getMatchLengthAt(i); michael@0: if (len > bestMatchLen) { michael@0: bestMatchLen = len; michael@0: if (!tznamesMatches->getTimeZoneIDAt(i, bestMatchTzID)) { michael@0: // name for a meta zone michael@0: if (tznamesMatches->getMetaZoneIDAt(i, mzID)) { michael@0: fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, bestMatchTzID); michael@0: } michael@0: } michael@0: UTimeZoneNameType nameType = tznamesMatches->getNameTypeAt(i); michael@0: if (U_FAILURE(status)) { michael@0: break; michael@0: } michael@0: switch (nameType) { michael@0: case UTZNM_LONG_STANDARD: michael@0: // isLongStandard = TRUE; michael@0: case UTZNM_SHORT_STANDARD: // this one is never used for generic, but just in case michael@0: isStandard = TRUE; // TODO: Remove this later, see the comments above. michael@0: bestMatchTimeType = UTZFMT_TIME_TYPE_STANDARD; michael@0: break; michael@0: case UTZNM_LONG_DAYLIGHT: michael@0: case UTZNM_SHORT_DAYLIGHT: // this one is never used for generic, but just in case michael@0: bestMatchTimeType = UTZFMT_TIME_TYPE_DAYLIGHT; michael@0: break; michael@0: default: michael@0: bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; michael@0: } michael@0: } michael@0: } michael@0: delete tznamesMatches; michael@0: if (U_FAILURE(status)) { michael@0: return 0; michael@0: } michael@0: michael@0: if (bestMatchLen == (text.length() - start)) { michael@0: // Full match michael@0: michael@0: //tzID.setTo(bestMatchTzID); michael@0: //timeType = bestMatchTimeType; michael@0: //return bestMatchLen; michael@0: michael@0: // TODO Some time zone uses a same name for the long standard name michael@0: // and the location name. When the match is a long standard name, michael@0: // then we need to check if the name is same with the location name. michael@0: // This is probably a data error or a design bug. michael@0: /* michael@0: if (!isLongStandard) { michael@0: tzID.setTo(bestMatchTzID); michael@0: timeType = bestMatchTimeType; michael@0: return bestMatchLen; michael@0: } michael@0: */ michael@0: // TODO The deprecation of commonlyUsed flag introduced the name michael@0: // conflict not only for long standard names, but short standard names too. michael@0: // These short names (found in zh_Hant) should be gone once we clean michael@0: // up CLDR time zone display name data. Once the short name conflict michael@0: // problem (with location name) is resolved, we should change the condition michael@0: // below back to the original one above. -Yoshito (2011-09-14) michael@0: if (!isStandard) { michael@0: tzID.setTo(bestMatchTzID); michael@0: timeType = bestMatchTimeType; michael@0: return bestMatchLen; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Find matches in the local trie michael@0: TimeZoneGenericNameMatchInfo *localMatches = findLocal(text, start, types, status); michael@0: if (U_FAILURE(status)) { michael@0: return 0; michael@0: } michael@0: if (localMatches != NULL) { michael@0: for (int32_t i = 0; i < localMatches->size(); i++) { michael@0: int32_t len = localMatches->getMatchLength(i); michael@0: michael@0: // TODO See the above TODO. We use len >= bestMatchLen michael@0: // because of the long standard/location name collision michael@0: // problem. If it is also a location name, carrying michael@0: // timeType = UTZFMT_TIME_TYPE_STANDARD will cause a michael@0: // problem in SimpleDateFormat michael@0: if (len >= bestMatchLen) { michael@0: bestMatchLen = localMatches->getMatchLength(i); michael@0: bestMatchTimeType = UTZFMT_TIME_TYPE_UNKNOWN; // because generic michael@0: localMatches->getTimeZoneID(i, bestMatchTzID); michael@0: } michael@0: } michael@0: delete localMatches; michael@0: } michael@0: michael@0: if (bestMatchLen > 0) { michael@0: timeType = bestMatchTimeType; michael@0: tzID.setTo(bestMatchTzID); michael@0: } michael@0: return bestMatchLen; michael@0: } michael@0: michael@0: TimeZoneGenericNameMatchInfo* michael@0: TZGNCore::findLocal(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { michael@0: GNameSearchHandler handler(types); michael@0: michael@0: TZGNCore *nonConstThis = const_cast(this); michael@0: michael@0: umtx_lock(&gLock); michael@0: { michael@0: fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); michael@0: } michael@0: umtx_unlock(&gLock); michael@0: michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: michael@0: TimeZoneGenericNameMatchInfo *gmatchInfo = NULL; michael@0: michael@0: int32_t maxLen = 0; michael@0: UVector *results = handler.getMatches(maxLen); michael@0: if (results != NULL && ((maxLen == (text.length() - start)) || fGNamesTrieFullyLoaded)) { michael@0: // perfect match michael@0: gmatchInfo = new TimeZoneGenericNameMatchInfo(results); michael@0: if (gmatchInfo == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: delete results; michael@0: return NULL; michael@0: } michael@0: return gmatchInfo; michael@0: } michael@0: michael@0: if (results != NULL) { michael@0: delete results; michael@0: } michael@0: michael@0: // All names are not yet loaded into the local trie. michael@0: // Load all available names into the trie. This could be very heavy. michael@0: umtx_lock(&gLock); michael@0: { michael@0: if (!fGNamesTrieFullyLoaded) { michael@0: StringEnumeration *tzIDs = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status); michael@0: if (U_SUCCESS(status)) { michael@0: const UnicodeString *tzID; michael@0: while ((tzID = tzIDs->snext(status))) { michael@0: if (U_FAILURE(status)) { michael@0: break; michael@0: } michael@0: nonConstThis->loadStrings(*tzID); michael@0: } michael@0: } michael@0: if (tzIDs != NULL) { michael@0: delete tzIDs; michael@0: } michael@0: michael@0: if (U_SUCCESS(status)) { michael@0: nonConstThis->fGNamesTrieFullyLoaded = TRUE; michael@0: } michael@0: } michael@0: } michael@0: umtx_unlock(&gLock); michael@0: michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: michael@0: umtx_lock(&gLock); michael@0: { michael@0: // now try it again michael@0: fGNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); michael@0: } michael@0: umtx_unlock(&gLock); michael@0: michael@0: results = handler.getMatches(maxLen); michael@0: if (results != NULL && maxLen > 0) { michael@0: gmatchInfo = new TimeZoneGenericNameMatchInfo(results); michael@0: if (gmatchInfo == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: delete results; michael@0: return NULL; michael@0: } michael@0: } michael@0: michael@0: return gmatchInfo; michael@0: } michael@0: michael@0: TimeZoneNames::MatchInfoCollection* michael@0: TZGNCore::findTimeZoneNames(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { michael@0: // Check if the target name typs is really in the TimeZoneNames michael@0: uint32_t nameTypes = 0; michael@0: if (types & UTZGNM_LONG) { michael@0: nameTypes |= (UTZNM_LONG_GENERIC | UTZNM_LONG_STANDARD); michael@0: } michael@0: if (types & UTZGNM_SHORT) { michael@0: nameTypes |= (UTZNM_SHORT_GENERIC | UTZNM_SHORT_STANDARD); michael@0: } michael@0: michael@0: if (types) { michael@0: // Find matches in the TimeZoneNames michael@0: return fTimeZoneNames->find(text, start, nameTypes, status); michael@0: } michael@0: michael@0: return NULL; michael@0: } michael@0: michael@0: typedef struct TZGNCoreRef { michael@0: TZGNCore* obj; michael@0: int32_t refCount; michael@0: double lastAccess; michael@0: } TZGNCoreRef; michael@0: michael@0: // TZGNCore object cache handling michael@0: static UMutex gTZGNLock = U_MUTEX_INITIALIZER; michael@0: static UHashtable *gTZGNCoreCache = NULL; michael@0: static UBool gTZGNCoreCacheInitialized = 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: U_CDECL_BEGIN michael@0: /** michael@0: * Cleanup callback func michael@0: */ michael@0: static UBool U_CALLCONV tzgnCore_cleanup(void) michael@0: { michael@0: if (gTZGNCoreCache != NULL) { michael@0: uhash_close(gTZGNCoreCache); michael@0: gTZGNCoreCache = NULL; michael@0: } michael@0: gTZGNCoreCacheInitialized = FALSE; michael@0: return TRUE; michael@0: } michael@0: michael@0: /** michael@0: * Deleter for TZGNCoreRef michael@0: */ michael@0: static void U_CALLCONV michael@0: deleteTZGNCoreRef(void *obj) { michael@0: icu::TZGNCoreRef *entry = (icu::TZGNCoreRef*)obj; michael@0: delete (icu::TZGNCore*) entry->obj; 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(gTZGNCoreCache, &pos))) { michael@0: TZGNCoreRef *entry = (TZGNCoreRef *)elem->value.pointer; michael@0: if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) { michael@0: // delete this entry michael@0: uhash_removeElement(gTZGNCoreCache, elem); michael@0: } michael@0: } michael@0: } michael@0: michael@0: TimeZoneGenericNames::TimeZoneGenericNames() michael@0: : fRef(0) { michael@0: } michael@0: michael@0: TimeZoneGenericNames::~TimeZoneGenericNames() { michael@0: umtx_lock(&gTZGNLock); michael@0: { michael@0: U_ASSERT(fRef->refCount > 0); michael@0: // Just decrement the reference count michael@0: fRef->refCount--; michael@0: } michael@0: umtx_unlock(&gTZGNLock); michael@0: } michael@0: michael@0: TimeZoneGenericNames* michael@0: TimeZoneGenericNames::createInstance(const Locale& locale, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: TimeZoneGenericNames* instance = new TimeZoneGenericNames(); michael@0: if (instance == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return NULL; michael@0: } michael@0: michael@0: TZGNCoreRef *cacheEntry = NULL; michael@0: { michael@0: Mutex lock(&gTZGNLock); michael@0: michael@0: if (!gTZGNCoreCacheInitialized) { michael@0: // Create empty hashtable michael@0: gTZGNCoreCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); michael@0: if (U_SUCCESS(status)) { michael@0: uhash_setKeyDeleter(gTZGNCoreCache, uprv_free); michael@0: uhash_setValueDeleter(gTZGNCoreCache, deleteTZGNCoreRef); michael@0: gTZGNCoreCacheInitialized = TRUE; michael@0: ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEGENERICNAMES, tzgnCore_cleanup); michael@0: } michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: michael@0: // Check the cache, if not available, create new one and cache michael@0: const char *key = locale.getName(); michael@0: cacheEntry = (TZGNCoreRef *)uhash_get(gTZGNCoreCache, key); michael@0: if (cacheEntry == NULL) { michael@0: TZGNCore *tzgnCore = NULL; michael@0: char *newKey = NULL; michael@0: michael@0: tzgnCore = new TZGNCore(locale, status); michael@0: if (tzgnCore == 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 = (TZGNCoreRef *)uprv_malloc(sizeof(TZGNCoreRef)); michael@0: if (cacheEntry == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: } else { michael@0: cacheEntry->obj = tzgnCore; michael@0: cacheEntry->refCount = 1; michael@0: cacheEntry->lastAccess = (double)uprv_getUTCtime(); michael@0: michael@0: uhash_put(gTZGNCoreCache, newKey, cacheEntry, &status); michael@0: } michael@0: } michael@0: if (U_FAILURE(status)) { michael@0: if (tzgnCore != NULL) { michael@0: delete tzgnCore; 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: } // End of mutex locked block michael@0: michael@0: if (cacheEntry == NULL) { michael@0: delete instance; michael@0: return NULL; michael@0: } michael@0: michael@0: instance->fRef = cacheEntry; michael@0: return instance; michael@0: } michael@0: michael@0: UBool michael@0: TimeZoneGenericNames::operator==(const TimeZoneGenericNames& other) const { michael@0: // Just compare if the other object also use the same michael@0: // ref entry michael@0: return fRef == other.fRef; michael@0: } michael@0: michael@0: TimeZoneGenericNames* michael@0: TimeZoneGenericNames::clone() const { michael@0: TimeZoneGenericNames* other = new TimeZoneGenericNames(); michael@0: if (other) { michael@0: umtx_lock(&gTZGNLock); michael@0: { michael@0: // Just increments the reference count michael@0: fRef->refCount++; michael@0: other->fRef = fRef; michael@0: } michael@0: umtx_unlock(&gTZGNLock); michael@0: } michael@0: return other; michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZoneGenericNames::getDisplayName(const TimeZone& tz, UTimeZoneGenericNameType type, michael@0: UDate date, UnicodeString& name) const { michael@0: return fRef->obj->getDisplayName(tz, type, date, name); michael@0: } michael@0: michael@0: UnicodeString& michael@0: TimeZoneGenericNames::getGenericLocationName(const UnicodeString& tzCanonicalID, UnicodeString& name) const { michael@0: return fRef->obj->getGenericLocationName(tzCanonicalID, name); michael@0: } michael@0: michael@0: int32_t michael@0: TimeZoneGenericNames::findBestMatch(const UnicodeString& text, int32_t start, uint32_t types, michael@0: UnicodeString& tzID, UTimeZoneFormatTimeType& timeType, UErrorCode& status) const { michael@0: return fRef->obj->findBestMatch(text, start, types, tzID, timeType, status); michael@0: } michael@0: michael@0: U_NAMESPACE_END michael@0: #endif