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