com.ibm.icu.util.LocalePriorityList Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of icu4j Show documentation
Show all versions of icu4j Show documentation
International Component for Unicode for Java (ICU4J) is a mature, widely used Java library
providing Unicode and Globalization support
/*
*******************************************************************************
* Copyright (C) 2010-2011, Google, Inc.; International Business Machines *
* Corporation and others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.util;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Provides an immutable list of languages (locales) in priority order.
* The string format is based on the Accept-Language format
* {@link "http://www.ietf.org/rfc/rfc2616.txt"}, such as
* "af, en, fr;q=0.9". Syntactically it is slightly
* more lenient, in allowing extra whitespace between elements, extra commas,
* and more than 3 decimals (on input), and pins between 0 and 1.
* In theory, Accept-Language indicates the relative 'quality' of each item,
* but in practice, all of the browsers just take an ordered list, like
* "en, fr, de", and synthesize arbitrary quality values that put these in the
* right order, like: "en, fr;q=0.7, de;q=0.3". The quality values in these de facto
* semantics thus have nothing to do with the relative qualities of the
* original. Accept-Language also doesn't
* specify the interpretation of multiple instances, eg what "en, fr, en;q=.5"
* means.
*
There are various ways to build a LanguagePriorityList, such
* as using the following equivalent patterns:
*
*
* list = LanguagePriorityList.add("af, en, fr;q=0.9").build();
*
* list2 = LanguagePriorityList
* .add(ULocale.forString("af"))
* .add(ULocale.ENGLISH)
* .add(ULocale.FRENCH, 0.9d)
* .build();
*
* When the list is built, the internal values are sorted in descending order by
* weight, and then by input order. That is, if two languages have the same weight, the first one in the original order
* comes first. If exactly the same language tag appears multiple times,
* the last one wins.
*
* 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:
* en;q=1.0
* de;q=1.0
* fr;q=0.7
* ja;q=0.3
* If it is off (the default), then all weights are reset to 1.0 after reordering.
* This is to match the effect of the Accept-Language semantics as used in browsers, and results in the following:
* * en;q=1.0
* de;q=1.0
* fr;q=1.0
* ja;q=1.0
* @author [email protected]
* @stable ICU 4.4
*/
public class LocalePriorityList implements Iterable {
private static final double D0 = 0.0d;
private static final Double D1 = 1.0d;
private static final Pattern languageSplitter = Pattern.compile("\\s*,\\s*");
private static final Pattern weightSplitter = Pattern
.compile("\\s*(\\S*)\\s*;\\s*q\\s*=\\s*(\\S*)");
private final Map languagesAndWeights;
/**
* Add a language code to the list being built, with weight 1.0.
*
* @param languageCode locale/language to be added
* @return internal builder, for chaining
* @stable ICU 4.4
*/
public static Builder add(ULocale languageCode) {
return new Builder().add(languageCode);
}
/**
* Add a language code to the list being built, with specified weight.
*
* @param languageCode locale/language to be added
* @param weight value from 0.0 to 1.0
* @return internal builder, for chaining
* @stable ICU 4.4
*/
public static Builder add(ULocale languageCode, final double weight) {
return new Builder().add(languageCode, weight);
}
/**
* Add a language priority list.
*
* @param languagePriorityList list to add all the members of
* @return internal builder, for chaining
* @stable ICU 4.4
*/
public static Builder add(LocalePriorityList languagePriorityList) {
return new Builder().add(languagePriorityList);
}
/**
* Add language codes to the list being built, using a string in rfc2616
* (lenient) format, where each language is a valid {@link ULocale}.
*
* @param acceptLanguageString String in rfc2616 format (but leniently parsed)
* @return internal builder, for chaining
* @stable ICU 4.4
*/
public static Builder add(String acceptLanguageString) {
return new Builder().add(acceptLanguageString);
}
/**
* Return the weight for a given language, or null if there is none. Note that
* the weights may be adjusted from those used to build the list.
*
* @param language to get weight of
* @return weight
* @stable ICU 4.4
*/
public Double getWeight(ULocale language) {
return languagesAndWeights.get(language);
}
/**
* {@inheritDoc}
* @stable ICU 4.4
*/
@Override
public String toString() {
final StringBuilder result = new StringBuilder();
for (final ULocale language : languagesAndWeights.keySet()) {
if (result.length() != 0) {
result.append(", ");
}
result.append(language);
double weight = languagesAndWeights.get(language);
if (weight != D1) {
result.append(";q=").append(weight);
}
}
return result.toString();
}
/**
* {@inheritDoc}
* @stable ICU 4.4
*/
public Iterator iterator() {
return languagesAndWeights.keySet().iterator();
}
/**
* {@inheritDoc}
* @stable ICU 4.4
*/
@Override
public boolean equals(final Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
try {
final LocalePriorityList that = (LocalePriorityList) o;
return languagesAndWeights.equals(that.languagesAndWeights);
} catch (final RuntimeException e) {
return false;
}
}
/**
* {@inheritDoc}
* @stable ICU 4.4
*/
@Override
public int hashCode() {
return languagesAndWeights.hashCode();
}
// ==================== Privates ====================
private LocalePriorityList(final Map languageToWeight) {
this.languagesAndWeights = languageToWeight;
}
/**
* Class used for building LanguagePriorityLists
* @stable ICU 4.4
*/
public static class Builder {
/**
* These store the input languages and weights, in chronological order,
* where later additions override previous ones.
*/
private final Map languageToWeight
= new LinkedHashMap();
/*
* Private constructor, only used by LocalePriorityList
*/
private Builder() {
}
/**
* Creates a LocalePriorityList. This is equivalent to
* {@link Builder#build(boolean) Builder.build(false)}.
*
* @return A LocalePriorityList
* @stable ICU 4.4
*/
public LocalePriorityList build() {
return build(false);
}
/**
* Creates a LocalePriorityList.
*
* @param preserveWeights when true, the weights originally came
* from a language priority list specified by add() are preserved.
* @return A LocalePriorityList
* @stable ICU 4.4
*/
public LocalePriorityList build(boolean preserveWeights) {
// Walk through the input list, collecting the items with the same weights.
final Map> doubleCheck = new TreeMap>(
myDescendingDouble);
for (final ULocale lang : languageToWeight.keySet()) {
Double weight = languageToWeight.get(lang);
Set s = doubleCheck.get(weight);
if (s == null) {
doubleCheck.put(weight, s = new LinkedHashSet());
}
s.add(lang);
}
// We now have a bunch of items sorted by weight, then chronologically.
// We can now create a list in the right order
final Map temp = new LinkedHashMap();
for (Entry> langEntry : doubleCheck.entrySet()) {
final Double weight = langEntry.getKey();
for (final ULocale lang : langEntry.getValue()) {
temp.put(lang, preserveWeights ? weight : D1);
}
}
return new LocalePriorityList(Collections.unmodifiableMap(temp));
}
/**
* Adds a LocalePriorityList
*
* @param languagePriorityList a LocalePriorityList
* @return this, for chaining
* @stable ICU 4.4
*/
public Builder add(
final LocalePriorityList languagePriorityList) {
for (final ULocale language : languagePriorityList.languagesAndWeights
.keySet()) {
add(language, languagePriorityList.languagesAndWeights.get(language));
}
return this;
}
/**
* Adds a new language code, with weight = 1.0.
*
* @param languageCode to add with weight 1.0
* @return this, for chaining
* @stable ICU 4.4
*/
public Builder add(final ULocale languageCode) {
return add(languageCode, D1);
}
/**
* Adds language codes, with each having weight = 1.0.
*
* @param languageCodes List of language codes.
* @return this, for chaining.
* @stable ICU 4.4
*/
public Builder add(ULocale... languageCodes) {
for (final ULocale languageCode : languageCodes) {
add(languageCode, D1);
}
return this;
}
/**
* Adds a new supported languageCode, with specified weight. Overrides any
* previous weight for the language.
*
* @param languageCode language/locale to add
* @param weight value between 0.0 and 1.1
* @return this, for chaining.
* @stable ICU 4.4
*/
public Builder add(final ULocale languageCode,
double weight) {
if (languageToWeight.containsKey(languageCode)) {
languageToWeight.remove(languageCode);
}
if (weight <= D0) {
return this; // skip zeros
} else if (weight > D1) {
weight = D1;
}
languageToWeight.put(languageCode, weight);
return this;
}
/**
* Adds rfc2616 list.
*
* @param acceptLanguageList in rfc2616 format
* @return this, for chaining.
* @stable ICU 4.4
*/
public Builder add(final String acceptLanguageList) {
final String[] items = languageSplitter.split(acceptLanguageList.trim());
final Matcher itemMatcher = weightSplitter.matcher("");
for (final String item : items) {
if (itemMatcher.reset(item).matches()) {
final ULocale language = new ULocale(itemMatcher.group(1));
final double weight = Double.parseDouble(itemMatcher.group(2));
if (!(weight >= D0 && weight <= D1)) { // do ! for NaN
throw new IllegalArgumentException("Illegal weight, must be 0..1: "
+ weight);
}
add(language, weight);
} else if (item.length() != 0) {
add(new ULocale(item));
}
}
return this;
}
}
private static Comparator myDescendingDouble = new Comparator() {
public int compare(Double o1, Double o2) {
return -o1.compareTo(o2);
}
};
}