localeprioritylist.cpp (7849B)
1 // © 2019 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 4 // localeprioritylist.cpp 5 // created: 2019jul11 Markus W. Scherer 6 7 #include "unicode/utypes.h" 8 #include "unicode/localpointer.h" 9 #include "unicode/locid.h" 10 #include "unicode/stringpiece.h" 11 #include "unicode/uobject.h" 12 #include "charstr.h" 13 #include "cmemory.h" 14 #include "localeprioritylist.h" 15 #include "uarrsort.h" 16 #include "uassert.h" 17 #include "uhash.h" 18 19 U_NAMESPACE_BEGIN 20 21 namespace { 22 23 int32_t hashLocale(const UHashTok token) { 24 const auto* locale = static_cast<const Locale*>(token.pointer); 25 return locale->hashCode(); 26 } 27 28 UBool compareLocales(const UHashTok t1, const UHashTok t2) { 29 const auto* l1 = static_cast<const Locale*>(t1.pointer); 30 const auto* l2 = static_cast<const Locale*>(t2.pointer); 31 return *l1 == *l2; 32 } 33 34 constexpr int32_t WEIGHT_ONE = 1000; 35 36 struct LocaleAndWeight { 37 Locale *locale; 38 int32_t weight; // 0..1000 = 0.0..1.0 39 int32_t index; // force stable sort 40 41 int32_t compare(const LocaleAndWeight &other) const { 42 int32_t diff = other.weight - weight; // descending: other-this 43 if (diff != 0) { return diff; } 44 return index - other.index; 45 } 46 }; 47 48 int32_t U_CALLCONV 49 compareLocaleAndWeight(const void * /*context*/, const void *left, const void *right) { 50 return static_cast<const LocaleAndWeight *>(left)-> 51 compare(*static_cast<const LocaleAndWeight *>(right)); 52 } 53 54 const char *skipSpaces(const char *p, const char *limit) { 55 while (p < limit && *p == ' ') { ++p; } 56 return p; 57 } 58 59 int32_t findTagLength(const char *p, const char *limit) { 60 // Look for accept-language delimiters. 61 // Leave other validation up to the Locale constructor. 62 const char *q; 63 for (q = p; q < limit; ++q) { 64 char c = *q; 65 if (c == ' ' || c == ',' || c == ';') { break; } 66 } 67 return static_cast<int32_t>(q - p); 68 } 69 70 /** 71 * Parses and returns a qvalue weight in millis. 72 * Advances p to after the parsed substring. 73 * Returns a negative value if parsing fails. 74 */ 75 int32_t parseWeight(const char *&p, const char *limit) { 76 p = skipSpaces(p, limit); 77 char c; 78 if (p == limit || ((c = *p) != '0' && c != '1')) { return -1; } 79 int32_t weight = (c - '0') * 1000; 80 if (++p == limit || *p != '.') { return weight; } 81 int32_t multiplier = 100; 82 while (++p != limit && '0' <= (c = *p) && c <= '9') { 83 c -= '0'; 84 if (multiplier > 0) { 85 weight += c * multiplier; 86 multiplier /= 10; 87 } else if (multiplier == 0) { 88 // round up 89 if (c >= 5) { ++weight; } 90 multiplier = -1; 91 } // else ignore further fraction digits 92 } 93 return weight <= WEIGHT_ONE ? weight : -1; // bad if > 1.0 94 } 95 96 } // namespace 97 98 /** 99 * Nothing but a wrapper over a MaybeStackArray of LocaleAndWeight. 100 * 101 * This wrapper exists (and is not in an anonymous namespace) 102 * so that we can forward-declare it in the header file and 103 * don't have to expose the MaybeStackArray specialization and 104 * the LocaleAndWeight to code (like the test) that #includes localeprioritylist.h. 105 * Also, otherwise we would have to do a platform-specific 106 * template export declaration of some kind for the MaybeStackArray specialization 107 * to be properly exported from the common DLL. 108 */ 109 struct LocaleAndWeightArray : public UMemory { 110 MaybeStackArray<LocaleAndWeight, 20> array; 111 }; 112 113 LocalePriorityList::LocalePriorityList(StringPiece s, UErrorCode &errorCode) { 114 if (U_FAILURE(errorCode)) { return; } 115 list = new LocaleAndWeightArray(); 116 if (list == nullptr) { 117 errorCode = U_MEMORY_ALLOCATION_ERROR; 118 return; 119 } 120 const char *p = s.data(); 121 const char *limit = p + s.length(); 122 while ((p = skipSpaces(p, limit)) != limit) { 123 if (*p == ',') { // empty range field 124 ++p; 125 continue; 126 } 127 int32_t tagLength = findTagLength(p, limit); 128 if (tagLength == 0) { 129 errorCode = U_ILLEGAL_ARGUMENT_ERROR; 130 return; 131 } 132 CharString tag(p, tagLength, errorCode); 133 if (U_FAILURE(errorCode)) { return; } 134 Locale locale = Locale(tag.data()); 135 if (locale.isBogus()) { 136 errorCode = U_ILLEGAL_ARGUMENT_ERROR; 137 return; 138 } 139 int32_t weight = WEIGHT_ONE; 140 if ((p = skipSpaces(p + tagLength, limit)) != limit && *p == ';') { 141 if ((p = skipSpaces(p + 1, limit)) == limit || *p != 'q' || 142 (p = skipSpaces(p + 1, limit)) == limit || *p != '=' || 143 (++p, (weight = parseWeight(p, limit)) < 0)) { 144 errorCode = U_ILLEGAL_ARGUMENT_ERROR; 145 return; 146 } 147 p = skipSpaces(p, limit); 148 } 149 if (p != limit && *p != ',') { // trailing junk 150 errorCode = U_ILLEGAL_ARGUMENT_ERROR; 151 return; 152 } 153 add(locale, weight, errorCode); 154 if (p == limit) { break; } 155 ++p; 156 } 157 sort(errorCode); 158 } 159 160 LocalePriorityList::~LocalePriorityList() { 161 if (list != nullptr) { 162 for (int32_t i = 0; i < listLength; ++i) { 163 delete list->array[i].locale; 164 } 165 delete list; 166 } 167 uhash_close(map); 168 } 169 170 const Locale *LocalePriorityList::localeAt(int32_t i) const { 171 return list->array[i].locale; 172 } 173 174 Locale *LocalePriorityList::orphanLocaleAt(int32_t i) { 175 if (list == nullptr) { return nullptr; } 176 LocaleAndWeight &lw = list->array[i]; 177 Locale *l = lw.locale; 178 lw.locale = nullptr; 179 return l; 180 } 181 182 bool LocalePriorityList::add(const Locale &locale, int32_t weight, UErrorCode &errorCode) { 183 if (U_FAILURE(errorCode)) { return false; } 184 if (map == nullptr) { 185 if (weight <= 0) { return true; } // do not add q=0 186 map = uhash_open(hashLocale, compareLocales, uhash_compareLong, &errorCode); 187 if (U_FAILURE(errorCode)) { return false; } 188 } 189 LocalPointer<Locale> clone; 190 UBool found = false; 191 int32_t index = uhash_getiAndFound(map, &locale, &found); 192 if (found) { 193 // Duplicate: Remove the old item and append it anew. 194 LocaleAndWeight &lw = list->array[index]; 195 clone.adoptInstead(lw.locale); 196 lw.locale = nullptr; 197 lw.weight = 0; 198 ++numRemoved; 199 } 200 if (weight <= 0) { // do not add q=0 201 if (found) { 202 // Not strictly necessary but cleaner. 203 uhash_removei(map, &locale); 204 } 205 return true; 206 } 207 if (clone.isNull()) { 208 clone.adoptInstead(locale.clone()); 209 if (clone.isNull() || (clone->isBogus() && !locale.isBogus())) { 210 errorCode = U_MEMORY_ALLOCATION_ERROR; 211 return false; 212 } 213 } 214 if (listLength == list->array.getCapacity()) { 215 int32_t newCapacity = listLength < 50 ? 100 : 4 * listLength; 216 if (list->array.resize(newCapacity, listLength) == nullptr) { 217 errorCode = U_MEMORY_ALLOCATION_ERROR; 218 return false; 219 } 220 } 221 uhash_putiAllowZero(map, clone.getAlias(), listLength, &errorCode); 222 if (U_FAILURE(errorCode)) { return false; } 223 LocaleAndWeight &lw = list->array[listLength]; 224 lw.locale = clone.orphan(); 225 lw.weight = weight; 226 lw.index = listLength++; 227 if (weight < WEIGHT_ONE) { hasWeights = true; } 228 U_ASSERT(uhash_count(map) == getLength()); 229 return true; 230 } 231 232 void LocalePriorityList::sort(UErrorCode &errorCode) { 233 // Sort by descending weights if there is a mix of weights. 234 // The comparator forces a stable sort via the item index. 235 if (U_FAILURE(errorCode) || getLength() <= 1 || !hasWeights) { return; } 236 uprv_sortArray(list->array.getAlias(), listLength, sizeof(LocaleAndWeight), 237 compareLocaleAndWeight, nullptr, false, &errorCode); 238 } 239 240 U_NAMESPACE_END