tznames.cpp (16966B)
1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2011-2015, International Business Machines Corporation and * 6 * others. All Rights Reserved. * 7 ******************************************************************************* 8 */ 9 10 #include "unicode/utypes.h" 11 12 #if !UCONFIG_NO_FORMATTING 13 14 #include "unicode/locid.h" 15 #include "unicode/tznames.h" 16 #include "unicode/uenum.h" 17 #include "cmemory.h" 18 #include "cstring.h" 19 #include "mutex.h" 20 #include "putilimp.h" 21 #include "tznames_impl.h" 22 #include "uassert.h" 23 #include "ucln_in.h" 24 #include "uhash.h" 25 #include "umutex.h" 26 #include "uvector.h" 27 28 29 U_NAMESPACE_BEGIN 30 31 // TimeZoneNames object cache handling 32 static UMutex gTimeZoneNamesLock; 33 static UHashtable *gTimeZoneNamesCache = nullptr; 34 static UBool gTimeZoneNamesCacheInitialized = false; 35 36 // Access count - incremented every time up to SWEEP_INTERVAL, 37 // then reset to 0 38 static int32_t gAccessCount = 0; 39 40 // Interval for calling the cache sweep function - every 100 times 41 #define SWEEP_INTERVAL 100 42 43 // Cache expiration in millisecond. When a cached entry is no 44 // longer referenced and exceeding this threshold since last 45 // access time, then the cache entry will be deleted by the sweep 46 // function. For now, 3 minutes. 47 #define CACHE_EXPIRATION 180000.0 48 49 typedef struct TimeZoneNamesCacheEntry { 50 TimeZoneNames* names; 51 int32_t refCount; 52 double lastAccess; 53 } TimeZoneNamesCacheEntry; 54 55 U_CDECL_BEGIN 56 /** 57 * Cleanup callback func 58 */ 59 static UBool U_CALLCONV timeZoneNames_cleanup() 60 { 61 if (gTimeZoneNamesCache != nullptr) { 62 uhash_close(gTimeZoneNamesCache); 63 gTimeZoneNamesCache = nullptr; 64 } 65 gTimeZoneNamesCacheInitialized = false; 66 return true; 67 } 68 69 /** 70 * Deleter for TimeZoneNamesCacheEntry 71 */ 72 static void U_CALLCONV 73 deleteTimeZoneNamesCacheEntry(void *obj) { 74 icu::TimeZoneNamesCacheEntry *entry = (icu::TimeZoneNamesCacheEntry*)obj; 75 if (entry->refCount <= 1) { 76 delete (icu::TimeZoneNamesImpl*) entry->names; 77 uprv_free(entry); 78 } else { 79 entry->refCount--; 80 } 81 } 82 U_CDECL_END 83 84 /** 85 * Function used for removing unreferrenced cache entries exceeding 86 * the expiration time. This function must be called with in the mutex 87 * block. 88 */ 89 static void sweepCache() { 90 int32_t pos = UHASH_FIRST; 91 const UHashElement* elem; 92 double now = static_cast<double>(uprv_getUTCtime()); 93 94 while ((elem = uhash_nextElement(gTimeZoneNamesCache, &pos)) != nullptr) { 95 TimeZoneNamesCacheEntry* entry = static_cast<TimeZoneNamesCacheEntry*>(elem->value.pointer); 96 if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) { 97 // delete this entry 98 uhash_removeElement(gTimeZoneNamesCache, elem); 99 } 100 } 101 } 102 103 // --------------------------------------------------- 104 // TimeZoneNamesDelegate 105 // --------------------------------------------------- 106 class TimeZoneNamesDelegate : public TimeZoneNames { 107 public: 108 TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status); 109 virtual ~TimeZoneNamesDelegate(); 110 111 virtual bool operator==(const TimeZoneNames& other) const override; 112 virtual bool operator!=(const TimeZoneNames& other) const {return !operator==(other);} 113 virtual TimeZoneNamesDelegate* clone() const override; 114 115 StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const override; 116 StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const override; 117 UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const override; 118 UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const override; 119 120 UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const override; 121 UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const override; 122 123 UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const override; 124 125 void loadAllDisplayNames(UErrorCode& status) override; 126 void getDisplayNames(const UnicodeString& tzID, const UTimeZoneNameType types[], int32_t numTypes, UDate date, UnicodeString dest[], UErrorCode& status) const override; 127 128 MatchInfoCollection* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const override; 129 private: 130 TimeZoneNamesDelegate(); 131 TimeZoneNamesCacheEntry* fTZnamesCacheEntry; 132 }; 133 134 TimeZoneNamesDelegate::TimeZoneNamesDelegate() 135 : fTZnamesCacheEntry(nullptr) { 136 } 137 138 TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status) { 139 Mutex lock(&gTimeZoneNamesLock); 140 if (!gTimeZoneNamesCacheInitialized) { 141 // Create empty hashtable if it is not already initialized. 142 gTimeZoneNamesCache = uhash_open(uhash_hashChars, uhash_compareChars, nullptr, &status); 143 if (U_SUCCESS(status)) { 144 uhash_setKeyDeleter(gTimeZoneNamesCache, uprv_free); 145 uhash_setValueDeleter(gTimeZoneNamesCache, deleteTimeZoneNamesCacheEntry); 146 gTimeZoneNamesCacheInitialized = true; 147 ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONENAMES, timeZoneNames_cleanup); 148 } 149 } 150 151 if (U_FAILURE(status)) { 152 return; 153 } 154 155 // Check the cache, if not available, create new one and cache 156 TimeZoneNamesCacheEntry *cacheEntry = nullptr; 157 158 const char *key = locale.getName(); 159 cacheEntry = static_cast<TimeZoneNamesCacheEntry*>(uhash_get(gTimeZoneNamesCache, key)); 160 if (cacheEntry == nullptr) { 161 TimeZoneNames *tznames = nullptr; 162 char *newKey = nullptr; 163 164 tznames = new TimeZoneNamesImpl(locale, status); 165 if (tznames == nullptr) { 166 status = U_MEMORY_ALLOCATION_ERROR; 167 } 168 if (U_SUCCESS(status)) { 169 newKey = static_cast<char*>(uprv_malloc(uprv_strlen(key) + 1)); 170 if (newKey == nullptr) { 171 status = U_MEMORY_ALLOCATION_ERROR; 172 } else { 173 uprv_strcpy(newKey, key); 174 } 175 } 176 if (U_SUCCESS(status)) { 177 cacheEntry = static_cast<TimeZoneNamesCacheEntry*>(uprv_malloc(sizeof(TimeZoneNamesCacheEntry))); 178 if (cacheEntry == nullptr) { 179 status = U_MEMORY_ALLOCATION_ERROR; 180 } else { 181 cacheEntry->names = tznames; 182 // The initial refCount is 2 because the entry is referenced both 183 // by this TimeZoneDelegate and by the gTimeZoneNamesCache 184 cacheEntry->refCount = 2; 185 cacheEntry->lastAccess = static_cast<double>(uprv_getUTCtime()); 186 187 uhash_put(gTimeZoneNamesCache, newKey, cacheEntry, &status); 188 } 189 } 190 if (U_FAILURE(status)) { 191 delete tznames; 192 if (newKey != nullptr) { 193 uprv_free(newKey); 194 } 195 if (cacheEntry != nullptr) { 196 uprv_free(cacheEntry); 197 } 198 cacheEntry = nullptr; 199 } 200 } else { 201 // Update the reference count 202 cacheEntry->refCount++; 203 cacheEntry->lastAccess = static_cast<double>(uprv_getUTCtime()); 204 } 205 gAccessCount++; 206 if (gAccessCount >= SWEEP_INTERVAL) { 207 // sweep 208 sweepCache(); 209 gAccessCount = 0; 210 } 211 fTZnamesCacheEntry = cacheEntry; 212 } 213 214 TimeZoneNamesDelegate::~TimeZoneNamesDelegate() { 215 umtx_lock(&gTimeZoneNamesLock); 216 { 217 if (fTZnamesCacheEntry) { 218 if (fTZnamesCacheEntry->refCount <= 1) { 219 delete fTZnamesCacheEntry->names; 220 uprv_free(fTZnamesCacheEntry); 221 } else { 222 // Just decrement the reference count 223 fTZnamesCacheEntry->refCount--; 224 } 225 } 226 } 227 umtx_unlock(&gTimeZoneNamesLock); 228 } 229 230 bool 231 TimeZoneNamesDelegate::operator==(const TimeZoneNames& other) const { 232 if (this == &other) { 233 return true; 234 } 235 // Just compare if the other object also use the same 236 // cache entry 237 const TimeZoneNamesDelegate* rhs = dynamic_cast<const TimeZoneNamesDelegate*>(&other); 238 if (rhs) { 239 return fTZnamesCacheEntry == rhs->fTZnamesCacheEntry; 240 } 241 return false; 242 } 243 244 TimeZoneNamesDelegate* 245 TimeZoneNamesDelegate::clone() const { 246 TimeZoneNamesDelegate* other = new TimeZoneNamesDelegate(); 247 if (other != nullptr) { 248 umtx_lock(&gTimeZoneNamesLock); 249 { 250 // Just increment the reference count 251 fTZnamesCacheEntry->refCount++; 252 other->fTZnamesCacheEntry = fTZnamesCacheEntry; 253 } 254 umtx_unlock(&gTimeZoneNamesLock); 255 } 256 return other; 257 } 258 259 StringEnumeration* 260 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const { 261 return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status); 262 } 263 264 StringEnumeration* 265 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const { 266 return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status); 267 } 268 269 UnicodeString& 270 TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const { 271 return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID); 272 } 273 274 UnicodeString& 275 TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const { 276 return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID); 277 } 278 279 UnicodeString& 280 TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const { 281 return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name); 282 } 283 284 UnicodeString& 285 TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const { 286 return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name); 287 } 288 289 UnicodeString& 290 TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const { 291 return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name); 292 } 293 294 void 295 TimeZoneNamesDelegate::loadAllDisplayNames(UErrorCode& status) { 296 fTZnamesCacheEntry->names->loadAllDisplayNames(status); 297 } 298 299 void 300 TimeZoneNamesDelegate::getDisplayNames(const UnicodeString& tzID, const UTimeZoneNameType types[], int32_t numTypes, UDate date, UnicodeString dest[], UErrorCode& status) const { 301 fTZnamesCacheEntry->names->getDisplayNames(tzID, types, numTypes, date, dest, status); 302 } 303 304 TimeZoneNames::MatchInfoCollection* 305 TimeZoneNamesDelegate::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { 306 return fTZnamesCacheEntry->names->find(text, start, types, status); 307 } 308 309 // --------------------------------------------------- 310 // TimeZoneNames base class 311 // --------------------------------------------------- 312 TimeZoneNames::~TimeZoneNames() { 313 } 314 315 TimeZoneNames* 316 TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) { 317 TimeZoneNames *instance = nullptr; 318 if (U_SUCCESS(status)) { 319 instance = new TimeZoneNamesDelegate(locale, status); 320 if (instance == nullptr && U_SUCCESS(status)) { 321 status = U_MEMORY_ALLOCATION_ERROR; 322 } 323 } 324 return instance; 325 } 326 327 TimeZoneNames* 328 TimeZoneNames::createTZDBInstance(const Locale& locale, UErrorCode& status) { 329 TimeZoneNames *instance = nullptr; 330 if (U_SUCCESS(status)) { 331 instance = new TZDBTimeZoneNames(locale); 332 if (instance == nullptr && U_SUCCESS(status)) { 333 status = U_MEMORY_ALLOCATION_ERROR; 334 } 335 } 336 return instance; 337 } 338 339 UnicodeString& 340 TimeZoneNames::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const { 341 return TimeZoneNamesImpl::getDefaultExemplarLocationName(tzID, name); 342 } 343 344 UnicodeString& 345 TimeZoneNames::getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const { 346 getTimeZoneDisplayName(tzID, type, name); 347 if (name.isEmpty()) { 348 char16_t mzIDBuf[32]; 349 UnicodeString mzID(mzIDBuf, 0, UPRV_LENGTHOF(mzIDBuf)); 350 getMetaZoneID(tzID, date, mzID); 351 getMetaZoneDisplayName(mzID, type, name); 352 } 353 return name; 354 } 355 356 // Empty default implementation, to be overridden in tznames_impl.cpp. 357 void 358 TimeZoneNames::loadAllDisplayNames(UErrorCode& /*status*/) { 359 } 360 361 // A default, lightweight implementation of getDisplayNames. 362 // Overridden in tznames_impl.cpp. 363 void 364 TimeZoneNames::getDisplayNames(const UnicodeString& tzID, const UTimeZoneNameType types[], int32_t numTypes, UDate date, UnicodeString dest[], UErrorCode& status) const { 365 if (U_FAILURE(status)) { return; } 366 if (tzID.isEmpty()) { return; } 367 UnicodeString mzID; 368 for (int i = 0; i < numTypes; i++) { 369 getTimeZoneDisplayName(tzID, types[i], dest[i]); 370 if (dest[i].isEmpty()) { 371 if (mzID.isEmpty()) { 372 getMetaZoneID(tzID, date, mzID); 373 } 374 getMetaZoneDisplayName(mzID, types[i], dest[i]); 375 } 376 } 377 } 378 379 380 struct MatchInfo : UMemory { 381 UTimeZoneNameType nameType; 382 UnicodeString id; 383 int32_t matchLength; 384 UBool isTZID; 385 386 MatchInfo(UTimeZoneNameType nameType, int32_t matchLength, const UnicodeString* tzID, const UnicodeString* mzID) { 387 this->nameType = nameType; 388 this->matchLength = matchLength; 389 if (tzID != nullptr) { 390 this->id.setTo(*tzID); 391 this->isTZID = true; 392 } else { 393 this->id.setTo(*mzID); 394 this->isTZID = false; 395 } 396 } 397 }; 398 399 U_CDECL_BEGIN 400 static void U_CALLCONV 401 deleteMatchInfo(void *obj) { 402 delete static_cast<MatchInfo *>(obj); 403 } 404 U_CDECL_END 405 406 // --------------------------------------------------- 407 // MatchInfoCollection class 408 // --------------------------------------------------- 409 TimeZoneNames::MatchInfoCollection::MatchInfoCollection() 410 : fMatches(nullptr) { 411 } 412 413 TimeZoneNames::MatchInfoCollection::~MatchInfoCollection() { 414 delete fMatches; 415 } 416 417 void 418 TimeZoneNames::MatchInfoCollection::addZone(UTimeZoneNameType nameType, int32_t matchLength, 419 const UnicodeString& tzID, UErrorCode& status) { 420 if (U_FAILURE(status)) { 421 return; 422 } 423 LocalPointer <MatchInfo> matchInfo(new MatchInfo(nameType, matchLength, &tzID, nullptr), status); 424 UVector *matchesVec = matches(status); 425 if (U_FAILURE(status)) { 426 return; 427 } 428 matchesVec->adoptElement(matchInfo.orphan(), status); 429 } 430 431 void 432 TimeZoneNames::MatchInfoCollection::addMetaZone(UTimeZoneNameType nameType, int32_t matchLength, 433 const UnicodeString& mzID, UErrorCode& status) { 434 if (U_FAILURE(status)) { 435 return; 436 } 437 LocalPointer<MatchInfo> matchInfo(new MatchInfo(nameType, matchLength, nullptr, &mzID), status); 438 UVector *matchesVec = matches(status); 439 if (U_FAILURE(status)) { 440 return; 441 } 442 matchesVec->adoptElement(matchInfo.orphan(), status); 443 } 444 445 int32_t 446 TimeZoneNames::MatchInfoCollection::size() const { 447 if (fMatches == nullptr) { 448 return 0; 449 } 450 return fMatches->size(); 451 } 452 453 UTimeZoneNameType 454 TimeZoneNames::MatchInfoCollection::getNameTypeAt(int32_t idx) const { 455 const MatchInfo* match = static_cast<const MatchInfo*>(fMatches->elementAt(idx)); 456 if (match) { 457 return match->nameType; 458 } 459 return UTZNM_UNKNOWN; 460 } 461 462 int32_t 463 TimeZoneNames::MatchInfoCollection::getMatchLengthAt(int32_t idx) const { 464 const MatchInfo* match = static_cast<const MatchInfo*>(fMatches->elementAt(idx)); 465 if (match) { 466 return match->matchLength; 467 } 468 return 0; 469 } 470 471 UBool 472 TimeZoneNames::MatchInfoCollection::getTimeZoneIDAt(int32_t idx, UnicodeString& tzID) const { 473 tzID.remove(); 474 const MatchInfo* match = static_cast<const MatchInfo*>(fMatches->elementAt(idx)); 475 if (match && match->isTZID) { 476 tzID.setTo(match->id); 477 return true; 478 } 479 return false; 480 } 481 482 UBool 483 TimeZoneNames::MatchInfoCollection::getMetaZoneIDAt(int32_t idx, UnicodeString& mzID) const { 484 mzID.remove(); 485 const MatchInfo* match = static_cast<const MatchInfo*>(fMatches->elementAt(idx)); 486 if (match && !match->isTZID) { 487 mzID.setTo(match->id); 488 return true; 489 } 490 return false; 491 } 492 493 UVector* 494 TimeZoneNames::MatchInfoCollection::matches(UErrorCode& status) { 495 if (U_FAILURE(status)) { 496 return nullptr; 497 } 498 if (fMatches != nullptr) { 499 return fMatches; 500 } 501 fMatches = new UVector(deleteMatchInfo, nullptr, status); 502 if (fMatches == nullptr) { 503 status = U_MEMORY_ALLOCATION_ERROR; 504 } else if (U_FAILURE(status)) { 505 delete fMatches; 506 fMatches = nullptr; 507 } 508 return fMatches; 509 } 510 511 512 U_NAMESPACE_END 513 #endif