1 /* 2 ******************************************************************************* 3 * Copyright (C) 2011-2013, International Business Machines Corporation and 4 * others. All Rights Reserved. 5 ******************************************************************************* 6 * 7 * File TZNAMES_IMPL.CPP 8 * 9 ******************************************************************************* 10 */ 11 12 #include "unicode/utypes.h" 13 14 #if !UCONFIG_NO_FORMATTING 15 16 #include "unicode/ustring.h" 17 #include "unicode/timezone.h" 18 19 #include "tznames_impl.h" 20 #include "cmemory.h" 21 #include "cstring.h" 22 #include "uassert.h" 23 #include "mutex.h" 24 #include "uresimp.h" 25 #include "ureslocs.h" 26 #include "zonemeta.h" 27 #include "ucln_in.h" 28 #include "uvector.h" 29 #include "olsontz.h" 30 31 32 U_NAMESPACE_BEGIN 33 34 #define ZID_KEY_MAX 128 35 #define MZ_PREFIX_LEN 5 36 37 static const char gZoneStrings[] = "zoneStrings"; 38 static const char gMZPrefix[] = "meta:"; 39 40 static const char* KEYS[] = {"lg", "ls", "ld", "sg", "ss", "sd"}; 41 static const int32_t KEYS_SIZE = (sizeof KEYS / sizeof KEYS[0]); 42 43 static const char gEcTag[] = "ec"; 44 45 static const char EMPTY[] = "<empty>"; // place holder for empty ZNames/TZNames 46 47 static const UTimeZoneNameType ALL_NAME_TYPES[] = { 48 UTZNM_LONG_GENERIC, UTZNM_LONG_STANDARD, UTZNM_LONG_DAYLIGHT, 49 UTZNM_SHORT_GENERIC, UTZNM_SHORT_STANDARD, UTZNM_SHORT_DAYLIGHT, 50 UTZNM_EXEMPLAR_LOCATION, 51 UTZNM_UNKNOWN // unknown as the last one 52 }; 53 54 #define DEFAULT_CHARACTERNODE_CAPACITY 1 55 56 // --------------------------------------------------- 57 // CharacterNode class implementation 58 // --------------------------------------------------- 59 void CharacterNode::clear() { 60 uprv_memset(this, 0, sizeof(*this)); 61 } 62 63 void CharacterNode::deleteValues(UObjectDeleter *valueDeleter) { 64 if (fValues == NULL) { 65 // Do nothing. 66 } else if (!fHasValuesVector) { 67 if (valueDeleter) { 68 valueDeleter(fValues); 69 } 70 } else { 71 delete (UVector *)fValues; 72 } 73 } 74 75 void 76 CharacterNode::addValue(void *value, UObjectDeleter *valueDeleter, UErrorCode &status) { 77 if (U_FAILURE(status)) { 78 if (valueDeleter) { 79 valueDeleter(value); 80 } 81 return; 82 } 83 if (fValues == NULL) { 84 fValues = value; 85 } else { 86 // At least one value already. 87 if (!fHasValuesVector) { 88 // There is only one value so far, and not in a vector yet. 89 // Create a vector and add the old value. 90 UVector *values = new UVector(valueDeleter, NULL, DEFAULT_CHARACTERNODE_CAPACITY, status); 91 if (U_FAILURE(status)) { 92 if (valueDeleter) { 93 valueDeleter(value); 94 } 95 return; 96 } 97 values->addElement(fValues, status); 98 fValues = values; 99 fHasValuesVector = TRUE; 100 } 101 // Add the new value. 102 ((UVector *)fValues)->addElement(value, status); 103 } 104 } 105 106 // --------------------------------------------------- 107 // TextTrieMapSearchResultHandler class implementation 108 // --------------------------------------------------- 109 TextTrieMapSearchResultHandler::~TextTrieMapSearchResultHandler(){ 110 } 111 112 // --------------------------------------------------- 113 // TextTrieMap class implementation 114 // --------------------------------------------------- 115 TextTrieMap::TextTrieMap(UBool ignoreCase, UObjectDeleter *valueDeleter) 116 : fIgnoreCase(ignoreCase), fNodes(NULL), fNodesCapacity(0), fNodesCount(0), 117 fLazyContents(NULL), fIsEmpty(TRUE), fValueDeleter(valueDeleter) { 118 } 119 120 TextTrieMap::~TextTrieMap() { 121 int32_t index; 122 for (index = 0; index < fNodesCount; ++index) { 123 fNodes[index].deleteValues(fValueDeleter); 124 } 125 uprv_free(fNodes); 126 if (fLazyContents != NULL) { 127 for (int32_t i=0; i<fLazyContents->size(); i+=2) { 128 if (fValueDeleter) { 129 fValueDeleter(fLazyContents->elementAt(i+1)); 130 } 131 } 132 delete fLazyContents; 133 } 134 } 135 136 int32_t TextTrieMap::isEmpty() const { 137 // Use a separate field for fIsEmpty because it will remain unchanged once the 138 // Trie is built, while fNodes and fLazyContents change with the lazy init 139 // of the nodes structure. Trying to test the changing fields has 140 // thread safety complications. 141 return fIsEmpty; 142 } 143 144 145 // We defer actually building the TextTrieMap node structure until the first time a 146 // search is performed. put() simply saves the parameters in case we do 147 // eventually need to build it. 148 // 149 void 150 TextTrieMap::put(const UnicodeString &key, void *value, ZNStringPool &sp, UErrorCode &status) { 151 const UChar *s = sp.get(key, status); 152 put(s, value, status); 153 } 154 155 // This method is for designed for a persistent key, such as string key stored in 156 // resource bundle. 157 void 158 TextTrieMap::put(const UChar *key, void *value, UErrorCode &status) { 159 fIsEmpty = FALSE; 160 if (fLazyContents == NULL) { 161 fLazyContents = new UVector(status); 162 if (fLazyContents == NULL) { 163 status = U_MEMORY_ALLOCATION_ERROR; 164 } 165 } 166 if (U_FAILURE(status)) { 167 return; 168 } 169 U_ASSERT(fLazyContents != NULL); 170 UChar *s = const_cast<UChar *>(key); 171 fLazyContents->addElement(s, status); 172 fLazyContents->addElement(value, status); 173 } 174 175 void 176 TextTrieMap::putImpl(const UnicodeString &key, void *value, UErrorCode &status) { 177 if (fNodes == NULL) { 178 fNodesCapacity = 512; 179 fNodes = (CharacterNode *)uprv_malloc(fNodesCapacity * sizeof(CharacterNode)); 180 fNodes[0].clear(); // Init root node. 181 fNodesCount = 1; 182 } 183 184 UnicodeString foldedKey; 185 const UChar *keyBuffer; 186 int32_t keyLength; 187 if (fIgnoreCase) { 188 // Ok to use fastCopyFrom() because we discard the copy when we return. 189 foldedKey.fastCopyFrom(key).foldCase(); 190 keyBuffer = foldedKey.getBuffer(); 191 keyLength = foldedKey.length(); 192 } else { 193 keyBuffer = key.getBuffer(); 194 keyLength = key.length(); 195 } 196 197 CharacterNode *node = fNodes; 198 int32_t index; 199 for (index = 0; index < keyLength; ++index) { 200 node = addChildNode(node, keyBuffer[index], status); 201 } 202 node->addValue(value, fValueDeleter, status); 203 } 204 205 UBool 206 TextTrieMap::growNodes() { 207 if (fNodesCapacity == 0xffff) { 208 return FALSE; // We use 16-bit node indexes. 209 } 210 int32_t newCapacity = fNodesCapacity + 1000; 211 if (newCapacity > 0xffff) { 212 newCapacity = 0xffff; 213 } 214 CharacterNode *newNodes = (CharacterNode *)uprv_malloc(newCapacity * sizeof(CharacterNode)); 215 if (newNodes == NULL) { 216 return FALSE; 217 } 218 uprv_memcpy(newNodes, fNodes, fNodesCount * sizeof(CharacterNode)); 219 uprv_free(fNodes); 220 fNodes = newNodes; 221 fNodesCapacity = newCapacity; 222 return TRUE; 223 } 224 225 CharacterNode* 226 TextTrieMap::addChildNode(CharacterNode *parent, UChar c, UErrorCode &status) { 227 if (U_FAILURE(status)) { 228 return NULL; 229 } 230 // Linear search of the sorted list of children. 231 uint16_t prevIndex = 0; 232 uint16_t nodeIndex = parent->fFirstChild; 233 while (nodeIndex > 0) { 234 CharacterNode *current = fNodes + nodeIndex; 235 UChar childCharacter = current->fCharacter; 236 if (childCharacter == c) { 237 return current; 238 } else if (childCharacter > c) { 239 break; 240 } 241 prevIndex = nodeIndex; 242 nodeIndex = current->fNextSibling; 243 } 244 245 // Ensure capacity. Grow fNodes[] if needed. 246 if (fNodesCount == fNodesCapacity) { 247 int32_t parentIndex = (int32_t)(parent - fNodes); 248 if (!growNodes()) { 249 status = U_MEMORY_ALLOCATION_ERROR; 250 return NULL; 251 } 252 parent = fNodes + parentIndex; 253 } 254 255 // Insert a new child node with c in sorted order. 256 CharacterNode *node = fNodes + fNodesCount; 257 node->clear(); 258 node->fCharacter = c; 259 node->fNextSibling = nodeIndex; 260 if (prevIndex == 0) { 261 parent->fFirstChild = (uint16_t)fNodesCount; 262 } else { 263 fNodes[prevIndex].fNextSibling = (uint16_t)fNodesCount; 264 } 265 ++fNodesCount; 266 return node; 267 } 268 269 CharacterNode* 270 TextTrieMap::getChildNode(CharacterNode *parent, UChar c) const { 271 // Linear search of the sorted list of children. 272 uint16_t nodeIndex = parent->fFirstChild; 273 while (nodeIndex > 0) { 274 CharacterNode *current = fNodes + nodeIndex; 275 UChar childCharacter = current->fCharacter; 276 if (childCharacter == c) { 277 return current; 278 } else if (childCharacter > c) { 279 break; 280 } 281 nodeIndex = current->fNextSibling; 282 } 283 return NULL; 284 } 285 286 // Mutex for protecting the lazy creation of the Trie node structure on the first call to search(). 287 static UMutex TextTrieMutex = U_MUTEX_INITIALIZER; 288 289 // buildTrie() - The Trie node structure is needed. Create it from the data that was 290 // saved at the time the ZoneStringFormatter was created. The Trie is only 291 // needed for parsing operations, which are less common than formatting, 292 // and the Trie is big, which is why its creation is deferred until first use. 293 void TextTrieMap::buildTrie(UErrorCode &status) { 294 if (fLazyContents != NULL) { 295 for (int32_t i=0; i<fLazyContents->size(); i+=2) { 296 const UChar *key = (UChar *)fLazyContents->elementAt(i); 297 void *val = fLazyContents->elementAt(i+1); 298 UnicodeString keyString(TRUE, key, -1); // Aliasing UnicodeString constructor. 299 putImpl(keyString, val, status); 300 } 301 delete fLazyContents; 302 fLazyContents = NULL; 303 } 304 } 305 306 void 307 TextTrieMap::search(const UnicodeString &text, int32_t start, 308 TextTrieMapSearchResultHandler *handler, UErrorCode &status) const { 309 { 310 // TODO: if locking the mutex for each check proves to be a performance problem, 311 // add a flag of type atomic_int32_t to class TextTrieMap, and use only 312 // the ICU atomic safe functions for assigning and testing. 313 // Don't test the pointer fLazyContents. 314 // Don't do unless it's really required. 315 Mutex lock(&TextTrieMutex); 316 if (fLazyContents != NULL) { 317 TextTrieMap *nonConstThis = const_cast<TextTrieMap *>(this); 318 nonConstThis->buildTrie(status); 319 } 320 } 321 if (fNodes == NULL) { 322 return; 323 } 324 search(fNodes, text, start, start, handler, status); 325 } 326 327 void 328 TextTrieMap::search(CharacterNode *node, const UnicodeString &text, int32_t start, 329 int32_t index, TextTrieMapSearchResultHandler *handler, UErrorCode &status) const { 330 if (U_FAILURE(status)) { 331 return; 332 } 333 if (node->hasValues()) { 334 if (!handler->handleMatch(index - start, node, status)) { 335 return; 336 } 337 if (U_FAILURE(status)) { 338 return; 339 } 340 } 341 UChar32 c = text.char32At(index); 342 if (fIgnoreCase) { 343 // size of character may grow after fold operation 344 UnicodeString tmp(c); 345 tmp.foldCase(); 346 int32_t tmpidx = 0; 347 while (tmpidx < tmp.length()) { 348 c = tmp.char32At(tmpidx); 349 node = getChildNode(node, c); 350 if (node == NULL) { 351 break; 352 } 353 tmpidx = tmp.moveIndex32(tmpidx, 1); 354 } 355 } else { 356 node = getChildNode(node, c); 357 } 358 if (node != NULL) { 359 search(node, text, start, index+1, handler, status); 360 } 361 } 362 363 // --------------------------------------------------- 364 // ZNStringPool class implementation 365 // --------------------------------------------------- 366 static const int32_t POOL_CHUNK_SIZE = 2000; 367 struct ZNStringPoolChunk: public UMemory { 368 ZNStringPoolChunk *fNext; // Ptr to next pool chunk 369 int32_t fLimit; // Index to start of unused area at end of fStrings 370 UChar fStrings[POOL_CHUNK_SIZE]; // Strings array 371 ZNStringPoolChunk(); 372 }; 373 374 ZNStringPoolChunk::ZNStringPoolChunk() { 375 fNext = NULL; 376 fLimit = 0; 377 } 378 379 ZNStringPool::ZNStringPool(UErrorCode &status) { 380 fChunks = NULL; 381 fHash = NULL; 382 if (U_FAILURE(status)) { 383 return; 384 } 385 fChunks = new ZNStringPoolChunk; 386 if (fChunks == NULL) { 387 status = U_MEMORY_ALLOCATION_ERROR; 388 return; 389 } 390 391 fHash = uhash_open(uhash_hashUChars /* keyHash */, 392 uhash_compareUChars /* keyComp */, 393 uhash_compareUChars /* valueComp */, 394 &status); 395 if (U_FAILURE(status)) { 396 return; 397 } 398 } 399 400 ZNStringPool::~ZNStringPool() { 401 if (fHash != NULL) { 402 uhash_close(fHash); 403 fHash = NULL; 404 } 405 406 while (fChunks != NULL) { 407 ZNStringPoolChunk *nextChunk = fChunks->fNext; 408 delete fChunks; 409 fChunks = nextChunk; 410 } 411 } 412 413 static const UChar EmptyString = 0; 414 415 const UChar *ZNStringPool::get(const UChar *s, UErrorCode &status) { 416 const UChar *pooledString; 417 if (U_FAILURE(status)) { 418 return &EmptyString; 419 } 420 421 pooledString = static_cast<UChar *>(uhash_get(fHash, s)); 422 if (pooledString != NULL) { 423 return pooledString; 424 } 425 426 int32_t length = u_strlen(s); 427 int32_t remainingLength = POOL_CHUNK_SIZE - fChunks->fLimit; 428 if (remainingLength <= length) { 429 U_ASSERT(length < POOL_CHUNK_SIZE); 430 if (length >= POOL_CHUNK_SIZE) { 431 status = U_INTERNAL_PROGRAM_ERROR; 432 return &EmptyString; 433 } 434 ZNStringPoolChunk *oldChunk = fChunks; 435 fChunks = new ZNStringPoolChunk; 436 if (fChunks == NULL) { 437 status = U_MEMORY_ALLOCATION_ERROR; 438 return &EmptyString; 439 } 440 fChunks->fNext = oldChunk; 441 } 442 443 UChar *destString = &fChunks->fStrings[fChunks->fLimit]; 444 u_strcpy(destString, s); 445 fChunks->fLimit += (length + 1); 446 uhash_put(fHash, destString, destString, &status); 447 return destString; 448 } 449 450 451 // 452 // ZNStringPool::adopt() Put a string into the hash, but do not copy the string data 453 // into the pool's storage. Used for strings from resource bundles, 454 // which will perisist for the life of the zone string formatter, and 455 // therefore can be used directly without copying. 456 const UChar *ZNStringPool::adopt(const UChar * s, UErrorCode &status) { 457 const UChar *pooledString; 458 if (U_FAILURE(status)) { 459 return &EmptyString; 460 } 461 if (s != NULL) { 462 pooledString = static_cast<UChar *>(uhash_get(fHash, s)); 463 if (pooledString == NULL) { 464 UChar *ncs = const_cast<UChar *>(s); 465 uhash_put(fHash, ncs, ncs, &status); 466 } 467 } 468 return s; 469 } 470 471 472 const UChar *ZNStringPool::get(const UnicodeString &s, UErrorCode &status) { 473 UnicodeString &nonConstStr = const_cast<UnicodeString &>(s); 474 return this->get(nonConstStr.getTerminatedBuffer(), status); 475 } 476 477 /* 478 * freeze(). Close the hash table that maps to the pooled strings. 479 * After freezing, the pool can not be searched or added to, 480 * but all existing references to pooled strings remain valid. 481 * 482 * The main purpose is to recover the storage used for the hash. 483 */ 484 void ZNStringPool::freeze() { 485 uhash_close(fHash); 486 fHash = NULL; 487 } 488 489 490 // --------------------------------------------------- 491 // ZNames - names common for time zone and meta zone 492 // --------------------------------------------------- 493 class ZNames : public UMemory { 494 public: 495 virtual ~ZNames(); 496 497 static ZNames* createInstance(UResourceBundle* rb, const char* key); 498 virtual const UChar* getName(UTimeZoneNameType type); 499 500 protected: 501 ZNames(const UChar** names); 502 static const UChar** loadData(UResourceBundle* rb, const char* key); 503 504 private: 505 const UChar** fNames; 506 }; 507 508 ZNames::ZNames(const UChar** names) 509 : fNames(names) { 510 } 511 512 ZNames::~ZNames() { 513 if (fNames != NULL) { 514 uprv_free(fNames); 515 } 516 } 517 518 ZNames* 519 ZNames::createInstance(UResourceBundle* rb, const char* key) { 520 const UChar** names = loadData(rb, key); 521 if (names == NULL) { 522 // No names data available 523 return NULL; 524 } 525 return new ZNames(names); 526 } 527 528 const UChar* 529 ZNames::getName(UTimeZoneNameType type) { 530 if (fNames == NULL) { 531 return NULL; 532 } 533 const UChar *name = NULL; 534 switch(type) { 535 case UTZNM_LONG_GENERIC: 536 name = fNames[0]; 537 break; 538 case UTZNM_LONG_STANDARD: 539 name = fNames[1]; 540 break; 541 case UTZNM_LONG_DAYLIGHT: 542 name = fNames[2]; 543 break; 544 case UTZNM_SHORT_GENERIC: 545 name = fNames[3]; 546 break; 547 case UTZNM_SHORT_STANDARD: 548 name = fNames[4]; 549 break; 550 case UTZNM_SHORT_DAYLIGHT: 551 name = fNames[5]; 552 break; 553 case UTZNM_EXEMPLAR_LOCATION: // implemeted by subclass 554 default: 555 name = NULL; 556 } 557 return name; 558 } 559 560 const UChar** 561 ZNames::loadData(UResourceBundle* rb, const char* key) { 562 if (rb == NULL || key == NULL || *key == 0) { 563 return NULL; 564 } 565 566 UErrorCode status = U_ZERO_ERROR; 567 const UChar **names = NULL; 568 569 UResourceBundle* rbTable = NULL; 570 rbTable = ures_getByKeyWithFallback(rb, key, rbTable, &status); 571 if (U_SUCCESS(status)) { 572 names = (const UChar **)uprv_malloc(sizeof(const UChar*) * KEYS_SIZE); 573 if (names != NULL) { 574 UBool isEmpty = TRUE; 575 for (int32_t i = 0; i < KEYS_SIZE; i++) { 576 status = U_ZERO_ERROR; 577 int32_t len = 0; 578 const UChar *value = ures_getStringByKeyWithFallback(rbTable, KEYS[i], &len, &status); 579 if (U_FAILURE(status) || len == 0) { 580 names[i] = NULL; 581 } else { 582 names[i] = value; 583 isEmpty = FALSE; 584 } 585 } 586 if (isEmpty) { 587 // No need to keep the names array 588 uprv_free(names); 589 names = NULL; 590 } 591 } 592 } 593 ures_close(rbTable); 594 return names; 595 } 596 597 // --------------------------------------------------- 598 // TZNames - names for a time zone 599 // --------------------------------------------------- 600 class TZNames : public ZNames { 601 public: 602 virtual ~TZNames(); 603 604 static TZNames* createInstance(UResourceBundle* rb, const char* key, const UnicodeString& tzID); 605 virtual const UChar* getName(UTimeZoneNameType type); 606 607 private: 608 TZNames(const UChar** names); 609 const UChar* fLocationName; 610 UChar* fLocationNameOwned; 611 }; 612 613 TZNames::TZNames(const UChar** names) 614 : ZNames(names), fLocationName(NULL), fLocationNameOwned(NULL) { 615 } 616 617 TZNames::~TZNames() { 618 if (fLocationNameOwned) { 619 uprv_free(fLocationNameOwned); 620 } 621 } 622 623 const UChar* 624 TZNames::getName(UTimeZoneNameType type) { 625 if (type == UTZNM_EXEMPLAR_LOCATION) { 626 return fLocationName; 627 } 628 return ZNames::getName(type); 629 } 630 631 TZNames* 632 TZNames::createInstance(UResourceBundle* rb, const char* key, const UnicodeString& tzID) { 633 if (rb == NULL || key == NULL || *key == 0) { 634 return NULL; 635 } 636 637 const UChar** names = loadData(rb, key); 638 const UChar* locationName = NULL; 639 UChar* locationNameOwned = NULL; 640 641 UErrorCode status = U_ZERO_ERROR; 642 int32_t len = 0; 643 644 UResourceBundle* table = ures_getByKeyWithFallback(rb, key, NULL, &status); 645 locationName = ures_getStringByKeyWithFallback(table, gEcTag, &len, &status); 646 // ignore missing resource here 647 status = U_ZERO_ERROR; 648 649 ures_close(table); 650 651 if (locationName == NULL) { 652 UnicodeString tmpName; 653 int32_t tmpNameLen = 0; 654 TimeZoneNamesImpl::getDefaultExemplarLocationName(tzID, tmpName); 655 tmpNameLen = tmpName.length(); 656 657 if (tmpNameLen > 0) { 658 locationNameOwned = (UChar*) uprv_malloc(sizeof(UChar) * (tmpNameLen + 1)); 659 if (locationNameOwned) { 660 tmpName.extract(locationNameOwned, tmpNameLen + 1, status); 661 locationName = locationNameOwned; 662 } 663 } 664 } 665 666 TZNames* tznames = NULL; 667 if (locationName != NULL || names != NULL) { 668 tznames = new TZNames(names); 669 if (tznames == NULL) { 670 if (locationNameOwned) { 671 uprv_free(locationNameOwned); 672 } 673 } 674 tznames->fLocationName = locationName; 675 tznames->fLocationNameOwned = locationNameOwned; 676 } 677 678 return tznames; 679 } 680 681 // --------------------------------------------------- 682 // The meta zone ID enumeration class 683 // --------------------------------------------------- 684 class MetaZoneIDsEnumeration : public StringEnumeration { 685 public: 686 MetaZoneIDsEnumeration(); 687 MetaZoneIDsEnumeration(const UVector& mzIDs); 688 MetaZoneIDsEnumeration(UVector* mzIDs); 689 virtual ~MetaZoneIDsEnumeration(); 690 static UClassID U_EXPORT2 getStaticClassID(void); 691 virtual UClassID getDynamicClassID(void) const; 692 virtual const UnicodeString* snext(UErrorCode& status); 693 virtual void reset(UErrorCode& status); 694 virtual int32_t count(UErrorCode& status) const; 695 private: 696 int32_t fLen; 697 int32_t fPos; 698 const UVector* fMetaZoneIDs; 699 UVector *fLocalVector; 700 }; 701 702 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(MetaZoneIDsEnumeration) 703 704 MetaZoneIDsEnumeration::MetaZoneIDsEnumeration() 705 : fLen(0), fPos(0), fMetaZoneIDs(NULL), fLocalVector(NULL) { 706 } 707 708 MetaZoneIDsEnumeration::MetaZoneIDsEnumeration(const UVector& mzIDs) 709 : fPos(0), fMetaZoneIDs(&mzIDs), fLocalVector(NULL) { 710 fLen = fMetaZoneIDs->size(); 711 } 712 713 MetaZoneIDsEnumeration::MetaZoneIDsEnumeration(UVector *mzIDs) 714 : fLen(0), fPos(0), fMetaZoneIDs(mzIDs), fLocalVector(mzIDs) { 715 if (fMetaZoneIDs) { 716 fLen = fMetaZoneIDs->size(); 717 } 718 } 719 720 const UnicodeString* 721 MetaZoneIDsEnumeration::snext(UErrorCode& status) { 722 if (U_SUCCESS(status) && fMetaZoneIDs != NULL && fPos < fLen) { 723 unistr.setTo((const UChar*)fMetaZoneIDs->elementAt(fPos++), -1); 724 return &unistr; 725 } 726 return NULL; 727 } 728 729 void 730 MetaZoneIDsEnumeration::reset(UErrorCode& /*status*/) { 731 fPos = 0; 732 } 733 734 int32_t 735 MetaZoneIDsEnumeration::count(UErrorCode& /*status*/) const { 736 return fLen; 737 } 738 739 MetaZoneIDsEnumeration::~MetaZoneIDsEnumeration() { 740 if (fLocalVector) { 741 delete fLocalVector; 742 } 743 } 744 745 U_CDECL_BEGIN 746 /** 747 * ZNameInfo stores zone name information in the trie 748 */ 749 typedef struct ZNameInfo { 750 UTimeZoneNameType type; 751 const UChar* tzID; 752 const UChar* mzID; 753 } ZNameInfo; 754 755 /** 756 * ZMatchInfo stores zone name match information used by find method 757 */ 758 typedef struct ZMatchInfo { 759 const ZNameInfo* znameInfo; 760 int32_t matchLength; 761 } ZMatchInfo; 762 U_CDECL_END 763 764 765 // --------------------------------------------------- 766 // ZNameSearchHandler 767 // --------------------------------------------------- 768 class ZNameSearchHandler : public TextTrieMapSearchResultHandler { 769 public: 770 ZNameSearchHandler(uint32_t types); 771 virtual ~ZNameSearchHandler(); 772 773 UBool handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status); 774 TimeZoneNames::MatchInfoCollection* getMatches(int32_t& maxMatchLen); 775 776 private: 777 uint32_t fTypes; 778 int32_t fMaxMatchLen; 779 TimeZoneNames::MatchInfoCollection* fResults; 780 }; 781 782 ZNameSearchHandler::ZNameSearchHandler(uint32_t types) 783 : fTypes(types), fMaxMatchLen(0), fResults(NULL) { 784 } 785 786 ZNameSearchHandler::~ZNameSearchHandler() { 787 if (fResults != NULL) { 788 delete fResults; 789 } 790 } 791 792 UBool 793 ZNameSearchHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) { 794 if (U_FAILURE(status)) { 795 return FALSE; 796 } 797 if (node->hasValues()) { 798 int32_t valuesCount = node->countValues(); 799 for (int32_t i = 0; i < valuesCount; i++) { 800 ZNameInfo *nameinfo = (ZNameInfo *)node->getValue(i); 801 if (nameinfo == NULL) { 802 break; 803 } 804 if ((nameinfo->type & fTypes) != 0) { 805 // matches a requested type 806 if (fResults == NULL) { 807 fResults = new TimeZoneNames::MatchInfoCollection(); 808 if (fResults == NULL) { 809 status = U_MEMORY_ALLOCATION_ERROR; 810 } 811 } 812 if (U_SUCCESS(status)) { 813 U_ASSERT(fResults != NULL); 814 if (nameinfo->tzID) { 815 fResults->addZone(nameinfo->type, matchLength, UnicodeString(nameinfo->tzID, -1), status); 816 } else { 817 U_ASSERT(nameinfo->mzID); 818 fResults->addMetaZone(nameinfo->type, matchLength, UnicodeString(nameinfo->mzID, -1), status); 819 } 820 if (U_SUCCESS(status) && matchLength > fMaxMatchLen) { 821 fMaxMatchLen = matchLength; 822 } 823 } 824 } 825 } 826 } 827 return TRUE; 828 } 829 830 TimeZoneNames::MatchInfoCollection* 831 ZNameSearchHandler::getMatches(int32_t& maxMatchLen) { 832 // give the ownership to the caller 833 TimeZoneNames::MatchInfoCollection* results = fResults; 834 maxMatchLen = fMaxMatchLen; 835 836 // reset 837 fResults = NULL; 838 fMaxMatchLen = 0; 839 return results; 840 } 841 842 // --------------------------------------------------- 843 // TimeZoneNamesImpl 844 // 845 // TimeZoneNames implementation class. This is the main 846 // part of this module. 847 // --------------------------------------------------- 848 849 U_CDECL_BEGIN 850 /** 851 * Deleter for ZNames 852 */ 853 static void U_CALLCONV 854 deleteZNames(void *obj) { 855 if (obj != EMPTY) { 856 delete (ZNames *)obj; 857 } 858 } 859 /** 860 * Deleter for TZNames 861 */ 862 static void U_CALLCONV 863 deleteTZNames(void *obj) { 864 if (obj != EMPTY) { 865 delete (TZNames *)obj; 866 } 867 } 868 869 /** 870 * Deleter for ZNameInfo 871 */ 872 static void U_CALLCONV 873 deleteZNameInfo(void *obj) { 874 uprv_free(obj); 875 } 876 877 U_CDECL_END 878 879 static UMutex gLock = U_MUTEX_INITIALIZER; 880 881 TimeZoneNamesImpl::TimeZoneNamesImpl(const Locale& locale, UErrorCode& status) 882 : fLocale(locale), 883 fZoneStrings(NULL), 884 fTZNamesMap(NULL), 885 fMZNamesMap(NULL), 886 fNamesTrieFullyLoaded(FALSE), 887 fNamesTrie(TRUE, deleteZNameInfo) { 888 initialize(locale, status); 889 } 890 891 void 892 TimeZoneNamesImpl::initialize(const Locale& locale, UErrorCode& status) { 893 if (U_FAILURE(status)) { 894 return; 895 } 896 897 // Load zoneStrings bundle 898 UErrorCode tmpsts = U_ZERO_ERROR; // OK with fallback warning.. 899 fZoneStrings = ures_open(U_ICUDATA_ZONE, locale.getName(), &tmpsts); 900 fZoneStrings = ures_getByKeyWithFallback(fZoneStrings, gZoneStrings, fZoneStrings, &tmpsts); 901 if (U_FAILURE(tmpsts)) { 902 status = tmpsts; 903 cleanup(); 904 return; 905 } 906 907 // Initialize hashtables holding time zone/meta zone names 908 fMZNamesMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); 909 fTZNamesMap = uhash_open(uhash_hashUChars, uhash_compareUChars, NULL, &status); 910 if (U_FAILURE(status)) { 911 cleanup(); 912 return; 913 } 914 915 uhash_setValueDeleter(fMZNamesMap, deleteZNames); 916 uhash_setValueDeleter(fTZNamesMap, deleteTZNames); 917 // no key deleters for name maps 918 919 // preload zone strings for the default zone 920 TimeZone *tz = TimeZone::createDefault(); 921 const UChar *tzID = ZoneMeta::getCanonicalCLDRID(*tz); 922 if (tzID != NULL) { 923 loadStrings(UnicodeString(tzID)); 924 } 925 delete tz; 926 927 return; 928 } 929 930 /* 931 * This method updates the cache and must be called with a lock, 932 * except initializer. 933 */ 934 void 935 TimeZoneNamesImpl::loadStrings(const UnicodeString& tzCanonicalID) { 936 loadTimeZoneNames(tzCanonicalID); 937 938 UErrorCode status = U_ZERO_ERROR; 939 StringEnumeration *mzIDs = getAvailableMetaZoneIDs(tzCanonicalID, status); 940 if (U_SUCCESS(status) && mzIDs != NULL) { 941 const UnicodeString *mzID; 942 while ((mzID = mzIDs->snext(status))) { 943 if (U_FAILURE(status)) { 944 break; 945 } 946 loadMetaZoneNames(*mzID); 947 } 948 delete mzIDs; 949 } 950 } 951 952 TimeZoneNamesImpl::~TimeZoneNamesImpl() { 953 cleanup(); 954 } 955 956 void 957 TimeZoneNamesImpl::cleanup() { 958 if (fZoneStrings != NULL) { 959 ures_close(fZoneStrings); 960 fZoneStrings = NULL; 961 } 962 if (fMZNamesMap != NULL) { 963 uhash_close(fMZNamesMap); 964 fMZNamesMap = NULL; 965 } 966 if (fTZNamesMap != NULL) { 967 uhash_close(fTZNamesMap); 968 fTZNamesMap = NULL; 969 } 970 } 971 972 UBool 973 TimeZoneNamesImpl::operator==(const TimeZoneNames& other) const { 974 if (this == &other) { 975 return TRUE; 976 } 977 // No implementation for now 978 return FALSE; 979 } 980 981 TimeZoneNames* 982 TimeZoneNamesImpl::clone() const { 983 UErrorCode status = U_ZERO_ERROR; 984 return new TimeZoneNamesImpl(fLocale, status); 985 } 986 987 StringEnumeration* 988 TimeZoneNamesImpl::getAvailableMetaZoneIDs(UErrorCode& status) const { 989 if (U_FAILURE(status)) { 990 return NULL; 991 } 992 const UVector* mzIDs = ZoneMeta::getAvailableMetazoneIDs(); 993 if (mzIDs == NULL) { 994 return new MetaZoneIDsEnumeration(); 995 } 996 return new MetaZoneIDsEnumeration(*mzIDs); 997 } 998 999 StringEnumeration* 1000 TimeZoneNamesImpl::getAvailableMetaZoneIDs(const UnicodeString& tzID, UErrorCode& status) const { 1001 if (U_FAILURE(status)) { 1002 return NULL; 1003 } 1004 const UVector* mappings = ZoneMeta::getMetazoneMappings(tzID); 1005 if (mappings == NULL) { 1006 return new MetaZoneIDsEnumeration(); 1007 } 1008 1009 MetaZoneIDsEnumeration *senum = NULL; 1010 UVector* mzIDs = new UVector(NULL, uhash_compareUChars, status); 1011 if (mzIDs == NULL) { 1012 status = U_MEMORY_ALLOCATION_ERROR; 1013 } 1014 if (U_SUCCESS(status)) { 1015 U_ASSERT(mzIDs != NULL); 1016 for (int32_t i = 0; U_SUCCESS(status) && i < mappings->size(); i++) { 1017 1018 OlsonToMetaMappingEntry *map = (OlsonToMetaMappingEntry *)mappings->elementAt(i); 1019 const UChar *mzID = map->mzid; 1020 if (!mzIDs->contains((void *)mzID)) { 1021 mzIDs->addElement((void *)mzID, status); 1022 } 1023 } 1024 if (U_SUCCESS(status)) { 1025 senum = new MetaZoneIDsEnumeration(mzIDs); 1026 } else { 1027 delete mzIDs; 1028 } 1029 } 1030 return senum; 1031 } 1032 1033 UnicodeString& 1034 TimeZoneNamesImpl::getMetaZoneID(const UnicodeString& tzID, UDate date, UnicodeString& mzID) const { 1035 ZoneMeta::getMetazoneID(tzID, date, mzID); 1036 return mzID; 1037 } 1038 1039 UnicodeString& 1040 TimeZoneNamesImpl::getReferenceZoneID(const UnicodeString& mzID, const char* region, UnicodeString& tzID) const { 1041 ZoneMeta::getZoneIdByMetazone(mzID, UnicodeString(region, -1, US_INV), tzID); 1042 return tzID; 1043 } 1044 1045 UnicodeString& 1046 TimeZoneNamesImpl::getMetaZoneDisplayName(const UnicodeString& mzID, 1047 UTimeZoneNameType type, 1048 UnicodeString& name) const { 1049 name.setToBogus(); // cleanup result. 1050 if (mzID.isEmpty()) { 1051 return name; 1052 } 1053 1054 ZNames *znames = NULL; 1055 TimeZoneNamesImpl *nonConstThis = const_cast<TimeZoneNamesImpl *>(this); 1056 1057 umtx_lock(&gLock); 1058 { 1059 znames = nonConstThis->loadMetaZoneNames(mzID); 1060 } 1061 umtx_unlock(&gLock); 1062 1063 if (znames != NULL) { 1064 const UChar* s = znames->getName(type); 1065 if (s != NULL) { 1066 name.setTo(TRUE, s, -1); 1067 } 1068 } 1069 return name; 1070 } 1071 1072 UnicodeString& 1073 TimeZoneNamesImpl::getTimeZoneDisplayName(const UnicodeString& tzID, UTimeZoneNameType type, UnicodeString& name) const { 1074 name.setToBogus(); // cleanup result. 1075 if (tzID.isEmpty()) { 1076 return name; 1077 } 1078 1079 TZNames *tznames = NULL; 1080 TimeZoneNamesImpl *nonConstThis = const_cast<TimeZoneNamesImpl *>(this); 1081 1082 umtx_lock(&gLock); 1083 { 1084 tznames = nonConstThis->loadTimeZoneNames(tzID); 1085 } 1086 umtx_unlock(&gLock); 1087 1088 if (tznames != NULL) { 1089 const UChar *s = tznames->getName(type); 1090 if (s != NULL) { 1091 name.setTo(TRUE, s, -1); 1092 } 1093 } 1094 return name; 1095 } 1096 1097 UnicodeString& 1098 TimeZoneNamesImpl::getExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) const { 1099 name.setToBogus(); // cleanup result. 1100 const UChar* locName = NULL; 1101 TZNames *tznames = NULL; 1102 TimeZoneNamesImpl *nonConstThis = const_cast<TimeZoneNamesImpl *>(this); 1103 1104 umtx_lock(&gLock); 1105 { 1106 tznames = nonConstThis->loadTimeZoneNames(tzID); 1107 } 1108 umtx_unlock(&gLock); 1109 1110 if (tznames != NULL) { 1111 locName = tznames->getName(UTZNM_EXEMPLAR_LOCATION); 1112 } 1113 if (locName != NULL) { 1114 name.setTo(TRUE, locName, -1); 1115 } 1116 1117 return name; 1118 } 1119 1120 1121 // Merge the MZ_PREFIX and mzId 1122 static void mergeTimeZoneKey(const UnicodeString& mzID, char* result) { 1123 if (mzID.isEmpty()) { 1124 result[0] = '\0'; 1125 return; 1126 } 1127 1128 char mzIdChar[ZID_KEY_MAX + 1]; 1129 int32_t keyLen; 1130 int32_t prefixLen = uprv_strlen(gMZPrefix); 1131 keyLen = mzID.extract(0, mzID.length(), mzIdChar, ZID_KEY_MAX + 1, US_INV); 1132 uprv_memcpy((void *)result, (void *)gMZPrefix, prefixLen); 1133 uprv_memcpy((void *)(result + prefixLen), (void *)mzIdChar, keyLen); 1134 result[keyLen + prefixLen] = '\0'; 1135 } 1136 1137 /* 1138 * This method updates the cache and must be called with a lock 1139 */ 1140 ZNames* 1141 TimeZoneNamesImpl::loadMetaZoneNames(const UnicodeString& mzID) { 1142 if (mzID.length() > (ZID_KEY_MAX - MZ_PREFIX_LEN)) { 1143 return NULL; 1144 } 1145 1146 ZNames *znames = NULL; 1147 1148 UErrorCode status = U_ZERO_ERROR; 1149 UChar mzIDKey[ZID_KEY_MAX + 1]; 1150 mzID.extract(mzIDKey, ZID_KEY_MAX + 1, status); 1151 U_ASSERT(status == U_ZERO_ERROR); // already checked length above 1152 mzIDKey[mzID.length()] = 0; 1153 1154 void *cacheVal = uhash_get(fMZNamesMap, mzIDKey); 1155 if (cacheVal == NULL) { 1156 char key[ZID_KEY_MAX + 1]; 1157 mergeTimeZoneKey(mzID, key); 1158 znames = ZNames::createInstance(fZoneStrings, key); 1159 1160 if (znames == NULL) { 1161 cacheVal = (void *)EMPTY; 1162 } else { 1163 cacheVal = znames; 1164 } 1165 // Use the persistent ID as the resource key, so we can 1166 // avoid duplications. 1167 const UChar* newKey = ZoneMeta::findMetaZoneID(mzID); 1168 if (newKey != NULL) { 1169 uhash_put(fMZNamesMap, (void *)newKey, cacheVal, &status); 1170 if (U_FAILURE(status)) { 1171 if (znames != NULL) { 1172 delete znames; 1173 } 1174 } else if (znames != NULL) { 1175 // put the name info into the trie 1176 for (int32_t i = 0; ALL_NAME_TYPES[i] != UTZNM_UNKNOWN; i++) { 1177 const UChar* name = znames->getName(ALL_NAME_TYPES[i]); 1178 if (name != NULL) { 1179 ZNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(ZNameInfo)); 1180 if (nameinfo != NULL) { 1181 nameinfo->type = ALL_NAME_TYPES[i]; 1182 nameinfo->tzID = NULL; 1183 nameinfo->mzID = newKey; 1184 fNamesTrie.put(name, nameinfo, status); 1185 } 1186 } 1187 } 1188 } 1189 1190 } else { 1191 // Should never happen with a valid input 1192 if (znames != NULL) { 1193 // It's not possible that we get a valid ZNames with unknown ID. 1194 // But just in case.. 1195 delete znames; 1196 znames = NULL; 1197 } 1198 } 1199 } else if (cacheVal != EMPTY) { 1200 znames = (ZNames *)cacheVal; 1201 } 1202 1203 return znames; 1204 } 1205 1206 /* 1207 * This method updates the cache and must be called with a lock 1208 */ 1209 TZNames* 1210 TimeZoneNamesImpl::loadTimeZoneNames(const UnicodeString& tzID) { 1211 if (tzID.length() > ZID_KEY_MAX) { 1212 return NULL; 1213 } 1214 1215 TZNames *tznames = NULL; 1216 1217 UErrorCode status = U_ZERO_ERROR; 1218 UChar tzIDKey[ZID_KEY_MAX + 1]; 1219 int32_t tzIDKeyLen = tzID.extract(tzIDKey, ZID_KEY_MAX + 1, status); 1220 U_ASSERT(status == U_ZERO_ERROR); // already checked length above 1221 tzIDKey[tzIDKeyLen] = 0; 1222 1223 void *cacheVal = uhash_get(fTZNamesMap, tzIDKey); 1224 if (cacheVal == NULL) { 1225 char key[ZID_KEY_MAX + 1]; 1226 UErrorCode status = U_ZERO_ERROR; 1227 // Replace "/" with ":". 1228 UnicodeString uKey(tzID); 1229 for (int32_t i = 0; i < uKey.length(); i++) { 1230 if (uKey.charAt(i) == (UChar)0x2F) { 1231 uKey.setCharAt(i, (UChar)0x3A); 1232 } 1233 } 1234 uKey.extract(0, uKey.length(), key, sizeof(key), US_INV); 1235 tznames = TZNames::createInstance(fZoneStrings, key, tzID); 1236 1237 if (tznames == NULL) { 1238 cacheVal = (void *)EMPTY; 1239 } else { 1240 cacheVal = tznames; 1241 } 1242 // Use the persistent ID as the resource key, so we can 1243 // avoid duplications. 1244 const UChar* newKey = ZoneMeta::findTimeZoneID(tzID); 1245 if (newKey != NULL) { 1246 uhash_put(fTZNamesMap, (void *)newKey, cacheVal, &status); 1247 if (U_FAILURE(status)) { 1248 if (tznames != NULL) { 1249 delete tznames; 1250 } 1251 } else if (tznames != NULL) { 1252 // put the name info into the trie 1253 for (int32_t i = 0; ALL_NAME_TYPES[i] != UTZNM_UNKNOWN; i++) { 1254 const UChar* name = tznames->getName(ALL_NAME_TYPES[i]); 1255 if (name != NULL) { 1256 ZNameInfo *nameinfo = (ZNameInfo *)uprv_malloc(sizeof(ZNameInfo)); 1257 if (nameinfo != NULL) { 1258 nameinfo->type = ALL_NAME_TYPES[i]; 1259 nameinfo->tzID = newKey; 1260 nameinfo->mzID = NULL; 1261 fNamesTrie.put(name, nameinfo, status); 1262 } 1263 } 1264 } 1265 } 1266 } else { 1267 // Should never happen with a valid input 1268 if (tznames != NULL) { 1269 // It's not possible that we get a valid TZNames with unknown ID. 1270 // But just in case.. 1271 delete tznames; 1272 tznames = NULL; 1273 } 1274 } 1275 } else if (cacheVal != EMPTY) { 1276 tznames = (TZNames *)cacheVal; 1277 } 1278 1279 return tznames; 1280 } 1281 1282 TimeZoneNames::MatchInfoCollection* 1283 TimeZoneNamesImpl::find(const UnicodeString& text, int32_t start, uint32_t types, UErrorCode& status) const { 1284 ZNameSearchHandler handler(types); 1285 1286 TimeZoneNamesImpl *nonConstThis = const_cast<TimeZoneNamesImpl *>(this); 1287 1288 umtx_lock(&gLock); 1289 { 1290 fNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); 1291 } 1292 umtx_unlock(&gLock); 1293 1294 if (U_FAILURE(status)) { 1295 return NULL; 1296 } 1297 1298 int32_t maxLen = 0; 1299 TimeZoneNames::MatchInfoCollection* matches = handler.getMatches(maxLen); 1300 if (matches != NULL && ((maxLen == (text.length() - start)) || fNamesTrieFullyLoaded)) { 1301 // perfect match 1302 return matches; 1303 } 1304 1305 delete matches; 1306 1307 // All names are not yet loaded into the trie 1308 umtx_lock(&gLock); 1309 { 1310 if (!fNamesTrieFullyLoaded) { 1311 const UnicodeString *id; 1312 1313 // load strings for all zones 1314 StringEnumeration *tzIDs = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status); 1315 if (U_SUCCESS(status)) { 1316 while ((id = tzIDs->snext(status))) { 1317 if (U_FAILURE(status)) { 1318 break; 1319 } 1320 // loadStrings also load related metazone strings 1321 nonConstThis->loadStrings(*id); 1322 } 1323 } 1324 if (tzIDs != NULL) { 1325 delete tzIDs; 1326 } 1327 if (U_SUCCESS(status)) { 1328 nonConstThis->fNamesTrieFullyLoaded = TRUE; 1329 } 1330 } 1331 } 1332 umtx_unlock(&gLock); 1333 1334 if (U_FAILURE(status)) { 1335 return NULL; 1336 } 1337 1338 umtx_lock(&gLock); 1339 { 1340 // now try it again 1341 fNamesTrie.search(text, start, (TextTrieMapSearchResultHandler *)&handler, status); 1342 } 1343 umtx_unlock(&gLock); 1344 1345 return handler.getMatches(maxLen); 1346 } 1347 1348 static const UChar gEtcPrefix[] = { 0x45, 0x74, 0x63, 0x2F }; // "Etc/" 1349 static const int32_t gEtcPrefixLen = 4; 1350 static const UChar gSystemVPrefix[] = { 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x56, 0x2F }; // "SystemV/ 1351 static const int32_t gSystemVPrefixLen = 8; 1352 static const UChar gRiyadh8[] = { 0x52, 0x69, 0x79, 0x61, 0x64, 0x68, 0x38 }; // "Riyadh8" 1353 static const int32_t gRiyadh8Len = 7; 1354 1355 UnicodeString& U_EXPORT2 1356 TimeZoneNamesImpl::getDefaultExemplarLocationName(const UnicodeString& tzID, UnicodeString& name) { 1357 if (tzID.isEmpty() || tzID.startsWith(gEtcPrefix, gEtcPrefixLen) 1358 || tzID.startsWith(gSystemVPrefix, gSystemVPrefixLen) || tzID.indexOf(gRiyadh8, gRiyadh8Len, 0) > 0) { 1359 name.setToBogus(); 1360 return name; 1361 } 1362 1363 int32_t sep = tzID.lastIndexOf((UChar)0x2F /* '/' */); 1364 if (sep > 0 && sep + 1 < tzID.length()) { 1365 name.setTo(tzID, sep + 1); 1366 name.findAndReplace(UnicodeString((UChar)0x5f /* _ */), 1367 UnicodeString((UChar)0x20 /* space */)); 1368 } else { 1369 name.setToBogus(); 1370 } 1371 return name; 1372 } 1373 1374 U_NAMESPACE_END 1375 1376 1377 #endif /* #if !UCONFIG_NO_FORMATTING */ 1378 1379 //eof 1380