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
29U_NAMESPACE_BEGIN
30
31// TimeZoneNames object cache handling
32static UMutex gTimeZoneNamesLock;
33static UHashtable *gTimeZoneNamesCache = NULL;
34static UBool gTimeZoneNamesCacheInitialized = FALSE;
35
36// Access count - incremented every time up to SWEEP_INTERVAL,
37// then reset to 0
38static 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
49typedef struct TimeZoneNamesCacheEntry {
50 TimeZoneNames* names;
51 int32_t refCount;
52 double lastAccess;
53} TimeZoneNamesCacheEntry;
54
55U_CDECL_BEGIN
56/**
57 * Cleanup callback func
58 */
59static 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 */
72static void U_CALLCONV
73deleteTimeZoneNamesCacheEntry(void *obj) {
74 icu::TimeZoneNamesCacheEntry *entry = (icu::TimeZoneNamesCacheEntry*)obj;
75 delete (icu::TimeZoneNamesImpl*) entry->names;
76 uprv_free(entry);
77}
78U_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 */
85static 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// ---------------------------------------------------
102class TimeZoneNamesDelegate : public TimeZoneNames {
103public:
104 TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status);
105 virtual ~TimeZoneNamesDelegate();
106
107 virtual UBool operator==(const TimeZoneNames& other) const;
108 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;
125private:
126 TimeZoneNamesDelegate();
127 TimeZoneNamesCacheEntry* fTZnamesCacheEntry;
128};
129
130TimeZoneNamesDelegate::TimeZoneNamesDelegate()
131: fTZnamesCacheEntry(0) {
132}
133
134TimeZoneNamesDelegate::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
210TimeZoneNamesDelegate::~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
222UBool
223TimeZoneNamesDelegate::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
236TimeZoneNamesDelegate*
237TimeZoneNamesDelegate::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
251StringEnumeration*
252TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const {
253 return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status);
254}
255
256StringEnumeration*
257TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const {
258 return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status);
259}
260
261UnicodeString&
262TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const {
263 return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID);
264}
265
266UnicodeString&
267TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const {
268 return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID);
269}
270
271UnicodeString&
272TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const {
273 return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name);
274}
275
276UnicodeString&
277TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const {
278 return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name);
279}
280
281UnicodeString&
282TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
283 return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name);
284}
285
286void
287TimeZoneNamesDelegate::loadAllDisplayNames(UErrorCode& status) {
288 fTZnamesCacheEntry->names->loadAllDisplayNames(status);
289}
290
291void
292TimeZoneNamesDelegate::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
296TimeZoneNames::MatchInfoCollection*
297TimeZoneNamesDelegate::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// ---------------------------------------------------
304TimeZoneNames::~TimeZoneNames() {
305}
306
307TimeZoneNames*
308TimeZoneNames::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
319TimeZoneNames*
320TimeZoneNames::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
331UnicodeString&
332TimeZoneNames::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const {
333 return TimeZoneNamesImpl::getDefaultExemplarLocationName(tzID, name);
334}
335
336UnicodeString&
337TimeZoneNames::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.
349void
350TimeZoneNames::loadAllDisplayNames(UErrorCode& /*status*/) {
351}
352
353// A default, lightweight implementation of getDisplayNames.
354// Overridden in tznames_impl.cpp.
355void
356TimeZoneNames::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
372struct MatchInfo : UMemory {
373 UTimeZoneNameType nameType;
374 UnicodeString id;
375 int32_t matchLength;
376 UBool isTZID;
377
378 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
391U_CDECL_BEGIN
392static void U_CALLCONV
393deleteMatchInfo(void *obj) {
394 delete static_cast<MatchInfo *>(obj);
395}
396U_CDECL_END
397
398// ---------------------------------------------------
399// MatchInfoCollection class
400// ---------------------------------------------------
401TimeZoneNames::MatchInfoCollection::MatchInfoCollection()
402: fMatches(NULL) {
403}
404
405TimeZoneNames::MatchInfoCollection::~MatchInfoCollection() {
406 if (fMatches != NULL) {
407 delete fMatches;
408 }
409}
410
411void
412TimeZoneNames::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
428void
429TimeZoneNames::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
445int32_t
446TimeZoneNames::MatchInfoCollection::size() const {
447 if (fMatches == NULL) {
448 return 0;
449 }
450 return fMatches->size();
451}
452
453UTimeZoneNameType
454TimeZoneNames::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
462int32_t
463TimeZoneNames::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
471UBool
472TimeZoneNames::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
482UBool
483TimeZoneNames::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
493UVector*
494TimeZoneNames::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
512U_NAMESPACE_END
513#endif
514