]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_8_1_1/main/classes/core/src/com/ibm/icu/util/LocalePriorityList.java
Added flags.
[Dictionary.git] / jars / icu4j-4_8_1_1 / main / classes / core / src / com / ibm / icu / util / LocalePriorityList.java
1 /*
2  ****************************************************************************************
3  * Copyright (C) 2010, Google, Inc.; International Business Machines Corporation and    *
4  * others. All Rights Reserved.                                                         *
5  ****************************************************************************************
6  */
7
8 package com.ibm.icu.util;
9
10 import java.util.Collections;
11 import java.util.Comparator;
12 import java.util.Iterator;
13 import java.util.LinkedHashMap;
14 import java.util.LinkedHashSet;
15 import java.util.Map;
16 import java.util.Set;
17 import java.util.TreeMap;
18 import java.util.regex.Matcher;
19 import java.util.regex.Pattern;
20
21 /**
22  * Provides an immutable list of languages (locales) in priority order.
23  * The string format is based on the Accept-Language format 
24  * {@link "http://www.ietf.org/rfc/rfc2616.txt"}, such as 
25  * "af, en, fr;q=0.9". Syntactically it is slightly
26  * more lenient, in allowing extra whitespace between elements, extra commas,
27  * and more than 3 decimals (on input), and pins between 0 and 1.
28  * <p>In theory, Accept-Language indicates the relative 'quality' of each item,
29  * but in practice, all of the browsers just take an ordered list, like 
30  * "en, fr, de", and synthesize arbitrary quality values that put these in the
31  * right order, like: "en, fr;q=0.7, de;q=0.3". The quality values in these de facto
32  * semantics thus have <b>nothing</b> to do with the relative qualities of the
33  * original. Accept-Language also doesn't
34  * specify the interpretation of multiple instances, eg what "en, fr, en;q=.5"
35  * means.
36  * <p>There are various ways to build a LanguagePriorityList, such
37  * as using the following equivalent patterns:
38  * 
39  * <pre>
40  * list = LanguagePriorityList.add(&quot;af, en, fr;q=0.9&quot;).build();
41  * 
42  * list2 = LanguagePriorityList
43  *  .add(ULocale.forString(&quot;af&quot;))
44  *  .add(ULocale.ENGLISH)
45  *  .add(ULocale.FRENCH, 0.9d)
46  *  .build();
47  * </pre>
48  * When the list is built, the internal values are sorted in descending order by
49  * weight, and then by input order. That is, if two languages have the same weight, the first one in the original order
50  * comes first. If exactly the same language tag appears multiple times,
51  * the last one wins. 
52  * 
53  * There are two options when building. If preserveWeights are on, then "de;q=0.3, ja;q=0.3, en, fr;q=0.7, de " would result in the following:
54  * <pre> en;q=1.0
55  * de;q=1.0
56  * fr;q=0.7
57  * ja;q=0.3</pre>
58  * If it is off (the default), then all weights are reset to 1.0 after reordering. 
59  * This is to match the effect of the Accept-Language semantics as used in browsers, and results in the following:
60  *  * <pre> en;q=1.0
61  * de;q=1.0
62  * fr;q=1.0
63  * ja;q=1.0</pre>
64  * @author markdavis@google.com
65  * @stable ICU 4.4
66  */
67 public class LocalePriorityList implements Iterable<ULocale> {
68     private static final double D0 = 0.0d;
69     private static final Double D1 = 1.0d;
70
71     private static final Pattern languageSplitter = Pattern.compile("\\s*,\\s*");
72     private static final Pattern weightSplitter = Pattern
73     .compile("\\s*(\\S*)\\s*;\\s*q\\s*=\\s*(\\S*)");
74     private final Map<ULocale, Double> languagesAndWeights;
75
76     /**
77      * Add a language code to the list being built, with weight 1.0.
78      * 
79      * @param languageCode locale/language to be added
80      * @return internal builder, for chaining
81      * @stable ICU 4.4
82      */
83     public static Builder add(ULocale languageCode) {
84         return new Builder().add(languageCode);
85     }
86
87     /**
88      * Add a language code to the list being built, with specified weight.
89      * 
90      * @param languageCode locale/language to be added
91      * @param weight value from 0.0 to 1.0
92      * @return internal builder, for chaining
93      * @stable ICU 4.4
94      */
95     public static Builder add(ULocale languageCode, final double weight) {
96         return new Builder().add(languageCode, weight);
97     }
98
99     /**
100      * Add a language priority list.
101      * 
102      * @param languagePriorityList list to add all the members of
103      * @return internal builder, for chaining
104      * @stable ICU 4.4
105      */
106     public static Builder add(LocalePriorityList languagePriorityList) {
107         return new Builder().add(languagePriorityList);
108     }
109
110     /**
111      * Add language codes to the list being built, using a string in rfc2616
112      * (lenient) format, where each language is a valid {@link ULocale}.
113      * 
114      * @param acceptLanguageString String in rfc2616 format (but leniently parsed)
115      * @return internal builder, for chaining
116      * @stable ICU 4.4
117      */
118     public static Builder add(String acceptLanguageString) {
119         return new Builder().add(acceptLanguageString);
120     }
121
122     /**
123      * Return the weight for a given language, or null if there is none. Note that
124      * the weights may be adjusted from those used to build the list.
125      * 
126      * @param language to get weight of
127      * @return weight
128      * @stable ICU 4.4
129      */
130     public Double getWeight(ULocale language) {
131         return languagesAndWeights.get(language);
132     }
133
134     /**
135      * {@inheritDoc}
136      * @stable ICU 4.4
137      */
138     @Override
139     public String toString() {
140         final StringBuilder result = new StringBuilder();
141         for (final ULocale language : languagesAndWeights.keySet()) {
142             if (result.length() != 0) {
143                 result.append(", ");
144             }
145             result.append(language);
146             double weight = languagesAndWeights.get(language);
147             if (weight != D1) {
148                 result.append(";q=").append(weight);
149             }
150         }
151         return result.toString();
152     }
153
154     /**
155      * {@inheritDoc}
156      * @stable ICU 4.4
157      */
158     public Iterator<ULocale> iterator() {
159         return languagesAndWeights.keySet().iterator();
160     }
161
162     /**
163      * {@inheritDoc}
164      * @stable ICU 4.4
165      */
166     @Override
167     public boolean equals(final Object o) {
168         try {
169             final LocalePriorityList that = (LocalePriorityList) o;
170             return languagesAndWeights.equals(that.languagesAndWeights);
171         } catch (final RuntimeException e) {
172             return false;
173         }
174     }
175
176     /**
177      * {@inheritDoc}
178      * @stable ICU 4.4
179      */
180     @Override
181     public int hashCode() {
182         return languagesAndWeights.hashCode();
183     }
184
185     // ==================== Privates ====================
186
187
188     private LocalePriorityList(final Map<ULocale, Double> languageToWeight) {
189         this.languagesAndWeights = languageToWeight;
190     }
191
192     /**
193      * Class used for building LanguagePriorityLists
194      * @stable ICU 4.4
195      */
196     public static class Builder {
197         /**
198          * These store the input languages and weights, in chronological order,
199          * where later additions override previous ones.
200          */
201         private final Map<ULocale, Double> languageToWeight 
202         = new LinkedHashMap<ULocale, Double>();
203
204         /*
205          * Private constructor, only used by LocalePriorityList
206          */
207         private Builder() {
208         }
209
210         /**
211          * Creates a LocalePriorityList.  This is equivalent to
212          * {@link Builder#build(boolean) Builder.build(false)}.
213          * 
214          * @return A LocalePriorityList
215          * @stable ICU 4.4
216          */
217         public LocalePriorityList build() {
218             return build(false);
219         }
220
221         /**
222          * Creates a LocalePriorityList.
223          * 
224          * @param preserveWeights when true, the weights originally came
225          * from a language priority list specified by add() are preserved.
226          * @return A LocalePriorityList
227          * @stable ICU 4.4
228          */
229         public LocalePriorityList build(boolean preserveWeights) {
230             // Walk through the input list, collecting the items with the same weights.
231             final Map<Double, Set<ULocale>> doubleCheck = new TreeMap<Double, Set<ULocale>>(
232                     myDescendingDouble);
233             for (final ULocale lang : languageToWeight.keySet()) {
234                 Double weight = languageToWeight.get(lang);
235                 Set<ULocale> s = doubleCheck.get(weight);
236                 if (s == null) {
237                     doubleCheck.put(weight, s = new LinkedHashSet<ULocale>());
238                 }
239                 s.add(lang);
240             }
241             // We now have a bunch of items sorted by weight, then chronologically.
242             // We can now create a list in the right order
243             final Map<ULocale, Double> temp = new LinkedHashMap<ULocale, Double>();
244             for (final Double weight : doubleCheck.keySet()) {
245                 for (final ULocale lang : doubleCheck.get(weight)) {
246                     temp.put(lang, preserveWeights ? weight : D1);
247                 }
248             }
249             return new LocalePriorityList(Collections.unmodifiableMap(temp));
250         }
251
252         /**
253          * Adds a LocalePriorityList
254          * 
255          * @param languagePriorityList a LocalePriorityList
256          * @return this, for chaining
257          * @stable ICU 4.4
258          */
259         public Builder add(
260                 final LocalePriorityList languagePriorityList) {
261             for (final ULocale language : languagePriorityList.languagesAndWeights
262                     .keySet()) {
263                 add(language, languagePriorityList.languagesAndWeights.get(language));
264             }
265             return this;
266         }
267
268         /**
269          * Adds a new language code, with weight = 1.0.
270          * 
271          * @param languageCode to add with weight 1.0
272          * @return this, for chaining
273          * @stable ICU 4.4
274          */
275         public Builder add(final ULocale languageCode) {
276             return add(languageCode, D1);
277         }
278
279         /**
280          * Adds language codes, with each having weight = 1.0.
281          * 
282          * @param languageCodes List of language codes.
283          * @return this, for chaining.
284          * @stable ICU 4.4
285          */
286         public Builder add(ULocale... languageCodes) {
287             for (final ULocale languageCode : languageCodes) {
288                 add(languageCode, D1);
289             }
290             return this;
291         }
292
293         /**
294          * Adds a new supported languageCode, with specified weight. Overrides any
295          * previous weight for the language.
296          * 
297          * @param languageCode language/locale to add
298          * @param weight value between 0.0 and 1.1
299          * @return this, for chaining.
300          * @stable ICU 4.4
301          */
302         public Builder add(final ULocale languageCode,
303                 double weight) {
304             if (languageToWeight.containsKey(languageCode)) {
305                 languageToWeight.remove(languageCode);
306             }
307             if (weight <= D0) {
308                 return this; // skip zeros
309             } else if (weight > D1) {
310                 weight = D1;
311             }
312             languageToWeight.put(languageCode, weight);
313             return this;
314         }
315
316         /**
317          * Adds rfc2616 list.
318          * 
319          * @param acceptLanguageList in rfc2616 format
320          * @return this, for chaining.
321          * @stable ICU 4.4
322          */
323         public Builder add(final String acceptLanguageList) {
324             final String[] items = languageSplitter.split(acceptLanguageList.trim());
325             final Matcher itemMatcher = weightSplitter.matcher("");
326             for (final String item : items) {
327                 if (itemMatcher.reset(item).matches()) {
328                     final ULocale language = new ULocale(itemMatcher.group(1));
329                     final double weight = Double.parseDouble(itemMatcher.group(2));
330                     if (!(weight >= D0 && weight <= D1)) { // do ! for NaN
331                         throw new IllegalArgumentException("Illegal weight, must be 0..1: "
332                                 + weight);
333                     }
334                     add(language, weight);
335                 } else if (item.length() != 0) {
336                     add(new ULocale(item));
337                 }
338             }
339             return this;
340         }
341     }
342
343     private static Comparator<Double> myDescendingDouble = new Comparator<Double>() {
344         public int compare(Double o1, Double o2) {
345             return -o1.compareTo(o2);
346         }
347     };
348 }