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