Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package libcore.java.util;
     18 
     19 import static java.util.Locale.LanguageRange.MAX_WEIGHT;
     20 
     21 import java.util.ArrayList;
     22 import java.util.Arrays;
     23 import java.util.Collections;
     24 import java.util.HashMap;
     25 import java.util.List;
     26 import java.util.Locale.LanguageRange;
     27 import java.util.Map;
     28 import java.util.stream.Collectors;
     29 
     30 import junit.framework.TestCase;
     31 
     32 /**
     33  * Tests {@link LanguageRange}.
     34  */
     35 public class LocaleLanguageRangeTest extends TestCase {
     36 
     37     /**
     38      * Checks that the constants for min/max weight don't accidentally change.
     39      */
     40     public void testWeight_constantValues() {
     41         assertEquals(0.0, LanguageRange.MIN_WEIGHT);
     42         assertEquals(1.0, MAX_WEIGHT);
     43     }
     44 
     45     public void testConstructor_defaultsToMaxWeight() {
     46         assertEquals(MAX_WEIGHT, new LanguageRange("de-DE").getWeight());
     47     }
     48 
     49     public void testConstructor_invalidWeight() {
     50         try {
     51             new LanguageRange("de-DE", -0.00000001);
     52             fail();
     53         } catch (IllegalArgumentException expected) {
     54 
     55         }
     56         try {
     57             new LanguageRange("de-DE", 1.00000001);
     58             fail();
     59         } catch (IllegalArgumentException expected) {
     60         }
     61         // These work:
     62         new LanguageRange("de-DE", 0);
     63         new LanguageRange("de-DE", 1);
     64     }
     65 
     66     public void testConstructor_nullRange() {
     67         try {
     68             new LanguageRange(null);
     69             fail();
     70         } catch (NullPointerException expected) {
     71         }
     72         try {
     73             new LanguageRange(null, MAX_WEIGHT);
     74             fail();
     75         } catch (NullPointerException expected) {
     76         }
     77         new LanguageRange("de-DE", MAX_WEIGHT); // works
     78     }
     79 
     80     public void testConstructor_checksForAtLeastOneSubtag() {
     81         assertRangeMalformed("");
     82         // The fact that ArrayIndexOutOfBoundsException instead of
     83         // IllegalArgumentException is thrown here is somewhat
     84         // inconsistent; the checks below ensure that we're aware
     85         // if we change the behavior in future.
     86         try {
     87             new LanguageRange("-");
     88             fail();
     89         } catch (ArrayIndexOutOfBoundsException expected) {
     90         }
     91         try {
     92             new LanguageRange("--");
     93             fail();
     94         } catch (ArrayIndexOutOfBoundsException expected) {
     95         }
     96     }
     97 
     98     public void testConstructor_checksForWellFormedSubtags() {
     99         // first subtag must not have digits
    100         assertRangeMalformed("012-xx");
    101         assertRangeMalformed("b0b-xx");
    102         new LanguageRange("bob-xx"); // okay
    103         new LanguageRange("bob-01"); // okay
    104 
    105         // subtags must be <= 8 characters
    106         assertRangeMalformed("de-abcdefghi-xx");
    107         new LanguageRange("de-abcdefgh-xx"); // okay
    108 
    109         // "-" only between subtags and only one in a row
    110         assertRangeMalformed("-de");
    111         assertRangeMalformed("de-");
    112         assertRangeMalformed("de--DE");
    113         new LanguageRange("de-DE"); // okay
    114         new LanguageRange("de"); // okay
    115     }
    116 
    117     public void testConstructor_acceptsWildcardSubtags() {
    118         new LanguageRange("de-*");
    119         new LanguageRange("*-DE");
    120         new LanguageRange("de-*-DE");
    121         new LanguageRange("*");
    122     }
    123 
    124     public void testEqualsAndHashCode() {
    125         checkEqual(new LanguageRange("en-US"), new LanguageRange("en-US"));
    126         checkNotEqual(new LanguageRange("en-US"), new LanguageRange("en-AU"));
    127 
    128         checkEqual(new LanguageRange("en-US"),
    129                 new LanguageRange("en-US", LanguageRange.MAX_WEIGHT));
    130         checkNotEqual(new LanguageRange("en-US"), new LanguageRange("en-US", 0.4));
    131 
    132         checkEqual(new LanguageRange("en-US", 0.3), new LanguageRange("en-US", 0.3));
    133         checkNotEqual(new LanguageRange("en-US", 0.3), new LanguageRange("en-US", 0.4));
    134         checkNotEqual(new LanguageRange("ja-JP", 0.5), new LanguageRange("de-DE", 0.5));
    135     }
    136 
    137     private static <T> void checkEqual(T a, T b) {
    138         assertEquals(a, b);
    139         assertEquals(b, a);
    140         assertEquals(a.hashCode(), b.hashCode());
    141     }
    142 
    143     private static <T> void checkNotEqual(T a, T b) {
    144         assertFalse(a.equals(b));
    145         assertFalse(b.equals(a));
    146         assertTrue(a.hashCode() != b.hashCode());
    147     }
    148 
    149     public void testGetRange() {
    150         assertEquals("de-de", new LanguageRange("de-DE", 0.12345).getRange());
    151     }
    152 
    153     public void testGetWeight() {
    154         assertEquals(0.12345, new LanguageRange("de-DE", 0.12345).getWeight());
    155     }
    156 
    157     public void testMapEquivalents_emptyList() {
    158         List<LanguageRange> noRange = Collections.emptyList();
    159         assertEquals(noRange, LanguageRange.mapEquivalents(noRange, Collections.emptyMap()));
    160         assertEquals(noRange, LanguageRange.mapEquivalents(noRange,
    161                 Collections.singletonMap("en-US", Arrays.asList("en-US", "en-AU", "en-UK"))));
    162     }
    163 
    164     public void testMapEquivalents_emptyMap_createsModifiableCopy() {
    165         List<LanguageRange> inputRanges = Collections.unmodifiableList(Arrays.asList(
    166                 new LanguageRange("de-DE"),
    167                 new LanguageRange("ja-JP")));
    168         List<LanguageRange> outputRanges =
    169                 LanguageRange.mapEquivalents(inputRanges, Collections.emptyMap());
    170         assertEquals(inputRanges, outputRanges);
    171         assertNotSame(inputRanges, outputRanges);
    172         // result is modifiable
    173         outputRanges.add(new LanguageRange("fr-FR"));
    174         outputRanges.clear();
    175     }
    176 
    177     /**
    178      * Tests the example from the {@link LanguageRange#mapEquivalents(List, Map)} documentation.
    179      */
    180     public void testMapEquivalents_exampleFromDocumentation() {
    181         Map<String, List<String>> map = new HashMap<>();
    182         map.put("zh", Collections.unmodifiableList(Arrays.asList("zh", "zh-Hans")));
    183         map.put("zh-HK", Collections.singletonList("zh-HK"));
    184         map.put("zh-TW", Collections.singletonList("zh-TW"));
    185 
    186         List<LanguageRange> inputPriorityList = Arrays.asList(
    187                 new LanguageRange("zh"),
    188                 new LanguageRange("zh-CN"),
    189                 new LanguageRange("en"),
    190                 new LanguageRange("zh-TW"),
    191                 new LanguageRange("zh-TW")
    192         );
    193         List<LanguageRange> expectedOutput = Arrays.asList(
    194                 new LanguageRange("zh"),
    195                 new LanguageRange("zh-Hans"),
    196                 new LanguageRange("zh-CN"),
    197                 new LanguageRange("zh-Hans-CN"),
    198                 new LanguageRange("en"),
    199                 new LanguageRange("zh-TW"),
    200                 new LanguageRange("zh-TW")
    201         );
    202         List<LanguageRange> outputProrityList = LanguageRange
    203                 .mapEquivalents(inputPriorityList, map);
    204         assertEquals(expectedOutput, outputProrityList);
    205     }
    206 
    207     public void testMapEquivalents_nullList() {
    208         try {
    209             LanguageRange.mapEquivalents(null, Collections.emptyMap());
    210             fail();
    211         } catch (NullPointerException expected) {
    212         }
    213     }
    214 
    215     /**
    216      * The documentation doesn't specify whether {@code mapEquivalents()} accepts a
    217      * null map, but the current behavior is the same as for an empty map. This test
    218      * ensures that we're aware if this behavior changse.
    219      */
    220     public void testMapEquivalents_nullMap() {
    221         List<LanguageRange> priorityList = Collections.unmodifiableList(Arrays.asList(
    222                 new LanguageRange("de-DE"),
    223                 new LanguageRange("en-UK"),
    224                 new LanguageRange("zh-CN")));
    225         assertEquals(priorityList, LanguageRange.mapEquivalents(priorityList, null));
    226     }
    227 
    228     /** Tests {@link LanguageRange#parse(String, Map)}. */
    229     public void testMapEquivalents() {
    230         List<LanguageRange> expected = Arrays.asList(
    231                 new LanguageRange("de-de", 1.0),
    232                 new LanguageRange("en-us", 0.7),
    233                 new LanguageRange("en-au", 0.7)
    234         );
    235         Map<String, List<String>> map = new HashMap<>();
    236         map.put("fr", Arrays.asList("de-DE"));
    237         map.put("en", Arrays.asList("en-US", "en-AU"));
    238         String ranges = "Accept-Language: fr,en;q=0.7";
    239         assertEquals(expected, LanguageRange.parse(ranges, map));
    240         // Per the documentation, this should be equivalent
    241         assertEquals(expected, LanguageRange.mapEquivalents(LanguageRange.parse(ranges), map));
    242     }
    243 
    244     /**
    245      * Because {@code mapEquivalents(ranges, map)} behaves identically
    246      * to {@code mapEquivalents(parse(ranges), map}, any equivalent
    247      * locales from {@link sun.util.locale.LocaleEquivalentMaps},
    248      * such as {@code "iw" -> "he"}, are expanded before the mapping
    249      * from {@code map} is applied.
    250      */
    251     public void testParse_map_localeEquivalent() {
    252         Map<String, List<String>> map = new HashMap<>();
    253         map.put("iw", Arrays.asList("de-DE"));
    254         map.put("en", Arrays.asList("en-US", "en-AU"));
    255 
    256         List<LanguageRange> expectedOutput = Arrays.asList(
    257                 new LanguageRange("de-de", 1.0), // iw -> de-de (map)
    258                 new LanguageRange("he", 1.0), // iw -> he (LocaleEquivalentMaps)
    259                 new LanguageRange("en-us", 0.7), // en -> en-us (map)
    260                 new LanguageRange("en-au", 0.7)); // en -> en-au (map)
    261 
    262         String ranges = "Accept-Language: iw,en;q=0.7";
    263         assertEquals(expectedOutput, LanguageRange.parse(ranges, map));
    264         // Per the documentation, this should be equivalent
    265         assertEquals(expectedOutput,
    266                 LanguageRange.mapEquivalents(LanguageRange.parse(ranges), map));
    267     }
    268 
    269     /**
    270      * Tests the example from the {@link LanguageRange#parse(String)} documentation.
    271      */
    272     public void testParse_acceptLanguage_exampleFromDocumentation() {
    273         List<LanguageRange> expected = Arrays.asList(
    274                 new LanguageRange("iw", 1.0),
    275                 new LanguageRange("he", 1.0),
    276                 new LanguageRange("en-us", 0.7),
    277                 new LanguageRange("en", 0.3)
    278         );
    279         assertEquals(expected, LanguageRange.parse("Accept-Language: iw,en-us;q=0.7,en;q=0.3"));
    280     }
    281 
    282     /**
    283      * Tests parsing the example from RFC 2616 section 14.4.
    284      */
    285     public void testParse_acceptLanguage_exampleFromRfc2616() {
    286         List<LanguageRange> expected = Arrays.asList(
    287                 new LanguageRange("da", 1.0),
    288                 new LanguageRange("en-gb", 0.8),
    289                 new LanguageRange("en", 0.7)
    290         );
    291         assertEquals(expected, LanguageRange.parse("Accept-Language: da, en-gb;q=0.8, en;q=0.7"));
    292     }
    293 
    294     public void testParse_acceptLanguage_malformed() {
    295         try {
    296             LanguageRange.parse("Accept-Language: fr,en-us;q=1;q=0.5");
    297             fail();
    298         } catch (IllegalArgumentException expected) {
    299         }
    300         try {
    301             LanguageRange.parse("Accept-Language: q=0.5");
    302             fail();
    303         } catch (IllegalArgumentException expected) {
    304         }
    305         try {
    306             LanguageRange.parse("Accept-Language: ;q=0.5");
    307             fail();
    308         } catch (IllegalArgumentException expected) {
    309         }
    310         try {
    311             LanguageRange.parse("Accept-Language: thislanguagetagistoolong;q=0.5");
    312             fail();
    313         } catch (IllegalArgumentException expected) {
    314         }
    315     }
    316 
    317     /**
    318      * The current implementation doesn't require a ' ' after the "Accept-Language:".
    319      * This test ensures that we're aware if this behavior changes.
    320      */
    321     public void testParse_acceptLanguage_missingSpaceAfterColon() {
    322         List<LanguageRange> languageRanges = Arrays.asList(
    323                 new LanguageRange("fr"),
    324                 new LanguageRange("en-us", 1)
    325         );
    326         assertEquals(languageRanges, LanguageRange.parse("Accept-Language:fr,en-us;q=1"));
    327     }
    328 
    329     public void testParse_acceptLanguage_wildCards() {
    330         List<LanguageRange> expected = Arrays.asList(
    331                 new LanguageRange("da", 1.0),
    332                 new LanguageRange("en-*", 0.8),
    333                 new LanguageRange("*", 0.7)
    334         );
    335         assertEquals(expected, LanguageRange.parse("Accept-Language: da, en-*;q=0.8, *;q=0.7"));
    336     }
    337 
    338     public void testParse_acceptLanguage_weightValid() {
    339         LanguageRange fr = new LanguageRange("fr");
    340         assertEquals(Arrays.asList(fr, new LanguageRange("en-us", 1.0)),
    341                 LanguageRange.parse("Accept-Language: fr,en-us;q=1"));
    342         assertEquals(Arrays.asList(fr, new LanguageRange("en-us", 0.1)),
    343                 LanguageRange.parse("Accept-Language: fr,en-us;q=.1"));
    344         assertEquals(Arrays.asList(fr, new LanguageRange("en-us", 0.12345678901234567890)),
    345                 LanguageRange.parse("Accept-Language: fr,en-us;q=0.12345678901234567890"));
    346         assertEquals(Arrays.asList(fr, new LanguageRange("en-us", 0)),
    347                 LanguageRange.parse("Accept-Language: fr,en-us;q=0"));
    348     }
    349 
    350     public void testParse_acceptLanguage_weightInvalid() {
    351         try {
    352             LanguageRange.parse("Accept-Language: iw,en-us;q=1.1");
    353             fail();
    354         } catch (IllegalArgumentException expected) {
    355         }
    356         try {
    357             LanguageRange.parse("Accept-Language: iw,en-us;q=-0.1");
    358             fail();
    359         } catch (IllegalArgumentException expected) {
    360         }
    361     }
    362 
    363     // Based on a test case that was contributed back to upstream maintainers through
    364     // https://bugs.openjdk.java.net/browse/JDK-8166994
    365     public void testParse_multiEquivalent_consistency() {
    366         List<String> parsed = rangesToStrings(LanguageRange.parse("ccq-xx"));
    367 
    368         assertEquals(parsed, rangesToStrings(LanguageRange.parse("ccq-xx"))); // consistency
    369         assertEquals(Arrays.asList("ccq-xx", "ybd-xx", "rki-xx"), parsed); // expected result
    370     }
    371 
    372     /**
    373      * Tests parsing a Locale range matching an entry from
    374      * {@link sun.util.locale.LocaleEquivalentMaps#singleEquivMap}.
    375      */
    376     public void testParse_singleEquivalent() {
    377         assertParseRanges("art-lojban", "jbo"); // example from RFC 4647 section 3.2
    378         assertParseRanges("yue", "zh-yue");
    379         assertParseRanges("yue-xx", "zh-yue-xx");
    380     }
    381 
    382     /**
    383      * Tests parsing a Locale range matching an entry from
    384      * {@link sun.util.locale.LocaleEquivalentMaps#multiEquivsMap}.
    385      */
    386     public void testParse_multiEquivalent() {
    387         assertParseRanges("mst", "myt", "mry");
    388         assertParseRanges("i-hak", "zh-hakka", "hak");
    389     }
    390 
    391     /**
    392      * Tests parsing a Locale range matching an entry from
    393      * {@link sun.util.locale.LocaleEquivalentMaps#regionVariantEquivMap}.
    394      */
    395     public void testParse_regionEquivalent() {
    396         // Region ("-de" or "-dd") matches the end
    397         assertParseRanges("de-de", "de-dd");
    398         assertParseRanges("xx-dd", "xx-de");
    399 
    400         // Region ("-de" or "-dd") matches the middle
    401         assertParseRanges("xx-de-yy", "xx-dd-yy");
    402         assertParseRanges("xx-dd-yy", "xx-de-yy");
    403 
    404         assertParseRanges("xx-bu", "xx-mm");
    405         assertParseRanges("xx-mm", "xx-bu");
    406     }
    407 
    408     /**
    409      * Tests parsing a Locale range matching entries from both
    410      * {@link sun.util.locale.LocaleEquivalentMaps#singleEquivMap} and
    411      * {@link sun.util.locale.LocaleEquivalentMaps#regionVariantEquivMap}.
    412      */
    413     public void testParse_singleAndRegionEquivalent() {
    414         assertParseRanges("sgn-ch-de", "sgg", "sgn-ch-dd");
    415         assertParseRanges("sgn-ch-de-xx", "sgg-xx", "sgn-ch-dd-xx");
    416     }
    417 
    418     /**
    419      * Asserts that {@code LanguageRange(ranges)} returns LanguageRanges whose
    420      * {@link LanguageRange#getRange() Range string}s are {@code ranges} and
    421      * {@code expectedAdditional}, in order.
    422      */
    423     private static void assertParseRanges(String ranges, String... expectedAdditional) {
    424         List<String> expected = new ArrayList<>();
    425         expected.add(ranges);
    426         expected.addAll(Arrays.asList(expectedAdditional));
    427 
    428         List<String> actual = rangesToStrings(LanguageRange.parse(ranges));
    429 
    430         assertEquals(expected, actual);
    431     }
    432 
    433     private static List<String> rangesToStrings(List<LanguageRange> languageRanges) {
    434         return languageRanges.stream().map(LanguageRange::getRange).collect(Collectors.toList());
    435     }
    436 
    437     private void assertRangeMalformed(String range) {
    438         try {
    439             new LanguageRange(range);
    440             fail("Range should be recognized as malformed: " + range);
    441         } catch (IllegalArgumentException expected) {
    442             // Check for the exception that is thrown when a malformed subtag is detected.
    443             // The exception message used here may change in future.
    444             assertEquals("range=" + range.toLowerCase(), expected.getMessage());
    445         }
    446     }
    447 
    448 }
    449