diff --git a/src/java.base/share/classes/java/text/ListFormat.java b/src/java.base/share/classes/java/text/ListFormat.java index 1f60ffd28bd..2ac29f14146 100644 --- a/src/java.base/share/classes/java/text/ListFormat.java +++ b/src/java.base/share/classes/java/text/ListFormat.java @@ -513,7 +513,7 @@ public final class ListFormat extends Format { public String toString() { return """ - ListFormat [locale: "%s", start: "%s", middle: "%s", end: "%s", two: "%s", three: "%s"] + ListFormat [locale: "%s", start: "%s", middle: "%s", end: "%s", two: "%s", three: "%s"] """.formatted(locale.getDisplayName(), patterns[START], patterns[MIDDLE], patterns[END], patterns[TWO], patterns[THREE]); } diff --git a/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java b/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java index c023788188b..dd7064416bd 100644 --- a/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java +++ b/src/java.base/share/classes/sun/util/locale/provider/LocaleResources.java @@ -843,7 +843,7 @@ public class LocaleResources { String typeStr = type.toString().toLowerCase(Locale.ROOT); String styleStr = style.toString().toLowerCase(Locale.ROOT); String[] lpArray; - String cacheKey = LIST_PATTERN + typeStr; + String cacheKey = LIST_PATTERN + typeStr + styleStr; removeEmptyReferences(); ResourceReference data = cache.get(cacheKey); @@ -851,9 +851,23 @@ public class LocaleResources { if (data == null || ((lpArray = (String[]) data.get()) == null)) { ResourceBundle rb = localeData.getDateFormatData(locale); lpArray = rb.getStringArray("ListPatterns_" + typeStr + (style == ListFormat.Style.FULL ? "" : "-" + styleStr)); - if (lpArray == null) { - cache.put(cacheKey, new ResourceReference(cacheKey, new String[5], referenceQueue)); + + if (lpArray[0].isEmpty() || lpArray[1].isEmpty() || lpArray[2].isEmpty()) { + var adapter = LocaleProviderAdapter.forType(LocaleProviderAdapter.Type.CLDR); + if (adapter instanceof ResourceBundleBasedAdapter rbba) { + var candList = rbba.getCandidateLocales("", locale); + // make sure there is at least one parent locale + if (candList.size() >= 2) { + var parentPatterns = adapter.getLocaleResources(candList.get(1)).getListPatterns(type, style); + for (int i = 0; i < 3; i++) { // exclude optional ones, ie, "two"/"three" + if (lpArray[i].isEmpty()) { + lpArray[i] = parentPatterns[i]; + } + } + } + } } + cache.put(cacheKey, new ResourceReference(cacheKey, lpArray, referenceQueue)); } return lpArray; diff --git a/test/jdk/java/text/Format/ListFormat/TestListFormat.java b/test/jdk/java/text/Format/ListFormat/TestListFormat.java index b6c3c2ae6fc..7f8258dbed0 100644 --- a/test/jdk/java/text/Format/ListFormat/TestListFormat.java +++ b/test/jdk/java/text/Format/ListFormat/TestListFormat.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8041488 + * @bug 8041488 8316974 * @summary Tests for ListFormat class * @run junit TestListFormat */ @@ -200,7 +200,19 @@ public class TestListFormat { arguments(CUSTOM_PATTERNS_MINIMAL, SAMPLE4), }; } - + static Arguments[] getInstance_3Arg_InheritPatterns() { + return new Arguments[] { + arguments(ListFormat.Type.STANDARD, ListFormat.Style.FULL), + arguments(ListFormat.Type.STANDARD, ListFormat.Style.SHORT), + arguments(ListFormat.Type.STANDARD, ListFormat.Style.NARROW), + arguments(ListFormat.Type.OR, ListFormat.Style.FULL), + arguments(ListFormat.Type.OR, ListFormat.Style.SHORT), + arguments(ListFormat.Type.OR, ListFormat.Style.NARROW), + arguments(ListFormat.Type.UNIT, ListFormat.Style.FULL), + arguments(ListFormat.Type.UNIT, ListFormat.Style.SHORT), + arguments(ListFormat.Type.UNIT, ListFormat.Style.NARROW), + }; + } @ParameterizedTest @MethodSource void getInstance_1Arg(String[] patterns, List input, String expected) throws ParseException { @@ -289,6 +301,27 @@ public class TestListFormat { assertEquals(-1, pp.getErrorIndex()); } + @ParameterizedTest + @MethodSource + void getInstance_3Arg_InheritPatterns(ListFormat.Type type, ListFormat.Style style) { + // No IAE should be thrown for all locales. Some locales in CLDR + // have partial patterns (start, middle, end) in it. Lacking ones + // should be inherited from parent locales. + Locale.availableLocales().forEach(l -> ListFormat.getInstance(l, type, style)); + } + @Test + void getInstance_3Arg_InheritanceValidation() { + // Tests if inheritance works as expected. + // World English ("en-001") has non-Oxford-comma pattern for "end", while + // English ("en") has Oxford-comma "end" pattern. Thus missing "standard"/"middle" + // should be inherited from "en", but "end" should stay non-Oxford for "en-001" + // Note that this test depends on a particular version of CLDR data. + assertEquals(""" + ListFormat [locale: "English (world)", start: "{0}, {1}", middle: "{0}, {1}", end: "{0} and {1}", two: "{0} and {1}", three: "{0}, {1} and {2}"] + """, + ListFormat.getInstance(Locale.forLanguageTag("en-001"), ListFormat.Type.STANDARD, ListFormat.Style.FULL).toString()); + } + private static void compareResult(ListFormat f, List input, String expected, boolean roundTrip) throws ParseException { var result = f.format(input); assertEquals(expected, result);