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 = NULL;
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  */
timeZoneNames_cleanup(void)59 static UBool U_CALLCONV timeZoneNames_cleanup(void)
60 {
61     if (gTimeZoneNamesCache != NULL) {
62         uhash_close(gTimeZoneNamesCache);
63         gTimeZoneNamesCache = NULL;
64     }
65     gTimeZoneNamesCacheInitialized = FALSE;
66     return TRUE;
67 }
68 
69 /**
70  * Deleter for TimeZoneNamesCacheEntry
71  */
72 static void U_CALLCONV
deleteTimeZoneNamesCacheEntry(void * obj)73 deleteTimeZoneNamesCacheEntry(void *obj) {
74     icu::TimeZoneNamesCacheEntry *entry = (icu::TimeZoneNamesCacheEntry*)obj;
75     delete (icu::TimeZoneNamesImpl*) entry->names;
76     uprv_free(entry);
77 }
78 U_CDECL_END
79 
80 /**
81  * Function used for removing unreferrenced cache entries exceeding
82  * the expiration time. This function must be called with in the mutex
83  * block.
84  */
sweepCache()85 static void sweepCache() {
86     int32_t pos = UHASH_FIRST;
87     const UHashElement* elem;
88     double now = (double)uprv_getUTCtime();
89 
90     while ((elem = uhash_nextElement(gTimeZoneNamesCache, &pos)) != 0) {
91         TimeZoneNamesCacheEntry *entry = (TimeZoneNamesCacheEntry *)elem->value.pointer;
92         if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) {
93             // delete this entry
94             uhash_removeElement(gTimeZoneNamesCache, elem);
95         }
96     }
97 }
98 
99 // ---------------------------------------------------
100 // TimeZoneNamesDelegate
101 // ---------------------------------------------------
102 class TimeZoneNamesDelegate : public TimeZoneNames {
103 public:
104     TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status);
105     virtual ~TimeZoneNamesDelegate();
106 
107     virtual UBool operator==(const TimeZoneNames& other) const;
operator !=(const TimeZoneNames & other) const108     virtual UBool operator!=(const TimeZoneNames& other) const {return !operator==(other);}
109     virtual TimeZoneNamesDelegate* clone() const;
110 
111     StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const;
112     StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const;
113     UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const;
114     UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const;
115 
116     UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const;
117     UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const;
118 
119     UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const;
120 
121     void loadAllDisplayNames(UErrorCode& status);
122     void getDisplayNames(const UnicodeString& tzID, const UTimeZoneNameType types[], int32_t numTypes, UDate date, UnicodeString dest[], UErrorCode& status) const;
123 
124     MatchInfoCollection* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const;
125 private:
126     TimeZoneNamesDelegate();
127     TimeZoneNamesCacheEntry*    fTZnamesCacheEntry;
128 };
129 
TimeZoneNamesDelegate()130 TimeZoneNamesDelegate::TimeZoneNamesDelegate()
131 : fTZnamesCacheEntry(0) {
132 }
133 
TimeZoneNamesDelegate(const Locale & locale,UErrorCode & status)134 TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status) {
135     Mutex lock(&gTimeZoneNamesLock);
136     if (!gTimeZoneNamesCacheInitialized) {
137         // Create empty hashtable if it is not already initialized.
138         gTimeZoneNamesCache = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status);
139         if (U_SUCCESS(status)) {
140             uhash_setKeyDeleter(gTimeZoneNamesCache, uprv_free);
141             uhash_setValueDeleter(gTimeZoneNamesCache, deleteTimeZoneNamesCacheEntry);
142             gTimeZoneNamesCacheInitialized = TRUE;
143             ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONENAMES, timeZoneNames_cleanup);
144         }
145     }
146 
147     if (U_FAILURE(status)) {
148         return;
149     }
150 
151     // Check the cache, if not available, create new one and cache
152     TimeZoneNamesCacheEntry *cacheEntry = NULL;
153 
154     const char *key = locale.getName();
155     cacheEntry = (TimeZoneNamesCacheEntry *)uhash_get(gTimeZoneNamesCache, key);
156     if (cacheEntry == NULL) {
157         TimeZoneNames *tznames = NULL;
158         char *newKey = NULL;
159 
160         tznames = new TimeZoneNamesImpl(locale, status);
161         if (tznames == NULL) {
162             status = U_MEMORY_ALLOCATION_ERROR;
163         }
164         if (U_SUCCESS(status)) {
165             newKey = (char *)uprv_malloc(uprv_strlen(key) + 1);
166             if (newKey == NULL) {
167                 status = U_MEMORY_ALLOCATION_ERROR;
168             } else {
169                 uprv_strcpy(newKey, key);
170             }
171         }
172         if (U_SUCCESS(status)) {
173             cacheEntry = (TimeZoneNamesCacheEntry *)uprv_malloc(sizeof(TimeZoneNamesCacheEntry));
174             if (cacheEntry == NULL) {
175                 status = U_MEMORY_ALLOCATION_ERROR;
176             } else {
177                 cacheEntry->names = tznames;
178                 cacheEntry->refCount = 1;
179                 cacheEntry->lastAccess = (double)uprv_getUTCtime();
180 
181                 uhash_put(gTimeZoneNamesCache, newKey, cacheEntry, &status);
182             }
183         }
184         if (U_FAILURE(status)) {
185             if (tznames != NULL) {
186                 delete tznames;
187             }
188             if (newKey != NULL) {
189                 uprv_free(newKey);
190             }
191             if (cacheEntry != NULL) {
192                 uprv_free(cacheEntry);
193             }
194             cacheEntry = NULL;
195         }
196     } else {
197         // Update the reference count
198         cacheEntry->refCount++;
199         cacheEntry->lastAccess = (double)uprv_getUTCtime();
200     }
201     gAccessCount++;
202     if (gAccessCount >= SWEEP_INTERVAL) {
203         // sweep
204         sweepCache();
205         gAccessCount = 0;
206     }
207     fTZnamesCacheEntry = cacheEntry;
208 }
209 
~TimeZoneNamesDelegate()210 TimeZoneNamesDelegate::~TimeZoneNamesDelegate() {
211     umtx_lock(&gTimeZoneNamesLock);
212     {
213         if (fTZnamesCacheEntry) {
214             U_ASSERT(fTZnamesCacheEntry->refCount > 0);
215             // Just decrement the reference count
216             fTZnamesCacheEntry->refCount--;
217         }
218     }
219     umtx_unlock(&gTimeZoneNamesLock);
220 }
221 
222 UBool
operator ==(const TimeZoneNames & other) const223 TimeZoneNamesDelegate::operator==(const TimeZoneNames& other) const {
224     if (this == &other) {
225         return TRUE;
226     }
227     // Just compare if the other object also use the same
228     // cache entry
229     const TimeZoneNamesDelegate* rhs = dynamic_cast<const TimeZoneNamesDelegate*>(&other);
230     if (rhs) {
231         return fTZnamesCacheEntry == rhs->fTZnamesCacheEntry;
232     }
233     return FALSE;
234 }
235 
236 TimeZoneNamesDelegate*
clone() const237 TimeZoneNamesDelegate::clone() const {
238     TimeZoneNamesDelegate* other = new TimeZoneNamesDelegate();
239     if (other != NULL) {
240         umtx_lock(&gTimeZoneNamesLock);
241         {
242             // Just increment the reference count
243             fTZnamesCacheEntry->refCount++;
244             other->fTZnamesCacheEntry = fTZnamesCacheEntry;
245         }
246         umtx_unlock(&gTimeZoneNamesLock);
247     }
248     return other;
249 }
250 
251 StringEnumeration*
getAvailableMetaZoneIDs(UErrorCode & status) const252 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const {
253     return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status);
254 }
255 
256 StringEnumeration*
getAvailableMetaZoneIDs(const UnicodeString & tzID,UErrorCode & status) const257 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const {
258     return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status);
259 }
260 
261 UnicodeString&
getMetaZoneID(const UnicodeString & tzID,UDate date,UnicodeString & mzID) const262 TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const {
263     return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID);
264 }
265 
266 UnicodeString&
getReferenceZoneID(const UnicodeString & mzID,const char * region,UnicodeString & tzID) const267 TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const {
268     return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID);
269 }
270 
271 UnicodeString&
getMetaZoneDisplayName(const UnicodeString & mzID,UTimeZoneNameType type,UnicodeString & name) const272 TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const {
273     return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name);
274 }
275 
276 UnicodeString&
getTimeZoneDisplayName(const UnicodeString & tzID,UTimeZoneNameType type,UnicodeString & name) const277 TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const {
278     return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name);
279 }
280 
281 UnicodeString&
getExemplarLocationName(const UnicodeString & tzID,UnicodeString & name) const282 TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
283     return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name);
284 }
285 
286 void
loadAllDisplayNames(UErrorCode & status)287 TimeZoneNamesDelegate::loadAllDisplayNames(UErrorCode& status) {
288     fTZnamesCacheEntry->names->loadAllDisplayNames(status);
289 }
290 
291 void
getDisplayNames(const UnicodeString & tzID,const UTimeZoneNameType types[],int32_t numTypes,UDate date,UnicodeString dest[],UErrorCode & status) const292 TimeZoneNamesDelegate::getDisplayNames(const UnicodeString& tzID, const UTimeZoneNameType types[], int32_t numTypes, UDate date, UnicodeString dest[], UErrorCode& status) const {
293     fTZnamesCacheEntry->names->getDisplayNames(tzID, types, numTypes, date, dest, status);
294 }
295 
296 TimeZoneNames::MatchInfoCollection*
find(const UnicodeString & text,int32_t start,uint32_t types,UErrorCode & status) const297 TimeZoneNamesDelegate::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const {
298     return fTZnamesCacheEntry->names->find(text, start, types, status);
299 }
300 
301 // ---------------------------------------------------
302 // TimeZoneNames base class
303 // ---------------------------------------------------
~TimeZoneNames()304 TimeZoneNames::~TimeZoneNames() {
305 }
306 
307 TimeZoneNames*
createInstance(const Locale & locale,UErrorCode & status)308 TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) {
309     TimeZoneNames *instance = NULL;
310     if (U_SUCCESS(status)) {
311         instance = new TimeZoneNamesDelegate(locale, status);
312         if (instance == NULL && U_SUCCESS(status)) {
313             status = U_MEMORY_ALLOCATION_ERROR;
314         }
315     }
316     return instance;
317 }
318 
319 TimeZoneNames*
createTZDBInstance(const Locale & locale,UErrorCode & status)320 TimeZoneNames::createTZDBInstance(const Locale& locale, UErrorCode& status) {
321     TimeZoneNames *instance = NULL;
322     if (U_SUCCESS(status)) {
323         instance = new TZDBTimeZoneNames(locale);
324         if (instance == NULL && U_SUCCESS(status)) {
325             status = U_MEMORY_ALLOCATION_ERROR;
326         }
327     }
328     return instance;
329 }
330 
331 UnicodeString&
getExemplarLocationName(const UnicodeString & tzID,UnicodeString & name) const332 TimeZoneNames::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
333     return TimeZoneNamesImpl::getDefaultExemplarLocationName(tzID, name);
334 }
335 
336 UnicodeString&
getDisplayName(const UnicodeString & tzID,UTimeZoneNameType type,UDate date,UnicodeString & name) const337 TimeZoneNames::getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const {
338     getTimeZoneDisplayName(tzID, type, name);
339     if (name.isEmpty()) {
340         UChar mzIDBuf[32];
341         UnicodeString mzID(mzIDBuf, 0, UPRV_LENGTHOF(mzIDBuf));
342         getMetaZoneID(tzID, date, mzID);
343         getMetaZoneDisplayName(mzID, type, name);
344     }
345     return name;
346 }
347 
348 // Empty default implementation, to be overriden in tznames_impl.cpp.
349 void
loadAllDisplayNames(UErrorCode &)350 TimeZoneNames::loadAllDisplayNames(UErrorCode& /*status*/) {
351 }
352 
353 // A default, lightweight implementation of getDisplayNames.
354 // Overridden in tznames_impl.cpp.
355 void
getDisplayNames(const UnicodeString & tzID,const UTimeZoneNameType types[],int32_t numTypes,UDate date,UnicodeString dest[],UErrorCode & status) const356 TimeZoneNames::getDisplayNames(const UnicodeString& tzID, const UTimeZoneNameType types[], int32_t numTypes, UDate date, UnicodeString dest[], UErrorCode& status) const {
357     if (U_FAILURE(status)) { return; }
358     if (tzID.isEmpty()) { return; }
359     UnicodeString mzID;
360     for (int i = 0; i < numTypes; i++) {
361         getTimeZoneDisplayName(tzID, types[i], dest[i]);
362         if (dest[i].isEmpty()) {
363             if (mzID.isEmpty()) {
364                 getMetaZoneID(tzID, date, mzID);
365             }
366             getMetaZoneDisplayName(mzID, types[i], dest[i]);
367         }
368     }
369 }
370 
371 
372 struct MatchInfo : UMemory {
373     UTimeZoneNameType nameType;
374     UnicodeString id;
375     int32_t matchLength;
376     UBool isTZID;
377 
MatchInfoMatchInfo378     MatchInfo(UTimeZoneNameType nameType, int32_t matchLength, const UnicodeString* tzID, const UnicodeString* mzID) {
379         this->nameType = nameType;
380         this->matchLength = matchLength;
381         if (tzID != NULL) {
382             this->id.setTo(*tzID);
383             this->isTZID = TRUE;
384         } else {
385             this->id.setTo(*mzID);
386             this->isTZID = FALSE;
387         }
388     }
389 };
390 
391 U_CDECL_BEGIN
392 static void U_CALLCONV
deleteMatchInfo(void * obj)393 deleteMatchInfo(void *obj) {
394     delete static_cast<MatchInfo *>(obj);
395 }
396 U_CDECL_END
397 
398 // ---------------------------------------------------
399 // MatchInfoCollection class
400 // ---------------------------------------------------
MatchInfoCollection()401 TimeZoneNames::MatchInfoCollection::MatchInfoCollection()
402 : fMatches(NULL) {
403 }
404 
~MatchInfoCollection()405 TimeZoneNames::MatchInfoCollection::~MatchInfoCollection() {
406     if (fMatches != NULL) {
407         delete fMatches;
408     }
409 }
410 
411 void
addZone(UTimeZoneNameType nameType,int32_t matchLength,const UnicodeString & tzID,UErrorCode & status)412 TimeZoneNames::MatchInfoCollection::addZone(UTimeZoneNameType nameType, int32_t matchLength,
413             const UnicodeString& tzID, UErrorCode& status) {
414     if (U_FAILURE(status)) {
415         return;
416     }
417     MatchInfo* matchInfo = new MatchInfo(nameType, matchLength, &tzID, NULL);
418     if (matchInfo == NULL) {
419         status = U_MEMORY_ALLOCATION_ERROR;
420         return;
421     }
422     matches(status)->addElement(matchInfo, status);
423     if (U_FAILURE(status)) {
424         delete matchInfo;
425     }
426 }
427 
428 void
addMetaZone(UTimeZoneNameType nameType,int32_t matchLength,const UnicodeString & mzID,UErrorCode & status)429 TimeZoneNames::MatchInfoCollection::addMetaZone(UTimeZoneNameType nameType, int32_t matchLength,
430             const UnicodeString& mzID, UErrorCode& status) {
431     if (U_FAILURE(status)) {
432         return;
433     }
434     MatchInfo* matchInfo = new MatchInfo(nameType, matchLength, NULL, &mzID);
435     if (matchInfo == NULL) {
436         status = U_MEMORY_ALLOCATION_ERROR;
437         return;
438     }
439     matches(status)->addElement(matchInfo, status);
440     if (U_FAILURE(status)) {
441         delete matchInfo;
442     }
443 }
444 
445 int32_t
size() const446 TimeZoneNames::MatchInfoCollection::size() const {
447     if (fMatches == NULL) {
448         return 0;
449     }
450     return fMatches->size();
451 }
452 
453 UTimeZoneNameType
getNameTypeAt(int32_t idx) const454 TimeZoneNames::MatchInfoCollection::getNameTypeAt(int32_t idx) const {
455     const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
456     if (match) {
457         return match->nameType;
458     }
459     return UTZNM_UNKNOWN;
460 }
461 
462 int32_t
getMatchLengthAt(int32_t idx) const463 TimeZoneNames::MatchInfoCollection::getMatchLengthAt(int32_t idx) const {
464     const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx);
465     if (match) {
466         return match->matchLength;
467     }
468     return 0;
469 }
470 
471 UBool
getTimeZoneIDAt(int32_t idx,UnicodeString & tzID) const472 TimeZoneNames::MatchInfoCollection::getTimeZoneIDAt(int32_t idx, UnicodeString& tzID) const {
473     tzID.remove();
474     const MatchInfo* match = (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
getMetaZoneIDAt(int32_t idx,UnicodeString & mzID) const483 TimeZoneNames::MatchInfoCollection::getMetaZoneIDAt(int32_t idx, UnicodeString& mzID) const {
484     mzID.remove();
485     const MatchInfo* match = (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*
matches(UErrorCode & status)494 TimeZoneNames::MatchInfoCollection::matches(UErrorCode& status) {
495     if (U_FAILURE(status)) {
496         return NULL;
497     }
498     if (fMatches != NULL) {
499         return fMatches;
500     }
501     fMatches = new UVector(deleteMatchInfo, NULL, status);
502     if (fMatches == NULL) {
503         status = U_MEMORY_ALLOCATION_ERROR;
504     } else if (U_FAILURE(status)) {
505         delete fMatches;
506         fMatches = NULL;
507     }
508     return fMatches;
509 }
510 
511 
512 U_NAMESPACE_END
513 #endif
514