8381644: Locale matching APIs should describe null element behavior

Reviewed-by: naoto
This commit is contained in:
Justin Lu 2026-05-12 16:23:06 +00:00
parent b32585a0a2
commit a2ea276e83
3 changed files with 75 additions and 39 deletions

View File

@ -3493,7 +3493,8 @@ public final class Locale implements Cloneable, Serializable {
* 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}
* are {@code null}. {@code NullPointerException} may be thrown if any
* elements within either {@code Collection} are {@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
@ -3503,6 +3504,8 @@ public final class Locale implements Cloneable, Serializable {
public static List<Locale> filter(List<LanguageRange> priorityList,
Collection<Locale> locales,
FilteringMode mode) {
Objects.requireNonNull(priorityList);
Objects.requireNonNull(locales);
return LocaleMatcher.filter(priorityList, locales, mode);
}
@ -3522,12 +3525,15 @@ public final class Locale implements Cloneable, Serializable {
* 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}
* are {@code null}. {@code NullPointerException} may be thrown if any
* elements within either {@code Collection} are {@code null}.
*
* @since 1.8
*/
public static List<Locale> filter(List<LanguageRange> priorityList,
Collection<Locale> locales) {
Objects.requireNonNull(priorityList);
Objects.requireNonNull(locales);
return filter(priorityList, locales, FilteringMode.AUTOSELECT_FILTERING);
}
@ -3553,8 +3559,9 @@ public final class Locale implements Cloneable, Serializable {
* @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 NullPointerException if {@code priorityList} or {@code tags}
* are {@code null}. {@code NullPointerException} may be thrown if any
* elements within either {@code Collection} are {@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
@ -3564,6 +3571,8 @@ public final class Locale implements Cloneable, Serializable {
public static List<String> filterTags(List<LanguageRange> priorityList,
Collection<String> tags,
FilteringMode mode) {
Objects.requireNonNull(priorityList);
Objects.requireNonNull(tags);
return LocaleMatcher.filterTags(priorityList, tags, mode);
}
@ -3590,13 +3599,15 @@ public final class Locale implements Cloneable, Serializable {
* @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 NullPointerException if {@code priorityList} or {@code tags}
* are {@code null}. {@code NullPointerException} may be thrown if any
* elements within either {@code Collection} are {@code null}.
* @since 1.8
*/
public static List<String> filterTags(List<LanguageRange> priorityList,
Collection<String> tags) {
Objects.requireNonNull(priorityList);
Objects.requireNonNull(tags);
return filterTags(priorityList, tags, FilteringMode.AUTOSELECT_FILTERING);
}
@ -3609,13 +3620,15 @@ public final class Locale implements Cloneable, Serializable {
* @param locales {@code Locale} instances used for matching
* @return the best matching {@code Locale} instance chosen based on
* priority or weight, or {@code null} if nothing matches.
* @throws NullPointerException if {@code priorityList} or {@code locales} is
* {@code null}
*
* @throws NullPointerException if {@code priorityList} or {@code locales}
* are {@code null}. {@code NullPointerException} may be thrown if any
* elements within either {@code Collection} are {@code null}.
* @since 1.8
*/
public static Locale lookup(List<LanguageRange> priorityList,
Collection<Locale> locales) {
Objects.requireNonNull(priorityList);
Objects.requireNonNull(locales);
return LocaleMatcher.lookup(priorityList, locales);
}
@ -3631,13 +3644,15 @@ public final class Locale implements Cloneable, Serializable {
* @param tags language tags 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}
*
* @throws NullPointerException if {@code priorityList} or {@code tags}
* are {@code null}. {@code NullPointerException} may be thrown if any
* elements within either {@code Collection} are {@code null}.
* @since 1.8
*/
public static String lookupTag(List<LanguageRange> priorityList,
Collection<String> tags) {
Objects.requireNonNull(priorityList);
Objects.requireNonNull(tags);
return LocaleMatcher.lookupTag(priorityList, tags);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026, 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
@ -30,12 +30,17 @@ import java.util.Collection;
import java.util.HashMap;
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.Locale.FilteringMode;
import java.util.Locale.LanguageRange;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Objects;
import static java.util.Locale.FilteringMode.AUTOSELECT_FILTERING;
import static java.util.Locale.FilteringMode.EXTENDED_FILTERING;
import static java.util.Locale.FilteringMode.MAP_EXTENDED_RANGES;
import static java.util.Locale.FilteringMode.REJECT_EXTENDED_RANGES;
import static java.util.Locale.LanguageRange.MAX_WEIGHT;
import static java.util.Locale.LanguageRange.MIN_WEIGHT;
/**
* Implementation for BCP47 Locale matching

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2026, 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
@ -23,13 +23,14 @@
/*
* @test
* @bug 7069824 8042360 8032842 8175539 8210443 8242010 8276302
* @bug 7069824 8042360 8032842 8175539 8210443 8242010 8276302 8381644
* @summary Verify implementation for Locale matching.
* @run junit/othervm LocaleMatchingTest
*/
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.ArrayList;
@ -41,6 +42,7 @@ import java.util.Locale;
import java.util.Locale.FilteringMode;
import java.util.Locale.LanguageRange;
import java.util.Map;
import java.util.stream.Stream;
import static java.util.Locale.FilteringMode.*;
import static java.util.Locale.LanguageRange.*;
@ -200,12 +202,13 @@ public class LocaleMatchingTest {
};
}
static 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},
};
static Stream<Arguments> LFilterLookupNPEData() {
return Stream.of(
// null Locale list
Arguments.of(List.of(), null),
// null priority list
Arguments.of(null, List.of())
);
}
static Object[][] LFilterTagsData() {
@ -392,19 +395,12 @@ public class LocaleMatchingTest {
ranges, tags, expectedLocales, actualLocales));
}
@MethodSource("LFilterNPEData")
@MethodSource("LFilterLookupNPEData")
@ParameterizedTest
void testLFilterNPE(String ranges, String tags, FilteringMode mode) {
if (ranges == null) {
// Ranges are null
assertThrows(NullPointerException.class, () -> LanguageRange.parse(ranges));
} else {
// Tags are null
List<LanguageRange> priorityList = LanguageRange.parse(ranges);
List<Locale> tagList = generateLocales(tags);
assertThrows(NullPointerException.class,
() -> showLocales(Locale.filter(priorityList, tagList, mode)));
}
void testLFilterNPE(List<LanguageRange> priorityList, List<Locale> locList) {
assertThrows(NullPointerException.class, () -> Locale.filter(priorityList, locList));
// Exercise 3-arg variant
assertThrows(NullPointerException.class, () -> Locale.filter(priorityList, locList, REJECT_EXTENDED_RANGES));
}
@Test
@ -433,6 +429,14 @@ public class LocaleMatchingTest {
ranges, tags, expectedTags, actualTags));
}
@MethodSource("LFilterLookupNPEData")
@ParameterizedTest
void testLFilterTagsNPE(List<LanguageRange> priorityList, List<String> tagList) {
assertThrows(NullPointerException.class, () -> Locale.filterTags(priorityList, tagList));
// Exercise 3-arg variant
assertThrows(NullPointerException.class, () -> Locale.filterTags(priorityList, tagList, REJECT_EXTENDED_RANGES));
}
@Test
void testLFilterTagsIAE() {
String ranges = "de-*-DE";
@ -454,6 +458,12 @@ public class LocaleMatchingTest {
ranges, tags, expectedLocale, actualLocale));
}
@MethodSource("LFilterLookupNPEData")
@ParameterizedTest
void testLLookupNPE(List<LanguageRange> priorityList, List<Locale> locList) {
assertThrows(NullPointerException.class, () -> Locale.lookup(priorityList, locList));
}
@MethodSource("LLookupTagData")
@ParameterizedTest
void testLLookupTag(String ranges, String tags, String expectedTag) {
@ -464,6 +474,12 @@ public class LocaleMatchingTest {
ranges, tags, expectedTag, actualTag));
}
@MethodSource("LFilterLookupNPEData")
@ParameterizedTest
void testLLookupTagsNPE(List<LanguageRange> priorityList, List<String> tagList) {
assertThrows(NullPointerException.class, () -> Locale.lookupTag(priorityList, tagList));
}
private static List<Locale> generateLocales(String tags) {
if (tags == null) {
return null;