Path: blob/master/thirdparty/icu4c/common/localeprioritylist.cpp
9904 views
// © 2019 and later: Unicode, Inc. and others.1// License & terms of use: http://www.unicode.org/copyright.html23// localeprioritylist.cpp4// created: 2019jul11 Markus W. Scherer56#include "unicode/utypes.h"7#include "unicode/localpointer.h"8#include "unicode/locid.h"9#include "unicode/stringpiece.h"10#include "unicode/uobject.h"11#include "charstr.h"12#include "cmemory.h"13#include "localeprioritylist.h"14#include "uarrsort.h"15#include "uassert.h"16#include "uhash.h"1718U_NAMESPACE_BEGIN1920namespace {2122int32_t hashLocale(const UHashTok token) {23const auto* locale = static_cast<const Locale*>(token.pointer);24return locale->hashCode();25}2627UBool compareLocales(const UHashTok t1, const UHashTok t2) {28const auto* l1 = static_cast<const Locale*>(t1.pointer);29const auto* l2 = static_cast<const Locale*>(t2.pointer);30return *l1 == *l2;31}3233constexpr int32_t WEIGHT_ONE = 1000;3435struct LocaleAndWeight {36Locale *locale;37int32_t weight; // 0..1000 = 0.0..1.038int32_t index; // force stable sort3940int32_t compare(const LocaleAndWeight &other) const {41int32_t diff = other.weight - weight; // descending: other-this42if (diff != 0) { return diff; }43return index - other.index;44}45};4647int32_t U_CALLCONV48compareLocaleAndWeight(const void * /*context*/, const void *left, const void *right) {49return static_cast<const LocaleAndWeight *>(left)->50compare(*static_cast<const LocaleAndWeight *>(right));51}5253const char *skipSpaces(const char *p, const char *limit) {54while (p < limit && *p == ' ') { ++p; }55return p;56}5758int32_t findTagLength(const char *p, const char *limit) {59// Look for accept-language delimiters.60// Leave other validation up to the Locale constructor.61const char *q;62for (q = p; q < limit; ++q) {63char c = *q;64if (c == ' ' || c == ',' || c == ';') { break; }65}66return static_cast<int32_t>(q - p);67}6869/**70* Parses and returns a qvalue weight in millis.71* Advances p to after the parsed substring.72* Returns a negative value if parsing fails.73*/74int32_t parseWeight(const char *&p, const char *limit) {75p = skipSpaces(p, limit);76char c;77if (p == limit || ((c = *p) != '0' && c != '1')) { return -1; }78int32_t weight = (c - '0') * 1000;79if (++p == limit || *p != '.') { return weight; }80int32_t multiplier = 100;81while (++p != limit && '0' <= (c = *p) && c <= '9') {82c -= '0';83if (multiplier > 0) {84weight += c * multiplier;85multiplier /= 10;86} else if (multiplier == 0) {87// round up88if (c >= 5) { ++weight; }89multiplier = -1;90} // else ignore further fraction digits91}92return weight <= WEIGHT_ONE ? weight : -1; // bad if > 1.093}9495} // namespace9697/**98* Nothing but a wrapper over a MaybeStackArray of LocaleAndWeight.99*100* This wrapper exists (and is not in an anonymous namespace)101* so that we can forward-declare it in the header file and102* don't have to expose the MaybeStackArray specialization and103* the LocaleAndWeight to code (like the test) that #includes localeprioritylist.h.104* Also, otherwise we would have to do a platform-specific105* template export declaration of some kind for the MaybeStackArray specialization106* to be properly exported from the common DLL.107*/108struct LocaleAndWeightArray : public UMemory {109MaybeStackArray<LocaleAndWeight, 20> array;110};111112LocalePriorityList::LocalePriorityList(StringPiece s, UErrorCode &errorCode) {113if (U_FAILURE(errorCode)) { return; }114list = new LocaleAndWeightArray();115if (list == nullptr) {116errorCode = U_MEMORY_ALLOCATION_ERROR;117return;118}119const char *p = s.data();120const char *limit = p + s.length();121while ((p = skipSpaces(p, limit)) != limit) {122if (*p == ',') { // empty range field123++p;124continue;125}126int32_t tagLength = findTagLength(p, limit);127if (tagLength == 0) {128errorCode = U_ILLEGAL_ARGUMENT_ERROR;129return;130}131CharString tag(p, tagLength, errorCode);132if (U_FAILURE(errorCode)) { return; }133Locale locale = Locale(tag.data());134if (locale.isBogus()) {135errorCode = U_ILLEGAL_ARGUMENT_ERROR;136return;137}138int32_t weight = WEIGHT_ONE;139if ((p = skipSpaces(p + tagLength, limit)) != limit && *p == ';') {140if ((p = skipSpaces(p + 1, limit)) == limit || *p != 'q' ||141(p = skipSpaces(p + 1, limit)) == limit || *p != '=' ||142(++p, (weight = parseWeight(p, limit)) < 0)) {143errorCode = U_ILLEGAL_ARGUMENT_ERROR;144return;145}146p = skipSpaces(p, limit);147}148if (p != limit && *p != ',') { // trailing junk149errorCode = U_ILLEGAL_ARGUMENT_ERROR;150return;151}152add(locale, weight, errorCode);153if (p == limit) { break; }154++p;155}156sort(errorCode);157}158159LocalePriorityList::~LocalePriorityList() {160if (list != nullptr) {161for (int32_t i = 0; i < listLength; ++i) {162delete list->array[i].locale;163}164delete list;165}166uhash_close(map);167}168169const Locale *LocalePriorityList::localeAt(int32_t i) const {170return list->array[i].locale;171}172173Locale *LocalePriorityList::orphanLocaleAt(int32_t i) {174if (list == nullptr) { return nullptr; }175LocaleAndWeight &lw = list->array[i];176Locale *l = lw.locale;177lw.locale = nullptr;178return l;179}180181bool LocalePriorityList::add(const Locale &locale, int32_t weight, UErrorCode &errorCode) {182if (U_FAILURE(errorCode)) { return false; }183if (map == nullptr) {184if (weight <= 0) { return true; } // do not add q=0185map = uhash_open(hashLocale, compareLocales, uhash_compareLong, &errorCode);186if (U_FAILURE(errorCode)) { return false; }187}188LocalPointer<Locale> clone;189UBool found = false;190int32_t index = uhash_getiAndFound(map, &locale, &found);191if (found) {192// Duplicate: Remove the old item and append it anew.193LocaleAndWeight &lw = list->array[index];194clone.adoptInstead(lw.locale);195lw.locale = nullptr;196lw.weight = 0;197++numRemoved;198}199if (weight <= 0) { // do not add q=0200if (found) {201// Not strictly necessary but cleaner.202uhash_removei(map, &locale);203}204return true;205}206if (clone.isNull()) {207clone.adoptInstead(locale.clone());208if (clone.isNull() || (clone->isBogus() && !locale.isBogus())) {209errorCode = U_MEMORY_ALLOCATION_ERROR;210return false;211}212}213if (listLength == list->array.getCapacity()) {214int32_t newCapacity = listLength < 50 ? 100 : 4 * listLength;215if (list->array.resize(newCapacity, listLength) == nullptr) {216errorCode = U_MEMORY_ALLOCATION_ERROR;217return false;218}219}220uhash_putiAllowZero(map, clone.getAlias(), listLength, &errorCode);221if (U_FAILURE(errorCode)) { return false; }222LocaleAndWeight &lw = list->array[listLength];223lw.locale = clone.orphan();224lw.weight = weight;225lw.index = listLength++;226if (weight < WEIGHT_ONE) { hasWeights = true; }227U_ASSERT(uhash_count(map) == getLength());228return true;229}230231void LocalePriorityList::sort(UErrorCode &errorCode) {232// Sort by descending weights if there is a mix of weights.233// The comparator forces a stable sort via the item index.234if (U_FAILURE(errorCode) || getLength() <= 1 || !hasWeights) { return; }235uprv_sortArray(list->array.getAlias(), listLength, sizeof(LocaleAndWeight),236compareLocaleAndWeight, nullptr, false, &errorCode);237}238239U_NAMESPACE_END240241242