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 
hashLocale(const UHashTok token)23 int32_t hashLocale(const UHashTok token) {
24     auto *locale = static_cast<const Locale *>(token.pointer);
25     return locale->hashCode();
26 }
27 
compareLocales(const UHashTok t1,const UHashTok t2)28 UBool compareLocales(const UHashTok t1, const UHashTok t2) {
29     auto *l1 = static_cast<const Locale *>(t1.pointer);
30     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 
compare__anon185549380111::LocaleAndWeight41     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
compareLocaleAndWeight(const void *,const void * left,const void * right)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 
skipSpaces(const char * p,const char * limit)54 const char *skipSpaces(const char *p, const char *limit) {
55     while (p < limit && *p == ' ') { ++p; }
56     return p;
57 }
58 
findTagLength(const char * p,const char * limit)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  */
parseWeight(const char * & p,const char * limit)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 
LocalePriorityList(StringPiece s,UErrorCode & errorCode)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 
~LocalePriorityList()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 
localeAt(int32_t i) const170 const Locale *LocalePriorityList::localeAt(int32_t i) const {
171     return list->array[i].locale;
172 }
173 
orphanLocaleAt(int32_t i)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 
add(const Locale & locale,int32_t weight,UErrorCode & errorCode)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 
sort(UErrorCode & errorCode)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
241