|
1 /* |
|
2 ******************************************************************************* |
|
3 * Copyright (C) 2011-2013, 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 "mutex.h" |
|
18 #include "putilimp.h" |
|
19 #include "tznames_impl.h" |
|
20 #include "uassert.h" |
|
21 #include "ucln_in.h" |
|
22 #include "uhash.h" |
|
23 #include "umutex.h" |
|
24 #include "uvector.h" |
|
25 |
|
26 |
|
27 U_NAMESPACE_BEGIN |
|
28 |
|
29 // TimeZoneNames object cache handling |
|
30 static UMutex gTimeZoneNamesLock = U_MUTEX_INITIALIZER; |
|
31 static UHashtable *gTimeZoneNamesCache = NULL; |
|
32 static UBool gTimeZoneNamesCacheInitialized = FALSE; |
|
33 |
|
34 // Access count - incremented every time up to SWEEP_INTERVAL, |
|
35 // then reset to 0 |
|
36 static int32_t gAccessCount = 0; |
|
37 |
|
38 // Interval for calling the cache sweep function - every 100 times |
|
39 #define SWEEP_INTERVAL 100 |
|
40 |
|
41 // Cache expiration in millisecond. When a cached entry is no |
|
42 // longer referenced and exceeding this threshold since last |
|
43 // access time, then the cache entry will be deleted by the sweep |
|
44 // function. For now, 3 minutes. |
|
45 #define CACHE_EXPIRATION 180000.0 |
|
46 |
|
47 typedef struct TimeZoneNamesCacheEntry { |
|
48 TimeZoneNames* names; |
|
49 int32_t refCount; |
|
50 double lastAccess; |
|
51 } TimeZoneNamesCacheEntry; |
|
52 |
|
53 U_CDECL_BEGIN |
|
54 /** |
|
55 * Cleanup callback func |
|
56 */ |
|
57 static UBool U_CALLCONV timeZoneNames_cleanup(void) |
|
58 { |
|
59 if (gTimeZoneNamesCache != NULL) { |
|
60 uhash_close(gTimeZoneNamesCache); |
|
61 gTimeZoneNamesCache = NULL; |
|
62 } |
|
63 gTimeZoneNamesCacheInitialized = FALSE; |
|
64 return TRUE; |
|
65 } |
|
66 |
|
67 /** |
|
68 * Deleter for TimeZoneNamesCacheEntry |
|
69 */ |
|
70 static void U_CALLCONV |
|
71 deleteTimeZoneNamesCacheEntry(void *obj) { |
|
72 icu::TimeZoneNamesCacheEntry *entry = (icu::TimeZoneNamesCacheEntry*)obj; |
|
73 delete (icu::TimeZoneNamesImpl*) entry->names; |
|
74 uprv_free(entry); |
|
75 } |
|
76 U_CDECL_END |
|
77 |
|
78 /** |
|
79 * Function used for removing unreferrenced cache entries exceeding |
|
80 * the expiration time. This function must be called with in the mutex |
|
81 * block. |
|
82 */ |
|
83 static void sweepCache() { |
|
84 int32_t pos = -1; |
|
85 const UHashElement* elem; |
|
86 double now = (double)uprv_getUTCtime(); |
|
87 |
|
88 while ((elem = uhash_nextElement(gTimeZoneNamesCache, &pos))) { |
|
89 TimeZoneNamesCacheEntry *entry = (TimeZoneNamesCacheEntry *)elem->value.pointer; |
|
90 if (entry->refCount <= 0 && (now - entry->lastAccess) > CACHE_EXPIRATION) { |
|
91 // delete this entry |
|
92 uhash_removeElement(gTimeZoneNamesCache, elem); |
|
93 } |
|
94 } |
|
95 } |
|
96 |
|
97 // --------------------------------------------------- |
|
98 // TimeZoneNamesDelegate |
|
99 // --------------------------------------------------- |
|
100 class TimeZoneNamesDelegate : public TimeZoneNames { |
|
101 public: |
|
102 TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status); |
|
103 virtual ~TimeZoneNamesDelegate(); |
|
104 |
|
105 virtual UBool operator==(const TimeZoneNames& other) const; |
|
106 virtual UBool operator!=(const TimeZoneNames& other) const {return !operator==(other);}; |
|
107 virtual TimeZoneNames* clone() const; |
|
108 |
|
109 StringEnumeration* getAvailableMetaZoneIDs(UErrorCode& status) const; |
|
110 StringEnumeration* getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const; |
|
111 UnicodeString& getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const; |
|
112 UnicodeString& getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const; |
|
113 |
|
114 UnicodeString& getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const; |
|
115 UnicodeString& getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const; |
|
116 |
|
117 UnicodeString& getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const; |
|
118 |
|
119 MatchInfoCollection* find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const; |
|
120 private: |
|
121 TimeZoneNamesDelegate(); |
|
122 TimeZoneNamesCacheEntry* fTZnamesCacheEntry; |
|
123 }; |
|
124 |
|
125 TimeZoneNamesDelegate::TimeZoneNamesDelegate() |
|
126 : fTZnamesCacheEntry(0) { |
|
127 } |
|
128 |
|
129 TimeZoneNamesDelegate::TimeZoneNamesDelegate(const Locale& locale, UErrorCode& status) { |
|
130 Mutex lock(&gTimeZoneNamesLock); |
|
131 if (!gTimeZoneNamesCacheInitialized) { |
|
132 // Create empty hashtable if it is not already initialized. |
|
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 if (U_FAILURE(status)) { |
|
143 return; |
|
144 } |
|
145 |
|
146 // Check the cache, if not available, create new one and cache |
|
147 TimeZoneNamesCacheEntry *cacheEntry = NULL; |
|
148 |
|
149 const char *key = locale.getName(); |
|
150 cacheEntry = (TimeZoneNamesCacheEntry *)uhash_get(gTimeZoneNamesCache, key); |
|
151 if (cacheEntry == NULL) { |
|
152 TimeZoneNames *tznames = NULL; |
|
153 char *newKey = NULL; |
|
154 |
|
155 tznames = new TimeZoneNamesImpl(locale, status); |
|
156 if (tznames == NULL) { |
|
157 status = U_MEMORY_ALLOCATION_ERROR; |
|
158 } |
|
159 if (U_SUCCESS(status)) { |
|
160 newKey = (char *)uprv_malloc(uprv_strlen(key) + 1); |
|
161 if (newKey == NULL) { |
|
162 status = U_MEMORY_ALLOCATION_ERROR; |
|
163 } else { |
|
164 uprv_strcpy(newKey, key); |
|
165 } |
|
166 } |
|
167 if (U_SUCCESS(status)) { |
|
168 cacheEntry = (TimeZoneNamesCacheEntry *)uprv_malloc(sizeof(TimeZoneNamesCacheEntry)); |
|
169 if (cacheEntry == NULL) { |
|
170 status = U_MEMORY_ALLOCATION_ERROR; |
|
171 } else { |
|
172 cacheEntry->names = tznames; |
|
173 cacheEntry->refCount = 1; |
|
174 cacheEntry->lastAccess = (double)uprv_getUTCtime(); |
|
175 |
|
176 uhash_put(gTimeZoneNamesCache, newKey, cacheEntry, &status); |
|
177 } |
|
178 } |
|
179 if (U_FAILURE(status)) { |
|
180 if (tznames != NULL) { |
|
181 delete tznames; |
|
182 } |
|
183 if (newKey != NULL) { |
|
184 uprv_free(newKey); |
|
185 } |
|
186 if (cacheEntry != NULL) { |
|
187 uprv_free(cacheEntry); |
|
188 } |
|
189 cacheEntry = NULL; |
|
190 } |
|
191 } else { |
|
192 // Update the reference count |
|
193 cacheEntry->refCount++; |
|
194 cacheEntry->lastAccess = (double)uprv_getUTCtime(); |
|
195 } |
|
196 gAccessCount++; |
|
197 if (gAccessCount >= SWEEP_INTERVAL) { |
|
198 // sweep |
|
199 sweepCache(); |
|
200 gAccessCount = 0; |
|
201 } |
|
202 fTZnamesCacheEntry = cacheEntry; |
|
203 } |
|
204 |
|
205 TimeZoneNamesDelegate::~TimeZoneNamesDelegate() { |
|
206 umtx_lock(&gTimeZoneNamesLock); |
|
207 { |
|
208 if (fTZnamesCacheEntry) { |
|
209 U_ASSERT(fTZnamesCacheEntry->refCount > 0); |
|
210 // Just decrement the reference count |
|
211 fTZnamesCacheEntry->refCount--; |
|
212 } |
|
213 } |
|
214 umtx_unlock(&gTimeZoneNamesLock); |
|
215 } |
|
216 |
|
217 UBool |
|
218 TimeZoneNamesDelegate::operator==(const TimeZoneNames& other) const { |
|
219 if (this == &other) { |
|
220 return TRUE; |
|
221 } |
|
222 // Just compare if the other object also use the same |
|
223 // cache entry |
|
224 const TimeZoneNamesDelegate* rhs = dynamic_cast<const TimeZoneNamesDelegate*>(&other); |
|
225 if (rhs) { |
|
226 return fTZnamesCacheEntry == rhs->fTZnamesCacheEntry; |
|
227 } |
|
228 return FALSE; |
|
229 } |
|
230 |
|
231 TimeZoneNames* |
|
232 TimeZoneNamesDelegate::clone() const { |
|
233 TimeZoneNamesDelegate* other = new TimeZoneNamesDelegate(); |
|
234 if (other != NULL) { |
|
235 umtx_lock(&gTimeZoneNamesLock); |
|
236 { |
|
237 // Just increment the reference count |
|
238 fTZnamesCacheEntry->refCount++; |
|
239 other->fTZnamesCacheEntry = fTZnamesCacheEntry; |
|
240 } |
|
241 umtx_unlock(&gTimeZoneNamesLock); |
|
242 } |
|
243 return other; |
|
244 } |
|
245 |
|
246 StringEnumeration* |
|
247 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(UErrorCode& status) const { |
|
248 return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(status); |
|
249 } |
|
250 |
|
251 StringEnumeration* |
|
252 TimeZoneNamesDelegate::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const { |
|
253 return fTZnamesCacheEntry->names->getAvailableMetaZoneIDs(tzID, status); |
|
254 } |
|
255 |
|
256 UnicodeString& |
|
257 TimeZoneNamesDelegate::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const { |
|
258 return fTZnamesCacheEntry->names->getMetaZoneID(tzID, date, mzID); |
|
259 } |
|
260 |
|
261 UnicodeString& |
|
262 TimeZoneNamesDelegate::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const { |
|
263 return fTZnamesCacheEntry->names->getReferenceZoneID(mzID, region, tzID); |
|
264 } |
|
265 |
|
266 UnicodeString& |
|
267 TimeZoneNamesDelegate::getMetaZoneDisplayName(const UnicodeString& mzID, UTimeZoneNameType type, UnicodeString& name) const { |
|
268 return fTZnamesCacheEntry->names->getMetaZoneDisplayName(mzID, type, name); |
|
269 } |
|
270 |
|
271 UnicodeString& |
|
272 TimeZoneNamesDelegate::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const { |
|
273 return fTZnamesCacheEntry->names->getTimeZoneDisplayName(tzID, type, name); |
|
274 } |
|
275 |
|
276 UnicodeString& |
|
277 TimeZoneNamesDelegate::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const { |
|
278 return fTZnamesCacheEntry->names->getExemplarLocationName(tzID, name); |
|
279 } |
|
280 |
|
281 TimeZoneNames::MatchInfoCollection* |
|
282 TimeZoneNamesDelegate::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { |
|
283 return fTZnamesCacheEntry->names->find(text, start, types, status); |
|
284 } |
|
285 |
|
286 // --------------------------------------------------- |
|
287 // TimeZoneNames base class |
|
288 // --------------------------------------------------- |
|
289 TimeZoneNames::~TimeZoneNames() { |
|
290 } |
|
291 |
|
292 TimeZoneNames* |
|
293 TimeZoneNames::createInstance(const Locale& locale, UErrorCode& status) { |
|
294 return new TimeZoneNamesDelegate(locale, status); |
|
295 } |
|
296 |
|
297 UnicodeString& |
|
298 TimeZoneNames::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const { |
|
299 return TimeZoneNamesImpl::getDefaultExemplarLocationName(tzID, name); |
|
300 } |
|
301 |
|
302 UnicodeString& |
|
303 TimeZoneNames::getDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UDate date, UnicodeString& name) const { |
|
304 getTimeZoneDisplayName(tzID, type, name); |
|
305 if (name.isEmpty()) { |
|
306 UnicodeString mzID; |
|
307 getMetaZoneID(tzID, date, mzID); |
|
308 getMetaZoneDisplayName(mzID, type, name); |
|
309 } |
|
310 return name; |
|
311 } |
|
312 |
|
313 |
|
314 struct MatchInfo : UMemory { |
|
315 UTimeZoneNameType nameType; |
|
316 UnicodeString id; |
|
317 int32_t matchLength; |
|
318 UBool isTZID; |
|
319 |
|
320 MatchInfo(UTimeZoneNameType nameType, int32_t matchLength, const UnicodeString* tzID, const UnicodeString* mzID) { |
|
321 this->nameType = nameType; |
|
322 this->matchLength = matchLength; |
|
323 if (tzID != NULL) { |
|
324 this->id.setTo(*tzID); |
|
325 this->isTZID = TRUE; |
|
326 } else { |
|
327 this->id.setTo(*mzID); |
|
328 this->isTZID = FALSE; |
|
329 } |
|
330 } |
|
331 }; |
|
332 |
|
333 U_CDECL_BEGIN |
|
334 static void U_CALLCONV |
|
335 deleteMatchInfo(void *obj) { |
|
336 delete static_cast<MatchInfo *>(obj); |
|
337 } |
|
338 U_CDECL_END |
|
339 |
|
340 // --------------------------------------------------- |
|
341 // MatchInfoCollection class |
|
342 // --------------------------------------------------- |
|
343 TimeZoneNames::MatchInfoCollection::MatchInfoCollection() |
|
344 : fMatches(NULL) { |
|
345 } |
|
346 |
|
347 TimeZoneNames::MatchInfoCollection::~MatchInfoCollection() { |
|
348 if (fMatches != NULL) { |
|
349 delete fMatches; |
|
350 } |
|
351 } |
|
352 |
|
353 void |
|
354 TimeZoneNames::MatchInfoCollection::addZone(UTimeZoneNameType nameType, int32_t matchLength, |
|
355 const UnicodeString& tzID, UErrorCode& status) { |
|
356 if (U_FAILURE(status)) { |
|
357 return; |
|
358 } |
|
359 MatchInfo* matchInfo = new MatchInfo(nameType, matchLength, &tzID, NULL); |
|
360 if (matchInfo == NULL) { |
|
361 status = U_MEMORY_ALLOCATION_ERROR; |
|
362 return; |
|
363 } |
|
364 matches(status)->addElement(matchInfo, status); |
|
365 if (U_FAILURE(status)) { |
|
366 delete matchInfo; |
|
367 } |
|
368 } |
|
369 |
|
370 void |
|
371 TimeZoneNames::MatchInfoCollection::addMetaZone(UTimeZoneNameType nameType, int32_t matchLength, |
|
372 const UnicodeString& mzID, UErrorCode& status) { |
|
373 if (U_FAILURE(status)) { |
|
374 return; |
|
375 } |
|
376 MatchInfo* matchInfo = new MatchInfo(nameType, matchLength, NULL, &mzID); |
|
377 if (matchInfo == NULL) { |
|
378 status = U_MEMORY_ALLOCATION_ERROR; |
|
379 return; |
|
380 } |
|
381 matches(status)->addElement(matchInfo, status); |
|
382 if (U_FAILURE(status)) { |
|
383 delete matchInfo; |
|
384 } |
|
385 } |
|
386 |
|
387 int32_t |
|
388 TimeZoneNames::MatchInfoCollection::size() const { |
|
389 if (fMatches == NULL) { |
|
390 return 0; |
|
391 } |
|
392 return fMatches->size(); |
|
393 } |
|
394 |
|
395 UTimeZoneNameType |
|
396 TimeZoneNames::MatchInfoCollection::getNameTypeAt(int32_t idx) const { |
|
397 const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx); |
|
398 if (match) { |
|
399 return match->nameType; |
|
400 } |
|
401 return UTZNM_UNKNOWN; |
|
402 } |
|
403 |
|
404 int32_t |
|
405 TimeZoneNames::MatchInfoCollection::getMatchLengthAt(int32_t idx) const { |
|
406 const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx); |
|
407 if (match) { |
|
408 return match->matchLength; |
|
409 } |
|
410 return 0; |
|
411 } |
|
412 |
|
413 UBool |
|
414 TimeZoneNames::MatchInfoCollection::getTimeZoneIDAt(int32_t idx, UnicodeString& tzID) const { |
|
415 tzID.remove(); |
|
416 const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx); |
|
417 if (match && match->isTZID) { |
|
418 tzID.setTo(match->id); |
|
419 return TRUE; |
|
420 } |
|
421 return FALSE; |
|
422 } |
|
423 |
|
424 UBool |
|
425 TimeZoneNames::MatchInfoCollection::getMetaZoneIDAt(int32_t idx, UnicodeString& mzID) const { |
|
426 mzID.remove(); |
|
427 const MatchInfo* match = (const MatchInfo*)fMatches->elementAt(idx); |
|
428 if (match && !match->isTZID) { |
|
429 mzID.setTo(match->id); |
|
430 return TRUE; |
|
431 } |
|
432 return FALSE; |
|
433 } |
|
434 |
|
435 UVector* |
|
436 TimeZoneNames::MatchInfoCollection::matches(UErrorCode& status) { |
|
437 if (U_FAILURE(status)) { |
|
438 return NULL; |
|
439 } |
|
440 if (fMatches != NULL) { |
|
441 return fMatches; |
|
442 } |
|
443 fMatches = new UVector(deleteMatchInfo, NULL, status); |
|
444 if (fMatches == NULL) { |
|
445 status = U_MEMORY_ALLOCATION_ERROR; |
|
446 } else if (U_FAILURE(status)) { |
|
447 delete fMatches; |
|
448 fMatches = NULL; |
|
449 } |
|
450 return fMatches; |
|
451 } |
|
452 |
|
453 |
|
454 U_NAMESPACE_END |
|
455 #endif |