12d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert// © 2016 and later: Unicode, Inc. and others.
22d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert// License & terms of use: http://www.unicode.org/copyright.html#License
37935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/*
47935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
5bee65486a185907111f3be60992433e133ec0e32Scott Russell * Copyright (C) 2010-2016, Google, Inc.; International Business Machines      *
67935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Corporation and others. All Rights Reserved.                                *
77935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
87935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
97935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpackage com.ibm.icu.util;
117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Collections;
137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Comparator;
147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Iterator;
157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.LinkedHashMap;
167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.LinkedHashSet;
177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Map;
187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Map.Entry;
197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Set;
207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.TreeMap;
217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.regex.Matcher;
227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.regex.Pattern;
237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/**
257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Provides an immutable list of languages (locales) in priority order.
267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * The string format is based on the Accept-Language format
27bee65486a185907111f3be60992433e133ec0e32Scott Russell * <a href="http://www.ietf.org/rfc/rfc2616.txt">http://www.ietf.org/rfc/rfc2616.txt</a>, such as
287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * "af, en, fr;q=0.9". Syntactically it is slightly
297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * more lenient, in allowing extra whitespace between elements, extra commas,
307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * and more than 3 decimals (on input), and pins between 0 and 1.
317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>In theory, Accept-Language indicates the relative 'quality' of each item,
327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * but in practice, all of the browsers just take an ordered list, like
337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * "en, fr, de", and synthesize arbitrary quality values that put these in the
347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * right order, like: "en, fr;q=0.7, de;q=0.3". The quality values in these de facto
357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * semantics thus have <b>nothing</b> to do with the relative qualities of the
367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * original. Accept-Language also doesn't
377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * specify the interpretation of multiple instances, eg what "en, fr, en;q=.5"
387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * means.
397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>There are various ways to build a LanguagePriorityList, such
407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * as using the following equivalent patterns:
417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <pre>
437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * list = LanguagePriorityList.add(&quot;af, en, fr;q=0.9&quot;).build();
447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * list2 = LanguagePriorityList
467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *  .add(ULocale.forString(&quot;af&quot;))
477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *  .add(ULocale.ENGLISH)
487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *  .add(ULocale.FRENCH, 0.9d)
497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *  .build();
507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * </pre>
517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * When the list is built, the internal values are sorted in descending order by
527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * weight, and then by input order. That is, if two languages have the same weight, the first one in the original order
537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * comes first. If exactly the same language tag appears multiple times,
547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * the last one wins.
557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * 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:
577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <pre> en;q=1.0
587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * de;q=1.0
597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * fr;q=0.7
607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * ja;q=0.3</pre>
617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * If it is off (the default), then all weights are reset to 1.0 after reordering.
627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * This is to match the effect of the Accept-Language semantics as used in browsers, and results in the following:
637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *  * <pre> en;q=1.0
647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * de;q=1.0
657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * fr;q=1.0
667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * ja;q=1.0</pre>
677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @author markdavis@google.com
687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @stable ICU 4.4
697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpublic class LocalePriorityList implements Iterable<ULocale> {
717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final double D0 = 0.0d;
727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final Double D1 = 1.0d;
737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final Pattern languageSplitter = Pattern.compile("\\s*,\\s*");
757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final Pattern weightSplitter = Pattern
767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    .compile("\\s*(\\S*)\\s*;\\s*q\\s*=\\s*(\\S*)");
777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private final Map<ULocale, Double> languagesAndWeights;
787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Add a language code to the list being built, with weight 1.0.
817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param languageCode locale/language to be added
837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return internal builder, for chaining
847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.4
857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static Builder add(ULocale... languageCode) {
877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new Builder().add(languageCode);
887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Add a language code to the list being built, with specified weight.
927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param languageCode locale/language to be added
947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param weight value from 0.0 to 1.0
957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return internal builder, for chaining
967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.4
977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static Builder add(ULocale languageCode, final double weight) {
997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new Builder().add(languageCode, weight);
1007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Add a language priority list.
1047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
1057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param languagePriorityList list to add all the members of
1067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return internal builder, for chaining
1077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.4
1087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static Builder add(LocalePriorityList languagePriorityList) {
1107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new Builder().add(languagePriorityList);
1117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Add language codes to the list being built, using a string in rfc2616
1157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * (lenient) format, where each language is a valid {@link ULocale}.
1167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
1177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param acceptLanguageString String in rfc2616 format (but leniently parsed)
1187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return internal builder, for chaining
1197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.4
1207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static Builder add(String acceptLanguageString) {
1227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new Builder().add(acceptLanguageString);
1237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Return the weight for a given language, or null if there is none. Note that
1277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the weights may be adjusted from those used to build the list.
1287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
1297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param language to get weight of
1307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return weight
1317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.4
1327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Double getWeight(ULocale language) {
1347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return languagesAndWeights.get(language);
1357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@inheritDoc}
1397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.4
1407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Override
1427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public String toString() {
1437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        final StringBuilder result = new StringBuilder();
1447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (final ULocale language : languagesAndWeights.keySet()) {
1457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (result.length() != 0) {
1467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                result.append(", ");
1477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            result.append(language);
1497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            double weight = languagesAndWeights.get(language);
1507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (weight != D1) {
1517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                result.append(";q=").append(weight);
1527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return result.toString();
1557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@inheritDoc}
1597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.4
1607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Iterator<ULocale> iterator() {
1627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return languagesAndWeights.keySet().iterator();
1637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@inheritDoc}
1677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.4
1687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Override
1707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public boolean equals(final Object o) {
1717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (o == null) {
1727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return false;
1737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (this == o) {
1757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return true;
1767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        try {
1787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            final LocalePriorityList that = (LocalePriorityList) o;
1797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return languagesAndWeights.equals(that.languagesAndWeights);
1807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } catch (final RuntimeException e) {
1817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return false;
1827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
1867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@inheritDoc}
1877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.4
1887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Override
1907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public int hashCode() {
1917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return languagesAndWeights.hashCode();
1927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // ==================== Privates ====================
1957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private LocalePriorityList(final Map<ULocale, Double> languageToWeight) {
1987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.languagesAndWeights = languageToWeight;
1997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
2027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Class used for building LanguagePriorityLists
2037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.4
2047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
2057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static class Builder {
2067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
2077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * These store the input languages and weights, in chronological order,
2087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * where later additions override previous ones.
2097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
2107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private final Map<ULocale, Double> languageToWeight
2117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        = new LinkedHashMap<ULocale, Double>();
2127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /*
2147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Private constructor, only used by LocalePriorityList
2157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
2167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private Builder() {
2177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
2207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Creates a LocalePriorityList.  This is equivalent to
2217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * {@link Builder#build(boolean) Builder.build(false)}.
2227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
2237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @return A LocalePriorityList
2247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @stable ICU 4.4
2257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
2267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public LocalePriorityList build() {
2277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return build(false);
2287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
2317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Creates a LocalePriorityList.
2327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
2337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @param preserveWeights when true, the weights originally came
2347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * from a language priority list specified by add() are preserved.
2357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @return A LocalePriorityList
2367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @stable ICU 4.4
2377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
2387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public LocalePriorityList build(boolean preserveWeights) {
2397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Walk through the input list, collecting the items with the same weights.
2407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            final Map<Double, Set<ULocale>> doubleCheck = new TreeMap<Double, Set<ULocale>>(
2417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    myDescendingDouble);
2427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (final ULocale lang : languageToWeight.keySet()) {
2437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Double weight = languageToWeight.get(lang);
2447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Set<ULocale> s = doubleCheck.get(weight);
2457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (s == null) {
2467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    doubleCheck.put(weight, s = new LinkedHashSet<ULocale>());
2477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
2487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                s.add(lang);
2497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // We now have a bunch of items sorted by weight, then chronologically.
2517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // We can now create a list in the right order
2527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            final Map<ULocale, Double> temp = new LinkedHashMap<ULocale, Double>();
2537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (Entry<Double, Set<ULocale>> langEntry : doubleCheck.entrySet()) {
2547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                final Double weight = langEntry.getKey();
2557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                for (final ULocale lang : langEntry.getValue()) {
2567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    temp.put(lang, preserveWeights ? weight : D1);
2577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
2587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return new LocalePriorityList(Collections.unmodifiableMap(temp));
2607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
2637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Adds a LocalePriorityList
2647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
2657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @param languagePriorityList a LocalePriorityList
2667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @return this, for chaining
2677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @stable ICU 4.4
2687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
2697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public Builder add(
2707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                final LocalePriorityList languagePriorityList) {
2717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (final ULocale language : languagePriorityList.languagesAndWeights
2727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    .keySet()) {
2737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                add(language, languagePriorityList.languagesAndWeights.get(language));
2747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return this;
2767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
2797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Adds a new language code, with weight = 1.0.
2807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
2817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @param languageCode to add with weight 1.0
2827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @return this, for chaining
2837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @stable ICU 4.4
2847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
2857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public Builder add(final ULocale languageCode) {
2867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return add(languageCode, D1);
2877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
2907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Adds language codes, with each having weight = 1.0.
2917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
2927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @param languageCodes List of language codes.
2937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @return this, for chaining.
2947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @stable ICU 4.4
2957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
2967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public Builder add(ULocale... languageCodes) {
2977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (final ULocale languageCode : languageCodes) {
2987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                add(languageCode, D1);
2997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return this;
3017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
3047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Adds a new supported languageCode, with specified weight. Overrides any
3057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * previous weight for the language.
3067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
3077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @param languageCode language/locale to add
3087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @param weight value between 0.0 and 1.1
3097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @return this, for chaining.
3107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @stable ICU 4.4
3117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
3127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public Builder add(final ULocale languageCode,
3137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                double weight) {
3147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (languageToWeight.containsKey(languageCode)) {
3157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                languageToWeight.remove(languageCode);
3167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (weight <= D0) {
3187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return this; // skip zeros
3197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if (weight > D1) {
3207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                weight = D1;
3217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            languageToWeight.put(languageCode, weight);
3237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return this;
3247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
3277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Adds rfc2616 list.
3287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
3297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @param acceptLanguageList in rfc2616 format
3307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @return this, for chaining.
3317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @stable ICU 4.4
3327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
3337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public Builder add(final String acceptLanguageList) {
3347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            final String[] items = languageSplitter.split(acceptLanguageList.trim());
3357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            final Matcher itemMatcher = weightSplitter.matcher("");
3367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (final String item : items) {
3377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (itemMatcher.reset(item).matches()) {
3387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    final ULocale language = new ULocale(itemMatcher.group(1));
3397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    final double weight = Double.parseDouble(itemMatcher.group(2));
3407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (!(weight >= D0 && weight <= D1)) { // do ! for NaN
3417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        throw new IllegalArgumentException("Illegal weight, must be 0..1: "
3427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                + weight);
3437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
3447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    add(language, weight);
3457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else if (item.length() != 0) {
3467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    add(new ULocale(item));
3477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return this;
3507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static Comparator<Double> myDescendingDouble = new Comparator<Double>() {
3547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int compare(Double o1, Double o2) {
3552d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            int result = o1.compareTo(o2);
3562d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert            return result > 0 ? -1 : result < 0 ? 1 : 0; // Reverse the order.
3577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    };
3597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert}
360