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