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