Update ResourceBundle related classes from java.util to OpenJDK8u60
Documented several previously undocumented Android-changed sections
that were missed in the initial draft of this CL.
- ResourceBundle
Added test for new method getBaseBundleName().
Added missing Android-changed comment for:
- use of UTF-8 for PropertyResourceBundles
- use of getLoader(VMStack.getCallingClassLoader())
- spi.ResourceBundleControlProvider
- PropertyResourceBundle, ListResourceBundle
Android-changed: Made access to the 'lookup' field thread safe.
- ServiceLoader
Do not use legacy security code.
Added missing Android-changed comments for loadFromSystemProperty()
and for fail() invocations where Android passes a 'Throwable cause'.
Bug: 29935305
Test: run cts -m CtsLibcoreTestCases
Change-Id: Idc3a7c5513644dd103e7d132789d37a1c40f1471
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/ResourceBundleTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/ResourceBundleTest.java
index db2ee7a..5d35e4c 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/ResourceBundleTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/ResourceBundleTest.java
@@ -39,6 +39,17 @@
assertEquals("[de_CH, de, ]", c.getCandidateLocales("base", new Locale("de", "CH")).toString());
}
+ public void test_getBaseName() {
+ String name = "tests.support.Support_TestResource";
+ ResourceBundle bundle = ResourceBundle.getBundle(name);
+ assertEquals(name, bundle.getBaseBundleName());
+
+ bundle = ResourceBundle.getBundle(name, Locale.getDefault());
+ assertEquals(name, bundle.getBaseBundleName());
+
+ assertNull(new Mock_ResourceBundle().getBaseBundleName());
+ }
+
/**
* java.util.ResourceBundle#getBundle(java.lang.String,
* java.util.Locale)
diff --git a/luni/src/test/java/libcore/java/util/ServiceLoaderTest.java b/luni/src/test/java/libcore/java/util/ServiceLoaderTest.java
index b69a2dd..f17eb22 100644
--- a/luni/src/test/java/libcore/java/util/ServiceLoaderTest.java
+++ b/luni/src/test/java/libcore/java/util/ServiceLoaderTest.java
@@ -44,7 +44,7 @@
ServiceLoader.load(ServiceLoaderTestInterfaceMissingClass.class).iterator().next();
fail();
} catch (ServiceConfigurationError expected) {
- assertTrue(expected.getCause() instanceof ClassNotFoundException);
+ assertTrue(expected.toString(), expected.getCause() instanceof ClassNotFoundException);
}
}
@@ -55,7 +55,7 @@
ServiceLoader.load(ServiceLoaderTestInterfaceWrongType.class).iterator().next();
fail();
} catch (ServiceConfigurationError expected) {
- assertTrue(expected.getCause() instanceof ClassCastException);
+ assertTrue(expected.toString(), expected.getCause() instanceof ClassCastException);
}
}
diff --git a/ojluni/src/main/java/java/util/ListResourceBundle.java b/ojluni/src/main/java/java/util/ListResourceBundle.java
index 736016f..99090ca 100644
--- a/ojluni/src/main/java/java/util/ListResourceBundle.java
+++ b/ojluni/src/main/java/java/util/ListResourceBundle.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2013, 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
@@ -89,7 +89,7 @@
*
* public class MyResources_fr extends ListResourceBundle {
* protected Object[][] getContents() {
- * return new Object[][] = {
+ * return new Object[][] {
* // LOCALIZE THIS
* {"s1", "Le disque \"{1}\" {0}."}, // MessageFormat pattern
* {"s2", "1"}, // location of {0} in pattern
@@ -105,6 +105,12 @@
* }
* </pre>
* </blockquote>
+ *
+ * <p>
+ * The implementation of a {@code ListResourceBundle} subclass must be thread-safe
+ * if it's simultaneously used by multiple threads. The default implementations
+ * of the methods in this class are thread-safe.
+ *
* @see ResourceBundle
* @see PropertyResourceBundle
* @since JDK1.1
@@ -200,5 +206,8 @@
lookup = temp;
}
- private Map<String,Object> lookup = null;
+ // Android-changed: Fix unsafe publication http://b/31467561
+ // Fixed in OpenJDK 9: http://hg.openjdk.java.net/jdk9/dev/jdk/rev/29ecac30ecae
+ // was: private Map<String,Object> lookup = null;
+ private volatile Map<String,Object> lookup = null;
}
diff --git a/ojluni/src/main/java/java/util/PropertyResourceBundle.java b/ojluni/src/main/java/java/util/PropertyResourceBundle.java
index c7fe3b4..23c3ffe 100644
--- a/ojluni/src/main/java/java/util/PropertyResourceBundle.java
+++ b/ojluni/src/main/java/java/util/PropertyResourceBundle.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2013, 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
@@ -100,6 +100,11 @@
* </blockquote>
*
* <p>
+ * The implementation of a {@code PropertyResourceBundle} subclass must be
+ * thread-safe if it's simultaneously used by multiple threads. The default
+ * implementations of the non-abstract methods in this class are thread-safe.
+ *
+ * <p>
* <strong>Note:</strong> PropertyResourceBundle can be constructed either
* from an InputStream or a Reader, which represents a property file.
* Constructing a PropertyResourceBundle instance from an InputStream requires
@@ -124,7 +129,10 @@
* to read from.
* @throws IOException if an I/O error occurs
* @throws NullPointerException if <code>stream</code> is null
+ * @throws IllegalArgumentException if {@code stream} contains a
+ * malformed Unicode escape sequence.
*/
+ @SuppressWarnings({"unchecked", "rawtypes"})
public PropertyResourceBundle (InputStream stream) throws IOException {
Properties properties = new Properties();
properties.load(stream);
@@ -141,8 +149,11 @@
* read from.
* @throws IOException if an I/O error occurs
* @throws NullPointerException if <code>reader</code> is null
+ * @throws IllegalArgumentException if a malformed Unicode escape sequence appears
+ * from {@code reader}.
* @since 1.6
*/
+ @SuppressWarnings({"unchecked", "rawtypes"})
public PropertyResourceBundle (Reader reader) throws IOException {
Properties properties = new Properties();
properties.load(reader);
@@ -186,5 +197,8 @@
// ==================privates====================
- private Map<String,Object> lookup;
+ // Android-changed: Fix unsafe publication http://b/31467561
+ // Fixed in OpenJDK 9: http://hg.openjdk.java.net/jdk9/dev/jdk/rev/29ecac30ecae
+ // was: private Map<String,Object> lookup;
+ private final Map<String,Object> lookup;
}
diff --git a/ojluni/src/main/java/java/util/ResourceBundle.java b/ojluni/src/main/java/java/util/ResourceBundle.java
index f604089..bf73b03 100644
--- a/ojluni/src/main/java/java/util/ResourceBundle.java
+++ b/ojluni/src/main/java/java/util/ResourceBundle.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2013, 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
@@ -41,7 +41,6 @@
package java.util;
-import dalvik.system.VMStack;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -59,7 +58,9 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.jar.JarEntry;
+import java.util.spi.ResourceBundleControlProvider;
+import dalvik.system.VMStack;
import sun.reflect.CallerSensitive;
import sun.util.locale.BaseLocale;
import sun.util.locale.LocaleObjectCache;
@@ -76,7 +77,7 @@
*
* <p>
* This allows you to write programs that can:
- * <UL type=SQUARE>
+ * <UL>
* <LI> be easily localized, or translated, into different languages
* <LI> handle multiple locales at once
* <LI> be easily modified later to support even more locales
@@ -186,7 +187,14 @@
* subclass. Your subclasses must override two methods: <code>handleGetObject</code>
* and <code>getKeys()</code>.
*
- * <h4>ResourceBundle.Control</h4>
+ * <p>
+ * The implementation of a {@code ResourceBundle} subclass must be thread-safe
+ * if it's simultaneously used by multiple threads. The default implementations
+ * of the non-abstract methods in this class, and the methods in the direct
+ * known concrete subclasses {@code ListResourceBundle} and
+ * {@code PropertyResourceBundle} are thread-safe.
+ *
+ * <h3>ResourceBundle.Control</h3>
*
* The {@link ResourceBundle.Control} class provides information necessary
* to perform the bundle loading process by the <code>getBundle</code>
@@ -197,7 +205,18 @@
* {@link #getBundle(String, Locale, ClassLoader, Control) getBundle}
* factory method for details.
*
- * <h4>Cache Management</h4>
+ * <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 <em>installed</em> {@link
+ * ResourceBundleControlProvider} implementations. Any installed providers are
+ * detected at the {@code ResourceBundle} class loading time. 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 installed for supporting the same base name,
+ * the first one returned from {@link ServiceLoader} will be used.
+ *
+ * <h3>Cache Management</h3>
*
* Resource bundle instances created by the <code>getBundle</code> factory
* methods are cached by default, and the factory methods return the same
@@ -213,7 +232,7 @@
* Control#needsReload(String, Locale, String, ClassLoader, ResourceBundle,
* long) ResourceBundle.Control.needsReload} for details.
*
- * <h4>Example</h4>
+ * <h3>Example</h3>
*
* The following is a very simple example of a <code>ResourceBundle</code>
* subclass, <code>MyResources</code>, that manages two resources (for a larger number of
@@ -299,7 +318,25 @@
/**
* Queue for reference objects referring to class loaders or bundles.
*/
- private static final ReferenceQueue referenceQueue = new ReferenceQueue();
+ private static final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
+
+ /**
+ * Returns the base name of this bundle, if known, or {@code null} if unknown.
+ *
+ * If not null, then this is the value of the {@code baseName} parameter
+ * that was passed to the {@code ResourceBundle.getBundle(...)} method
+ * when the resource bundle was loaded.
+ *
+ * @return The base name of the resource bundle, as provided to and expected
+ * by the {@code ResourceBundle.getBundle(...)} methods.
+ *
+ * @see #getBundle(java.lang.String, java.util.Locale, java.lang.ClassLoader)
+ *
+ * @since 1.8
+ */
+ public String getBaseBundleName() {
+ return name;
+ }
/**
* The parent bundle of this bundle.
@@ -334,6 +371,21 @@
*/
private volatile Set<String> keySet;
+ private static final List<ResourceBundleControlProvider> providers;
+
+ static {
+ List<ResourceBundleControlProvider> list = null;
+ ServiceLoader<ResourceBundleControlProvider> serviceLoaders
+ = ServiceLoader.loadInstalled(ResourceBundleControlProvider.class);
+ for (ResourceBundleControlProvider provider : serviceLoaders) {
+ if (list == null) {
+ list = new ArrayList<>();
+ }
+ list.add(provider);
+ }
+ providers = list;
+ }
+
/**
* Sole constructor. (For invocation by subclass constructors, typically
* implicit.)
@@ -394,12 +446,13 @@
if (parent != null) {
obj = parent.getObject(key);
}
- if (obj == null)
+ if (obj == null) {
throw new MissingResourceException("Can't find resource for bundle "
+this.getClass().getName()
+", key "+key,
this.getClass().getName(),
key);
+ }
}
return obj;
}
@@ -420,6 +473,10 @@
* resources on behalf of the client.
*/
private static ClassLoader getLoader(ClassLoader cl) {
+ // Android-changed: On Android, this method takes a ClassLoader argument:
+ // Callers call {@code getLoader(VMStack.getCallingClassLoader())}
+ // instead of {@code getLoader(Reflection.getCallerClass())}.
+ // ClassLoader cl = caller == null ? null : caller.getClassLoader();
if (cl == null) {
// When the caller's loader is the boot class loader, cl is null
// here. In that case, ClassLoader.getSystemClassLoader() may
@@ -486,7 +543,7 @@
* null, but the base name and the locale must have a non-null
* value.
*/
- private static final class CacheKey implements Cloneable {
+ private static class CacheKey implements Cloneable {
// These three are the actual keys for lookup in Map.
private String name;
private Locale locale;
@@ -581,8 +638,7 @@
// treat it as unequal
&& (loader != null)
&& (loader == otherEntry.loaderRef.get());
- } catch (NullPointerException e) {
- } catch (ClassCastException e) {
+ } catch ( NullPointerException | ClassCastException e) {
}
return false;
}
@@ -612,7 +668,7 @@
return clone;
} catch (CloneNotSupportedException e) {
//this should never happen
- throw new InternalError();
+ throw new InternalError(e);
}
}
@@ -667,11 +723,11 @@
* garbage collected when nobody else is using them. The ResourceBundle
* class has no reason to keep class loaders alive.
*/
- private static final class LoaderReference extends WeakReference<ClassLoader>
- implements CacheKeyReference {
+ private static class LoaderReference extends WeakReference<ClassLoader>
+ implements CacheKeyReference {
private CacheKey cacheKey;
- LoaderReference(ClassLoader referent, ReferenceQueue q, CacheKey key) {
+ LoaderReference(ClassLoader referent, ReferenceQueue<Object> q, CacheKey key) {
super(referent, q);
cacheKey = key;
}
@@ -685,11 +741,11 @@
* References to bundles are soft references so that they can be garbage
* collected when they have no hard references.
*/
- private static final class BundleReference extends SoftReference<ResourceBundle>
- implements CacheKeyReference {
+ private static class BundleReference extends SoftReference<ResourceBundle>
+ implements CacheKeyReference {
private CacheKey cacheKey;
- BundleReference(ResourceBundle referent, ReferenceQueue q, CacheKey key) {
+ BundleReference(ResourceBundle referent, ReferenceQueue<Object> q, CacheKey key) {
super(referent, q);
cacheKey = key;
}
@@ -721,9 +777,10 @@
public static final ResourceBundle getBundle(String baseName)
{
return getBundleImpl(baseName, Locale.getDefault(),
- /* must determine loader here, else we break stack invariant */
+ // Android-changed: use of VMStack.getCallingClassLoader()
getLoader(VMStack.getCallingClassLoader()),
- Control.INSTANCE);
+ // getLoader(Reflection.getCallerClass()),
+ getDefaultControl(baseName));
}
/**
@@ -764,8 +821,9 @@
public static final ResourceBundle getBundle(String baseName,
Control control) {
return getBundleImpl(baseName, Locale.getDefault(),
- /* must determine loader here, else we break stack invariant */
+ // Android-changed: use of VMStack.getCallingClassLoader()
getLoader(VMStack.getCallingClassLoader()),
+ // getLoader(Reflection.getCallerClass()),
control);
}
@@ -795,9 +853,10 @@
Locale locale)
{
return getBundleImpl(baseName, locale,
- /* must determine loader here, else we break stack invariant */
+ // Android-changed: use of VMStack.getCallingClassLoader()
getLoader(VMStack.getCallingClassLoader()),
- Control.INSTANCE);
+ // getLoader(Reflection.getCallerClass()),
+ getDefaultControl(baseName));
}
/**
@@ -841,8 +900,9 @@
public static final ResourceBundle getBundle(String baseName, Locale targetLocale,
Control control) {
return getBundleImpl(baseName, targetLocale,
- /* must determine loader here, else we break stack invariant */
+ // Android-changed: use of VMStack.getCallingClassLoader()
getLoader(VMStack.getCallingClassLoader()),
+ // getLoader(Reflection.getCallerClass()),
control);
}
@@ -850,9 +910,15 @@
* Gets a resource bundle using the specified base name, locale, and class
* loader.
*
- * <p><a name="default_behavior"></a>This method behaves the same as calling
+ * <p>This method behaves the same as calling
* {@link #getBundle(String, Locale, ClassLoader, Control)} passing a
- * default instance of {@link Control}. The following describes this behavior.
+ * 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>.
*
* <p><code>getBundle</code> uses the base name, the specified locale, and
* the default locale (obtained from {@link java.util.Locale#getDefault()
@@ -947,8 +1013,8 @@
* <p>If still no result bundle is found, the base name alone is looked up. If
* this still fails, a <code>MissingResourceException</code> is thrown.
*
- * <p><a name="parent_chain"></a> Once a result resource bundle has been found,
- * its <em>parent chain</em> is instantiated. If the result bundle already
+ * <p><a name="parent_chain"> Once a result resource bundle has been found,
+ * its <em>parent chain</em> is instantiated</a>. If the result bundle already
* has a parent (perhaps because it was returned from a cache) the chain is
* complete.
*
@@ -977,8 +1043,8 @@
* path name (using "/") instead of a fully qualified class name (using
* ".").
*
- * <p><a name="default_behavior_example"></a>
- * <strong>Example:</strong>
+ * <p><a name="default_behavior_example">
+ * <strong>Example:</strong></a>
* <p>
* The following class and property files are provided:
* <pre>
@@ -999,7 +1065,7 @@
* <p>Calling <code>getBundle</code> with the locale arguments below will
* instantiate resource bundles as follows:
*
- * <table>
+ * <table summary="getBundle() locale to resource bundle mapping">
* <tr><td>Locale("fr", "CH")</td><td>MyResources_fr_CH.class, parent MyResources_fr.properties, parent MyResources.class</td></tr>
* <tr><td>Locale("fr", "FR")</td><td>MyResources_fr.properties, parent MyResources.class</td></tr>
* <tr><td>Locale("de", "DE")</td><td>MyResources_en.properties, parent MyResources.class</td></tr>
@@ -1027,7 +1093,7 @@
if (loader == null) {
throw new NullPointerException();
}
- return getBundleImpl(baseName, locale, loader, Control.INSTANCE);
+ return getBundleImpl(baseName, locale, loader, getDefaultControl(baseName));
}
/**
@@ -1039,7 +1105,6 @@
* bundles. Conceptually, the bundle loading process with the given
* <code>control</code> is performed in the following steps.
*
- * <p>
* <ol>
* <li>This factory method looks up the resource bundle in the cache for
* the specified <code>baseName</code>, <code>targetLocale</code> and
@@ -1079,45 +1144,45 @@
* <code>control.newBundle</code>.
*
* <table style="width: 50%; text-align: left; margin-left: 40px;"
- * border="0" cellpadding="2" cellspacing="2">
- * <tbody><code>
+ * border="0" cellpadding="2" cellspacing="2" summary="locale-format combinations for newBundle">
+ * <tbody>
* <tr>
* <td
- * style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;">Locale<br>
+ * style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;"><code>Locale</code><br>
* </td>
* <td
- * style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;">format<br>
+ * style="vertical-align: top; text-align: left; font-weight: bold; width: 50%;"><code>format</code><br>
* </td>
* </tr>
* <tr>
- * <td style="vertical-align: top; width: 50%;">Locale("de", "DE")<br>
+ * <td style="vertical-align: top; width: 50%;"><code>Locale("de", "DE")</code><br>
* </td>
- * <td style="vertical-align: top; width: 50%;">java.class<br>
+ * <td style="vertical-align: top; width: 50%;"><code>java.class</code><br>
* </td>
* </tr>
* <tr>
- * <td style="vertical-align: top; width: 50%;">Locale("de", "DE")</td>
- * <td style="vertical-align: top; width: 50%;">java.properties<br>
+ * <td style="vertical-align: top; width: 50%;"><code>Locale("de", "DE")</code></td>
+ * <td style="vertical-align: top; width: 50%;"><code>java.properties</code><br>
* </td>
* </tr>
* <tr>
- * <td style="vertical-align: top; width: 50%;">Locale("de")</td>
- * <td style="vertical-align: top; width: 50%;">java.class</td>
+ * <td style="vertical-align: top; width: 50%;"><code>Locale("de")</code></td>
+ * <td style="vertical-align: top; width: 50%;"><code>java.class</code></td>
* </tr>
* <tr>
- * <td style="vertical-align: top; width: 50%;">Locale("de")</td>
- * <td style="vertical-align: top; width: 50%;">java.properties</td>
+ * <td style="vertical-align: top; width: 50%;"><code>Locale("de")</code></td>
+ * <td style="vertical-align: top; width: 50%;"><code>java.properties</code></td>
* </tr>
* <tr>
- * <td style="vertical-align: top; width: 50%;">Locale("")<br>
+ * <td style="vertical-align: top; width: 50%;"><code>Locale("")</code><br>
* </td>
- * <td style="vertical-align: top; width: 50%;">java.class</td>
+ * <td style="vertical-align: top; width: 50%;"><code>java.class</code></td>
* </tr>
* <tr>
- * <td style="vertical-align: top; width: 50%;">Locale("")</td>
- * <td style="vertical-align: top; width: 50%;">java.properties</td>
+ * <td style="vertical-align: top; width: 50%;"><code>Locale("")</code></td>
+ * <td style="vertical-align: top; width: 50%;"><code>java.properties</code></td>
* </tr>
- * </code></tbody>
+ * </tbody>
* </table>
* </li>
*
@@ -1210,7 +1275,7 @@
* <p><code>getBundle</code> finds
* <code>foo/bar/Messages_fr.properties</code> and creates a
* <code>ResourceBundle</code> instance. Then, <code>getBundle</code>
- * sets up its parent chain from the list of the candiate locales. Only
+ * sets up its parent chain from the list of the candidate locales. Only
* <code>foo/bar/Messages.properties</code> is found in the list and
* <code>getBundle</code> creates a <code>ResourceBundle</code> instance
* that becomes the parent of the instance for
@@ -1248,6 +1313,18 @@
return getBundleImpl(baseName, targetLocale, loader, control);
}
+ private static Control getDefaultControl(String baseName) {
+ if (providers != null) {
+ for (ResourceBundleControlProvider provider : providers) {
+ Control control = provider.getControl(baseName);
+ if (control != null) {
+ return control;
+ }
+ }
+ }
+ return Control.INSTANCE;
+ }
+
private static ResourceBundle getBundleImpl(String baseName, Locale locale,
ClassLoader loader, Control control) {
if (locale == null || control == null) {
@@ -1333,8 +1410,8 @@
* Checks if the given <code>List</code> is not null, not empty,
* not having null in its elements.
*/
- private static final boolean checkList(List a) {
- boolean valid = (a != null && a.size() != 0);
+ private static boolean checkList(List<?> a) {
+ boolean valid = (a != null && !a.isEmpty());
if (valid) {
int size = a.size();
for (int i = 0; valid && i < size; i++) {
@@ -1344,12 +1421,12 @@
return valid;
}
- private static final ResourceBundle findBundle(CacheKey cacheKey,
- List<Locale> candidateLocales,
- List<String> formats,
- int index,
- Control control,
- ResourceBundle baseBundle) {
+ private static ResourceBundle findBundle(CacheKey cacheKey,
+ List<Locale> candidateLocales,
+ List<String> formats,
+ int index,
+ Control control,
+ ResourceBundle baseBundle) {
Locale targetLocale = candidateLocales.get(index);
ResourceBundle parent = null;
if (index != candidateLocales.size() - 1) {
@@ -1421,10 +1498,10 @@
return parent;
}
- private static final ResourceBundle loadBundle(CacheKey cacheKey,
- List<String> formats,
- Control control,
- boolean reload) {
+ private static ResourceBundle loadBundle(CacheKey cacheKey,
+ List<String> formats,
+ Control control,
+ boolean reload) {
// Here we actually load the bundle in the order of formats
// specified by the getFormats() value.
@@ -1461,7 +1538,7 @@
return bundle;
}
- private static final boolean isValidBundle(ResourceBundle bundle) {
+ private static boolean isValidBundle(ResourceBundle bundle) {
return bundle != null && bundle != NONEXISTENT_BUNDLE;
}
@@ -1469,7 +1546,7 @@
* Determines whether any of resource bundles in the parent chain,
* including the leaf, have expired.
*/
- private static final boolean hasValidParentChain(ResourceBundle bundle) {
+ private static boolean hasValidParentChain(ResourceBundle bundle) {
long now = System.currentTimeMillis();
while (bundle != null) {
if (bundle.expired) {
@@ -1490,9 +1567,9 @@
/**
* Throw a MissingResourceException with proper message
*/
- private static final void throwMissingResourceException(String baseName,
- Locale locale,
- Throwable cause) {
+ private static void throwMissingResourceException(String baseName,
+ Locale locale,
+ Throwable cause) {
// If the cause is a MissingResourceException, avoid creating
// a long chain. (6355009)
if (cause instanceof MissingResourceException) {
@@ -1515,8 +1592,8 @@
* cache or its parent has expired. <code>bundle.expire</code> is true
* upon return if the bundle in the cache has expired.
*/
- private static final ResourceBundle findBundleInCache(CacheKey cacheKey,
- Control control) {
+ private static ResourceBundle findBundleInCache(CacheKey cacheKey,
+ Control control) {
BundleReference bundleRef = cacheList.get(cacheKey);
if (bundleRef == null) {
return null;
@@ -1622,9 +1699,9 @@
* the bundle before this call, the one found in the cache is
* returned.
*/
- private static final ResourceBundle putBundleInCache(CacheKey cacheKey,
- ResourceBundle bundle,
- Control control) {
+ private static ResourceBundle putBundleInCache(CacheKey cacheKey,
+ ResourceBundle bundle,
+ Control control) {
setExpirationTime(cacheKey, control);
if (cacheKey.expirationTime != Control.TTL_DONT_CACHE) {
CacheKey key = (CacheKey) cacheKey.clone();
@@ -1655,7 +1732,7 @@
return bundle;
}
- private static final void setExpirationTime(CacheKey cacheKey, Control control) {
+ private static void setExpirationTime(CacheKey cacheKey, Control control) {
long ttl = control.getTimeToLive(cacheKey.getName(),
cacheKey.getLocale());
if (ttl >= 0) {
@@ -1680,7 +1757,9 @@
*/
@CallerSensitive
public static final void clearCache() {
+ // Android-changed: use of VMStack.getCallingClassLoader()
clearCache(getLoader(VMStack.getCallingClassLoader()));
+ // clearCache(getLoader(Reflection.getCallerClass()));
}
/**
@@ -2160,11 +2239,11 @@
* one by one as below:
*
* <ul>
- * <li> [<em>L</em>, <em>C</em>, <em>V</em>]
- * <li> [<em>L</em>, <em>C</em>]
- * <li> [<em>L</em>]
- * <li> <code>Locale.ROOT</code>
- * </ul>
+ * <li> [<em>L</em>, <em>C</em>, <em>V</em>] </li>
+ * <li> [<em>L</em>, <em>C</em>] </li>
+ * <li> [<em>L</em>] </li>
+ * <li> <code>Locale.ROOT</code> </li>
+ * </ul></li>
*
* <li>For an input <code>Locale</code> with a non-empty script value,
* append candidate <code>Locale</code>s by omitting the final component
@@ -2172,33 +2251,33 @@
* <code>Locale</code> with country and variant restored:
*
* <ul>
- * <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V</em>]
- * <li> [<em>L</em>, <em>S</em>, <em>C</em>]
- * <li> [<em>L</em>, <em>S</em>]
- * <li> [<em>L</em>, <em>C</em>, <em>V</em>]
- * <li> [<em>L</em>, <em>C</em>]
- * <li> [<em>L</em>]
- * <li> <code>Locale.ROOT</code>
- * </ul>
+ * <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V</em>]</li>
+ * <li> [<em>L</em>, <em>S</em>, <em>C</em>]</li>
+ * <li> [<em>L</em>, <em>S</em>]</li>
+ * <li> [<em>L</em>, <em>C</em>, <em>V</em>]</li>
+ * <li> [<em>L</em>, <em>C</em>]</li>
+ * <li> [<em>L</em>]</li>
+ * <li> <code>Locale.ROOT</code></li>
+ * </ul></li>
*
* <li>For an input <code>Locale</code> with a variant value consisting
* of multiple subtags separated by underscore, generate candidate
* <code>Locale</code>s by omitting the variant subtags one by one, then
- * insert them after every occurence of <code> Locale</code>s with the
+ * insert them after every occurrence of <code> Locale</code>s with the
* full variant value in the original list. For example, if the
* the variant consists of two subtags <em>V1</em> and <em>V2</em>:
*
* <ul>
- * <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V1</em>, <em>V2</em>]
- * <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V1</em>]
- * <li> [<em>L</em>, <em>S</em>, <em>C</em>]
- * <li> [<em>L</em>, <em>S</em>]
- * <li> [<em>L</em>, <em>C</em>, <em>V1</em>, <em>V2</em>]
- * <li> [<em>L</em>, <em>C</em>, <em>V1</em>]
- * <li> [<em>L</em>, <em>C</em>]
- * <li> [<em>L</em>]
- * <li> <code>Locale.ROOT</code>
- * </ul>
+ * <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V1</em>, <em>V2</em>]</li>
+ * <li> [<em>L</em>, <em>S</em>, <em>C</em>, <em>V1</em>]</li>
+ * <li> [<em>L</em>, <em>S</em>, <em>C</em>]</li>
+ * <li> [<em>L</em>, <em>S</em>]</li>
+ * <li> [<em>L</em>, <em>C</em>, <em>V1</em>, <em>V2</em>]</li>
+ * <li> [<em>L</em>, <em>C</em>, <em>V1</em>]</li>
+ * <li> [<em>L</em>, <em>C</em>]</li>
+ * <li> [<em>L</em>]</li>
+ * <li> <code>Locale.ROOT</code></li>
+ * </ul></li>
*
* <li>Special cases for Chinese. When an input <code>Locale</code> has the
* language "zh" (Chinese) and an empty script value, either "Hans" (Simplified) or
@@ -2209,21 +2288,21 @@
* is empty, no script is supplied. For example, for <code>Locale("zh", "CN")
* </code>, the candidate list will be:
* <ul>
- * <li> [<em>L</em>("zh"), <em>S</em>("Hans"), <em>C</em>("CN")]
- * <li> [<em>L</em>("zh"), <em>S</em>("Hans")]
- * <li> [<em>L</em>("zh"), <em>C</em>("CN")]
- * <li> [<em>L</em>("zh")]
- * <li> <code>Locale.ROOT</code>
+ * <li> [<em>L</em>("zh"), <em>S</em>("Hans"), <em>C</em>("CN")]</li>
+ * <li> [<em>L</em>("zh"), <em>S</em>("Hans")]</li>
+ * <li> [<em>L</em>("zh"), <em>C</em>("CN")]</li>
+ * <li> [<em>L</em>("zh")]</li>
+ * <li> <code>Locale.ROOT</code></li>
* </ul>
*
* For <code>Locale("zh", "TW")</code>, the candidate list will be:
* <ul>
- * <li> [<em>L</em>("zh"), <em>S</em>("Hant"), <em>C</em>("TW")]
- * <li> [<em>L</em>("zh"), <em>S</em>("Hant")]
- * <li> [<em>L</em>("zh"), <em>C</em>("TW")]
- * <li> [<em>L</em>("zh")]
- * <li> <code>Locale.ROOT</code>
- * </ul>
+ * <li> [<em>L</em>("zh"), <em>S</em>("Hant"), <em>C</em>("TW")]</li>
+ * <li> [<em>L</em>("zh"), <em>S</em>("Hant")]</li>
+ * <li> [<em>L</em>("zh"), <em>C</em>("TW")]</li>
+ * <li> [<em>L</em>("zh")]</li>
+ * <li> <code>Locale.ROOT</code></li>
+ * </ul></li>
*
* <li>Special cases for Norwegian. Both <code>Locale("no", "NO",
* "NY")</code> and <code>Locale("nn", "NO")</code> represent Norwegian
@@ -2231,10 +2310,10 @@
* list is generated up to [<em>L</em>("nn")], and then the following
* candidates are added:
*
- * <ul><li> [<em>L</em>("no"), <em>C</em>("NO"), <em>V</em>("NY")]
- * <li> [<em>L</em>("no"), <em>C</em>("NO")]
- * <li> [<em>L</em>("no")]
- * <li> <code>Locale.ROOT</code>
+ * <ul><li> [<em>L</em>("no"), <em>C</em>("NO"), <em>V</em>("NY")]</li>
+ * <li> [<em>L</em>("no"), <em>C</em>("NO")]</li>
+ * <li> [<em>L</em>("no")]</li>
+ * <li> <code>Locale.ROOT</code></li>
* </ul>
*
* If the locale is exactly <code>Locale("no", "NO", "NY")</code>, it is first
@@ -2251,20 +2330,18 @@
* candidate list:
*
* <ul>
- * <li> [<em>L</em>("nb"), <em>C</em>("NO"), <em>V</em>("POSIX")]
- * <li> [<em>L</em>("no"), <em>C</em>("NO"), <em>V</em>("POSIX")]
- * <li> [<em>L</em>("nb"), <em>C</em>("NO")]
- * <li> [<em>L</em>("no"), <em>C</em>("NO")]
- * <li> [<em>L</em>("nb")]
- * <li> [<em>L</em>("no")]
- * <li> <code>Locale.ROOT</code>
+ * <li> [<em>L</em>("nb"), <em>C</em>("NO"), <em>V</em>("POSIX")]</li>
+ * <li> [<em>L</em>("no"), <em>C</em>("NO"), <em>V</em>("POSIX")]</li>
+ * <li> [<em>L</em>("nb"), <em>C</em>("NO")]</li>
+ * <li> [<em>L</em>("no"), <em>C</em>("NO")]</li>
+ * <li> [<em>L</em>("nb")]</li>
+ * <li> [<em>L</em>("no")]</li>
+ * <li> <code>Locale.ROOT</code></li>
* </ul>
*
* <code>Locale("no", "NO", "POSIX")</code> would generate the same list
* except that locales with "no" would appear before the corresponding
* locales with "nb".</li>
- *
- * </li>
* </ol>
*
* <p>The default implementation uses an {@link ArrayList} that
@@ -2284,9 +2361,9 @@
* is returned. And if the resource bundles for the "ja" and
* "" <code>Locale</code>s are found, then the runtime resource
* lookup path (parent chain) is:
- * <pre>
+ * <pre>{@code
* Messages_ja -> Messages
- * </pre>
+ * }</pre>
*
* @param baseName
* the base name of the resource bundle, a fully
@@ -2353,18 +2430,27 @@
if (script.length() == 0 && region.length() > 0) {
// Supply script for users who want to use zh_Hans/zh_Hant
// as bundle names (recommended for Java7+)
- if (region.equals("TW") || region.equals("HK") || region.equals("MO")) {
+ switch (region) {
+ case "TW":
+ case "HK":
+ case "MO":
script = "Hant";
- } else if (region.equals("CN") || region.equals("SG")) {
+ break;
+ case "CN":
+ case "SG":
script = "Hans";
+ break;
}
} else if (script.length() > 0 && region.length() == 0) {
// Supply region(country) for users who still package Chinese
// bundles using old convension.
- if (script.equals("Hans")) {
+ switch (script) {
+ case "Hans":
region = "CN";
- } else if (script.equals("Hant")) {
+ break;
+ case "Hant":
region = "TW";
+ break;
}
}
}
@@ -2565,6 +2651,7 @@
ResourceBundle bundle = null;
if (format.equals("java.class")) {
try {
+ @SuppressWarnings("unchecked")
Class<? extends ResourceBundle> bundleClass
= (Class<? extends ResourceBundle>)loader.loadClass(bundleName);
@@ -2579,7 +2666,10 @@
} catch (ClassNotFoundException e) {
}
} else if (format.equals("java.properties")) {
- final String resourceName = toResourceName(bundleName, "properties");
+ final String resourceName = toResourceName0(bundleName, "properties");
+ if (resourceName == null) {
+ return bundle;
+ }
final ClassLoader classLoader = loader;
final boolean reloadFlag = reload;
InputStream stream = null;
@@ -2610,8 +2700,10 @@
}
if (stream != null) {
try {
+ // Android-changed: Use UTF-8 for property based resources. b/26879578
bundle = new PropertyResourceBundle(
new InputStreamReader(stream, StandardCharsets.UTF_8));
+ // bundle = new PropertyResourceBundle(stream);
} finally {
stream.close();
}
@@ -2734,7 +2826,10 @@
}
boolean result = false;
try {
- String resourceName = toResourceName(toBundleName(baseName, locale), format);
+ String resourceName = toResourceName0(toBundleName(baseName, locale), format);
+ if (resourceName == null) {
+ return result;
+ }
URL url = loader.getResource(resourceName);
if (url != null) {
long lastModified = 0;
@@ -2780,7 +2875,7 @@
* and <code>variant</code> are the language, script, country, and variant
* values of <code>locale</code>, respectively. Final component values that
* are empty Strings are omitted along with the preceding '_'. When the
- * script is empty, the script value is ommitted along with the preceding '_'.
+ * script is empty, the script value is omitted along with the preceding '_'.
* If all of the values are empty strings, then <code>baseName</code>
* is returned.
*
@@ -2868,6 +2963,15 @@
sb.append(bundleName.replace('.', '/')).append('.').append(suffix);
return sb.toString();
}
+
+ private String toResourceName0(String bundleName, String suffix) {
+ // application protocol check
+ if (bundleName.contains("://")) {
+ return null;
+ } else {
+ return toResourceName(bundleName, suffix);
+ }
+ }
}
private static class SingleFormatControl extends Control {
diff --git a/ojluni/src/main/java/java/util/ServiceLoader.java b/ojluni/src/main/java/java/util/ServiceLoader.java
index 4b3b029..8a0b3a8 100644
--- a/ojluni/src/main/java/java/util/ServiceLoader.java
+++ b/ojluni/src/main/java/java/util/ServiceLoader.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2013, 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
@@ -66,12 +66,13 @@
*
* <p><a name="format"> A service provider is identified by placing a
* <i>provider-configuration file</i> in the resource directory
- * <tt>META-INF/services</tt>. The file's name is the fully-qualified <a
+ * <tt>META-INF/services</tt>.</a> The file's name is the fully-qualified <a
* href="../lang/ClassLoader.html#name">binary name</a> of the service's type.
* The file contains a list of fully-qualified binary names of concrete
* provider classes, one per line. Space and tab characters surrounding each
* name, as well as blank lines, are ignored. The comment character is
- * <tt>'#'</tt> (<tt>'\u0023'</tt>, <font size="-1">NUMBER SIGN</font>); on
+ * <tt>'#'</tt> (<tt>'\u0023'</tt>,
+ * <font style="font-size:smaller;">NUMBER SIGN</font>); on
* each line all characters following the first comment character are ignored.
* The file must be encoded in UTF-8.
*
@@ -186,10 +187,14 @@
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
- private Class<S> service;
+ private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
- private ClassLoader loader;
+ private final ClassLoader loader;
+
+ // The access control context taken when the ServiceLoader is created
+ // Android-changed: do not use legacy security code
+ // private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
@@ -214,25 +219,28 @@
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
- service = svc;
- loader = cl;
+ service = Objects.requireNonNull(svc, "Service interface cannot be null");
+ loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
+ // Android-changed: Do not use legacy security code (System.getSecurtiyManager()
+ // is always null).
+ // acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
- private static void fail(Class service, String msg, Throwable cause)
+ private static void fail(Class<?> service, String msg, Throwable cause)
throws ServiceConfigurationError
{
throw new ServiceConfigurationError(service.getName() + ": " + msg,
cause);
}
- private static void fail(Class service, String msg)
+ private static void fail(Class<?> service, String msg)
throws ServiceConfigurationError
{
throw new ServiceConfigurationError(service.getName() + ": " + msg);
}
- private static void fail(Class service, URL u, int line, String msg)
+ private static void fail(Class<?> service, URL u, int line, String msg)
throws ServiceConfigurationError
{
fail(service, u + ":" + line + ": " + msg);
@@ -241,7 +249,7 @@
// Parse a single line from the given configuration file, adding the name
// on the line to the names list.
//
- private int parseLine(Class service, URL u, BufferedReader r, int lc,
+ private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
List<String> names)
throws IOException, ServiceConfigurationError
{
@@ -287,7 +295,7 @@
// If an I/O error occurs while reading from the given URL, or
// if a configuration-file format error is detected
//
- private Iterator<String> parse(Class service, URL u)
+ private Iterator<String> parse(Class<?> service, URL u)
throws ServiceConfigurationError
{
InputStream in = null;
@@ -328,7 +336,7 @@
this.loader = loader;
}
- public boolean hasNext() {
+ private boolean hasNextService() {
if (nextName != null) {
return true;
}
@@ -353,10 +361,9 @@
return true;
}
- public S next() {
- if (!hasNext()) {
+ private S nextService() {
+ if (!hasNextService())
throw new NoSuchElementException();
- }
String cn = nextName;
nextName = null;
Class<?> c = null;
@@ -364,13 +371,18 @@
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
+ // Android-changed: Let the ServiceConfigurationError have a cause.
"Provider " + cn + " not found", x);
+ // "Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
+ // Android-changed: Let the ServiceConfigurationError have a cause.
ClassCastException cce = new ClassCastException(
service.getCanonicalName() + " is not assignable from " + c.getCanonicalName());
fail(service,
"Provider " + cn + " not a subtype", cce);
+ // fail(service,
+ // "Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
@@ -378,12 +390,40 @@
return p;
} catch (Throwable x) {
fail(service,
- "Provider " + cn + " could not be instantiated: " + x,
+ "Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
+ public boolean hasNext() {
+ // Android-changed: do not use legacy security code
+ /* if (acc == null) { */
+ return hasNextService();
+ /*
+ } else {
+ PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
+ public Boolean run() { return hasNextService(); }
+ };
+ return AccessController.doPrivileged(action, acc);
+ }
+ */
+ }
+
+ public S next() {
+ // Android-changed: do not use legacy security code
+ /* if (acc == null) { */
+ return nextService();
+ /*
+ } else {
+ PrivilegedAction<S> action = new PrivilegedAction<S>() {
+ public S run() { return nextService(); }
+ };
+ return AccessController.doPrivileged(action, acc);
+ }
+ */
+ }
+
public void remove() {
throw new UnsupportedOperationException();
}
@@ -427,6 +467,12 @@
* Invoking its {@link java.util.Iterator#remove() remove} method will
* cause an {@link UnsupportedOperationException} to be thrown.
*
+ * @implNote When adding providers to the cache, the {@link #iterator
+ * Iterator} processes resources in the order that the {@link
+ * java.lang.ClassLoader#getResources(java.lang.String)
+ * ClassLoader.getResources(String)} method finds the service configuration
+ * files.
+ *
* @return An iterator that lazily loads providers for this loader's
* service
*/
@@ -459,6 +505,8 @@
* Creates a new service loader for the given service type and class
* loader.
*
+ * @param <S> the class of the service type
+ *
* @param service
* The interface or abstract class representing the service
*
@@ -492,6 +540,8 @@
* ServiceLoader.load(<i>service</i>,
* Thread.currentThread().getContextClassLoader())</pre></blockquote>
*
+ * @param <S> the class of the service type
+ *
* @param service
* The interface or abstract class representing the service
*
@@ -521,6 +571,8 @@
* have been installed into the current Java virtual machine; providers on
* the application's class path will be ignored.
*
+ * @param <S> the class of the service type
+ *
* @param service
* The interface or abstract class representing the service
*
@@ -536,6 +588,8 @@
return ServiceLoader.load(service, prev);
}
+ // Android-changed BEGIN: Add a method to instantiate a class from a system
+ // property (used in other parts of libcore).
/**
* Internal API to support built-in SPIs that check a system property first.
* Returns an instance specified by a property with the class' binary name, or null if
@@ -554,6 +608,7 @@
throw new Error(e);
}
}
+ // Android-changed END
/**
* Returns a string describing this service.
diff --git a/ojluni/src/main/java/java/util/spi/ResourceBundleControlProvider.java b/ojluni/src/main/java/java/util/spi/ResourceBundleControlProvider.java
new file mode 100644
index 0000000..e731f0f
--- /dev/null
+++ b/ojluni/src/main/java/java/util/spi/ResourceBundleControlProvider.java
@@ -0,0 +1,65 @@
+/*
+ * 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. 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.
+ */
+
+package java.util.spi;
+
+import java.util.ResourceBundle;
+
+/**
+ * An interface for service providers that provide implementations of {@link
+ * java.util.ResourceBundle.Control}. The <a
+ * href="../ResourceBundle.html#default_behavior">default resource bundle loading
+ * behavior</a> of the {@code ResourceBundle.getBundle} factory methods that take
+ * no {@link java.util.ResourceBundle.Control} instance can be modified with {@code
+ * ResourceBundleControlProvider} implementations.
+ *
+ * <p>Provider implementations must be packaged using the <a
+ * href="{@docRoot}openjdk-redirect.html?v=8&path=/technotes/guides/extensions/index.html">Java Extension
+ * Mechanism</a> as installed extensions. Refer to {@link java.util.ServiceLoader}
+ * for the extension packaging. Any installed {@code
+ * ResourceBundleControlProvider} implementations are loaded using {@link
+ * java.util.ServiceLoader} at the {@code ResourceBundle} class loading time.
+ *
+ * @author Masayoshi Okutsu
+ * @since 1.8
+ * @see ResourceBundle#getBundle(String, java.util.Locale, ClassLoader, ResourceBundle.Control)
+ * ResourceBundle.getBundle
+ * @see java.util.ServiceLoader#loadInstalled(Class)
+ */
+public interface ResourceBundleControlProvider {
+ /**
+ * Returns a {@code ResourceBundle.Control} instance that is used
+ * to handle resource bundle loading for the given {@code
+ * baseName}. This method must return {@code null} if the given
+ * {@code baseName} isn't handled by this provider.
+ *
+ * @param baseName the base name of the resource bundle
+ * @return a {@code ResourceBundle.Control} instance,
+ * or {@code null} if the given {@code baseName} is not
+ * applicable to this provider.
+ * @throws NullPointerException if {@code baseName} is {@code null}
+ */
+ public ResourceBundle.Control getControl(String baseName);
+}
diff --git a/openjdk_java_files.mk b/openjdk_java_files.mk
index bf4126a..5818d65 100644
--- a/openjdk_java_files.mk
+++ b/openjdk_java_files.mk
@@ -1179,6 +1179,7 @@
ojluni/src/main/java/java/util/spi/CurrencyNameProvider.java \
ojluni/src/main/java/java/util/spi/LocaleNameProvider.java \
ojluni/src/main/java/java/util/spi/LocaleServiceProvider.java \
+ ojluni/src/main/java/java/util/spi/ResourceBundleControlProvider.java \
ojluni/src/main/java/java/util/spi/TimeZoneNameProvider.java \
ojluni/src/main/java/jdk/net/ExtendedSocketOptions.java \
ojluni/src/main/java/jdk/net/NetworkPermission.java \