Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/util/locale/LocaleMatcher.java
38918 views
/*1* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.util.locale;2627import java.util.ArrayList;28import java.util.Collection;29import java.util.HashMap;30import java.util.Iterator;31import java.util.LinkedHashMap;32import java.util.LinkedList;33import java.util.List;34import java.util.Locale;35import java.util.Locale.*;36import static java.util.Locale.FilteringMode.*;37import static java.util.Locale.LanguageRange.*;38import java.util.Map;39import java.util.Set;4041/**42* Implementation for BCP47 Locale matching43*44*/45public final class LocaleMatcher {4647public static List<Locale> filter(List<LanguageRange> priorityList,48Collection<Locale> locales,49FilteringMode mode) {50if (priorityList.isEmpty() || locales.isEmpty()) {51return new ArrayList<>(); // need to return a empty mutable List52}5354// Create a list of language tags to be matched.55List<String> tags = new ArrayList<>();56for (Locale locale : locales) {57tags.add(locale.toLanguageTag());58}5960// Filter language tags.61List<String> filteredTags = filterTags(priorityList, tags, mode);6263// Create a list of matching locales.64List<Locale> filteredLocales = new ArrayList<>(filteredTags.size());65for (String tag : filteredTags) {66filteredLocales.add(Locale.forLanguageTag(tag));67}6869return filteredLocales;70}7172public static List<String> filterTags(List<LanguageRange> priorityList,73Collection<String> tags,74FilteringMode mode) {75if (priorityList.isEmpty() || tags.isEmpty()) {76return new ArrayList<>(); // need to return a empty mutable List77}7879ArrayList<LanguageRange> list;80if (mode == EXTENDED_FILTERING) {81return filterExtended(priorityList, tags);82} else {83list = new ArrayList<>();84for (LanguageRange lr : priorityList) {85String range = lr.getRange();86if (range.startsWith("*-")87|| range.indexOf("-*") != -1) { // Extended range88if (mode == AUTOSELECT_FILTERING) {89return filterExtended(priorityList, tags);90} else if (mode == MAP_EXTENDED_RANGES) {91if (range.charAt(0) == '*') {92range = "*";93} else {94range = range.replaceAll("-[*]", "");95}96list.add(new LanguageRange(range, lr.getWeight()));97} else if (mode == REJECT_EXTENDED_RANGES) {98throw new IllegalArgumentException("An extended range \""99+ range100+ "\" found in REJECT_EXTENDED_RANGES mode.");101}102} else { // Basic range103list.add(lr);104}105}106107return filterBasic(list, tags);108}109}110111private static List<String> filterBasic(List<LanguageRange> priorityList,112Collection<String> tags) {113List<String> list = new ArrayList<>();114for (LanguageRange lr : priorityList) {115String range = lr.getRange();116if (range.equals("*")) {117return new ArrayList<String>(tags);118} else {119for (String tag : tags) {120tag = tag.toLowerCase();121if (tag.startsWith(range)) {122int len = range.length();123if ((tag.length() == len || tag.charAt(len) == '-')124&& !list.contains(tag)) {125list.add(tag);126}127}128}129}130}131132return list;133}134135private static List<String> filterExtended(List<LanguageRange> priorityList,136Collection<String> tags) {137List<String> list = new ArrayList<>();138for (LanguageRange lr : priorityList) {139String range = lr.getRange();140if (range.equals("*")) {141return new ArrayList<String>(tags);142}143String[] rangeSubtags = range.split("-");144for (String tag : tags) {145tag = tag.toLowerCase();146String[] tagSubtags = tag.split("-");147if (!rangeSubtags[0].equals(tagSubtags[0])148&& !rangeSubtags[0].equals("*")) {149continue;150}151152int rangeIndex = 1;153int tagIndex = 1;154155while (rangeIndex < rangeSubtags.length156&& tagIndex < tagSubtags.length) {157if (rangeSubtags[rangeIndex].equals("*")) {158rangeIndex++;159} else if (rangeSubtags[rangeIndex].equals(tagSubtags[tagIndex])) {160rangeIndex++;161tagIndex++;162} else if (tagSubtags[tagIndex].length() == 1163&& !tagSubtags[tagIndex].equals("*")) {164break;165} else {166tagIndex++;167}168}169170if (rangeSubtags.length == rangeIndex && !list.contains(tag)) {171list.add(tag);172}173}174}175176return list;177}178179public static Locale lookup(List<LanguageRange> priorityList,180Collection<Locale> locales) {181if (priorityList.isEmpty() || locales.isEmpty()) {182return null;183}184185// Create a list of language tags to be matched.186List<String> tags = new ArrayList<>();187for (Locale locale : locales) {188tags.add(locale.toLanguageTag());189}190191// Look up a language tags.192String lookedUpTag = lookupTag(priorityList, tags);193194if (lookedUpTag == null) {195return null;196} else {197return Locale.forLanguageTag(lookedUpTag);198}199}200201public static String lookupTag(List<LanguageRange> priorityList,202Collection<String> tags) {203if (priorityList.isEmpty() || tags.isEmpty()) {204return null;205}206207for (LanguageRange lr : priorityList) {208String range = lr.getRange();209210// Special language range ("*") is ignored in lookup.211if (range.equals("*")) {212continue;213}214215String rangeForRegex = range.replaceAll("\\x2A", "\\\\p{Alnum}*");216while (rangeForRegex.length() > 0) {217for (String tag : tags) {218tag = tag.toLowerCase();219if (tag.matches(rangeForRegex)) {220return tag;221}222}223224// Truncate from the end....225int index = rangeForRegex.lastIndexOf('-');226if (index >= 0) {227rangeForRegex = rangeForRegex.substring(0, index);228229// if range ends with an extension key, truncate it.230if (rangeForRegex.lastIndexOf('-') == rangeForRegex.length()-2) {231rangeForRegex =232rangeForRegex.substring(0, rangeForRegex.length()-2);233}234} else {235rangeForRegex = "";236}237}238}239240return null;241}242243public static List<LanguageRange> parse(String ranges) {244ranges = ranges.replaceAll(" ", "").toLowerCase();245if (ranges.startsWith("accept-language:")) {246ranges = ranges.substring(16); // delete unnecessary prefix247}248249String[] langRanges = ranges.split(",");250List<LanguageRange> list = new ArrayList<>(langRanges.length);251List<String> tempList = new ArrayList<>();252int numOfRanges = 0;253254for (String range : langRanges) {255int index;256String r;257double w;258259if ((index = range.indexOf(";q=")) == -1) {260r = range;261w = MAX_WEIGHT;262} else {263r = range.substring(0, index);264index += 3;265try {266w = Double.parseDouble(range.substring(index));267}268catch (Exception e) {269throw new IllegalArgumentException("weight=\""270+ range.substring(index)271+ "\" for language range \"" + r + "\"");272}273274if (w < MIN_WEIGHT || w > MAX_WEIGHT) {275throw new IllegalArgumentException("weight=" + w276+ " for language range \"" + r277+ "\". It must be between " + MIN_WEIGHT278+ " and " + MAX_WEIGHT + ".");279}280}281282if (!tempList.contains(r)) {283LanguageRange lr = new LanguageRange(r, w);284index = numOfRanges;285for (int j = 0; j < numOfRanges; j++) {286if (list.get(j).getWeight() < w) {287index = j;288break;289}290}291list.add(index, lr);292numOfRanges++;293tempList.add(r);294295// Check if the range has an equivalent using IANA LSR data.296// If yes, add it to the User's Language Priority List as well.297298// aa-XX -> aa-YY299String equivalent;300if ((equivalent = getEquivalentForRegionAndVariant(r)) != null301&& !tempList.contains(equivalent)) {302list.add(index+1, new LanguageRange(equivalent, w));303numOfRanges++;304tempList.add(equivalent);305}306307String[] equivalents;308if ((equivalents = getEquivalentsForLanguage(r)) != null) {309for (String equiv: equivalents) {310// aa-XX -> bb-XX(, cc-XX)311if (!tempList.contains(equiv)) {312list.add(index+1, new LanguageRange(equiv, w));313numOfRanges++;314tempList.add(equiv);315}316317// bb-XX -> bb-YY(, cc-YY)318equivalent = getEquivalentForRegionAndVariant(equiv);319if (equivalent != null320&& !tempList.contains(equivalent)) {321list.add(index+1, new LanguageRange(equivalent, w));322numOfRanges++;323tempList.add(equivalent);324}325}326}327}328}329330return list;331}332333private static String[] getEquivalentsForLanguage(String range) {334String r = range;335336while (r.length() > 0) {337if (LocaleEquivalentMaps.singleEquivMap.containsKey(r)) {338String equiv = LocaleEquivalentMaps.singleEquivMap.get(r);339// Return immediately for performance if the first matching340// subtag is found.341return new String[] {range.replaceFirst(r, equiv)};342} else if (LocaleEquivalentMaps.multiEquivsMap.containsKey(r)) {343String[] equivs = LocaleEquivalentMaps.multiEquivsMap.get(r);344for (int i = 0; i < equivs.length; i++) {345equivs[i] = range.replaceFirst(r, equivs[i]);346}347return equivs;348}349350// Truncate the last subtag simply.351int index = r.lastIndexOf('-');352if (index == -1) {353break;354}355r = r.substring(0, index);356}357358return null;359}360361private static String getEquivalentForRegionAndVariant(String range) {362int extensionKeyIndex = getExtentionKeyIndex(range);363364for (String subtag : LocaleEquivalentMaps.regionVariantEquivMap.keySet()) {365int index;366if ((index = range.indexOf(subtag)) != -1) {367// Check if the matching text is a valid region or variant.368if (extensionKeyIndex != Integer.MIN_VALUE369&& index > extensionKeyIndex) {370continue;371}372373int len = index + subtag.length();374if (range.length() == len || range.charAt(len) == '-') {375return range.replaceFirst(subtag, LocaleEquivalentMaps.regionVariantEquivMap.get(subtag));376}377}378}379380return null;381}382383private static int getExtentionKeyIndex(String s) {384char[] c = s.toCharArray();385int index = Integer.MIN_VALUE;386for (int i = 1; i < c.length; i++) {387if (c[i] == '-') {388if (i - index == 2) {389return index;390} else {391index = i;392}393}394}395return Integer.MIN_VALUE;396}397398public static List<LanguageRange> mapEquivalents(399List<LanguageRange>priorityList,400Map<String, List<String>> map) {401if (priorityList.isEmpty()) {402return new ArrayList<>(); // need to return a empty mutable List403}404if (map == null || map.isEmpty()) {405return new ArrayList<LanguageRange>(priorityList);406}407408// Create a map, key=originalKey.toLowerCaes(), value=originalKey409Map<String, String> keyMap = new HashMap<>();410for (String key : map.keySet()) {411keyMap.put(key.toLowerCase(), key);412}413414List<LanguageRange> list = new ArrayList<>();415for (LanguageRange lr : priorityList) {416String range = lr.getRange();417String r = range;418boolean hasEquivalent = false;419420while (r.length() > 0) {421if (keyMap.containsKey(r)) {422hasEquivalent = true;423List<String> equivalents = map.get(keyMap.get(r));424if (equivalents != null) {425int len = r.length();426for (String equivalent : equivalents) {427list.add(new LanguageRange(equivalent.toLowerCase()428+ range.substring(len),429lr.getWeight()));430}431}432// Return immediately if the first matching subtag is found.433break;434}435436// Truncate the last subtag simply.437int index = r.lastIndexOf('-');438if (index == -1) {439break;440}441r = r.substring(0, index);442}443444if (!hasEquivalent) {445list.add(lr);446}447}448449return list;450}451452private LocaleMatcher() {}453454}455456457