• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2016 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 /*
5  *******************************************************************************
6  * Copyright (C) 2010-2016, Google, Inc.; International Business Machines      *
7  * Corporation and others. All Rights Reserved.                                *
8  *******************************************************************************
9  */
10 
11 package ohos.global.icu.util;
12 
13 import java.util.Collections;
14 import java.util.Comparator;
15 import java.util.Iterator;
16 import java.util.LinkedHashMap;
17 import java.util.LinkedList;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 import java.util.Set;
22 import java.util.TreeMap;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25 
26 /**
27  * Provides an immutable list of languages/locales in priority order.
28  * The string format is based on the Accept-Language format
29  * (<a href="https://tools.ietf.org/html/rfc2616#section-14.4">RFC 2616 Section 14.4</a>), such as
30  * "af, en, fr;q=0.9". Syntactically it is slightly
31  * more lenient, in allowing extra whitespace between elements, extra commas,
32  * and more than 3 decimals (on input). The qvalues must be between 0 and 1.
33  *
34  * <p>In theory, Accept-Language indicates the relative 'quality' of each item,
35  * but in practice, all of the browsers just take an ordered list, like
36  * "en, fr, de", and synthesize arbitrary quality values that put these in the
37  * right order, like: "en, fr;q=0.7, de;q=0.3". The quality values in these de facto
38  * semantics thus have <b>nothing</b> to do with the relative qualities of the
39  * original. Accept-Language also doesn't
40  * specify the interpretation of multiple instances, eg what "en, fr, en;q=.5"
41  * means.
42  * <p>There are various ways to build a LocalePriorityList, such
43  * as using the following equivalent patterns:
44  *
45  * <pre>
46  * list = LocalePriorityList.add(&quot;af, en, fr;q=0.9&quot;).build();
47  *
48  * list2 = LocalePriorityList
49  *  .add(ULocale.forString(&quot;af&quot;))
50  *  .add(ULocale.ENGLISH)
51  *  .add(ULocale.FRENCH, 0.9d)
52  *  .build();
53  * </pre>
54  * When the list is built, the internal values are sorted in descending order by weight,
55  * and then by input order.
56  * That is, if two languages/locales have the same weight, the first one in the original order comes first.
57  * If exactly the same language tag appears multiple times, the last one wins.
58  *
59  * <p>There are two options when building.
60  * If preserveWeights are on, then "de;q=0.3, ja;q=0.3, en, fr;q=0.7, de " would result in the following:
61  * <pre> en;q=1.0
62  * de;q=1.0
63  * fr;q=0.7
64  * ja;q=0.3</pre>
65  * If it is off (the default), then all weights are reset to 1.0 after reordering.
66  * This is to match the effect of the Accept-Language semantics as used in browsers, and results in the following:
67  *  * <pre> en;q=1.0
68  * de;q=1.0
69  * fr;q=1.0
70  * ja;q=1.0</pre>
71  * @author markdavis@google.com
72  * @hide exposed on OHOS
73  */
74 public class LocalePriorityList implements Iterable<ULocale> {
75     private static final Double D1 = 1.0d;
76 
77     private static final Pattern languageSplitter = Pattern.compile("\\s*,\\s*");
78     private static final Pattern weightSplitter = Pattern
79             .compile("\\s*(\\S*)\\s*;\\s*q\\s*=\\s*(\\S*)");
80     private final Map<ULocale, Double> languagesAndWeights;
81 
82     /**
83      * Creates a Builder and adds locales, each with weight 1.0.
84      *
85      * @param locales locales/languages to be added
86      * @return a new builder with these locales, for chaining
87      */
add(ULocale... locales)88     public static Builder add(ULocale... locales) {
89         return new Builder().add(locales);
90     }
91 
92     /**
93      * Creates a Builder and adds a locale with a specified weight.
94      * A zero or negative weight leads to removing the locale.
95      * A weight greater than 1 is pinned to 1.
96      *
97      * @param locale locale/language to be added
98      * @param weight value from 0.0 to 1.0
99      * @return a new builder with this locale, for chaining
100      */
add(ULocale locale, final double weight)101     public static Builder add(ULocale locale, final double weight) {
102         return new Builder().add(locale, weight);
103     }
104 
105     /**
106      * Creates a Builder and adds locales with weights.
107      *
108      * @param list list of locales with weights
109      * @return a new builder with these locales, for chaining
110      */
add(LocalePriorityList list)111     public static Builder add(LocalePriorityList list) {
112         return new Builder(list);
113     }
114 
115     /**
116      * Creates a Builder, parses the RFC 2616 string, and adds locales with weights accordingly.
117      *
118      * @param acceptLanguageString String in RFC 2616 format (leniently parsed)
119      * @return a new builder with these locales, for chaining
120      */
add(String acceptLanguageString)121     public static Builder add(String acceptLanguageString) {
122         return new Builder().add(acceptLanguageString);
123     }
124 
125     /**
126      * Returns the weight for a given language/locale, or null if there is none.
127      * Note that the weights may be adjusted from those used to build the list.
128      *
129      * @param locale to get weight of
130      * @return weight
131      */
getWeight(ULocale locale)132     public Double getWeight(ULocale locale) {
133         return languagesAndWeights.get(locale);
134     }
135 
136     /**
137      * Returns the locales as an immutable Set view.
138      * The set has the same iteration order as this object itself.
139      *
140      * @return the locales
141      * @hide draft / provisional / internal are hidden on OHOS
142      */
getULocales()143     public Set<ULocale> getULocales() {
144         return languagesAndWeights.keySet();
145     }
146 
147     /**
148      * {@inheritDoc}
149      */
150     @Override
toString()151     public String toString() {
152         final StringBuilder result = new StringBuilder();
153         for (Entry<ULocale, Double> entry : languagesAndWeights.entrySet()) {
154             ULocale language = entry.getKey();
155             double weight = entry.getValue();
156             if (result.length() != 0) {
157                 result.append(", ");
158             }
159             result.append(language);
160             if (weight != 1.0) {
161                 result.append(";q=").append(weight);
162             }
163         }
164         return result.toString();
165     }
166 
167     /**
168      * {@inheritDoc}
169      */
170     @Override
iterator()171     public Iterator<ULocale> iterator() {
172         return languagesAndWeights.keySet().iterator();
173     }
174 
175     /**
176      * {@inheritDoc}
177      */
178     @Override
equals(final Object o)179     public boolean equals(final Object o) {
180         if (o == null) {
181             return false;
182         }
183         if (this == o) {
184             return true;
185         }
186         try {
187             final LocalePriorityList that = (LocalePriorityList) o;
188             return languagesAndWeights.equals(that.languagesAndWeights);
189         } catch (final RuntimeException e) {
190             return false;
191         }
192     }
193 
194     /**
195      * {@inheritDoc}
196      */
197     @Override
hashCode()198     public int hashCode() {
199         return languagesAndWeights.hashCode();
200     }
201 
202     // ==================== Privates ====================
203 
204 
LocalePriorityList(final Map<ULocale, Double> languageToWeight)205     private LocalePriorityList(final Map<ULocale, Double> languageToWeight) {
206         this.languagesAndWeights = languageToWeight;
207     }
208 
209     /**
210      * Class used for building LocalePriorityLists.
211      * @hide exposed on OHOS
212      */
213     public static class Builder {
214         /**
215          * These store the input languages and weights, in chronological order,
216          * where later additions override previous ones.
217          */
218         private Map<ULocale, Double> languageToWeight;
219         /**
220          * The builder is reusable but rarely reused. Avoid cloning the map when not needed.
221          * Exactly one of languageToWeight and built is null.
222          */
223         private LocalePriorityList built;
224         private boolean hasWeights = false;  // other than 1.0
225 
226         /**
227          * Private constructor, only used by LocalePriorityList
228          */
Builder()229         private Builder() {
230             languageToWeight = new LinkedHashMap<>();
231         }
232 
Builder(LocalePriorityList list)233         private Builder(LocalePriorityList list) {
234             built = list;
235             for (Double value : list.languagesAndWeights.values()) {
236                 double weight = value;
237                 assert 0.0 < weight && weight <= 1.0;
238                 if (weight != 1.0) {
239                     hasWeights = true;
240                     break;
241                 }
242             }
243         }
244 
245         /**
246          * Creates a LocalePriorityList.  This is equivalent to
247          * {@link Builder#build(boolean) Builder.build(false)}.
248          *
249          * @return A LocalePriorityList
250          */
251         public LocalePriorityList build() {
252             return build(false);
253         }
254 
255         /**
256          * Creates a LocalePriorityList.
257          *
258          * @param preserveWeights when true, each locale's given weight is preserved.
259          * @return A LocalePriorityList
260          */
261         public LocalePriorityList build(boolean preserveWeights) {
262             if (built != null) {
263                 // Calling build() again without changing anything in between.
264                 // Just return the same immutable list.
265                 return built;
266             }
267             Map<ULocale, Double> temp;
268             if (hasWeights) {
269                 // Walk through the input list, collecting the items with the same weights.
270                 final TreeMap<Double, List<ULocale>> weightToLanguages =
271                         new TreeMap<>(myDescendingDouble);
272                 for (Entry<ULocale, Double> entry : languageToWeight.entrySet()) {
273                     ULocale lang = entry.getKey();
274                     Double weight = entry.getValue();
275                     List<ULocale> s = weightToLanguages.get(weight);
276                     if (s == null) {
277                         weightToLanguages.put(weight, s = new LinkedList<>());
278                     }
279                     s.add(lang);
280                 }
281                 // We now have a bunch of items sorted by weight, then chronologically.
282                 // We can now create a list in the right order.
283                 if (weightToLanguages.size() <= 1) {
284                     // There is at most one weight.
285                     temp = languageToWeight;
286                     if (weightToLanguages.isEmpty() || weightToLanguages.firstKey() == 1.0) {
287                         hasWeights = false;
288                     }
289                 } else {
290                     temp = new LinkedHashMap<>();
291                     for (Entry<Double, List<ULocale>> langEntry : weightToLanguages.entrySet()) {
292                         final Double weight = preserveWeights ? langEntry.getKey() : D1;
293                         for (final ULocale lang : langEntry.getValue()) {
294                             temp.put(lang, weight);
295                         }
296                     }
297                 }
298             } else {
299                 // Nothing to sort.
300                 temp = languageToWeight;
301             }
302             languageToWeight = null;
303             return built = new LocalePriorityList(Collections.unmodifiableMap(temp));
304         }
305 
306         /**
307          * Adds locales with weights.
308          *
309          * @param list list of locales with weights
310          * @return this, for chaining
311          */
312         public Builder add(final LocalePriorityList list) {
313             for (Entry<ULocale, Double> entry : list.languagesAndWeights.entrySet()) {
314                 add(entry.getKey(), entry.getValue());
315             }
316             return this;
317         }
318 
319         /**
320          * Adds a locale with weight 1.0.
321          *
322          * @param locale to add with weight 1.0
323          * @return this, for chaining
324          */
325         public Builder add(final ULocale locale) {
326             return add(locale, 1.0);
327         }
328 
329         /**
330          * Adds locales, each with weight 1.0.
331          *
332          * @param locales locales/languages to be added
333          * @return this, for chaining.
334          */
335         public Builder add(ULocale... locales) {
336             for (final ULocale languageCode : locales) {
337                 add(languageCode, 1.0);
338             }
339             return this;
340         }
341 
342         /**
343          * Adds a locale with a specified weight.
344          * Overrides any previous weight for the locale.
345          * A zero or negative weight leads to removing the locale.
346          * A weight greater than 1 is pinned to 1.
347          *
348          * @param locale language/locale to add
349          * @param weight value between 0.0 and 1.1
350          * @return this, for chaining.
351          */
352         public Builder add(final ULocale locale, double weight) {
353             if (languageToWeight == null) {
354                 // Builder reuse after build().
355                 languageToWeight = new LinkedHashMap<>(built.languagesAndWeights);
356                 built = null;
357             }
358             if (languageToWeight.containsKey(locale)) {
359                 languageToWeight.remove(locale);
360             }
361             Double value;
362             if (weight <= 0.0) {
363                 return this; // skip zeros
364             } else if (weight >= 1.0) {
365                 value = D1;
366             } else {
367                 value = weight;
368                 hasWeights = true;
369             }
370             languageToWeight.put(locale, value);
371             return this;
372         }
373 
374         /**
375          * Parses the RFC 2616 string, and adds locales with weights accordingly.
376          *
377          * @param acceptLanguageList in RFC 2616 format (leniently parsed)
378          * @return this, for chaining.
379          */
380         public Builder add(final String acceptLanguageList) {
381             final String[] items = languageSplitter.split(acceptLanguageList.trim());
382             final Matcher itemMatcher = weightSplitter.matcher("");
383             for (final String item : items) {
384                 if (itemMatcher.reset(item).matches()) {
385                     final ULocale language = new ULocale(itemMatcher.group(1));
386                     final double weight = Double.parseDouble(itemMatcher.group(2));
387                     if (!(0.0 <= weight && weight <= 1.0)) { // do ! for NaN
388                         throw new IllegalArgumentException(
389                                 "Illegal weight, must be 0..1: " + weight);
390                     }
391                     add(language, weight);
392                 } else if (item.length() != 0) {
393                     add(new ULocale(item));
394                 }
395             }
396             return this;
397         }
398     }
399 
400     private static Comparator<Double> myDescendingDouble = new Comparator<Double>() {
401         @Override
402         public int compare(Double o1, Double o2) {
403             int result = o1.compareTo(o2);
404             return result > 0 ? -1 : result < 0 ? 1 : 0; // Reverse the order.
405         }
406     };
407 }
408