提交 ab9e1ef1 编写于 作者: P peytoia

7069824: Support for BCP47 locale matching

Reviewed-by: naoto, okutsu
上级 05cd8405
......@@ -55,6 +55,7 @@ import sun.util.locale.BaseLocale;
import sun.util.locale.InternalLocaleBuilder;
import sun.util.locale.LanguageTag;
import sun.util.locale.LocaleExtensions;
import sun.util.locale.LocaleMatcher;
import sun.util.locale.LocaleObjectCache;
import sun.util.locale.LocaleSyntaxException;
import sun.util.locale.LocaleUtils;
......@@ -71,10 +72,11 @@ import sun.util.resources.OpenListResourceBundle;
* according to the customs and conventions of the user's native country,
* region, or culture.
*
* <p> The <code>Locale</code> class implements identifiers
* interchangeable with BCP 47 (IETF BCP 47, "Tags for Identifying
* Languages"), with support for the LDML (UTS#35, "Unicode Locale
* Data Markup Language") BCP 47-compatible extensions for locale data
* <p> The {@code Locale} class implements IETF BCP 47 which is composed of
* <a href="http://tools.ietf.org/html/rfc4647">RFC 4647 "Matching of Language
* Tags"</a> and <a href="http://tools.ietf.org/html/rfc5646">RFC 5646 "Tags
* for Identifying Languages"</a> with support for the LDML (UTS#35, "Unicode
* Locale Data Markup Language") BCP 47-compatible extensions for locale data
* exchange.
*
* <p> A <code>Locale</code> object logically consists of the fields
......@@ -267,6 +269,77 @@ import sun.util.resources.OpenListResourceBundle;
* </pre>
* </blockquote>
*
* <h4><a name="LocaleMatching">Locale Matching</a></h4>
*
* <p>If an application or a system is internationalized and provides localized
* resources for multiple locales, it sometimes needs to find one or more
* locales (or language tags) which meet each user's specific preferences. Note
* that a term "language tag" is used interchangeably with "locale" in this
* locale matching documentation.
*
* <p>In order to do matching a user's preferred locales to a set of language
* tags, <a href="http://tools.ietf.org/html/rfc4647">RFC 4647 Matching of
* Language Tags</a> defines two mechanisms: filtering and lookup.
* <em>Filtering</em> is used to get all matching locales, whereas
* <em>lookup</em> is to choose the best matching locale.
* Matching is done case-insensitively. These matching mechanisms are described
* in the following sections.
*
* <p>A user's preference is called a <em>Language Priority List</em> and is
* expressed as a list of language ranges. There are syntactically two types of
* language ranges: basic and extended. See
* {@link Locale.LanguageRange Locale.LanguageRange} for details.
*
* <h5>Filtering</h5>
*
* <p>The filtering operation returns all matching language tags. It is defined
* in RFC 4647 as follows:
* "In filtering, each language range represents the least specific language
* tag (that is, the language tag with fewest number of subtags) that is an
* acceptable match. All of the language tags in the matching set of tags will
* have an equal or greater number of subtags than the language range. Every
* non-wildcard subtag in the language range will appear in every one of the
* matching language tags."
*
* <p>There are two types of filtering: filtering for basic language ranges
* (called "basic filtering") and filtering for extended language ranges
* (called "extended filtering"). They may return different results by what
* kind of language ranges are included in the given Language Priority List.
* {@link Locale.FilteringMode} is a parameter to specify how filtering should
* be done.
*
* <h5>Lookup</h5>
*
* <p>The lookup operation returns the best matching language tags. It is
* defined in RFC 4647 as follows:
* "By contrast with filtering, each language range represents the most
* specific tag that is an acceptable match. The first matching tag found,
* according to the user's priority, is considered the closest match and is the
* item returned."
*
* <p>For example, if a Language Priority List consists of two language ranges,
* {@code "zh-Hant-TW"} and {@code "en-US"}, in prioritized order, lookup
* method progressively searches the language tags below in order to find the
* best matching language tag.
* <blockquote>
* <pre>
* 1. zh-Hant-TW
* 2. zh-Hant
* 3. zh
* 4. en-US
* 5. en
* </pre>
* </blockquote>
* If there is a language tag which matches completely to a language range
* above, the language tag is returned.
*
* <p>{@code "*"} is the special language range, and it is ignored in lookup.
*
* <p>If multiple language tags match as a result of the subtag {@code '*'}
* included in a language range, the first matching language tag returned by
* an {@link Iterator} over a {@link Collection} of language tags is treated as
* the best matching one.
*
* <h4>Use of Locale</h4>
*
* <p>Once you've created a <code>Locale</code> you can query it for information
......@@ -2574,4 +2647,611 @@ public final class Locale implements Cloneable, Serializable {
return Locale.getInstance(baseloc, extensions);
}
}
/**
* This enum provides constants to select a filtering mode for locale
* matching. Refer to <a href="http://tools.ietf.org/html/rfc4647">RFC 4647
* Matching of Language Tags</a> for details.
*
* <p>As an example, think of two Language Priority Lists each of which
* includes only one language range and a set of following language tags:
*
* <pre>
* de (German)
* de-DE (German, Germany)
* de-Deva (German, in Devanagari script)
* de-Deva-DE (German, in Devanagari script, Germany)
* de-DE-1996 (German, Germany, orthography of 1996)
* de-Latn-DE (German, in Latin script, Germany)
* de-Latn-DE-1996 (German, in Latin script, Germany, orthography of 1996)
* </pre>
*
* The filtering method will behave as follows:
*
* <table cellpadding=2>
* <tr>
* <th>Filtering Mode</th>
* <th>Language Priority List: {@code "de-DE"}</th>
* <th>Language Priority List: {@code "de-*-DE"}</th>
* </tr>
* <tr>
* <td valign=top>
* {@link FilteringMode#AUTOSELECT_FILTERING AUTOSELECT_FILTERING}
* </td>
* <td valign=top>
* Performs <em>basic</em> filtering and returns {@code "de-DE"} and
* {@code "de-DE-1996"}.
* </td>
* <td valign=top>
* Performs <em>extended</em> filtering and returns {@code "de-DE"},
* {@code "de-Deva-DE"}, {@code "de-DE-1996"}, {@code "de-Latn-DE"}, and
* {@code "de-Latn-DE-1996"}.
* </td>
* </tr>
* <tr>
* <td valign=top>
* {@link FilteringMode#EXTENDED_FILTERING EXTENDED_FILTERING}
* </td>
* <td valign=top>
* Performs <em>extended</em> filtering and returns {@code "de-DE"},
* {@code "de-Deva-DE"}, {@code "de-DE-1996"}, {@code "de-Latn-DE"}, and
* {@code "de-Latn-DE-1996"}.
* </td>
* <td valign=top>Same as above.</td>
* </tr>
* <tr>
* <td valign=top>
* {@link FilteringMode#IGNORE_EXTENDED_RANGES IGNORE_EXTENDED_RANGES}
* </td>
* <td valign=top>
* Performs <em>basic</em> filtering and returns {@code "de-DE"} and
* {@code "de-DE-1996"}.
* </td>
* <td valign=top>
* Performs <em>basic</em> filtering and returns {@code null} because
* nothing matches.
* </td>
* </tr>
* <tr>
* <td valign=top>
* {@link FilteringMode#MAP_EXTENDED_RANGES MAP_EXTENDED_RANGES}
* </td>
* <td valign=top>Same as above.</td>
* <td valign=top>
* Performs <em>basic</em> filtering and returns {@code "de-DE"} and
* {@code "de-DE-1996"} because {@code "de-*-DE"} is mapped to
* {@code "de-DE"}.
* </td>
* </tr>
* <tr>
* <td valign=top>
* {@link FilteringMode#REJECT_EXTENDED_RANGES REJECT_EXTENDED_RANGES}
* </td>
* <td valign=top>Same as above.</td>
* <td valign=top>
* Throws {@link IllegalArgumentException} because {@code "de-*-DE"} is
* not a valid basic language range.
* </td>
* </tr>
* </table>
*
* @see #filter(List, Collection, FilteringMode)
* @see #filterTags(List, Collection, FilteringMode)
*
* @since 1.8
*/
public static enum FilteringMode {
/**
* Specifies automatic filtering mode based on the given Language
* Priority List consisting of language ranges. If all of the ranges
* are basic, basic filtering is selected. Otherwise, extended
* filtering is selected.
*/
AUTOSELECT_FILTERING,
/**
* Specifies extended filtering.
*/
EXTENDED_FILTERING,
/**
* Specifies basic filtering: Note that any extended language ranges
* included in the given Language Priority List are ignored.
*/
IGNORE_EXTENDED_RANGES,
/**
* Specifies basic filtering: If any extended language ranges are
* included in the given Language Priority List, they are mapped to the
* basic language range. Specifically, a language range starting with a
* subtag {@code "*"} is treated as a language range {@code "*"}. For
* example, {@code "*-US"} is treated as {@code "*"}. If {@code "*"} is
* not the first subtag, {@code "*"} and extra {@code "-"} are removed.
* For example, {@code "ja-*-JP"} is mapped to {@code "ja-JP"}.
*/
MAP_EXTENDED_RANGES,
/**
* Specifies basic filtering: If any extended language ranges are
* included in the given Language Priority List, the list is rejected
* and the filtering method throws {@link IllegalArgumentException}.
*/
REJECT_EXTENDED_RANGES
};
/**
* This class expresses a <em>Language Range</em> defined in
* <a href="http://tools.ietf.org/html/rfc4647">RFC 4647 Matching of
* Language Tags</a>. A language range is an identifier which is used to
* select language tag(s) meeting specific requirements by using the
* mechanisms described in <a href="Locale.html#LocaleMatching">Locale
* Matching</a>. A list which represents a user's preferences and consists
* of language ranges is called a <em>Language Priority List</em>.
*
* <p>There are two types of language ranges: basic and extended. In RFC
* 4647, the syntax of language ranges is expressed in
* <a href="http://tools.ietf.org/html/rfc4234">ABNF</a> as follows:
* <blockquote>
* <pre>
* basic-language-range = (1*8ALPHA *("-" 1*8alphanum)) / "*"
* extended-language-range = (1*8ALPHA / "*")
* *("-" (1*8alphanum / "*"))
* alphanum = ALPHA / DIGIT
* </pre>
* </blockquote>
* For example, {@code "en"} (English), {@code "ja-JP"} (Japanese, Japan),
* {@code "*"} (special language range which matches any language tag) are
* basic language ranges, whereas {@code "*-CH"} (any languages,
* Switzerland), {@code "es-*"} (Spanish, any regions), and
* {@code "zh-Hant-*"} (Traditional Chinese, any regions) are extended
* language ranges.
*
* @see #filter
* @see #filterTags
* @see #lookup
* @see #lookupTag
*
* @since 1.8
*/
public static final class LanguageRange {
/**
* A constant holding the maximum value of weight, 1.0, which indicates
* that the language range is a good fit for the user.
*/
public static final double MAX_WEIGHT = 1.0;
/**
* A constant holding the minimum value of weight, 0.0, which indicates
* that the language range is not a good fit for the user.
*/
public static final double MIN_WEIGHT = 0.0;
private final String range;
private final double weight;
private volatile int hash = 0;
/**
* Constructs a {@code LanguageRange} using the given {@code range}.
* Note that no validation is done against the IANA Language Subtag
* Registry at time of construction.
*
* <p>This is equivalent to {@code LanguageRange(range, MAX_WEIGHT)}.
*
* @param range a language range
* @throws NullPointerException if the given {@code range} is
* {@code null}
*/
public LanguageRange(String range) {
this(range, MAX_WEIGHT);
}
/**
* Constructs a {@code LanguageRange} using the given {@code range} and
* {@code weight}. Note that no validation is done against the IANA
* Language Subtag Registry at time of construction.
*
* @param range a language range
* @param weight a weight value between {@code MIN_WEIGHT} and
* {@code MAX_WEIGHT}
* @throws NullPointerException if the given {@code range} is
* {@code null}
* @throws IllegalArgumentException if the given {@code weight} is less
* than {@code MIN_WEIGHT} or greater than {@code MAX_WEIGHT}
*/
public LanguageRange(String range, double weight) {
if (range == null) {
throw new NullPointerException();
}
if (weight < MIN_WEIGHT || weight > MAX_WEIGHT) {
throw new IllegalArgumentException("weight=" + weight);
}
range = range.toLowerCase();
// Do syntax check.
boolean isIllFormed = false;
String[] subtags = range.split("-");
if (isSubtagIllFormed(subtags[0], true)
|| range.endsWith("-")) {
isIllFormed = true;
} else {
for (int i = 1; i < subtags.length; i++) {
if (isSubtagIllFormed(subtags[i], false)) {
isIllFormed = true;
}
break;
}
}
if (isIllFormed) {
throw new IllegalArgumentException("range=" + range);
}
this.range = range;
this.weight = weight;
}
private static boolean isSubtagIllFormed(String subtag,
boolean isFirstSubtag) {
if (subtag.equals("") || subtag.length() > 8) {
return true;
} else if (subtag.equals("*")) {
return false;
}
char[] charArray = subtag.toCharArray();
if (isFirstSubtag) { // ALPHA
for (char c : charArray) {
if (c < 'a' || c > 'z') {
return true;
}
}
} else { // ALPHA / DIGIT
for (char c : charArray) {
if (c < '0' || (c > '9' && c < 'a') || c > 'z') {
return true;
}
}
}
return false;
}
/**
* Returns the language range of this {@code LanguageRange}.
*
* @return the language range.
*/
public String getRange() {
return range;
}
/**
* Returns the weight of this {@code LanguageRange}.
*
* @return the weight value.
*/
public double getWeight() {
return weight;
}
/**
* Parses the given {@code ranges} to generate a Language Priority List.
*
* <p>This method performs a syntactic check for each language range in
* the given {@code ranges} but doesn't do validation using the IANA
* Language Subtag Registry.
*
* <p>The {@code ranges} to be given can take one of the following
* forms:
*
* <pre>
* "Accept-Language: ja,en;q=0.4" (weighted list with Accept-Language prefix)
* "ja,en;q=0.4" (weighted list)
* "ja,en" (prioritized list)
* </pre>
*
* In a weighted list, each language range is given a weight value.
* The weight value is identical to the "quality value" in
* <a href="http://tools.ietf.org/html/rfc2616">RFC 2616</a>, and it
* expresses how much the user prefers the language. A weight value is
* specified after a corresponding language range followed by
* {@code ";q="}, and the default weight value is {@code MAX_WEIGHT}
* when it is omitted.
*
* <p>Unlike a weighted list, language ranges in a prioritized list
* are sorted in the descending order based on its priority. The first
* language range has the highest priority and meets the user's
* preference most.
*
* <p>In either case, language ranges are sorted in descending order in
* the Language Priority List based on priority or weight. If a
* language range appears in the given {@code ranges} more than once,
* only the first one is included on the Language Priority List.
*
* <p>The returned list consists of language ranges from the given
* {@code ranges} and their equivalents found in the IANA Language
* Subtag Registry. For example, if the given {@code ranges} is
* {@code "Accept-Language: iw,en-us;q=0.7,en;q=0.3"}, the elements in
* the list to be returned are:
*
* <pre>
* <b>Range</b> <b>Weight</b>
* "iw" (older tag for Hebrew) 1.0
* "he" (new preferred code for Hebrew) 1.0
* "en-us" (English, United States) 0.7
* "en" (English) 0.3
* </pre>
*
* Two language ranges, {@code "iw"} and {@code "he"}, have the same
* highest priority in the list. By adding {@code "he"} to the user's
* Language Priority List, locale-matching method can find Hebrew as a
* matching locale (or language tag) even if the application or system
* offers only {@code "he"} as a supported locale (or language tag).
*
* @param ranges a list of comma-separated language ranges or a list of
* language ranges in the form of the "Accept-Language" header
* defined in <a href="http://tools.ietf.org/html/rfc2616">RFC
* 2616</a>
* @return a Language Priority List consisting of language ranges
* included in the given {@code ranges} and their equivalent
* language ranges if available. The list is modifiable.
* @throws NullPointerException if {@code ranges} is null
* @throws IllegalArgumentException if a language range or a weight
* found in the given {@code ranges} is ill-formed
*/
public static List<LanguageRange> parse(String ranges) {
return LocaleMatcher.parse(ranges);
}
/**
* Parses the given {@code ranges} to generate a Language Priority
* List, and then customizes the list using the given {@code map}.
* This method is equivalent to
* {@code mapEquivalents(parse(ranges), map)}.
*
* @param ranges a list of comma-separated language ranges or a list
* of language ranges in the form of the "Accept-Language" header
* defined in <a href="http://tools.ietf.org/html/rfc2616">RFC
* 2616</a>
* @param map a map containing information to customize language ranges
* @return a Language Priority List with customization. The list is
* @throws NullPointerException if {@code ranges} is null
* @throws IllegalArgumentException if a language range or a weight
* found in the given {@code ranges} is ill-formed
* @see #parse(String)
* @see #mapEquivalents
*/
public static List<LanguageRange> parse(String ranges,
Map<String, List<String>> map) {
return mapEquivalents(parse(ranges), map);
}
/**
* Generates a new customized Language Priority List using the given
* {@code priorityList} and {@code map}. If the given {@code map} is
* empty, this method returns a copy of the given {@code priorityList}.
*
* <p>In the map, a key represents a language range whereas a value is
* a list of equivalents of it. {@code '*'} cannot be used in the map.
* Each equivalent language range has the same weight value as its
* original language range.
*
* <pre>
* An example of map:
* <b>Key</b> <b>Value</b>
* "zh" (Chinese) "zh",
* "zh-Hans"(Simplified Chinese)
* "zh-HK" (Chinese, Hong Kong) "zh-HK"
* "zh-TW" (Chinese, Taiwan) "zh-TW"
* </pre>
*
* The customization is performed after modification using the IANA
* Language Subtag Registry.
*
* <p>For example, if a user's Language Priority List consists of five
* language ranges ({@code "zh"}, {@code "zh-CN"}, {@code "en"},
* {@code "zh-TW"}, and {@code "zh-HK"}), the newly generated Language
* Priority List which is customized using the above map example will
* consists of {@code "zh"}, {@code "zh-Hans"}, {@code "zh-CN"},
* {@code "zh-Hans-CN"}, {@code "en"}, {@code "zh-TW"}, and
* {@code "zh-HK"}.
*
* <p>{@code "zh-HK"} and {@code "zh-TW"} aren't converted to
* {@code "zh-Hans-HK"} nor {@code "zh-Hans-TW"} even if they are
* included in the Language Priority List. In this example, mapping
* is used to clearly distinguish Simplified Chinese and Traditional
* Chinese.
*
* <p>If the {@code "zh"}-to-{@code "zh"} mapping isn't included in the
* map, a simple replacement will be performed and the customized list
* won't include {@code "zh"} and {@code "zh-CN"}.
*
* @param priorityList user's Language Priority List
* @param map a map containing information to customize language ranges
* @return a new Language Priority List with customization. The list is
* modifiable.
* @throws NullPointerException if {@code priorityList} is {@code null}
* @see #parse(String, Map)
*/
public static List<LanguageRange> mapEquivalents(
List<LanguageRange>priorityList,
Map<String, List<String>> map) {
return LocaleMatcher.mapEquivalents(priorityList, map);
}
/**
* Returns a hash code value for the object.
*
* @return a hash code value for this object.
*/
@Override
public int hashCode() {
if (hash == 0) {
int result = 17;
result = 37*result + range.hashCode();
long bitsWeight = Double.doubleToLongBits(weight);
result = 37*result + (int)(bitsWeight ^ (bitsWeight >>> 32));
hash = result;
}
return hash;
}
/**
* Compares this object to the specified object. The result is true if
* and only if the argument is not {@code null} and is a
* {@code LanguageRange} object that contains the same {@code range}
* and {@code weight} values as this object.
*
* @param obj the object to compare with
* @return {@code true} if this object's {@code range} and
* {@code weight} are the same as the {@code obj}'s; {@code false}
* otherwise.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof LanguageRange)) {
return false;
}
LanguageRange other = (LanguageRange)obj;
return hash == other.hash
&& range.equals(other.range)
&& weight == other.weight;
}
}
/**
* Returns a list of matching {@code Locale} instances using the filtering
* mechanism defined in RFC 4647.
*
* @param priorityList user's Language Priority List in which each language
* tag is sorted in descending order based on priority or weight
* @param locales {@code Locale} instances used for matching
* @param mode filtering mode
* @return a list of {@code Locale} instances for matching language tags
* sorted in descending order based on priority or weight, or an empty
* list if nothing matches. The list is modifiable.
* @throws NullPointerException if {@code priorityList} or {@code locales}
* is {@code null}
* @throws IllegalArgumentException if one or more extended language ranges
* are included in the given list when
* {@link FilteringMode#REJECT_EXTENDED_RANGES} is specified
*
* @since 1.8
*/
public static List<Locale> filter(List<LanguageRange> priorityList,
Collection<Locale> locales,
FilteringMode mode) {
return LocaleMatcher.filter(priorityList, locales, mode);
}
/**
* Returns a list of matching {@code Locale} instances using the filtering
* mechanism defined in RFC 4647. This is equivalent to
* {@link #filter(List, Collection, FilteringMode)} when {@code mode} is
* {@link FilteringMode#AUTOSELECT_FILTERING}.
*
* @param priorityList user's Language Priority List in which each language
* tag is sorted in descending order based on priority or weight
* @param locales {@code Locale} instances used for matching
* @return a list of {@code Locale} instances for matching language tags
* sorted in descending order based on priority or weight, or an empty
* list if nothing matches. The list is modifiable.
* @throws NullPointerException if {@code priorityList} or {@code locales}
* is {@code null}
*
* @since 1.8
*/
public static List<Locale> filter(List<LanguageRange> priorityList,
Collection<Locale> locales) {
return filter(priorityList, locales, FilteringMode.AUTOSELECT_FILTERING);
}
/**
* Returns a list of matching languages tags using the basic filtering
* mechanism defined in RFC 4647.
*
* @param priorityList user's Language Priority List in which each language
* tag is sorted in descending order based on priority or weight
* @param tags language tags
* @param mode filtering mode
* @return a list of matching language tags sorted in descending order
* based on priority or weight, or an empty list if nothing matches.
* The list is modifiable.
* @throws NullPointerException if {@code priorityList} or {@code tags} is
* {@code null}
* @throws IllegalArgumentException if one or more extended language ranges
* are included in the given list when
* {@link FilteringMode#REJECT_EXTENDED_RANGES} is specified
*
* @since 1.8
*/
public static List<String> filterTags(List<LanguageRange> priorityList,
Collection<String> tags,
FilteringMode mode) {
return LocaleMatcher.filterTags(priorityList, tags, mode);
}
/**
* Returns a list of matching languages tags using the basic filtering
* mechanism defined in RFC 4647. This is equivalent to
* {@link #filterTags(List, Collection, FilteringMode)} when {@code mode}
* is {@link FilteringMode#AUTOSELECT_FILTERING}.
*
* @param priorityList user's Language Priority List in which each language
* tag is sorted in descending order based on priority or weight
* @param tags language tags
* @return a list of matching language tags sorted in descending order
* based on priority or weight, or an empty list if nothing matches.
* The list is modifiable.
* @throws NullPointerException if {@code priorityList} or {@code tags} is
* {@code null}
*
* @since 1.8
*/
public static List<String> filterTags(List<LanguageRange> priorityList,
Collection<String> tags) {
return filterTags(priorityList, tags, FilteringMode.AUTOSELECT_FILTERING);
}
/**
* Returns a {@code Locale} instance for the best-matching language
* tag using the lookup mechanism defined in RFC 4647.
*
* @param priorityList user's Language Priority List in which each language
* tag is sorted in descending order based on priority or weight
* @param locales {@code Locale} instances used for matching
* @return the best matching <code>Locale</code> instance chosen based on
* priority or weight, or {@code null} if nothing matches.
* @throws NullPointerException if {@code priorityList} or {@code tags} is
* {@code null}
*
* @since 1.8
*/
public static Locale lookup(List<LanguageRange> priorityList,
Collection<Locale> locales) {
return LocaleMatcher.lookup(priorityList, locales);
}
/**
* Returns the best-matching language tag using the lookup mechanism
* defined in RFC 4647.
*
* @param priorityList user's Language Priority List in which each language
* tag is sorted in descending order based on priority or weight
* @param tags language tangs used for matching
* @return the best matching language tag chosen based on priority or
* weight, or {@code null} if nothing matches.
* @throws NullPointerException if {@code priorityList} or {@code tags} is
* {@code null}
*
* @since 1.8
*/
public static String lookupTag(List<LanguageRange> priorityList,
Collection<String> tags) {
return LocaleMatcher.lookupTag(priorityList, tags);
}
}
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.util.locale;
import java.util.HashMap;
import java.util.Map;
/**
* Locale equivalent map for BCP47 Locale matching
*/
final class LocaleEquivalentMaps {
static final Map<String, String> singleEquivMap;
static final Map<String, String[]> multiEquivsMap;
static final Map<String, String> regionVariantEquivMap;
static {
singleEquivMap = new HashMap<>();
multiEquivsMap = new HashMap<>();
regionVariantEquivMap = new HashMap<>();
// This is an auto-generated file and should not be manually edited.
// LSR Revision: 2012-09-04
singleEquivMap.put("ami", "i-ami");
singleEquivMap.put("art-lojban", "jbo");
singleEquivMap.put("ase", "sgn-us");
singleEquivMap.put("ayx", "nun");
singleEquivMap.put("bfi", "sgn-gb");
singleEquivMap.put("bjd", "drl");
singleEquivMap.put("bnn", "i-bnn");
singleEquivMap.put("bzs", "sgn-br");
singleEquivMap.put("cjr", "mom");
singleEquivMap.put("cka", "cmr");
singleEquivMap.put("cmk", "xch");
singleEquivMap.put("cmn-hans", "zh-cmn-hans");
singleEquivMap.put("cmn-hant", "zh-cmn-hant");
singleEquivMap.put("cmr", "cka");
singleEquivMap.put("csn", "sgn-co");
singleEquivMap.put("dev", "gav");
singleEquivMap.put("drh", "khk");
singleEquivMap.put("drl", "bjd");
singleEquivMap.put("dse", "sgn-nl");
singleEquivMap.put("dsl", "sgn-dk");
singleEquivMap.put("fsl", "sgn-fr");
singleEquivMap.put("gan", "zh-gan");
singleEquivMap.put("gav", "dev");
singleEquivMap.put("gsg", "sgn-de");
singleEquivMap.put("gss", "sgn-gr");
singleEquivMap.put("he", "iw");
singleEquivMap.put("hle", "sca");
singleEquivMap.put("hrr", "jal");
singleEquivMap.put("hsn", "zh-xiang");
singleEquivMap.put("i-ami", "ami");
singleEquivMap.put("i-bnn", "bnn");
singleEquivMap.put("i-klingon", "tlh");
singleEquivMap.put("i-lux", "lb");
singleEquivMap.put("i-navajo", "nv");
singleEquivMap.put("i-pwn", "pwn");
singleEquivMap.put("i-tao", "tao");
singleEquivMap.put("i-tay", "tay");
singleEquivMap.put("i-tsu", "tsu");
singleEquivMap.put("ibi", "opa");
singleEquivMap.put("id", "in");
singleEquivMap.put("in", "id");
singleEquivMap.put("ise", "sgn-it");
singleEquivMap.put("isg", "sgn-ie");
singleEquivMap.put("iw", "he");
singleEquivMap.put("jal", "hrr");
singleEquivMap.put("jbo", "art-lojban");
singleEquivMap.put("ji", "yi");
singleEquivMap.put("jsl", "sgn-jp");
singleEquivMap.put("jv", "jw");
singleEquivMap.put("jw", "jv");
singleEquivMap.put("kgh", "kml");
singleEquivMap.put("khk", "drh");
singleEquivMap.put("kml", "kgh");
singleEquivMap.put("lb", "i-lux");
singleEquivMap.put("lcq", "ppr");
singleEquivMap.put("lrr", "yma");
singleEquivMap.put("mfs", "sgn-mx");
singleEquivMap.put("mo", "ro");
singleEquivMap.put("mom", "cjr");
singleEquivMap.put("nan", "zh-min-nan");
singleEquivMap.put("nb", "no-bok");
singleEquivMap.put("ncs", "sgn-ni");
singleEquivMap.put("nn", "no-nyn");
singleEquivMap.put("no-bok", "nb");
singleEquivMap.put("no-nyn", "nn");
singleEquivMap.put("nsl", "sgn-no");
singleEquivMap.put("nun", "ayx");
singleEquivMap.put("nv", "i-navajo");
singleEquivMap.put("opa", "ibi");
singleEquivMap.put("ppr", "lcq");
singleEquivMap.put("psr", "sgn-pt");
singleEquivMap.put("pwn", "i-pwn");
singleEquivMap.put("ras", "tie");
singleEquivMap.put("ro", "mo");
singleEquivMap.put("sca", "hle");
singleEquivMap.put("sfb", "sgn-be-fr");
singleEquivMap.put("sfs", "sgn-za");
singleEquivMap.put("sgg", "sgn-ch-de");
singleEquivMap.put("sgn-be-fr", "sfb");
singleEquivMap.put("sgn-be-nl", "vgt");
singleEquivMap.put("sgn-br", "bzs");
singleEquivMap.put("sgn-ch-de", "sgg");
singleEquivMap.put("sgn-co", "csn");
singleEquivMap.put("sgn-de", "gsg");
singleEquivMap.put("sgn-dk", "dsl");
singleEquivMap.put("sgn-es", "ssp");
singleEquivMap.put("sgn-fr", "fsl");
singleEquivMap.put("sgn-gb", "bfi");
singleEquivMap.put("sgn-gr", "gss");
singleEquivMap.put("sgn-ie", "isg");
singleEquivMap.put("sgn-it", "ise");
singleEquivMap.put("sgn-jp", "jsl");
singleEquivMap.put("sgn-mx", "mfs");
singleEquivMap.put("sgn-ni", "ncs");
singleEquivMap.put("sgn-nl", "dse");
singleEquivMap.put("sgn-no", "nsl");
singleEquivMap.put("sgn-pt", "psr");
singleEquivMap.put("sgn-se", "swl");
singleEquivMap.put("sgn-us", "ase");
singleEquivMap.put("sgn-za", "sfs");
singleEquivMap.put("ssp", "sgn-es");
singleEquivMap.put("swl", "sgn-se");
singleEquivMap.put("tao", "i-tao");
singleEquivMap.put("tay", "i-tay");
singleEquivMap.put("tie", "ras");
singleEquivMap.put("tkk", "twm");
singleEquivMap.put("tlh", "i-klingon");
singleEquivMap.put("tlw", "weo");
singleEquivMap.put("tsu", "i-tsu");
singleEquivMap.put("twm", "tkk");
singleEquivMap.put("vgt", "sgn-be-nl");
singleEquivMap.put("weo", "tlw");
singleEquivMap.put("wuu", "zh-wuu");
singleEquivMap.put("xch", "cmk");
singleEquivMap.put("yi", "ji");
singleEquivMap.put("yma", "lrr");
singleEquivMap.put("yue", "zh-yue");
singleEquivMap.put("zh-cmn-hans", "cmn-hans");
singleEquivMap.put("zh-cmn-hant", "cmn-hant");
singleEquivMap.put("zh-gan", "gan");
singleEquivMap.put("zh-min-nan", "nan");
singleEquivMap.put("zh-wuu", "wuu");
singleEquivMap.put("zh-xiang", "hsn");
singleEquivMap.put("zh-yue", "yue");
multiEquivsMap.put("ccq", new String[] {"rki", "ybd"});
multiEquivsMap.put("cmn", new String[] {"zh-guoyu", "zh-cmn"});
multiEquivsMap.put("drw", new String[] {"prs", "tnf"});
multiEquivsMap.put("hak", new String[] {"i-hak", "zh-hakka"});
multiEquivsMap.put("i-hak", new String[] {"hak", "zh-hakka"});
multiEquivsMap.put("mry", new String[] {"mst", "myt"});
multiEquivsMap.put("mst", new String[] {"mry", "myt"});
multiEquivsMap.put("myt", new String[] {"mry", "mst"});
multiEquivsMap.put("prs", new String[] {"drw", "tnf"});
multiEquivsMap.put("rki", new String[] {"ccq", "ybd"});
multiEquivsMap.put("tnf", new String[] {"prs", "drw"});
multiEquivsMap.put("ybd", new String[] {"rki", "ccq"});
multiEquivsMap.put("zh-cmn", new String[] {"cmn", "zh-guoyu"});
multiEquivsMap.put("zh-guoyu", new String[] {"cmn", "zh-cmn"});
multiEquivsMap.put("zh-hakka", new String[] {"hak", "i-hak"});
regionVariantEquivMap.put("-alalc97", "-heploc");
regionVariantEquivMap.put("-bu", "-mm");
regionVariantEquivMap.put("-cd", "-zr");
regionVariantEquivMap.put("-dd", "-de");
regionVariantEquivMap.put("-de", "-dd");
regionVariantEquivMap.put("-fr", "-fx");
regionVariantEquivMap.put("-fx", "-fr");
regionVariantEquivMap.put("-heploc", "-alalc97");
regionVariantEquivMap.put("-mm", "-bu");
regionVariantEquivMap.put("-tl", "-tp");
regionVariantEquivMap.put("-tp", "-tl");
regionVariantEquivMap.put("-yd", "-ye");
regionVariantEquivMap.put("-ye", "-yd");
regionVariantEquivMap.put("-zr", "-cd");
}
}
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.util.locale;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Locale.*;
import static java.util.Locale.FilteringMode.*;
import static java.util.Locale.LanguageRange.*;
import java.util.Map;
import java.util.Set;
/**
* Implementation for BCP47 Locale matching
*
*/
public final class LocaleMatcher {
public static List<Locale> filter(List<LanguageRange> priorityList,
Collection<Locale> locales,
FilteringMode mode) {
if (priorityList.isEmpty() || locales.isEmpty()) {
return new ArrayList<>(); // need to return a empty mutable List
}
// Create a list of language tags to be matched.
List<String> tags = new ArrayList<>();
for (Locale locale : locales) {
tags.add(locale.toLanguageTag());
}
// Filter language tags.
List<String> filteredTags = filterTags(priorityList, tags, mode);
// Create a list of matching locales.
List<Locale> filteredLocales = new ArrayList<>(filteredTags.size());
for (String tag : filteredTags) {
filteredLocales.add(Locale.forLanguageTag(tag));
}
return filteredLocales;
}
public static List<String> filterTags(List<LanguageRange> priorityList,
Collection<String> tags,
FilteringMode mode) {
if (priorityList.isEmpty() || tags.isEmpty()) {
return new ArrayList<>(); // need to return a empty mutable List
}
ArrayList<LanguageRange> list;
if (mode == EXTENDED_FILTERING) {
return filterExtended(priorityList, tags);
} else {
list = new ArrayList<>();
for (LanguageRange lr : priorityList) {
String range = lr.getRange();
if (range.startsWith("*-")
|| range.indexOf("-*") != -1) { // Extended range
if (mode == AUTOSELECT_FILTERING) {
return filterExtended(priorityList, tags);
} else if (mode == MAP_EXTENDED_RANGES) {
if (range.charAt(0) == '*') {
range = "*";
} else {
range = range.replaceAll("-[*]", "");
}
list.add(new LanguageRange(range, lr.getWeight()));
} else if (mode == REJECT_EXTENDED_RANGES) {
throw new IllegalArgumentException("An extended range \""
+ range
+ "\" found in REJECT_EXTENDED_RANGES mode.");
}
} else { // Basic range
list.add(lr);
}
}
return filterBasic(list, tags);
}
}
private static List<String> filterBasic(List<LanguageRange> priorityList,
Collection<String> tags) {
List<String> list = new ArrayList<>();
for (LanguageRange lr : priorityList) {
String range = lr.getRange();
if (range.equals("*")) {
return new ArrayList<String>(tags);
} else {
for (String tag : tags) {
tag = tag.toLowerCase();
if (tag.startsWith(range)) {
int len = range.length();
if ((tag.length() == len || tag.charAt(len) == '-')
&& !list.contains(tag)) {
list.add(tag);
}
}
}
}
}
return list;
}
private static List<String> filterExtended(List<LanguageRange> priorityList,
Collection<String> tags) {
List<String> list = new ArrayList<>();
for (LanguageRange lr : priorityList) {
String range = lr.getRange();
if (range.equals("*")) {
return new ArrayList<String>(tags);
}
String[] rangeSubtags = range.split("-");
for (String tag : tags) {
tag = tag.toLowerCase();
String[] tagSubtags = tag.split("-");
if (!rangeSubtags[0].equals(tagSubtags[0])
&& !rangeSubtags[0].equals("*")) {
continue;
}
int rangeIndex = 1;
int tagIndex = 1;
while (rangeIndex < rangeSubtags.length
&& tagIndex < tagSubtags.length) {
if (rangeSubtags[rangeIndex].equals("*")) {
rangeIndex++;
} else if (rangeSubtags[rangeIndex].equals(tagSubtags[tagIndex])) {
rangeIndex++;
tagIndex++;
} else if (tagSubtags[tagIndex].length() == 1
&& !tagSubtags[tagIndex].equals("*")) {
break;
} else {
tagIndex++;
}
}
if (rangeSubtags.length == rangeIndex && !list.contains(tag)) {
list.add(tag);
}
}
}
return list;
}
public static Locale lookup(List<LanguageRange> priorityList,
Collection<Locale> locales) {
if (priorityList.isEmpty() || locales.isEmpty()) {
return null;
}
// Create a list of language tags to be matched.
List<String> tags = new ArrayList<>();
for (Locale locale : locales) {
tags.add(locale.toLanguageTag());
}
// Look up a language tags.
String lookedUpTag = lookupTag(priorityList, tags);
if (lookedUpTag == null) {
return null;
} else {
return Locale.forLanguageTag(lookedUpTag);
}
}
public static String lookupTag(List<LanguageRange> priorityList,
Collection<String> tags) {
if (priorityList.isEmpty() || tags.isEmpty()) {
return null;
}
for (LanguageRange lr : priorityList) {
String range = lr.getRange();
// Special language range ("*") is ignored in lookup.
if (range.equals("*")) {
continue;
}
String rangeForRegex = range.replaceAll("\\x2A", "\\\\p{Alnum}*");
while (rangeForRegex.length() > 0) {
for (String tag : tags) {
tag = tag.toLowerCase();
if (tag.matches(rangeForRegex)) {
return tag;
}
}
// Truncate from the end....
int index = rangeForRegex.lastIndexOf('-');
if (index >= 0) {
rangeForRegex = rangeForRegex.substring(0, index);
// if range ends with an extension key, truncate it.
if (rangeForRegex.lastIndexOf('-') == rangeForRegex.length()-2) {
rangeForRegex =
rangeForRegex.substring(0, rangeForRegex.length()-2);
}
} else {
rangeForRegex = "";
}
}
}
return null;
}
public static List<LanguageRange> parse(String ranges) {
ranges = ranges.replaceAll(" ", "").toLowerCase();
if (ranges.startsWith("accept-language:")) {
ranges = ranges.substring(16); // delete unnecessary prefix
}
String[] langRanges = ranges.split(",");
List<LanguageRange> list = new ArrayList<>(langRanges.length);
List<String> tempList = new ArrayList<>();
int numOfRanges = 0;
for (String range : langRanges) {
int index;
String r;
double w;
if ((index = range.indexOf(";q=")) == -1) {
r = range;
w = MAX_WEIGHT;
} else {
r = range.substring(0, index);
index += 3;
try {
w = Double.parseDouble(range.substring(index));
}
catch (Exception e) {
throw new IllegalArgumentException("weight=\""
+ range.substring(index)
+ "\" for language range \"" + r + "\"");
}
if (w < MIN_WEIGHT || w > MAX_WEIGHT) {
throw new IllegalArgumentException("weight=" + w
+ " for language range \"" + r
+ "\". It must be between " + MIN_WEIGHT
+ " and " + MAX_WEIGHT + ".");
}
}
if (!tempList.contains(r)) {
LanguageRange lr = new LanguageRange(r, w);
index = numOfRanges;
for (int j = 0; j < numOfRanges; j++) {
if (list.get(j).getWeight() < w) {
index = j;
break;
}
}
list.add(index, lr);
numOfRanges++;
tempList.add(r);
// Check if the range has an equivalent using IANA LSR data.
// If yes, add it to the User's Language Priority List as well.
// aa-XX -> aa-YY
String equivalent;
if ((equivalent = getEquivalentForRegionAndVariant(r)) != null
&& !tempList.contains(equivalent)) {
list.add(index+1, new LanguageRange(equivalent, w));
numOfRanges++;
tempList.add(equivalent);
}
String[] equivalents;
if ((equivalents = getEquivalentsForLanguage(r)) != null) {
for (String equiv: equivalents) {
// aa-XX -> bb-XX(, cc-XX)
if (!tempList.contains(equiv)) {
list.add(index+1, new LanguageRange(equiv, w));
numOfRanges++;
tempList.add(equiv);
}
// bb-XX -> bb-YY(, cc-YY)
equivalent = getEquivalentForRegionAndVariant(equiv);
if (equivalent != null
&& !tempList.contains(equivalent)) {
list.add(index+1, new LanguageRange(equivalent, w));
numOfRanges++;
tempList.add(equivalent);
}
}
}
}
}
return list;
}
private static String[] getEquivalentsForLanguage(String range) {
String r = range;
while (r.length() > 0) {
if (LocaleEquivalentMaps.singleEquivMap.containsKey(r)) {
String equiv = LocaleEquivalentMaps.singleEquivMap.get(r);
// Return immediately for performance if the first matching
// subtag is found.
return new String[] {range.replaceFirst(r, equiv)};
} else if (LocaleEquivalentMaps.multiEquivsMap.containsKey(r)) {
String[] equivs = LocaleEquivalentMaps.multiEquivsMap.get(r);
for (int i = 0; i < equivs.length; i++) {
equivs[i] = range.replaceFirst(r, equivs[i]);
}
return equivs;
}
// Truncate the last subtag simply.
int index = r.lastIndexOf('-');
if (index == -1) {
break;
}
r = r.substring(0, index);
}
return null;
}
private static String getEquivalentForRegionAndVariant(String range) {
int extensionKeyIndex = getExtentionKeyIndex(range);
for (String subtag : LocaleEquivalentMaps.regionVariantEquivMap.keySet()) {
int index;
if ((index = range.indexOf(subtag)) != -1) {
// Check if the matching text is a valid region or variant.
if (extensionKeyIndex != Integer.MIN_VALUE
&& index > extensionKeyIndex) {
continue;
}
int len = index + subtag.length();
if (range.length() == len || range.charAt(len) == '-') {
return range.replaceFirst(subtag, LocaleEquivalentMaps.regionVariantEquivMap.get(subtag));
}
}
}
return null;
}
private static int getExtentionKeyIndex(String s) {
char[] c = s.toCharArray();
int index = Integer.MIN_VALUE;
for (int i = 1; i < c.length; i++) {
if (c[i] == '-') {
if (i - index == 2) {
return index;
} else {
index = i;
}
}
}
return Integer.MIN_VALUE;
}
public static List<LanguageRange> mapEquivalents(
List<LanguageRange>priorityList,
Map<String, List<String>> map) {
if (priorityList.isEmpty()) {
return new ArrayList<>(); // need to return a empty mutable List
}
if (map == null || map.isEmpty()) {
return new ArrayList<LanguageRange>(priorityList);
}
// Create a map, key=originalKey.toLowerCaes(), value=originalKey
Map<String, String> keyMap = new HashMap<>();
for (String key : map.keySet()) {
keyMap.put(key.toLowerCase(), key);
}
List<LanguageRange> list = new ArrayList<>();
for (LanguageRange lr : priorityList) {
String range = lr.getRange();
String r = range;
boolean hasEquivalent = false;
while (r.length() > 0) {
if (keyMap.containsKey(r)) {
hasEquivalent = true;
List<String> equivalents = map.get(keyMap.get(r));
if (equivalents != null) {
int len = r.length();
for (String equivalent : equivalents) {
list.add(new LanguageRange(equivalent.toLowerCase()
+ range.substring(len),
lr.getWeight()));
}
}
// Return immediately if the first matching subtag is found.
break;
}
// Truncate the last subtag simply.
int index = r.lastIndexOf('-');
if (index == -1) {
break;
}
r = r.substring(0, index);
}
if (!hasEquivalent) {
list.add(lr);
}
}
return list;
}
private LocaleMatcher() {}
}
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 7069824
* @summary Verify implementation for Locale matching.
* @run main Bug7069824
*/
import java.util.*;
import java.util.Locale.*;
import static java.util.Locale.FilteringMode.*;
import static java.util.Locale.LanguageRange.*;
public class Bug7069824 {
static boolean err = false;
public static void main(String[] args) {
testLanguageRange();
testLocale();
if (err) {
throw new RuntimeException("Failed.");
}
}
private static void testLanguageRange() {
System.out.println("Test LanguageRange class...");
testConstants();
testConstructors();
testMethods();
}
private static void testLocale() {
System.out.println("Test Locale class...");
test_filter();
test_filterTags();
test_lookup();
test_lookupTag();
}
private static void testConstants() {
boolean error = false;
if (MIN_WEIGHT != 0.0) {
error = true;
System.err.println(" MIN_WEIGHT should be 0.0 but got "
+ MIN_WEIGHT);
}
if (MAX_WEIGHT != 1.0) {
error = true;
System.err.println(" MAX_WEIGHT should be 1.0 but got "
+ MAX_WEIGHT);
}
if (error) {
err = true;
System.err.println(" testConstants() failed.");
} else {
System.out.println(" testConstants() passed.");
}
}
private static void testConstructors() {
boolean error = false;
LanguageRange lr;
String range;
double weight;
range = null;
try {
lr = new LanguageRange(range);
error = true;
System.err.println(" NPE should be thrown for LanguageRange("
+ range + ").");
}
catch (NullPointerException ex) {
}
range = null;
weight = 0.8;
try {
lr = new LanguageRange(range, weight);
error = true;
System.err.println(" NPE should be thrown for LanguageRange("
+ range + ", " + weight + ").");
}
catch (NullPointerException ex) {
}
range = "elvish";
try {
lr = new LanguageRange(range);
}
catch (Exception ex) {
error = true;
System.err.println(" " + ex
+ " should not be thrown for LanguageRange(" + range + ").");
}
range = "de-DE";
try {
lr = new LanguageRange(range);
}
catch (Exception ex) {
error = true;
System.err.println(" " + ex
+ " should not be thrown for LanguageRange(" + range + ").");
}
range = "ar";
weight = 0.8;
try {
lr = new LanguageRange(range, weight);
}
catch (Exception ex) {
error = true;
System.err.println(" " + ex
+ " should not be thrown for LanguageRange(" + range + ", "
+ weight + ").");
}
range = "ja";
weight = -0.8;
try {
lr = new LanguageRange(range, weight);
error = true;
System.err.println(" IAE should be thrown for LanguageRange("
+ range + ", " + weight + ").");
}
catch (IllegalArgumentException ex) {
}
range = "Elvish";
weight = 3.0;
try {
lr = new LanguageRange(range, weight);
error = true;
System.err.println(" IAE should be thrown for LanguageRange("
+ range + ", " + weight + ").");
}
catch (IllegalArgumentException ex) {
}
String[] illformedRanges = {"-ja", "ja--JP", "en-US-", "a4r", "ar*",
"ar-*EG", "", "abcdefghijklmn", "ja-J=", "ja-opqrstuvwxyz"};
for (String r : illformedRanges) {
try {
lr = new LanguageRange(r);
error = true;
System.err.println(" IAE should be thrown for LanguageRange("
+ r + ").");
}
catch (IllegalArgumentException ex) {
}
}
if (error) {
err = true;
System.err.println(" testConstructors() failed.");
} else {
System.out.println(" testConstructors() passed.");
}
}
private static void testMethods() {
test_getRange();
test_getWeight();
test_equals();
test_parse();
test_mapEquivalents();
}
private static void test_getRange() {
boolean error = false;
String range = "ja";
double weight = 0.5;
LanguageRange lr = new LanguageRange(range, weight);
if (!lr.getRange().equals(range)) {
error = true;
System.err.println(" LanguageRange.getRange() returned unexpected value. Expected: "
+ range + ", got: " + lr.getRange());
}
range = "en-US";
weight = 0.5;
lr = new LanguageRange(range, weight);
if (!lr.getRange().equals(range.toLowerCase())) {
error = true;
System.err.println(" LanguageRange.getRange() returned unexpected value. Expected: "
+ range + ", got: " + lr.getRange());
}
if (error) {
err = true;
System.err.println(" test_getRange() failed.");
} else {
System.out.println(" test_getRange() passed.");
}
}
private static void test_getWeight() {
boolean error = false;
String range = "ja";
double weight = 0.5;
LanguageRange lr = new LanguageRange(range, weight);
if (lr.getWeight() != weight) {
error = true;
System.err.println(" LanguageRange.getWeight() returned unexpected value. Expected: "
+ weight + ", got: " + lr.getWeight());
}
range = "ja";
weight = MAX_WEIGHT; // default
lr = new LanguageRange(range);
if (!lr.getRange().equals(range) || lr.getWeight() != MAX_WEIGHT) {
error = true;
System.err.println(" LanguageRange.getWeight() returned unexpected value. Expected: "
+ weight + ", got: " + lr.getWeight());
}
if (error) {
err = true;
System.err.println(" test_getWeight() failed.");
} else {
System.out.println(" test_getWeight() passed.");
}
}
private static void test_equals() {
boolean error = false;
LanguageRange lr1 = new LanguageRange("ja", 1.0);
LanguageRange lr2 = new LanguageRange("ja");
LanguageRange lr3 = new LanguageRange("ja", 0.1);
LanguageRange lr4 = new LanguageRange("en", 1.0);
if (!lr1.equals(lr2)) {
error = true;
System.err.println(" LanguageRange(LR(ja, 1.0)).equals(LR(ja)) should return true.");
}
if (lr1.equals(lr3)) {
error = true;
System.err.println(" LanguageRange(LR(ja, 1.0)).equals(LR(ja, 0.1)) should return false.");
}
if (lr1.equals(lr4)) {
error = true;
System.err.println(" LanguageRange(LR(ja, 1.0)).equals(LR(en, 1.0)) should return false.");
}
if (lr1.equals(null)) {
error = true;
System.err.println(" LanguageRange(LR(ja, 1.0)).equals(null) should return false.");
}
if (lr1.equals("")) {
error = true;
System.err.println(" LanguageRange(LR(ja, 1.0)).equals(\"\") should return false.");
}
if (error) {
err = true;
System.err.println(" test_equals() failed.");
} else {
System.out.println(" test_equals() passed.");
}
}
private static void test_parse() {
boolean error = false;
List<LanguageRange> list;
String str = null;
try {
list = LanguageRange.parse(str);
error = true;
System.err.println(" NPE should be thrown for parse("
+ str + ").");
}
catch (NullPointerException ex) {
}
str = "";
try {
list = LanguageRange.parse("");
error = true;
System.err.println(" IAE should be thrown for parse("
+ str + ").");
}
catch (IllegalArgumentException ex) {
}
str = "ja;q=3";
try {
list = LanguageRange.parse(str);
error = true;
System.err.println("IAE should be thrown for parse("
+ str + ").");
}
catch (IllegalArgumentException ex) {
}
str = "Accept-Language: fr-FX,de-DE;q=0.5, fr-tp-x-FOO;q=0.1,"
+ "en-X-tp;q=0.6,en-FR;q=.7,de-de;q=0.8, iw;q=0.4, "
+ "he;q=0.4, de-de;q=0.5,ja, in-tpp, in-tp;q=0.2";
ArrayList<LanguageRange> expected = new ArrayList<>();
expected.add(new LanguageRange("fr-fx", 1.0));
expected.add(new LanguageRange("fr-fr", 1.0));
expected.add(new LanguageRange("ja", 1.0));
expected.add(new LanguageRange("in-tpp", 1.0));
expected.add(new LanguageRange("id-tpp", 1.0));
expected.add(new LanguageRange("en-fr", 0.7));
expected.add(new LanguageRange("en-fx", 0.7));
expected.add(new LanguageRange("en-x-tp", 0.6));
expected.add(new LanguageRange("de-de", 0.5));
expected.add(new LanguageRange("de-dd", 0.5));
expected.add(new LanguageRange("iw", 0.4));
expected.add(new LanguageRange("he", 0.4));
expected.add(new LanguageRange("in-tp", 0.2));
expected.add(new LanguageRange("id-tl", 0.2));
expected.add(new LanguageRange("id-tp", 0.2));
expected.add(new LanguageRange("in-tl", 0.2));
expected.add(new LanguageRange("fr-tp-x-foo", 0.1));
expected.add(new LanguageRange("fr-tl-x-foo", 0.1));
List<LanguageRange> got = LanguageRange.parse(str);
if (!areEqual(expected, got)) {
error = true;
System.err.println(" #1 parse() test failed.");
}
str = "Accept-Language: hak-CN;q=0.8, no-bok-NO;q=0.9, no-nyn, cmn-CN;q=0.1";
expected = new ArrayList<>();
expected.add(new LanguageRange("no-nyn", 1.0));
expected.add(new LanguageRange("nn", 1.0));
expected.add(new LanguageRange("no-bok-no", 0.9));
expected.add(new LanguageRange("nb-no", 0.9));
expected.add(new LanguageRange("hak-CN", 0.8));
expected.add(new LanguageRange("zh-hakka-CN", 0.8));
expected.add(new LanguageRange("i-hak-CN", 0.8));
expected.add(new LanguageRange("cmn-CN", 0.1));
expected.add(new LanguageRange("zh-cmn-CN", 0.1));
expected.add(new LanguageRange("zh-guoyu-CN", 0.1));
got = LanguageRange.parse(str);
if (!areEqual(expected, got)) {
error = true;
System.err.println(" #2 parse() test failed.");
}
str = "Accept-Language: rki;q=0.4, no-bok-NO;q=0.9, ccq;q=0.5";
expected = new ArrayList<>();
expected.add(new LanguageRange("no-bok-no", 0.9));
expected.add(new LanguageRange("nb-no", 0.9));
expected.add(new LanguageRange("rki", 0.4));
expected.add(new LanguageRange("ybd", 0.4));
expected.add(new LanguageRange("ccq", 0.4));
got = LanguageRange.parse(str);
if (!areEqual(expected, got)) {
error = true;
System.err.println(" #3 parse() test failed.");
}
if (error) {
err = true;
System.err.println(" test_parse() failed.");
} else {
System.out.println(" test_parse() passed.");
}
}
private static boolean areEqual(List<LanguageRange> expected,
List<LanguageRange> got) {
boolean error = false;
int expectedSize = expected.size();
int actualSize = got.size();
if (expectedSize != actualSize) {
error = true;
System.err.println(" Expected size=" + expectedSize);
for (LanguageRange lr : expected) {
System.err.println(" range=" + lr.getRange()
+ ", weight=" + lr.getWeight());
}
System.out.println(" Actual size=" + actualSize);
for (LanguageRange lr : got) {
System.err.println(" range=" + lr.getRange()
+ ", weight=" + lr.getWeight());
}
} else {
for (int i = 0; i < expectedSize; i++) {
LanguageRange lr1 = expected.get(i);
LanguageRange lr2 = got.get(i);
if (!lr1.getRange().equals(lr2.getRange())
|| lr1.getWeight() != lr2.getWeight()) {
error = true;
System.err.println(" " + i + ": Expected: range=" + lr1.getRange()
+ ", weight=" + lr1.getWeight());
System.err.println(" " + i + ": Actual: range=" + lr2.getRange()
+ ", weight=" + lr2.getWeight());
}
}
}
return !error;
}
private static void test_mapEquivalents() {
boolean error = false;
String ranges = "zh, zh-TW;q=0.8, ar;q=0.9, EN, zh-HK, ja-JP;q=0.2, es;q=0.4";
List<LanguageRange> priorityList = LanguageRange.parse(ranges);
HashMap<String, List<String>> map = null;
try {
List<LanguageRange> list =
LanguageRange.mapEquivalents(priorityList, null);
}
catch (Exception ex) {
error = true;
System.err.println(ex
+ " should not be thrown for mapEquivalents(priorityList, null).");
}
map = new HashMap<>();
try {
List<LanguageRange> list =
LanguageRange.mapEquivalents(priorityList, map);
}
catch (Exception ex) {
error = true;
System.err.println(ex
+ " should not be thrown for mapEquivalents(priorityList, empty map).");
}
ArrayList<String> equivalentList = new ArrayList<>();
equivalentList.add("ja");
equivalentList.add("ja-Hira");
map.put("ja", equivalentList);
try {
List<LanguageRange> list = LanguageRange.mapEquivalents(null, map);
error = true;
System.err.println("NPE should be thrown for mapEquivalents(null, map).");
}
catch (NullPointerException ex) {
}
map = new LinkedHashMap<>();
ArrayList<String> equivalentList1 = new ArrayList<>();
equivalentList1.add("ja");
equivalentList1.add("ja-Hira");
map.put("ja", equivalentList1);
ArrayList<String> equivalentList2 = new ArrayList<>();
equivalentList2.add("zh-Hans");
equivalentList2.add("zh-Hans-CN");
equivalentList2.add("zh-CN");
map.put("zh", equivalentList2);
ArrayList<String> equivalentList3 = new ArrayList<>();
equivalentList3.add("zh-TW");
equivalentList3.add("zh-Hant");
map.put("zh-TW", equivalentList3);
map.put("es", null);
ArrayList<String> equivalentList4 = new ArrayList<>();
map.put("en", equivalentList4);
ArrayList<String> equivalentList5 = new ArrayList<>();
equivalentList5.add("de");
map.put("zh-HK", equivalentList5);
ArrayList<LanguageRange> expected = new ArrayList<>();
expected.add(new LanguageRange("zh-hans", 1.0));
expected.add(new LanguageRange("zh-hans-cn", 1.0));
expected.add(new LanguageRange("zh-cn", 1.0));
expected.add(new LanguageRange("de", 1.0));
expected.add(new LanguageRange("ar", 0.9));
expected.add(new LanguageRange("zh-tw", 0.8));
expected.add(new LanguageRange("zh-hant", 0.8));
expected.add(new LanguageRange("ja-jp", 0.2));
expected.add(new LanguageRange("ja-hira-jp", 0.2));
List<LanguageRange> got =
LanguageRange.mapEquivalents(priorityList, map);
if (!areEqual(expected, got)) {
error = true;
}
if (error) {
err = true;
System.err.println(" test_mapEquivalents() failed.");
} else {
System.out.println(" test_mapEquivalents() passed.");
}
}
private static void test_filter() {
boolean error = false;
String ranges = "ja-JP, fr-FR";
String tags = "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP";
FilteringMode mode = EXTENDED_FILTERING;
List<LanguageRange> priorityList = LanguageRange.parse(ranges);
List<Locale> tagList = generateLocales(tags);
String actualLocales =
showLocales(Locale.filter(priorityList, tagList, mode));
String expectedLocales = "ja-JP-hepburn, ja-Latn-JP";
if (!expectedLocales.equals(actualLocales)) {
error = true;
showErrorMessage("#1 filter(" + mode + ")",
ranges, tags, expectedLocales, actualLocales);
}
ranges = "ja-*-JP, fr-FR";
tags = "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP";
mode = EXTENDED_FILTERING;
priorityList = LanguageRange.parse(ranges);
tagList = generateLocales(tags);
actualLocales = showLocales(Locale.filter(priorityList, tagList, mode));
expectedLocales = "ja-JP-hepburn, ja-Latn-JP";
if (!expectedLocales.equals(actualLocales)) {
error = true;
showErrorMessage("#2 filter(" + mode + ")",
ranges, tags, expectedLocales, actualLocales);
}
ranges = "ja-*-JP, fr-FR, de-de;q=0.2";
tags = "de-DE, en, ja-JP-hepburn, de-de, fr, he, ja-Latn-JP";
mode = AUTOSELECT_FILTERING;
priorityList = LanguageRange.parse(ranges);
tagList = generateLocales(tags);
actualLocales = showLocales(Locale.filter(priorityList, tagList, mode));
expectedLocales = "ja-JP-hepburn, ja-Latn-JP, de-DE";
if (!expectedLocales.equals(actualLocales)) {
error = true;
showErrorMessage("#3 filter(" + mode + ")",
ranges, tags,expectedLocales, actualLocales);
}
ranges = "ja-JP, fr-FR, de-de;q=0.2";
tags = "de-DE, en, ja-JP-hepburn, de-de, fr, he, ja-Latn-JP";
mode = AUTOSELECT_FILTERING;
priorityList = LanguageRange.parse(ranges);
tagList = generateLocales(tags);
actualLocales = showLocales(Locale.filter(priorityList, tagList, mode));
expectedLocales = "ja-JP-hepburn, de-DE";
if (!expectedLocales.equals(actualLocales)) {
error = true;
showErrorMessage("#4 filter(" + mode + ")",
ranges, tags, expectedLocales, actualLocales);
}
ranges = "en;q=0.2, ja-*-JP, fr-JP";
tags = "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP";
mode = IGNORE_EXTENDED_RANGES;
priorityList = LanguageRange.parse(ranges);
tagList = generateLocales(tags);
actualLocales = showLocales(Locale.filter(priorityList, tagList, mode));
expectedLocales = "en";
if (!expectedLocales.equals(actualLocales)) {
error = true;
showErrorMessage("#5 filter(" + mode + ")",
ranges, tags, expectedLocales, actualLocales);
}
ranges = "en;q=0.2, ja-*-JP, fr-JP";
tags = "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP";
mode = MAP_EXTENDED_RANGES;
priorityList = LanguageRange.parse(ranges);
tagList = generateLocales(tags);
actualLocales = showLocales(Locale.filter(priorityList, tagList, mode));
expectedLocales = "ja-JP-hepburn, en";
if (!expectedLocales.equals(actualLocales)) {
error = true;
showErrorMessage("#6 filter(" + mode + ")",
ranges, tags, expectedLocales, actualLocales);
}
ranges = "en;q=0.2, ja-JP, fr-JP";
tags = "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP";
mode = REJECT_EXTENDED_RANGES;
priorityList = LanguageRange.parse(ranges);
tagList = generateLocales(tags);
actualLocales = showLocales(Locale.filter(priorityList, tagList, mode));
expectedLocales = "ja-JP-hepburn, en";
if (!expectedLocales.equals(actualLocales)) {
error = true;
showErrorMessage("#7 filter(" + mode + ")",
ranges, tags, expectedLocales, actualLocales);
}
ranges = "en;q=0.2, ja-*-JP, fr-JP";
tags = "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP";
mode = REJECT_EXTENDED_RANGES;
priorityList = LanguageRange.parse(ranges);
tagList = generateLocales(tags);
try {
actualLocales =
showLocales(Locale.filter(priorityList, tagList, mode));
error = true;
System.out.println("IAE should be thrown for filter("
+ mode + ").");
}
catch (IllegalArgumentException ex) {
}
ranges = "en;q=0.2, ja-*-JP, fr-JP";
tags = null;
mode = REJECT_EXTENDED_RANGES;
priorityList = LanguageRange.parse(ranges);
tagList = generateLocales(tags);
try {
actualLocales =
showLocales(Locale.filter(priorityList, tagList, mode));
error = true;
System.out.println("NPE should be thrown for filter(tags=null).");
}
catch (NullPointerException ex) {
}
ranges = null;
tags = "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP";
mode = REJECT_EXTENDED_RANGES;
try {
priorityList = LanguageRange.parse(ranges);
tagList = generateLocales(tags);
actualLocales =
showLocales(Locale.filter(priorityList, tagList, mode));
error = true;
System.out.println("NPE should be thrown for filter(ranges=null).");
}
catch (NullPointerException ex) {
}
ranges = "en;q=0.2, ja-*-JP, fr-JP";
tags = "";
mode = REJECT_EXTENDED_RANGES;
priorityList = LanguageRange.parse(ranges);
tagList = generateLocales(tags);
try {
actualLocales =
showLocales(Locale.filter(priorityList, tagList, mode));
}
catch (Exception ex) {
error = true;
System.out.println(ex
+ " should not be thrown for filter(" + ranges + ", \"\").");
}
if (error) {
err = true;
System.out.println(" test_filter() failed.");
} else {
System.out.println(" test_filter() passed.");
}
}
private static void test_filterTags() {
boolean error = false;
String ranges = "en;q=0.2, *;q=0.6, ja";
String tags = "de-DE, en, ja-JP-hepburn, fr-JP, he";
List<LanguageRange> priorityList = LanguageRange.parse(ranges);
List<String> tagList = generateLanguageTags(tags);
String actualTags =
showLanguageTags(Locale.filterTags(priorityList, tagList));
String expectedTags = tags;
if (!expectedTags.equals(actualTags)) {
error = true;
showErrorMessage("#1 filterTags()",
ranges, tags, expectedTags, actualTags);
}
ranges = "en;q=0.2, ja-JP, fr-JP";
tags = "de-DE, en, ja-JP-hepburn, fr, he";
priorityList = LanguageRange.parse(ranges);
tagList = generateLanguageTags(tags);
actualTags = showLanguageTags(Locale.filterTags(priorityList, tagList));
expectedTags = "ja-jp-hepburn, en";
if (!expectedTags.equals(actualTags)) {
error = true;
showErrorMessage("#2 filterTags()",
ranges, tags, expectedTags, actualTags);
}
ranges = "de-DE";
tags = "de-DE, de-de, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
+ "de-Latn-DE-1996, de-Deva-DE, de, de-x-DE, de-Deva";
FilteringMode mode = MAP_EXTENDED_RANGES;
priorityList = LanguageRange.parse(ranges);
tagList = generateLanguageTags(tags);
actualTags = showLanguageTags(Locale.filterTags(priorityList, tagList, mode));
expectedTags = "de-de, de-de-x-goethe";
if (!expectedTags.equals(actualTags)) {
error = true;
showErrorMessage("#3 filterTags(" + mode + ")",
ranges, tags, expectedTags, actualTags);
}
ranges = "de-DE";
tags = "de-DE, de-de, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
+ "de-Latn-DE-1996, de-Deva-DE, de, de-x-DE, de-Deva";
mode = EXTENDED_FILTERING;
priorityList = LanguageRange.parse(ranges);
tagList = generateLanguageTags(tags);
actualTags = showLanguageTags(Locale.filterTags(priorityList, tagList, mode));
expectedTags = "de-de, de-latn-de, de-latf-de, de-de-x-goethe, "
+ "de-latn-de-1996, de-deva-de";
if (!expectedTags.equals(actualTags)) {
error = true;
showErrorMessage("#4 filterTags(" + mode + ")",
ranges, tags, expectedTags, actualTags);
}
ranges = "de-*-DE";
tags = "de-DE, de-de, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, "
+ "de-Latn-DE-1996, de-Deva-DE, de, de-x-DE, de-Deva";
mode = EXTENDED_FILTERING;
priorityList = LanguageRange.parse(ranges);
tagList = generateLanguageTags(tags);
actualTags = showLanguageTags(Locale.filterTags(priorityList, tagList, mode));
expectedTags = "de-de, de-latn-de, de-latf-de, de-de-x-goethe, "
+ "de-latn-de-1996, de-deva-de";
if (!expectedTags.equals(actualTags)) {
error = true;
showErrorMessage("#5 filterTags(" + mode + ")",
ranges, tags, expectedTags, actualTags);
}
if (error) {
err = true;
System.out.println(" test_filterTags() failed.");
} else {
System.out.println(" test_filterTags() passed.");
}
}
private static void test_lookup() {
boolean error = false;
String ranges = "en;q=0.2, *-JP;q=0.6, iw";
String tags = "de-DE, en, ja-JP-hepburn, fr-JP, he";
List<LanguageRange> priorityList = LanguageRange.parse(ranges);
List<Locale> localeList = generateLocales(tags);
String actualLocale =
Locale.lookup(priorityList, localeList).toLanguageTag();
String expectedLocale ="he";
if (!expectedLocale.equals(actualLocale)) {
error = true;
showErrorMessage("#1 lookup()", ranges, tags, expectedLocale, actualLocale);
}
ranges = "en;q=0.2, *-JP;q=0.6, iw";
tags = "de-DE, he-IL, en, iw";
priorityList = LanguageRange.parse(ranges);
localeList = generateLocales(tags);
actualLocale = Locale.lookup(priorityList, localeList).toLanguageTag();
expectedLocale = "he";
if (!expectedLocale.equals(actualLocale)) {
error = true;
showErrorMessage("#2 lookup()", ranges, tags, expectedLocale, actualLocale);
}
ranges = "en;q=0.2, ja-*-JP-x-foo;q=0.6, iw";
tags = "de-DE, fr, en, ja-Latn-JP";
priorityList = LanguageRange.parse(ranges);
localeList = generateLocales(tags);
actualLocale = Locale.lookup(priorityList, localeList).toLanguageTag();
expectedLocale = "ja-Latn-JP";
if (!expectedLocale.equals(actualLocale)) {
error = true;
showErrorMessage("#3 lookup()", ranges, tags, expectedLocale, actualLocale);
}
if (error) {
err = true;
System.out.println(" test_lookup() failed.");
} else {
System.out.println(" test_lookup() passed.");
}
}
private static void test_lookupTag() {
boolean error = false;
String ranges = "en, *";
String tags = "es, de, ja-JP";
List<LanguageRange> priorityList = LanguageRange.parse(ranges);
List<String> tagList = generateLanguageTags(tags);
String actualTag = Locale.lookupTag(priorityList, tagList);
String expectedTag = null;
if (actualTag != null) {
error = true;
showErrorMessage("#1 lookupTag()", ranges, tags, expectedTag, actualTag);
}
ranges= "en;q=0.2, *-JP";
tags = "de-DE, en, ja-JP-hepburn, fr-JP, en-JP";
priorityList = LanguageRange.parse(ranges);
tagList = generateLanguageTags(tags);
actualTag = Locale.lookupTag(priorityList, tagList);
expectedTag = "fr-jp";
if (!expectedTag.equals(actualTag)) {
error = true;
showErrorMessage("#2 lookupTag()", ranges, tags, expectedTag, actualTag);
}
ranges = "en;q=0.2, ar-MO, iw";
tags = "de-DE, he, fr-JP";
priorityList = LanguageRange.parse(ranges);
tagList = generateLanguageTags(tags);
actualTag = Locale.lookupTag(priorityList, tagList);
expectedTag = "he";
if (!expectedTag.equals(actualTag)) {
error = true;
showErrorMessage("#3 lookupTag()", ranges, tags, expectedTag, actualTag);
}
ranges = "en;q=0.2, ar-MO, he";
tags = "de-DE, iw, fr-JP";
priorityList = LanguageRange.parse(ranges);
tagList = generateLanguageTags(tags);
actualTag = Locale.lookupTag(priorityList, tagList);
expectedTag = "iw";
if (!expectedTag.equals(actualTag)) {
error = true;
showErrorMessage("#4 lookupTag()", ranges, tags, expectedTag, actualTag);
}
if (error) {
err = true;
System.out.println(" test_lookupTag() failed.");
} else {
System.out.println(" test_lookupTag() passed.");
}
}
private static List<Locale> generateLocales(String tags) {
if (tags == null) {
return null;
}
List<Locale> localeList = new ArrayList<>();
if (tags.equals("")) {
return localeList;
}
String[] t = tags.split(", ");
for (String tag : t) {
localeList.add(Locale.forLanguageTag(tag));
}
return localeList;
}
private static List<String> generateLanguageTags(String tags) {
List<String> tagList = new ArrayList<>();
String[] t = tags.split(", ");
for (String tag : t) {
tagList.add(tag);
}
return tagList;
}
private static String showPriorityList(List<LanguageRange> priorityList) {
StringBuilder sb = new StringBuilder();
Iterator<LanguageRange> itr = priorityList.iterator();
LanguageRange lr;
if (itr.hasNext()) {
lr = itr.next();
sb.append(lr.getRange());
sb.append(";q=");
sb.append(lr.getWeight());
}
while (itr.hasNext()) {
sb.append(", ");
lr = itr.next();
sb.append(lr.getRange());
sb.append(";q=");
sb.append(lr.getWeight());
}
return sb.toString();
}
private static String showLanguageTags(List<String> tags) {
StringBuilder sb = new StringBuilder();
Iterator<String> itr = tags.iterator();
if (itr.hasNext()) {
sb.append(itr.next());
}
while (itr.hasNext()) {
sb.append(", ");
sb.append(itr.next());
}
return sb.toString().trim();
}
private static String showLocales(List<Locale> locales) {
StringBuilder sb = new StringBuilder();
Iterator<Locale> itr = locales.iterator();
if (itr.hasNext()) {
sb.append(itr.next().toLanguageTag());
}
while (itr.hasNext()) {
sb.append(", ");
sb.append(itr.next().toLanguageTag());
}
return sb.toString().trim();
}
private static void showErrorMessage(String methodName,
String priorityList,
String tags,
String expectedTags,
String actualTags) {
System.out.println("\nIncorrect " + methodName + " result.");
System.out.println(" Priority list : " + priorityList);
System.out.println(" Language tags : " + tags);
System.out.println(" Expected value : " + expectedTags);
System.out.println(" Actual value : " + actualTags);
}
}
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.*;
import java.nio.charset.*;
import java.nio.file.*;
import java.util.*;
public class EquivMapsGenerator {
/*
* IANA Language Subtag Registry file downloaded from
* http://www.iana.org/assignments/language-subtag-registry
*/
private static final String DEFAULT_LSR_FILE =
"language-subtag-registry.txt";
private static boolean verbose = false;
public static void main(String[] args) throws Exception {
String fileLSR = DEFAULT_LSR_FILE;
for (int i = 0; i < args.length; i++) {
String s = args[i];
if (s.equals("-lsr")) {
fileLSR = args[++i];
} else if (s.equals("-verbose")) {
verbose = true;
}
}
readLSRfile(fileLSR);
generateEquivalentMap();
generateSourceCode();
}
private static String LSRrevisionDate;
private static Map<String, StringBuilder> initialLanguageMap =
new TreeMap<>();
private static Map<String, StringBuilder> initialRegionVariantMap =
new TreeMap<>();
private static Map<String, String> sortedLanguageMap1 = new TreeMap<>();
private static Map<String, String[]> sortedLanguageMap2 = new TreeMap<>();
private static Map<String, String> sortedRegionVariantMap =
new TreeMap<>();
private static void readLSRfile(String filename) throws Exception {
String type = null;
String tag = null;
String preferred = null;
int mappingNum = 0;
for (String line : Files.readAllLines(Paths.get(filename),
Charset.forName("UTF-8"))) {
line = line.toLowerCase();
int index = line.indexOf(' ')+1;
if (line.startsWith("file-date:")) {
LSRrevisionDate = line.substring(index);
if (verbose) {
System.out.println("LSR revision date=" + LSRrevisionDate);
}
} else if (line.startsWith("type:")) {
type = line.substring(index);
} else if (line.startsWith("tag:") || line.startsWith("subtag:")) {
tag = line.substring(index);
} else if (line.startsWith("preferred-value:")
&& !type.equals("extlang")) {
preferred = line.substring(index);
mappingNum++;
processDeprecatedData(type, tag, preferred);
} else if (line.equals("%%")) {
type = null;
tag = null;
preferred = null;
}
}
if (verbose) {
System.out.println("readLSRfile(" + filename + ")");
System.out.println(" Total number of mapping=" + mappingNum);
System.out.println("\n Map for language. Size="
+ initialLanguageMap.size());
for (String key : initialLanguageMap.keySet()) {
System.out.println(" " + key + ": \""
+ initialLanguageMap.get(key) + "\"");
}
System.out.println("\n Map for region and variant. Size="
+ initialRegionVariantMap.size());
for (String key : initialRegionVariantMap.keySet()) {
System.out.println(" " + key + ": \""
+ initialRegionVariantMap.get(key) + "\"");
}
}
}
private static void processDeprecatedData(String type,
String tag,
String preferred) {
StringBuilder sb;
if (type.equals("region") || type.equals("variant")) {
if (!initialRegionVariantMap.containsKey(preferred)) {
sb = new StringBuilder("-");
sb.append(preferred);
sb.append(",-");
sb.append(tag);
initialRegionVariantMap.put("-"+preferred, sb);
} else {
throw new RuntimeException("New case, need implementation."
+ " A region/variant subtag \"" + preferred
+ "\" is registered for more than one subtags.");
}
} else { // language, grandfahered, and redundant
if (!initialLanguageMap.containsKey(preferred)) {
sb = new StringBuilder(preferred);
sb.append(',');
sb.append(tag);
initialLanguageMap.put(preferred, sb);
} else {
sb = initialLanguageMap.get(preferred);
sb.append(',');
sb.append(tag);
initialLanguageMap.put(preferred, sb);
}
}
}
private static void generateEquivalentMap() {
String[] subtags;
for (String preferred : initialLanguageMap.keySet()) {
subtags = initialLanguageMap.get(preferred).toString().split(",");
if (subtags.length == 2) {
sortedLanguageMap1.put(subtags[0], subtags[1]);
sortedLanguageMap1.put(subtags[1], subtags[0]);
} else if (subtags.length == 3) {
sortedLanguageMap2.put(subtags[0],
new String[]{subtags[1], subtags[2]});
sortedLanguageMap2.put(subtags[1],
new String[]{subtags[0], subtags[2]});
sortedLanguageMap2.put(subtags[2],
new String[]{subtags[0], subtags[1]});
} else {
throw new RuntimeException("New case, need implementation."
+ " A language subtag \"" + preferred
+ "\" is registered for more than two subtags. ");
}
}
for (String preferred : initialRegionVariantMap.keySet()) {
subtags =
initialRegionVariantMap.get(preferred).toString().split(",");
sortedRegionVariantMap.put(subtags[0], subtags[1]);
sortedRegionVariantMap.put(subtags[1], subtags[0]);
}
if (verbose) {
System.out.println("generateEquivalentMap()");
System.out.println(" \nSorted map for language subtags which have only one equivalent. Size="
+ sortedLanguageMap1.size());
for (String key : sortedLanguageMap1.keySet()) {
System.out.println(" " + key + ": \""
+ sortedLanguageMap1.get(key) + "\"");
}
System.out.println("\n Sorted map for language subtags which have multiple equivalents. Size="
+ sortedLanguageMap2.size());
for (String key : sortedLanguageMap2.keySet()) {
String[] s = sortedLanguageMap2.get(key);
System.out.println(" " + key + ": \""
+ s[0] + "\", \"" + s[1] + "\"");
}
System.out.println("\n Sorted map for region and variant subtags. Size="
+ sortedRegionVariantMap.size());
for (String key : sortedRegionVariantMap.keySet()) {
System.out.println(" " + key + ": \""
+ sortedRegionVariantMap.get(key) + "\"");
}
}
System.out.println();
}
private final static String headerText =
"final class LocaleEquivalentMaps {\n\n"
+ " static final Map<String, String> singleEquivMap;\n"
+ " static final Map<String, String[]> multiEquivsMap;\n"
+ " static final Map<String, String> regionVariantEquivMap;\n\n"
+ " static {\n"
+ " singleEquivMap = new HashMap<>();\n"
+ " multiEquivsMap = new HashMap<>();\n"
+ " regionVariantEquivMap = new HashMap<>();\n\n"
+ " // This is an auto-generated file and should not be manually edited.\n";
private final static String footerText =
" }\n\n"
+ "}";
private static void generateSourceCode() {
System.out.println(headerText
+ " // LSR Revision: " + LSRrevisionDate);
for (String key : sortedLanguageMap1.keySet()) {
String value = sortedLanguageMap1.get(key);
System.out.println(" singleEquivMap.put(\""
+ key + "\", \"" + value + "\");");
}
System.out.println();
for (String key : sortedLanguageMap2.keySet()) {
String[] values = sortedLanguageMap2.get(key);
System.out.println(" multiEquivsMap.put(\""
+ key + "\", new String[] {\"" + values[0] + "\", \""
+ values[1] + "\"});");
}
System.out.println();
for (String key : sortedRegionVariantMap.keySet()) {
String value = sortedRegionVariantMap.get(key);
System.out.println(" regionVariantEquivMap.put(\""
+ key + "\", \"" + value + "\");");
}
System.out.println(footerText);
}
}
因为 它太大了无法显示 source diff 。你可以改为 查看blob
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册