# 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 @@ *
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. + * *
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 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> pa =
+ () -> {
+ return Collections.unmodifiableList(
+ ServiceLoader.load(ResourceBundleControlProvider.class,
+ ClassLoader.getSystemClassLoader()).stream()
+ .map(ServiceLoader.Provider::get)
+ .collect(Collectors.toList()));
+ };
+
+ private static final List