/* * Copyright (c) 2014, 2023, 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 8042360 8032842 8175539 8210443 8242010 8276302 * @summary Verify implementation for Locale matching. * @run testng/othervm LocaleMatchingTest */ import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Locale.FilteringMode; import java.util.Locale.LanguageRange; import java.util.Map; import static java.util.Locale.FilteringMode.*; import static java.util.Locale.LanguageRange.*; import static org.testng.Assert.*; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class LocaleMatchingTest { @DataProvider(name = "LRConstructorData") Object[][] LRConstructorData() { return new Object[][] { // Range, Weight {"elvish", MAX_WEIGHT}, {"de-DE", MAX_WEIGHT}, {"de-Latn-DE-1996", MAX_WEIGHT}, {"zh-Hant-CN-x-private1-private2", MAX_WEIGHT}, {"ar", 0.8}, {"en-US", 0.5}, {"sr-Latn-BA", 0}, {"ja", 1}, }; } @DataProvider(name = "LRConstructorNPEData") Object[][] LRConstructorNPEData() { return new Object[][] { // Range, Weight {null, MAX_WEIGHT}, {null, 0.8}, }; } @DataProvider(name = "LRConstructorIAEData") Object[][] LRConstructorIAEData() { return new Object[][] { // Range, Weight {"ja", -0.8}, {"Elvish", 3.0}, {"-ja", MAX_WEIGHT}, {"ja--JP", MAX_WEIGHT}, {"en-US-", MAX_WEIGHT}, {"a4r", MAX_WEIGHT}, {"ar*", MAX_WEIGHT}, {"ar-*EG", MAX_WEIGHT}, {"abcdefghijklmn", MAX_WEIGHT}, {"ja-J=", MAX_WEIGHT}, {"ja-opqrstuvwxyz", MAX_WEIGHT}, {"zh_CN", MAX_WEIGHT}, {"1996-de-Latn", MAX_WEIGHT}, // Testcase for 8042360 {"en-Latn-1234567890", MAX_WEIGHT}, }; } @DataProvider(name = "LRParseData") Object[][] LRParseData() { return new Object[][] { // Ranges, Expected result {"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=0.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", List.of(new LanguageRange("fr-fx", 1.0), new LanguageRange("fr-fr", 1.0), new LanguageRange("ja", 1.0), new LanguageRange("in-tpp", 1.0), new LanguageRange("id-tpp", 1.0), new LanguageRange("en-fr", 0.7), new LanguageRange("en-fx", 0.7), new LanguageRange("en-x-tp", 0.6), new LanguageRange("de-de", 0.5), new LanguageRange("de-dd", 0.5), new LanguageRange("iw", 0.4), new LanguageRange("he", 0.4), new LanguageRange("in-tp", 0.2), new LanguageRange("id-tl", 0.2), new LanguageRange("id-tp", 0.2), new LanguageRange("in-tl", 0.2), new LanguageRange("fr-tp-x-foo", 0.1), new LanguageRange("fr-tl-x-foo", 0.1))}, {"Accept-Language: hak-CN;q=0.8, no-bok-NO;q=0.9, no-nyn, cmn-CN;q=0.1", List.of(new LanguageRange("no-nyn", 1.0), new LanguageRange("nn", 1.0), new LanguageRange("no-bok-no", 0.9), new LanguageRange("nb-no", 0.9), new LanguageRange("hak-CN", 0.8), new LanguageRange("zh-hakka-CN", 0.8), new LanguageRange("i-hak-CN", 0.8), new LanguageRange("zh-hak-CN", 0.8), new LanguageRange("cmn-CN", 0.1), new LanguageRange("zh-guoyu-CN", 0.1), new LanguageRange("zh-cmn-CN", 0.1))}, {"Accept-Language: rki;q=0.4, no-bok-NO;q=0.9, ccq;q=0.5", List.of(new LanguageRange("no-bok-no", 0.9), new LanguageRange("nb-no", 0.9), new LanguageRange("rki", 0.4), new LanguageRange("ybd", 0.4), new LanguageRange("ccq", 0.4))}, }; } @DataProvider(name = "LRParseIAEData") Object[][] LRParseIAEData() { return new Object[][] { // Ranges {""}, {"ja;q=3"}, }; } @DataProvider(name = "LRMapEquivalentsData") Object[][] LRMapEquivalentsData() { return new Object[][] { // Ranges, Map, Expected result {LanguageRange.parse("zh, zh-TW;q=0.8, ar;q=0.9, EN, zh-HK, ja-JP;q=0.2, es;q=0.4"), new HashMap<>(), LanguageRange.parse("zh, zh-TW;q=0.8, ar;q=0.9, EN, zh-HK, ja-JP;q=0.2, es;q=0.4")}, {LanguageRange.parse("zh, zh-TW;q=0.8, ar;q=0.9, EN, zh-HK, ja-JP;q=0.2, es;q=0.4"), null, LanguageRange.parse("zh, zh-TW;q=0.8, ar;q=0.9, EN, zh-HK, ja-JP;q=0.2, es;q=0.4")}, {LanguageRange.parse("zh, zh-TW;q=0.8, ar;q=0.9, EN, zh-HK, ja-JP;q=0.2, es;q=0.4"), new LinkedHashMap>() { { put("ja", List.of("ja", "ja-Hira")); put("zh", List.of("zh-Hans", "zh-Hans-CN", "zh-CN")); put("zh-TW", List.of("zh-TW", "zh-Hant")); put("es", null); put("en", List.of()); put("zh-HK", List.of("de")); } }, List.of(new LanguageRange("zh-hans", 1.0), new LanguageRange("zh-hans-cn", 1.0), new LanguageRange("zh-cn", 1.0), new LanguageRange("de", 1.0), new LanguageRange("ar", 0.9), new LanguageRange("zh-tw", 0.8), new LanguageRange("zh-hant", 0.8), new LanguageRange("ja-jp", 0.2), new LanguageRange("ja-hira-jp", 0.2))}, }; } @DataProvider(name = "LFilterData") Object[][] LFilterData() { return new Object[][] { // Range, LanguageTags, FilteringMode, Expected locales {"ja-JP, fr-FR", "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP", EXTENDED_FILTERING, "ja-JP-hepburn, ja-Latn-JP"}, {"ja-*-JP, fr-FR", "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP", EXTENDED_FILTERING, "ja-JP-hepburn, ja-Latn-JP"}, {"ja-*-JP, fr-FR, de-de;q=0.2", "de-DE, en, ja-JP-hepburn, de-de, fr, he, ja-Latn-JP", AUTOSELECT_FILTERING, "ja-JP-hepburn, ja-Latn-JP, de-DE"}, {"ja-JP, fr-FR, de-de;q=0.2", "de-DE, en, ja-JP-hepburn, de-de, fr, he, ja-Latn-JP", AUTOSELECT_FILTERING, "ja-JP-hepburn, de-DE"}, {"en;q=0.2, ja-*-JP, fr-JP", "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP", IGNORE_EXTENDED_RANGES, "en"}, {"en;q=0.2, ja-*-JP, fr-JP", "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP", MAP_EXTENDED_RANGES, "ja-JP-hepburn, en"}, {"en;q=0.2, ja-JP, fr-JP", "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP", REJECT_EXTENDED_RANGES, "ja-JP-hepburn, en"}, {"en;q=0.2, ja-*-JP, fr-JP", "", REJECT_EXTENDED_RANGES, ""}, }; } @DataProvider(name = "LFilterNPEData") Object[][] LFilterNPEData() { return new Object[][] { // Range, LanguageTags, FilteringMode {"en;q=0.2, ja-*-JP, fr-JP", null, REJECT_EXTENDED_RANGES}, {null, "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP", REJECT_EXTENDED_RANGES}, }; } @DataProvider(name = "LFilterTagsData") Object[][] LFilterTagsData() { return new Object[][] { // Range, LanguageTags, FilteringMode, Expected language tags {"fr-FR, fr-BG;q=0.8, *;q=0.5, en;q=0", "en-US, fr-FR, fr-CA, fr-BG", null, "fr-FR, fr-BG, fr-CA"}, {"fr-FR, fr-*-BG;q=0.8, *;q=0.5, en;q=0", "en-US, fr-FR, fr-CA, fr-BG", null, "fr-FR, fr-BG, fr-CA"}, {"en;q=0.2, *;q=0.6, ja", "de-DE, en, ja-JP-hepburn, fr-JP, he", null, "ja-JP-hepburn, de-DE, en, fr-JP, he"}, {"en;q=0.2, ja-JP, fr-JP", "de-DE, en, ja-JP-hepburn, fr, he", null, "ja-JP-hepburn, en"}, {"en;q=0.2, ja-JP, fr-JP, iw", "de-DE, he, en, ja-JP-hepburn, fr, he-IL", null, "ja-JP-hepburn, he, he-IL, en"}, {"en;q=0.2, ja-JP, fr-JP, he", "de-DE, en, ja-JP-hepburn, fr, iw-IL", null, "ja-JP-hepburn, iw-IL, en"}, {"de-DE", "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", null, "de-DE, de-DE-x-goethe"}, {"de-*-DE", "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", null, "de-DE, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, " + "de-Latn-DE-1996, de-Deva-DE"}, {"de-DE", "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", EXTENDED_FILTERING, "de-DE, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, " + "de-Latn-DE-1996, de-Deva-DE"}, {"de-*-DE", "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", EXTENDED_FILTERING, "de-DE, de-Latn-DE, de-Latf-DE, de-DE-x-goethe, " + "de-Latn-DE-1996, de-Deva-DE"}, {"de-DE", "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", IGNORE_EXTENDED_RANGES, "de-DE, de-DE-x-goethe"}, {"de-*-DE", "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", IGNORE_EXTENDED_RANGES, ""}, {"de-DE", "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", MAP_EXTENDED_RANGES, "de-DE, de-DE-x-goethe"}, {"de-*-DE", "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", MAP_EXTENDED_RANGES, "de-DE, de-DE-x-goethe"}, {"de-DE", "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", REJECT_EXTENDED_RANGES, "de-DE, de-DE-x-goethe"}, // The next test in this chain is in testLFilterTagsIAE. }; } @DataProvider(name = "LLookupData") Object[][] LLookupData() { return new Object[][] { // Range, LanguageTags, Expected locale {"en;q=0.2, *-JP;q=0.6, iw", "de-DE, en, ja-JP-hepburn, fr-JP, he", "he"}, {"en;q=0.2, *-JP;q=0.6, iw", "de-DE, he-IL, en, iw", "he"}, {"en;q=0.2, ja-*-JP-x-foo;q=0.6, iw", "de-DE, fr, en, ja-Latn-JP", "ja-Latn-JP"}, }; } @DataProvider(name = "LLookupTagData") Object[][] LLookupTagData() { return new Object[][] { // Range, LanguageTags, Expected language tag {"en, *", "es, de, ja-JP", null}, {"en;q=0.2, *-JP", "de-DE, en, ja-JP-hepburn, fr-JP, en-JP", "fr-JP"}, {"en;q=0.2, ar-MO, iw", "de-DE, he, fr-JP", "he"}, {"en;q=0.2, ar-MO, he", "de-DE, iw, fr-JP", "iw"}, {"de-DE-1996;q=0.8, en;q=0.2, iw;q=0.9, zh-Hans-CN;q=0.7", "de-DE, zh-CN, he, iw, fr-JP", "iw"}, {"de-DE-1996;q=0.8, en;q=0.2, he;q=0.9, zh-Hans-CN;q=0.7", "de-DE, zh-CN, he, iw, fr-JP", "he"}, }; } @Test public void testLRConstants() { assertEquals(MIN_WEIGHT, 0.0, " MIN_WEIGHT should be 0.0 but got " + MIN_WEIGHT); assertEquals(MAX_WEIGHT, 1.0, " MAX_WEIGHT should be 1.0 but got " + MAX_WEIGHT); } @Test(dataProvider = "LRConstructorData") public void testLRConstructors(String range, double weight) { LanguageRange lr; if (weight == MAX_WEIGHT) { lr = new LanguageRange(range); } else { lr = new LanguageRange(range, weight); } assertEquals(lr.getRange(), range.toLowerCase(Locale.ROOT), " LR.getRange() returned unexpected value. Expected: " + range.toLowerCase(Locale.ROOT) + ", got: " + lr.getRange()); assertEquals(lr.getWeight(), weight, " LR.getWeight() returned unexpected value. Expected: " + weight + ", got: " + lr.getWeight()); } @Test(dataProvider = "LRConstructorNPEData", expectedExceptions = NullPointerException.class) public void testLRConstructorNPE(String range, double weight) { if (weight == MAX_WEIGHT) { new LanguageRange(range); } else { new LanguageRange(range, weight); } } @Test(dataProvider = "LRConstructorIAEData", expectedExceptions = IllegalArgumentException.class) public void testLRConstructorIAE(String range, double weight) { if (weight == MAX_WEIGHT) { new LanguageRange(range); } else { new LanguageRange(range, weight); } } @Test public void testLREquals() { 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); assertEquals(lr1, lr2, " LR(ja, 1.0).equals(LR(ja)) should return true."); assertNotEquals(lr1, lr3, " LR(ja, 1.0).equals(LR(ja, 0.1)) should return false."); assertNotEquals(lr1, lr4, " LR(ja, 1.0).equals(LR(en, 1.0)) should return false."); assertNotNull(lr1, " LR(ja, 1.0).equals(null) should return false."); assertNotEquals(lr1, "", " LR(ja, 1.0).equals(\"\") should return false."); } @Test(dataProvider = "LRParseData") public void testLRParse(String ranges, List expected) { assertEquals(LanguageRange.parse(ranges), expected, " LR.parse(" + ranges + ") test failed."); } @Test(expectedExceptions = NullPointerException.class) public void testLRParseNPE() { LanguageRange.parse(null); } @Test(dataProvider = "LRParseIAEData", expectedExceptions = IllegalArgumentException.class) public void testLRParseIAE(String ranges) { LanguageRange.parse(ranges); } @Test(dataProvider = "LRMapEquivalentsData") public void testLRMapEquivalents(List priorityList, Map> map, List expected) { assertEquals(LanguageRange.mapEquivalents(priorityList, map), expected, " LR.mapEquivalents() test failed."); } @Test(expectedExceptions = NullPointerException.class) public void testLRMapEquivalentsNPE() { LanguageRange.mapEquivalents(null, Map.of("ja", List.of("ja", "ja-Hira"))); } @Test(dataProvider = "LFilterData") public void testLFilter(String ranges, String tags, FilteringMode mode, String expectedLocales) { List priorityList = LanguageRange.parse(ranges); List tagList = generateLocales(tags); String actualLocales = showLocales(Locale.filter(priorityList, tagList, mode)); assertEquals(actualLocales, expectedLocales, showErrorMessage(" L.Filter(" + mode + ")", ranges, tags, expectedLocales, actualLocales)); } @Test(dataProvider = "LFilterNPEData", expectedExceptions = NullPointerException.class) public void testLFilterNPE(String ranges, String tags, FilteringMode mode) { List priorityList = LanguageRange.parse(ranges); List tagList = generateLocales(tags); showLocales(Locale.filter(priorityList, tagList, mode)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testLFilterIAE() { String ranges = "en;q=0.2, ja-*-JP, fr-JP"; String tags = "de-DE, en, ja-JP-hepburn, fr, he, ja-Latn-JP"; List priorityList = LanguageRange.parse(ranges); List tagList = generateLocales(tags); showLocales(Locale.filter(priorityList, tagList, REJECT_EXTENDED_RANGES)); } @Test(dataProvider = "LFilterTagsData") public void testLFilterTags(String ranges, String tags, FilteringMode mode, String expectedTags) { List priorityList = LanguageRange.parse(ranges); List tagList = generateLanguageTags(tags); String actualTags; if (mode == null) { actualTags = showLanguageTags(Locale.filterTags(priorityList, tagList)); } else { actualTags = showLanguageTags(Locale.filterTags(priorityList, tagList, mode)); } assertEquals(actualTags, expectedTags, showErrorMessage(" L.FilterTags(" + (mode != null ? mode : "") + ")", ranges, tags, expectedTags, actualTags)); } @Test(expectedExceptions = IllegalArgumentException.class) public void testLFilterTagsIAE() { String ranges = "de-*-DE"; String 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"; List priorityList = LanguageRange.parse(ranges); showLanguageTags(Locale.filterTags(priorityList, generateLanguageTags(tags), REJECT_EXTENDED_RANGES)); } @Test(dataProvider = "LLookupData") public void testLLookup(String ranges, String tags, String expectedLocale) { List priorityList = LanguageRange.parse(ranges); List localeList = generateLocales(tags); String actualLocale = Locale.lookup(priorityList, localeList).toLanguageTag(); assertEquals(actualLocale, expectedLocale, showErrorMessage(" L.Lookup()", ranges, tags, expectedLocale, actualLocale)); } @Test(dataProvider = "LLookupTagData") public void testLLookupTag(String ranges, String tags, String expectedTag) { List priorityList = LanguageRange.parse(ranges); List tagList = generateLanguageTags(tags); String actualTag = Locale.lookupTag(priorityList, tagList); assertEquals(actualTag, expectedTag, showErrorMessage(" L.LookupTag()", ranges, tags, expectedTag, actualTag)); } private static List generateLocales(String tags) { if (tags == null) { return null; } List 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 generateLanguageTags(String tags) { List tagList = new ArrayList<>(); String[] t = tags.split(", "); for (String tag : t) { tagList.add(tag); } return tagList; } private static String showLanguageTags(List tags) { StringBuilder sb = new StringBuilder(); Iterator 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 locales) { StringBuilder sb = new StringBuilder(); Iterator 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 String showErrorMessage(String methodName, String priorityList, String tags, String expectedTags, String actualTags) { return "Incorrect " + methodName + " result." + " Priority list : " + priorityList + " Language tags : " + tags + " Expected value : " + expectedTags + " Actual value : " + actualTags; } }