| /* |
| ******************************************************************************* |
| * Copyright (C) 2007-2010, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ******************************************************************************* |
| */ |
| |
| #include "unicode/utypes.h" |
| |
| #if !UCONFIG_NO_FORMATTING |
| |
| #include "zstrfmt.h" |
| |
| #include "unicode/ustring.h" |
| #include "unicode/putil.h" |
| #include "unicode/msgfmt.h" |
| #include "unicode/basictz.h" |
| #include "unicode/simpletz.h" |
| #include "unicode/rbtz.h" |
| #include "unicode/vtzone.h" |
| |
| #include "uvector.h" |
| #include "cstring.h" |
| #include "cmemory.h" |
| #include "uresimp.h" |
| #include "zonemeta.h" |
| #include "olsontz.h" |
| #include "umutex.h" |
| #include "ucln_in.h" |
| #include "uassert.h" |
| #include "ureslocs.h" |
| |
| /** |
| * global ZoneStringFormatCache stuffs |
| */ |
| static UMTX gZSFCacheLock = NULL; |
| static U_NAMESPACE_QUALIFIER ZSFCache *gZoneStringFormatCache = NULL; |
| |
| U_CDECL_BEGIN |
| /** |
| * ZoneStringFormatCache cleanup callback func |
| */ |
| static UBool U_CALLCONV zoneStringFormat_cleanup(void) |
| { |
| umtx_destroy(&gZSFCacheLock); |
| if (gZoneStringFormatCache != NULL) { |
| delete gZoneStringFormatCache; |
| gZoneStringFormatCache = NULL; |
| } |
| gZoneStringFormatCache = NULL; |
| return TRUE; |
| } |
| |
| /** |
| * Deleter for ZoneStringInfo |
| */ |
| static void U_CALLCONV |
| deleteZoneStringInfo(void *obj) { |
| delete (U_NAMESPACE_QUALIFIER ZoneStringInfo*)obj; |
| } |
| |
| /** |
| * Deleter for ZoneStrings |
| */ |
| static void U_CALLCONV |
| deleteZoneStrings(void *obj) { |
| delete (U_NAMESPACE_QUALIFIER ZoneStrings*)obj; |
| } |
| U_CDECL_END |
| |
| U_NAMESPACE_BEGIN |
| |
| #define ZID_KEY_MAX 128 |
| |
| static const char gCountriesTag[] = "Countries"; |
| static const char gZoneStringsTag[] = "zoneStrings"; |
| static const char gShortGenericTag[] = "sg"; |
| static const char gShortStandardTag[] = "ss"; |
| static const char gShortDaylightTag[] = "sd"; |
| static const char gLongGenericTag[] = "lg"; |
| static const char gLongStandardTag[] = "ls"; |
| static const char gLongDaylightTag[] = "ld"; |
| static const char gExemplarCityTag[] = "ec"; |
| static const char gCommonlyUsedTag[] = "cu"; |
| static const char gFallbackFormatTag[] = "fallbackFormat"; |
| static const char gRegionFormatTag[] = "regionFormat"; |
| |
| #define MZID_PREFIX_LEN 5 |
| static const char gMetazoneIdPrefix[] = "meta:"; |
| |
| #define MAX_METAZONES_PER_ZONE 10 |
| |
| static const UChar gDefFallbackPattern[] = {0x7B, 0x31, 0x7D, 0x20, 0x28, 0x7B, 0x30, 0x7D, 0x29, 0x00}; // "{1} ({0})" |
| static const UChar gDefRegionPattern[] = {0x7B, 0x30, 0x7D, 0x00}; // "{0}" |
| static const UChar gCommonlyUsedTrue[] = {0x31, 0x00}; // "1" |
| |
| static const double kDstCheckRange = (double)184*U_MILLIS_PER_DAY; |
| |
| static int32_t |
| getTimeZoneTranslationTypeIndex(TimeZoneTranslationType type) { |
| int32_t typeIdx = 0; |
| switch (type) { |
| case LOCATION: |
| typeIdx = ZSIDX_LOCATION; |
| break; |
| case GENERIC_LONG: |
| typeIdx = ZSIDX_LONG_GENERIC; |
| break; |
| case GENERIC_SHORT: |
| typeIdx = ZSIDX_SHORT_GENERIC; |
| break; |
| case STANDARD_LONG: |
| typeIdx = ZSIDX_LONG_STANDARD; |
| break; |
| case STANDARD_SHORT: |
| typeIdx = ZSIDX_SHORT_STANDARD; |
| break; |
| case DAYLIGHT_LONG: |
| typeIdx = ZSIDX_LONG_DAYLIGHT; |
| break; |
| case DAYLIGHT_SHORT: |
| typeIdx = ZSIDX_SHORT_DAYLIGHT; |
| break; |
| } |
| return typeIdx; |
| } |
| |
| static int32_t |
| getTimeZoneTranslationType(TimeZoneTranslationTypeIndex typeIdx) { |
| int32_t type = 0; |
| switch (typeIdx) { |
| case ZSIDX_LOCATION: |
| type = LOCATION; |
| break; |
| case ZSIDX_LONG_GENERIC: |
| type = GENERIC_LONG; |
| break; |
| case ZSIDX_SHORT_GENERIC: |
| type = GENERIC_SHORT; |
| break; |
| case ZSIDX_LONG_STANDARD: |
| type = STANDARD_LONG; |
| break; |
| case ZSIDX_SHORT_STANDARD: |
| type = STANDARD_SHORT; |
| break; |
| case ZSIDX_LONG_DAYLIGHT: |
| type = DAYLIGHT_LONG; |
| break; |
| case ZSIDX_COUNT: |
| case ZSIDX_SHORT_DAYLIGHT: |
| type = DAYLIGHT_SHORT; |
| break; |
| default: |
| break; |
| } |
| return type; |
| } |
| |
| #define DEFAULT_CHARACTERNODE_CAPACITY 1 |
| |
| // ---------------------------------------------------------------------------- |
| void CharacterNode::clear() { |
| uprv_memset(this, 0, sizeof(*this)); |
| } |
| |
| void CharacterNode::deleteValues() { |
| if (fValues == NULL) { |
| // Do nothing. |
| } else if (!fHasValuesVector) { |
| deleteZoneStringInfo(fValues); |
| } else { |
| delete (UVector *)fValues; |
| } |
| } |
| |
| void |
| CharacterNode::addValue(void *value, UErrorCode &status) { |
| if (U_FAILURE(status)) { |
| deleteZoneStringInfo(value); |
| return; |
| } |
| if (fValues == NULL) { |
| fValues = value; |
| } else { |
| // At least one value already. |
| if (!fHasValuesVector) { |
| // There is only one value so far, and not in a vector yet. |
| // Create a vector and add the old value. |
| UVector *values = new UVector(deleteZoneStringInfo, NULL, DEFAULT_CHARACTERNODE_CAPACITY, status); |
| if (U_FAILURE(status)) { |
| deleteZoneStringInfo(value); |
| return; |
| } |
| values->addElement(fValues, status); |
| fValues = values; |
| fHasValuesVector = TRUE; |
| } |
| // Add the new value. |
| ((UVector *)fValues)->addElement(value, status); |
| } |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Virtual destructor to avoid warning |
| TextTrieMapSearchResultHandler::~TextTrieMapSearchResultHandler(){ |
| } |
| |
| // ---------------------------------------------------------------------------- |
| TextTrieMap::TextTrieMap(UBool ignoreCase) |
| : fIgnoreCase(ignoreCase), fNodes(NULL), fNodesCapacity(0), fNodesCount(0), |
| fLazyContents(NULL), fIsEmpty(TRUE) { |
| } |
| |
| TextTrieMap::~TextTrieMap() { |
| int32_t index; |
| for (index = 0; index < fNodesCount; ++index) { |
| fNodes[index].deleteValues(); |
| } |
| uprv_free(fNodes); |
| if (fLazyContents != NULL) { |
| for (int32_t i=0; i<fLazyContents->size(); i+=2) { |
| ZoneStringInfo *zsinf = (ZoneStringInfo *)fLazyContents->elementAt(i+1); |
| delete zsinf; |
| } |
| delete fLazyContents; |
| } |
| } |
| |
| int32_t TextTrieMap::isEmpty() const { |
| // Use a separate field for fIsEmpty because it will remain unchanged once the |
| // Trie is built, while fNodes and fLazyContents change with the lazy init |
| // of the nodes structure. Trying to test the changing fields has |
| // thread safety complications. |
| return fIsEmpty; |
| } |
| |
| |
| // We defer actually building the TextTrieMap node structure until the first time a |
| // search is performed. put() simply saves the parameters in case we do |
| // eventually need to build it. |
| // |
| void |
| TextTrieMap::put(const UnicodeString &key, void *value, ZSFStringPool &sp, UErrorCode &status) { |
| fIsEmpty = FALSE; |
| if (fLazyContents == NULL) { |
| fLazyContents = new UVector(status); |
| if (fLazyContents == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| } |
| } |
| if (U_FAILURE(status)) { |
| return; |
| } |
| UChar *s = const_cast<UChar *>(sp.get(key, status)); |
| fLazyContents->addElement(s, status); |
| fLazyContents->addElement(value, status); |
| } |
| |
| |
| void |
| TextTrieMap::putImpl(const UnicodeString &key, void *value, UErrorCode &status) { |
| if (fNodes == NULL) { |
| fNodesCapacity = 512; |
| fNodes = (CharacterNode *)uprv_malloc(fNodesCapacity * sizeof(CharacterNode)); |
| fNodes[0].clear(); // Init root node. |
| fNodesCount = 1; |
| } |
| |
| UnicodeString foldedKey; |
| const UChar *keyBuffer; |
| int32_t keyLength; |
| if (fIgnoreCase) { |
| // Ok to use fastCopyFrom() because we discard the copy when we return. |
| foldedKey.fastCopyFrom(key).foldCase(); |
| keyBuffer = foldedKey.getBuffer(); |
| keyLength = foldedKey.length(); |
| } else { |
| keyBuffer = key.getBuffer(); |
| keyLength = key.length(); |
| } |
| |
| CharacterNode *node = fNodes; |
| int32_t index; |
| for (index = 0; index < keyLength; ++index) { |
| node = addChildNode(node, keyBuffer[index], status); |
| } |
| node->addValue(value, status); |
| } |
| |
| UBool |
| TextTrieMap::growNodes() { |
| if (fNodesCapacity == 0xffff) { |
| return FALSE; // We use 16-bit node indexes. |
| } |
| int32_t newCapacity = fNodesCapacity + 1000; |
| if (newCapacity > 0xffff) { |
| newCapacity = 0xffff; |
| } |
| CharacterNode *newNodes = (CharacterNode *)uprv_malloc(newCapacity * sizeof(CharacterNode)); |
| if (newNodes == NULL) { |
| return FALSE; |
| } |
| uprv_memcpy(newNodes, fNodes, fNodesCount * sizeof(CharacterNode)); |
| uprv_free(fNodes); |
| fNodes = newNodes; |
| fNodesCapacity = newCapacity; |
| return TRUE; |
| } |
| |
| CharacterNode* |
| TextTrieMap::addChildNode(CharacterNode *parent, UChar c, UErrorCode &status) { |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| // Linear search of the sorted list of children. |
| uint16_t prevIndex = 0; |
| uint16_t nodeIndex = parent->fFirstChild; |
| while (nodeIndex > 0) { |
| CharacterNode *current = fNodes + nodeIndex; |
| UChar childCharacter = current->fCharacter; |
| if (childCharacter == c) { |
| return current; |
| } else if (childCharacter > c) { |
| break; |
| } |
| prevIndex = nodeIndex; |
| nodeIndex = current->fNextSibling; |
| } |
| |
| // Ensure capacity. Grow fNodes[] if needed. |
| if (fNodesCount == fNodesCapacity) { |
| int32_t parentIndex = (int32_t)(parent - fNodes); |
| if (!growNodes()) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| return NULL; |
| } |
| parent = fNodes + parentIndex; |
| } |
| |
| // Insert a new child node with c in sorted order. |
| CharacterNode *node = fNodes + fNodesCount; |
| node->clear(); |
| node->fCharacter = c; |
| node->fNextSibling = nodeIndex; |
| if (prevIndex == 0) { |
| parent->fFirstChild = (uint16_t)fNodesCount; |
| } else { |
| fNodes[prevIndex].fNextSibling = (uint16_t)fNodesCount; |
| } |
| ++fNodesCount; |
| return node; |
| } |
| |
| CharacterNode* |
| TextTrieMap::getChildNode(CharacterNode *parent, UChar c) const { |
| // Linear search of the sorted list of children. |
| uint16_t nodeIndex = parent->fFirstChild; |
| while (nodeIndex > 0) { |
| CharacterNode *current = fNodes + nodeIndex; |
| UChar childCharacter = current->fCharacter; |
| if (childCharacter == c) { |
| return current; |
| } else if (childCharacter > c) { |
| break; |
| } |
| nodeIndex = current->fNextSibling; |
| } |
| return NULL; |
| } |
| |
| // Mutex for protecting the lazy creation of the Trie node structure on the first call to search(). |
| static UMTX TextTrieMutex; |
| |
| // buildTrie() - The Trie node structure is needed. Create it from the data that was |
| // saved at the time the ZoneStringFormatter was created. The Trie is only |
| // needed for parsing operations, which are less common than formatting, |
| // and the Trie is big, which is why its creation is deferred until first use. |
| void TextTrieMap::buildTrie(UErrorCode &status) { |
| umtx_lock(&TextTrieMutex); |
| if (fLazyContents != NULL) { |
| for (int32_t i=0; i<fLazyContents->size(); i+=2) { |
| const UChar *key = (UChar *)fLazyContents->elementAt(i); |
| void *val = fLazyContents->elementAt(i+1); |
| UnicodeString keyString(TRUE, key, -1); // Aliasing UnicodeString constructor. |
| putImpl(keyString, val, status); |
| } |
| delete fLazyContents; |
| fLazyContents = NULL; |
| } |
| umtx_unlock(&TextTrieMutex); |
| } |
| |
| |
| void |
| TextTrieMap::search(const UnicodeString &text, int32_t start, |
| TextTrieMapSearchResultHandler *handler, UErrorCode &status) const { |
| UBool trieNeedsInitialization = FALSE; |
| UMTX_CHECK(&TextTrieMutex, fLazyContents != NULL, trieNeedsInitialization); |
| if (trieNeedsInitialization) { |
| TextTrieMap *nonConstThis = const_cast<TextTrieMap *>(this); |
| nonConstThis->buildTrie(status); |
| } |
| if (fNodes == NULL) { |
| return; |
| } |
| search(fNodes, text, start, start, handler, status); |
| } |
| |
| void |
| TextTrieMap::search(CharacterNode *node, const UnicodeString &text, int32_t start, |
| int32_t index, TextTrieMapSearchResultHandler *handler, UErrorCode &status) const { |
| if (U_FAILURE(status)) { |
| return; |
| } |
| if (node->hasValues()) { |
| if (!handler->handleMatch(index - start, node, status)) { |
| return; |
| } |
| if (U_FAILURE(status)) { |
| return; |
| } |
| } |
| UChar32 c = text.char32At(index); |
| if (fIgnoreCase) { |
| // size of character may grow after fold operation |
| UnicodeString tmp(c); |
| tmp.foldCase(); |
| int32_t tmpidx = 0; |
| while (tmpidx < tmp.length()) { |
| c = tmp.char32At(tmpidx); |
| node = getChildNode(node, c); |
| if (node == NULL) { |
| break; |
| } |
| tmpidx = tmp.moveIndex32(tmpidx, 1); |
| } |
| } else { |
| node = getChildNode(node, c); |
| } |
| if (node != NULL) { |
| search(node, text, start, index+1, handler, status); |
| } |
| } |
| |
| // ---------------------------------------------------------------------------- |
| ZoneStringInfo::ZoneStringInfo(const UnicodeString &id, const UnicodeString &str, |
| TimeZoneTranslationType type, ZSFStringPool &sp, UErrorCode &status) |
| : fType(type) { |
| fId = sp.get(id, status); |
| fStr = sp.get(str, status); |
| } |
| |
| ZoneStringInfo::~ZoneStringInfo() { |
| } |
| |
| |
| // ---------------------------------------------------------------------------- |
| ZoneStringSearchResultHandler::ZoneStringSearchResultHandler(UErrorCode &status) |
| : fResults(status) |
| { |
| clear(); |
| } |
| |
| ZoneStringSearchResultHandler::~ZoneStringSearchResultHandler() { |
| clear(); |
| } |
| |
| UBool |
| ZoneStringSearchResultHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) { |
| if (U_FAILURE(status)) { |
| return FALSE; |
| } |
| if (node->hasValues()) { |
| int32_t valuesCount = node->countValues(); |
| for (int32_t i = 0; i < valuesCount; i++) { |
| ZoneStringInfo *zsinfo = (ZoneStringInfo*)node->getValue(i); |
| if (zsinfo == NULL) { |
| break; |
| } |
| // Update the results |
| UBool foundType = FALSE; |
| for (int32_t j = 0; j < fResults.size(); j++) { |
| ZoneStringInfo *tmp = (ZoneStringInfo*)fResults.elementAt(j); |
| if (zsinfo->fType == tmp->fType) { |
| int32_t lenidx = getTimeZoneTranslationTypeIndex(tmp->fType); |
| if (matchLength > fMatchLen[lenidx]) { |
| // Same type, longer match |
| fResults.setElementAt(zsinfo, j); |
| fMatchLen[lenidx] = matchLength; |
| } |
| foundType = TRUE; |
| break; |
| } |
| } |
| if (!foundType) { |
| // not found in the current list |
| fResults.addElement(zsinfo, status); |
| fMatchLen[getTimeZoneTranslationTypeIndex(zsinfo->fType)] = matchLength; |
| } |
| } |
| } |
| return TRUE; |
| } |
| |
| int32_t |
| ZoneStringSearchResultHandler::countMatches(void) { |
| return fResults.size(); |
| } |
| |
| const ZoneStringInfo* |
| ZoneStringSearchResultHandler::getMatch(int32_t index, int32_t &matchLength) { |
| ZoneStringInfo *zsinfo = NULL; |
| if (index < fResults.size()) { |
| zsinfo = (ZoneStringInfo*)fResults.elementAt(index); |
| matchLength = fMatchLen[getTimeZoneTranslationTypeIndex(zsinfo->fType)]; |
| } |
| return zsinfo; |
| } |
| |
| void |
| ZoneStringSearchResultHandler::clear(void) { |
| fResults.removeAllElements(); |
| for (int32_t i = 0; i < (int32_t)(sizeof(fMatchLen)/sizeof(fMatchLen[0])); i++) { |
| fMatchLen[i] = 0; |
| } |
| } |
| |
| // Mutex for protecting the lazy load of a zone ID (or a full load) to ZoneStringFormat structures. |
| static UMTX ZoneStringFormatMutex; |
| |
| |
| // ---------------------------------------------------------------------------- |
| ZoneStringFormat::ZoneStringFormat(const UnicodeString* const* strings, |
| int32_t rowCount, int32_t columnCount, UErrorCode &status) |
| : fLocale(""), |
| fTzidToStrings(NULL), |
| fMzidToStrings(NULL), |
| fZoneStringsTrie(TRUE), |
| fStringPool(status), |
| fZoneStringsArray(NULL), |
| fMetazoneItem(NULL), |
| fZoneItem(NULL), |
| fIsFullyLoaded(FALSE) |
| { |
| if (U_FAILURE(status)) { |
| return; |
| } |
| fLocale.setToBogus(); |
| if (strings == NULL || columnCount <= 0 || rowCount <= 0) { |
| status = U_ILLEGAL_ARGUMENT_ERROR; |
| return; |
| } |
| fTzidToStrings = uhash_open(uhash_hashUChars, // key hash function |
| uhash_compareUChars, // key comparison function |
| NULL, // Value comparison function |
| &status); |
| fMzidToStrings = uhash_open(uhash_hashUChars, |
| uhash_compareUChars, |
| NULL, |
| &status); |
| |
| uhash_setValueDeleter(fTzidToStrings, deleteZoneStrings); |
| uhash_setValueDeleter(fMzidToStrings, deleteZoneStrings); |
| |
| for (int32_t row = 0; row < rowCount; row++) { |
| if (strings[row][0].isEmpty()) { |
| continue; |
| } |
| UnicodeString *names = new UnicodeString[ZSIDX_COUNT]; |
| for (int32_t col = 1; col < columnCount; col++) { |
| if (!strings[row][col].isEmpty()) { |
| int32_t typeIdx = -1; |
| switch (col) { |
| case 1: |
| typeIdx = ZSIDX_LONG_STANDARD; |
| break; |
| case 2: |
| typeIdx = ZSIDX_SHORT_STANDARD; |
| break; |
| case 3: |
| typeIdx = ZSIDX_LONG_DAYLIGHT; |
| break; |
| case 4: |
| typeIdx = ZSIDX_SHORT_DAYLIGHT; |
| break; |
| case 5: |
| typeIdx = ZSIDX_LOCATION; |
| break; |
| case 6: |
| typeIdx = ZSIDX_LONG_GENERIC; |
| break; |
| case 7: |
| typeIdx = ZSIDX_SHORT_GENERIC; |
| break; |
| } |
| if (typeIdx != -1) { |
| names[typeIdx].setTo(strings[row][col]); |
| |
| // Put the name into the trie |
| int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)typeIdx); |
| ZoneStringInfo *zsinf = new ZoneStringInfo(strings[row][0], |
| strings[row][col], |
| (TimeZoneTranslationType)type, |
| fStringPool, |
| status); |
| fZoneStringsTrie.put(strings[row][col], zsinf, fStringPool, status); |
| if (U_FAILURE(status)) { |
| delete zsinf; |
| goto error_cleanup; |
| } |
| } |
| } |
| } |
| // Note: ZoneStrings constructor adopts and delete the names array. |
| ZoneStrings *zstrings = new ZoneStrings(names, ZSIDX_COUNT, TRUE, NULL, 0, 0, |
| fStringPool, status); |
| UChar *utzid = const_cast<UChar *>(fStringPool.get(strings[row][0], status)); |
| uhash_put(fTzidToStrings, utzid, zstrings, &status); |
| if (U_FAILURE(status)) { |
| delete zstrings; |
| goto error_cleanup; |
| } |
| } |
| fStringPool.freeze(); |
| fIsFullyLoaded = TRUE; |
| return; |
| |
| error_cleanup: |
| return; |
| } |
| |
| ZoneStringFormat::ZoneStringFormat(const Locale &locale, UErrorCode &status) |
| : fLocale(locale), |
| fTzidToStrings(NULL), |
| fMzidToStrings(NULL), |
| fZoneStringsTrie(TRUE), |
| fStringPool(status), |
| fZoneStringsArray(NULL), |
| fMetazoneItem(NULL), |
| fZoneItem(NULL), |
| fIsFullyLoaded(FALSE) |
| { |
| if (U_FAILURE(status)) { |
| return; |
| } |
| fTzidToStrings = uhash_open(uhash_hashUChars, // key hash function |
| uhash_compareUChars, // key comparison function |
| NULL, // Value comparison function |
| &status); |
| fMzidToStrings = uhash_open(uhash_hashUChars, // key hash function |
| uhash_compareUChars, // key comparison function |
| NULL, // Value comparison function |
| &status); |
| uhash_setValueDeleter(fTzidToStrings, deleteZoneStrings); |
| uhash_setValueDeleter(fMzidToStrings, deleteZoneStrings); |
| } |
| |
| // Load only a single zone |
| void |
| ZoneStringFormat::loadZone(const UnicodeString &utzid, UErrorCode &status) |
| { |
| if (fIsFullyLoaded) { |
| return; |
| } |
| |
| if (U_FAILURE(status)) { |
| return; |
| } |
| |
| umtx_lock(&ZoneStringFormatMutex); |
| |
| if (fZoneStringsArray == NULL) { |
| fZoneStringsArray = ures_open(U_ICUDATA_ZONE, fLocale.getName(), &status); |
| fZoneStringsArray = ures_getByKeyWithFallback(fZoneStringsArray, gZoneStringsTag, fZoneStringsArray, &status); |
| if (U_FAILURE(status)) { |
| // If no locale bundles are available, zoneStrings will be null. |
| // We still want to go through the rest of zone strings initialization, |
| // because generic location format is generated from tzid for the case. |
| // The rest of code should work even zoneStrings is null. |
| status = U_ZERO_ERROR; |
| ures_close(fZoneStringsArray); |
| fZoneStringsArray = NULL; |
| } |
| } |
| |
| // Skip non-canonical IDs |
| UnicodeString canonicalID; |
| TimeZone::getCanonicalID(utzid, canonicalID, status); |
| if (U_FAILURE(status)) { |
| // Ignore unknown ID - we should not get here, but just in case. |
| // status = U_ZERO_ERROR; |
| umtx_unlock(&ZoneStringFormatMutex); |
| return; |
| } |
| |
| if (U_SUCCESS(status)) { |
| if (uhash_count(fTzidToStrings) > 0) { |
| ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); |
| if (zstrings != NULL) { |
| umtx_unlock(&ZoneStringFormatMutex); |
| return; // We already about this one |
| } |
| } |
| } |
| |
| addSingleZone(canonicalID, status); |
| |
| umtx_unlock(&ZoneStringFormatMutex); |
| } |
| |
| // Load only a single zone |
| void |
| ZoneStringFormat::addSingleZone(UnicodeString &utzid, UErrorCode &status) |
| { |
| if (U_FAILURE(status)) { |
| return; |
| } |
| |
| if (uhash_count(fTzidToStrings) > 0) { |
| ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, utzid.getTerminatedBuffer()); |
| if (zstrings != NULL) { |
| return; // We already about this one |
| } |
| } |
| |
| MessageFormat *fallbackFmt = NULL; |
| MessageFormat *regionFmt = NULL; |
| |
| fallbackFmt = getFallbackFormat(fLocale, status); |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| regionFmt = getRegionFormat(fLocale, status); |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| |
| |
| { |
| char zidkey[ZID_KEY_MAX+1]; |
| char tzid[ZID_KEY_MAX+1]; |
| utzid.extract(0, utzid.length(), zidkey, ZID_KEY_MAX, US_INV); |
| utzid.extract(0, utzid.length(), tzid, ZID_KEY_MAX, US_INV); |
| |
| const UChar *zstrarray[ZSIDX_COUNT]; |
| const UChar *mzstrarray[ZSIDX_COUNT]; |
| UnicodeString mzPartialLoc[MAX_METAZONES_PER_ZONE][4]; |
| |
| // Replace '/' with ':' |
| char *pCity = NULL; |
| char *p = zidkey; |
| while (*p) { |
| if (*p == '/') { |
| *p = ':'; |
| pCity = p + 1; |
| } |
| p++; |
| } |
| |
| if (fZoneStringsArray != NULL) { |
| fZoneItem = ures_getByKeyWithFallback(fZoneStringsArray, zidkey, fZoneItem, &status); |
| if (U_FAILURE(status)) { |
| // If failed to open the zone item, create only location string |
| ures_close(fZoneItem); |
| fZoneItem = NULL; |
| status = U_ZERO_ERROR; |
| } |
| } |
| |
| UnicodeString region; |
| getRegion(region); |
| |
| zstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(fZoneItem, gLongStandardTag); |
| zstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(fZoneItem, gShortStandardTag); |
| zstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(fZoneItem, gLongDaylightTag); |
| zstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(fZoneItem, gShortDaylightTag); |
| zstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(fZoneItem, gLongGenericTag); |
| zstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(fZoneItem, gShortGenericTag); |
| |
| // Compose location format string |
| UnicodeString location; |
| UnicodeString country; |
| UnicodeString city; |
| UnicodeString countryCode; |
| ZoneMeta::getCanonicalCountry(utzid, countryCode); |
| if (!countryCode.isEmpty()) { |
| const UChar* tmpCity = getZoneStringFromBundle(fZoneItem, gExemplarCityTag); |
| if (tmpCity != NULL) { |
| city.setTo(TRUE, tmpCity, -1); |
| } else { |
| city.setTo(UnicodeString(pCity, -1, US_INV)); |
| // Replace '_' with ' ' |
| for (int32_t i = 0; i < city.length(); i++) { |
| if (city.charAt(i) == (UChar)0x5F /*'_'*/) { |
| city.setCharAt(i, (UChar)0x20 /*' '*/); |
| } |
| } |
| } |
| getLocalizedCountry(countryCode, fLocale, country); |
| UnicodeString singleCountry; |
| ZoneMeta::getSingleCountry(utzid, singleCountry); |
| FieldPosition fpos; |
| if (singleCountry.isEmpty()) { |
| Formattable params [] = { |
| Formattable(city), |
| Formattable(country) |
| }; |
| fallbackFmt->format(params, 2, location, fpos, status); |
| } else { |
| // If the zone is only one zone in the country, do not add city |
| Formattable params [] = { |
| Formattable(country) |
| }; |
| regionFmt->format(params, 1, location, fpos, status); |
| } |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| |
| zstrarray[ZSIDX_LOCATION] = location.getTerminatedBuffer(); |
| } else { |
| if (uprv_strlen(tzid) > 4 && uprv_strncmp(tzid, "Etc/", 4) == 0) { |
| // "Etc/xxx" is not associated with a specific location, so localized |
| // GMT format is always used as generic location format. |
| zstrarray[ZSIDX_LOCATION] = NULL; |
| } else { |
| // When a new time zone ID, which is actually associated with a specific |
| // location, is added in tzdata, but the current CLDR data does not have |
| // the information yet, ICU creates a generic location string based on |
| // the ID. This implementation supports canonical time zone round trip |
| // with format pattern "VVVV". See #6602 for the details. |
| UnicodeString loc(utzid); |
| int32_t slashIdx = loc.lastIndexOf((UChar)0x2f); |
| if (slashIdx == -1) { |
| // A time zone ID without slash in the tz database is not |
| // associated with a specific location. For instances, |
| // MET, CET, EET and WET fall into this category. |
| // In this case, we still use GMT format as fallback. |
| zstrarray[ZSIDX_LOCATION] = NULL; |
| } else { |
| FieldPosition fpos; |
| Formattable params[] = { |
| Formattable(loc) |
| }; |
| regionFmt->format(params, 1, location, fpos, status); |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| zstrarray[ZSIDX_LOCATION] = location.getTerminatedBuffer(); |
| } |
| } |
| } |
| |
| UBool commonlyUsed = isCommonlyUsed(fZoneItem); |
| |
| // Resolve metazones used by this zone |
| int32_t mzPartialLocIdx = 0; |
| const UVector *metazoneMappings = ZoneMeta::getMetazoneMappings(utzid); |
| if (metazoneMappings != NULL) { |
| for (int32_t i = 0; i < metazoneMappings->size(); i++) { |
| const OlsonToMetaMappingEntry *mzmap = |
| (const OlsonToMetaMappingEntry*)metazoneMappings->elementAt(i); |
| UnicodeString mzid(mzmap->mzid); |
| const ZoneStrings *mzStrings = |
| (const ZoneStrings*)uhash_get(fMzidToStrings, mzid.getTerminatedBuffer()); |
| if (mzStrings == NULL) { |
| // If the metazone strings are not yet processed, do it now. |
| char mzidkey[ZID_KEY_MAX]; |
| uprv_strcpy(mzidkey, gMetazoneIdPrefix); |
| u_UCharsToChars(mzmap->mzid, mzidkey + MZID_PREFIX_LEN, u_strlen(mzmap->mzid) + 1); |
| fMetazoneItem = ures_getByKeyWithFallback(fZoneStringsArray, mzidkey, fMetazoneItem, &status); |
| if (U_FAILURE(status)) { |
| // No resources available for this metazone |
| // Resource bundle will be cleaned up after end of the loop. |
| status = U_ZERO_ERROR; |
| continue; |
| } |
| UBool mzCommonlyUsed = isCommonlyUsed(fMetazoneItem); |
| mzstrarray[ZSIDX_LONG_STANDARD] = getZoneStringFromBundle(fMetazoneItem, gLongStandardTag); |
| mzstrarray[ZSIDX_SHORT_STANDARD] = getZoneStringFromBundle(fMetazoneItem, gShortStandardTag); |
| mzstrarray[ZSIDX_LONG_DAYLIGHT] = getZoneStringFromBundle(fMetazoneItem, gLongDaylightTag); |
| mzstrarray[ZSIDX_SHORT_DAYLIGHT] = getZoneStringFromBundle(fMetazoneItem, gShortDaylightTag); |
| mzstrarray[ZSIDX_LONG_GENERIC] = getZoneStringFromBundle(fMetazoneItem, gLongGenericTag); |
| mzstrarray[ZSIDX_SHORT_GENERIC] = getZoneStringFromBundle(fMetazoneItem, gShortGenericTag); |
| mzstrarray[ZSIDX_LOCATION] = NULL; |
| |
| int32_t lastNonNullIdx = ZSIDX_COUNT - 1; |
| while (lastNonNullIdx >= 0) { |
| if (mzstrarray[lastNonNullIdx] != NULL) { |
| break; |
| } |
| lastNonNullIdx--; |
| } |
| UnicodeString *strings_mz = NULL; |
| ZoneStrings *tmp_mzStrings = NULL; |
| if (lastNonNullIdx >= 0) { |
| // Create UnicodeString array and put strings to the zone string trie |
| strings_mz = new UnicodeString[lastNonNullIdx + 1]; |
| |
| UnicodeString preferredIdForLocale; |
| ZoneMeta::getZoneIdByMetazone(mzid, region, preferredIdForLocale); |
| |
| for (int32_t typeidx = 0; typeidx <= lastNonNullIdx; typeidx++) { |
| if (mzstrarray[typeidx] != NULL) { |
| strings_mz[typeidx].setTo(TRUE, mzstrarray[typeidx], -1); |
| |
| // Add a metazone string to the zone string trie |
| int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)typeidx); |
| ZoneStringInfo *zsinfo = new ZoneStringInfo( |
| preferredIdForLocale, |
| strings_mz[typeidx], |
| (TimeZoneTranslationType)type, |
| fStringPool, |
| status); |
| fZoneStringsTrie.put(strings_mz[typeidx], zsinfo, fStringPool, status); |
| if (U_FAILURE(status)) { |
| delete []strings_mz; |
| goto error_cleanup; |
| } |
| } |
| } |
| // Note: ZoneStrings constructor adopts and deletes the strings_mz array. |
| tmp_mzStrings = new ZoneStrings(strings_mz, lastNonNullIdx + 1, |
| mzCommonlyUsed, NULL, 0, 0, fStringPool, status); |
| } else { |
| // Create ZoneStrings with empty contents |
| tmp_mzStrings = new ZoneStrings(NULL, 0, FALSE, NULL, 0, 0, fStringPool, status); |
| } |
| |
| UChar *umzid = const_cast<UChar *>(fStringPool.get(mzid, status)); |
| uhash_put(fMzidToStrings, umzid, tmp_mzStrings, &status); |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| |
| mzStrings = tmp_mzStrings; |
| } |
| |
| // Compose generic partial location format |
| UnicodeString lg; |
| UnicodeString sg; |
| |
| mzStrings->getString(ZSIDX_LONG_GENERIC, lg); |
| mzStrings->getString(ZSIDX_SHORT_GENERIC, sg); |
| |
| if (!lg.isEmpty() || !sg.isEmpty()) { |
| UBool addMzPartialLocationNames = TRUE; |
| for (int32_t j = 0; j < mzPartialLocIdx; j++) { |
| if (mzPartialLoc[j][0] == mzid) { |
| // already processed |
| addMzPartialLocationNames = FALSE; |
| break; |
| } |
| } |
| if (addMzPartialLocationNames) { |
| UnicodeString *locationPart = NULL; |
| // Check if the zone is the preferred zone for the territory associated with the zone |
| UnicodeString preferredID; |
| ZoneMeta::getZoneIdByMetazone(mzid, countryCode, preferredID); |
| if (utzid == preferredID) { |
| // Use country for the location |
| locationPart = &country; |
| } else { |
| // Use city for the location |
| locationPart = &city; |
| } |
| // Reset the partial location string array |
| mzPartialLoc[mzPartialLocIdx][0].setTo(mzid); |
| mzPartialLoc[mzPartialLocIdx][1].remove(); |
| mzPartialLoc[mzPartialLocIdx][2].remove(); |
| mzPartialLoc[mzPartialLocIdx][3].remove(); |
| |
| if (locationPart->length() != 0) { |
| FieldPosition fpos; |
| if (!lg.isEmpty()) { |
| Formattable params [] = { |
| Formattable(*locationPart), |
| Formattable(lg) |
| }; |
| fallbackFmt->format(params, 2, mzPartialLoc[mzPartialLocIdx][1], fpos, status); |
| } |
| if (!sg.isEmpty()) { |
| Formattable params [] = { |
| Formattable(*locationPart), |
| Formattable(sg) |
| }; |
| fallbackFmt->format(params, 2, mzPartialLoc[mzPartialLocIdx][2], fpos, status); |
| if (mzStrings->isShortFormatCommonlyUsed()) { |
| mzPartialLoc[mzPartialLocIdx][3].setTo(TRUE, gCommonlyUsedTrue, -1); |
| } |
| } |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| } |
| mzPartialLocIdx++; |
| } |
| } |
| } |
| } |
| // Collected names for a zone |
| |
| // Create UnicodeString array for localized zone strings |
| int32_t lastIdx = ZSIDX_COUNT - 1; |
| while (lastIdx >= 0) { |
| if (zstrarray[lastIdx] != NULL) { |
| break; |
| } |
| lastIdx--; |
| } |
| UnicodeString *strings = NULL; |
| int32_t stringsCount = lastIdx + 1; |
| |
| if (stringsCount > 0) { |
| strings = new UnicodeString[stringsCount]; |
| for (int32_t i = 0; i < stringsCount; i++) { |
| if (zstrarray[i] != NULL) { |
| strings[i].setTo(zstrarray[i], -1); |
| |
| // Add names to the trie |
| int32_t type = getTimeZoneTranslationType((TimeZoneTranslationTypeIndex)i); |
| ZoneStringInfo *zsinfo = new ZoneStringInfo(utzid, |
| strings[i], |
| (TimeZoneTranslationType)type, |
| fStringPool, |
| status); |
| fZoneStringsTrie.put(strings[i], zsinfo, fStringPool, status); |
| if (U_FAILURE(status)) { |
| delete zsinfo; |
| delete[] strings; |
| goto error_cleanup; |
| } |
| } |
| } |
| } |
| |
| // Create UnicodeString array for generic partial location strings |
| UnicodeString **genericPartialLocationNames = NULL; |
| int32_t genericPartialRowCount = mzPartialLocIdx; |
| int32_t genericPartialColCount = 4; |
| |
| if (genericPartialRowCount != 0) { |
| genericPartialLocationNames = |
| (UnicodeString**)uprv_malloc(genericPartialRowCount * sizeof(UnicodeString*)); |
| if (genericPartialLocationNames == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| delete[] strings; |
| goto error_cleanup; |
| } |
| for (int32_t i = 0; i < genericPartialRowCount; i++) { |
| genericPartialLocationNames[i] = new UnicodeString[genericPartialColCount]; |
| for (int32_t j = 0; j < genericPartialColCount; j++) { |
| genericPartialLocationNames[i][j].setTo(mzPartialLoc[i][j]); |
| // Add names to the trie |
| if ((j == 1 || j == 2) &&!genericPartialLocationNames[i][j].isEmpty()) { |
| ZoneStringInfo *zsinfo; |
| TimeZoneTranslationType type = (j == 1) ? GENERIC_LONG : GENERIC_SHORT; |
| zsinfo = new ZoneStringInfo(utzid, genericPartialLocationNames[i][j], type, |
| fStringPool, status); |
| fZoneStringsTrie.put(genericPartialLocationNames[i][j], zsinfo, fStringPool, status); |
| if (U_FAILURE(status)) { |
| delete[] genericPartialLocationNames[i]; |
| uprv_free(genericPartialLocationNames); |
| delete[] strings; |
| goto error_cleanup; |
| } |
| } |
| } |
| } |
| } |
| |
| // Finally, create ZoneStrings instance and put it into the tzidToStinrgs map |
| ZoneStrings *zstrings = new ZoneStrings(strings, stringsCount, commonlyUsed, |
| genericPartialLocationNames, genericPartialRowCount, |
| genericPartialColCount, fStringPool, status); |
| |
| UChar *uutzid = const_cast<UChar *>(fStringPool.get(utzid, status)); |
| uhash_put(fTzidToStrings, uutzid, zstrings, &status); |
| if (U_FAILURE(status)) { |
| delete zstrings; |
| goto error_cleanup; |
| } |
| } |
| |
| error_cleanup: |
| if (fallbackFmt != NULL) { |
| delete fallbackFmt; |
| } |
| if (regionFmt != NULL) { |
| delete regionFmt; |
| } |
| // fStringPool.freeze(); |
| } |
| |
| void |
| ZoneStringFormat::loadFull(UErrorCode &status) |
| { |
| if (U_FAILURE(status)) { |
| return; |
| } |
| if (fIsFullyLoaded) { |
| return; |
| } |
| |
| umtx_lock(&ZoneStringFormatMutex); |
| |
| if (fZoneStringsArray == NULL) { |
| fZoneStringsArray = ures_open(U_ICUDATA_ZONE, fLocale.getName(), &status); |
| fZoneStringsArray = ures_getByKeyWithFallback(fZoneStringsArray, gZoneStringsTag, fZoneStringsArray, &status); |
| if (U_FAILURE(status)) { |
| // If no locale bundles are available, zoneStrings will be null. |
| // We still want to go through the rest of zone strings initialization, |
| // because generic location format is generated from tzid for the case. |
| // The rest of code should work even zoneStrings is null. |
| status = U_ZERO_ERROR; |
| ures_close(fZoneStringsArray); |
| fZoneStringsArray = NULL; |
| } |
| } |
| |
| StringEnumeration *tzids = NULL; |
| |
| tzids = TimeZone::createEnumeration(); |
| const char *tzid; |
| while ((tzid = tzids->next(NULL, status))) { |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| // Skip non-canonical IDs |
| UnicodeString utzid(tzid, -1, US_INV); |
| UnicodeString canonicalID; |
| TimeZone::getCanonicalID(utzid, canonicalID, status); |
| if (U_FAILURE(status)) { |
| // Ignore unknown ID - we should not get here, but just in case. |
| status = U_ZERO_ERROR; |
| continue; |
| } |
| |
| if (U_SUCCESS(status)) { |
| if (uhash_count(fTzidToStrings) > 0) { |
| ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); |
| if (zstrings != NULL) { |
| continue; // We already about this one |
| } |
| } |
| } |
| |
| addSingleZone(canonicalID, status); |
| |
| if (U_FAILURE(status)) { |
| goto error_cleanup; |
| } |
| } |
| |
| fIsFullyLoaded = TRUE; |
| |
| error_cleanup: |
| if (tzids != NULL) { |
| delete tzids; |
| } |
| fStringPool.freeze(); |
| |
| umtx_unlock(&ZoneStringFormatMutex); |
| } |
| |
| |
| ZoneStringFormat::~ZoneStringFormat() { |
| uhash_close(fTzidToStrings); |
| uhash_close(fMzidToStrings); |
| ures_close(fZoneItem); |
| ures_close(fMetazoneItem); |
| ures_close(fZoneStringsArray); |
| } |
| |
| SafeZoneStringFormatPtr* |
| ZoneStringFormat::getZoneStringFormat(const Locale& locale, UErrorCode &status) { |
| umtx_lock(&gZSFCacheLock); |
| if (gZoneStringFormatCache == NULL) { |
| gZoneStringFormatCache = new ZSFCache(10 /* capacity */); |
| ucln_i18n_registerCleanup(UCLN_I18N_ZSFORMAT, zoneStringFormat_cleanup); |
| } |
| umtx_unlock(&gZSFCacheLock); |
| |
| return gZoneStringFormatCache->get(locale, status); |
| } |
| |
| |
| UnicodeString** |
| ZoneStringFormat::createZoneStringsArray(UDate date, int32_t &rowCount, int32_t &colCount, UErrorCode &status) const { |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| ZoneStringFormat *nonConstThis = const_cast<ZoneStringFormat *>(this); |
| nonConstThis->loadFull(status); |
| |
| UnicodeString **result = NULL; |
| rowCount = 0; |
| colCount = 0; |
| |
| // Collect canonical time zone IDs |
| UVector canonicalIDs(uhash_deleteUnicodeString, uhash_compareUnicodeString, status); |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| StringEnumeration *tzids = TimeZone::createEnumeration(); |
| const UChar *tzid; |
| while ((tzid = tzids->unext(NULL, status))) { |
| if (U_FAILURE(status)) { |
| delete tzids; |
| return NULL; |
| } |
| UnicodeString utzid(tzid); |
| UnicodeString canonicalID; |
| TimeZone::getCanonicalID(UnicodeString(tzid), canonicalID, status); |
| if (U_FAILURE(status)) { |
| // Ignore unknown ID - we should not get here, but just in case. |
| status = U_ZERO_ERROR; |
| continue; |
| } |
| if (utzid == canonicalID) { |
| canonicalIDs.addElement(new UnicodeString(utzid), status); |
| if (U_FAILURE(status)) { |
| delete tzids; |
| return NULL; |
| } |
| } |
| } |
| delete tzids; |
| |
| // Allocate array |
| result = (UnicodeString**)uprv_malloc(canonicalIDs.size() * sizeof(UnicodeString*)); |
| if (result == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| return NULL; |
| } |
| for (int32_t i = 0; i < canonicalIDs.size(); i++) { |
| result[i] = new UnicodeString[8]; |
| UnicodeString *id = (UnicodeString*)canonicalIDs.elementAt(i); |
| result[i][0].setTo(*id); |
| getLongStandard(*id, date, result[i][1]); |
| getShortStandard(*id, date, FALSE, result[i][2]); |
| getLongDaylight(*id, date, result[i][3]); |
| getShortDaylight(*id, date, FALSE, result[i][4]); |
| getGenericLocation(*id, result[i][5]); |
| getLongGenericNonLocation(*id, date, result[i][6]); |
| getShortGenericNonLocation(*id, date, FALSE, result[i][7]); |
| } |
| |
| rowCount = canonicalIDs.size(); |
| colCount = 8; |
| return result; |
| } |
| |
| UnicodeString& |
| ZoneStringFormat::getSpecificLongString(const Calendar &cal, UnicodeString &result, |
| UErrorCode &status) const { |
| result.remove(); |
| if (U_FAILURE(status)) { |
| return result; |
| } |
| UnicodeString tzid; |
| cal.getTimeZone().getID(tzid); |
| UDate date = cal.getTime(status); |
| if (cal.get(UCAL_DST_OFFSET, status) == 0) { |
| return getString(tzid, ZSIDX_LONG_STANDARD, date, FALSE /*not used*/, result); |
| } else { |
| return getString(tzid, ZSIDX_LONG_DAYLIGHT, date, FALSE /*not used*/, result); |
| } |
| } |
| |
| UnicodeString& |
| ZoneStringFormat::getSpecificShortString(const Calendar &cal, UBool commonlyUsedOnly, |
| UnicodeString &result, UErrorCode &status) const { |
| result.remove(); |
| if (U_FAILURE(status)) { |
| return result; |
| } |
| UnicodeString tzid; |
| cal.getTimeZone().getID(tzid); |
| UDate date = cal.getTime(status); |
| if (cal.get(UCAL_DST_OFFSET, status) == 0) { |
| return getString(tzid, ZSIDX_SHORT_STANDARD, date, commonlyUsedOnly, result); |
| } else { |
| return getString(tzid, ZSIDX_SHORT_DAYLIGHT, date, commonlyUsedOnly, result); |
| } |
| } |
| |
| UnicodeString& |
| ZoneStringFormat::getGenericLongString(const Calendar &cal, UnicodeString &result, |
| UErrorCode &status) const { |
| return getGenericString(cal, FALSE /*long*/, FALSE /* not used */, result, status); |
| } |
| |
| UnicodeString& |
| ZoneStringFormat::getGenericShortString(const Calendar &cal, UBool commonlyUsedOnly, |
| UnicodeString &result, UErrorCode &status) const { |
| return getGenericString(cal, TRUE /*short*/, commonlyUsedOnly, result, status); |
| } |
| |
| UnicodeString& |
| ZoneStringFormat::getGenericLocationString(const Calendar &cal, UnicodeString &result, |
| UErrorCode &status) const { |
| UnicodeString tzid; |
| cal.getTimeZone().getID(tzid); |
| UDate date = cal.getTime(status); |
| return getString(tzid, ZSIDX_LOCATION, date, FALSE /*not used*/, result); |
| } |
| |
| const ZoneStringInfo* |
| ZoneStringFormat::findSpecificLong(const UnicodeString &text, int32_t start, |
| int32_t &matchLength, UErrorCode &status) const { |
| return find(text, start, STANDARD_LONG | DAYLIGHT_LONG, matchLength, status); |
| } |
| |
| const ZoneStringInfo* |
| ZoneStringFormat::findSpecificShort(const UnicodeString &text, int32_t start, |
| int32_t &matchLength, UErrorCode &status) const { |
| return find(text, start, STANDARD_SHORT | DAYLIGHT_SHORT, matchLength, status); |
| } |
| |
| const ZoneStringInfo* |
| ZoneStringFormat::findGenericLong(const UnicodeString &text, int32_t start, |
| int32_t &matchLength, UErrorCode &status) const { |
| return find(text, start, GENERIC_LONG | STANDARD_LONG | LOCATION, matchLength, status); |
| } |
| |
| const ZoneStringInfo* |
| ZoneStringFormat::findGenericShort(const UnicodeString &text, int32_t start, |
| int32_t &matchLength, UErrorCode &status) const { |
| return find(text, start, GENERIC_SHORT | STANDARD_SHORT | LOCATION, matchLength, status); |
| } |
| |
| const ZoneStringInfo* |
| ZoneStringFormat::findGenericLocation(const UnicodeString &text, int32_t start, |
| int32_t &matchLength, UErrorCode &status) const { |
| return find(text, start, LOCATION, matchLength, status); |
| } |
| |
| UnicodeString& |
| ZoneStringFormat::getString(const UnicodeString &tzid, TimeZoneTranslationTypeIndex typeIdx, UDate date, |
| UBool commonlyUsedOnly, UnicodeString& result) const { |
| UErrorCode status = U_ZERO_ERROR; |
| result.remove(); |
| if (!fIsFullyLoaded) { |
| // Lazy loading |
| ZoneStringFormat *nonConstThis = const_cast<ZoneStringFormat *>(this); |
| nonConstThis->loadZone(tzid, status); |
| } |
| |
| // ICU's own array does not have entries for aliases |
| UnicodeString canonicalID; |
| TimeZone::getCanonicalID(tzid, canonicalID, status); |
| if (U_FAILURE(status)) { |
| // Unknown ID, but users might have their own data. |
| canonicalID.setTo(tzid); |
| } |
| |
| if (uhash_count(fTzidToStrings) > 0) { |
| ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); |
| if (zstrings != NULL) { |
| switch (typeIdx) { |
| case ZSIDX_LONG_STANDARD: |
| case ZSIDX_LONG_DAYLIGHT: |
| case ZSIDX_LONG_GENERIC: |
| case ZSIDX_LOCATION: |
| zstrings->getString(typeIdx, result); |
| break; |
| case ZSIDX_SHORT_STANDARD: |
| case ZSIDX_SHORT_DAYLIGHT: |
| case ZSIDX_COUNT: //added to avoid warning |
| case ZSIDX_SHORT_GENERIC: |
| if (!commonlyUsedOnly || zstrings->isShortFormatCommonlyUsed()) { |
| zstrings->getString(typeIdx, result); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| if (result.isEmpty() && uhash_count(fMzidToStrings) > 0 && typeIdx != ZSIDX_LOCATION) { |
| // Try metazone |
| UnicodeString mzid; |
| ZoneMeta::getMetazoneID(canonicalID, date, mzid); |
| if (!mzid.isEmpty()) { |
| ZoneStrings *mzstrings = (ZoneStrings*)uhash_get(fMzidToStrings, mzid.getTerminatedBuffer()); |
| if (mzstrings != NULL) { |
| switch (typeIdx) { |
| case ZSIDX_LONG_STANDARD: |
| case ZSIDX_LONG_DAYLIGHT: |
| case ZSIDX_LONG_GENERIC: |
| case ZSIDX_LOCATION: |
| mzstrings->getString(typeIdx, result); |
| break; |
| case ZSIDX_SHORT_STANDARD: |
| case ZSIDX_SHORT_DAYLIGHT: |
| case ZSIDX_COUNT: //added to avoid warning |
| case ZSIDX_SHORT_GENERIC: |
| if (!commonlyUsedOnly || mzstrings->isShortFormatCommonlyUsed()) { |
| mzstrings->getString(typeIdx, result); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| UnicodeString& |
| ZoneStringFormat::getGenericString(const Calendar &cal, UBool isShort, UBool commonlyUsedOnly, |
| UnicodeString &result, UErrorCode &status) const { |
| result.remove(); |
| UDate time = cal.getTime(status); |
| if (U_FAILURE(status)) { |
| return result; |
| } |
| const TimeZone &tz = cal.getTimeZone(); |
| UnicodeString tzid; |
| tz.getID(tzid); |
| |
| if (!fIsFullyLoaded) { |
| // Lazy loading |
| ZoneStringFormat *nonConstThis = const_cast<ZoneStringFormat *>(this); |
| nonConstThis->loadZone(tzid, status); |
| } |
| |
| // ICU's own array does not have entries for aliases |
| UnicodeString canonicalID; |
| TimeZone::getCanonicalID(tzid, canonicalID, status); |
| if (U_FAILURE(status)) { |
| // Unknown ID, but users might have their own data. |
| status = U_ZERO_ERROR; |
| canonicalID.setTo(tzid); |
| } |
| |
| ZoneStrings *zstrings = NULL; |
| if (uhash_count(fTzidToStrings) > 0) { |
| zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); |
| if (zstrings != NULL) { |
| if (isShort) { |
| if (!commonlyUsedOnly || zstrings->isShortFormatCommonlyUsed()) { |
| zstrings->getString(ZSIDX_SHORT_GENERIC, result); |
| } |
| } else { |
| zstrings->getString(ZSIDX_LONG_GENERIC, result); |
| } |
| } |
| } |
| if (result.isEmpty() && uhash_count(fMzidToStrings) > 0) { |
| // try metazone |
| int32_t raw, sav; |
| UnicodeString mzid; |
| ZoneMeta::getMetazoneID(canonicalID, time, mzid); |
| if (!mzid.isEmpty()) { |
| UBool useStandard = FALSE; |
| sav = cal.get(UCAL_DST_OFFSET, status); |
| if (U_FAILURE(status)) { |
| return result; |
| } |
| if (sav == 0) { |
| useStandard = TRUE; |
| // Check if the zone actually uses daylight saving time around the time |
| TimeZone *tmptz = tz.clone(); |
| BasicTimeZone *btz = NULL; |
| if (dynamic_cast<OlsonTimeZone *>(tmptz) != NULL |
| || dynamic_cast<SimpleTimeZone *>(tmptz) != NULL |
| || dynamic_cast<RuleBasedTimeZone *>(tmptz) != NULL |
| || dynamic_cast<VTimeZone *>(tmptz) != NULL) { |
| btz = (BasicTimeZone*)tmptz; |
| } |
| |
| if (btz != NULL) { |
| TimeZoneTransition before; |
| UBool beforTrs = btz->getPreviousTransition(time, TRUE, before); |
| if (beforTrs |
| && (time - before.getTime() < kDstCheckRange) |
| && before.getFrom()->getDSTSavings() != 0) { |
| useStandard = FALSE; |
| } else { |
| TimeZoneTransition after; |
| UBool afterTrs = btz->getNextTransition(time, FALSE, after); |
| if (afterTrs |
| && (after.getTime() - time < kDstCheckRange) |
| && after.getTo()->getDSTSavings() != 0) { |
| useStandard = FALSE; |
| } |
| } |
| } else { |
| // If not BasicTimeZone... only if the instance is not an ICU's implementation. |
| // We may get a wrong answer in edge case, but it should practically work OK. |
| tmptz->getOffset(time - kDstCheckRange, FALSE, raw, sav, status); |
| if (sav != 0) { |
| useStandard = FALSE; |
| } else { |
| tmptz->getOffset(time + kDstCheckRange, FALSE, raw, sav, status); |
| if (sav != 0){ |
| useStandard = FALSE; |
| } |
| } |
| if (U_FAILURE(status)) { |
| delete tmptz; |
| result.remove(); |
| return result; |
| } |
| } |
| delete tmptz; |
| } |
| if (useStandard) { |
| getString(canonicalID, (isShort ? ZSIDX_SHORT_STANDARD : ZSIDX_LONG_STANDARD), |
| time, commonlyUsedOnly, result); |
| |
| // Note: |
| // In CLDR 1.5.1, a same localization is used for both generic and standard |
| // for some metazones in some locales. This is actually data bugs and should |
| // be resolved in later versions of CLDR. For now, we check if the standard |
| // name is different from its generic name below. |
| if (!result.isEmpty()) { |
| UnicodeString genericNonLocation; |
| getString(canonicalID, (isShort ? ZSIDX_SHORT_GENERIC : ZSIDX_LONG_GENERIC), |
| time, commonlyUsedOnly, genericNonLocation); |
| if (!genericNonLocation.isEmpty() && result == genericNonLocation) { |
| result.remove(); |
| } |
| } |
| } |
| if (result.isEmpty()) { |
| ZoneStrings *mzstrings = (ZoneStrings*)uhash_get(fMzidToStrings, mzid.getTerminatedBuffer()); |
| if (mzstrings != NULL) { |
| if (isShort) { |
| if (!commonlyUsedOnly || mzstrings->isShortFormatCommonlyUsed()) { |
| mzstrings->getString(ZSIDX_SHORT_GENERIC, result); |
| } |
| } else { |
| mzstrings->getString(ZSIDX_LONG_GENERIC, result); |
| } |
| } |
| if (!result.isEmpty()) { |
| // Check if the offsets at the given time matches the preferred zone's offsets |
| UnicodeString preferredId; |
| UnicodeString region; |
| ZoneMeta::getZoneIdByMetazone(mzid, getRegion(region), preferredId); |
| if (canonicalID != preferredId) { |
| // Check if the offsets at the given time are identical with the preferred zone |
| raw = cal.get(UCAL_ZONE_OFFSET, status); |
| if (U_FAILURE(status)) { |
| result.remove(); |
| return result; |
| } |
| TimeZone *preferredZone = TimeZone::createTimeZone(preferredId); |
| int32_t prfRaw, prfSav; |
| // Check offset in preferred time zone with wall time. |
| // With getOffset(time, false, preferredOffsets), |
| // you may get incorrect results because of time overlap at DST->STD |
| // transition. |
| preferredZone->getOffset(time + raw + sav, TRUE, prfRaw, prfSav, status); |
| delete preferredZone; |
| |
| if (U_FAILURE(status)) { |
| result.remove(); |
| return result; |
| } |
| if ((raw != prfRaw || sav != prfSav) && zstrings != NULL) { |
| // Use generic partial location string as fallback |
| zstrings->getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly, result); |
| } |
| } |
| } |
| } |
| } |
| } |
| if (result.isEmpty()) { |
| // Use location format as the final fallback |
| getString(canonicalID, ZSIDX_LOCATION, time, FALSE /*not used*/, result); |
| } |
| |
| return result; |
| } |
| |
| UnicodeString& |
| ZoneStringFormat::getGenericPartialLocationString(const UnicodeString &tzid, UBool isShort, |
| UDate date, UBool commonlyUsedOnly, UnicodeString &result) const { |
| UErrorCode status = U_ZERO_ERROR; |
| result.remove(); |
| if (!fIsFullyLoaded) { |
| // Lazy loading |
| ZoneStringFormat *nonConstThis = const_cast<ZoneStringFormat *>(this); |
| nonConstThis->loadZone(tzid, status); |
| } |
| |
| if (uhash_count(fTzidToStrings) <= 0) { |
| return result; |
| } |
| |
| UnicodeString canonicalID; |
| TimeZone::getCanonicalID(tzid, canonicalID, status); |
| if (U_FAILURE(status)) { |
| // Unknown ID, so no corresponding meta data. |
| return result; |
| } |
| |
| UnicodeString mzid; |
| ZoneMeta::getMetazoneID(canonicalID, date, mzid); |
| |
| if (!mzid.isEmpty()) { |
| ZoneStrings *zstrings = (ZoneStrings*)uhash_get(fTzidToStrings, canonicalID.getTerminatedBuffer()); |
| if (zstrings != NULL) { |
| zstrings->getGenericPartialLocationString(mzid, isShort, commonlyUsedOnly, result); |
| } |
| } |
| return result; |
| } |
| |
| // This method does lazy zone string loading |
| const ZoneStringInfo* |
| ZoneStringFormat::find(const UnicodeString &text, int32_t start, int32_t types, |
| int32_t &matchLength, UErrorCode &status) const { |
| |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| |
| const ZoneStringInfo * result = subFind(text, start, types, matchLength, status); |
| if (fIsFullyLoaded) { |
| return result; |
| } |
| // When zone string data is partially loaded, |
| // this method return the result only when |
| // the input text is fully consumed. |
| if (result != NULL) { |
| UnicodeString tmpString; |
| matchLength = (result->getString(tmpString)).length(); |
| if (text.length() - start == matchLength) { |
| return result; |
| } |
| } |
| |
| // Now load all zone strings |
| ZoneStringFormat *nonConstThis = const_cast<ZoneStringFormat *>(this); |
| nonConstThis->loadFull(status); |
| |
| return subFind(text, start, types, matchLength, status); |
| } |
| |
| |
| /* |
| * Find a prefix matching time zone for the given zone string types. |
| * @param text The text contains a time zone string |
| * @param start The start index within the text |
| * @param types The bit mask representing a set of requested types |
| * @return If any zone string matched for the requested types, returns a |
| * ZoneStringInfo for the longest match. If no matches are found for |
| * the requested types, returns a ZoneStringInfo for the longest match |
| * for any other types. If nothing matches at all, returns null. |
| */ |
| const ZoneStringInfo* |
| ZoneStringFormat::subFind(const UnicodeString &text, int32_t start, int32_t types, |
| int32_t &matchLength, UErrorCode &status) const { |
| matchLength = 0; |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| if (fZoneStringsTrie.isEmpty()) { |
| return NULL; |
| } |
| |
| const ZoneStringInfo *result = NULL; |
| const ZoneStringInfo *fallback = NULL; |
| int32_t fallbackMatchLen = 0; |
| |
| ZoneStringSearchResultHandler handler(status); |
| fZoneStringsTrie.search(text, start, (TextTrieMapSearchResultHandler*)&handler, status); |
| if (U_SUCCESS(status)) { |
| int32_t numMatches = handler.countMatches(); |
| for (int32_t i = 0; i < numMatches; i++) { |
| int32_t tmpMatchLen = 0; // init. output only param to silence gcc |
| const ZoneStringInfo *tmp = handler.getMatch(i, tmpMatchLen); |
| if ((types & tmp->fType) != 0) { |
| if (result == NULL || matchLength < tmpMatchLen) { |
| result = tmp; |
| matchLength = tmpMatchLen; |
| } else if (matchLength == tmpMatchLen) { |
| // Tie breaker - there are some examples that a |
| // long standard name is identical with a location |
| // name - for example, "Uruguay Time". In this case, |
| // we interpret it as generic, not specific. |
| if (tmp->isGeneric() && !result->isGeneric()) { |
| result = tmp; |
| } |
| } |
| } else if (result == NULL) { |
| if (fallback == NULL || fallbackMatchLen < tmpMatchLen) { |
| fallback = tmp; |
| fallbackMatchLen = tmpMatchLen; |
| } else if (fallbackMatchLen == tmpMatchLen) { |
| if (tmp->isGeneric() && !fallback->isGeneric()) { |
| fallback = tmp; |
| } |
| } |
| } |
| } |
| if (result == NULL && fallback != NULL) { |
| result = fallback; |
| matchLength = fallbackMatchLen; |
| } |
| } |
| return result; |
| } |
| |
| |
| UnicodeString& |
| ZoneStringFormat::getRegion(UnicodeString ®ion) const { |
| const char* country = fLocale.getCountry(); |
| // TODO: Utilize addLikelySubtag in Locale to resolve default region |
| // when the implementation is ready. |
| region.setTo(UnicodeString(country, -1, US_INV)); |
| return region; |
| } |
| |
| MessageFormat* |
| ZoneStringFormat::getFallbackFormat(const Locale &locale, UErrorCode &status) { |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| UnicodeString pattern(TRUE, gDefFallbackPattern, -1); |
| UResourceBundle *zoneStringsArray = ures_open(U_ICUDATA_ZONE, locale.getName(), &status); |
| zoneStringsArray = ures_getByKeyWithFallback(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status); |
| int32_t len; |
| const UChar *flbkfmt = ures_getStringByKeyWithFallback(zoneStringsArray, gFallbackFormatTag, &len, &status); |
| if (U_SUCCESS(status)) { |
| pattern.setTo(flbkfmt); |
| } else { |
| status = U_ZERO_ERROR; |
| } |
| ures_close(zoneStringsArray); |
| |
| MessageFormat *fallbackFmt = new MessageFormat(pattern, status); |
| return fallbackFmt; |
| } |
| |
| MessageFormat* |
| ZoneStringFormat::getRegionFormat(const Locale& locale, UErrorCode &status) { |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| UnicodeString pattern(TRUE, gDefRegionPattern, -1); |
| UResourceBundle *zoneStringsArray = ures_open(U_ICUDATA_ZONE, locale.getName(), &status); |
| zoneStringsArray = ures_getByKeyWithFallback(zoneStringsArray, gZoneStringsTag, zoneStringsArray, &status); |
| int32_t len; |
| const UChar *regionfmt = ures_getStringByKeyWithFallback(zoneStringsArray, gRegionFormatTag, &len, &status); |
| if (U_SUCCESS(status)) { |
| pattern.setTo(regionfmt); |
| } else { |
| status = U_ZERO_ERROR; |
| } |
| ures_close(zoneStringsArray); |
| |
| MessageFormat *regionFmt = new MessageFormat(pattern, status); |
| return regionFmt; |
| } |
| |
| const UChar* |
| ZoneStringFormat::getZoneStringFromBundle(const UResourceBundle *zoneitem, const char *key) { |
| const UChar *str = NULL; |
| if (zoneitem != NULL) { |
| UErrorCode status = U_ZERO_ERROR; |
| int32_t len; |
| str = ures_getStringByKeyWithFallback(zoneitem, key, &len, &status); |
| str = fStringPool.adopt(str, status); |
| if (U_FAILURE(status)) { |
| str = NULL; |
| } |
| } |
| return str; |
| } |
| |
| UBool |
| ZoneStringFormat::isCommonlyUsed(const UResourceBundle *zoneitem) { |
| if (zoneitem == NULL) { |
| return TRUE; |
| } |
| |
| UBool commonlyUsed = FALSE; |
| UErrorCode status = U_ZERO_ERROR; |
| UResourceBundle *cuRes = ures_getByKey(zoneitem, gCommonlyUsedTag, NULL, &status); |
| int32_t cuValue = ures_getInt(cuRes, &status); |
| if (U_SUCCESS(status)) { |
| if (cuValue == 1) { |
| commonlyUsed = TRUE; |
| } |
| } |
| ures_close(cuRes); |
| return commonlyUsed; |
| } |
| |
| UnicodeString& |
| ZoneStringFormat::getLocalizedCountry(const UnicodeString &countryCode, const Locale &locale, UnicodeString &displayCountry) { |
| // We do not want to use display country names only from the target language bundle |
| // Note: we should do this in better way. |
| displayCountry.remove(); |
| int32_t ccLen = countryCode.length(); |
| if (ccLen > 0 && ccLen < ULOC_COUNTRY_CAPACITY) { |
| UErrorCode status = U_ZERO_ERROR; |
| UResourceBundle *localeBundle = ures_open(NULL, locale.getName(), &status); |
| if (U_SUCCESS(status)) { |
| const char *bundleLocStr = ures_getLocale(localeBundle, &status); |
| if (U_SUCCESS(status) && uprv_strlen(bundleLocStr) > 0) { |
| Locale bundleLoc(bundleLocStr); |
| if (uprv_strcmp(bundleLocStr, "root") != 0 && |
| uprv_strcmp(bundleLoc.getLanguage(), locale.getLanguage()) == 0) { |
| // Create a fake locale strings |
| char tmpLocStr[ULOC_COUNTRY_CAPACITY + 3]; |
| uprv_strcpy(tmpLocStr, "xx_"); |
| u_UCharsToChars(countryCode.getBuffer(), &tmpLocStr[3], ccLen); |
| tmpLocStr[3 + ccLen] = 0; |
| |
| Locale tmpLoc(tmpLocStr); |
| tmpLoc.getDisplayCountry(locale, displayCountry); |
| } |
| } |
| } |
| ures_close(localeBundle); |
| } |
| if (displayCountry.isEmpty()) { |
| // Use the country code as the fallback |
| displayCountry.setTo(countryCode); |
| } |
| return displayCountry; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| /* |
| * ZoneStrings constructor adopts (and promptly copies and deletes) |
| * the input UnicodeString arrays. |
| */ |
| ZoneStrings::ZoneStrings(UnicodeString *strings, |
| int32_t stringsCount, |
| UBool commonlyUsed, |
| UnicodeString **genericPartialLocationStrings, |
| int32_t genericRowCount, |
| int32_t genericColCount, |
| ZSFStringPool &sp, |
| UErrorCode &status) |
| : fStrings(NULL), |
| fStringsCount(stringsCount), |
| fIsCommonlyUsed(commonlyUsed), |
| fGenericPartialLocationStrings(NULL), |
| fGenericPartialLocationRowCount(genericRowCount), |
| fGenericPartialLocationColCount(genericColCount) |
| { |
| if (U_FAILURE(status)) { |
| return; |
| } |
| int32_t i, j; |
| if (strings != NULL) { |
| fStrings = (const UChar **)uprv_malloc(sizeof(const UChar **) * stringsCount); |
| if (fStrings == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| return; |
| } |
| for (i=0; i<fStringsCount; i++) { |
| fStrings[i] = sp.get(strings[i], status); |
| } |
| delete[] strings; |
| } |
| if (genericPartialLocationStrings != NULL) { |
| fGenericPartialLocationStrings = |
| (const UChar ***)uprv_malloc(sizeof(const UChar ***) * genericRowCount); |
| if (fGenericPartialLocationStrings == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| return; |
| } |
| for (i=0; i < fGenericPartialLocationRowCount; i++) { |
| fGenericPartialLocationStrings[i] = |
| (const UChar **)uprv_malloc(sizeof(const UChar **) * genericColCount); |
| if (fGenericPartialLocationStrings[i] == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| continue; // Continue so that fGenericPartialLocationStrings will not contain uninitialized junk, |
| } // which would crash the destructor. |
| for (j=0; j<genericColCount; j++) { |
| fGenericPartialLocationStrings[i][j] = |
| sp.get(genericPartialLocationStrings[i][j], status); |
| } |
| delete[] genericPartialLocationStrings[i]; |
| } |
| uprv_free(genericPartialLocationStrings); |
| } |
| } |
| |
| ZoneStrings::~ZoneStrings() { |
| uprv_free(fStrings); |
| if (fGenericPartialLocationStrings != NULL) { |
| for (int32_t i = 0; i < fGenericPartialLocationRowCount; i++) { |
| uprv_free(fGenericPartialLocationStrings[i]); |
| } |
| uprv_free(fGenericPartialLocationStrings); |
| } |
| } |
| |
| |
| UnicodeString& |
| ZoneStrings::getString(int32_t typeIdx, UnicodeString &result) const { |
| if (typeIdx >= 0 && typeIdx < fStringsCount) { |
| result.setTo(fStrings[typeIdx], -1); |
| } else { |
| result.remove(); |
| } |
| return result; |
| } |
| |
| UnicodeString& |
| ZoneStrings::getGenericPartialLocationString(const UnicodeString &mzid, UBool isShort, |
| UBool commonlyUsedOnly, UnicodeString &result) const { |
| UBool isSet = FALSE; |
| if (fGenericPartialLocationColCount >= 2) { |
| for (int32_t i = 0; i < fGenericPartialLocationRowCount; i++) { |
| if (mzid.compare(fGenericPartialLocationStrings[i][0], -1) == 0) { |
| if (isShort) { |
| if (fGenericPartialLocationColCount >= 3) { |
| if (!commonlyUsedOnly || |
| fGenericPartialLocationColCount == 3 || |
| fGenericPartialLocationStrings[i][3][0] != 0) { |
| result.setTo(fGenericPartialLocationStrings[i][2], -1); |
| isSet = TRUE; |
| } |
| } |
| } else { |
| result.setTo(fGenericPartialLocationStrings[i][1], -1); |
| isSet = TRUE; |
| } |
| break; |
| } |
| } |
| } |
| if (!isSet) { |
| result.remove(); |
| } |
| return result; |
| } |
| |
| // -------------------------------------------------------------- |
| SafeZoneStringFormatPtr::SafeZoneStringFormatPtr(ZSFCacheEntry *cacheEntry) |
| : fCacheEntry(cacheEntry) { |
| } |
| |
| SafeZoneStringFormatPtr::~SafeZoneStringFormatPtr() { |
| fCacheEntry->delRef(); |
| } |
| |
| const ZoneStringFormat* |
| SafeZoneStringFormatPtr::get() const { |
| return fCacheEntry->getZoneStringFormat(); |
| } |
| |
| ZSFCacheEntry::ZSFCacheEntry(const Locale &locale, ZoneStringFormat *zsf, ZSFCacheEntry *next) |
| : fLocale(locale), fZoneStringFormat(zsf), |
| fNext(next), fRefCount(1) |
| { |
| } |
| |
| ZSFCacheEntry::~ZSFCacheEntry () { |
| delete fZoneStringFormat; |
| } |
| |
| const ZoneStringFormat* |
| ZSFCacheEntry::getZoneStringFormat(void) { |
| return (const ZoneStringFormat*)fZoneStringFormat; |
| } |
| |
| void |
| ZSFCacheEntry::delRef(void) { |
| umtx_lock(&gZSFCacheLock); |
| --fRefCount; |
| umtx_unlock(&gZSFCacheLock); |
| } |
| |
| ZSFCache::ZSFCache(int32_t capacity) |
| : fCapacity(capacity), fFirst(NULL) { |
| } |
| |
| ZSFCache::~ZSFCache() { |
| ZSFCacheEntry *entry = fFirst; |
| while (entry) { |
| ZSFCacheEntry *next = entry->fNext; |
| delete entry; |
| entry = next; |
| } |
| } |
| |
| SafeZoneStringFormatPtr* |
| ZSFCache::get(const Locale &locale, UErrorCode &status) { |
| SafeZoneStringFormatPtr *result = NULL; |
| |
| // Search the cache entry list |
| ZSFCacheEntry *entry = NULL; |
| ZSFCacheEntry *next, *prev; |
| |
| umtx_lock(&gZSFCacheLock); |
| entry = fFirst; |
| prev = NULL; |
| while (entry) { |
| next = entry->fNext; |
| if (entry->fLocale == locale) { |
| // Add reference count |
| entry->fRefCount++; |
| |
| // move the entry to the top |
| if (entry != fFirst) { |
| prev->fNext = next; |
| entry->fNext = fFirst; |
| fFirst = entry; |
| } |
| break; |
| } |
| prev = entry; |
| entry = next; |
| } |
| umtx_unlock(&gZSFCacheLock); |
| |
| // Create a new ZoneStringFormat |
| if (entry == NULL) { |
| ZoneStringFormat *zsf = new ZoneStringFormat(locale, status); |
| if (U_FAILURE(status)) { |
| delete zsf; |
| return NULL; |
| } |
| if (zsf == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| return NULL; |
| } |
| |
| // Now add the new entry |
| umtx_lock(&gZSFCacheLock); |
| // Make sure no other threads already created the one for the same locale |
| entry = fFirst; |
| prev = NULL; |
| while (entry) { |
| next = entry->fNext; |
| if (entry->fLocale == locale) { |
| // Add reference count |
| entry->fRefCount++; |
| |
| // move the entry to the top |
| if (entry != fFirst) { |
| prev->fNext = next; |
| entry->fNext = fFirst; |
| fFirst = entry; |
| } |
| break; |
| } |
| prev = entry; |
| entry = next; |
| } |
| if (entry == NULL) { |
| // Add the new one to the top |
| next = fFirst; |
| entry = new ZSFCacheEntry(locale, zsf, next); |
| fFirst = entry; |
| } else { |
| delete zsf; |
| } |
| umtx_unlock(&gZSFCacheLock); |
| } |
| |
| result = new SafeZoneStringFormatPtr(entry); |
| |
| // Now, delete unused cache entries beyond the capacity |
| umtx_lock(&gZSFCacheLock); |
| entry = fFirst; |
| prev = NULL; |
| int32_t idx = 1; |
| while (entry) { |
| next = entry->fNext; |
| if (idx >= fCapacity && entry->fRefCount == 0) { |
| if (entry == fFirst) { |
| fFirst = next; |
| } else { |
| prev->fNext = next; |
| } |
| delete entry; |
| } else { |
| prev = entry; |
| } |
| entry = next; |
| idx++; |
| } |
| umtx_unlock(&gZSFCacheLock); |
| |
| return result; |
| } |
| |
| |
| /* |
| * Zone String Formatter String Pool Implementation |
| * |
| * String pool for (UChar *) strings. Avoids having repeated copies of the same string. |
| */ |
| |
| static const int32_t POOL_CHUNK_SIZE = 2000; |
| struct ZSFStringPoolChunk: public UMemory { |
| ZSFStringPoolChunk *fNext; // Ptr to next pool chunk |
| int32_t fLimit; // Index to start of unused area at end of fStrings |
| UChar fStrings[POOL_CHUNK_SIZE]; // Strings array |
| ZSFStringPoolChunk(); |
| }; |
| |
| ZSFStringPoolChunk::ZSFStringPoolChunk() { |
| fNext = NULL; |
| fLimit = 0; |
| } |
| |
| ZSFStringPool::ZSFStringPool(UErrorCode &status) { |
| fChunks = NULL; |
| fHash = NULL; |
| if (U_FAILURE(status)) { |
| return; |
| } |
| fChunks = new ZSFStringPoolChunk; |
| if (fChunks == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| return; |
| } |
| |
| fHash = uhash_open(uhash_hashUChars /* keyHash */, |
| uhash_compareUChars /* keyComp */, |
| uhash_compareUChars /* valueComp */, |
| &status); |
| if (U_FAILURE(status)) { |
| return; |
| } |
| } |
| |
| |
| ZSFStringPool::~ZSFStringPool() { |
| if (fHash != NULL) { |
| uhash_close(fHash); |
| fHash = NULL; |
| } |
| |
| while (fChunks != NULL) { |
| ZSFStringPoolChunk *nextChunk = fChunks->fNext; |
| delete fChunks; |
| fChunks = nextChunk; |
| } |
| } |
| |
| static const UChar EmptyString = 0; |
| |
| const UChar *ZSFStringPool::get(const UChar *s, UErrorCode &status) { |
| const UChar *pooledString; |
| if (U_FAILURE(status)) { |
| return &EmptyString; |
| } |
| |
| pooledString = static_cast<UChar *>(uhash_get(fHash, s)); |
| if (pooledString != NULL) { |
| return pooledString; |
| } |
| |
| int32_t length = u_strlen(s); |
| int32_t remainingLength = POOL_CHUNK_SIZE - fChunks->fLimit; |
| if (remainingLength <= length) { |
| U_ASSERT(length < POOL_CHUNK_SIZE); |
| if (length >= POOL_CHUNK_SIZE) { |
| status = U_INTERNAL_PROGRAM_ERROR; |
| return &EmptyString; |
| } |
| ZSFStringPoolChunk *oldChunk = fChunks; |
| fChunks = new ZSFStringPoolChunk; |
| if (fChunks == NULL) { |
| status = U_MEMORY_ALLOCATION_ERROR; |
| return &EmptyString; |
| } |
| fChunks->fNext = oldChunk; |
| } |
| |
| UChar *destString = &fChunks->fStrings[fChunks->fLimit]; |
| u_strcpy(destString, s); |
| fChunks->fLimit += (length + 1); |
| uhash_put(fHash, destString, destString, &status); |
| return destString; |
| } |
| |
| |
| // |
| // ZSFStringPool::adopt() Put a string into the hash, but do not copy the string data |
| // into the pool's storage. Used for strings from resource bundles, |
| // which will perisist for the life of the zone string formatter, and |
| // therefore can be used directly without copying. |
| const UChar *ZSFStringPool::adopt(const UChar * s, UErrorCode &status) { |
| const UChar *pooledString; |
| if (U_FAILURE(status)) { |
| return &EmptyString; |
| } |
| if (s != NULL) { |
| pooledString = static_cast<UChar *>(uhash_get(fHash, s)); |
| if (pooledString == NULL) { |
| UChar *ncs = const_cast<UChar *>(s); |
| uhash_put(fHash, ncs, ncs, &status); |
| } |
| } |
| return s; |
| } |
| |
| |
| const UChar *ZSFStringPool::get(const UnicodeString &s, UErrorCode &status) { |
| UnicodeString &nonConstStr = const_cast<UnicodeString &>(s); |
| return this->get(nonConstStr.getTerminatedBuffer(), status); |
| } |
| |
| /* |
| * freeze(). Close the hash table that maps to the pooled strings. |
| * After freezing, the pool can not be searched or added to, |
| * but all existing references to pooled strings remain valid. |
| * |
| * The main purpose is to recover the storage used for the hash. |
| */ |
| void ZSFStringPool::freeze() { |
| uhash_close(fHash); |
| fHash = NULL; |
| } |
| |
| U_NAMESPACE_END |
| |
| #endif /* #if !UCONFIG_NO_FORMATTING */ |