8172365: Provide a better migration path for ResourceBundleControlProvider

Reviewed-by: mchung
This commit is contained in:
Naoto Sato 2017-01-30 14:38:08 -08:00
parent ae1d2480be
commit 88f86ecc32
10 changed files with 515 additions and 18 deletions

View File

@ -61,7 +61,10 @@ import java.security.PrivilegedExceptionAction;
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 @@ import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION;
* <li>{@code ResourceBundle.Control} is <em>not</em> 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.
* </li>
* </ul>
*
@ -262,6 +267,18 @@ import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION;
* {@link #getBundle(String, Locale, ClassLoader, Control) getBundle}
* factory method for details.
*
* <p><a name="modify_default_behavior">For the {@code getBundle} factory</a>
* methods that take no {@link Control} instance, their <a
* href="#default_behavior"> default behavior</a> 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.
*
* <h3>Cache Management</h3>
*
* Resource bundle instances created by the <code>getBundle</code> factory
@ -367,7 +384,8 @@ public abstract class ResourceBundle {
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 @@ public abstract class ResourceBundle {
{
Class<?> caller = Reflection.getCallerClass();
return getBundleImpl(baseName, Locale.getDefault(),
caller, Control.INSTANCE);
caller, getDefaultControl(caller, baseName));
}
/**
@ -889,7 +907,7 @@ public abstract class ResourceBundle {
{
Class<?> caller = Reflection.getCallerClass();
return getBundleImpl(baseName, locale,
caller, Control.INSTANCE);
caller, getDefaultControl(caller, baseName));
}
/**
@ -925,7 +943,8 @@ public abstract class ResourceBundle {
@CallerSensitive
public static ResourceBundle getBundle(String baseName, Module module) {
return getBundleFromModule(Reflection.getCallerClass(), module, baseName,
Locale.getDefault(), Control.INSTANCE);
Locale.getDefault(),
getDefaultControl(module, baseName));
}
/**
@ -953,7 +972,9 @@ public abstract class ResourceBundle {
* equivalent to calling {@link #getBundle(String, Locale, ClassLoader)
* getBundle(baseName, targetLocale, module.getClassLoader()} to load
* resource bundles that are visible to the class loader of the given
* unnamed module.
* unnamed module. Custom {@link java.util.spi.ResourceBundleControlProvider}
* implementations, if present, will only be invoked if the specified
* module is an unnamed module.
*
* @param baseName the base name of the resource bundle,
* a fully qualified class name
@ -974,7 +995,7 @@ public abstract class ResourceBundle {
@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 +1051,10 @@ public abstract class ResourceBundle {
*
* <p>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 <a href="#modify_default_behavior">modifying the default
* behavior</a>.
*
* <p><a name="default_behavior">The following describes the default
* behavior</a>.
@ -1228,7 +1252,7 @@ public abstract class ResourceBundle {
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 +1477,39 @@ public abstract class ResourceBundle {
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<List<ResourceBundleControlProvider>> pa =
() -> {
return Collections.unmodifiableList(
ServiceLoader.load(ResourceBundleControlProvider.class,
ClassLoader.getSystemClassLoader()).stream()
.map(ServiceLoader.Provider::get)
.collect(Collectors.toList()));
};
private static final List<ResourceBundleControlProvider> 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 +2471,8 @@ public abstract class ResourceBundle {
* @apiNote <a name="note">{@code ResourceBundle.Control} is not supported
* in named modules.</a> 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

View File

@ -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 @@ import java.util.ResourceBundle;
* no {@link java.util.ResourceBundle.Control} instance can be modified with {@code
* ResourceBundleControlProvider} implementations.
*
* <p>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.
*
* <p>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

View File

@ -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)");
}
}
}

View File

@ -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;
}
}

View File

@ -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<String> 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<String> getKeys() {
// Not implemented
return null;
}
}
}

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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. Oracle designates this
particular file as subject to the "Classpath" exception as provided
by Oracle in the LICENSE file that accompanied this code.
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.
-->
<!---->
<!-- DTD for properties -->
<!DOCTYPE properties [
<!ELEMENT properties ( comment?, entry* ) >
<!ATTLIST properties version CDATA #FIXED "1.0">
<!ELEMENT comment (#PCDATA) >
<!ELEMENT entry (#PCDATA) >
<!ATTLIST entry key CDATA #REQUIRED>
]>
<properties>
<comment>Test data for UserDefaultControlTest.java</comment>
<entry key="type">XML</entry>
</properties>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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. Oracle designates this
particular file as subject to the "Classpath" exception as provided
by Oracle in the LICENSE file that accompanied this code.
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.
-->
<!---->
<!-- DTD for properties -->
<!DOCTYPE properties [
<!ELEMENT properties ( comment?, entry* ) >
<!ATTLIST properties version CDATA #FIXED "1.0">
<!ELEMENT comment (#PCDATA) >
<!ELEMENT entry (#PCDATA) >
<!ATTLIST entry key CDATA #REQUIRED>
]>
<properties>
<comment>Test data for UserDefaultControlTest.java</comment>
<entry key="type"></entry>
</properties>

View File

@ -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

View File

@ -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);
}
}

View File

@ -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;
}