-
Bug
-
Resolution: Fixed
-
P4
-
8, 11, 17, 18
-
b04
-
generic
-
generic
A DESCRIPTION OF THE PROBLEM :
Locale.filterTags methods ignore actual weight when matching "*" (as if it is 1) because LocaleMatcher.{filterBasic,filterExtended} do so.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached tests.
---------- BEGIN SOURCE ----------
import java.util.List;
import java.util.Locale;
class LocaleFilterTagsTest {
public static void main(String[] args) {
assertFilterTags("fr-FR, fr-BG;q=0.8, *;q=0.5, en;q=0",
List.of("en-US", "fr-FR", "fr-CA", "fr-BG"), List.of("fr-FR", "fr-BG", "fr-CA"));
assertFilterTags("fr-FR, fr-*-BG;q=0.8, *;q=0.5, en;q=0",
List.of("en-US", "fr-FR", "fr-CA", "fr-BG"), List.of("fr-FR", "fr-BG", "fr-CA"));
assertFilterTags("en;q=0.2, *;q=0.6, ja",
List.of("de-DE", "en", "ja-JP-hepburn", "fr-JP", "he"),
List.of("ja-JP-hepburn", "de-DE", "en", "fr-JP", "he"));
}
private static void assertFilterTags(String ranges, List<String> tags,
List<String> expectedTags) {
final List<String> actualTags = Locale.filterTags(Locale.LanguageRange.parse(ranges), tags);
if (!expectedTags.equals(actualTags)) {
throw new AssertionError(
String.format("Expected: %s, Actual: %s.", expectedTags, actualTags));
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Locale.filterTags methods ignore actual weight when matching "*" (as if
it is 1) because LocaleMatcher.{filterBasic,filterExtended} do so.
Fix the bug and add regression test cases for it as well as existing
behavior.
---
.../sun/util/locale/LocaleMatcher.java | 78 +++++--------------
test/jdk/java/util/Locale/Bug7069824.java | 51 +++++++++++-
2 files changed, 67 insertions(+), 62 deletions(-)
diff --git a/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java b/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java
index 93629220de5..249aee9a705 100644
--- a/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java
+++ b/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java
@@ -124,8 +124,16 @@ public final class LocaleMatcher {
for (LanguageRange lr : nonZeroRanges) {
String range = lr.getRange();
if (range.equals("*")) {
- tags = removeTagsMatchingBasicZeroRange(zeroRanges, tags);
- return new ArrayList<String>(tags);
+ for (String tag : tags) {
+ String lowerCaseTag = tag.toLowerCase(Locale.ROOT);
+
+ if (!caseInsensitiveMatch(list, lowerCaseTag)
+ && !shouldIgnoreFilterBasicMatch(zeroRanges, lowerCaseTag)) {
+ list.add(tag);
+ }
+ }
+
+ break;
} else {
for (String tag : tags) {
// change to lowercase for case-insensitive matching
@@ -148,33 +156,6 @@ public final class LocaleMatcher {
return list;
}
- /**
- * Removes the tag(s) which are falling in the basic exclusion range(s) i.e
- * range(s) with q=0 and returns the updated collection. If the basic
- * language ranges contains '*' as one of its non zero range then instead of
- * returning all the tags, remove those which are matching the range with
- * quality weight q=0.
- */
- private static Collection<String> removeTagsMatchingBasicZeroRange(
- List<LanguageRange> zeroRange, Collection<String> tags) {
- if (zeroRange.isEmpty()) {
- tags = removeDuplicates(tags);
- return tags;
- }
-
- List<String> matchingTags = new ArrayList<>();
- for (String tag : tags) {
- // change to lowercase for case-insensitive matching
- String lowerCaseTag = tag.toLowerCase(Locale.ROOT);
- if (!shouldIgnoreFilterBasicMatch(zeroRange, lowerCaseTag)
- && !caseInsensitiveMatch(matchingTags, lowerCaseTag)) {
- matchingTags.add(tag); // preserving the case of the input tag
- }
- }
-
- return matchingTags;
- }
-
/**
* Remove duplicate tags from the given {@code tags} by
* ignoring case considerations.
@@ -240,8 +221,16 @@ public final class LocaleMatcher {
for (LanguageRange lr : nonZeroRanges) {
String range = lr.getRange();
if (range.equals("*")) {
- tags = removeTagsMatchingExtendedZeroRange(zeroRanges, tags);
- return new ArrayList<String>(tags);
+ for (String tag : tags) {
+ String lowerCaseTag = tag.toLowerCase(Locale.ROOT);
+
+ if (!caseInsensitiveMatch(list, lowerCaseTag)
+ && !shouldIgnoreFilterExtendedMatch(zeroRanges, lowerCaseTag)) {
+ list.add(tag);
+ }
+ }
+
+ break;
}
String[] rangeSubtags = range.split("-");
for (String tag : tags) {
@@ -267,33 +256,6 @@ public final class LocaleMatcher {
return list;
}
- /**
- * Removes the tag(s) which are falling in the extended exclusion range(s)
- * i.e range(s) with q=0 and returns the updated collection. If the extended
- * language ranges contains '*' as one of its non zero range then instead of
- * returning all the tags, remove those which are matching the range with
- * quality weight q=0.
- */
- private static Collection<String> removeTagsMatchingExtendedZeroRange(
- List<LanguageRange> zeroRange, Collection<String> tags) {
- if (zeroRange.isEmpty()) {
- tags = removeDuplicates(tags);
- return tags;
- }
-
- List<String> matchingTags = new ArrayList<>();
- for (String tag : tags) {
- // change to lowercase for case-insensitive matching
- String lowerCaseTag = tag.toLowerCase(Locale.ROOT);
- if (!shouldIgnoreFilterExtendedMatch(zeroRange, lowerCaseTag)
- && !caseInsensitiveMatch(matchingTags, lowerCaseTag)) {
- matchingTags.add(tag); // preserve the case of the input tag
- }
- }
-
- return matchingTags;
- }
-
/**
* The tag which is falling in the extended exclusion range(s) should
* not be considered as the matching tag. Ignores the tag matching with the
diff --git a/test/jdk/java/util/Locale/Bug7069824.java b/test/jdk/java/util/Locale/Bug7069824.java
index 0eb1f21c275..dce97ef1246 100644
--- a/test/jdk/java/util/Locale/Bug7069824.java
+++ b/test/jdk/java/util/Locale/Bug7069824.java
@@ -208,27 +208,61 @@ public class Bug7069824 {
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, "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",
- MAP_EXTENDED_RANGES, "de-DE, de-DE-x-goethe"},
+ 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-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-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.
};
}
@@ -380,6 +414,15 @@ public class Bug7069824 {
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<LanguageRange> 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<LanguageRange> priorityList = LanguageRange.parse(ranges);
--
2.25.1
FREQUENCY : always
Locale.filterTags methods ignore actual weight when matching "*" (as if it is 1) because LocaleMatcher.{filterBasic,filterExtended} do so.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached tests.
---------- BEGIN SOURCE ----------
import java.util.List;
import java.util.Locale;
class LocaleFilterTagsTest {
public static void main(String[] args) {
assertFilterTags("fr-FR, fr-BG;q=0.8, *;q=0.5, en;q=0",
List.of("en-US", "fr-FR", "fr-CA", "fr-BG"), List.of("fr-FR", "fr-BG", "fr-CA"));
assertFilterTags("fr-FR, fr-*-BG;q=0.8, *;q=0.5, en;q=0",
List.of("en-US", "fr-FR", "fr-CA", "fr-BG"), List.of("fr-FR", "fr-BG", "fr-CA"));
assertFilterTags("en;q=0.2, *;q=0.6, ja",
List.of("de-DE", "en", "ja-JP-hepburn", "fr-JP", "he"),
List.of("ja-JP-hepburn", "de-DE", "en", "fr-JP", "he"));
}
private static void assertFilterTags(String ranges, List<String> tags,
List<String> expectedTags) {
final List<String> actualTags = Locale.filterTags(Locale.LanguageRange.parse(ranges), tags);
if (!expectedTags.equals(actualTags)) {
throw new AssertionError(
String.format("Expected: %s, Actual: %s.", expectedTags, actualTags));
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Locale.filterTags methods ignore actual weight when matching "*" (as if
it is 1) because LocaleMatcher.{filterBasic,filterExtended} do so.
Fix the bug and add regression test cases for it as well as existing
behavior.
---
.../sun/util/locale/LocaleMatcher.java | 78 +++++--------------
test/jdk/java/util/Locale/Bug7069824.java | 51 +++++++++++-
2 files changed, 67 insertions(+), 62 deletions(-)
diff --git a/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java b/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java
index 93629220de5..249aee9a705 100644
--- a/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java
+++ b/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java
@@ -124,8 +124,16 @@ public final class LocaleMatcher {
for (LanguageRange lr : nonZeroRanges) {
String range = lr.getRange();
if (range.equals("*")) {
- tags = removeTagsMatchingBasicZeroRange(zeroRanges, tags);
- return new ArrayList<String>(tags);
+ for (String tag : tags) {
+ String lowerCaseTag = tag.toLowerCase(Locale.ROOT);
+
+ if (!caseInsensitiveMatch(list, lowerCaseTag)
+ && !shouldIgnoreFilterBasicMatch(zeroRanges, lowerCaseTag)) {
+ list.add(tag);
+ }
+ }
+
+ break;
} else {
for (String tag : tags) {
// change to lowercase for case-insensitive matching
@@ -148,33 +156,6 @@ public final class LocaleMatcher {
return list;
}
- /**
- * Removes the tag(s) which are falling in the basic exclusion range(s) i.e
- * range(s) with q=0 and returns the updated collection. If the basic
- * language ranges contains '*' as one of its non zero range then instead of
- * returning all the tags, remove those which are matching the range with
- * quality weight q=0.
- */
- private static Collection<String> removeTagsMatchingBasicZeroRange(
- List<LanguageRange> zeroRange, Collection<String> tags) {
- if (zeroRange.isEmpty()) {
- tags = removeDuplicates(tags);
- return tags;
- }
-
- List<String> matchingTags = new ArrayList<>();
- for (String tag : tags) {
- // change to lowercase for case-insensitive matching
- String lowerCaseTag = tag.toLowerCase(Locale.ROOT);
- if (!shouldIgnoreFilterBasicMatch(zeroRange, lowerCaseTag)
- && !caseInsensitiveMatch(matchingTags, lowerCaseTag)) {
- matchingTags.add(tag); // preserving the case of the input tag
- }
- }
-
- return matchingTags;
- }
-
/**
* Remove duplicate tags from the given {@code tags} by
* ignoring case considerations.
@@ -240,8 +221,16 @@ public final class LocaleMatcher {
for (LanguageRange lr : nonZeroRanges) {
String range = lr.getRange();
if (range.equals("*")) {
- tags = removeTagsMatchingExtendedZeroRange(zeroRanges, tags);
- return new ArrayList<String>(tags);
+ for (String tag : tags) {
+ String lowerCaseTag = tag.toLowerCase(Locale.ROOT);
+
+ if (!caseInsensitiveMatch(list, lowerCaseTag)
+ && !shouldIgnoreFilterExtendedMatch(zeroRanges, lowerCaseTag)) {
+ list.add(tag);
+ }
+ }
+
+ break;
}
String[] rangeSubtags = range.split("-");
for (String tag : tags) {
@@ -267,33 +256,6 @@ public final class LocaleMatcher {
return list;
}
- /**
- * Removes the tag(s) which are falling in the extended exclusion range(s)
- * i.e range(s) with q=0 and returns the updated collection. If the extended
- * language ranges contains '*' as one of its non zero range then instead of
- * returning all the tags, remove those which are matching the range with
- * quality weight q=0.
- */
- private static Collection<String> removeTagsMatchingExtendedZeroRange(
- List<LanguageRange> zeroRange, Collection<String> tags) {
- if (zeroRange.isEmpty()) {
- tags = removeDuplicates(tags);
- return tags;
- }
-
- List<String> matchingTags = new ArrayList<>();
- for (String tag : tags) {
- // change to lowercase for case-insensitive matching
- String lowerCaseTag = tag.toLowerCase(Locale.ROOT);
- if (!shouldIgnoreFilterExtendedMatch(zeroRange, lowerCaseTag)
- && !caseInsensitiveMatch(matchingTags, lowerCaseTag)) {
- matchingTags.add(tag); // preserve the case of the input tag
- }
- }
-
- return matchingTags;
- }
-
/**
* The tag which is falling in the extended exclusion range(s) should
* not be considered as the matching tag. Ignores the tag matching with the
diff --git a/test/jdk/java/util/Locale/Bug7069824.java b/test/jdk/java/util/Locale/Bug7069824.java
index 0eb1f21c275..dce97ef1246 100644
--- a/test/jdk/java/util/Locale/Bug7069824.java
+++ b/test/jdk/java/util/Locale/Bug7069824.java
@@ -208,27 +208,61 @@ public class Bug7069824 {
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, "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",
- MAP_EXTENDED_RANGES, "de-DE, de-DE-x-goethe"},
+ 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-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-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.
};
}
@@ -380,6 +414,15 @@ public class Bug7069824 {
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<LanguageRange> 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<LanguageRange> priorityList = LanguageRange.parse(ranges);
--
2.25.1
FREQUENCY : always