]>
Commit | Line | Data |
---|---|---|
46f4442e A |
1 | /* |
2 | ******************************************************************************* | |
729e4ab9 | 3 | * Copyright (C) 2007-2010, International Business Machines Corporation and * |
46f4442e A |
4 | * others. All Rights Reserved. * |
5 | ******************************************************************************* | |
6 | */ | |
7 | ||
8 | #include "unicode/utypes.h" | |
9 | ||
10 | #if !UCONFIG_NO_FORMATTING | |
11 | ||
12 | #include "zstrfmt.h" | |
13 | ||
14 | #include "unicode/ustring.h" | |
15 | #include "unicode/putil.h" | |
16 | #include "unicode/msgfmt.h" | |
17 | #include "unicode/basictz.h" | |
18 | #include "unicode/simpletz.h" | |
19 | #include "unicode/rbtz.h" | |
20 | #include "unicode/vtzone.h" | |
21 | ||
22 | #include "uvector.h" | |
23 | #include "cstring.h" | |
24 | #include "cmemory.h" | |
25 | #include "uresimp.h" | |
26 | #include "zonemeta.h" | |
27 | #include "olsontz.h" | |
28 | #include "umutex.h" | |
29 | #include "ucln_in.h" | |
729e4ab9 A |
30 | #include "uassert.h" |
31 | #include "ureslocs.h" | |
46f4442e A |
32 | |
33 | /** | |
34 | * global ZoneStringFormatCache stuffs | |
35 | */ | |
36 | static UMTX gZSFCacheLock = NULL; | |
37 | static U_NAMESPACE_QUALIFIER ZSFCache *gZoneStringFormatCache = NULL; | |
38 | ||
39 | U_CDECL_BEGIN | |
40 | /** | |
41 | * ZoneStringFormatCache cleanup callback func | |
42 | */ | |
43 | static UBool U_CALLCONV zoneStringFormat_cleanup(void) | |
44 | { | |
45 | umtx_destroy(&gZSFCacheLock); | |
46 | if (gZoneStringFormatCache != NULL) { | |
47 | delete gZoneStringFormatCache; | |
48 | gZoneStringFormatCache = NULL; | |
49 | } | |
50 | gZoneStringFormatCache = NULL; | |
51 | return TRUE; | |
52 | } | |
53 | ||
54 | /** | |
55 | * Deleter for ZoneStringInfo | |
56 | */ | |
57 | static void U_CALLCONV | |
58 | deleteZoneStringInfo(void *obj) { | |
59 | delete (U_NAMESPACE_QUALIFIER ZoneStringInfo*)obj; | |
60 | } | |
61 | ||
62 | /** | |
63 | * Deleter for ZoneStrings | |
64 | */ | |
65 | static void U_CALLCONV | |
66 | deleteZoneStrings(void *obj) { | |
67 | delete (U_NAMESPACE_QUALIFIER ZoneStrings*)obj; | |
68 | } | |
69 | U_CDECL_END | |
70 | ||
71 | U_NAMESPACE_BEGIN | |
72 | ||
73 | #define ZID_KEY_MAX 128 | |
74 | ||
75 | static const char gCountriesTag[] = "Countries"; | |
76 | static const char gZoneStringsTag[] = "zoneStrings"; | |
77 | static const char gShortGenericTag[] = "sg"; | |
78 | static const char gShortStandardTag[] = "ss"; | |
79 | static const char gShortDaylightTag[] = "sd"; | |
80 | static const char gLongGenericTag[] = "lg"; | |
81 | static const char gLongStandardTag[] = "ls"; | |
82 | static const char gLongDaylightTag[] = "ld"; | |
83 | static const char gExemplarCityTag[] = "ec"; | |
84 | static const char gCommonlyUsedTag[] = "cu"; | |
85 | static const char gFallbackFormatTag[] = "fallbackFormat"; | |
86 | static const char gRegionFormatTag[] = "regionFormat"; | |
87 | ||
88 | #define MZID_PREFIX_LEN 5 | |
89 | static const char gMetazoneIdPrefix[] = "meta:"; | |
90 | ||
91 | #define MAX_METAZONES_PER_ZONE 10 | |
92 | ||
93 | static const UChar gDefFallbackPattern[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})" | |
94 | static const UChar gDefRegionPattern[] = {0x7B, 0x30, 0x7D, 0x00}; // "{0}" | |
95 | static const UChar gCommonlyUsedTrue[] = {0x31, 0x00}; // "1" | |
96 | ||
97 | static const double kDstCheckRange = (double)184*U_MILLIS_PER_DAY; | |
98 | ||
99 | static int32_t | |
100 | getTimeZoneTranslationTypeIndex(TimeZoneTranslationType type) { | |
101 | int32_t typeIdx = 0; | |
102 | switch (type) { | |
103 | case LOCATION: | |
104 | typeIdx = ZSIDX_LOCATION; | |
105 | break; | |
106 | case GENERIC_LONG: | |
107 | typeIdx = ZSIDX_LONG_GENERIC; | |
108 | break; | |
109 | case GENERIC_SHORT: | |
110 | typeIdx = ZSIDX_SHORT_GENERIC; | |
111 | break; | |
112 | case STANDARD_LONG: | |
113 | typeIdx = ZSIDX_LONG_STANDARD; | |
114 | break; | |
115 | case STANDARD_SHORT: | |
116 | typeIdx = ZSIDX_SHORT_STANDARD; | |
117 | break; | |
118 | case DAYLIGHT_LONG: | |
119 | typeIdx = ZSIDX_LONG_DAYLIGHT; | |
120 | break; | |
121 | case DAYLIGHT_SHORT: | |
122 | typeIdx = ZSIDX_SHORT_DAYLIGHT; | |
123 | break; | |
124 | } | |
125 | return typeIdx; | |
126 | } | |
127 | ||
128 | static int32_t | |
129 | getTimeZoneTranslationType(TimeZoneTranslationTypeIndex typeIdx) { | |
130 | int32_t type = 0; | |
131 | switch (typeIdx) { | |
132 | case ZSIDX_LOCATION: | |
133 | type = LOCATION; | |
134 | break; | |
135 | case ZSIDX_LONG_GENERIC: | |
136 | type = GENERIC_LONG; | |
137 | break; | |
138 | case ZSIDX_SHORT_GENERIC: | |
139 | type = GENERIC_SHORT; | |
140 | break; | |
141 | case ZSIDX_LONG_STANDARD: | |
142 | type = STANDARD_LONG; | |
143 | break; | |
144 | case ZSIDX_SHORT_STANDARD: | |
145 | type = STANDARD_SHORT; | |
146 | break; | |
147 | case ZSIDX_LONG_DAYLIGHT: | |
148 | type = DAYLIGHT_LONG; | |
149 | break; | |
150 | case ZSIDX_COUNT: | |
151 | case ZSIDX_SHORT_DAYLIGHT: | |
152 | type = DAYLIGHT_SHORT; | |
153 | break; | |
729e4ab9 A |
154 | default: |
155 | break; | |
46f4442e A |
156 | } |
157 | return type; | |
158 | } | |
159 | ||
160 | #define DEFAULT_CHARACTERNODE_CAPACITY 1 | |
161 | ||
162 | // ---------------------------------------------------------------------------- | |
163 | void CharacterNode::clear() { | |
164 | uprv_memset(this, 0, sizeof(*this)); | |
165 | } | |
166 | ||
167 | void CharacterNode::deleteValues() { | |
168 | if (fValues == NULL) { | |
169 | // Do nothing. | |
170 | } else if (!fHasValuesVector) { | |
171 | deleteZoneStringInfo(fValues); | |
172 | } else { | |
173 | delete (UVector *)fValues; | |
174 | } | |
175 | } | |
176 | ||
177 | void | |
178 | CharacterNode::addValue(void *value, UErrorCode &status) { | |
179 | if (U_FAILURE(status)) { | |
180 | deleteZoneStringInfo(value); | |
181 | return; | |
182 | } | |
183 | if (fValues == NULL) { | |
184 | fValues = value; | |
185 | } else { | |
186 | // At least one value already. | |
187 | if (!fHasValuesVector) { | |
188 | // There is only one value so far, and not in a vector yet. | |
189 | // Create a vector and add the old value. | |
190 | UVector *values = new UVector(deleteZoneStringInfo, NULL, DEFAULT_CHARACTERNODE_CAPACITY, status); | |
191 | if (U_FAILURE(status)) { | |
192 | deleteZoneStringInfo(value); | |
193 | return; | |
194 | } | |
195 | values->addElement(fValues, status); | |
196 | fValues = values; | |
197 | fHasValuesVector = TRUE; | |
198 | } | |
199 | // Add the new value. | |
200 | ((UVector *)fValues)->addElement(value, status); | |
201 | } | |
202 | } | |
203 | ||
204 | //---------------------------------------------------------------------------- | |
205 | // Virtual destructor to avoid warning | |
206 | TextTrieMapSearchResultHandler::~TextTrieMapSearchResultHandler(){ | |
207 | } | |
208 | ||
209 | // ---------------------------------------------------------------------------- | |
210 | TextTrieMap::TextTrieMap(UBool ignoreCase) | |
729e4ab9 A |
211 | : fIgnoreCase(ignoreCase), fNodes(NULL), fNodesCapacity(0), fNodesCount(0), |
212 | fLazyContents(NULL), fIsEmpty(TRUE) { | |
46f4442e A |
213 | } |
214 | ||
215 | TextTrieMap::~TextTrieMap() { | |
216 | int32_t index; | |
217 | for (index = 0; index < fNodesCount; ++index) { | |
218 | fNodes[index].deleteValues(); | |
219 | } | |
220 | uprv_free(fNodes); | |
729e4ab9 A |
221 | if (fLazyContents != NULL) { |
222 | for (int32_t i=0; i<fLazyContents->size(); i+=2) { | |
223 | ZoneStringInfo *zsinf = (ZoneStringInfo *)fLazyContents->elementAt(i+1); | |
224 | delete zsinf; | |
225 | } | |
226 | delete fLazyContents; | |
227 | } | |
228 | } | |
229 | ||
230 | int32_t TextTrieMap::isEmpty() const { | |
231 | // Use a separate field for fIsEmpty because it will remain unchanged once the | |
232 | // Trie is built, while fNodes and fLazyContents change with the lazy init | |
233 | // of the nodes structure. Trying to test the changing fields has | |
234 | // thread safety complications. | |
235 | return fIsEmpty; | |
46f4442e A |
236 | } |
237 | ||
729e4ab9 A |
238 | |
239 | // We defer actually building the TextTrieMap node structure until the first time a | |
240 | // search is performed. put() simply saves the parameters in case we do | |
241 | // eventually need to build it. | |
242 | // | |
243 | void | |
244 | TextTrieMap::put(const UnicodeString &key, void *value, ZSFStringPool &sp, UErrorCode &status) { | |
245 | fIsEmpty = FALSE; | |
246 | if (fLazyContents == NULL) { | |
247 | fLazyContents = new UVector(status); | |
248 | if (fLazyContents == NULL) { | |
249 | status = U_MEMORY_ALLOCATION_ERROR; | |
250 | } | |
251 | } | |
252 | if (U_FAILURE(status)) { | |
253 | return; | |
254 | } | |
255 | UChar *s = const_cast<UChar *>(sp.get(key, status)); | |
256 | fLazyContents->addElement(s, status); | |
257 | fLazyContents->addElement(value, status); | |
258 | } | |
259 | ||
260 | ||
46f4442e | 261 | void |
729e4ab9 | 262 | TextTrieMap::putImpl(const UnicodeString &key, void *value, UErrorCode &status) { |
46f4442e A |
263 | if (fNodes == NULL) { |
264 | fNodesCapacity = 512; | |
265 | fNodes = (CharacterNode *)uprv_malloc(fNodesCapacity * sizeof(CharacterNode)); | |
266 | fNodes[0].clear(); // Init root node. | |
267 | fNodesCount = 1; | |
268 | } | |
269 | ||
270 | UnicodeString foldedKey; | |
271 | const UChar *keyBuffer; | |
272 | int32_t keyLength; | |
273 | if (fIgnoreCase) { | |
274 | // Ok to use fastCopyFrom() because we discard the copy when we return. | |
275 | foldedKey.fastCopyFrom(key).foldCase(); | |
276 | keyBuffer = foldedKey.getBuffer(); | |
277 | keyLength = foldedKey.length(); | |
278 | } else { | |
279 | keyBuffer = key.getBuffer(); | |
280 | keyLength = key.length(); | |
281 | } | |
282 | ||
283 | CharacterNode *node = fNodes; | |
284 | int32_t index; | |
285 | for (index = 0; index < keyLength; ++index) { | |
286 | node = addChildNode(node, keyBuffer[index], status); | |
287 | } | |
288 | node->addValue(value, status); | |
289 | } | |
290 | ||
291 | UBool | |
292 | TextTrieMap::growNodes() { | |
293 | if (fNodesCapacity == 0xffff) { | |
294 | return FALSE; // We use 16-bit node indexes. | |
295 | } | |
729e4ab9 | 296 | int32_t newCapacity = fNodesCapacity + 1000; |
46f4442e A |
297 | if (newCapacity > 0xffff) { |
298 | newCapacity = 0xffff; | |
299 | } | |
300 | CharacterNode *newNodes = (CharacterNode *)uprv_malloc(newCapacity * sizeof(CharacterNode)); | |
301 | if (newNodes == NULL) { | |
302 | return FALSE; | |
303 | } | |
304 | uprv_memcpy(newNodes, fNodes, fNodesCount * sizeof(CharacterNode)); | |
305 | uprv_free(fNodes); | |
306 | fNodes = newNodes; | |
307 | fNodesCapacity = newCapacity; | |
308 | return TRUE; | |
309 | } | |
310 | ||
311 | CharacterNode* | |
312 | TextTrieMap::addChildNode(CharacterNode *parent, UChar c, UErrorCode &status) { | |
313 | if (U_FAILURE(status)) { | |
314 | return NULL; | |
315 | } | |
316 | // Linear search of the sorted list of children. | |
317 | uint16_t prevIndex = 0; | |
318 | uint16_t nodeIndex = parent->fFirstChild; | |
319 | while (nodeIndex > 0) { | |
320 | CharacterNode *current = fNodes + nodeIndex; | |
321 | UChar childCharacter = current->fCharacter; | |
322 | if (childCharacter == c) { | |
323 | return current; | |
324 | } else if (childCharacter > c) { | |
325 | break; | |
326 | } | |
327 | prevIndex = nodeIndex; | |
328 | nodeIndex = current->fNextSibling; | |
329 | } | |
330 | ||
331 | // Ensure capacity. Grow fNodes[] if needed. | |
332 | if (fNodesCount == fNodesCapacity) { | |
729e4ab9 | 333 | int32_t parentIndex = (int32_t)(parent - fNodes); |
46f4442e A |
334 | if (!growNodes()) { |
335 | status = U_MEMORY_ALLOCATION_ERROR; | |
336 | return NULL; | |
337 | } | |
338 | parent = fNodes + parentIndex; | |
339 | } | |
340 | ||
341 | // Insert a new child node with c in sorted order. | |
342 | CharacterNode *node = fNodes + fNodesCount; | |
343 | node->clear(); | |
344 | node->fCharacter = c; | |
345 | node->fNextSibling = nodeIndex; | |
346 | if (prevIndex == 0) { | |
347 | parent->fFirstChild = (uint16_t)fNodesCount; | |
348 | } else { | |
349 | fNodes[prevIndex].fNextSibling = (uint16_t)fNodesCount; | |
350 | } | |
351 | ++fNodesCount; | |
352 | return node; | |
353 | } | |
354 | ||
355 | CharacterNode* | |
356 | TextTrieMap::getChildNode(CharacterNode *parent, UChar c) const { | |
357 | // Linear search of the sorted list of children. | |
358 | uint16_t nodeIndex = parent->fFirstChild; | |
359 | while (nodeIndex > 0) { | |
360 | CharacterNode *current = fNodes + nodeIndex; | |
361 | UChar childCharacter = current->fCharacter; | |
362 | if (childCharacter == c) { | |
363 | return current; | |
364 | } else if (childCharacter > c) { | |
365 | break; | |
366 | } | |
367 | nodeIndex = current->fNextSibling; | |
368 | } | |
369 | return NULL; | |
370 | } | |
371 | ||
729e4ab9 A |
372 | // Mutex for protecting the lazy creation of the Trie node structure on the first call to search(). |
373 | static UMTX TextTrieMutex; | |
374 | ||
375 | // buildTrie() - The Trie node structure is needed. Create it from the data that was | |
376 | // saved at the time the ZoneStringFormatter was created. The Trie is only | |
377 | // needed for parsing operations, which are less common than formatting, | |
378 | // and the Trie is big, which is why its creation is deferred until first use. | |
379 | void TextTrieMap::buildTrie(UErrorCode &status) { | |
380 | umtx_lock(&TextTrieMutex); | |
381 | if (fLazyContents != NULL) { | |
382 | for (int32_t i=0; i<fLazyContents->size(); i+=2) { | |
383 | const UChar *key = (UChar *)fLazyContents->elementAt(i); | |
384 | void *val = fLazyContents->elementAt(i+1); | |
385 | UnicodeString keyString(TRUE, key, -1); // Aliasing UnicodeString constructor. | |
386 | putImpl(keyString, val, status); | |
387 | } | |
388 | delete fLazyContents; | |
389 | fLazyContents = NULL; | |
390 | } | |
391 | umtx_unlock(&TextTrieMutex); | |
392 | } | |
393 | ||
394 | ||
46f4442e A |
395 | void |
396 | TextTrieMap::search(const UnicodeString &text, int32_t start, | |
397 | TextTrieMapSearchResultHandler *handler, UErrorCode &status) const { | |
729e4ab9 A |
398 | UBool trieNeedsInitialization = FALSE; |
399 | UMTX_CHECK(&TextTrieMutex, fLazyContents != NULL, trieNeedsInitialization); | |
400 | if (trieNeedsInitialization) { | |
401 | TextTrieMap *nonConstThis = const_cast<TextTrieMap *>(this); | |
402 | nonConstThis->buildTrie(status); | |
403 | } | |
46f4442e A |
404 | if (fNodes == NULL) { |
405 | return; | |
406 | } | |
407 | search(fNodes, text, start, start, handler, status); | |
408 | } | |
409 | ||
410 | void | |
411 | TextTrieMap::search(CharacterNode *node, const UnicodeString &text, int32_t start, | |
412 | int32_t index, TextTrieMapSearchResultHandler *handler, UErrorCode &status) const { | |
413 | if (U_FAILURE(status)) { | |
414 | return; | |
415 | } | |
416 | if (node->hasValues()) { | |
417 | if (!handler->handleMatch(index - start, node, status)) { | |
418 | return; | |
419 | } | |
420 | if (U_FAILURE(status)) { | |
421 | return; | |
422 | } | |
423 | } | |
424 | UChar32 c = text.char32At(index); | |
425 | if (fIgnoreCase) { | |
426 | // size of character may grow after fold operation | |
427 | UnicodeString tmp(c); | |
428 | tmp.foldCase(); | |
429 | int32_t tmpidx = 0; | |
430 | while (tmpidx < tmp.length()) { | |
431 | c = tmp.char32At(tmpidx); | |
432 | node = getChildNode(node, c); | |
433 | if (node == NULL) { | |
434 | break; | |
435 | } | |
436 | tmpidx = tmp.moveIndex32(tmpidx, 1); | |
437 | } | |
438 | } else { | |
439 | node = getChildNode(node, c); | |
440 | } | |
441 | if (node != NULL) { | |
442 | search(node, text, start, index+1, handler, status); | |
443 | } | |
444 | } | |
445 | ||
446 | // ---------------------------------------------------------------------------- | |
447 | ZoneStringInfo::ZoneStringInfo(const UnicodeString &id, const UnicodeString &str, | |
729e4ab9 A |
448 | TimeZoneTranslationType type, ZSFStringPool &sp, UErrorCode &status) |
449 | : fType(type) { | |
450 | fId = sp.get(id, status); | |
451 | fStr = sp.get(str, status); | |
46f4442e A |
452 | } |
453 | ||
454 | ZoneStringInfo::~ZoneStringInfo() { | |
455 | } | |
729e4ab9 A |
456 | |
457 | ||
46f4442e A |
458 | // ---------------------------------------------------------------------------- |
459 | ZoneStringSearchResultHandler::ZoneStringSearchResultHandler(UErrorCode &status) | |
460 | : fResults(status) | |
461 | { | |
462 | clear(); | |
463 | } | |
464 | ||
465 | ZoneStringSearchResultHandler::~ZoneStringSearchResultHandler() { | |
466 | clear(); | |
467 | } | |
468 | ||
469 | UBool | |
470 | ZoneStringSearchResultHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) { | |
471 | if (U_FAILURE(status)) { | |
472 | return FALSE; | |
473 | } | |
474 | if (node->hasValues()) { | |
475 | int32_t valuesCount = node->countValues(); | |
476 | for (int32_t i = 0; i < valuesCount; i++) { | |
477 | ZoneStringInfo *zsinfo = (ZoneStringInfo*)node->getValue(i); | |
478 | if (zsinfo == NULL) { | |
479 | break; | |
480 | } | |
481 | // Update the results | |
482 | UBool foundType = FALSE; | |
483 | for (int32_t j = 0; j < fResults.size(); j++) { | |
484 | ZoneStringInfo *tmp = (ZoneStringInfo*)fResults.elementAt(j); | |
485 | if (zsinfo->fType == tmp->fType) { | |
486 | int32_t lenidx = getTimeZoneTranslationTypeIndex(tmp->fType); | |
487 | if (matchLength > fMatchLen[lenidx]) { | |
488 | // Same type, longer match | |
489 | fResults.setElementAt(zsinfo, j); | |
490 | fMatchLen[lenidx] = matchLength; | |
491 | } | |
492 | foundType = TRUE; | |
493 | break; | |
494 | } | |
495 | } | |
496 | if (!foundType) { | |
497 | // not found in the current list | |
498 | fResults.addElement(zsinfo, status); | |
499 | fMatchLen[getTimeZoneTranslationTypeIndex(zsinfo->fType)] = matchLength; | |
500 | } | |
501 | } | |
502 | } | |
503 | return TRUE; | |
504 | } | |
505 | ||
506 | int32_t | |
507 | ZoneStringSearchResultHandler::countMatches(void) { | |
508 | return fResults.size(); | |
509 | } | |
510 | ||
511 | const ZoneStringInfo* | |
512 | ZoneStringSearchResultHandler::getMatch(int32_t index, int32_t &matchLength) { | |
513 | ZoneStringInfo *zsinfo = NULL; | |
514 | if (index < fResults.size()) { | |
515 | zsinfo = (ZoneStringInfo*)fResults.elementAt(index); | |
516 | matchLength = fMatchLen[getTimeZoneTranslationTypeIndex(zsinfo->fType)]; | |
517 | } | |
518 | return zsinfo; | |
519 | } | |
520 | ||
521 | void | |
522 | ZoneStringSearchResultHandler::clear(void) { | |
523 | fResults.removeAllElements(); | |
524 | for (int32_t i = 0; i < (int32_t)(sizeof(fMatchLen)/sizeof(fMatchLen[0])); i++) { | |
525 | fMatchLen[i] = 0; | |
526 | } | |
527 | } | |
729e4ab9 A |
528 | |
529 | // Mutex for protecting the lazy load of a zone ID (or a full load) to ZoneStringFormat structures. | |
530 | static UMTX ZoneStringFormatMutex; | |
531 | ||
532 | ||
46f4442e A |
533 | // ---------------------------------------------------------------------------- |
534 | ZoneStringFormat::ZoneStringFormat(const UnicodeString* const* strings, | |
535 | int32_t rowCount, int32_t columnCount, UErrorCode &status) | |
536 | : fLocale(""), | |
729e4ab9 A |
537 | fTzidToStrings(NULL), |
538 | fMzidToStrings(NULL), | |
539 | fZoneStringsTrie(TRUE), | |
540 | fStringPool(status), | |
541 | fZoneStringsArray(NULL), | |
542 | fMetazoneItem(NULL), | |
543 | fZoneItem(NULL), | |
544 | fIsFullyLoaded(FALSE) | |
46f4442e A |
545 | { |
546 | if (U_FAILURE(status)) { | |
547 | return; | |
548 | } | |
549 | fLocale.setToBogus(); | |
550 | if (strings == NULL || columnCount <= 0 || rowCount <= 0) { | |
551 | status = U_ILLEGAL_ARGUMENT_ERROR; | |
552 | return; | |
553 | } | |
729e4ab9 A |
554 | fTzidToStrings = uhash_open(uhash_hashUChars, // key hash function |
555 | uhash_compareUChars, // key comparison function | |
556 | NULL, // Value comparison function | |
557 | &status); | |
558 | fMzidToStrings = uhash_open(uhash_hashUChars, | |
559 | uhash_compareUChars, | |
560 | NULL, | |
561 | &status); | |
46f4442e | 562 | |
729e4ab9 A |
563 | uhash_setValueDeleter(fTzidToStrings, deleteZoneStrings); |
564 | uhash_setValueDeleter(fMzidToStrings, deleteZoneStrings); | |
46f4442e A |
565 | |
566 | for (int32_t row = 0; row < rowCount; row++) { | |
567 | if (strings[row][0].isEmpty()) { | |
568 | continue; | |
569 | } | |
570 | UnicodeString *names = new UnicodeString[ZSIDX_COUNT]; | |
571 | for (int32_t col = 1; col < columnCount; col++) { | |
572 | if (!strings[row][col].isEmpty()) { | |
573 | int32_t typeIdx = -1; | |
574 | switch (col) { | |
575 | case 1: | |
576 | typeIdx = ZSIDX_LONG_STANDARD; | |
577 | break; | |
578 | case 2: | |
579 | typeIdx = ZSIDX_SHORT_STANDARD; | |
580 | break; | |
581 | case 3: | |
582 | typeIdx = ZSIDX_LONG_DAYLIGHT; | |
583 | break; | |
584 | case 4: | |
585 | typeIdx = ZSIDX_SHORT_DAYLIGHT; | |
586 | break; | |
587 | case 5: | |
588 | typeIdx = ZSIDX_LOCATION; | |
589 | break; | |
590 | case 6: | |
591 | typeIdx = ZSIDX_LONG_GENERIC; | |
592 | break; | |
593 | case 7: | |
594 | typeIdx = ZSIDX_SHORT_GENERIC; | |
595 | break; | |
596 | } | |
597 | if (typeIdx != -1) { | |
598 | names[typeIdx].setTo(strings[row][col]); | |
599 | ||
600 | // Put the name into the trie | |
601 | int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)typeIdx); | |
729e4ab9 A |
602 | ZoneStringInfo *zsinf = new ZoneStringInfo(strings[row][0], |
603 | strings[row][col], | |
604 | (TimeZoneTranslationType)type, | |
605 | fStringPool, | |
606 | status); | |
607 | fZoneStringsTrie.put(strings[row][col], zsinf, fStringPool, status); | |
46f4442e A |
608 | if (U_FAILURE(status)) { |
609 | delete zsinf; | |
610 | goto error_cleanup; | |
611 | } | |
612 | } | |
613 | } | |
614 | } | |
729e4ab9 A |
615 | // Note: ZoneStrings constructor adopts and delete the names array. |
616 | ZoneStrings *zstrings = new ZoneStrings(names, ZSIDX_COUNT, TRUE, NULL, 0, 0, | |
617 | fStringPool, status); | |
618 | UChar *utzid = const_cast<UChar *>(fStringPool.get(strings[row][0], status)); | |
619 | uhash_put(fTzidToStrings, utzid, zstrings, &status); | |
46f4442e A |
620 | if (U_FAILURE(status)) { |
621 | delete zstrings; | |
622 | goto error_cleanup; | |
623 | } | |
624 | } | |
729e4ab9 A |
625 | fStringPool.freeze(); |
626 | fIsFullyLoaded = TRUE; | |
46f4442e A |
627 | return; |
628 | ||
629 | error_cleanup: | |
630 | return; | |
631 | } | |
632 | ||
633 | ZoneStringFormat::ZoneStringFormat(const Locale &locale, UErrorCode &status) | |
634 | : fLocale(locale), | |
729e4ab9 A |
635 | fTzidToStrings(NULL), |
636 | fMzidToStrings(NULL), | |
637 | fZoneStringsTrie(TRUE), | |
638 | fStringPool(status), | |
639 | fZoneStringsArray(NULL), | |
640 | fMetazoneItem(NULL), | |
641 | fZoneItem(NULL), | |
642 | fIsFullyLoaded(FALSE) | |
46f4442e A |
643 | { |
644 | if (U_FAILURE(status)) { | |
645 | return; | |
646 | } | |
729e4ab9 A |
647 | fTzidToStrings = uhash_open(uhash_hashUChars, // key hash function |
648 | uhash_compareUChars, // key comparison function | |
649 | NULL, // Value comparison function | |
650 | &status); | |
651 | fMzidToStrings = uhash_open(uhash_hashUChars, // key hash function | |
652 | uhash_compareUChars, // key comparison function | |
653 | NULL, // Value comparison function | |
654 | &status); | |
655 | uhash_setValueDeleter(fTzidToStrings, deleteZoneStrings); | |
656 | uhash_setValueDeleter(fMzidToStrings, deleteZoneStrings); | |
657 | } | |
46f4442e | 658 | |
729e4ab9 A |
659 | // Load only a single zone |
660 | void | |
661 | ZoneStringFormat::loadZone(const UnicodeString &utzid, UErrorCode &status) | |
662 | { | |
663 | if (fIsFullyLoaded) { | |
664 | return; | |
665 | } | |
666 | ||
667 | if (U_FAILURE(status)) { | |
668 | return; | |
669 | } | |
670 | ||
671 | umtx_lock(&ZoneStringFormatMutex); | |
672 | ||
673 | if (fZoneStringsArray == NULL) { | |
674 | fZoneStringsArray = ures_open(U_ICUDATA_ZONE, fLocale.getName(), &status); | |
675 | fZoneStringsArray = ures_getByKeyWithFallback(fZoneStringsArray, gZoneStringsTag, fZoneStringsArray, &status); | |
676 | if (U_FAILURE(status)) { | |
677 | // If no locale bundles are available, zoneStrings will be null. | |
678 | // We still want to go through the rest of zone strings initialization, | |
679 | // because generic location format is generated from tzid for the case. | |
680 | // The rest of code should work even zoneStrings is null. | |
681 | status = U_ZERO_ERROR; | |
682 | ures_close(fZoneStringsArray); | |
683 | fZoneStringsArray = NULL; | |
684 | } | |
685 | } | |
686 | ||
687 | // Skip non-canonical IDs | |
688 | UnicodeString canonicalID; | |
689 | TimeZone::getCanonicalID(utzid, canonicalID, status); | |
690 | if (U_FAILURE(status)) { | |
691 | // Ignore unknown ID - we should not get here, but just in case. | |
692 | // status = U_ZERO_ERROR; | |
693 | umtx_unlock(&ZoneStringFormatMutex); | |
694 | return; | |
695 | } | |
46f4442e | 696 | |
729e4ab9 A |
697 | if (U_SUCCESS(status)) { |
698 | if (uhash_count(fTzidToStrings) > 0) { | |
699 | ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); | |
700 | if (zstrings != NULL) { | |
701 | umtx_unlock(&ZoneStringFormatMutex); | |
702 | return; // We already about this one | |
703 | } | |
704 | } | |
705 | } | |
46f4442e | 706 | |
729e4ab9 | 707 | addSingleZone(canonicalID, status); |
46f4442e | 708 | |
729e4ab9 A |
709 | umtx_unlock(&ZoneStringFormatMutex); |
710 | } | |
711 | ||
712 | // Load only a single zone | |
713 | void | |
714 | ZoneStringFormat::addSingleZone(UnicodeString &utzid, UErrorCode &status) | |
715 | { | |
716 | if (U_FAILURE(status)) { | |
717 | return; | |
718 | } | |
46f4442e | 719 | |
729e4ab9 A |
720 | if (uhash_count(fTzidToStrings) > 0) { |
721 | ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, utzid.getTerminatedBuffer()); | |
722 | if (zstrings != NULL) { | |
723 | return; // We already about this one | |
724 | } | |
725 | } | |
726 | ||
727 | MessageFormat *fallbackFmt = NULL; | |
728 | MessageFormat *regionFmt = NULL; | |
46f4442e | 729 | |
729e4ab9 | 730 | fallbackFmt = getFallbackFormat(fLocale, status); |
46f4442e A |
731 | if (U_FAILURE(status)) { |
732 | goto error_cleanup; | |
733 | } | |
729e4ab9 | 734 | regionFmt = getRegionFormat(fLocale, status); |
46f4442e A |
735 | if (U_FAILURE(status)) { |
736 | goto error_cleanup; | |
737 | } | |
738 | ||
729e4ab9 A |
739 | |
740 | { | |
741 | char zidkey[ZID_KEY_MAX+1]; | |
742 | char tzid[ZID_KEY_MAX+1]; | |
743 | utzid.extract(0, utzid.length(), zidkey, ZID_KEY_MAX, US_INV); | |
744 | utzid.extract(0, utzid.length(), tzid, ZID_KEY_MAX, US_INV); | |
745 | ||
746 | const UChar *zstrarray[ZSIDX_COUNT]; | |
747 | const UChar *mzstrarray[ZSIDX_COUNT]; | |
748 | UnicodeString mzPartialLoc[MAX_METAZONES_PER_ZONE][4]; | |
749 | ||
750 | // Replace '/' with ':' | |
751 | char *pCity = NULL; | |
752 | char *p = zidkey; | |
753 | while (*p) { | |
754 | if (*p == '/') { | |
755 | *p = ':'; | |
756 | pCity = p + 1; | |
757 | } | |
758 | p++; | |
759 | } | |
760 | ||
761 | if (fZoneStringsArray != NULL) { | |
762 | fZoneItem = ures_getByKeyWithFallback(fZoneStringsArray, zidkey, fZoneItem, &status); | |
763 | if (U_FAILURE(status)) { | |
764 | // If failed to open the zone item, create only location string | |
765 | ures_close(fZoneItem); | |
766 | fZoneItem = NULL; | |
767 | status = U_ZERO_ERROR; | |
768 | } | |
769 | } | |
770 | ||
771 | UnicodeString region; | |
772 | getRegion(region); | |
773 | ||
774 | zstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(fZoneItem, gLongStandardTag); | |
775 | zstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(fZoneItem, gShortStandardTag); | |
776 | zstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(fZoneItem, gLongDaylightTag); | |
777 | zstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(fZoneItem, gShortDaylightTag); | |
778 | zstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(fZoneItem, gLongGenericTag); | |
779 | zstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(fZoneItem, gShortGenericTag); | |
780 | ||
781 | // Compose location format string | |
782 | UnicodeString location; | |
783 | UnicodeString country; | |
784 | UnicodeString city; | |
785 | UnicodeString countryCode; | |
786 | ZoneMeta::getCanonicalCountry(utzid, countryCode); | |
787 | if (!countryCode.isEmpty()) { | |
788 | const UChar* tmpCity = getZoneStringFromBundle(fZoneItem, gExemplarCityTag); | |
789 | if (tmpCity != NULL) { | |
790 | city.setTo(TRUE, tmpCity, -1); | |
791 | } else { | |
792 | city.setTo(UnicodeString(pCity, -1, US_INV)); | |
793 | // Replace '_' with ' ' | |
794 | for (int32_t i = 0; i < city.length(); i++) { | |
795 | if (city.charAt(i) == (UChar)0x5F /*'_'*/) { | |
796 | city.setCharAt(i, (UChar)0x20 /*' '*/); | |
797 | } | |
798 | } | |
799 | } | |
800 | getLocalizedCountry(countryCode, fLocale, country); | |
801 | UnicodeString singleCountry; | |
802 | ZoneMeta::getSingleCountry(utzid, singleCountry); | |
803 | FieldPosition fpos; | |
804 | if (singleCountry.isEmpty()) { | |
805 | Formattable params [] = { | |
806 | Formattable(city), | |
807 | Formattable(country) | |
808 | }; | |
809 | fallbackFmt->format(params, 2, location, fpos, status); | |
810 | } else { | |
811 | // If the zone is only one zone in the country, do not add city | |
812 | Formattable params [] = { | |
813 | Formattable(country) | |
814 | }; | |
815 | regionFmt->format(params, 1, location, fpos, status); | |
816 | } | |
817 | if (U_FAILURE(status)) { | |
818 | goto error_cleanup; | |
819 | } | |
820 | ||
821 | zstrarray[ZSIDX_LOCATION] = location.getTerminatedBuffer(); | |
822 | } else { | |
823 | if (uprv_strlen(tzid) > 4 && uprv_strncmp(tzid, "Etc/", 4) == 0) { | |
824 | // "Etc/xxx" is not associated with a specific location, so localized | |
825 | // GMT format is always used as generic location format. | |
826 | zstrarray[ZSIDX_LOCATION] = NULL; | |
827 | } else { | |
828 | // When a new time zone ID, which is actually associated with a specific | |
829 | // location, is added in tzdata, but the current CLDR data does not have | |
830 | // the information yet, ICU creates a generic location string based on | |
831 | // the ID. This implementation supports canonical time zone round trip | |
832 | // with format pattern "VVVV". See #6602 for the details. | |
833 | UnicodeString loc(utzid); | |
834 | int32_t slashIdx = loc.lastIndexOf((UChar)0x2f); | |
835 | if (slashIdx == -1) { | |
836 | // A time zone ID without slash in the tz database is not | |
837 | // associated with a specific location. For instances, | |
838 | // MET, CET, EET and WET fall into this category. | |
839 | // In this case, we still use GMT format as fallback. | |
840 | zstrarray[ZSIDX_LOCATION] = NULL; | |
841 | } else { | |
842 | FieldPosition fpos; | |
843 | Formattable params[] = { | |
844 | Formattable(loc) | |
845 | }; | |
846 | regionFmt->format(params, 1, location, fpos, status); | |
847 | if (U_FAILURE(status)) { | |
848 | goto error_cleanup; | |
849 | } | |
850 | zstrarray[ZSIDX_LOCATION] = location.getTerminatedBuffer(); | |
851 | } | |
852 | } | |
853 | } | |
854 | ||
855 | UBool commonlyUsed = isCommonlyUsed(fZoneItem); | |
856 | ||
857 | // Resolve metazones used by this zone | |
858 | int32_t mzPartialLocIdx = 0; | |
859 | const UVector *metazoneMappings = ZoneMeta::getMetazoneMappings(utzid); | |
860 | if (metazoneMappings != NULL) { | |
861 | for (int32_t i = 0; i < metazoneMappings->size(); i++) { | |
862 | const OlsonToMetaMappingEntry *mzmap = | |
863 | (const OlsonToMetaMappingEntry*)metazoneMappings->elementAt(i); | |
864 | UnicodeString mzid(mzmap->mzid); | |
865 | const ZoneStrings *mzStrings = | |
866 | (const ZoneStrings*)uhash_get(fMzidToStrings, mzid.getTerminatedBuffer()); | |
867 | if (mzStrings == NULL) { | |
868 | // If the metazone strings are not yet processed, do it now. | |
869 | char mzidkey[ZID_KEY_MAX]; | |
870 | uprv_strcpy(mzidkey, gMetazoneIdPrefix); | |
871 | u_UCharsToChars(mzmap->mzid, mzidkey + MZID_PREFIX_LEN, u_strlen(mzmap->mzid) + 1); | |
872 | fMetazoneItem = ures_getByKeyWithFallback(fZoneStringsArray, mzidkey, fMetazoneItem, &status); | |
873 | if (U_FAILURE(status)) { | |
874 | // No resources available for this metazone | |
875 | // Resource bundle will be cleaned up after end of the loop. | |
876 | status = U_ZERO_ERROR; | |
877 | continue; | |
878 | } | |
879 | UBool mzCommonlyUsed = isCommonlyUsed(fMetazoneItem); | |
880 | mzstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(fMetazoneItem, gLongStandardTag); | |
881 | mzstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(fMetazoneItem, gShortStandardTag); | |
882 | mzstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(fMetazoneItem, gLongDaylightTag); | |
883 | mzstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(fMetazoneItem, gShortDaylightTag); | |
884 | mzstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(fMetazoneItem, gLongGenericTag); | |
885 | mzstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(fMetazoneItem, gShortGenericTag); | |
886 | mzstrarray[ZSIDX_LOCATION] = NULL; | |
887 | ||
888 | int32_t lastNonNullIdx = ZSIDX_COUNT - 1; | |
889 | while (lastNonNullIdx >= 0) { | |
890 | if (mzstrarray[lastNonNullIdx] != NULL) { | |
891 | break; | |
892 | } | |
893 | lastNonNullIdx--; | |
894 | } | |
895 | UnicodeString *strings_mz = NULL; | |
896 | ZoneStrings *tmp_mzStrings = NULL; | |
897 | if (lastNonNullIdx >= 0) { | |
898 | // Create UnicodeString array and put strings to the zone string trie | |
899 | strings_mz = new UnicodeString[lastNonNullIdx + 1]; | |
900 | ||
901 | UnicodeString preferredIdForLocale; | |
902 | ZoneMeta::getZoneIdByMetazone(mzid, region, preferredIdForLocale); | |
903 | ||
904 | for (int32_t typeidx = 0; typeidx <= lastNonNullIdx; typeidx++) { | |
905 | if (mzstrarray[typeidx] != NULL) { | |
906 | strings_mz[typeidx].setTo(TRUE, mzstrarray[typeidx], -1); | |
907 | ||
908 | // Add a metazone string to the zone string trie | |
909 | int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)typeidx); | |
910 | ZoneStringInfo *zsinfo = new ZoneStringInfo( | |
911 | preferredIdForLocale, | |
912 | strings_mz[typeidx], | |
913 | (TimeZoneTranslationType)type, | |
914 | fStringPool, | |
915 | status); | |
916 | fZoneStringsTrie.put(strings_mz[typeidx], zsinfo, fStringPool, status); | |
917 | if (U_FAILURE(status)) { | |
918 | delete []strings_mz; | |
919 | goto error_cleanup; | |
920 | } | |
921 | } | |
922 | } | |
923 | // Note: ZoneStrings constructor adopts and deletes the strings_mz array. | |
924 | tmp_mzStrings = new ZoneStrings(strings_mz, lastNonNullIdx + 1, | |
925 | mzCommonlyUsed, NULL, 0, 0, fStringPool, status); | |
926 | } else { | |
927 | // Create ZoneStrings with empty contents | |
928 | tmp_mzStrings = new ZoneStrings(NULL, 0, FALSE, NULL, 0, 0, fStringPool, status); | |
929 | } | |
930 | ||
931 | UChar *umzid = const_cast<UChar *>(fStringPool.get(mzid, status)); | |
932 | uhash_put(fMzidToStrings, umzid, tmp_mzStrings, &status); | |
933 | if (U_FAILURE(status)) { | |
934 | goto error_cleanup; | |
935 | } | |
936 | ||
937 | mzStrings = tmp_mzStrings; | |
938 | } | |
939 | ||
940 | // Compose generic partial location format | |
941 | UnicodeString lg; | |
942 | UnicodeString sg; | |
943 | ||
944 | mzStrings->getString(ZSIDX_LONG_GENERIC, lg); | |
945 | mzStrings->getString(ZSIDX_SHORT_GENERIC, sg); | |
946 | ||
947 | if (!lg.isEmpty() || !sg.isEmpty()) { | |
948 | UBool addMzPartialLocationNames = TRUE; | |
949 | for (int32_t j = 0; j < mzPartialLocIdx; j++) { | |
950 | if (mzPartialLoc[j][0] == mzid) { | |
951 | // already processed | |
952 | addMzPartialLocationNames = FALSE; | |
953 | break; | |
954 | } | |
955 | } | |
956 | if (addMzPartialLocationNames) { | |
957 | UnicodeString *locationPart = NULL; | |
958 | // Check if the zone is the preferred zone for the territory associated with the zone | |
959 | UnicodeString preferredID; | |
960 | ZoneMeta::getZoneIdByMetazone(mzid, countryCode, preferredID); | |
961 | if (utzid == preferredID) { | |
962 | // Use country for the location | |
963 | locationPart = &country; | |
964 | } else { | |
965 | // Use city for the location | |
966 | locationPart = &city; | |
967 | } | |
968 | // Reset the partial location string array | |
969 | mzPartialLoc[mzPartialLocIdx][0].setTo(mzid); | |
970 | mzPartialLoc[mzPartialLocIdx][1].remove(); | |
971 | mzPartialLoc[mzPartialLocIdx][2].remove(); | |
972 | mzPartialLoc[mzPartialLocIdx][3].remove(); | |
973 | ||
974 | if (locationPart->length() != 0) { | |
975 | FieldPosition fpos; | |
976 | if (!lg.isEmpty()) { | |
977 | Formattable params [] = { | |
978 | Formattable(*locationPart), | |
979 | Formattable(lg) | |
980 | }; | |
981 | fallbackFmt->format(params, 2, mzPartialLoc[mzPartialLocIdx][1], fpos, status); | |
982 | } | |
983 | if (!sg.isEmpty()) { | |
984 | Formattable params [] = { | |
985 | Formattable(*locationPart), | |
986 | Formattable(sg) | |
987 | }; | |
988 | fallbackFmt->format(params, 2, mzPartialLoc[mzPartialLocIdx][2], fpos, status); | |
989 | if (mzStrings->isShortFormatCommonlyUsed()) { | |
990 | mzPartialLoc[mzPartialLocIdx][3].setTo(TRUE, gCommonlyUsedTrue, -1); | |
991 | } | |
992 | } | |
993 | if (U_FAILURE(status)) { | |
994 | goto error_cleanup; | |
995 | } | |
996 | } | |
997 | mzPartialLocIdx++; | |
998 | } | |
999 | } | |
1000 | } | |
1001 | } | |
1002 | // Collected names for a zone | |
1003 | ||
1004 | // Create UnicodeString array for localized zone strings | |
1005 | int32_t lastIdx = ZSIDX_COUNT - 1; | |
1006 | while (lastIdx >= 0) { | |
1007 | if (zstrarray[lastIdx] != NULL) { | |
1008 | break; | |
1009 | } | |
1010 | lastIdx--; | |
1011 | } | |
1012 | UnicodeString *strings = NULL; | |
1013 | int32_t stringsCount = lastIdx + 1; | |
1014 | ||
1015 | if (stringsCount > 0) { | |
1016 | strings = new UnicodeString[stringsCount]; | |
1017 | for (int32_t i = 0; i < stringsCount; i++) { | |
1018 | if (zstrarray[i] != NULL) { | |
1019 | strings[i].setTo(zstrarray[i], -1); | |
1020 | ||
1021 | // Add names to the trie | |
1022 | int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)i); | |
1023 | ZoneStringInfo *zsinfo = new ZoneStringInfo(utzid, | |
1024 | strings[i], | |
1025 | (TimeZoneTranslationType)type, | |
1026 | fStringPool, | |
1027 | status); | |
1028 | fZoneStringsTrie.put(strings[i], zsinfo, fStringPool, status); | |
1029 | if (U_FAILURE(status)) { | |
1030 | delete zsinfo; | |
1031 | delete[] strings; | |
1032 | goto error_cleanup; | |
1033 | } | |
1034 | } | |
1035 | } | |
1036 | } | |
1037 | ||
1038 | // Create UnicodeString array for generic partial location strings | |
1039 | UnicodeString **genericPartialLocationNames = NULL; | |
1040 | int32_t genericPartialRowCount = mzPartialLocIdx; | |
1041 | int32_t genericPartialColCount = 4; | |
1042 | ||
1043 | if (genericPartialRowCount != 0) { | |
1044 | genericPartialLocationNames = | |
1045 | (UnicodeString**)uprv_malloc(genericPartialRowCount * sizeof(UnicodeString*)); | |
1046 | if (genericPartialLocationNames == NULL) { | |
1047 | status = U_MEMORY_ALLOCATION_ERROR; | |
1048 | delete[] strings; | |
1049 | goto error_cleanup; | |
1050 | } | |
1051 | for (int32_t i = 0; i < genericPartialRowCount; i++) { | |
1052 | genericPartialLocationNames[i] = new UnicodeString[genericPartialColCount]; | |
1053 | for (int32_t j = 0; j < genericPartialColCount; j++) { | |
1054 | genericPartialLocationNames[i][j].setTo(mzPartialLoc[i][j]); | |
1055 | // Add names to the trie | |
1056 | if ((j == 1 || j == 2) &&!genericPartialLocationNames[i][j].isEmpty()) { | |
1057 | ZoneStringInfo *zsinfo; | |
1058 | TimeZoneTranslationType type = (j == 1) ? GENERIC_LONG : GENERIC_SHORT; | |
1059 | zsinfo = new ZoneStringInfo(utzid, genericPartialLocationNames[i][j], type, | |
1060 | fStringPool, status); | |
1061 | fZoneStringsTrie.put(genericPartialLocationNames[i][j], zsinfo, fStringPool, status); | |
1062 | if (U_FAILURE(status)) { | |
1063 | delete[] genericPartialLocationNames[i]; | |
1064 | uprv_free(genericPartialLocationNames); | |
1065 | delete[] strings; | |
1066 | goto error_cleanup; | |
1067 | } | |
1068 | } | |
1069 | } | |
1070 | } | |
1071 | } | |
1072 | ||
1073 | // Finally, create ZoneStrings instance and put it into the tzidToStinrgs map | |
1074 | ZoneStrings *zstrings = new ZoneStrings(strings, stringsCount, commonlyUsed, | |
1075 | genericPartialLocationNames, genericPartialRowCount, | |
1076 | genericPartialColCount, fStringPool, status); | |
1077 | ||
1078 | UChar *uutzid = const_cast<UChar *>(fStringPool.get(utzid, status)); | |
1079 | uhash_put(fTzidToStrings, uutzid, zstrings, &status); | |
1080 | if (U_FAILURE(status)) { | |
1081 | delete zstrings; | |
1082 | goto error_cleanup; | |
1083 | } | |
1084 | } | |
1085 | ||
1086 | error_cleanup: | |
1087 | if (fallbackFmt != NULL) { | |
1088 | delete fallbackFmt; | |
1089 | } | |
1090 | if (regionFmt != NULL) { | |
1091 | delete regionFmt; | |
1092 | } | |
1093 | // fStringPool.freeze(); | |
1094 | } | |
1095 | ||
1096 | void | |
1097 | ZoneStringFormat::loadFull(UErrorCode &status) | |
1098 | { | |
1099 | if (U_FAILURE(status)) { | |
1100 | return; | |
1101 | } | |
1102 | if (fIsFullyLoaded) { | |
1103 | return; | |
1104 | } | |
1105 | ||
1106 | umtx_lock(&ZoneStringFormatMutex); | |
1107 | ||
1108 | if (fZoneStringsArray == NULL) { | |
1109 | fZoneStringsArray = ures_open(U_ICUDATA_ZONE, fLocale.getName(), &status); | |
1110 | fZoneStringsArray = ures_getByKeyWithFallback(fZoneStringsArray, gZoneStringsTag, fZoneStringsArray, &status); | |
1111 | if (U_FAILURE(status)) { | |
1112 | // If no locale bundles are available, zoneStrings will be null. | |
1113 | // We still want to go through the rest of zone strings initialization, | |
1114 | // because generic location format is generated from tzid for the case. | |
1115 | // The rest of code should work even zoneStrings is null. | |
1116 | status = U_ZERO_ERROR; | |
1117 | ures_close(fZoneStringsArray); | |
1118 | fZoneStringsArray = NULL; | |
1119 | } | |
1120 | } | |
1121 | ||
1122 | StringEnumeration *tzids = NULL; | |
1123 | ||
46f4442e A |
1124 | tzids = TimeZone::createEnumeration(); |
1125 | const char *tzid; | |
1126 | while ((tzid = tzids->next(NULL, status))) { | |
1127 | if (U_FAILURE(status)) { | |
1128 | goto error_cleanup; | |
1129 | } | |
1130 | // Skip non-canonical IDs | |
1131 | UnicodeString utzid(tzid, -1, US_INV); | |
1132 | UnicodeString canonicalID; | |
1133 | TimeZone::getCanonicalID(utzid, canonicalID, status); | |
1134 | if (U_FAILURE(status)) { | |
1135 | // Ignore unknown ID - we should not get here, but just in case. | |
1136 | status = U_ZERO_ERROR; | |
1137 | continue; | |
1138 | } | |
46f4442e | 1139 | |
729e4ab9 A |
1140 | if (U_SUCCESS(status)) { |
1141 | if (uhash_count(fTzidToStrings) > 0) { | |
1142 | ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); | |
1143 | if (zstrings != NULL) { | |
1144 | continue; // We already about this one | |
1145 | } | |
1146 | } | |
1147 | } | |
1148 | ||
1149 | addSingleZone(canonicalID, status); | |
1150 | ||
46f4442e | 1151 | if (U_FAILURE(status)) { |
729e4ab9 A |
1152 | goto error_cleanup; |
1153 | } | |
46f4442e A |
1154 | } |
1155 | ||
729e4ab9 A |
1156 | fIsFullyLoaded = TRUE; |
1157 | ||
46f4442e | 1158 | error_cleanup: |
46f4442e A |
1159 | if (tzids != NULL) { |
1160 | delete tzids; | |
1161 | } | |
729e4ab9 A |
1162 | fStringPool.freeze(); |
1163 | ||
1164 | umtx_unlock(&ZoneStringFormatMutex); | |
46f4442e A |
1165 | } |
1166 | ||
729e4ab9 | 1167 | |
46f4442e | 1168 | ZoneStringFormat::~ZoneStringFormat() { |
729e4ab9 A |
1169 | uhash_close(fTzidToStrings); |
1170 | uhash_close(fMzidToStrings); | |
1171 | ures_close(fZoneItem); | |
1172 | ures_close(fMetazoneItem); | |
1173 | ures_close(fZoneStringsArray); | |
46f4442e A |
1174 | } |
1175 | ||
1176 | SafeZoneStringFormatPtr* | |
1177 | ZoneStringFormat::getZoneStringFormat(const Locale& locale, UErrorCode &status) { | |
1178 | umtx_lock(&gZSFCacheLock); | |
1179 | if (gZoneStringFormatCache == NULL) { | |
1180 | gZoneStringFormatCache = new ZSFCache(10 /* capacity */); | |
1181 | ucln_i18n_registerCleanup(UCLN_I18N_ZSFORMAT, zoneStringFormat_cleanup); | |
1182 | } | |
1183 | umtx_unlock(&gZSFCacheLock); | |
1184 | ||
1185 | return gZoneStringFormatCache->get(locale, status); | |
1186 | } | |
1187 | ||
1188 | ||
1189 | UnicodeString** | |
1190 | ZoneStringFormat::createZoneStringsArray(UDate date, int32_t &rowCount, int32_t &colCount, UErrorCode &status) const { | |
1191 | if (U_FAILURE(status)) { | |
1192 | return NULL; | |
1193 | } | |
729e4ab9 A |
1194 | ZoneStringFormat *nonConstThis = const_cast<ZoneStringFormat *>(this); |
1195 | nonConstThis->loadFull(status); | |
1196 | ||
46f4442e A |
1197 | UnicodeString **result = NULL; |
1198 | rowCount = 0; | |
1199 | colCount = 0; | |
1200 | ||
1201 | // Collect canonical time zone IDs | |
1202 | UVector canonicalIDs(uhash_deleteUnicodeString, uhash_compareUnicodeString, status); | |
1203 | if (U_FAILURE(status)) { | |
1204 | return NULL; | |
1205 | } | |
1206 | StringEnumeration *tzids = TimeZone::createEnumeration(); | |
1207 | const UChar *tzid; | |
1208 | while ((tzid = tzids->unext(NULL, status))) { | |
1209 | if (U_FAILURE(status)) { | |
1210 | delete tzids; | |
1211 | return NULL; | |
1212 | } | |
1213 | UnicodeString utzid(tzid); | |
1214 | UnicodeString canonicalID; | |
1215 | TimeZone::getCanonicalID(UnicodeString(tzid), canonicalID, status); | |
1216 | if (U_FAILURE(status)) { | |
1217 | // Ignore unknown ID - we should not get here, but just in case. | |
1218 | status = U_ZERO_ERROR; | |
1219 | continue; | |
1220 | } | |
1221 | if (utzid == canonicalID) { | |
1222 | canonicalIDs.addElement(new UnicodeString(utzid), status); | |
1223 | if (U_FAILURE(status)) { | |
1224 | delete tzids; | |
1225 | return NULL; | |
1226 | } | |
1227 | } | |
1228 | } | |
1229 | delete tzids; | |
1230 | ||
1231 | // Allocate array | |
1232 | result = (UnicodeString**)uprv_malloc(canonicalIDs.size() * sizeof(UnicodeString*)); | |
1233 | if (result == NULL) { | |
1234 | status = U_MEMORY_ALLOCATION_ERROR; | |
1235 | return NULL; | |
1236 | } | |
1237 | for (int32_t i = 0; i < canonicalIDs.size(); i++) { | |
1238 | result[i] = new UnicodeString[8]; | |
1239 | UnicodeString *id = (UnicodeString*)canonicalIDs.elementAt(i); | |
1240 | result[i][0].setTo(*id); | |
1241 | getLongStandard(*id, date, result[i][1]); | |
1242 | getShortStandard(*id, date, FALSE, result[i][2]); | |
1243 | getLongDaylight(*id, date, result[i][3]); | |
1244 | getShortDaylight(*id, date, FALSE, result[i][4]); | |
1245 | getGenericLocation(*id, result[i][5]); | |
1246 | getLongGenericNonLocation(*id, date, result[i][6]); | |
1247 | getShortGenericNonLocation(*id, date, FALSE, result[i][7]); | |
1248 | } | |
1249 | ||
1250 | rowCount = canonicalIDs.size(); | |
1251 | colCount = 8; | |
1252 | return result; | |
1253 | } | |
1254 | ||
1255 | UnicodeString& | |
1256 | ZoneStringFormat::getSpecificLongString(const Calendar &cal, UnicodeString &result, | |
1257 | UErrorCode &status) const { | |
1258 | result.remove(); | |
1259 | if (U_FAILURE(status)) { | |
1260 | return result; | |
1261 | } | |
1262 | UnicodeString tzid; | |
1263 | cal.getTimeZone().getID(tzid); | |
1264 | UDate date = cal.getTime(status); | |
1265 | if (cal.get(UCAL_DST_OFFSET, status) == 0) { | |
1266 | return getString(tzid, ZSIDX_LONG_STANDARD, date, FALSE /*not used*/, result); | |
1267 | } else { | |
1268 | return getString(tzid, ZSIDX_LONG_DAYLIGHT, date, FALSE /*not used*/, result); | |
1269 | } | |
1270 | } | |
1271 | ||
1272 | UnicodeString& | |
1273 | ZoneStringFormat::getSpecificShortString(const Calendar &cal, UBool commonlyUsedOnly, | |
1274 | UnicodeString &result, UErrorCode &status) const { | |
1275 | result.remove(); | |
1276 | if (U_FAILURE(status)) { | |
1277 | return result; | |
1278 | } | |
1279 | UnicodeString tzid; | |
1280 | cal.getTimeZone().getID(tzid); | |
1281 | UDate date = cal.getTime(status); | |
1282 | if (cal.get(UCAL_DST_OFFSET, status) == 0) { | |
1283 | return getString(tzid, ZSIDX_SHORT_STANDARD, date, commonlyUsedOnly, result); | |
1284 | } else { | |
1285 | return getString(tzid, ZSIDX_SHORT_DAYLIGHT, date, commonlyUsedOnly, result); | |
1286 | } | |
1287 | } | |
1288 | ||
1289 | UnicodeString& | |
1290 | ZoneStringFormat::getGenericLongString(const Calendar &cal, UnicodeString &result, | |
1291 | UErrorCode &status) const { | |
1292 | return getGenericString(cal, FALSE /*long*/, FALSE /* not used */, result, status); | |
1293 | } | |
1294 | ||
1295 | UnicodeString& | |
1296 | ZoneStringFormat::getGenericShortString(const Calendar &cal, UBool commonlyUsedOnly, | |
1297 | UnicodeString &result, UErrorCode &status) const { | |
1298 | return getGenericString(cal, TRUE /*short*/, commonlyUsedOnly, result, status); | |
1299 | } | |
1300 | ||
1301 | UnicodeString& | |
1302 | ZoneStringFormat::getGenericLocationString(const Calendar &cal, UnicodeString &result, | |
1303 | UErrorCode &status) const { | |
1304 | UnicodeString tzid; | |
1305 | cal.getTimeZone().getID(tzid); | |
1306 | UDate date = cal.getTime(status); | |
1307 | return getString(tzid, ZSIDX_LOCATION, date, FALSE /*not used*/, result); | |
1308 | } | |
1309 | ||
1310 | const ZoneStringInfo* | |
1311 | ZoneStringFormat::findSpecificLong(const UnicodeString &text, int32_t start, | |
1312 | int32_t &matchLength, UErrorCode &status) const { | |
1313 | return find(text, start, STANDARD_LONG | DAYLIGHT_LONG, matchLength, status); | |
1314 | } | |
1315 | ||
1316 | const ZoneStringInfo* | |
1317 | ZoneStringFormat::findSpecificShort(const UnicodeString &text, int32_t start, | |
1318 | int32_t &matchLength, UErrorCode &status) const { | |
1319 | return find(text, start, STANDARD_SHORT | DAYLIGHT_SHORT, matchLength, status); | |
1320 | } | |
1321 | ||
1322 | const ZoneStringInfo* | |
1323 | ZoneStringFormat::findGenericLong(const UnicodeString &text, int32_t start, | |
1324 | int32_t &matchLength, UErrorCode &status) const { | |
1325 | return find(text, start, GENERIC_LONG | STANDARD_LONG | LOCATION, matchLength, status); | |
1326 | } | |
1327 | ||
1328 | const ZoneStringInfo* | |
1329 | ZoneStringFormat::findGenericShort(const UnicodeString &text, int32_t start, | |
1330 | int32_t &matchLength, UErrorCode &status) const { | |
1331 | return find(text, start, GENERIC_SHORT | STANDARD_SHORT | LOCATION, matchLength, status); | |
1332 | } | |
1333 | ||
1334 | const ZoneStringInfo* | |
1335 | ZoneStringFormat::findGenericLocation(const UnicodeString &text, int32_t start, | |
1336 | int32_t &matchLength, UErrorCode &status) const { | |
1337 | return find(text, start, LOCATION, matchLength, status); | |
1338 | } | |
1339 | ||
1340 | UnicodeString& | |
1341 | ZoneStringFormat::getString(const UnicodeString &tzid, TimeZoneTranslationTypeIndex typeIdx, UDate date, | |
1342 | UBool commonlyUsedOnly, UnicodeString& result) const { | |
729e4ab9 | 1343 | UErrorCode status = U_ZERO_ERROR; |
46f4442e | 1344 | result.remove(); |
729e4ab9 A |
1345 | if (!fIsFullyLoaded) { |
1346 | // Lazy loading | |
1347 | ZoneStringFormat *nonConstThis = const_cast<ZoneStringFormat *>(this); | |
1348 | nonConstThis->loadZone(tzid, status); | |
1349 | } | |
46f4442e A |
1350 | |
1351 | // ICU's own array does not have entries for aliases | |
1352 | UnicodeString canonicalID; | |
46f4442e A |
1353 | TimeZone::getCanonicalID(tzid, canonicalID, status); |
1354 | if (U_FAILURE(status)) { | |
1355 | // Unknown ID, but users might have their own data. | |
1356 | canonicalID.setTo(tzid); | |
1357 | } | |
1358 | ||
729e4ab9 A |
1359 | if (uhash_count(fTzidToStrings) > 0) { |
1360 | ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); | |
46f4442e A |
1361 | if (zstrings != NULL) { |
1362 | switch (typeIdx) { | |
1363 | case ZSIDX_LONG_STANDARD: | |
1364 | case ZSIDX_LONG_DAYLIGHT: | |
1365 | case ZSIDX_LONG_GENERIC: | |
1366 | case ZSIDX_LOCATION: | |
1367 | zstrings->getString(typeIdx, result); | |
1368 | break; | |
1369 | case ZSIDX_SHORT_STANDARD: | |
1370 | case ZSIDX_SHORT_DAYLIGHT: | |
1371 | case ZSIDX_COUNT: //added to avoid warning | |
1372 | case ZSIDX_SHORT_GENERIC: | |
1373 | if (!commonlyUsedOnly || zstrings->isShortFormatCommonlyUsed()) { | |
1374 | zstrings->getString(typeIdx, result); | |
1375 | } | |
1376 | break; | |
729e4ab9 A |
1377 | default: |
1378 | break; | |
46f4442e A |
1379 | } |
1380 | } | |
1381 | } | |
729e4ab9 | 1382 | if (result.isEmpty() && uhash_count(fMzidToStrings) > 0 && typeIdx != ZSIDX_LOCATION) { |
46f4442e A |
1383 | // Try metazone |
1384 | UnicodeString mzid; | |
1385 | ZoneMeta::getMetazoneID(canonicalID, date, mzid); | |
1386 | if (!mzid.isEmpty()) { | |
729e4ab9 | 1387 | ZoneStrings *mzstrings = (ZoneStrings*)uhash_get(fMzidToStrings, mzid.getTerminatedBuffer()); |
46f4442e A |
1388 | if (mzstrings != NULL) { |
1389 | switch (typeIdx) { | |
1390 | case ZSIDX_LONG_STANDARD: | |
1391 | case ZSIDX_LONG_DAYLIGHT: | |
1392 | case ZSIDX_LONG_GENERIC: | |
1393 | case ZSIDX_LOCATION: | |
1394 | mzstrings->getString(typeIdx, result); | |
1395 | break; | |
1396 | case ZSIDX_SHORT_STANDARD: | |
1397 | case ZSIDX_SHORT_DAYLIGHT: | |
1398 | case ZSIDX_COUNT: //added to avoid warning | |
1399 | case ZSIDX_SHORT_GENERIC: | |
1400 | if (!commonlyUsedOnly || mzstrings->isShortFormatCommonlyUsed()) { | |
1401 | mzstrings->getString(typeIdx, result); | |
1402 | } | |
1403 | break; | |
729e4ab9 A |
1404 | default: |
1405 | break; | |
46f4442e A |
1406 | } |
1407 | } | |
1408 | } | |
1409 | } | |
1410 | return result; | |
1411 | } | |
1412 | ||
1413 | UnicodeString& | |
1414 | ZoneStringFormat::getGenericString(const Calendar &cal, UBool isShort, UBool commonlyUsedOnly, | |
1415 | UnicodeString &result, UErrorCode &status) const { | |
1416 | result.remove(); | |
1417 | UDate time = cal.getTime(status); | |
1418 | if (U_FAILURE(status)) { | |
1419 | return result; | |
1420 | } | |
1421 | const TimeZone &tz = cal.getTimeZone(); | |
1422 | UnicodeString tzid; | |
1423 | tz.getID(tzid); | |
1424 | ||
729e4ab9 A |
1425 | if (!fIsFullyLoaded) { |
1426 | // Lazy loading | |
1427 | ZoneStringFormat *nonConstThis = const_cast<ZoneStringFormat *>(this); | |
1428 | nonConstThis->loadZone(tzid, status); | |
1429 | } | |
1430 | ||
46f4442e A |
1431 | // ICU's own array does not have entries for aliases |
1432 | UnicodeString canonicalID; | |
1433 | TimeZone::getCanonicalID(tzid, canonicalID, status); | |
1434 | if (U_FAILURE(status)) { | |
1435 | // Unknown ID, but users might have their own data. | |
1436 | status = U_ZERO_ERROR; | |
1437 | canonicalID.setTo(tzid); | |
1438 | } | |
1439 | ||
1440 | ZoneStrings *zstrings = NULL; | |
729e4ab9 A |
1441 | if (uhash_count(fTzidToStrings) > 0) { |
1442 | zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); | |
46f4442e A |
1443 | if (zstrings != NULL) { |
1444 | if (isShort) { | |
1445 | if (!commonlyUsedOnly || zstrings->isShortFormatCommonlyUsed()) { | |
1446 | zstrings->getString(ZSIDX_SHORT_GENERIC, result); | |
1447 | } | |
1448 | } else { | |
1449 | zstrings->getString(ZSIDX_LONG_GENERIC, result); | |
1450 | } | |
1451 | } | |
1452 | } | |
729e4ab9 | 1453 | if (result.isEmpty() && uhash_count(fMzidToStrings) > 0) { |
46f4442e A |
1454 | // try metazone |
1455 | int32_t raw, sav; | |
1456 | UnicodeString mzid; | |
1457 | ZoneMeta::getMetazoneID(canonicalID, time, mzid); | |
1458 | if (!mzid.isEmpty()) { | |
1459 | UBool useStandard = FALSE; | |
1460 | sav = cal.get(UCAL_DST_OFFSET, status); | |
1461 | if (U_FAILURE(status)) { | |
1462 | return result; | |
1463 | } | |
1464 | if (sav == 0) { | |
1465 | useStandard = TRUE; | |
1466 | // Check if the zone actually uses daylight saving time around the time | |
1467 | TimeZone *tmptz = tz.clone(); | |
1468 | BasicTimeZone *btz = NULL; | |
729e4ab9 A |
1469 | if (dynamic_cast<OlsonTimeZone *>(tmptz) != NULL |
1470 | || dynamic_cast<SimpleTimeZone *>(tmptz) != NULL | |
1471 | || dynamic_cast<RuleBasedTimeZone *>(tmptz) != NULL | |
1472 | || dynamic_cast<VTimeZone *>(tmptz) != NULL) { | |
46f4442e A |
1473 | btz = (BasicTimeZone*)tmptz; |
1474 | } | |
1475 | ||
1476 | if (btz != NULL) { | |
1477 | TimeZoneTransition before; | |
1478 | UBool beforTrs = btz->getPreviousTransition(time, TRUE, before); | |
1479 | if (beforTrs | |
1480 | && (time - before.getTime() < kDstCheckRange) | |
1481 | && before.getFrom()->getDSTSavings() != 0) { | |
1482 | useStandard = FALSE; | |
1483 | } else { | |
1484 | TimeZoneTransition after; | |
1485 | UBool afterTrs = btz->getNextTransition(time, FALSE, after); | |
1486 | if (afterTrs | |
1487 | && (after.getTime() - time < kDstCheckRange) | |
1488 | && after.getTo()->getDSTSavings() != 0) { | |
1489 | useStandard = FALSE; | |
1490 | } | |
1491 | } | |
1492 | } else { | |
1493 | // If not BasicTimeZone... only if the instance is not an ICU's implementation. | |
1494 | // We may get a wrong answer in edge case, but it should practically work OK. | |
1495 | tmptz->getOffset(time - kDstCheckRange, FALSE, raw, sav, status); | |
1496 | if (sav != 0) { | |
1497 | useStandard = FALSE; | |
1498 | } else { | |
1499 | tmptz->getOffset(time + kDstCheckRange, FALSE, raw, sav, status); | |
1500 | if (sav != 0){ | |
1501 | useStandard = FALSE; | |
1502 | } | |
1503 | } | |
1504 | if (U_FAILURE(status)) { | |
1505 | delete tmptz; | |
1506 | result.remove(); | |
1507 | return result; | |
1508 | } | |
1509 | } | |
1510 | delete tmptz; | |
1511 | } | |
1512 | if (useStandard) { | |
1513 | getString(canonicalID, (isShort ? ZSIDX_SHORT_STANDARD : ZSIDX_LONG_STANDARD), | |
1514 | time, commonlyUsedOnly, result); | |
1515 | ||
1516 | // Note: | |
1517 | // In CLDR 1.5.1, a same localization is used for both generic and standard | |
1518 | // for some metazones in some locales. This is actually data bugs and should | |
1519 | // be resolved in later versions of CLDR. For now, we check if the standard | |
1520 | // name is different from its generic name below. | |
1521 | if (!result.isEmpty()) { | |
1522 | UnicodeString genericNonLocation; | |
1523 | getString(canonicalID, (isShort ? ZSIDX_SHORT_GENERIC : ZSIDX_LONG_GENERIC), | |
1524 | time, commonlyUsedOnly, genericNonLocation); | |
1525 | if (!genericNonLocation.isEmpty() && result == genericNonLocation) { | |
1526 | result.remove(); | |
1527 | } | |
1528 | } | |
1529 | } | |
1530 | if (result.isEmpty()) { | |
729e4ab9 | 1531 | ZoneStrings *mzstrings = (ZoneStrings*)uhash_get(fMzidToStrings, mzid.getTerminatedBuffer()); |
46f4442e A |
1532 | if (mzstrings != NULL) { |
1533 | if (isShort) { | |
1534 | if (!commonlyUsedOnly || mzstrings->isShortFormatCommonlyUsed()) { | |
1535 | mzstrings->getString(ZSIDX_SHORT_GENERIC, result); | |
1536 | } | |
1537 | } else { | |
1538 | mzstrings->getString(ZSIDX_LONG_GENERIC, result); | |
1539 | } | |
1540 | } | |
1541 | if (!result.isEmpty()) { | |
1542 | // Check if the offsets at the given time matches the preferred zone's offsets | |
1543 | UnicodeString preferredId; | |
1544 | UnicodeString region; | |
1545 | ZoneMeta::getZoneIdByMetazone(mzid, getRegion(region), preferredId); | |
1546 | if (canonicalID != preferredId) { | |
1547 | // Check if the offsets at the given time are identical with the preferred zone | |
1548 | raw = cal.get(UCAL_ZONE_OFFSET, status); | |
1549 | if (U_FAILURE(status)) { | |
1550 | result.remove(); | |
1551 | return result; | |
1552 | } | |
1553 | TimeZone *preferredZone = TimeZone::createTimeZone(preferredId); | |
1554 | int32_t prfRaw, prfSav; | |
1555 | // Check offset in preferred time zone with wall time. | |
1556 | // With getOffset(time, false, preferredOffsets), | |
1557 | // you may get incorrect results because of time overlap at DST->STD | |
1558 | // transition. | |
1559 | preferredZone->getOffset(time + raw + sav, TRUE, prfRaw, prfSav, status); | |
1560 | delete preferredZone; | |
1561 | ||
1562 | if (U_FAILURE(status)) { | |
1563 | result.remove(); | |
1564 | return result; | |
1565 | } | |
1566 | if ((raw != prfRaw || sav != prfSav) && zstrings != NULL) { | |
1567 | // Use generic partial location string as fallback | |
1568 | zstrings->getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly, result); | |
1569 | } | |
1570 | } | |
1571 | } | |
1572 | } | |
1573 | } | |
1574 | } | |
1575 | if (result.isEmpty()) { | |
1576 | // Use location format as the final fallback | |
1577 | getString(canonicalID, ZSIDX_LOCATION, time, FALSE /*not used*/, result); | |
1578 | } | |
1579 | ||
1580 | return result; | |
1581 | } | |
1582 | ||
1583 | UnicodeString& | |
1584 | ZoneStringFormat::getGenericPartialLocationString(const UnicodeString &tzid, UBool isShort, | |
1585 | UDate date, UBool commonlyUsedOnly, UnicodeString &result) const { | |
729e4ab9 | 1586 | UErrorCode status = U_ZERO_ERROR; |
46f4442e | 1587 | result.remove(); |
729e4ab9 A |
1588 | if (!fIsFullyLoaded) { |
1589 | // Lazy loading | |
1590 | ZoneStringFormat *nonConstThis = const_cast<ZoneStringFormat *>(this); | |
1591 | nonConstThis->loadZone(tzid, status); | |
1592 | } | |
1593 | ||
1594 | if (uhash_count(fTzidToStrings) <= 0) { | |
46f4442e A |
1595 | return result; |
1596 | } | |
1597 | ||
1598 | UnicodeString canonicalID; | |
46f4442e A |
1599 | TimeZone::getCanonicalID(tzid, canonicalID, status); |
1600 | if (U_FAILURE(status)) { | |
1601 | // Unknown ID, so no corresponding meta data. | |
1602 | return result; | |
1603 | } | |
1604 | ||
1605 | UnicodeString mzid; | |
1606 | ZoneMeta::getMetazoneID(canonicalID, date, mzid); | |
1607 | ||
1608 | if (!mzid.isEmpty()) { | |
729e4ab9 | 1609 | ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); |
46f4442e A |
1610 | if (zstrings != NULL) { |
1611 | zstrings->getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly, result); | |
1612 | } | |
1613 | } | |
1614 | return result; | |
1615 | } | |
1616 | ||
729e4ab9 | 1617 | // This method does lazy zone string loading |
46f4442e A |
1618 | const ZoneStringInfo* |
1619 | ZoneStringFormat::find(const UnicodeString &text, int32_t start, int32_t types, | |
1620 | int32_t &matchLength, UErrorCode &status) const { | |
729e4ab9 A |
1621 | |
1622 | if (U_FAILURE(status)) { | |
1623 | return NULL; | |
1624 | } | |
1625 | ||
1626 | const ZoneStringInfo * result = subFind(text, start, types, matchLength, status); | |
1627 | if (fIsFullyLoaded) { | |
1628 | return result; | |
1629 | } | |
1630 | // When zone string data is partially loaded, | |
1631 | // this method return the result only when | |
1632 | // the input text is fully consumed. | |
1633 | if (result != NULL) { | |
1634 | UnicodeString tmpString; | |
1635 | matchLength = (result->getString(tmpString)).length(); | |
1636 | if (text.length() - start == matchLength) { | |
1637 | return result; | |
1638 | } | |
1639 | } | |
1640 | ||
1641 | // Now load all zone strings | |
1642 | ZoneStringFormat *nonConstThis = const_cast<ZoneStringFormat *>(this); | |
1643 | nonConstThis->loadFull(status); | |
1644 | ||
1645 | return subFind(text, start, types, matchLength, status); | |
1646 | } | |
1647 | ||
1648 | ||
1649 | /* | |
1650 | * Find a prefix matching time zone for the given zone string types. | |
1651 | * @param text The text contains a time zone string | |
1652 | * @param start The start index within the text | |
1653 | * @param types The bit mask representing a set of requested types | |
1654 | * @return If any zone string matched for the requested types, returns a | |
1655 | * ZoneStringInfo for the longest match. If no matches are found for | |
1656 | * the requested types, returns a ZoneStringInfo for the longest match | |
1657 | * for any other types. If nothing matches at all, returns null. | |
1658 | */ | |
1659 | const ZoneStringInfo* | |
1660 | ZoneStringFormat::subFind(const UnicodeString &text, int32_t start, int32_t types, | |
1661 | int32_t &matchLength, UErrorCode &status) const { | |
46f4442e A |
1662 | matchLength = 0; |
1663 | if (U_FAILURE(status)) { | |
1664 | return NULL; | |
1665 | } | |
1666 | if (fZoneStringsTrie.isEmpty()) { | |
1667 | return NULL; | |
1668 | } | |
729e4ab9 | 1669 | |
46f4442e A |
1670 | const ZoneStringInfo *result = NULL; |
1671 | const ZoneStringInfo *fallback = NULL; | |
1672 | int32_t fallbackMatchLen = 0; | |
1673 | ||
1674 | ZoneStringSearchResultHandler handler(status); | |
1675 | fZoneStringsTrie.search(text, start, (TextTrieMapSearchResultHandler*)&handler, status); | |
1676 | if (U_SUCCESS(status)) { | |
1677 | int32_t numMatches = handler.countMatches(); | |
1678 | for (int32_t i = 0; i < numMatches; i++) { | |
729e4ab9 | 1679 | int32_t tmpMatchLen = 0; // init. output only param to silence gcc |
46f4442e A |
1680 | const ZoneStringInfo *tmp = handler.getMatch(i, tmpMatchLen); |
1681 | if ((types & tmp->fType) != 0) { | |
1682 | if (result == NULL || matchLength < tmpMatchLen) { | |
1683 | result = tmp; | |
1684 | matchLength = tmpMatchLen; | |
1685 | } else if (matchLength == tmpMatchLen) { | |
1686 | // Tie breaker - there are some examples that a | |
1687 | // long standard name is identical with a location | |
1688 | // name - for example, "Uruguay Time". In this case, | |
1689 | // we interpret it as generic, not specific. | |
1690 | if (tmp->isGeneric() && !result->isGeneric()) { | |
1691 | result = tmp; | |
1692 | } | |
1693 | } | |
1694 | } else if (result == NULL) { | |
1695 | if (fallback == NULL || fallbackMatchLen < tmpMatchLen) { | |
1696 | fallback = tmp; | |
1697 | fallbackMatchLen = tmpMatchLen; | |
1698 | } else if (fallbackMatchLen == tmpMatchLen) { | |
1699 | if (tmp->isGeneric() && !fallback->isGeneric()) { | |
1700 | fallback = tmp; | |
1701 | } | |
1702 | } | |
1703 | } | |
1704 | } | |
1705 | if (result == NULL && fallback != NULL) { | |
1706 | result = fallback; | |
1707 | matchLength = fallbackMatchLen; | |
1708 | } | |
1709 | } | |
1710 | return result; | |
1711 | } | |
1712 | ||
1713 | ||
1714 | UnicodeString& | |
1715 | ZoneStringFormat::getRegion(UnicodeString ®ion) const { | |
1716 | const char* country = fLocale.getCountry(); | |
1717 | // TODO: Utilize addLikelySubtag in Locale to resolve default region | |
1718 | // when the implementation is ready. | |
1719 | region.setTo(UnicodeString(country, -1, US_INV)); | |
1720 | return region; | |
1721 | } | |
1722 | ||
1723 | MessageFormat* | |
1724 | ZoneStringFormat::getFallbackFormat(const Locale &locale, UErrorCode &status) { | |
1725 | if (U_FAILURE(status)) { | |
1726 | return NULL; | |
1727 | } | |
1728 | UnicodeString pattern(TRUE, gDefFallbackPattern, -1); | |
729e4ab9 | 1729 | UResourceBundle *zoneStringsArray = ures_open(U_ICUDATA_ZONE, locale.getName(), &status); |
46f4442e A |
1730 | zoneStringsArray = ures_getByKeyWithFallback(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status); |
1731 | int32_t len; | |
1732 | const UChar *flbkfmt = ures_getStringByKeyWithFallback(zoneStringsArray, gFallbackFormatTag, &len, &status); | |
1733 | if (U_SUCCESS(status)) { | |
1734 | pattern.setTo(flbkfmt); | |
1735 | } else { | |
1736 | status = U_ZERO_ERROR; | |
1737 | } | |
1738 | ures_close(zoneStringsArray); | |
1739 | ||
1740 | MessageFormat *fallbackFmt = new MessageFormat(pattern, status); | |
1741 | return fallbackFmt; | |
1742 | } | |
1743 | ||
1744 | MessageFormat* | |
1745 | ZoneStringFormat::getRegionFormat(const Locale& locale, UErrorCode &status) { | |
1746 | if (U_FAILURE(status)) { | |
1747 | return NULL; | |
1748 | } | |
1749 | UnicodeString pattern(TRUE, gDefRegionPattern, -1); | |
729e4ab9 | 1750 | UResourceBundle *zoneStringsArray = ures_open(U_ICUDATA_ZONE, locale.getName(), &status); |
46f4442e A |
1751 | zoneStringsArray = ures_getByKeyWithFallback(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status); |
1752 | int32_t len; | |
1753 | const UChar *regionfmt = ures_getStringByKeyWithFallback(zoneStringsArray, gRegionFormatTag, &len, &status); | |
1754 | if (U_SUCCESS(status)) { | |
1755 | pattern.setTo(regionfmt); | |
1756 | } else { | |
1757 | status = U_ZERO_ERROR; | |
1758 | } | |
1759 | ures_close(zoneStringsArray); | |
1760 | ||
1761 | MessageFormat *regionFmt = new MessageFormat(pattern, status); | |
1762 | return regionFmt; | |
1763 | } | |
1764 | ||
1765 | const UChar* | |
1766 | ZoneStringFormat::getZoneStringFromBundle(const UResourceBundle *zoneitem, const char *key) { | |
1767 | const UChar *str = NULL; | |
1768 | if (zoneitem != NULL) { | |
1769 | UErrorCode status = U_ZERO_ERROR; | |
1770 | int32_t len; | |
1771 | str = ures_getStringByKeyWithFallback(zoneitem, key, &len, &status); | |
729e4ab9 | 1772 | str = fStringPool.adopt(str, status); |
46f4442e A |
1773 | if (U_FAILURE(status)) { |
1774 | str = NULL; | |
1775 | } | |
1776 | } | |
1777 | return str; | |
1778 | } | |
1779 | ||
1780 | UBool | |
1781 | ZoneStringFormat::isCommonlyUsed(const UResourceBundle *zoneitem) { | |
1782 | if (zoneitem == NULL) { | |
1783 | return TRUE; | |
1784 | } | |
1785 | ||
1786 | UBool commonlyUsed = FALSE; | |
1787 | UErrorCode status = U_ZERO_ERROR; | |
1788 | UResourceBundle *cuRes = ures_getByKey(zoneitem, gCommonlyUsedTag, NULL, &status); | |
1789 | int32_t cuValue = ures_getInt(cuRes, &status); | |
1790 | if (U_SUCCESS(status)) { | |
1791 | if (cuValue == 1) { | |
1792 | commonlyUsed = TRUE; | |
1793 | } | |
1794 | } | |
1795 | ures_close(cuRes); | |
1796 | return commonlyUsed; | |
1797 | } | |
1798 | ||
1799 | UnicodeString& | |
1800 | ZoneStringFormat::getLocalizedCountry(const UnicodeString &countryCode, const Locale &locale, UnicodeString &displayCountry) { | |
1801 | // We do not want to use display country names only from the target language bundle | |
1802 | // Note: we should do this in better way. | |
1803 | displayCountry.remove(); | |
1804 | int32_t ccLen = countryCode.length(); | |
1805 | if (ccLen > 0 && ccLen < ULOC_COUNTRY_CAPACITY) { | |
1806 | UErrorCode status = U_ZERO_ERROR; | |
1807 | UResourceBundle *localeBundle = ures_open(NULL, locale.getName(), &status); | |
1808 | if (U_SUCCESS(status)) { | |
1809 | const char *bundleLocStr = ures_getLocale(localeBundle, &status); | |
1810 | if (U_SUCCESS(status) && uprv_strlen(bundleLocStr) > 0) { | |
1811 | Locale bundleLoc(bundleLocStr); | |
729e4ab9 A |
1812 | if (uprv_strcmp(bundleLocStr, "root") != 0 && |
1813 | uprv_strcmp(bundleLoc.getLanguage(), locale.getLanguage()) == 0) { | |
46f4442e A |
1814 | // Create a fake locale strings |
1815 | char tmpLocStr[ULOC_COUNTRY_CAPACITY + 3]; | |
1816 | uprv_strcpy(tmpLocStr, "xx_"); | |
1817 | u_UCharsToChars(countryCode.getBuffer(), &tmpLocStr[3], ccLen); | |
1818 | tmpLocStr[3 + ccLen] = 0; | |
1819 | ||
1820 | Locale tmpLoc(tmpLocStr); | |
1821 | tmpLoc.getDisplayCountry(locale, displayCountry); | |
1822 | } | |
1823 | } | |
1824 | } | |
1825 | ures_close(localeBundle); | |
1826 | } | |
1827 | if (displayCountry.isEmpty()) { | |
1828 | // Use the country code as the fallback | |
1829 | displayCountry.setTo(countryCode); | |
1830 | } | |
1831 | return displayCountry; | |
1832 | } | |
1833 | ||
1834 | // ---------------------------------------------------------------------------- | |
1835 | /* | |
729e4ab9 A |
1836 | * ZoneStrings constructor adopts (and promptly copies and deletes) |
1837 | * the input UnicodeString arrays. | |
46f4442e | 1838 | */ |
729e4ab9 A |
1839 | ZoneStrings::ZoneStrings(UnicodeString *strings, |
1840 | int32_t stringsCount, | |
1841 | UBool commonlyUsed, | |
1842 | UnicodeString **genericPartialLocationStrings, | |
1843 | int32_t genericRowCount, | |
1844 | int32_t genericColCount, | |
1845 | ZSFStringPool &sp, | |
1846 | UErrorCode &status) | |
1847 | : fStrings(NULL), | |
1848 | fStringsCount(stringsCount), | |
1849 | fIsCommonlyUsed(commonlyUsed), | |
1850 | fGenericPartialLocationStrings(NULL), | |
1851 | fGenericPartialLocationRowCount(genericRowCount), | |
1852 | fGenericPartialLocationColCount(genericColCount) | |
1853 | { | |
1854 | if (U_FAILURE(status)) { | |
1855 | return; | |
1856 | } | |
1857 | int32_t i, j; | |
1858 | if (strings != NULL) { | |
1859 | fStrings = (const UChar **)uprv_malloc(sizeof(const UChar **) * stringsCount); | |
1860 | if (fStrings == NULL) { | |
1861 | status = U_MEMORY_ALLOCATION_ERROR; | |
1862 | return; | |
1863 | } | |
1864 | for (i=0; i<fStringsCount; i++) { | |
1865 | fStrings[i] = sp.get(strings[i], status); | |
1866 | } | |
1867 | delete[] strings; | |
1868 | } | |
1869 | if (genericPartialLocationStrings != NULL) { | |
1870 | fGenericPartialLocationStrings = | |
1871 | (const UChar ***)uprv_malloc(sizeof(const UChar ***) * genericRowCount); | |
1872 | if (fGenericPartialLocationStrings == NULL) { | |
1873 | status = U_MEMORY_ALLOCATION_ERROR; | |
1874 | return; | |
1875 | } | |
1876 | for (i=0; i < fGenericPartialLocationRowCount; i++) { | |
1877 | fGenericPartialLocationStrings[i] = | |
1878 | (const UChar **)uprv_malloc(sizeof(const UChar **) * genericColCount); | |
1879 | if (fGenericPartialLocationStrings[i] == NULL) { | |
1880 | status = U_MEMORY_ALLOCATION_ERROR; | |
1881 | continue; // Continue so that fGenericPartialLocationStrings will not contain uninitialized junk, | |
1882 | } // which would crash the destructor. | |
1883 | for (j=0; j<genericColCount; j++) { | |
1884 | fGenericPartialLocationStrings[i][j] = | |
1885 | sp.get(genericPartialLocationStrings[i][j], status); | |
1886 | } | |
1887 | delete[] genericPartialLocationStrings[i]; | |
1888 | } | |
1889 | uprv_free(genericPartialLocationStrings); | |
1890 | } | |
46f4442e A |
1891 | } |
1892 | ||
1893 | ZoneStrings::~ZoneStrings() { | |
729e4ab9 | 1894 | uprv_free(fStrings); |
46f4442e A |
1895 | if (fGenericPartialLocationStrings != NULL) { |
1896 | for (int32_t i = 0; i < fGenericPartialLocationRowCount; i++) { | |
729e4ab9 | 1897 | uprv_free(fGenericPartialLocationStrings[i]); |
46f4442e A |
1898 | } |
1899 | uprv_free(fGenericPartialLocationStrings); | |
1900 | } | |
1901 | } | |
1902 | ||
1903 | ||
1904 | UnicodeString& | |
1905 | ZoneStrings::getString(int32_t typeIdx, UnicodeString &result) const { | |
1906 | if (typeIdx >= 0 && typeIdx < fStringsCount) { | |
729e4ab9 | 1907 | result.setTo(fStrings[typeIdx], -1); |
46f4442e A |
1908 | } else { |
1909 | result.remove(); | |
1910 | } | |
1911 | return result; | |
1912 | } | |
1913 | ||
1914 | UnicodeString& | |
1915 | ZoneStrings::getGenericPartialLocationString(const UnicodeString &mzid, UBool isShort, | |
1916 | UBool commonlyUsedOnly, UnicodeString &result) const { | |
1917 | UBool isSet = FALSE; | |
1918 | if (fGenericPartialLocationColCount >= 2) { | |
1919 | for (int32_t i = 0; i < fGenericPartialLocationRowCount; i++) { | |
729e4ab9 | 1920 | if (mzid.compare(fGenericPartialLocationStrings[i][0], -1) == 0) { |
46f4442e A |
1921 | if (isShort) { |
1922 | if (fGenericPartialLocationColCount >= 3) { | |
1923 | if (!commonlyUsedOnly || | |
729e4ab9 A |
1924 | fGenericPartialLocationColCount == 3 || |
1925 | fGenericPartialLocationStrings[i][3][0] != 0) { | |
1926 | result.setTo(fGenericPartialLocationStrings[i][2], -1); | |
46f4442e A |
1927 | isSet = TRUE; |
1928 | } | |
1929 | } | |
1930 | } else { | |
729e4ab9 | 1931 | result.setTo(fGenericPartialLocationStrings[i][1], -1); |
46f4442e A |
1932 | isSet = TRUE; |
1933 | } | |
1934 | break; | |
1935 | } | |
1936 | } | |
1937 | } | |
1938 | if (!isSet) { | |
1939 | result.remove(); | |
1940 | } | |
1941 | return result; | |
1942 | } | |
1943 | ||
1944 | // -------------------------------------------------------------- | |
1945 | SafeZoneStringFormatPtr::SafeZoneStringFormatPtr(ZSFCacheEntry *cacheEntry) | |
1946 | : fCacheEntry(cacheEntry) { | |
1947 | } | |
1948 | ||
1949 | SafeZoneStringFormatPtr::~SafeZoneStringFormatPtr() { | |
1950 | fCacheEntry->delRef(); | |
1951 | } | |
1952 | ||
1953 | const ZoneStringFormat* | |
1954 | SafeZoneStringFormatPtr::get() const { | |
1955 | return fCacheEntry->getZoneStringFormat(); | |
1956 | } | |
1957 | ||
1958 | ZSFCacheEntry::ZSFCacheEntry(const Locale &locale, ZoneStringFormat *zsf, ZSFCacheEntry *next) | |
1959 | : fLocale(locale), fZoneStringFormat(zsf), | |
1960 | fNext(next), fRefCount(1) | |
1961 | { | |
1962 | } | |
1963 | ||
1964 | ZSFCacheEntry::~ZSFCacheEntry () { | |
1965 | delete fZoneStringFormat; | |
1966 | } | |
1967 | ||
1968 | const ZoneStringFormat* | |
1969 | ZSFCacheEntry::getZoneStringFormat(void) { | |
1970 | return (const ZoneStringFormat*)fZoneStringFormat; | |
1971 | } | |
1972 | ||
1973 | void | |
1974 | ZSFCacheEntry::delRef(void) { | |
1975 | umtx_lock(&gZSFCacheLock); | |
1976 | --fRefCount; | |
1977 | umtx_unlock(&gZSFCacheLock); | |
1978 | } | |
1979 | ||
1980 | ZSFCache::ZSFCache(int32_t capacity) | |
1981 | : fCapacity(capacity), fFirst(NULL) { | |
1982 | } | |
1983 | ||
1984 | ZSFCache::~ZSFCache() { | |
1985 | ZSFCacheEntry *entry = fFirst; | |
1986 | while (entry) { | |
1987 | ZSFCacheEntry *next = entry->fNext; | |
1988 | delete entry; | |
1989 | entry = next; | |
1990 | } | |
1991 | } | |
1992 | ||
1993 | SafeZoneStringFormatPtr* | |
1994 | ZSFCache::get(const Locale &locale, UErrorCode &status) { | |
1995 | SafeZoneStringFormatPtr *result = NULL; | |
1996 | ||
1997 | // Search the cache entry list | |
1998 | ZSFCacheEntry *entry = NULL; | |
1999 | ZSFCacheEntry *next, *prev; | |
2000 | ||
2001 | umtx_lock(&gZSFCacheLock); | |
2002 | entry = fFirst; | |
2003 | prev = NULL; | |
2004 | while (entry) { | |
2005 | next = entry->fNext; | |
2006 | if (entry->fLocale == locale) { | |
2007 | // Add reference count | |
2008 | entry->fRefCount++; | |
2009 | ||
2010 | // move the entry to the top | |
2011 | if (entry != fFirst) { | |
2012 | prev->fNext = next; | |
2013 | entry->fNext = fFirst; | |
2014 | fFirst = entry; | |
2015 | } | |
2016 | break; | |
2017 | } | |
2018 | prev = entry; | |
2019 | entry = next; | |
2020 | } | |
2021 | umtx_unlock(&gZSFCacheLock); | |
2022 | ||
2023 | // Create a new ZoneStringFormat | |
2024 | if (entry == NULL) { | |
2025 | ZoneStringFormat *zsf = new ZoneStringFormat(locale, status); | |
2026 | if (U_FAILURE(status)) { | |
2027 | delete zsf; | |
2028 | return NULL; | |
2029 | } | |
2030 | if (zsf == NULL) { | |
2031 | status = U_MEMORY_ALLOCATION_ERROR; | |
2032 | return NULL; | |
2033 | } | |
729e4ab9 | 2034 | |
46f4442e A |
2035 | // Now add the new entry |
2036 | umtx_lock(&gZSFCacheLock); | |
2037 | // Make sure no other threads already created the one for the same locale | |
2038 | entry = fFirst; | |
2039 | prev = NULL; | |
2040 | while (entry) { | |
2041 | next = entry->fNext; | |
2042 | if (entry->fLocale == locale) { | |
2043 | // Add reference count | |
2044 | entry->fRefCount++; | |
2045 | ||
2046 | // move the entry to the top | |
2047 | if (entry != fFirst) { | |
2048 | prev->fNext = next; | |
2049 | entry->fNext = fFirst; | |
2050 | fFirst = entry; | |
2051 | } | |
2052 | break; | |
2053 | } | |
2054 | prev = entry; | |
2055 | entry = next; | |
2056 | } | |
2057 | if (entry == NULL) { | |
2058 | // Add the new one to the top | |
2059 | next = fFirst; | |
2060 | entry = new ZSFCacheEntry(locale, zsf, next); | |
2061 | fFirst = entry; | |
2062 | } else { | |
2063 | delete zsf; | |
2064 | } | |
2065 | umtx_unlock(&gZSFCacheLock); | |
2066 | } | |
2067 | ||
2068 | result = new SafeZoneStringFormatPtr(entry); | |
2069 | ||
2070 | // Now, delete unused cache entries beyond the capacity | |
2071 | umtx_lock(&gZSFCacheLock); | |
2072 | entry = fFirst; | |
2073 | prev = NULL; | |
2074 | int32_t idx = 1; | |
2075 | while (entry) { | |
2076 | next = entry->fNext; | |
2077 | if (idx >= fCapacity && entry->fRefCount == 0) { | |
2078 | if (entry == fFirst) { | |
2079 | fFirst = next; | |
2080 | } else { | |
2081 | prev->fNext = next; | |
2082 | } | |
2083 | delete entry; | |
2084 | } else { | |
2085 | prev = entry; | |
2086 | } | |
2087 | entry = next; | |
2088 | idx++; | |
2089 | } | |
2090 | umtx_unlock(&gZSFCacheLock); | |
2091 | ||
2092 | return result; | |
2093 | } | |
2094 | ||
729e4ab9 A |
2095 | |
2096 | /* | |
2097 | * Zone String Formatter String Pool Implementation | |
2098 | * | |
2099 | * String pool for (UChar *) strings. Avoids having repeated copies of the same string. | |
2100 | */ | |
2101 | ||
2102 | static const int32_t POOL_CHUNK_SIZE = 2000; | |
2103 | struct ZSFStringPoolChunk: public UMemory { | |
2104 | ZSFStringPoolChunk *fNext; // Ptr to next pool chunk | |
2105 | int32_t fLimit; // Index to start of unused area at end of fStrings | |
2106 | UChar fStrings[POOL_CHUNK_SIZE]; // Strings array | |
2107 | ZSFStringPoolChunk(); | |
2108 | }; | |
2109 | ||
2110 | ZSFStringPoolChunk::ZSFStringPoolChunk() { | |
2111 | fNext = NULL; | |
2112 | fLimit = 0; | |
2113 | } | |
2114 | ||
2115 | ZSFStringPool::ZSFStringPool(UErrorCode &status) { | |
2116 | fChunks = NULL; | |
2117 | fHash = NULL; | |
2118 | if (U_FAILURE(status)) { | |
2119 | return; | |
2120 | } | |
2121 | fChunks = new ZSFStringPoolChunk; | |
2122 | if (fChunks == NULL) { | |
2123 | status = U_MEMORY_ALLOCATION_ERROR; | |
2124 | return; | |
2125 | } | |
2126 | ||
2127 | fHash = uhash_open(uhash_hashUChars /* keyHash */, | |
2128 | uhash_compareUChars /* keyComp */, | |
2129 | uhash_compareUChars /* valueComp */, | |
2130 | &status); | |
2131 | if (U_FAILURE(status)) { | |
2132 | return; | |
2133 | } | |
2134 | } | |
2135 | ||
2136 | ||
2137 | ZSFStringPool::~ZSFStringPool() { | |
2138 | if (fHash != NULL) { | |
2139 | uhash_close(fHash); | |
2140 | fHash = NULL; | |
2141 | } | |
2142 | ||
2143 | while (fChunks != NULL) { | |
2144 | ZSFStringPoolChunk *nextChunk = fChunks->fNext; | |
2145 | delete fChunks; | |
2146 | fChunks = nextChunk; | |
2147 | } | |
2148 | } | |
2149 | ||
2150 | static const UChar EmptyString = 0; | |
2151 | ||
2152 | const UChar *ZSFStringPool::get(const UChar *s, UErrorCode &status) { | |
2153 | const UChar *pooledString; | |
2154 | if (U_FAILURE(status)) { | |
2155 | return &EmptyString; | |
2156 | } | |
2157 | ||
2158 | pooledString = static_cast<UChar *>(uhash_get(fHash, s)); | |
2159 | if (pooledString != NULL) { | |
2160 | return pooledString; | |
2161 | } | |
2162 | ||
2163 | int32_t length = u_strlen(s); | |
2164 | int32_t remainingLength = POOL_CHUNK_SIZE - fChunks->fLimit; | |
2165 | if (remainingLength <= length) { | |
2166 | U_ASSERT(length < POOL_CHUNK_SIZE); | |
2167 | if (length >= POOL_CHUNK_SIZE) { | |
2168 | status = U_INTERNAL_PROGRAM_ERROR; | |
2169 | return &EmptyString; | |
2170 | } | |
2171 | ZSFStringPoolChunk *oldChunk = fChunks; | |
2172 | fChunks = new ZSFStringPoolChunk; | |
2173 | if (fChunks == NULL) { | |
2174 | status = U_MEMORY_ALLOCATION_ERROR; | |
2175 | return &EmptyString; | |
2176 | } | |
2177 | fChunks->fNext = oldChunk; | |
2178 | } | |
2179 | ||
2180 | UChar *destString = &fChunks->fStrings[fChunks->fLimit]; | |
2181 | u_strcpy(destString, s); | |
2182 | fChunks->fLimit += (length + 1); | |
2183 | uhash_put(fHash, destString, destString, &status); | |
2184 | return destString; | |
2185 | } | |
2186 | ||
2187 | ||
2188 | // | |
2189 | // ZSFStringPool::adopt() Put a string into the hash, but do not copy the string data | |
2190 | // into the pool's storage. Used for strings from resource bundles, | |
2191 | // which will perisist for the life of the zone string formatter, and | |
2192 | // therefore can be used directly without copying. | |
2193 | const UChar *ZSFStringPool::adopt(const UChar * s, UErrorCode &status) { | |
2194 | const UChar *pooledString; | |
2195 | if (U_FAILURE(status)) { | |
2196 | return &EmptyString; | |
2197 | } | |
2198 | if (s != NULL) { | |
2199 | pooledString = static_cast<UChar *>(uhash_get(fHash, s)); | |
2200 | if (pooledString == NULL) { | |
2201 | UChar *ncs = const_cast<UChar *>(s); | |
2202 | uhash_put(fHash, ncs, ncs, &status); | |
2203 | } | |
2204 | } | |
2205 | return s; | |
2206 | } | |
2207 | ||
2208 | ||
2209 | const UChar *ZSFStringPool::get(const UnicodeString &s, UErrorCode &status) { | |
2210 | UnicodeString &nonConstStr = const_cast<UnicodeString &>(s); | |
2211 | return this->get(nonConstStr.getTerminatedBuffer(), status); | |
2212 | } | |
2213 | ||
2214 | /* | |
2215 | * freeze(). Close the hash table that maps to the pooled strings. | |
2216 | * After freezing, the pool can not be searched or added to, | |
2217 | * but all existing references to pooled strings remain valid. | |
2218 | * | |
2219 | * The main purpose is to recover the storage used for the hash. | |
2220 | */ | |
2221 | void ZSFStringPool::freeze() { | |
2222 | uhash_close(fHash); | |
2223 | fHash = NULL; | |
2224 | } | |
2225 | ||
46f4442e A |
2226 | U_NAMESPACE_END |
2227 | ||
2228 | #endif /* #if !UCONFIG_NO_FORMATTING */ |