--- old/src/share/classes/sun/util/locale/provider/LocaleServiceProviderPool.java Mon Oct 22 14:06:07 2012 +++ new/src/share/classes/sun/util/locale/provider/LocaleServiceProviderPool.java Mon Oct 22 14:06:05 2012 @@ -356,7 +356,7 @@ * @param locale the input locale * @return the list of candidate locales for the given locale */ - private static List getLookupLocales(Locale locale) { + static List getLookupLocales(Locale locale) { // Note: We currently use the default implementation of // ResourceBundle.Control.getCandidateLocales. The result // returned by getCandidateLocales are already normalized --- old/src/share/classes/sun/util/locale/provider/SPILocaleProviderAdapter.java Mon Oct 22 14:06:13 2012 +++ new/src/share/classes/sun/util/locale/provider/SPILocaleProviderAdapter.java Mon Oct 22 14:06:11 2012 @@ -28,8 +28,11 @@ import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; -import java.util.ServiceLoader; -import java.util.spi.LocaleServiceProvider; +import java.text.*; +import java.text.spi.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.spi.*; /** * LocaleProviderAdapter implementation for the installed SPI implementations. @@ -54,11 +57,28 @@ @Override @SuppressWarnings("unchecked") public P run() { - P lsp = null; + P delegate = null; + for (LocaleServiceProvider provider : ServiceLoader.loadInstalled(c)) { - lsp = (P) provider; + if (delegate == null) { + try { + delegate = + (P) Class.forName(SPILocaleProviderAdapter.class.getCanonicalName() + + "$" + + c.getSimpleName() + + "Delegate") + .newInstance(); + } catch (ClassNotFoundException | + InstantiationException | + IllegalAccessException e) { + LocaleServiceProviderPool.config(SPILocaleProviderAdapter.class, e.toString()); + return null; + } + } + + ((Delegate)delegate).addImpl(provider); } - return lsp; + return delegate; } }); } catch (PrivilegedActionException e) { @@ -66,4 +86,554 @@ } return null; } + + /* + * Delegate interface. All the implementations have to have the class name + * following "Delegate" convention. + */ + interface Delegate

{ + public void addImpl(P impl); + public P getImpl(Locale locale); + } + + /* + * Obtain the real SPI implementation, using locale fallback + */ + private static

P getImpl(Map map, Locale locale) { + for (Locale l : LocaleServiceProviderPool.getLookupLocales(locale)) { + P ret = map.get(l); + if (ret != null) { + return ret; + } + } + return null; + } + + /* + * Delegates for the actual SPI implementations. + */ + static class BreakIteratorProviderDelegate extends BreakIteratorProvider + implements Delegate { + private ConcurrentMap map = new ConcurrentHashMap<>(); + + @Override + public void addImpl(BreakIteratorProvider impl) { + for (Locale l : impl.getAvailableLocales()) { + map.put(l, impl); + } + } + + @Override + public BreakIteratorProvider getImpl(Locale locale) { + return SPILocaleProviderAdapter.getImpl(map, locale); + } + + @Override + public Locale[] getAvailableLocales() { + return map.keySet().toArray(new Locale[0]); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return map.keySet().contains(locale); + } + + @Override + public BreakIterator getWordInstance(Locale locale) { + BreakIteratorProvider bip = getImpl(locale); + if (bip != null) { + return bip.getWordInstance(locale); + } else { + return null; + } + } + + @Override + public BreakIterator getLineInstance(Locale locale) { + BreakIteratorProvider bip = getImpl(locale); + if (bip != null) { + return bip.getLineInstance(locale); + } else { + return null; + } + } + + @Override + public BreakIterator getCharacterInstance(Locale locale) { + BreakIteratorProvider bip = getImpl(locale); + if (bip != null) { + return bip.getCharacterInstance(locale); + } else { + return null; + } + } + + @Override + public BreakIterator getSentenceInstance(Locale locale) { + BreakIteratorProvider bip = getImpl(locale); + if (bip != null) { + return bip.getSentenceInstance(locale); + } else { + return null; + } + } + + } + + static class CollatorProviderDelegate extends CollatorProvider implements Delegate { + private ConcurrentMap map = new ConcurrentHashMap<>(); + + @Override + public void addImpl(CollatorProvider impl) { + for (Locale l : impl.getAvailableLocales()) { + map.put(l, impl); + } + } + + @Override + public CollatorProvider getImpl(Locale locale) { + return SPILocaleProviderAdapter.getImpl(map, locale); + } + + @Override + public Locale[] getAvailableLocales() { + return map.keySet().toArray(new Locale[0]); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return map.keySet().contains(locale); + } + + @Override + public Collator getInstance(Locale locale) { + CollatorProvider cp = getImpl(locale); + if (cp != null) { + return cp.getInstance(locale); + } else { + return null; + } + } + } + + static class DateFormatProviderDelegate extends DateFormatProvider + implements Delegate { + private ConcurrentMap map = new ConcurrentHashMap<>(); + + @Override + public void addImpl(DateFormatProvider impl) { + for (Locale l : impl.getAvailableLocales()) { + map.put(l, impl); + } + } + + @Override + public DateFormatProvider getImpl(Locale locale) { + return SPILocaleProviderAdapter.getImpl(map, locale); + } + + @Override + public Locale[] getAvailableLocales() { + return map.keySet().toArray(new Locale[0]); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return map.keySet().contains(locale); + } + + @Override + public DateFormat getTimeInstance(int style, Locale locale) { + DateFormatProvider dfp = getImpl(locale); + if (dfp != null) { + return dfp.getTimeInstance(style, locale); + } else { + return null; + } + } + + @Override + public DateFormat getDateInstance(int style, Locale locale) { + DateFormatProvider dfp = getImpl(locale); + if (dfp != null) { + return dfp.getDateInstance(style, locale); + } else { + return null; + } + } + + @Override + public DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale) { + DateFormatProvider dfp = getImpl(locale); + if (dfp != null) { + return dfp.getDateTimeInstance(dateStyle, timeStyle, locale); + } else { + return null; + } + } + } + + static class DateFormatSymbolsProviderDelegate extends DateFormatSymbolsProvider + implements Delegate { + private ConcurrentMap map = new ConcurrentHashMap<>(); + + @Override + public void addImpl(DateFormatSymbolsProvider impl) { + for (Locale l : impl.getAvailableLocales()) { + map.put(l, impl); + } + } + + @Override + public DateFormatSymbolsProvider getImpl(Locale locale) { + return SPILocaleProviderAdapter.getImpl(map, locale); + } + + @Override + public Locale[] getAvailableLocales() { + return map.keySet().toArray(new Locale[0]); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return map.keySet().contains(locale); + } + + @Override + public DateFormatSymbols getInstance(Locale locale) { + DateFormatSymbolsProvider dfsp = getImpl(locale); + if (dfsp != null) { + return dfsp.getInstance(locale); + } else { + return null; + } + } + } + + static class DecimalFormatSymbolsProviderDelegate extends DecimalFormatSymbolsProvider + implements Delegate { + private ConcurrentMap map = new ConcurrentHashMap<>(); + + @Override + public void addImpl(DecimalFormatSymbolsProvider impl) { + for (Locale l : impl.getAvailableLocales()) { + map.put(l, impl); + } + } + + @Override + public DecimalFormatSymbolsProvider getImpl(Locale locale) { + return SPILocaleProviderAdapter.getImpl(map, locale); + } + + @Override + public Locale[] getAvailableLocales() { + return map.keySet().toArray(new Locale[0]); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return map.keySet().contains(locale); + } + + @Override + public DecimalFormatSymbols getInstance(Locale locale) { + DecimalFormatSymbolsProvider dfsp = getImpl(locale); + if (dfsp != null) { + return dfsp.getInstance(locale); + } else { + return null; + } + } + } + + static class NumberFormatProviderDelegate extends NumberFormatProvider + implements Delegate { + private ConcurrentMap map = new ConcurrentHashMap<>(); + + @Override + public void addImpl(NumberFormatProvider impl) { + for (Locale l : impl.getAvailableLocales()) { + map.put(l, impl); + } + } + + @Override + public NumberFormatProvider getImpl(Locale locale) { + return SPILocaleProviderAdapter.getImpl(map, locale); + } + + @Override + public Locale[] getAvailableLocales() { + return map.keySet().toArray(new Locale[0]); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return map.keySet().contains(locale); + } + + @Override + public NumberFormat getCurrencyInstance(Locale locale) { + NumberFormatProvider nfp = getImpl(locale); + if (nfp != null) { + return nfp.getCurrencyInstance(locale); + } else { + return null; + } + } + + @Override + public NumberFormat getIntegerInstance(Locale locale) { + NumberFormatProvider nfp = getImpl(locale); + if (nfp != null) { + return nfp.getIntegerInstance(locale); + } else { + return null; + } + } + + @Override + public NumberFormat getNumberInstance(Locale locale) { + NumberFormatProvider nfp = getImpl(locale); + if (nfp != null) { + return nfp.getNumberInstance(locale); + } else { + return null; + } + } + + @Override + public NumberFormat getPercentInstance(Locale locale) { + NumberFormatProvider nfp = getImpl(locale); + if (nfp != null) { + return nfp.getPercentInstance(locale); + } else { + return null; + } + } + } + + static class CalendarDataProviderDelegate extends CalendarDataProvider + implements Delegate { + private ConcurrentMap map = new ConcurrentHashMap<>(); + + @Override + public void addImpl(CalendarDataProvider impl) { + for (Locale l : impl.getAvailableLocales()) { + map.put(l, impl); + } + } + + @Override + public CalendarDataProvider getImpl(Locale locale) { + return SPILocaleProviderAdapter.getImpl(map, locale); + } + + @Override + public Locale[] getAvailableLocales() { + return map.keySet().toArray(new Locale[0]); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return map.keySet().contains(locale); + } + + @Override + public int getFirstDayOfWeek(Locale locale) { + CalendarDataProvider cdp = getImpl(locale); + if (cdp != null) { + return cdp.getFirstDayOfWeek(locale); + } else { + return 0; + } + } + + @Override + public int getMinimalDaysInFirstWeek(Locale locale) { + CalendarDataProvider cdp = getImpl(locale); + if (cdp != null) { + return cdp.getMinimalDaysInFirstWeek(locale); + } else { + return 0; + } + } + + @Override + public String getDisplayName(String calendarType, + int field, int value, + int style, Locale locale) { + CalendarDataProvider cdp = getImpl(locale); + if (cdp != null) { + return cdp.getDisplayName(calendarType, field, value, style, locale); + } else { + return null; + } + } + + @Override + public Map getDisplayNames(String calendarType, + int field, int style, + Locale locale) { + CalendarDataProvider cdp = getImpl(locale); + if (cdp != null) { + return cdp.getDisplayNames(calendarType, field, style, locale); + } else { + return null; + } + } + } + + static class CurrencyNameProviderDelegate extends CurrencyNameProvider + implements Delegate { + private ConcurrentMap map = new ConcurrentHashMap<>(); + + @Override + public void addImpl(CurrencyNameProvider impl) { + for (Locale l : impl.getAvailableLocales()) { + map.put(l, impl); + } + } + + @Override + public CurrencyNameProvider getImpl(Locale locale) { + return SPILocaleProviderAdapter.getImpl(map, locale); + } + + @Override + public Locale[] getAvailableLocales() { + return map.keySet().toArray(new Locale[0]); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return map.keySet().contains(locale); + } + + @Override + public String getSymbol(String currencyCode, Locale locale) { + CurrencyNameProvider cnp = getImpl(locale); + if (cnp != null) { + return cnp.getSymbol(currencyCode, locale); + } else { + return null; + } + } + + @Override + public String getDisplayName(String currencyCode, Locale locale) { + CurrencyNameProvider cnp = getImpl(locale); + if (cnp != null) { + return cnp.getDisplayName(currencyCode, locale); + } else { + return null; + } + } + } + + static class LocaleNameProviderDelegate extends LocaleNameProvider + implements Delegate { + private ConcurrentMap map = new ConcurrentHashMap<>(); + + @Override + public void addImpl(LocaleNameProvider impl) { + for (Locale l : impl.getAvailableLocales()) { + map.put(l, impl); + } + } + + @Override + public LocaleNameProvider getImpl(Locale locale) { + return SPILocaleProviderAdapter.getImpl(map, locale); + } + + @Override + public Locale[] getAvailableLocales() { + return map.keySet().toArray(new Locale[0]); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return map.keySet().contains(locale); + } + + @Override + public String getDisplayLanguage(String languageCode, Locale locale) { + LocaleNameProvider lnp = getImpl(locale); + if (lnp != null) { + return lnp.getDisplayLanguage(languageCode, locale); + } else { + return null; + } + } + + @Override + public String getDisplayScript(String scriptCode, Locale locale) { + LocaleNameProvider lnp = getImpl(locale); + if (lnp != null) { + return lnp.getDisplayScript(scriptCode, locale); + } else { + return null; + } + } + + @Override + public String getDisplayCountry(String countryCode, Locale locale) { + LocaleNameProvider lnp = getImpl(locale); + if (lnp != null) { + return lnp.getDisplayCountry(countryCode, locale); + } else { + return null; + } + } + + @Override + public String getDisplayVariant(String variant, Locale locale) { + LocaleNameProvider lnp = getImpl(locale); + if (lnp != null) { + return lnp.getDisplayVariant(variant, locale); + } else { + return null; + } + } + + } + + static class TimeZoneNameProviderDelegate extends TimeZoneNameProvider + implements Delegate { + private ConcurrentMap map = new ConcurrentHashMap<>(); + + @Override + public void addImpl(TimeZoneNameProvider impl) { + for (Locale l : impl.getAvailableLocales()) { + map.put(l, impl); + } + } + + @Override + public TimeZoneNameProvider getImpl(Locale locale) { + return SPILocaleProviderAdapter.getImpl(map, locale); + } + + @Override + public Locale[] getAvailableLocales() { + return map.keySet().toArray(new Locale[0]); + } + + @Override + public boolean isSupportedLocale(Locale locale) { + return map.keySet().contains(locale); + } + + @Override + public String getDisplayName(String ID, boolean daylight, int style, Locale locale) { + TimeZoneNameProvider tznp = getImpl(locale); + if (tznp != null) { + return tznp.getDisplayName(ID, daylight, style, locale); + } else { + return null; + } + } + } } --- old/test/java/util/PluggableLocale/CurrencyNameProviderTest.java Mon Oct 22 14:06:18 2012 +++ new/test/java/util/PluggableLocale/CurrencyNameProviderTest.java Mon Oct 22 14:06:17 2012 @@ -48,10 +48,13 @@ void test1() { com.bar.CurrencyNameProviderImpl cnp = new com.bar.CurrencyNameProviderImpl(); + com.bar.CurrencyNameProviderImpl2 cnp2 = new com.bar.CurrencyNameProviderImpl2(); Locale[] availloc = Locale.getAvailableLocales(); Locale[] testloc = availloc.clone(); List jreimplloc = Arrays.asList(LocaleProviderAdapter.forJRE().getCurrencyNameProvider().getAvailableLocales()); - List providerloc = Arrays.asList(cnp.getAvailableLocales()); + List providerloc = new ArrayList(); + providerloc.addAll(Arrays.asList(cnp.getAvailableLocales())); + providerloc.addAll(Arrays.asList(cnp2.getAvailableLocales())); for (Locale target: availloc) { // pure JRE implementation @@ -79,8 +82,13 @@ String providerscurrency = null; String providersname = null; if (providerloc.contains(target)) { - providerscurrency = cnp.getSymbol(c.getCurrencyCode(), target); - providersname = cnp.getDisplayName(c.getCurrencyCode(), target); + if (cnp.isSupportedLocale(target)) { + providerscurrency = cnp.getSymbol(c.getCurrencyCode(), target); + providersname = cnp.getDisplayName(c.getCurrencyCode(), target); + } else { + providerscurrency = cnp2.getSymbol(c.getCurrencyCode(), target); + providersname = cnp2.getDisplayName(c.getCurrencyCode(), target); + } } // JRE's name @@ -109,18 +117,22 @@ final String pattern = "###,###\u00A4"; final String YEN_IN_OSAKA = "100,000\u5186\u3084\u3002"; final String YEN_IN_KYOTO = "100,000\u5186\u3069\u3059\u3002"; + final String YEN_IN_TOKYO= "100,000JPY-tokyo"; final Locale OSAKA = new Locale("ja", "JP", "osaka"); final Locale KYOTO = new Locale("ja", "JP", "kyoto"); + final Locale TOKYO = new Locale("ja", "JP", "tokyo"); Integer i = new Integer(100000); String formatted; DecimalFormat df; void test2() { + Locale defloc = Locale.getDefault(); + try { df = new DecimalFormat(pattern, DecimalFormatSymbols.getInstance(OSAKA)); System.out.println(formatted = df.format(i)); if(!formatted.equals(YEN_IN_OSAKA)) { - throw new RuntimeException("formatted zone names mismatch. " + + throw new RuntimeException("formatted currency names mismatch. " + "Should match with " + YEN_IN_OSAKA); } @@ -130,13 +142,25 @@ df = new DecimalFormat(pattern, DecimalFormatSymbols.getInstance()); System.out.println(formatted = df.format(i)); if(!formatted.equals(YEN_IN_KYOTO)) { - throw new RuntimeException("formatted zone names mismatch. " + + throw new RuntimeException("formatted currency names mismatch. " + "Should match with " + YEN_IN_KYOTO); } df.parse(YEN_IN_KYOTO); + + Locale.setDefault(TOKYO); + df = new DecimalFormat(pattern, DecimalFormatSymbols.getInstance()); + System.out.println(formatted = df.format(i)); + if(!formatted.equals(YEN_IN_TOKYO)) { + throw new RuntimeException("formatted currency names mismatch. " + + "Should match with " + YEN_IN_TOKYO); + } + + df.parse(YEN_IN_TOKYO); } catch (ParseException pe) { throw new RuntimeException("parse error occured" + pe); + } finally { + Locale.setDefault(defloc); } } } --- old/test/java/util/PluggableLocale/CurrencyNameProviderTest.sh Mon Oct 22 14:06:24 2012 +++ new/test/java/util/PluggableLocale/CurrencyNameProviderTest.sh Mon Oct 22 14:06:22 2012 @@ -23,6 +23,6 @@ #!/bin/sh # # @test -# @bug 4052440 +# @bug 4052440 8000997 # @summary CurrencyNameProvider tests # @run shell ExecTest.sh bar CurrencyNameProviderTest true --- old/test/java/util/PluggableLocale/GenericTest.java Mon Oct 22 14:06:29 2012 +++ new/test/java/util/PluggableLocale/GenericTest.java Mon Oct 22 14:06:28 2012 @@ -38,6 +38,7 @@ com.foo.DecimalFormatSymbolsProviderImpl decimalFSP = new com.foo.DecimalFormatSymbolsProviderImpl(); com.foo.NumberFormatProviderImpl numberFP = new com.foo.NumberFormatProviderImpl(); com.bar.CurrencyNameProviderImpl currencyNP = new com.bar.CurrencyNameProviderImpl(); + com.bar.CurrencyNameProviderImpl2 currencyNP2 = new com.bar.CurrencyNameProviderImpl2(); com.bar.LocaleNameProviderImpl localeNP = new com.bar.LocaleNameProviderImpl(); com.bar.TimeZoneNameProviderImpl tzNP = new com.bar.TimeZoneNameProviderImpl(); com.bar.CalendarDataProviderImpl calDataP = new com.bar.CalendarDataProviderImpl(); @@ -68,6 +69,7 @@ expected.addAll(Arrays.asList(decimalFSP.getAvailableLocales())); expected.addAll(Arrays.asList(numberFP.getAvailableLocales())); expected.addAll(Arrays.asList(currencyNP.getAvailableLocales())); + expected.addAll(Arrays.asList(currencyNP2.getAvailableLocales())); expected.addAll(Arrays.asList(localeNP.getAvailableLocales())); expected.addAll(Arrays.asList(tzNP.getAvailableLocales())); expected.addAll(Arrays.asList(calDataP.getAvailableLocales())); --- old/test/java/util/PluggableLocale/providersrc/Makefile Mon Oct 22 14:06:42 2012 +++ new/test/java/util/PluggableLocale/providersrc/Makefile Mon Oct 22 14:06:40 2012 @@ -35,6 +35,7 @@ BARFILES_JAVA = \ CurrencyNameProviderImpl.java \ + CurrencyNameProviderImpl2.java \ TimeZoneNameProviderImpl.java \ LocaleNameProviderImpl.java \ CalendarDataProviderImpl.java \ --- old/test/java/util/PluggableLocale/providersrc/java.util.spi.CurrencyNameProvider Mon Oct 22 14:06:47 2012 +++ new/test/java/util/PluggableLocale/providersrc/java.util.spi.CurrencyNameProvider Mon Oct 22 14:06:46 2012 @@ -5,3 +5,4 @@ # implementation class # com.bar.CurrencyNameProviderImpl +com.bar.CurrencyNameProviderImpl2 --- /dev/null Mon Oct 22 14:06:53 2012 +++ new/test/java/util/PluggableLocale/providersrc/CurrencyNameProviderImpl2.java Mon Oct 22 14:06:51 2012 @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012, 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. + */ +/* + * + */ + +package com.bar; + +import java.util.*; +import java.util.spi.*; + +import com.foobar.Utils; + +public class CurrencyNameProviderImpl2 extends CurrencyNameProvider { + static Locale[] avail = {new Locale("ja", "JP", "tokyo")}; + public Locale[] getAvailableLocales() { + return avail; + } + + @Override + public String getSymbol(String c, Locale locale) { + if (!Utils.supportsLocale(Arrays.asList(avail), locale)) { + throw new IllegalArgumentException("locale is not supported: "+locale); + } + + if (c.equals("JPY") && Utils.supportsLocale(avail[0], locale)) { + return "JPY-tokyo"; + } + return null; + } + + @Override + public String getDisplayName(String c, Locale locale) { + if (!Utils.supportsLocale(Arrays.asList(avail), locale)) { + throw new IllegalArgumentException("locale is not supported: "+locale); + } + + if (c.equals("JPY") && Utils.supportsLocale(avail[0], locale)) { + return "JPY-tokyo"; + } + return null; + } +}