tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ucol_res.cpp (25635B)


      1 // © 2016 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html
      3 /*
      4 *******************************************************************************
      5 *   Copyright (C) 1996-2016, International Business Machines
      6 *   Corporation and others.  All Rights Reserved.
      7 *******************************************************************************
      8 *   file name:  ucol_res.cpp
      9 *   encoding:   UTF-8
     10 *   tab size:   8 (not used)
     11 *   indentation:4
     12 *
     13 * Description:
     14 * This file contains dependencies that the collation run-time doesn't normally
     15 * need. This mainly contains resource bundle usage and collation meta information
     16 *
     17 * Modification history
     18 * Date        Name      Comments
     19 * 1996-1999   various members of ICU team maintained C API for collation framework
     20 * 02/16/2001  synwee    Added internal method getPrevSpecialCE
     21 * 03/01/2001  synwee    Added maxexpansion functionality.
     22 * 03/16/2001  weiv      Collation framework is rewritten in C and made UCA compliant
     23 * 12/08/2004  grhoten   Split part of ucol.cpp into ucol_res.cpp
     24 * 2012-2014   markus    Rewritten in C++ again.
     25 */
     26 
     27 #include "unicode/utypes.h"
     28 
     29 #if !UCONFIG_NO_COLLATION
     30 
     31 #include "unicode/coll.h"
     32 #include "unicode/localpointer.h"
     33 #include "unicode/locid.h"
     34 #include "unicode/tblcoll.h"
     35 #include "unicode/ucol.h"
     36 #include "unicode/uloc.h"
     37 #include "unicode/unistr.h"
     38 #include "unicode/ures.h"
     39 #include "charstr.h"
     40 #include "cmemory.h"
     41 #include "cstring.h"
     42 #include "collationdatareader.h"
     43 #include "collationroot.h"
     44 #include "collationtailoring.h"
     45 #include "resource.h"
     46 #include "putilimp.h"
     47 #include "uassert.h"
     48 #include "ucln_in.h"
     49 #include "ucol_imp.h"
     50 #include "uenumimp.h"
     51 #include "ulist.h"
     52 #include "umutex.h"
     53 #include "unifiedcache.h"
     54 #include "uresimp.h"
     55 #include "ustrenum.h"
     56 #include "utracimp.h"
     57 
     58 U_NAMESPACE_BEGIN
     59 
     60 namespace {
     61 
     62 const char16_t* rootRules = nullptr;
     63 int32_t rootRulesLength = 0;
     64 UResourceBundle* rootBundle = nullptr;
     65 UInitOnce gInitOnceUcolRes{};
     66 
     67 }  // namespace
     68 
     69 U_CDECL_BEGIN
     70 
     71 static UBool U_CALLCONV
     72 ucol_res_cleanup() {
     73    rootRules = nullptr;
     74    rootRulesLength = 0;
     75    ures_close(rootBundle);
     76    rootBundle = nullptr;
     77    gInitOnceUcolRes.reset();
     78    return true;
     79 }
     80 
     81 void U_CALLCONV
     82 CollationLoader::loadRootRules(UErrorCode &errorCode) {
     83    if(U_FAILURE(errorCode)) { return; }
     84    rootBundle = ures_open(U_ICUDATA_COLL, kRootLocaleName, &errorCode);
     85    if(U_FAILURE(errorCode)) { return; }
     86    rootRules = ures_getStringByKey(rootBundle, "UCARules", &rootRulesLength, &errorCode);
     87    if(U_FAILURE(errorCode)) {
     88        ures_close(rootBundle);
     89        rootBundle = nullptr;
     90        return;
     91    }
     92    ucln_i18n_registerCleanup(UCLN_I18N_UCOL_RES, ucol_res_cleanup);
     93 }
     94 
     95 U_CDECL_END
     96 
     97 void
     98 CollationLoader::appendRootRules(UnicodeString &s) {
     99    UErrorCode errorCode = U_ZERO_ERROR;
    100    umtx_initOnce(gInitOnceUcolRes, CollationLoader::loadRootRules, errorCode);
    101    if(U_SUCCESS(errorCode)) {
    102        s.append(rootRules, rootRulesLength);
    103    }
    104 }
    105 
    106 void
    107 CollationLoader::loadRules(const char *localeID, const char *collationType,
    108                           UnicodeString &rules, UErrorCode &errorCode) {
    109    if(U_FAILURE(errorCode)) { return; }
    110    U_ASSERT(collationType != nullptr && *collationType != 0);
    111    // Copy the type for lowercasing.
    112    char type[16];
    113    int32_t typeLength = static_cast<int32_t>(uprv_strlen(collationType));
    114    if(typeLength >= UPRV_LENGTHOF(type)) {
    115        errorCode = U_ILLEGAL_ARGUMENT_ERROR;
    116        return;
    117    }
    118    uprv_memcpy(type, collationType, typeLength + 1);
    119    T_CString_toLowerCase(type);
    120 
    121    LocalUResourceBundlePointer bundle(ures_open(U_ICUDATA_COLL, localeID, &errorCode));
    122    LocalUResourceBundlePointer collations(
    123            ures_getByKey(bundle.getAlias(), "collations", nullptr, &errorCode));
    124    LocalUResourceBundlePointer data(
    125            ures_getByKeyWithFallback(collations.getAlias(), type, nullptr, &errorCode));
    126    int32_t length;
    127    const char16_t *s =  ures_getStringByKey(data.getAlias(), "Sequence", &length, &errorCode);
    128    if(U_FAILURE(errorCode)) { return; }
    129 
    130    // No string pointer aliasing so that we need not hold onto the resource bundle.
    131    rules.setTo(s, length);
    132    if(rules.isBogus()) {
    133        errorCode = U_MEMORY_ALLOCATION_ERROR;
    134    }
    135 }
    136 
    137 template<> U_I18N_API
    138 const CollationCacheEntry *
    139 LocaleCacheKey<CollationCacheEntry>::createObject(const void *creationContext,
    140                                                  UErrorCode &errorCode) const {
    141    CollationLoader *loader =
    142            reinterpret_cast<CollationLoader *>(
    143                    const_cast<void *>(creationContext));
    144    return loader->createCacheEntry(errorCode);
    145 }
    146 
    147 const CollationCacheEntry *
    148 CollationLoader::loadTailoring(const Locale &locale, UErrorCode &errorCode) {
    149    const CollationCacheEntry *rootEntry = CollationRoot::getRootCacheEntry(errorCode);
    150    if(U_FAILURE(errorCode)) { return nullptr; }
    151    const char *name = locale.getName();
    152    if(*name == 0 || uprv_strcmp(name, "root") == 0) {
    153 
    154        // Have to add a ref.
    155        rootEntry->addRef();
    156        return rootEntry;
    157    }
    158 
    159    // Clear warning codes before loading where they get cached.
    160    errorCode = U_ZERO_ERROR;
    161    CollationLoader loader(rootEntry, locale, errorCode);
    162 
    163    // getCacheEntry adds a ref for us.
    164    return loader.getCacheEntry(errorCode);
    165 }
    166 
    167 CollationLoader::CollationLoader(const CollationCacheEntry *re, const Locale &requested,
    168                                 UErrorCode &errorCode)
    169        : cache(UnifiedCache::getInstance(errorCode)), rootEntry(re),
    170          validLocale(re->validLocale), locale(requested),
    171          typesTried(0), typeFallback(false),
    172          bundle(nullptr), collations(nullptr), data(nullptr) {
    173    type[0] = 0;
    174    defaultType[0] = 0;
    175    if(U_FAILURE(errorCode)) { return; }
    176 
    177    if (locale.isBogus()) {
    178        errorCode = U_ILLEGAL_ARGUMENT_ERROR;
    179        return;
    180    }
    181    // Canonicalize the locale ID: Ignore all irrelevant keywords.
    182    const char *baseName = locale.getBaseName();
    183    if(uprv_strcmp(locale.getName(), baseName) != 0) {
    184        locale = Locale(baseName);
    185        // Due to ICU-22416, we may have bogus locale constructed from
    186        // a string of getBaseName().
    187        if (locale.isBogus()) {
    188            errorCode = U_ILLEGAL_ARGUMENT_ERROR;
    189            return;
    190        }
    191 
    192        // Fetch the collation type from the locale ID.
    193        int32_t typeLength = requested.getKeywordValue("collation",
    194                type, UPRV_LENGTHOF(type) - 1, errorCode);
    195        if(U_FAILURE(errorCode)) {
    196            errorCode = U_ILLEGAL_ARGUMENT_ERROR;
    197            return;
    198        }
    199        type[typeLength] = 0;  // in case of U_NOT_TERMINATED_WARNING
    200        if(typeLength == 0) {
    201            // No collation type.
    202        } else if(uprv_stricmp(type, "default") == 0) {
    203            // Ignore "default" (case-insensitive).
    204            type[0] = 0;
    205        } else {
    206            // Copy the collation type.
    207            T_CString_toLowerCase(type);
    208            locale.setKeywordValue("collation", type, errorCode);
    209        }
    210    }
    211 }
    212 
    213 CollationLoader::~CollationLoader() {
    214    ures_close(data);
    215    ures_close(collations);
    216    ures_close(bundle);
    217 }
    218 
    219 const CollationCacheEntry *
    220 CollationLoader::createCacheEntry(UErrorCode &errorCode) {
    221    // This is a linear lookup and fallback flow turned into a state machine.
    222    // Most local variables have been turned into instance fields.
    223    // In a cache miss, cache.get() calls CacheKey::createObject(),
    224    // which means that we progress via recursion.
    225    // loadFromCollations() will recurse to itself as well for collation type fallback.
    226    if(bundle == nullptr) {
    227        return loadFromLocale(errorCode);
    228    } else if(collations == nullptr) {
    229        return loadFromBundle(errorCode);
    230    } else if(data == nullptr) {
    231        return loadFromCollations(errorCode);
    232    } else {
    233        return loadFromData(errorCode);
    234    }
    235 }
    236 
    237 const CollationCacheEntry *
    238 CollationLoader::loadFromLocale(UErrorCode &errorCode) {
    239    if(U_FAILURE(errorCode)) { return nullptr; }
    240    U_ASSERT(bundle == nullptr);
    241    bundle = ures_openNoDefault(U_ICUDATA_COLL, locale.getBaseName(), &errorCode);
    242    if(errorCode == U_MISSING_RESOURCE_ERROR) {
    243        errorCode = U_USING_DEFAULT_WARNING;
    244 
    245        // Have to add that ref that we promise.
    246        rootEntry->addRef();
    247        return rootEntry;
    248    }
    249    Locale requestedLocale(locale);
    250    const char *vLocale = ures_getLocaleByType(bundle, ULOC_ACTUAL_LOCALE, &errorCode);
    251    if(U_FAILURE(errorCode)) { return nullptr; }
    252    locale = validLocale = Locale(vLocale);  // no type until loadFromCollations()
    253    if(type[0] != 0) {
    254        locale.setKeywordValue("collation", type, errorCode);
    255    }
    256    if(locale != requestedLocale) {
    257        return getCacheEntry(errorCode);
    258    } else {
    259        return loadFromBundle(errorCode);
    260    }
    261 }
    262 
    263 const CollationCacheEntry *
    264 CollationLoader::loadFromBundle(UErrorCode &errorCode) {
    265    if(U_FAILURE(errorCode)) { return nullptr; }
    266    U_ASSERT(collations == nullptr);
    267    // There are zero or more tailorings in the collations table.
    268    collations = ures_getByKey(bundle, "collations", nullptr, &errorCode);
    269    if(errorCode == U_MISSING_RESOURCE_ERROR) {
    270        errorCode = U_USING_DEFAULT_WARNING;
    271        // Return the root tailoring with the validLocale, without collation type.
    272        return makeCacheEntryFromRoot(validLocale, errorCode);
    273    }
    274    if(U_FAILURE(errorCode)) { return nullptr; }
    275 
    276    // Fetch the default type from the data.
    277    {
    278        UErrorCode internalErrorCode = U_ZERO_ERROR;
    279        LocalUResourceBundlePointer def(
    280                ures_getByKeyWithFallback(collations, "default", nullptr, &internalErrorCode));
    281        int32_t length;
    282        const char16_t *s = ures_getString(def.getAlias(), &length, &internalErrorCode);
    283        if(U_SUCCESS(internalErrorCode) && 0 < length && length < UPRV_LENGTHOF(defaultType)) {
    284            u_UCharsToChars(s, defaultType, length + 1);
    285        } else {
    286            uprv_strcpy(defaultType, "standard");
    287        }
    288    }
    289 
    290    // Record which collation types we have looked for already,
    291    // so that we do not deadlock in the cache.
    292    //
    293    // If there is no explicit type, then we look in the cache
    294    // for the entry with the default type.
    295    // If the explicit type is the default type, then we do not look in the cache
    296    // for the entry with an empty type.
    297    // Otherwise, two concurrent requests with opposite fallbacks would deadlock each other.
    298    // Also, it is easier to always enter the next method with a non-empty type.
    299    if(type[0] == 0) {
    300        uprv_strcpy(type, defaultType);
    301        typesTried |= TRIED_DEFAULT;
    302        if(uprv_strcmp(type, "search") == 0) {
    303            typesTried |= TRIED_SEARCH;
    304        }
    305        if(uprv_strcmp(type, "standard") == 0) {
    306            typesTried |= TRIED_STANDARD;
    307        }
    308        locale.setKeywordValue("collation", type, errorCode);
    309        return getCacheEntry(errorCode);
    310    } else {
    311        if(uprv_strcmp(type, defaultType) == 0) {
    312            typesTried |= TRIED_DEFAULT;
    313        }
    314        if(uprv_strcmp(type, "search") == 0) {
    315            typesTried |= TRIED_SEARCH;
    316        }
    317        if(uprv_strcmp(type, "standard") == 0) {
    318            typesTried |= TRIED_STANDARD;
    319        }
    320        return loadFromCollations(errorCode);
    321    }
    322 }
    323 
    324 const CollationCacheEntry *
    325 CollationLoader::loadFromCollations(UErrorCode &errorCode) {
    326    if(U_FAILURE(errorCode)) { return nullptr; }
    327    U_ASSERT(data == nullptr);
    328    // Load the collations/type tailoring, with type fallback.
    329    LocalUResourceBundlePointer localData(
    330            ures_getByKeyWithFallback(collations, type, nullptr, &errorCode));
    331    int32_t typeLength = static_cast<int32_t>(uprv_strlen(type));
    332    if(errorCode == U_MISSING_RESOURCE_ERROR) {
    333        errorCode = U_USING_DEFAULT_WARNING;
    334        typeFallback = true;
    335        if((typesTried & TRIED_SEARCH) == 0 &&
    336                typeLength > 6 && uprv_strncmp(type, "search", 6) == 0) {
    337            // fall back from something like "searchjl" to "search"
    338            typesTried |= TRIED_SEARCH;
    339            type[6] = 0;
    340        } else if((typesTried & TRIED_DEFAULT) == 0) {
    341            // fall back to the default type
    342            typesTried |= TRIED_DEFAULT;
    343            uprv_strcpy(type, defaultType);
    344        } else if((typesTried & TRIED_STANDARD) == 0) {
    345            // fall back to the "standard" type
    346            typesTried |= TRIED_STANDARD;
    347            uprv_strcpy(type, "standard");
    348        } else {
    349            // Return the root tailoring with the validLocale, without collation type.
    350            return makeCacheEntryFromRoot(validLocale, errorCode);
    351        }
    352        locale.setKeywordValue("collation", type, errorCode);
    353        return getCacheEntry(errorCode);
    354    }
    355    if(U_FAILURE(errorCode)) { return nullptr; }
    356 
    357    data = localData.orphan();
    358    const char *actualLocale = ures_getLocaleByType(data, ULOC_ACTUAL_LOCALE, &errorCode);
    359    if(U_FAILURE(errorCode)) { return nullptr; }
    360    const char *vLocale = validLocale.getBaseName();
    361    UBool actualAndValidLocalesAreDifferent = Locale(actualLocale) != Locale(vLocale);
    362 
    363    // Set the collation types on the informational locales,
    364    // except when they match the default types (for brevity and backwards compatibility).
    365    // For the valid locale, suppress the default type.
    366    if(uprv_strcmp(type, defaultType) != 0) {
    367        validLocale.setKeywordValue("collation", type, errorCode);
    368        if(U_FAILURE(errorCode)) { return nullptr; }
    369    }
    370 
    371    // Is this the same as the root collator? If so, then use that instead.
    372    if((*actualLocale == 0 || uprv_strcmp(actualLocale, "root") == 0) &&
    373            uprv_strcmp(type, "standard") == 0) {
    374        if(typeFallback) {
    375            errorCode = U_USING_DEFAULT_WARNING;
    376        }
    377        return makeCacheEntryFromRoot(validLocale, errorCode);
    378    }
    379 
    380    locale = Locale(actualLocale);
    381    if(actualAndValidLocalesAreDifferent) {
    382        locale.setKeywordValue("collation", type, errorCode);
    383        const CollationCacheEntry *entry = getCacheEntry(errorCode);
    384        return makeCacheEntry(validLocale, entry, errorCode);
    385    } else {
    386        return loadFromData(errorCode);
    387    }
    388 }
    389 
    390 const CollationCacheEntry *
    391 CollationLoader::loadFromData(UErrorCode &errorCode) {
    392    if(U_FAILURE(errorCode)) { return nullptr; }
    393    LocalPointer<CollationTailoring> t(new CollationTailoring(rootEntry->tailoring->settings));
    394    if(t.isNull() || t->isBogus()) {
    395        errorCode = U_MEMORY_ALLOCATION_ERROR;
    396        return nullptr;
    397    }
    398 
    399    // deserialize
    400    LocalUResourceBundlePointer binary(ures_getByKey(data, "%%CollationBin", nullptr, &errorCode));
    401    // Note: U_MISSING_RESOURCE_ERROR --> The old code built from rules if available
    402    // but that created undesirable dependencies.
    403    int32_t length = 0;
    404    const uint8_t *inBytes = ures_getBinary(binary.getAlias(), &length, &errorCode);
    405    CollationDataReader::read(rootEntry->tailoring, inBytes, length, *t, errorCode);
    406    // Note: U_COLLATOR_VERSION_MISMATCH --> The old code built from rules if available
    407    // but that created undesirable dependencies.
    408    if(U_FAILURE(errorCode)) { return nullptr; }
    409 
    410    // Try to fetch the optional rules string.
    411    {
    412        UErrorCode internalErrorCode = U_ZERO_ERROR;
    413        int32_t len;
    414        const char16_t *s = ures_getStringByKey(data, "Sequence", &len,
    415                                             &internalErrorCode);
    416        if(U_SUCCESS(internalErrorCode)) {
    417            t->rules.setTo(true, s, len);
    418        }
    419    }
    420 
    421    const char *actualLocale = locale.getBaseName();  // without type
    422    const char *vLocale = validLocale.getBaseName();
    423    UBool actualAndValidLocalesAreDifferent = Locale(actualLocale) != Locale(vLocale);
    424 
    425    // For the actual locale, suppress the default type *according to the actual locale*.
    426    // For example, zh has default=pinyin and contains all of the Chinese tailorings.
    427    // zh_Hant has default=stroke but has no other data.
    428    // For the valid locale "zh_Hant" we need to suppress stroke.
    429    // For the actual locale "zh" we need to suppress pinyin instead.
    430    if(actualAndValidLocalesAreDifferent) {
    431        // Opening a bundle for the actual locale should always succeed.
    432        LocalUResourceBundlePointer actualBundle(
    433                ures_open(U_ICUDATA_COLL, actualLocale, &errorCode));
    434        if(U_FAILURE(errorCode)) { return nullptr; }
    435        UErrorCode internalErrorCode = U_ZERO_ERROR;
    436        LocalUResourceBundlePointer def(
    437                ures_getByKeyWithFallback(actualBundle.getAlias(), "collations/default", nullptr,
    438                                          &internalErrorCode));
    439        int32_t len;
    440        const char16_t *s = ures_getString(def.getAlias(), &len, &internalErrorCode);
    441        if(U_SUCCESS(internalErrorCode) && len < UPRV_LENGTHOF(defaultType)) {
    442            u_UCharsToChars(s, defaultType, len + 1);
    443        } else {
    444            uprv_strcpy(defaultType, "standard");
    445        }
    446    }
    447    t->actualLocale = locale;
    448    if(uprv_strcmp(type, defaultType) != 0) {
    449        t->actualLocale.setKeywordValue("collation", type, errorCode);
    450    } else if(uprv_strcmp(locale.getName(), locale.getBaseName()) != 0) {
    451        // Remove the collation keyword if it was set.
    452        t->actualLocale.setKeywordValue("collation", nullptr, errorCode);
    453    }
    454    if(U_FAILURE(errorCode)) { return nullptr; }
    455 
    456    if(typeFallback) {
    457        errorCode = U_USING_DEFAULT_WARNING;
    458    }
    459    t->bundle = bundle;
    460    bundle = nullptr;
    461    const CollationCacheEntry *entry = new CollationCacheEntry(validLocale, t.getAlias());
    462    if(entry == nullptr) {
    463        errorCode = U_MEMORY_ALLOCATION_ERROR;
    464        return nullptr;
    465    } else {
    466        t.orphan();
    467    }
    468    // Have to add that reference that we promise.
    469    entry->addRef();
    470    return entry;
    471 }
    472 
    473 const CollationCacheEntry *
    474 CollationLoader::getCacheEntry(UErrorCode &errorCode) {
    475    LocaleCacheKey<CollationCacheEntry> key(locale);
    476    const CollationCacheEntry *entry = nullptr;
    477    cache->get(key, this, entry, errorCode);
    478    return entry;
    479 }
    480 
    481 const CollationCacheEntry *
    482 CollationLoader::makeCacheEntryFromRoot(
    483        const Locale &/*loc*/,
    484        UErrorCode &errorCode) const {
    485    if (U_FAILURE(errorCode)) {
    486        return nullptr;
    487    }
    488    rootEntry->addRef();
    489    return makeCacheEntry(validLocale, rootEntry, errorCode);
    490 }
    491 
    492 const CollationCacheEntry *
    493 CollationLoader::makeCacheEntry(
    494        const Locale &loc,
    495        const CollationCacheEntry *entryFromCache,
    496        UErrorCode &errorCode) {
    497    if(U_FAILURE(errorCode) || loc == entryFromCache->validLocale) {
    498        return entryFromCache;
    499    }
    500    CollationCacheEntry *entry = new CollationCacheEntry(loc, entryFromCache->tailoring);
    501    if(entry == nullptr) {
    502        errorCode = U_MEMORY_ALLOCATION_ERROR;
    503        entryFromCache->removeRef();
    504        return nullptr;
    505    }
    506    entry->addRef();
    507    entryFromCache->removeRef();
    508    return entry;
    509 }
    510 
    511 U_NAMESPACE_END
    512 
    513 U_NAMESPACE_USE
    514 
    515 U_CAPI UCollator*
    516 ucol_open(const char *loc,
    517          UErrorCode *status)
    518 {
    519    UTRACE_ENTRY_OC(UTRACE_UCOL_OPEN);
    520    UTRACE_DATA1(UTRACE_INFO, "locale = \"%s\"", loc);
    521    UCollator *result = nullptr;
    522 
    523    Collator *coll = Collator::createInstance(loc, *status);
    524    if(U_SUCCESS(*status)) {
    525        result = coll->toUCollator();
    526    }
    527    UTRACE_EXIT_PTR_STATUS(result, *status);
    528    return result;
    529 }
    530 
    531 
    532 U_CAPI int32_t U_EXPORT2
    533 ucol_getDisplayName(    const    char        *objLoc,
    534                    const    char        *dispLoc,
    535                    char16_t          *result,
    536                    int32_t         resultLength,
    537                    UErrorCode        *status)
    538 {
    539    if(U_FAILURE(*status)) return -1;
    540    UnicodeString dst;
    541    if(!(result==nullptr && resultLength==0)) {
    542        // nullptr destination for pure preflighting: empty dummy string
    543        // otherwise, alias the destination buffer
    544        dst.setTo(result, 0, resultLength);
    545    }
    546    Collator::getDisplayName(Locale(objLoc), Locale(dispLoc), dst);
    547    return dst.extract(result, resultLength, *status);
    548 }
    549 
    550 U_CAPI const char* U_EXPORT2
    551 ucol_getAvailable(int32_t index)
    552 {
    553    int32_t count = 0;
    554    const Locale *loc = Collator::getAvailableLocales(count);
    555    if (loc != nullptr && index < count) {
    556        return loc[index].getName();
    557    }
    558    return nullptr;
    559 }
    560 
    561 U_CAPI int32_t U_EXPORT2
    562 ucol_countAvailable()
    563 {
    564    int32_t count = 0;
    565    Collator::getAvailableLocales(count);
    566    return count;
    567 }
    568 
    569 #if !UCONFIG_NO_SERVICE
    570 U_CAPI UEnumeration* U_EXPORT2
    571 ucol_openAvailableLocales(UErrorCode *status) {
    572    // This is a wrapper over Collator::getAvailableLocales()
    573    if (U_FAILURE(*status)) {
    574        return nullptr;
    575    }
    576    StringEnumeration *s = icu::Collator::getAvailableLocales();
    577    if (s == nullptr) {
    578        *status = U_MEMORY_ALLOCATION_ERROR;
    579        return nullptr;
    580    }
    581    return uenum_openFromStringEnumeration(s, status);
    582 }
    583 #endif
    584 
    585 // Note: KEYWORDS[0] != RESOURCE_NAME - alan
    586 
    587 static const char RESOURCE_NAME[] = "collations";
    588 
    589 static const char* const KEYWORDS[] = { "collation" };
    590 
    591 #define KEYWORD_COUNT UPRV_LENGTHOF(KEYWORDS)
    592 
    593 U_CAPI UEnumeration* U_EXPORT2
    594 ucol_getKeywords(UErrorCode *status) {
    595    UEnumeration *result = nullptr;
    596    if (U_SUCCESS(*status)) {
    597        return uenum_openCharStringsEnumeration(KEYWORDS, KEYWORD_COUNT, status);
    598    }
    599    return result;
    600 }
    601 
    602 U_CAPI UEnumeration* U_EXPORT2
    603 ucol_getKeywordValues(const char *keyword, UErrorCode *status) {
    604    if (U_FAILURE(*status)) {
    605        return nullptr;
    606    }
    607    // hard-coded to accept exactly one collation keyword
    608    // modify if additional collation keyword is added later
    609    if (keyword==nullptr || uprv_strcmp(keyword, KEYWORDS[0])!=0)
    610    {
    611        *status = U_ILLEGAL_ARGUMENT_ERROR;
    612        return nullptr;
    613    }
    614    return ures_getKeywordValues(U_ICUDATA_COLL, RESOURCE_NAME, status);
    615 }
    616 
    617 static const UEnumeration defaultKeywordValues = {
    618    nullptr,
    619    nullptr,
    620    ulist_close_keyword_values_iterator,
    621    ulist_count_keyword_values,
    622    uenum_unextDefault,
    623    ulist_next_keyword_value,
    624    ulist_reset_keyword_values_iterator
    625 };
    626 
    627 namespace {
    628 
    629 struct KeywordsSink : public ResourceSink {
    630 public:
    631    KeywordsSink(UErrorCode &errorCode) :
    632            values(ulist_createEmptyList(&errorCode)), hasDefault(false) {}
    633    virtual ~KeywordsSink();
    634 
    635    virtual void put(const char *key, ResourceValue &value, UBool /*noFallback*/,
    636                     UErrorCode &errorCode) override {
    637        if (U_FAILURE(errorCode)) { return; }
    638        ResourceTable collations = value.getTable(errorCode);
    639        for (int32_t i = 0; collations.getKeyAndValue(i, key, value); ++i) {
    640            UResType type = value.getType();
    641            if (type == URES_STRING) {
    642                if (!hasDefault && uprv_strcmp(key, "default") == 0) {
    643                    CharString defcoll;
    644                    defcoll.appendInvariantChars(value.getUnicodeString(errorCode), errorCode);
    645                    if (U_SUCCESS(errorCode) && !defcoll.isEmpty()) {
    646                        char *ownedDefault = uprv_strdup(defcoll.data());
    647                        if (ownedDefault == nullptr) {
    648                            errorCode = U_MEMORY_ALLOCATION_ERROR;
    649                            return;
    650                        }
    651                        ulist_removeString(values, defcoll.data());
    652                        ulist_addItemBeginList(values, ownedDefault, true, &errorCode);
    653                        hasDefault = true;
    654                    }
    655                }
    656            } else if (type == URES_TABLE && uprv_strncmp(key, "private-", 8) != 0) {
    657                if (!ulist_containsString(values, key, static_cast<int32_t>(uprv_strlen(key)))) {
    658                    ulist_addItemEndList(values, key, false, &errorCode);
    659                }
    660            }
    661            if (U_FAILURE(errorCode)) { return; }
    662        }
    663    }
    664 
    665    UList *values;
    666    UBool hasDefault;
    667 };
    668 
    669 KeywordsSink::~KeywordsSink() {
    670    ulist_deleteList(values);
    671 }
    672 
    673 }  // namespace
    674 
    675 U_CAPI UEnumeration* U_EXPORT2
    676 ucol_getKeywordValuesForLocale(const char* /*key*/, const char* locale,
    677                               UBool /*commonlyUsed*/, UErrorCode* status) {
    678    // Note: The parameter commonlyUsed is not used.
    679    // The switch is in the method signature for consistency
    680    // with other locale services.
    681 
    682    // Read available collation values from collation bundles.
    683    LocalUResourceBundlePointer bundle(ures_open(U_ICUDATA_COLL, locale, status));
    684    KeywordsSink sink(*status);
    685    ures_getAllItemsWithFallback(bundle.getAlias(), RESOURCE_NAME, sink, *status);
    686    if (U_FAILURE(*status)) { return nullptr; }
    687 
    688    UEnumeration *en = (UEnumeration *)uprv_malloc(sizeof(UEnumeration));
    689    if (en == nullptr) {
    690        *status = U_MEMORY_ALLOCATION_ERROR;
    691        return nullptr;
    692    }
    693    memcpy(en, &defaultKeywordValues, sizeof(UEnumeration));
    694    ulist_resetList(sink.values);  // Initialize the iterator.
    695    en->context = sink.values;
    696    sink.values = nullptr;  // Avoid deletion in the sink destructor.
    697    return en;
    698 }
    699 
    700 U_CAPI int32_t U_EXPORT2
    701 ucol_getFunctionalEquivalent(char* result, int32_t resultCapacity,
    702                             const char* keyword, const char* locale,
    703                             UBool* isAvailable, UErrorCode* status)
    704 {
    705    // N.B.: Resource name is "collations" but keyword is "collation"
    706    return ures_getFunctionalEquivalent(result, resultCapacity, U_ICUDATA_COLL,
    707        "collations", keyword, locale,
    708        isAvailable, true, status);
    709 }
    710 
    711 #endif /* #if !UCONFIG_NO_COLLATION */