# HG changeset patch # User naoto # Date 1484962691 28800 # Fri Jan 20 17:38:11 2017 -0800 # Node ID 92f95e91dceb779a4bacf9b15bd82eedffee9377 # Parent 750c88f3be91d6f0cb6f70d6b426c541241c3397 [mq]: 8172365 diff --git a/src/java.base/share/classes/java/util/ResourceBundle.java b/src/java.base/share/classes/java/util/ResourceBundle.java --- a/src/java.base/share/classes/java/util/ResourceBundle.java +++ b/src/java.base/share/classes/java/util/ResourceBundle.java @@ -61,7 +61,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.jar.JarEntry; +import java.util.spi.ResourceBundleControlProvider; import java.util.spi.ResourceBundleProvider; +import java.util.stream.Collectors; +import java.util.stream.Stream; import jdk.internal.loader.BootLoader; import jdk.internal.misc.JavaUtilResourceBundleAccess; @@ -232,6 +235,8 @@ *
  • {@code ResourceBundle.Control} is not supported in named modules. * If the {@code getBundle} method with a {@code ResourceBundle.Control} is called * in a named module, the method will throw an {@code UnsupportedOperationException}. + * Any service providers of {@link ResourceBundleControlProvider} are ignored in + * named modules. *
  • * * @@ -262,6 +267,18 @@ * {@link #getBundle(String, Locale, ClassLoader, Control) getBundle} * factory method for details. * + *

    For the {@code getBundle} factory + * methods that take no {@link Control} instance, their default behavior of resource bundle loading + * can be modified with custom {@link + * ResourceBundleControlProvider} implementations. + * If any of the + * providers provides a {@link Control} for the given base name, that {@link + * Control} will be used instead of the default {@link Control}. If there is + * more than one service provider for supporting the same base name, + * the first one returned from {@link ServiceLoader} will be used. + * A custom {@link Control} implementation is ignored by named modules. + * *

    Cache Management

    * * Resource bundle instances created by the getBundle factory @@ -367,7 +384,8 @@ public ResourceBundle getBundle(String baseName, Locale locale, Module module) { // use the given module as the caller to bypass the access check return getBundleImpl(module, module, - baseName, locale, Control.INSTANCE); + baseName, locale, + getDefaultControl(module, baseName)); } @Override @@ -815,7 +833,7 @@ { Class caller = Reflection.getCallerClass(); return getBundleImpl(baseName, Locale.getDefault(), - caller, Control.INSTANCE); + caller, getDefaultControl(caller, baseName)); } /** @@ -889,7 +907,7 @@ { Class caller = Reflection.getCallerClass(); return getBundleImpl(baseName, locale, - caller, Control.INSTANCE); + caller, getDefaultControl(caller, baseName)); } /** @@ -925,7 +943,8 @@ @CallerSensitive public static ResourceBundle getBundle(String baseName, Module module) { return getBundleFromModule(Reflection.getCallerClass(), module, baseName, - Locale.getDefault(), Control.INSTANCE); + Locale.getDefault(), + getDefaultControl(module, baseName)); } /** @@ -974,7 +993,7 @@ @CallerSensitive public static ResourceBundle getBundle(String baseName, Locale targetLocale, Module module) { return getBundleFromModule(Reflection.getCallerClass(), module, baseName, targetLocale, - Control.INSTANCE); + getDefaultControl(module, baseName)); } /** @@ -1030,7 +1049,10 @@ * *

    This method behaves the same as calling * {@link #getBundle(String, Locale, ClassLoader, Control)} passing a - * default instance of {@link Control}. + * default instance of {@link Control} unless another {@link Control} is + * provided with the {@link ResourceBundleControlProvider} SPI. Refer to the + * description of modifying the default + * behavior. * *

    The following describes the default * behavior. @@ -1228,7 +1250,7 @@ throw new NullPointerException(); } Class caller = Reflection.getCallerClass(); - return getBundleImpl(baseName, locale, caller, loader, Control.INSTANCE); + return getBundleImpl(baseName, locale, caller, loader, getDefaultControl(caller, baseName)); } /** @@ -1453,6 +1475,39 @@ return getBundleImpl(baseName, targetLocale, caller, loader, control); } + private static Control getDefaultControl(Class caller, String baseName) { + return getDefaultControl(caller.getModule(), baseName); + } + + private static Control getDefaultControl(Module targetModule, String baseName) { + return targetModule.isNamed() ? + Control.INSTANCE : + ResourceBundleControlProviderHolder.getControl(baseName); + } + + private static class ResourceBundleControlProviderHolder { + private static final PrivilegedAction> pa = + () -> { + return Collections.unmodifiableList( + ServiceLoader.load(ResourceBundleControlProvider.class, + ClassLoader.getSystemClassLoader()).stream() + .map(ServiceLoader.Provider::get) + .collect(Collectors.toList())); + }; + + private static final List CONTROL_PROVIDERS = + AccessController.doPrivileged(pa); + + private static Control getControl(String baseName) { + return CONTROL_PROVIDERS.isEmpty() ? + Control.INSTANCE : + CONTROL_PROVIDERS.stream() + .flatMap(provider -> Stream.ofNullable(provider.getControl(baseName))) + .findFirst() + .orElse(Control.INSTANCE); + } + } + private static void checkNamedModule(Class caller) { if (caller.getModule().isNamed()) { throw new UnsupportedOperationException( @@ -2414,7 +2469,8 @@ * @apiNote {@code ResourceBundle.Control} is not supported * in named modules. If the {@code ResourceBundle.getBundle} method with * a {@code ResourceBundle.Control} is called in a named module, the method - * will throw an {@link UnsupportedOperationException}. + * will throw an {@link UnsupportedOperationException}. Any service providers + * of {@link ResourceBundleControlProvider} are ignored in named modules. * * @since 1.6 * @see java.util.spi.ResourceBundleProvider diff --git a/src/java.base/share/classes/java/util/spi/ResourceBundleControlProvider.java b/src/java.base/share/classes/java/util/spi/ResourceBundleControlProvider.java --- a/src/java.base/share/classes/java/util/spi/ResourceBundleControlProvider.java +++ b/src/java.base/share/classes/java/util/spi/ResourceBundleControlProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2017, 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 @@ -35,19 +35,19 @@ * no {@link java.util.ResourceBundle.Control} instance can be modified with {@code * ResourceBundleControlProvider} implementations. * + *

    Provider implementations are loaded from the application's class path + * using {@link java.util.ServiceLoader} at the first invocation of the + * {@code ResourceBundle.getBundle} factory method that takes no + * {@link java.util.ResourceBundle.Control} instance. + * + *

    All {@code ResourceBundleControlProvider}s are ignored in named modules. + * * @author Masayoshi Okutsu * @since 1.8 * @see ResourceBundle#getBundle(String, java.util.Locale, ClassLoader, ResourceBundle.Control) * ResourceBundle.getBundle - * @see java.util.ServiceLoader#loadInstalled(Class) - * @deprecated There is no longer any mechanism to install a custom - * {@code ResourceBundleControlProvider} implementation defined - * by the platform class loader or its ancestor. The recommended - * way to use a custom {@code Control} implementation to load resource bundle - * is to use {@link java.util.ResourceBundle#getBundle(String, Control)} - * or other factory methods that take custom {@link java.util.ResourceBundle.Control}. + * @see java.util.ServiceLoader#load(Class) */ -@Deprecated(since="9", forRemoval=true) public interface ResourceBundleControlProvider { /** * Returns a {@code ResourceBundle.Control} instance that is used diff --git a/test/java/util/spi/ResourceBundleControlProvider/UserDefaultControlTest.java b/test/java/util/spi/ResourceBundleControlProvider/UserDefaultControlTest.java new file mode 100644 --- /dev/null +++ b/test/java/util/spi/ResourceBundleControlProvider/UserDefaultControlTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2012, 2017, 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. + */ +/* + * @test + * @bug 6959653 8172365 + * @summary Test ResourceBundle.Control provided using SPI. + * @library test + * @build test/* + * @build com.foo.UserControlProvider + * @run main/othervm UserDefaultControlTest false + * @run main/othervm UserDefaultControlTest true + */ + +import java.io.*; +import java.lang.reflect.*; +import java.net.*; +import java.nio.file.*; +import java.util.*; + +import jdk.test.*; + +public class UserDefaultControlTest { + public static void main(String... args) throws Exception { + boolean smExists = Boolean.valueOf(args[0]); + initServices(); + if (smExists) { + System.out.println("test with security manager present:"); + System.setSecurityManager(new SecurityManager()); + } else { + System.out.println("test without security manager present:"); + } + + test(smExists); + } + + private static void initServices() throws IOException { + Path testClasses = Paths.get(System.getProperty("test.classes")); + Path services = testClasses.resolve(Paths.get("META-INF", "services")); + Files.createDirectories(services); + Files.write(services.resolve("java.util.spi.ResourceBundleControlProvider"), + List.of("com.foo.UserControlProvider")); + Path comfoo = testClasses.resolve(Paths.get("com", "foo")); + Path testSrcComFoo = + Paths.get(System.getProperty("test.src")).resolve(Paths.get("com", "foo")); + Files.copy(testSrcComFoo.resolve("XmlRB.xml"), comfoo.resolve("XmlRB.xml"), + StandardCopyOption.REPLACE_EXISTING); + Files.copy(testSrcComFoo.resolve("XmlRB_ja.xml"), comfoo.resolve("XmlRB_ja.xml"), + StandardCopyOption.REPLACE_EXISTING); + } + + private static void test(boolean smExists) { + ResourceBundle rb; + + try { + rb = ResourceBundle.getBundle("com.foo.XmlRB", Locale.ROOT); + if (smExists) { + throw new RuntimeException("getBundle did not throw " + + "MissingResourceException with a security manager"); + } + } catch (MissingResourceException e) { + if (smExists) { + // failed successfully + return; + } else { + throw e; + } + } + + String type = rb.getString("type"); + if (!type.equals("XML")) { + throw new RuntimeException("Root Locale: type: got " + type + + ", expected XML (ASCII)"); + } + + rb = ResourceBundle.getBundle("com.foo.XmlRB", Locale.JAPAN); + type = rb.getString("type"); + // Expect fullwidth "XML" + if (!type.equals("\uff38\uff2d\uff2c")) { + throw new RuntimeException("Locale.JAPAN: type: got " + type + + ", expected \uff38\uff2d\uff2c (fullwidth XML)"); + } + + try { + rb = ResourceBundle.getBundle("com.bar.XmlRB", Locale.JAPAN); + throw new RuntimeException("com.bar.XmlRB test failed."); + } catch (MissingResourceException e) { + // OK + } + + // tests with named module. Only resource bundles on the classpath + // should be available, unless an unnamed module is explicitly + // specified. + rb = ResourceBundleDelegate.getBundle("simple", Locale.ROOT); + try { + rb = ResourceBundleDelegate.getBundle("com.foo.XmlRB", Locale.ROOT); + throw new RuntimeException("getBundle in a named module incorrectly loaded " + + "a resouce bundle through RBControlProvider"); + } catch (MissingResourceException e) { + // OK + } + + Module unnamedModule = UserDefaultControlTest.class + .getClassLoader() + .getUnnamedModule(); + rb = ResourceBundleDelegate.getBundle("com.foo.XmlRB", Locale.JAPAN, unnamedModule); + type = rb.getString("type"); + // Expect fullwidth "XML" + if (!type.equals("\uff38\uff2d\uff2c")) { + throw new RuntimeException("getBundle called from named module for unnamed module." + + " Locale.JAPAN: type: got " + type + + ", expected \uff38\uff2d\uff2c (fullwidth XML)"); + } + } +} diff --git a/test/java/util/spi/ResourceBundleControlProvider/com/foo/UserControlProvider.java b/test/java/util/spi/ResourceBundleControlProvider/com/foo/UserControlProvider.java new file mode 100644 --- /dev/null +++ b/test/java/util/spi/ResourceBundleControlProvider/com/foo/UserControlProvider.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012, 2017, 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.foo; + +import java.util.ResourceBundle; +import java.util.spi.ResourceBundleControlProvider; + +public class UserControlProvider implements ResourceBundleControlProvider { + static final ResourceBundle.Control XMLCONTROL = new UserXMLControl(); + + public ResourceBundle.Control getControl(String baseName) { + System.out.println(getClass().getName()+".getControl called for " + baseName); + + // Throws a NPE if baseName is null. + if (baseName.startsWith("com.foo.Xml")) { + System.out.println("\treturns " + XMLCONTROL); + return XMLCONTROL; + } + System.out.println("\treturns null"); + return null; + } +} diff --git a/test/java/util/spi/ResourceBundleControlProvider/com/foo/UserXMLControl.java b/test/java/util/spi/ResourceBundleControlProvider/com/foo/UserXMLControl.java new file mode 100644 --- /dev/null +++ b/test/java/util/spi/ResourceBundleControlProvider/com/foo/UserXMLControl.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2012, 2017, 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.foo; + +import java.io.*; +import java.net.*; +import java.util.*; +import static java.util.ResourceBundle.Control.*; + +public class UserXMLControl extends ResourceBundle.Control { + @Override + public List getFormats(String baseName) { + if (baseName == null) { + throw new NullPointerException(); + } + return Arrays.asList("xml"); + } + + @Override + public ResourceBundle newBundle(String baseName, Locale locale, + String format, + ClassLoader loader, + boolean reload) + throws IllegalAccessException, + InstantiationException, IOException { + if (baseName == null || locale == null + || format == null || loader == null) { + throw new NullPointerException(); + } + ResourceBundle bundle = null; + if (format.equals("xml")) { + String bundleName = toBundleName(baseName, locale); + String resourceName = toResourceName(bundleName, format); + URL url = loader.getResource(resourceName); + if (url != null) { + URLConnection connection = url.openConnection(); + if (connection != null) { + if (reload) { + // disable caches if reloading + connection.setUseCaches(false); + } + try (InputStream stream = connection.getInputStream()) { + if (stream != null) { + BufferedInputStream bis = new BufferedInputStream(stream); + bundle = new XMLResourceBundle(bis); + } + } + } + } + } + return bundle; + } + + private static class XMLResourceBundle extends ResourceBundle { + private Properties props; + + XMLResourceBundle(InputStream stream) throws IOException { + props = new Properties(); + props.loadFromXML(stream); + } + + protected Object handleGetObject(String key) { + if (key == null) { + throw new NullPointerException(); + } + return props.get(key); + } + + public Enumeration getKeys() { + // Not implemented + return null; + } + } +} diff --git a/test/java/util/spi/ResourceBundleControlProvider/com/foo/XmlRB.xml b/test/java/util/spi/ResourceBundleControlProvider/com/foo/XmlRB.xml new file mode 100644 --- /dev/null +++ b/test/java/util/spi/ResourceBundleControlProvider/com/foo/XmlRB.xml @@ -0,0 +1,40 @@ + + + + + + + + + + +]> + + + Test data for UserDefaultControlTest.java + XML + diff --git a/test/java/util/spi/ResourceBundleControlProvider/com/foo/XmlRB_ja.xml b/test/java/util/spi/ResourceBundleControlProvider/com/foo/XmlRB_ja.xml new file mode 100644 --- /dev/null +++ b/test/java/util/spi/ResourceBundleControlProvider/com/foo/XmlRB_ja.xml @@ -0,0 +1,40 @@ + + + + + + + + + + +]> + + + Test data for UserDefaultControlTest.java + XML + diff --git a/test/java/util/spi/ResourceBundleControlProvider/simple.properties b/test/java/util/spi/ResourceBundleControlProvider/simple.properties new file mode 100644 --- /dev/null +++ b/test/java/util/spi/ResourceBundleControlProvider/simple.properties @@ -0,0 +1,23 @@ +# +# Copyright (c) 2017, 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. +# +key = value diff --git a/test/java/util/spi/ResourceBundleControlProvider/test/jdk/test/ResourceBundleDelegate.java b/test/java/util/spi/ResourceBundleControlProvider/test/jdk/test/ResourceBundleDelegate.java new file mode 100644 --- /dev/null +++ b/test/java/util/spi/ResourceBundleControlProvider/test/jdk/test/ResourceBundleDelegate.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017, 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 jdk.test; + +import java.lang.reflect.Module; +import java.util.Locale; +import java.util.ResourceBundle; + +public class ResourceBundleDelegate { + public static ResourceBundle getBundle(String baseName, Locale locale) { + return ResourceBundle.getBundle(baseName, locale); + } + + public static ResourceBundle getBundle(String baseName, Locale locale, Module module) { + return ResourceBundle.getBundle(baseName, locale, module); + } +} diff --git a/test/java/util/spi/ResourceBundleControlProvider/test/module-info.java b/test/java/util/spi/ResourceBundleControlProvider/test/module-info.java new file mode 100644 --- /dev/null +++ b/test/java/util/spi/ResourceBundleControlProvider/test/module-info.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2017, 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. + */ + +module test { + exports jdk.test; +}