1 /* 2 ******************************************************************************* 3 * Copyright (C) 2011, 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 "tznames.h" 13 #include "tznames_impl.h" 14 15 #include "unicode/locid.h" 16 #include "unicode/uenum.h" 17 #include "cmemory.h" 18 #include "cstring.h" 19 #include "putilimp.h" 20 #include "uassert.h" 21 #include "ucln_in.h" 22 #include "uhash.h" 23 #include "umutex.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 UMTX gTimeZoneNamesLock = NULL; 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 umtx_destroy(&gTimeZoneNamesLock); 66 67 if (gTimeZoneNamesCache != NULL) { 68 uhash_close(gTimeZoneNamesCache); 69 gTimeZoneNamesCache = NULL; 70 } 71 gTimeZoneNamesCacheInitialized = FALSE; 72 return TRUE; 73 } 74 75 /** 76 * Deleter for TimeZoneNamesCacheEntry 77 */ 78 static void U_CALLCONV 79 deleteTimeZoneNamesCacheEntry(void *obj) { 80 icu::TimeZoneNamesCacheEntry *entry = (icu::TimeZoneNamesCacheEntry*)obj; 81 delete (icu::TimeZoneNamesImpl*) entry->names; 82 uprv_free(entry); 83 } 84 U_CDECL_END 85 86 /** 87 * Function used for removing unreferrenced cache entries exceeding 88 * the expiration time. This function must be called with in the mutex 89 * block. 90 */ 91 static void sweepCache() { 92 int32_t pos = -1; 93 const UHashElement* elem; 94 double now = (double)uprv_getUTCtime(); 95 96 while ((elem = uhash_nextElement(gTimeZoneNamesCache, &pos))) { 97 TimeZoneNamesCacheEntry *entry = (TimeZoneNamesCacheEntry *)elem->value.pointer; 98 if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) { 99 // delete this entry 100 uhash_removeElement(gTimeZoneNamesCache, elem); 101 } 102 } 103 } 104 105 class TimeZoneNamesDelegate : public TimeZoneNames { 106 public: 107 TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status); 108 virtual ~TimeZoneNamesDelegate(); 109 110 StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const; 111 StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const; 112 UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const; 113 UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const; 114 115 UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const; 116 UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const; 117 118 UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const; 119 120 TimeZoneNameMatchInfo* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const; 121 private: 122 TimeZoneNamesCacheEntry* fTZnamesCacheEntry; 123 }; 124 125 TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status) { 126 UBool initialized; 127 UMTX_CHECK(&gTimeZoneNamesLock, gTimeZoneNamesCacheInitialized, initialized); 128 if (!initialized) { 129 // Create empty hashtable 130 umtx_lock(&gTimeZoneNamesLock); 131 { 132 if (!gTimeZoneNamesCacheInitialized) { 133 gTimeZoneNamesCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); 134 if (U_SUCCESS(status)) { 135 uhash_setKeyDeleter(gTimeZoneNamesCache, uprv_free); 136 uhash_setValueDeleter(gTimeZoneNamesCache, deleteTimeZoneNamesCacheEntry); 137 gTimeZoneNamesCacheInitialized = TRUE; 138 ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONENAMES, timeZoneNames_cleanup); 139 } 140 } 141 } 142 umtx_unlock(&gTimeZoneNamesLock); 143 144 if (U_FAILURE(status)) { 145 return; 146 } 147 } 148 149 // Check the cache, if not available, create new one and cache 150 TimeZoneNamesCacheEntry *cacheEntry = NULL; 151 umtx_lock(&gTimeZoneNamesLock); 152 { 153 const char *key = locale.getName(); 154 cacheEntry = (TimeZoneNamesCacheEntry *)uhash_get(gTimeZoneNamesCache, key); 155 if (cacheEntry == NULL) { 156 TimeZoneNames *tznames = NULL; 157 char *newKey = NULL; 158 159 tznames = new TimeZoneNamesImpl(locale, status); 160 if (tznames == NULL) { 161 status = U_MEMORY_ALLOCATION_ERROR; 162 } 163 if (U_SUCCESS(status)) { 164 newKey = (char *)uprv_malloc(uprv_strlen(key) + 1); 165 if (newKey == NULL) { 166 status = U_MEMORY_ALLOCATION_ERROR; 167 } else { 168 uprv_strcpy(newKey, key); 169 } 170 } 171 if (U_SUCCESS(status)) { 172 cacheEntry = (TimeZoneNamesCacheEntry *)uprv_malloc(sizeof(TimeZoneNamesCacheEntry)); 173 if (cacheEntry == NULL) { 174 status = U_MEMORY_ALLOCATION_ERROR; 175 } else { 176 cacheEntry->names = tznames; 177 cacheEntry->refCount = 1; 178 cacheEntry->lastAccess = (double)uprv_getUTCtime(); 179 180 uhash_put(gTimeZoneNamesCache, newKey, cacheEntry, &status); 181 } 182 } 183 if (U_FAILURE(status)) { 184 if (tznames != NULL) { 185 delete tznames; 186 } 187 if (newKey != NULL) { 188 uprv_free(newKey); 189 } 190 if (cacheEntry != NULL) { 191 uprv_free(cacheEntry); 192 } 193 cacheEntry = NULL; 194 } 195 } else { 196 // Update the reference count 197 cacheEntry->refCount++; 198 cacheEntry->lastAccess = (double)uprv_getUTCtime(); 199 } 200 gAccessCount++; 201 if (gAccessCount >= SWEEP_INTERVAL) { 202 // sweep 203 sweepCache(); 204 gAccessCount = 0; 205 } 206 } 207 umtx_unlock(&gTimeZoneNamesLock); 208 209 fTZnamesCacheEntry = cacheEntry; 210 } 211 212 TimeZoneNamesDelegate::~TimeZoneNamesDelegate() { 213 umtx_lock(&gTimeZoneNamesLock); 214 { 215 U_ASSERT(fTZnamesCacheEntry->refCount > 0); 216 // Just decrement the reference count 217 fTZnamesCacheEntry->refCount--; 218 } 219 umtx_unlock(&gTimeZoneNamesLock); 220 } 221 222 StringEnumeration* 223 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const { 224 return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status); 225 } 226 227 StringEnumeration* 228 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const { 229 return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status); 230 } 231 232 UnicodeString& 233 TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const { 234 return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID); 235 } 236 237 UnicodeString& 238 TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const { 239 return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID); 240 } 241 242 UnicodeString& 243 TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const { 244 return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name); 245 } 246 247 UnicodeString& 248 TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const { 249 return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name); 250 } 251 252 UnicodeString& 253 TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const { 254 return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name); 255 } 256 257 TimeZoneNameMatchInfo* 258 TimeZoneNamesDelegate::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { 259 return fTZnamesCacheEntry->names->find(text, start, types, status); 260 } 261 262 263 264 TimeZoneNames* 265 TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) { 266 return new TimeZoneNamesDelegate(locale, status); 267 } 268 269 UnicodeString& 270 TimeZoneNames::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const { 271 if (tzID.isEmpty() || tzID.startsWith(gEtcPrefix, gEtcPrefixLen) 272 || tzID.startsWith(gSystemVPrefix, gSystemVPrefixLen) || tzID.indexOf(gRiyadh8, gRiyadh8Len, 0) > 0) { 273 name.setToBogus(); 274 return name; 275 } 276 277 int32_t sep = tzID.lastIndexOf((UChar)0x2F /* '/' */); 278 if (sep > 0 && sep + 1 < tzID.length()) { 279 name.setTo(tzID, sep + 1); 280 name.findAndReplace(UnicodeString((UChar)0x5f /* _ */), 281 UnicodeString((UChar)0x20 /* space */)); 282 } else { 283 name.setToBogus(); 284 } 285 return name; 286 } 287 288 UnicodeString& 289 TimeZoneNames::getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const { 290 getTimeZoneDisplayName(tzID, type, name); 291 if (name.isEmpty()) { 292 UnicodeString mzID; 293 getMetaZoneID(tzID, date, mzID); 294 getMetaZoneDisplayName(mzID, type, name); 295 } 296 return name; 297 } 298 299 U_NAMESPACE_END 300 #endif 301