xref: /aosp_15_r20/external/cronet/third_party/icu/source/i18n/ucol_res.cpp (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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 static const char16_t *rootRules = nullptr;
63 static int32_t rootRulesLength = 0;
64 static UResourceBundle *rootBundle = nullptr;
65 static UInitOnce gInitOnceUcolRes {};
66 
67 }  // namespace
68 
69 U_CDECL_BEGIN
70 
71 static UBool U_CALLCONV
ucol_res_cleanup()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
loadRootRules(UErrorCode & errorCode)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
appendRootRules(UnicodeString & s)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
loadRules(const char * localeID,const char * collationType,UnicodeString & rules,UErrorCode & errorCode)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 *
createObject(const void * creationContext,UErrorCode & errorCode) const139 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 *
loadTailoring(const Locale & locale,UErrorCode & errorCode)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 
CollationLoader(const CollationCacheEntry * re,const Locale & requested,UErrorCode & errorCode)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 
~CollationLoader()213 CollationLoader::~CollationLoader() {
214     ures_close(data);
215     ures_close(collations);
216     ures_close(bundle);
217 }
218 
219 const CollationCacheEntry *
createCacheEntry(UErrorCode & errorCode)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 *
loadFromLocale(UErrorCode & errorCode)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 *
loadFromBundle(UErrorCode & errorCode)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 *
loadFromCollations(UErrorCode & errorCode)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 *
loadFromData(UErrorCode & errorCode)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 *
getCacheEntry(UErrorCode & errorCode)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 *
makeCacheEntryFromRoot(const Locale &,UErrorCode & errorCode) const482 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 *
makeCacheEntry(const Locale & loc,const CollationCacheEntry * entryFromCache,UErrorCode & errorCode)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*
ucol_open(const char * loc,UErrorCode * status)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
ucol_getDisplayName(const char * objLoc,const char * dispLoc,char16_t * result,int32_t resultLength,UErrorCode * status)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
ucol_getAvailable(int32_t index)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
ucol_countAvailable()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
ucol_openAvailableLocales(UErrorCode * status)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
ucol_getKeywords(UErrorCode * status)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
ucol_getKeywordValues(const char * keyword,UErrorCode * status)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:
KeywordsSink__anon1ad6a6060211::KeywordsSink631     KeywordsSink(UErrorCode &errorCode) :
632             values(ulist_createEmptyList(&errorCode)), hasDefault(false) {}
633     virtual ~KeywordsSink();
634 
put__anon1ad6a6060211::KeywordsSink635     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, (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 
~KeywordsSink()669 KeywordsSink::~KeywordsSink() {
670     ulist_deleteList(values);
671 }
672 
673 }  // namespace
674 
675 U_CAPI UEnumeration* U_EXPORT2
ucol_getKeywordValuesForLocale(const char *,const char * locale,UBool,UErrorCode * status)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
ucol_getFunctionalEquivalent(char * result,int32_t resultCapacity,const char * keyword,const char * locale,UBool * isAvailable,UErrorCode * status)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 */
712