123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- // © 2019 and later: Unicode, Inc. and others.
- // License & terms of use: http://www.unicode.org/copyright.html
- // localeprioritylist.cpp
- // created: 2019jul11 Markus W. Scherer
- #include "unicode/utypes.h"
- #include "unicode/localpointer.h"
- #include "unicode/locid.h"
- #include "unicode/stringpiece.h"
- #include "unicode/uobject.h"
- #include "charstr.h"
- #include "cmemory.h"
- #include "localeprioritylist.h"
- #include "uarrsort.h"
- #include "uassert.h"
- #include "uhash.h"
- U_NAMESPACE_BEGIN
- namespace {
- int32_t hashLocale(const UHashTok token) {
- auto *locale = static_cast<const Locale *>(token.pointer);
- return locale->hashCode();
- }
- UBool compareLocales(const UHashTok t1, const UHashTok t2) {
- auto *l1 = static_cast<const Locale *>(t1.pointer);
- auto *l2 = static_cast<const Locale *>(t2.pointer);
- return *l1 == *l2;
- }
- constexpr int32_t WEIGHT_ONE = 1000;
- struct LocaleAndWeight {
- Locale *locale;
- int32_t weight; // 0..1000 = 0.0..1.0
- int32_t index; // force stable sort
- int32_t compare(const LocaleAndWeight &other) const {
- int32_t diff = other.weight - weight; // descending: other-this
- if (diff != 0) { return diff; }
- return index - other.index;
- }
- };
- int32_t U_CALLCONV
- compareLocaleAndWeight(const void * /*context*/, const void *left, const void *right) {
- return static_cast<const LocaleAndWeight *>(left)->
- compare(*static_cast<const LocaleAndWeight *>(right));
- }
- const char *skipSpaces(const char *p, const char *limit) {
- while (p < limit && *p == ' ') { ++p; }
- return p;
- }
- int32_t findTagLength(const char *p, const char *limit) {
- // Look for accept-language delimiters.
- // Leave other validation up to the Locale constructor.
- const char *q;
- for (q = p; q < limit; ++q) {
- char c = *q;
- if (c == ' ' || c == ',' || c == ';') { break; }
- }
- return static_cast<int32_t>(q - p);
- }
- /**
- * Parses and returns a qvalue weight in millis.
- * Advances p to after the parsed substring.
- * Returns a negative value if parsing fails.
- */
- int32_t parseWeight(const char *&p, const char *limit) {
- p = skipSpaces(p, limit);
- char c;
- if (p == limit || ((c = *p) != '0' && c != '1')) { return -1; }
- int32_t weight = (c - '0') * 1000;
- if (++p == limit || *p != '.') { return weight; }
- int32_t multiplier = 100;
- while (++p != limit && '0' <= (c = *p) && c <= '9') {
- c -= '0';
- if (multiplier > 0) {
- weight += c * multiplier;
- multiplier /= 10;
- } else if (multiplier == 0) {
- // round up
- if (c >= 5) { ++weight; }
- multiplier = -1;
- } // else ignore further fraction digits
- }
- return weight <= WEIGHT_ONE ? weight : -1; // bad if > 1.0
- }
- } // namespace
- /**
- * Nothing but a wrapper over a MaybeStackArray of LocaleAndWeight.
- *
- * This wrapper exists (and is not in an anonymous namespace)
- * so that we can forward-declare it in the header file and
- * don't have to expose the MaybeStackArray specialization and
- * the LocaleAndWeight to code (like the test) that #includes localeprioritylist.h.
- * Also, otherwise we would have to do a platform-specific
- * template export declaration of some kind for the MaybeStackArray specialization
- * to be properly exported from the common DLL.
- */
- struct LocaleAndWeightArray : public UMemory {
- MaybeStackArray<LocaleAndWeight, 20> array;
- };
- LocalePriorityList::LocalePriorityList(StringPiece s, UErrorCode &errorCode) {
- if (U_FAILURE(errorCode)) { return; }
- list = new LocaleAndWeightArray();
- if (list == nullptr) {
- errorCode = U_MEMORY_ALLOCATION_ERROR;
- return;
- }
- const char *p = s.data();
- const char *limit = p + s.length();
- while ((p = skipSpaces(p, limit)) != limit) {
- if (*p == ',') { // empty range field
- ++p;
- continue;
- }
- int32_t tagLength = findTagLength(p, limit);
- if (tagLength == 0) {
- errorCode = U_ILLEGAL_ARGUMENT_ERROR;
- return;
- }
- CharString tag(p, tagLength, errorCode);
- if (U_FAILURE(errorCode)) { return; }
- Locale locale = Locale(tag.data());
- if (locale.isBogus()) {
- errorCode = U_ILLEGAL_ARGUMENT_ERROR;
- return;
- }
- int32_t weight = WEIGHT_ONE;
- if ((p = skipSpaces(p + tagLength, limit)) != limit && *p == ';') {
- if ((p = skipSpaces(p + 1, limit)) == limit || *p != 'q' ||
- (p = skipSpaces(p + 1, limit)) == limit || *p != '=' ||
- (++p, (weight = parseWeight(p, limit)) < 0)) {
- errorCode = U_ILLEGAL_ARGUMENT_ERROR;
- return;
- }
- p = skipSpaces(p, limit);
- }
- if (p != limit && *p != ',') { // trailing junk
- errorCode = U_ILLEGAL_ARGUMENT_ERROR;
- return;
- }
- add(locale, weight, errorCode);
- if (p == limit) { break; }
- ++p;
- }
- sort(errorCode);
- }
- LocalePriorityList::~LocalePriorityList() {
- if (list != nullptr) {
- for (int32_t i = 0; i < listLength; ++i) {
- delete list->array[i].locale;
- }
- delete list;
- }
- uhash_close(map);
- }
- const Locale *LocalePriorityList::localeAt(int32_t i) const {
- return list->array[i].locale;
- }
- Locale *LocalePriorityList::orphanLocaleAt(int32_t i) {
- if (list == nullptr) { return nullptr; }
- LocaleAndWeight &lw = list->array[i];
- Locale *l = lw.locale;
- lw.locale = nullptr;
- return l;
- }
- bool LocalePriorityList::add(const Locale &locale, int32_t weight, UErrorCode &errorCode) {
- if (U_FAILURE(errorCode)) { return false; }
- if (map == nullptr) {
- if (weight <= 0) { return true; } // do not add q=0
- map = uhash_open(hashLocale, compareLocales, uhash_compareLong, &errorCode);
- if (U_FAILURE(errorCode)) { return false; }
- }
- LocalPointer<Locale> clone;
- UBool found = false;
- int32_t index = uhash_getiAndFound(map, &locale, &found);
- if (found) {
- // Duplicate: Remove the old item and append it anew.
- LocaleAndWeight &lw = list->array[index];
- clone.adoptInstead(lw.locale);
- lw.locale = nullptr;
- lw.weight = 0;
- ++numRemoved;
- }
- if (weight <= 0) { // do not add q=0
- if (found) {
- // Not strictly necessary but cleaner.
- uhash_removei(map, &locale);
- }
- return true;
- }
- if (clone.isNull()) {
- clone.adoptInstead(locale.clone());
- if (clone.isNull() || (clone->isBogus() && !locale.isBogus())) {
- errorCode = U_MEMORY_ALLOCATION_ERROR;
- return false;
- }
- }
- if (listLength == list->array.getCapacity()) {
- int32_t newCapacity = listLength < 50 ? 100 : 4 * listLength;
- if (list->array.resize(newCapacity, listLength) == nullptr) {
- errorCode = U_MEMORY_ALLOCATION_ERROR;
- return false;
- }
- }
- uhash_putiAllowZero(map, clone.getAlias(), listLength, &errorCode);
- if (U_FAILURE(errorCode)) { return false; }
- LocaleAndWeight &lw = list->array[listLength];
- lw.locale = clone.orphan();
- lw.weight = weight;
- lw.index = listLength++;
- if (weight < WEIGHT_ONE) { hasWeights = true; }
- U_ASSERT(uhash_count(map) == getLength());
- return true;
- }
- void LocalePriorityList::sort(UErrorCode &errorCode) {
- // Sort by descending weights if there is a mix of weights.
- // The comparator forces a stable sort via the item index.
- if (U_FAILURE(errorCode) || getLength() <= 1 || !hasWeights) { return; }
- uprv_sortArray(list->array.getAlias(), listLength, sizeof(LocaleAndWeight),
- compareLocaleAndWeight, nullptr, false, &errorCode);
- }
- U_NAMESPACE_END
|