Path: blob/master/test/jdk/java/util/Locale/LSRDataTest.java
41149 views
/*1* Copyright (c) 2018, 2020, 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.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @bug 8204938 824201026* @summary Checks the IANA language subtag registry data update27* with Locale.LanguageRange parse method.28* @run main LSRDataTest29*/30import java.io.IOException;31import java.nio.charset.Charset;32import java.nio.file.Files;33import java.nio.file.Paths;34import java.nio.file.Path;35import java.util.ArrayList;36import java.util.HashMap;37import java.util.List;38import java.util.Map;39import java.util.Locale;40import java.util.Locale.LanguageRange;41import java.util.stream.Collectors;42import java.util.stream.Stream;4344import static java.util.Locale.LanguageRange.MAX_WEIGHT;45import static java.util.Locale.LanguageRange.MIN_WEIGHT;4647public class LSRDataTest {4849private static final char HYPHEN = '-';50private static final Map<String, String> singleLangEquivMap = new HashMap<>();51private static final Map<String, List<String>> multiLangEquivsMap = new HashMap<>();52private static final Map<String, String> regionVariantEquivMap = new HashMap<>();5354// path to the lsr file from the make folder, this test relies on the55// relative path to the file in the make folder, considering56// test and make will always exist in the same jdk layout57private static final String LSR_FILE_PATH = System.getProperty("test.src", ".")58+ "/../../../../../make/data/lsrdata/language-subtag-registry.txt";5960public static void main(String[] args) throws IOException {6162loadLSRData(Paths.get(LSR_FILE_PATH).toRealPath());6364// checking the tags with weight65String ranges = "Accept-Language: aam, adp, aue, bcg, cqu, ema,"66+ " en-gb-oed, gti, koj, kwq, kxe, lii, lmm, mtm, ngv,"67+ " oyb, phr, pub, suj, taj;q=0.9, yug;q=0.5, gfx;q=0.4";68List<LanguageRange> expected = parse(ranges);69List<LanguageRange> actual = LanguageRange.parse(ranges);70checkEquality(actual, expected);7172// checking all language ranges73ranges = generateLangRanges();74expected = parse(ranges);75actual = LanguageRange.parse(ranges);76checkEquality(actual, expected);7778// checking all region/variant ranges79ranges = generateRegionRanges();80expected = parse(ranges);81actual = LanguageRange.parse(ranges);82checkEquality(actual, expected);8384}8586// generate range string containing all equiv language tags87private static String generateLangRanges() {88return Stream.concat(singleLangEquivMap.keySet().stream(), multiLangEquivsMap89.keySet().stream()).collect(Collectors.joining(","));90}9192// generate range string containing all equiv region tags93private static String generateRegionRanges() {94return regionVariantEquivMap.keySet().stream()95.map(r -> "en".concat(r)).collect(Collectors.joining(", "));96}9798// load LSR data from the file99private static void loadLSRData(Path path) throws IOException {100String type = null;101String tag = null;102String preferred = null;103String prefix = null;104105for (String line : Files.readAllLines(path, Charset.forName("UTF-8"))) {106line = line.toLowerCase(Locale.ROOT);107int index = line.indexOf(' ') + 1;108if (line.startsWith("type:")) {109type = line.substring(index);110} else if (line.startsWith("tag:") || line.startsWith("subtag:")) {111tag = line.substring(index);112} else if (line.startsWith("preferred-value:")) {113preferred = line.substring(index);114} else if (line.startsWith("prefix:")) {115prefix = line.substring(index);116} else if (line.equals("%%")) {117processDataAndGenerateMaps(type, tag, preferred, prefix);118type = null;119tag = null;120preferred = null;121prefix = null;122}123}124125// Last entry126processDataAndGenerateMaps(type, tag, preferred, prefix);127}128129private static void processDataAndGenerateMaps(String type,130String tag,131String preferred,132String prefix) {133134if (type == null || tag == null || preferred == null) {135return;136}137138if (type.equals("extlang") && prefix != null) {139tag = prefix + "-" + tag;140}141142if (type.equals("region") || type.equals("variant")) {143if (!regionVariantEquivMap.containsKey(preferred)) {144String tPref = HYPHEN + preferred;145String tTag = HYPHEN + tag;146regionVariantEquivMap.put(tPref, tTag);147regionVariantEquivMap.put(tTag, tPref);148} else {149throw new RuntimeException("New case, need implementation."150+ " A region/variant subtag \"" + preferred151+ "\" is registered for more than one subtags.");152}153} else { // language, extlang, legacy, and redundant154if (!singleLangEquivMap.containsKey(preferred)155&& !multiLangEquivsMap.containsKey(preferred)) {156// new entry add it into single equiv map157singleLangEquivMap.put(preferred, tag);158singleLangEquivMap.put(tag, preferred);159} else if (singleLangEquivMap.containsKey(preferred)160&& !multiLangEquivsMap.containsKey(preferred)) {161String value = singleLangEquivMap.get(preferred);162List<String> subtags = List.of(preferred, value, tag);163// remove from single eqiv map before adding to multi equiv164singleLangEquivMap.keySet().removeAll(subtags);165addEntriesToMultiEquivsMap(subtags);166} else if (multiLangEquivsMap.containsKey(preferred)167&& !singleLangEquivMap.containsKey(preferred)) {168List<String> subtags = multiLangEquivsMap.get(preferred);169// should use the order preferred, subtags, tag to keep the170// expected order same as the JDK API in multi equivalent maps171subtags.add(0, preferred);172subtags.add(tag);173addEntriesToMultiEquivsMap(subtags);174}175}176}177178// Add entries into the multi equivalent map from the given subtags179private static void addEntriesToMultiEquivsMap(List<String> subtags) {180// for each subtag within the given subtags, add an entry in multi181// equivalent language map with subtag as the key and the value182// as the list of all subtags excluding the one which is getting183// traversed184subtags.forEach(subtag -> multiLangEquivsMap.put(subtag, subtags.stream()185.filter(t -> !t.equals(subtag))186.collect(Collectors.toList())));187}188189private static List<LanguageRange> parse(String ranges) {190ranges = ranges.replace(" ", "").toLowerCase(Locale.ROOT);191if (ranges.startsWith("accept-language:")) {192ranges = ranges.substring(16);193}194String[] langRanges = ranges.split(",");195List<LanguageRange> priorityList = new ArrayList<>(langRanges.length);196int numOfRanges = 0;197for (String range : langRanges) {198int wIndex = range.indexOf(";q=");199String tag;200double weight = 0.0;201if (wIndex == -1) {202tag = range;203weight = MAX_WEIGHT;204} else {205tag = range.substring(0, wIndex);206try {207weight = Double.parseDouble(range.substring(wIndex + 3));208} catch (RuntimeException ex) {209throw new IllegalArgumentException("weight= " + weight + " for"210+ " language range \"" + tag + "\", should be"211+ " represented as a double");212}213214if (weight < MIN_WEIGHT || weight > MAX_WEIGHT) {215throw new IllegalArgumentException("weight=" + weight216+ " for language range \"" + tag217+ "\", must be between " + MIN_WEIGHT218+ " and " + MAX_WEIGHT + ".");219}220}221222LanguageRange entry = new LanguageRange(tag, weight);223if (!priorityList.contains(entry)) {224225int index = numOfRanges;226// find the index in the list to add the current range at the227// correct index sorted by the descending order of weight228for (int i = 0; i < priorityList.size(); i++) {229if (priorityList.get(i).getWeight() < weight) {230index = i;231break;232}233}234priorityList.add(index, entry);235numOfRanges++;236237String equivalent = getEquivalentForRegionAndVariant(tag);238if (equivalent != null) {239LanguageRange equivRange = new LanguageRange(equivalent, weight);240if (!priorityList.contains(equivRange)) {241priorityList.add(index + 1, equivRange);242numOfRanges++;243}244}245246List<String> equivalents = getEquivalentsForLanguage(tag);247if (equivalents != null) {248for (String equiv : equivalents) {249LanguageRange equivRange = new LanguageRange(equiv, weight);250if (!priorityList.contains(equivRange)) {251priorityList.add(index + 1, equivRange);252numOfRanges++;253}254255equivalent = getEquivalentForRegionAndVariant(equiv);256if (equivalent != null) {257equivRange = new LanguageRange(equivalent, weight);258if (!priorityList.contains(equivRange)) {259priorityList.add(index + 1, equivRange);260numOfRanges++;261}262}263}264}265}266}267return priorityList;268}269270/**271* A faster alternative approach to String.replaceFirst(), if the given272* string is a literal String, not a regex.273*/274private static String replaceFirstSubStringMatch(String range,275String substr, String replacement) {276int pos = range.indexOf(substr);277if (pos == -1) {278return range;279} else {280return range.substring(0, pos) + replacement281+ range.substring(pos + substr.length());282}283}284285private static List<String> getEquivalentsForLanguage(String range) {286String r = range;287288while (r.length() > 0) {289if (singleLangEquivMap.containsKey(r)) {290String equiv = singleLangEquivMap.get(r);291// Return immediately for performance if the first matching292// subtag is found.293return List.of(replaceFirstSubStringMatch(range, r, equiv));294} else if (multiLangEquivsMap.containsKey(r)) {295List<String> equivs = multiLangEquivsMap.get(r);296List<String> result = new ArrayList(equivs.size());297for (int i = 0; i < equivs.size(); i++) {298result.add(i, replaceFirstSubStringMatch(range,299r, equivs.get(i)));300}301return result;302}303304// Truncate the last subtag simply.305int index = r.lastIndexOf(HYPHEN);306if (index == -1) {307break;308}309r = r.substring(0, index);310}311312return null;313}314315private static String getEquivalentForRegionAndVariant(String range) {316int extensionKeyIndex = getExtentionKeyIndex(range);317318for (String subtag : regionVariantEquivMap.keySet()) {319int index;320if ((index = range.indexOf(subtag)) != -1) {321// Check if the matching text is a valid region or variant.322if (extensionKeyIndex != Integer.MIN_VALUE323&& index > extensionKeyIndex) {324continue;325}326327int len = index + subtag.length();328if (range.length() == len || range.charAt(len) == HYPHEN) {329return replaceFirstSubStringMatch(range, subtag,330regionVariantEquivMap.get(subtag));331}332}333}334335return null;336}337338private static int getExtentionKeyIndex(String s) {339char[] c = s.toCharArray();340int index = Integer.MIN_VALUE;341for (int i = 1; i < c.length; i++) {342if (c[i] == HYPHEN) {343if (i - index == 2) {344return index;345} else {346index = i;347}348}349}350return Integer.MIN_VALUE;351}352353private static void checkEquality(List<LanguageRange> expected,354List<LanguageRange> actual) {355356int expectedSize = expected.size();357int actualSize = actual.size();358359if (expectedSize != actualSize) {360throw new RuntimeException("[FAILED: Size of the priority list"361+ " does not match, Expected size=" + expectedSize + "]");362} else {363for (int i = 0; i < expectedSize; i++) {364LanguageRange lr1 = expected.get(i);365LanguageRange lr2 = actual.get(i);366367if (!lr1.getRange().equals(lr2.getRange())368|| lr1.getWeight() != lr2.getWeight()) {369throw new RuntimeException("[FAILED: Ranges at index "370+ i + " do not match Expected: range=" + lr1.getRange()371+ ", weight=" + lr1.getWeight() + ", Actual: range="372+ lr2.getRange() + ", weight=" + lr2.getWeight() + "]");373}374}375}376}377}378379380