+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
/*
******************************************************************************
* Copyright (C) 1997-2016, International Business Machines Corporation and
******************************************************************************
*/
+#include "unicode/ures.h"
#include "unicode/ustring.h"
#include "unicode/ucnv.h"
#include "charstr.h"
#include "ucln_cmn.h"
#include "cmemory.h"
#include "cstring.h"
+#include "mutex.h"
#include "uhash.h"
#include "unicode/uenum.h"
#include "uenumimp.h"
#include "umutex.h"
#include "putilimp.h"
#include "uassert.h"
+#include "uresdata.h"
+
+#include <stdio.h> /* for sprintf */
using namespace icu;
completely removed.
*/
static UHashtable *cache = NULL;
-static icu::UInitOnce gCacheInitOnce;
+static icu::UInitOnce gCacheInitOnce = U_INITONCE_INITIALIZER;
-static UMutex resbMutex = U_MUTEX_INITIALIZER;
+static UMutex resbMutex;
/* INTERNAL: hashes an entry */
static int32_t U_CALLCONV hashEntry(const UHashTok parm) {
/**
- * Internal function, gets parts of locale name according
+ * Internal function, gets parts of locale name according
* to the position of '_' character
*/
static UBool chopLocale(char *name) {
return FALSE;
}
+static icu::UInitOnce parentLocaleInitOnce = U_INITONCE_INITIALIZER;
+static UHashtable* parentLocaleTable = NULL;
+static char* parentLocaleStrings = NULL;
+static const int32_t parentLocaleStringsCapacity = 2350; // actual size is 2292-- leave a little extra space in case the table changes
+
+static void doInitParentLocaleTable() {
+ UErrorCode err = U_ZERO_ERROR;
+ parentLocaleTable = uhash_open(uhash_hashIChars, uhash_compareIChars, uhash_compareIChars, &err);
+
+ UResourceBundle* curBundle = ures_openDirect(NULL, "supplementalData", &err);
+ curBundle = ures_getByKey(curBundle, "parentLocales", curBundle, &err);
+ parentLocaleStrings = (char*)uprv_malloc(parentLocaleStringsCapacity);
+ const char* parentLocaleStringsEnd = parentLocaleStrings + parentLocaleStringsCapacity;
+ if (U_FAILURE(err) || parentLocaleStrings == NULL) {
+ U_ASSERT(FALSE); // we should never end up in here; make sure
+ uhash_close(parentLocaleTable);
+ parentLocaleTable = NULL;
+ ures_close(curBundle);
+ uprv_free(parentLocaleStrings);
+ return;
+ }
+
+ char* nextString = parentLocaleStrings;
+ UResourceBundle* localesForParent = NULL; // localesForParent in the next line is an in/out parameter
+ localesForParent = ures_getNextResource(curBundle, localesForParent, &err);
+ while (U_SUCCESS(err) && localesForParent != NULL) {
+ const char* parentID = ures_getKey(localesForParent);
+ if (nextString + uprv_strlen(parentID) + 1 >= parentLocaleStringsEnd) {
+ // if we can't build this whole table, we're in trouble
+ U_ASSERT(FALSE);
+ break;
+ }
+ uprv_strcpy(nextString, parentID);
+ nextString += uprv_strlen(parentID) + 1;
+
+ int32_t numChildIDs = ures_getSize(localesForParent);
+ for (int32_t i = 0; i < numChildIDs; i++) {
+ int32_t childLength = parentLocaleStringsEnd - nextString - 1;
+ const char* childID = ures_getUTF8StringByIndex(localesForParent, i, nextString, &childLength, TRUE, &err);
+ nextString += childLength + 1;
+
+ if (U_SUCCESS(err)) {
+ uhash_put(parentLocaleTable, (void*)childID, (void*)parentID, &err);
+ } else {
+ // again, if we can't build this whole table, we're in trouble
+ U_ASSERT(FALSE);
+ break;
+ }
+ }
+ localesForParent = ures_getNextResource(curBundle, localesForParent, &err);
+ }
+
+ ures_close(localesForParent);
+ ures_close(curBundle);
+}
+
+static void initParentLocaleTable() {
+ umtx_initOnce(parentLocaleInitOnce, &doInitParentLocaleTable);
+}
+
+/**
+ * <rdar://problem/63880069>
+ * Currently internal function which should eventually be moved (with new name) to ulocimp.h, or perhaps uloc.h.
+ * Somewhat like uloc_getParent, but only returns a parent from parentLocales data.
+ */
+U_CAPI int32_t U_EXPORT2
+ures_getLocParent(const char* localeID,
+ char* parent,
+ int32_t parentCapacity,
+ UErrorCode* err)
+{
+ if (U_FAILURE(*err))
+ return 0;
+ if (localeID == NULL)
+ localeID = uloc_getDefault();
+
+ initParentLocaleTable();
+ U_ASSERT(parentLocaleTable != NULL); // should not get NULL here
+ if (parentLocaleTable != NULL) {
+ const char* parentID = (const char*)uhash_get(parentLocaleTable, localeID);
+ if (parentID != NULL) {
+ int32_t parentLen = uprv_strlen(parentID);
+ uprv_memcpy(parent, parentID, uprv_min(parentLen, parentCapacity));
+ return u_terminateChars(parent, parentCapacity, parentLen, err);
+ }
+ }
+ return 0;
+ // A more general version of this might do the following instead:
+ // return uloc_getParent(localeID, parent, parentCapacity, err);
+}
+
+/**
+ * Internal function, determines the search path for resource bundle files.
+ * Currently, this function is used only by findFirstExisting() to help search for resource bundle files when a bundle for the specified
+ * locale doesn't exist. The code that supports inheritance of resources between existing resource bundle files continues to
+ * use chopLocale() below.
+ * @param name In-out parameter: On input, the locale ID to get a parent locale ID for (this is a locale's base name, without keywords); on output, the
+ * requested parent locale ID.
+ * @param savedRegionCode Pointer to a character array where this function can store the region code from the input locale ID for use by future
+ * calls to this function. The caller doesn't usually use the contents of this array itself; it's just temporary storage for this function. The caller can pass NULL,
+ * but this isn't recommended.
+ */
+static UBool getParentLocaleID(char *name, char* savedRegionCode) {
+ // first look the locale ID up in the parent locale table (if it exists); if that table specifies a parent
+ // for it, return that
+ if (parentLocaleTable != NULL) {
+ const char* parentID = (const char*)uhash_get(parentLocaleTable, name);
+ if (parentID != NULL) {
+ uprv_strcpy(name, parentID);
+ return TRUE;
+ }
+ }
+
+ // if the parent locale table didn't specify a locale ID for our locale, derive it algorithmically
+ char language[ULOC_LANG_CAPACITY];
+ char script[ULOC_SCRIPT_CAPACITY];
+ char country[ULOC_COUNTRY_CAPACITY];
+ char variant[ULOC_KEYWORD_AND_VALUES_CAPACITY];
+ UErrorCode err = U_ZERO_ERROR;
+
+ uloc_getLanguage(name, language, ULOC_LANG_CAPACITY, &err);
+ uloc_getScript(name, script, ULOC_SCRIPT_CAPACITY, &err);
+ uloc_getCountry(name, country, ULOC_COUNTRY_CAPACITY, &err);
+ uloc_getVariant(name, variant, ULOC_KEYWORD_AND_VALUES_CAPACITY, &err);
+
+ if (U_SUCCESS(err)) {
+ // if the locale ID has both script and country codes (and no variant), save the country code in
+ // savedRegionField and chop it off of the locale ID
+ if (variant[0] == '\0' && script[0] != '\0' && country[0] != '\0') {
+ uprv_strcpy(savedRegionCode, country);
+ return chopLocale(name);
+ }
+
+ // if the locale ID has language and script and we have a saved region code, replace the script code
+ // in the locale ID with the saved region code
+ if (variant[0] == '\0' && country[0] == '\0' && script[0] != '\0') {
+ sprintf(name, "%s_%s", language, savedRegionCode);
+ return TRUE;
+ }
+ }
+
+ // if we have any other configuration of fields (or couldn't get fields for some reason), clear out the saved
+ // region code and just use chopLocale()
+ *savedRegionCode = '\0';
+ return chopLocale(name);
+}
+
/**
* Internal function
*/
static void entryIncrease(UResourceDataEntry *entry) {
- umtx_lock(&resbMutex);
+ Mutex lock(&resbMutex);
entry->fCountExisting++;
while(entry->fParent != NULL) {
entry = entry->fParent;
entry->fCountExisting++;
}
- umtx_unlock(&resbMutex);
}
/**
/*if shared data hasn't even been lazy evaluated yet
* return 0
*/
- umtx_lock(&resbMutex);
+ Mutex lock(&resbMutex);
if (cache == NULL) {
- umtx_unlock(&resbMutex);
return 0;
}
* got decremented by free_entry().
*/
} while(deletedMore);
- umtx_unlock(&resbMutex);
return rbDeletedNum;
}
const UHashElement *e;
UResourceDataEntry *resB;
- umtx_lock(&resbMutex);
+ Mutex lock(&resbMutex);
if (cache == NULL) {
- umtx_unlock(&resbMutex);
fprintf(stderr,"%s:%d: RB Cache is NULL.\n", __FILE__, __LINE__);
return FALSE;
}
}
fprintf(stderr,"%s:%d: RB Cache still contains %d items.\n", __FILE__, __LINE__, uhash_count(cache));
-
- umtx_unlock(&resbMutex);
-
return cacheNotEmpty;
}
uhash_close(cache);
cache = NULL;
}
+ if (parentLocaleTable != NULL) {
+ uhash_close(parentLocaleTable);
+ uprv_free(parentLocaleStrings);
+ parentLocaleTable = NULL;
+ parentLocaleStrings = NULL;
+ parentLocaleInitOnce.reset();
+ }
gCacheInitOnce.reset();
return TRUE;
}
/** INTERNAL: Initializes the cache for resources */
-static void createCache(UErrorCode &status) {
+static void U_CALLCONV createCache(UErrorCode &status) {
U_ASSERT(cache == NULL);
cache = uhash_open(hashEntry, compareEntries, NULL, &status);
ucln_common_registerCleanup(UCLN_COMMON_URES, ures_cleanup);
/* this is the actual loading */
res_load(&(r->fData), r->fPath, r->fName, status);
- if (U_FAILURE(*status)) {
+ if (U_FAILURE(*status)) {
+ /* if we failed to load due to an out-of-memory error, exit early. */
+ if (*status == U_MEMORY_ALLOCATION_ERROR) {
+ uprv_free(r);
+ return NULL;
+ }
/* we have no such entry in dll, so it will always use fallback */
*status = U_USING_FALLBACK_WARNING;
r->fBogus = U_USING_FALLBACK_WARNING;
/* We'll try to get alias string from the bundle */
aliasres = res_getResource(&(r->fData), "%%ALIAS");
if (aliasres != RES_BOGUS) {
- const UChar *alias = res_getString(&(r->fData), aliasres, &aliasLen);
+ // No tracing: called during initial data loading
+ const UChar *alias = res_getStringNoTrace(&(r->fData), aliasres, &aliasLen);
if(alias != NULL && aliasLen > 0) { /* if there is actual alias - unload and load new data */
u_UCharsToChars(alias, aliasName, aliasLen+1);
r->fAlias = init_entry(aliasName, path, status);
/* CAUTION: resbMutex must be locked when calling this function! */
static UResourceDataEntry *
findFirstExisting(const char* path, char* name,
- UBool *isRoot, UBool *hasChopped, UBool *isDefault, UErrorCode* status) {
+ UBool *isRoot, UBool *foundParent, UBool *isDefault, UErrorCode* status) {
UResourceDataEntry *r = NULL;
UBool hasRealData = FALSE;
const char *defaultLoc = uloc_getDefault();
- *hasChopped = TRUE; /* we're starting with a fresh name */
+ *foundParent = TRUE; /* we're starting with a fresh name */
+ char savedRegionCode[ULOC_COUNTRY_CAPACITY] = "";
- while(*hasChopped && !hasRealData) {
+ while(*foundParent && !hasRealData) {
r = init_entry(name, path, status);
/* Null pointer test */
if (U_FAILURE(*status)) {
*isRoot = (UBool)(uprv_strcmp(name, kRootLocaleName) == 0);
/*Fallback data stuff*/
- *hasChopped = chopLocale(name);
+ if (!hasRealData) {
+ *foundParent = getParentLocaleID(name, savedRegionCode);
+ } else {
+ // we've already found a real resource file; what we return to the caller is the parent
+ // locale ID for inheritance, which should come from chopLocale(), not getParentLocaleID()
+ *foundParent = chopLocale(name);
+ }
+ if (*foundParent && *name == '\0') {
+ uprv_strcpy(name, "und");
+ }
}
return r;
}
ures_setIsStackObject(resB, TRUE);
}
+U_NAMESPACE_BEGIN
+
+StackUResourceBundle::StackUResourceBundle() {
+ ures_initStackObject(&bundle);
+}
+
+StackUResourceBundle::~StackUResourceBundle() {
+ ures_close(&bundle);
+}
+
+U_NAMESPACE_END
+
static UBool // returns U_SUCCESS(*status)
loadParentsExceptRoot(UResourceDataEntry *&t1,
char name[], int32_t nameCapacity,
Resource parentRes = res_getResource(&t1->fData, "%%Parent");
if (parentRes != RES_BOGUS) { // An explicit parent was found.
int32_t parentLocaleLen = 0;
- const UChar *parentLocaleName = res_getString(&(t1->fData), parentRes, &parentLocaleLen);
+ // No tracing: called during initial data loading
+ const UChar *parentLocaleName = res_getStringNoTrace(&(t1->fData), parentRes, &parentLocaleLen);
if(parentLocaleName != NULL && 0 < parentLocaleLen && parentLocaleLen < nameCapacity) {
u_UCharsToChars(parentLocaleName, name, parentLocaleLen + 1);
if (uprv_strcmp(name, kRootLocaleName) == 0) {
UErrorCode usrStatus = U_ZERO_ERROR;
if (usingUSRData) { // This code inserts user override data into the inheritance chain.
u2 = init_entry(name, usrDataPath, &usrStatus);
+ // If we failed due to out-of-memory, report that to the caller and exit early.
+ if (usrStatus == U_MEMORY_ALLOCATION_ERROR) {
+ *status = usrStatus;
+ return FALSE;
+ }
}
if (usingUSRData && U_SUCCESS(usrStatus) && u2->fBogus == U_ZERO_ERROR) {
char usrDataPath[96];
initCache(status);
+ initParentLocaleTable();
if(U_FAILURE(*status)) {
return NULL;
}
}
- umtx_lock(&resbMutex);
- { /* umtx_lock */
- /* We're going to skip all the locales that do not have any data */
- r = findFirstExisting(path, name, &isRoot, &hasChopped, &isDefault, &intStatus);
+ Mutex lock(&resbMutex); // Lock resbMutex until the end of this function.
+
+ /* We're going to skip all the locales that do not have any data */
+ r = findFirstExisting(path, name, &isRoot, &hasChopped, &isDefault, &intStatus);
+
+ // If we failed due to out-of-memory, report the failure and exit early.
+ if (intStatus == U_MEMORY_ALLOCATION_ERROR) {
+ *status = intStatus;
+ goto finish;
+ }
+
+ if(r != NULL) { /* if there is one real locale, we can look for parents. */
+ t1 = r;
+ hasRealData = TRUE;
+ if ( usingUSRData ) { /* This code inserts user override data into the inheritance chain */
+ UErrorCode usrStatus = U_ZERO_ERROR;
+ UResourceDataEntry *u1 = init_entry(t1->fName, usrDataPath, &usrStatus);
+ // If we failed due to out-of-memory, report the failure and exit early.
+ if (intStatus == U_MEMORY_ALLOCATION_ERROR) {
+ *status = intStatus;
+ goto finish;
+ }
+ if ( u1 != NULL ) {
+ if(u1->fBogus == U_ZERO_ERROR) {
+ u1->fParent = t1;
+ r = u1;
+ } else {
+ /* the USR override data wasn't found, set it to be deleted */
+ u1->fCountExisting = 0;
+ }
+ }
+ }
+ if (hasChopped && !isRoot) {
+ if (!loadParentsExceptRoot(t1, name, UPRV_LENGTHOF(name), usingUSRData, usrDataPath, status)) {
+ goto finish;
+ }
+ }
+ }
- if(r != NULL) { /* if there is one real locale, we can look for parents. */
+ /* we could have reached this point without having any real data */
+ /* if that is the case, we need to chain in the default locale */
+ if(r==NULL && openType == URES_OPEN_LOCALE_DEFAULT_ROOT && !isDefault && !isRoot) {
+ /* insert default locale */
+ uprv_strcpy(name, uloc_getDefault());
+ r = findFirstExisting(path, name, &isRoot, &hasChopped, &isDefault, &intStatus);
+ // If we failed due to out-of-memory, report the failure and exit early.
+ if (intStatus == U_MEMORY_ALLOCATION_ERROR) {
+ *status = intStatus;
+ goto finish;
+ }
+ intStatus = U_USING_DEFAULT_WARNING;
+ if(r != NULL) { /* the default locale exists */
t1 = r;
hasRealData = TRUE;
- if ( usingUSRData ) { /* This code inserts user override data into the inheritance chain */
- UErrorCode usrStatus = U_ZERO_ERROR;
- UResourceDataEntry *u1 = init_entry(t1->fName, usrDataPath, &usrStatus);
- if ( u1 != NULL ) {
- if(u1->fBogus == U_ZERO_ERROR) {
- u1->fParent = t1;
- r = u1;
- } else {
- /* the USR override data wasn't found, set it to be deleted */
- u1->fCountExisting = 0;
- }
- }
- }
+ isDefault = TRUE;
+ // TODO: Why not if (usingUSRData) { ... } like in the non-default-locale code path?
if (hasChopped && !isRoot) {
if (!loadParentsExceptRoot(t1, name, UPRV_LENGTHOF(name), usingUSRData, usrDataPath, status)) {
- goto finishUnlock;
+ goto finish;
}
}
}
+ }
- /* we could have reached this point without having any real data */
- /* if that is the case, we need to chain in the default locale */
- if(r==NULL && openType == URES_OPEN_LOCALE_DEFAULT_ROOT && !isDefault && !isRoot) {
- /* insert default locale */
- uprv_strcpy(name, uloc_getDefault());
- r = findFirstExisting(path, name, &isRoot, &hasChopped, &isDefault, &intStatus);
+ /* we could still have r == NULL at this point - maybe even default locale is not */
+ /* present */
+ if(r == NULL) {
+ uprv_strcpy(name, kRootLocaleName);
+ r = findFirstExisting(path, name, &isRoot, &hasChopped, &isDefault, &intStatus);
+ // If we failed due to out-of-memory, report the failure and exit early.
+ if (intStatus == U_MEMORY_ALLOCATION_ERROR) {
+ *status = intStatus;
+ goto finish;
+ }
+ if(r != NULL) {
+ t1 = r;
intStatus = U_USING_DEFAULT_WARNING;
- if(r != NULL) { /* the default locale exists */
- t1 = r;
- hasRealData = TRUE;
- isDefault = TRUE;
- // TODO: Why not if (usingUSRData) { ... } like in the non-default-locale code path?
- if (hasChopped && !isRoot) {
- if (!loadParentsExceptRoot(t1, name, UPRV_LENGTHOF(name), usingUSRData, usrDataPath, status)) {
- goto finishUnlock;
- }
- }
- }
+ hasRealData = TRUE;
+ } else { /* we don't even have the root locale */
+ *status = U_MISSING_RESOURCE_ERROR;
+ goto finish;
}
-
- /* we could still have r == NULL at this point - maybe even default locale is not */
- /* present */
- if(r == NULL) {
- uprv_strcpy(name, kRootLocaleName);
- r = findFirstExisting(path, name, &isRoot, &hasChopped, &isDefault, &intStatus);
- if(r != NULL) {
- t1 = r;
- intStatus = U_USING_DEFAULT_WARNING;
- hasRealData = TRUE;
- } else { /* we don't even have the root locale */
- *status = U_MISSING_RESOURCE_ERROR;
- goto finishUnlock;
- }
- } else if(!isRoot && uprv_strcmp(t1->fName, kRootLocaleName) != 0 &&
- t1->fParent == NULL && !r->fData.noFallback) {
- if (!insertRootBundle(t1, status)) {
- goto finishUnlock;
- }
- if(!hasRealData) {
- r->fBogus = U_USING_DEFAULT_WARNING;
- }
+ } else if(!isRoot && uprv_strcmp(t1->fName, kRootLocaleName) != 0 &&
+ t1->fParent == NULL && !r->fData.noFallback) {
+ if (!insertRootBundle(t1, status)) {
+ goto finish;
}
-
- // TODO: Does this ever loop?
- while(r != NULL && !isRoot && t1->fParent != NULL) {
- t1->fParent->fCountExisting++;
- t1 = t1->fParent;
+ if(!hasRealData) {
+ r->fBogus = U_USING_DEFAULT_WARNING;
}
- } /* umtx_lock */
-finishUnlock:
- umtx_unlock(&resbMutex);
+ }
+ // TODO: Does this ever loop?
+ while(r != NULL && !isRoot && t1->fParent != NULL) {
+ t1->fParent->fCountExisting++;
+ t1 = t1->fParent;
+ }
+
+finish:
if(U_SUCCESS(*status)) {
if(intStatus != U_ZERO_ERROR) {
*status = intStatus;
return NULL;
}
- umtx_lock(&resbMutex);
+ Mutex lock(&resbMutex);
// findFirstExisting() without fallbacks.
UResourceDataEntry *r = init_entry(localeID, path, status);
if(U_SUCCESS(*status)) {
t1 = t1->fParent;
}
}
- umtx_unlock(&resbMutex);
return r;
}
*/
static void entryClose(UResourceDataEntry *resB) {
- umtx_lock(&resbMutex);
+ Mutex lock(&resbMutex);
entryCloseInt(resB);
- umtx_unlock(&resbMutex);
}
/*
UResourceDataEntry *dataEntry = mainRes->fData;
char stackPath[URES_MAX_BUFFER_SIZE];
char *pathBuf = stackPath, *myPath = pathBuf;
- if(uprv_strlen(keyPath) > URES_MAX_BUFFER_SIZE) {
+ if(uprv_strlen(keyPath) >= UPRV_LENGTHOF(stackPath)) {
pathBuf = (char *)uprv_malloc((uprv_strlen(keyPath)+1)*sizeof(char));
if(pathBuf == NULL) {
*status = U_MEMORY_ALLOCATION_ERROR;
+ ures_close(mainRes);
return NULL;
}
}
if(mainRes != result) {
ures_close(mainRes);
}
+ ResourceTracer(resB).maybeTrace("getalias");
return result;
}
} else {
/*resB->fParent = parent->fRes;*/
uprv_memmove(&resB->fResData, rdata, sizeof(ResourceData));
resB->fSize = res_countArrayItems(&(resB->fResData), resB->fRes);
+ ResourceTracer(resB).trace("get");
return resB;
}
*status = U_ILLEGAL_ARGUMENT_ERROR;
return NULL;
}
- s = res_getString(&(resB->fResData), resB->fRes, len);
+ s = res_getString({resB}, &(resB->fResData), resB->fRes, len);
if (s == NULL) {
*status = U_RESOURCE_TYPE_MISMATCH;
}
*status = U_ILLEGAL_ARGUMENT_ERROR;
return NULL;
}
- p = res_getBinary(&(resB->fResData), resB->fRes, len);
+ p = res_getBinary({resB}, &(resB->fResData), resB->fRes, len);
if (p == NULL) {
*status = U_RESOURCE_TYPE_MISMATCH;
}
*status = U_ILLEGAL_ARGUMENT_ERROR;
return NULL;
}
- p = res_getIntVector(&(resB->fResData), resB->fRes, len);
+ p = res_getIntVector({resB}, &(resB->fResData), resB->fRes, len);
if (p == NULL) {
*status = U_RESOURCE_TYPE_MISMATCH;
}
*status = U_RESOURCE_TYPE_MISMATCH;
return 0xffffffff;
}
- return RES_GET_INT(resB->fRes);
+ return res_getInt({resB}, resB->fRes);
}
U_CAPI uint32_t U_EXPORT2 ures_getUInt(const UResourceBundle* resB, UErrorCode *status) {
*status = U_RESOURCE_TYPE_MISMATCH;
return 0xffffffff;
}
- return RES_GET_UINT(resB->fRes);
+ return res_getUInt({resB}, resB->fRes);
}
U_CAPI UResType U_EXPORT2 ures_getType(const UResourceBundle *resB) {
}
U_CAPI const char * U_EXPORT2 ures_getKey(const UResourceBundle *resB) {
+ //
+ // TODO: Trace ures_getKey? I guess not usually.
+ //
+ // We usually get the key string to decide whether we want the value, or to
+ // make a key-value pair. Tracing the value should suffice.
+ //
+ // However, I believe we have some data (e.g., in res_index) where the key
+ // strings are the data. Tracing the enclosing table should suffice.
+ //
if(resB == NULL) {
return NULL;
}
-
return(resB->fKey);
}
ures_close(tempRes);
return result;
} else {
- return res_getString(&(resB->fResData), r, len);
+ return res_getString({resB, sIndex}, &(resB->fResData), r, len);
}
}
switch(RES_GET_TYPE(resB->fRes)) {
case URES_STRING:
case URES_STRING_V2:
- return res_getString(&(resB->fResData), resB->fRes, len);
+ return res_getString({resB}, &(resB->fResData), resB->fRes, len);
case URES_TABLE:
case URES_TABLE16:
case URES_TABLE32:
switch(RES_GET_TYPE(resB->fRes)) {
case URES_STRING:
case URES_STRING_V2:
- return res_getString(&(resB->fResData), resB->fRes, len);
+ return res_getString({resB}, &(resB->fResData), resB->fRes, len);
case URES_TABLE:
case URES_TABLE16:
case URES_TABLE32:
break;
}
}
- } while(*myPath); /* Continue until the whole path is consumed */
+ } while(res != RES_BOGUS && *myPath); /* Continue until the whole path is consumed */
}
}
/*const ResourceData *rd = getFallbackData(resB, &key, &realData, &res, status);*/
namespace {
-void getAllContainerItemsWithFallback(
+void getAllItemsWithFallback(
const UResourceBundle *bundle, ResourceDataValue &value,
- ResourceArraySink *arraySink, ResourceTableSink *tableSink,
+ ResourceSink &sink,
UErrorCode &errorCode) {
if (U_FAILURE(errorCode)) { return; }
// We recursively enumerate child-first,
// only storing parent items in the absence of child items.
- // We store a placeholder value for the no-fallback/no-inheritance marker
+ // The sink needs to store a placeholder value for the no-fallback/no-inheritance marker
// to prevent a parent item from being stored.
//
// It would be possible to recursively enumerate parent-first,
// overriding parent items with child items.
- // When we see the no-fallback/no-inheritance marker,
- // then we would remove the parent's item.
+ // When the sink sees the no-fallback/no-inheritance marker,
+ // then it would remove the parent's item.
// We would deserialize parent values even though they are overridden in a child bundle.
- UResType expectedType = arraySink != NULL ? URES_ARRAY : URES_TABLE;
- if (ures_getType(bundle) == expectedType) {
- value.pResData = &bundle->fResData;
- if (arraySink != NULL) {
- ures_getAllArrayItems(&bundle->fResData, bundle->fRes, value, *arraySink, errorCode);
- } else /* tableSink != NULL */ {
- ures_getAllTableItems(&bundle->fResData, bundle->fRes, value, *tableSink, errorCode);
- }
- }
- UResourceDataEntry *entry = bundle->fData->fParent;
- if (entry != NULL && U_SUCCESS(entry->fBogus)) {
+ value.setData(&bundle->fResData);
+ UResourceDataEntry *parentEntry = bundle->fData->fParent;
+ UBool hasParent = parentEntry != NULL && U_SUCCESS(parentEntry->fBogus);
+ value.setResource(bundle->fRes, ResourceTracer(bundle));
+ sink.put(bundle->fKey, value, !hasParent, errorCode);
+ if (hasParent) {
// We might try to query the sink whether
// any fallback from the parent bundle is still possible.
// so that we need not create UResourceBundle objects.
UResourceBundle parentBundle;
ures_initStackObject(&parentBundle);
- parentBundle.fTopLevelData = parentBundle.fData = entry;
+ parentBundle.fTopLevelData = parentBundle.fData = parentEntry;
// TODO: What is the difference between bundle fData and fTopLevelData?
- uprv_memcpy(&parentBundle.fResData, &entry->fData, sizeof(ResourceData));
+ uprv_memcpy(&parentBundle.fResData, &parentEntry->fData, sizeof(ResourceData));
// TODO: Try to replace bundle.fResData with just using bundle.fData->fData.
parentBundle.fHasFallback = !parentBundle.fResData.noFallback;
parentBundle.fIsTopLevel = TRUE;
parentBundle.fRes = parentBundle.fResData.rootRes;
parentBundle.fSize = res_countArrayItems(&(parentBundle.fResData), parentBundle.fRes);
parentBundle.fIndex = -1;
- entryIncrease(entry);
+ entryIncrease(parentEntry);
// Look up the container item in the parent bundle.
UResourceBundle containerBundle;
ures_initStackObject(&containerBundle);
const UResourceBundle *rb;
+ UErrorCode pathErrorCode = U_ZERO_ERROR; // Ignore if parents up to root do not have this path.
if (bundle->fResPath == NULL || *bundle->fResPath == 0) {
rb = &parentBundle;
} else {
rb = ures_getByKeyWithFallback(&parentBundle, bundle->fResPath,
- &containerBundle, &errorCode);
+ &containerBundle, &pathErrorCode);
}
- if (U_SUCCESS(errorCode) && ures_getType(rb) == expectedType) {
- getAllContainerItemsWithFallback(rb, value,
- arraySink, tableSink, errorCode);
+ if (U_SUCCESS(pathErrorCode)) {
+ getAllItemsWithFallback(rb, value, sink, errorCode);
}
ures_close(&containerBundle);
ures_close(&parentBundle);
}
}
-void getAllContainerItemsWithFallback(
- const UResourceBundle *bundle, const char *path,
- ResourceArraySink *arraySink, ResourceTableSink *tableSink,
- UErrorCode &errorCode) {
+} // namespace
+
+// Requires a ResourceDataValue fill-in, so that we need not cast from a ResourceValue.
+// Unfortunately, the caller must know which subclass to make and pass in.
+// Alternatively, we could make it as polymorphic as in Java by
+// returning a ResourceValue pointer (possibly wrapped into a LocalPointer)
+// that the caller then owns.
+//
+// Also requires a UResourceBundle fill-in, so that the value's ResourceTracer
+// can point to a non-local bundle.
+// Without tracing, the child bundle could be a function-local object.
+U_CAPI void U_EXPORT2
+ures_getValueWithFallback(const UResourceBundle *bundle, const char *path,
+ UResourceBundle *tempFillIn,
+ ResourceDataValue &value, UErrorCode &errorCode) {
if (U_FAILURE(errorCode)) { return; }
- if (path == NULL) {
+ if (path == nullptr) {
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
- UResourceBundle stackBundle;
- ures_initStackObject(&stackBundle);
const UResourceBundle *rb;
if (*path == 0) {
// empty path
rb = bundle;
} else {
- rb = ures_getByKeyWithFallback(bundle, path, &stackBundle, &errorCode);
+ rb = ures_getByKeyWithFallback(bundle, path, tempFillIn, &errorCode);
if (U_FAILURE(errorCode)) {
- ures_close(&stackBundle);
return;
}
}
- UResType expectedType = arraySink != NULL ? URES_ARRAY : URES_TABLE;
- if (ures_getType(rb) != expectedType) {
- errorCode = U_RESOURCE_TYPE_MISMATCH;
- ures_close(&stackBundle);
+ value.setData(&rb->fResData);
+ value.setResource(rb->fRes, ResourceTracer(rb));
+}
+
+U_CAPI void U_EXPORT2
+ures_getAllItemsWithFallback(const UResourceBundle *bundle, const char *path,
+ icu::ResourceSink &sink, UErrorCode &errorCode) {
+ if (U_FAILURE(errorCode)) { return; }
+ if (path == nullptr) {
+ errorCode = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
+ StackUResourceBundle stackBundle;
+ const UResourceBundle *rb;
+ if (*path == 0) {
+ // empty path
+ rb = bundle;
+ } else {
+ rb = ures_getByKeyWithFallback(bundle, path, stackBundle.getAlias(), &errorCode);
+ if (U_FAILURE(errorCode)) {
+ return;
+ }
+ }
// Get all table items with fallback.
ResourceDataValue value;
- getAllContainerItemsWithFallback(rb, value, arraySink, tableSink, errorCode);
- ures_close(&stackBundle);
-}
-
-} // namespace
-
-U_CAPI void U_EXPORT2
-ures_getAllArrayItemsWithFallback(const UResourceBundle *bundle, const char *path,
- ResourceArraySink &sink, UErrorCode &errorCode) {
- getAllContainerItemsWithFallback(bundle, path, &sink, NULL, errorCode);
-}
-
-U_CAPI void U_EXPORT2
-ures_getAllTableItemsWithFallback(const UResourceBundle *bundle, const char *path,
- ResourceTableSink &sink, UErrorCode &errorCode) {
- getAllContainerItemsWithFallback(bundle, path, NULL, &sink, errorCode);
+ getAllItemsWithFallback(rb, value, sink, errorCode);
}
U_CAPI UResourceBundle* U_EXPORT2 ures_getByKey(const UResourceBundle *resB, const char* inKey, UResourceBundle *fillIn, UErrorCode *status) {
switch (RES_GET_TYPE(res)) {
case URES_STRING:
case URES_STRING_V2:
- return res_getString(rd, res, len);
+ return res_getString({resB, key}, rd, res, len);
case URES_ALIAS:
{
const UChar* result = 0;
switch (RES_GET_TYPE(res)) {
case URES_STRING:
case URES_STRING_V2:
- return res_getString(&(resB->fResData), res, len);
+ return res_getString({resB, key}, &(resB->fResData), res, len);
case URES_ALIAS:
{
const UChar* result = 0;
/* here should go a first attempt to locate the key using index table */
const ResourceData *rd = getFallbackData(resB, &key, &realData, &res, status);
if(U_SUCCESS(*status)) {
+ // TODO: Tracing
return res_getString(rd, res, len);
} else {
*status = U_MISSING_RESOURCE_ERROR;
r->fSize = res_countArrayItems(&(r->fResData), r->fRes);
r->fIndex = -1;
+ ResourceTracer(r).traceOpen();
+
return r;
}
}
/**
- * API: This function is used to open a resource bundle
+ * Internal API: This function is used to open a resource bundle
* proper fallback chaining is executed while initialization.
* The result is stored in cache for later fallback search.
+ *
+ * Same as ures_open(), but uses the fill-in parameter and does not allocate a new bundle.
*/
-U_CAPI void U_EXPORT2
+U_INTERNAL void U_EXPORT2
ures_openFillIn(UResourceBundle *r, const char* path,
const char* localeID, UErrorCode* status) {
if(U_SUCCESS(*status) && r == NULL) {
ures_openWithType(r, path, localeID, URES_OPEN_LOCALE_DEFAULT_ROOT, status);
}
+/**
+ * Same as ures_openDirect(), but uses the fill-in parameter and does not allocate a new bundle.
+ */
+U_INTERNAL void U_EXPORT2
+ures_openDirectFillIn(UResourceBundle *r, const char* path, const char* localeID, UErrorCode* status) {
+ if(U_SUCCESS(*status) && r == NULL) {
+ *status = U_ILLEGAL_ARGUMENT_ERROR;
+ return;
+ }
+ ures_openWithType(r, path, localeID, URES_OPEN_DIRECT, status);
+}
+
+/**
+ * Same as ures_open(), except that if no resource bundle for the specified package name and locale exists,
+ * and the incoming locale specifies a country, this will fall back to the resource bundle for the specified country
+ * and the specified country's default language. For example, if caller asks for fr_JP and no bundle for fr_JP exists,
+ * ures_open() will fall back to fr and this function will fall back to ja_JP.
+ */
+U_INTERNAL UResourceBundle* U_EXPORT2
+ures_openWithCountryFallback(const char* packageName,
+ const char* locale,
+ UBool* didFallBackByCountry,
+ UErrorCode* status) {
+ // First, call ures_open().
+ UResourceBundle* result = ures_open(packageName, locale, status);
+ if (didFallBackByCountry != NULL) {
+ *didFallBackByCountry = FALSE;
+ }
+
+ // If the original locale specified a country and the resource bundle we got from ures_open() above
+ // is a fallback locale that throws away the specified country, create a NEW fallback bundle using
+ // the originally-specified country code paired with that country's default language code (as given
+ // by Locale::addLikelySubtags() ). That is, if the user asks for number patterns for ja_US, use
+ // the patterns for en_US instead of the patterns for ja.
+ char country[ULOC_COUNTRY_CAPACITY];
+ uloc_getCountry(locale, country, ULOC_COUNTRY_CAPACITY, status);
+
+ // Do the special logic if we got a fallback resource bundle and the original locale specified a country.
+ if (*status == U_USING_FALLBACK_WARNING && uprv_strlen(country) > 0) {
+ // If the fallback bundle's locale *doesn't* specify a country, or specifies a different country
+ // than we originally asked for, do our special fallback logic.
+ char receivedCountry[ULOC_COUNTRY_CAPACITY];
+ uloc_getCountry(ures_getLocaleByType(result, ULOC_ACTUAL_LOCALE, status), receivedCountry, ULOC_COUNTRY_CAPACITY, status);
+ if (uprv_strcmp(country, receivedCountry) != 0) {
+ char language[ULOC_LANG_CAPACITY];
+ char script[ULOC_SCRIPT_CAPACITY];
+ const char* countryAndParameters = locale; // this changes below
+ char countryLocale[ULOC_FULLNAME_CAPACITY];
+ UBool originalLocaleHasScript = FALSE;
+
+ // Isolate out the fields in the original locale.
+ uloc_getLanguage(locale, language, ULOC_LANG_CAPACITY, status);
+ uloc_getScript(locale, script, ULOC_SCRIPT_CAPACITY, status);
+ originalLocaleHasScript = uprv_strlen(script) > 0;
+ countryAndParameters = locale + uprv_strlen(language) + 1; // +1 for the _ after the language
+ if (originalLocaleHasScript) {
+ countryAndParameters += uprv_strlen(script) + 1; // +1 for the _ after the script
+ }
+
+ // Get the default language for the specified country by fabricating a locale ID with
+ // that country code and "und" for the language code and calling uloc_addLikelySubtags().
+ sprintf(countryLocale, "und_%s", country);
+ uloc_addLikelySubtags(countryLocale, countryLocale, ULOC_FULLNAME_CAPACITY, status);
+ uloc_getLanguage(countryLocale, language, ULOC_LANG_CAPACITY, status);
+ if (!originalLocaleHasScript) {
+ uloc_getScript(countryLocale, script, ULOC_SCRIPT_CAPACITY, status);
+ }
+
+ if (U_SUCCESS(*status)) {
+ UResourceBundle* newResource = NULL;
+
+ // Create a new locale ID using the language from uloc_addLikelySubtags() and the script (if present),
+ // country, and parameters from the original locale and try opening a resource for it.
+ *status = U_ZERO_ERROR;
+ sprintf(countryLocale, "%s_%s_%s", language, script, countryAndParameters);
+ newResource = ures_open(packageName, countryLocale, status);
+
+ // If we got back a fallback locale of the default locale, we have more work to do...
+ if (*status == U_USING_FALLBACK_WARNING || *status == U_USING_DEFAULT_WARNING) {
+ char receivedLanguage[ULOC_LANG_CAPACITY];
+ uloc_getLanguage(ures_getLocaleByType(newResource, ULOC_ACTUAL_LOCALE, status), receivedLanguage, ULOC_LANG_CAPACITY, status);
+
+ // If we got back a resource for the default locale, or we got back a resource for a locale with
+ // a different language than the one we asked for, that means uloc_addLikelySubtags() gave us back
+ // a locale with a language we don't actually have resource bundles for, or it gave us back "und"
+ // instead of a real language code. For the non-"und" cases, we're getting back the most important
+ // spoken language for that country, but we only have resource data for that country's "official"
+ // language. Most of the time, that language is English (we also use English for "und"); the table
+ // below covers the exceptions. Look up the appropriate language in the table and try again to
+ // load a resource bundle for that language.
+ if (*status == U_USING_DEFAULT_WARNING || uprv_strcmp(language, receivedLanguage) != 0) {
+ static const char* substituteLanguageTable[] = {
+ "pap_Latn_BQ", "nl",
+ "pap_Latn_CW", "nl",
+ "aa_Latn_DJ", "fr",
+ "ht_Latn_HT", "fr",
+ "bi_Latn_VU", "fr"
+ };
+ uprv_strcpy(language, "en");
+ for (int32_t i = 0; i < sizeof(substituteLanguageTable) / sizeof(char*); i += 2) {
+ if (uprv_strncmp(countryLocale, substituteLanguageTable[i], uprv_strlen(substituteLanguageTable[i])) == 0) {
+ uprv_strcpy(language, substituteLanguageTable[i + 1]);
+ break;
+ }
+ }
+ sprintf(countryLocale, "%s_%s_%s", language, script, countryAndParameters);
+ ures_close(newResource);
+ newResource = ures_open(packageName, countryLocale, status);
+ }
+ }
+
+ // If that succeeds, that's what we return as our result.
+ if (U_SUCCESS(*status)) {
+ // If the user passed us a pointer in didFallBackByCountry, set it based on whether our special
+ // logic actually retrieved a different resource bundle than the ures_open() call at the top
+ // of the function.
+ if (didFallBackByCountry != NULL) {
+ UErrorCode tmpStatus = U_ZERO_ERROR;
+ const char* languageLocale = ures_getLocaleByType(result, ULOC_ACTUAL_LOCALE, &tmpStatus);
+ const char* countryLocale = ures_getLocaleByType(newResource, ULOC_ACTUAL_LOCALE, &tmpStatus);
+ *didFallBackByCountry = U_SUCCESS(tmpStatus) && languageLocale != NULL && countryLocale != NULL && uprv_strcmp(languageLocale, countryLocale) != 0;
+ }
+ ures_close(result);
+ result = newResource;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
/**
* API: Counts members. For arrays and tables, returns number of resources.
* For strings, returns 1.
return ures_getSize(&ctx->installed);
}
-static const char* U_CALLCONV
+U_CDECL_BEGIN
+
+
+static const char * U_CALLCONV
ures_loc_nextLocale(UEnumeration* en,
int32_t* resultLength,
UErrorCode* status) {
UResourceBundle *k = NULL;
const char *result = NULL;
int32_t len = 0;
- if(ures_hasNext(res) && (k = ures_getNextResource(res, &ctx->curr, status))) {
+ if(ures_hasNext(res) && (k = ures_getNextResource(res, &ctx->curr, status)) != 0) {
result = ures_getKey(k);
len = (int32_t)uprv_strlen(result);
}
ures_resetIterator(res);
}
+U_CDECL_END
static const UEnumeration gLocalesEnum = {
NULL,
uprv_strcpy(found, ures_getLocaleByType(res, ULOC_VALID_LOCALE, &subStatus));
}
- uloc_getParent(found,parent,sizeof(parent),&subStatus);
+ if (uprv_strcmp(found, parent) != 0) {
+ uprv_strcpy(parent, found);
+ } else {
+ uloc_getParent(found, parent, sizeof(parent), &subStatus);
+ }
+
ures_close(res);
} while(!defVal[0] && *found && uprv_strcmp(found, "root") != 0 && U_SUCCESS(*status));
subStatus = U_ZERO_ERROR;
- uprv_strcpy(found, parent);
+ UBool haveFound = FALSE;
+ // At least for collations which may be aliased, we need to use the VALID locale
+ // as the parent instead of just truncating, as long as the VALID locale is not
+ // root and has a different language than the parent. Use of the VALID locale
+ // here is similar to the procedure used at the end of the previous do-while loop
+ // for all resource types. This is for <rdar://problem/31138554>.
+ // It may be appropriate for all resources here too, filing an ICU ticket.
+ if (res != NULL && uprv_strcmp(resName, "collations") == 0) {
+ const char *validLoc = ures_getLocaleByType(res, ULOC_VALID_LOCALE, &subStatus);
+ if (U_SUCCESS(subStatus) && validLoc != NULL && validLoc[0] != 0 && uprv_strcmp(validLoc, "root") != 0) {
+ char validLang[ULOC_LANG_CAPACITY];
+ char parentLang[ULOC_LANG_CAPACITY];
+ uloc_getLanguage(validLoc, validLang, ULOC_LANG_CAPACITY, &subStatus);
+ uloc_getLanguage(parent, parentLang, ULOC_LANG_CAPACITY, &subStatus);
+ if (U_SUCCESS(subStatus) && uprv_strcmp(validLang, parentLang) != 0) {
+ // validLoc is not root and has a different language than parent, use it instead
+ uprv_strcpy(found, validLoc);
+ haveFound = TRUE;
+ }
+ }
+ subStatus = U_ZERO_ERROR;
+ }
+ if (!haveFound) {
+ uprv_strcpy(found, parent);
+ }
+
uloc_getParent(found,parent,1023,&subStatus);
ures_close(res);
} while(!full[0] && *found && U_SUCCESS(*status));
valuesBuf[0]=0;
valuesBuf[1]=0;
- while((locale = uenum_next(locs, &locLen, status))) {
+ while((locale = uenum_next(locs, &locLen, status)) != 0) {
UResourceBundle *bund = NULL;
UResourceBundle *subPtr = NULL;
UErrorCode subStatus = U_ZERO_ERROR; /* don't fail if a bundle is unopenable */
continue;
}
- while((subPtr = ures_getNextResource(&item,&subItem,&subStatus))
+ while((subPtr = ures_getNextResource(&item,&subItem,&subStatus)) != 0
&& U_SUCCESS(subStatus)) {
const char *k;
int32_t i;