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 */
timeZoneNames_cleanup(void)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
deleteTimeZoneNamesCacheEntry(void * obj)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 */
sweepCache()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;
operator !=(const TimeZoneNames & other) const112 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
TimeZoneNamesDelegate()131 TimeZoneNamesDelegate::TimeZoneNamesDelegate()
132 : fTZnamesCacheEntry(0) {
133 }
134
TimeZoneNamesDelegate(const Locale & locale,UErrorCode & status)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
~TimeZoneNamesDelegate()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
operator ==(const TimeZoneNames & other) const235 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*
clone() const249 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*
getAvailableMetaZoneIDs(UErrorCode & status) const264 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const {
265 return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status);
266 }
267
268 StringEnumeration*
getAvailableMetaZoneIDs(const UnicodeString & tzID,UErrorCode & status) const269 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const {
270 return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status);
271 }
272
273 UnicodeString&
getMetaZoneID(const UnicodeString & tzID,UDate date,UnicodeString & mzID) const274 TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const {
275 return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID);
276 }
277
278 UnicodeString&
getReferenceZoneID(const UnicodeString & mzID,const char * region,UnicodeString & tzID) const279 TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const {
280 return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID);
281 }
282
283 UnicodeString&
getMetaZoneDisplayName(const UnicodeString & mzID,UTimeZoneNameType type,UnicodeString & name) const284 TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const {
285 return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name);
286 }
287
288 UnicodeString&
getTimeZoneDisplayName(const UnicodeString & tzID,UTimeZoneNameType type,UnicodeString & name) const289 TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const {
290 return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name);
291 }
292
293 UnicodeString&
getExemplarLocationName(const UnicodeString & tzID,UnicodeString & name) const294 TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
295 return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name);
296 }
297
298 TimeZoneNames::MatchInfoCollection*
find(const UnicodeString & text,int32_t start,uint32_t types,UErrorCode & status) const299 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 // ---------------------------------------------------
UOBJECT_DEFINE_NO_RTTI_IMPLEMENTATION(TimeZoneNames)306 UOBJECT_DEFINE_NO_RTTI_IMPLEMENTATION(TimeZoneNames)
307
308 TimeZoneNames::~TimeZoneNames() {
309 }
310
311 TimeZoneNames*
createInstance(const Locale & locale,UErrorCode & status)312 TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) {
313 return new TimeZoneNamesDelegate(locale, status);
314 }
315
316 UnicodeString&
getExemplarLocationName(const UnicodeString & tzID,UnicodeString & name) const317 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&
getDisplayName(const UnicodeString & tzID,UTimeZoneNameType type,UDate date,UnicodeString & name) const336 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
MatchInfoMatchInfo353 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
deleteMatchInfo(void * obj)368 deleteMatchInfo(void *obj) {
369 delete static_cast<MatchInfo *>(obj);
370 }
371 U_CDECL_END
372
373 // ---------------------------------------------------
374 // MatchInfoCollection class
375 // ---------------------------------------------------
MatchInfoCollection()376 TimeZoneNames::MatchInfoCollection::MatchInfoCollection()
377 : fMatches(NULL) {
378 }
379
~MatchInfoCollection()380 TimeZoneNames::MatchInfoCollection::~MatchInfoCollection() {
381 if (fMatches != NULL) {
382 delete fMatches;
383 }
384 }
385
386 void
addZone(UTimeZoneNameType nameType,int32_t matchLength,const UnicodeString & tzID,UErrorCode & status)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
addMetaZone(UTimeZoneNameType nameType,int32_t matchLength,const UnicodeString & mzID,UErrorCode & status)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
size() const421 TimeZoneNames::MatchInfoCollection::size() const {
422 if (fMatches == NULL) {
423 return 0;
424 }
425 return fMatches->size();
426 }
427
428 UTimeZoneNameType
getNameTypeAt(int32_t idx) const429 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
getMatchLengthAt(int32_t idx) const438 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
getTimeZoneIDAt(int32_t idx,UnicodeString & tzID) const447 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
getMetaZoneIDAt(int32_t idx,UnicodeString & mzID) const458 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*
matches(UErrorCode & status)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