| Index: icu46/source/i18n/zstrfmt.cpp
|
| ===================================================================
|
| --- icu46/source/i18n/zstrfmt.cpp (revision 0)
|
| +++ icu46/source/i18n/zstrfmt.cpp (revision 0)
|
| @@ -0,0 +1,2228 @@
|
| +/*
|
| +*******************************************************************************
|
| +* 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 */
|
|
|
| Property changes on: icu46/source/i18n/zstrfmt.cpp
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|