d6b4693c05
Reviewed-by: iris, joehw
594 lines
24 KiB
Java
594 lines
24 KiB
Java
/*
|
|
* Copyright (c) 2007, 2022, 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
|
|
@summary test Resource Bundle for bug 4168625
|
|
@build Bug4168625Class Bug4168625Getter Bug4168625Resource Bug4168625Resource3 Bug4168625Resource3_en Bug4168625Resource3_en_CA Bug4168625Resource3_en_IE Bug4168625Resource3_en_US Bug4168625Resource2_en_US Bug4168625Resource2
|
|
@run main/timeout=600 Bug4168625Test
|
|
@bug 4168625 6993339
|
|
*/
|
|
/*
|
|
*
|
|
*
|
|
* (C) Copyright IBM Corp. 1999 - All Rights Reserved
|
|
*
|
|
* The original version of this source code and documentation is
|
|
* copyrighted and owned by IBM. These materials are provided
|
|
* under terms of a License Agreement between IBM and Sun.
|
|
* This technology is protected by multiple US and International
|
|
* patents. This notice and attribution to IBM may not be removed.
|
|
*
|
|
*/
|
|
|
|
import java.util.*;
|
|
import java.io.*;
|
|
|
|
/**
|
|
* This test tries to correct two efficiency problems with the caching
|
|
* mechanism of ResourceBundle. It also allows concurrent loads
|
|
* of resource bundles to be performed if the bundles are unrelated (ex. a
|
|
* load of a local system resource by one thread while another thread is
|
|
* doing a slow load over a network).
|
|
*/
|
|
public class Bug4168625Test extends RBTestFmwk {
|
|
public static void main(String[] args) throws Exception {
|
|
new Bug4168625Test().run(args);
|
|
}
|
|
|
|
/**
|
|
* Verify that getBundle will do something reasonable when part of the
|
|
* resource hierarchy is missing.
|
|
*/
|
|
public void testMissingParent() throws Exception {
|
|
final Locale oldDefault = Locale.getDefault();
|
|
Locale.setDefault(Locale.US);
|
|
try {
|
|
final Locale loc = Locale.of("jf", "jf");
|
|
ResourceBundle bundle = ResourceBundle.getBundle("Bug4168625Resource2", loc);
|
|
final String s1 = bundle.getString("name");
|
|
if (!s1.equals("Bug4168625Resource2_en_US")) {
|
|
errln("getBundle did not find leaf bundle: "+bundle.getClass().getName());
|
|
}
|
|
final String s2 = bundle.getString("baseName");
|
|
if (!s2.equals("Bug4168625Resource2")) {
|
|
errln("getBundle did not set up proper inheritance chain");
|
|
}
|
|
} finally {
|
|
Locale.setDefault(oldDefault);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Previous versions of ResourceBundle have had the following
|
|
* caching behavior. Assume the classes
|
|
* Bug4168625Resource_fr_FR, Bug4168625Resource_fr,
|
|
* Bug4168625Resource_en_US, and Bug4168625Resource_en don't
|
|
* exist. The class Bug4168625Resource does. Assume the default
|
|
* locale is en_US.
|
|
* <P>
|
|
* <pre>
|
|
* getBundle("Bug4168625Resource", Locale.FRANCE);
|
|
* -->try to load Bug4168625Resource_fr_FR
|
|
* -->try to load Bug4168625Resource_fr
|
|
* -->try to load Bug4168625Resource_en_US
|
|
* -->try to load Bug4168625Resource_en
|
|
* -->load Bug4168625Resource
|
|
* -->cache Bug4168625Resource as Bug4168625Resource
|
|
* -->cache Bug4168625Resource as Bug4168625Resource_en
|
|
* -->cache Bug4168625Resource as Bug4168625Resource_en_US
|
|
* -->return Bug4168625Resource
|
|
* getBundle("Bug4168625Resource", Locale.FRANCE);
|
|
* -->try to load Bug4168625Resource_fr_FR
|
|
* -->try to load Bug4168625Resource_fr
|
|
* -->find cached Bug4168625Resource_en_US
|
|
* -->return Bug4168625Resource_en_US (which is realy Bug4168625Resource)
|
|
* </pre>
|
|
* <P>
|
|
* The second call causes two loads for Bug4168625Resource_fr_FR and
|
|
* Bug4168625Resource_en which have already been tried and failed. These
|
|
* two loads should have been cached as Bug4168625Resource by the first
|
|
* call.
|
|
*
|
|
* The following, more efficient behavior is desired:
|
|
* <P>
|
|
* <pre>
|
|
* getBundle("Bug4168625Resource", Locale.FRANCE);
|
|
* -->try to load Bug4168625Resource_fr_FR
|
|
* -->try to load Bug4168625Resource_fr
|
|
* -->try to load Bug4168625Resource_en_US
|
|
* -->try to load Bug4168625Resource_en
|
|
* -->load Bug4168625Resource
|
|
* -->cache Bug4168625Resource as Bug4168625Resource
|
|
* -->cache Bug4168625Resource as Bug4168625Resource_en
|
|
* -->cache Bug4168625Resource as Bug4168625Resource_en_US
|
|
* -->cache Bug4168625Resource as Bug4168625Resource_fr
|
|
* -->cache Bug4168625Resource as Bug4168625Resource_fr_FR
|
|
* -->return Bug4168625Resource
|
|
* getBundle("Bug4168625Resource", Locale.FRANCE);
|
|
* -->find cached Bug4168625Resource_fr_FR
|
|
* -->return Bug4168625Resource_en_US (which is realy Bug4168625Resource)
|
|
* </pre>
|
|
* <P>
|
|
*
|
|
*/
|
|
public void testCacheFailures() throws Exception {
|
|
checkResourceLoading("Bug4168625Resource", Locale.FRANCE);
|
|
}
|
|
|
|
/**
|
|
* Previous versions of ResourceBundle have had the following
|
|
* caching behavior. Assume the current locale is locale is en_US.
|
|
* The classes Bug4168625Resource_en_US, and Bug4168625Resource_en don't
|
|
* exist. The class Bug4168625Resource does.
|
|
* <P>
|
|
* <pre>
|
|
* getBundle("Bug4168625Resource", Locale.US);
|
|
* -->try to load Bug4168625Resource_en_US
|
|
* -->try to load Bug4168625Resource_en
|
|
* -->try to load Bug4168625Resource_en_US
|
|
* -->try to load Bug4168625Resource_en
|
|
* -->load Bug4168625Resource
|
|
* -->cache Bug4168625Resource as Bug4168625Resource
|
|
* -->cache Bug4168625Resource as Bug4168625Resource_en
|
|
* -->cache Bug4168625Resource as Bug4168625Resource_en_US
|
|
* -->return Bug4168625Resource
|
|
* </pre>
|
|
* <P>
|
|
* The redundant loads of Bug4168625Resource_en_US and Bug4168625Resource_en
|
|
* should not occur. The desired behavior is as follows:
|
|
* <P>
|
|
* <pre>
|
|
* getBundle("Bug4168625Resource", Locale.US);
|
|
* -->try to load Bug4168625Resource_en_US
|
|
* -->try to load Bug4168625Resource_en
|
|
* -->load Bug4168625Resource
|
|
* -->cache Bug4168625Resource as Bug4168625Resource
|
|
* -->cache Bug4168625Resource as Bug4168625Resource_en
|
|
* -->cache Bug4168625Resource as Bug4168625Resource_en_US
|
|
* -->return Bug4168625Resource
|
|
* </pre>
|
|
* <P>
|
|
*/
|
|
public void testRedundantLoads() throws Exception {
|
|
checkResourceLoading("Bug4168625Resource", Locale.getDefault());
|
|
}
|
|
|
|
/**
|
|
* Ensure that resources are only loaded once and are cached correctly
|
|
*/
|
|
private void checkResourceLoading(String resName, Locale l) throws Exception {
|
|
final Loader loader = new Loader( new String[] { "Bug4168625Class" }, new String[] { "Bug4168625Resource3_en_US", "Bug4168625Resource3_en_CA" });
|
|
final Class c = loader.loadClass("Bug4168625Class");
|
|
Bug4168625Getter test = (Bug4168625Getter)c.newInstance();
|
|
final String resClassName;
|
|
if (l.toString().length() > 0) {
|
|
resClassName = resName+"_"+l;
|
|
} else {
|
|
resClassName = resName;
|
|
}
|
|
|
|
Object bundle = test.getResourceBundle(resName, l);
|
|
loader.logClasses("Initial lookup of "+resClassName+" generated the following loads:");
|
|
|
|
final Vector lastLoad = new Vector(loader.loadedClasses.size());
|
|
boolean dups = false;
|
|
for (int i = loader.loadedClasses.size() - 1; i >= 0 ; i--) {
|
|
final Object item = loader.loadedClasses.elementAt(i);
|
|
loader.loadedClasses.removeElementAt(i);
|
|
if (loader.loadedClasses.contains(item)) {
|
|
logln("Resource loaded more than once: "+item);
|
|
dups = true;
|
|
} else {
|
|
lastLoad.addElement(item);
|
|
}
|
|
}
|
|
if (dups) {
|
|
errln("ResourceBundle loaded some classes multiple times");
|
|
}
|
|
|
|
loader.loadedClasses.removeAllElements();
|
|
bundle = test.getResourceBundle(resName, l);
|
|
loader.logClasses("Second lookup of "+resClassName+" generated the following loads:");
|
|
|
|
dups = false;
|
|
for (int i = 0; i < loader.loadedClasses.size(); i++) {
|
|
Object item = loader.loadedClasses.elementAt(i);
|
|
if (lastLoad.contains(item)) {
|
|
logln("ResourceBundle did not cache "+item+" correctly");
|
|
dups = true;
|
|
}
|
|
}
|
|
if (dups) {
|
|
errln("Resource bundle not caching some classes properly");
|
|
}
|
|
}
|
|
|
|
private class ConcurrentLoadingThread extends Thread {
|
|
private Loader loader;
|
|
public Object bundle;
|
|
private Bug4168625Getter test;
|
|
private Locale locale;
|
|
private String resourceName = "Bug4168625Resource3";
|
|
public ConcurrentLoadingThread(Loader loader, Bug4168625Getter test, Locale l, String resourceName) {
|
|
this.loader = loader;
|
|
this.test = test;
|
|
this.locale = l;
|
|
this.resourceName = resourceName;
|
|
}
|
|
public ConcurrentLoadingThread(Loader loader, Bug4168625Getter test, Locale l) {
|
|
this.loader = loader;
|
|
this.test = test;
|
|
this.locale = l;
|
|
}
|
|
public void run() {
|
|
try {
|
|
logln(">>"+threadName()+">run");
|
|
bundle = test.getResourceBundle(resourceName, locale);
|
|
} catch (Exception e) {
|
|
errln("TEST CAUGHT UNEXPECTED EXCEPTION: "+e);
|
|
} finally {
|
|
logln("<<"+threadName()+"<run");
|
|
}
|
|
}
|
|
public synchronized void waitUntilPinged() {
|
|
logln(">>"+threadName()+">waitUntilPinged");
|
|
loader.notifyEveryone();
|
|
try {
|
|
wait(30000); //wait 30 seconds max.
|
|
} catch (InterruptedException e) {
|
|
logln("Test deadlocked.");
|
|
}
|
|
logln("<<"+threadName()+"<waitUntilPinged");
|
|
}
|
|
public synchronized void ping() {
|
|
logln(">>"+threadName()+">ping "+threadName(this));
|
|
notifyAll();
|
|
logln("<<"+threadName()+"<ping "+threadName(this));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* This test ensures that multiple resources can be loading at the same
|
|
* time as long as they don't depend on each other in some way.
|
|
*/
|
|
public void testConcurrentLoading() throws Exception {
|
|
final Loader loader = new Loader( new String[] { "Bug4168625Class" }, new String[] { "Bug4168625Resource3_en_US", "Bug4168625Resource3_en_CA" });
|
|
final Class c = loader.loadClass("Bug4168625Class");
|
|
final Bug4168625Getter test = (Bug4168625Getter)c.newInstance();
|
|
|
|
ConcurrentLoadingThread thread1 = new ConcurrentLoadingThread(loader, test, Locale.of("en", "CA"));
|
|
ConcurrentLoadingThread thread2 = new ConcurrentLoadingThread(loader, test, Locale.of("en", "IE"));
|
|
|
|
thread1.start(); //start thread 1
|
|
loader.waitForNotify(1); //wait for thread1 to do getBundle & block in loader
|
|
thread2.start(); //start second thread
|
|
thread2.join(); //wait until thread2 terminates.
|
|
|
|
//Thread1 should be blocked inside getBundle at the class loader
|
|
//Thread2 should have completed its getBundle call and terminated
|
|
if (!thread1.isAlive() || thread2.isAlive()) {
|
|
errln("ResourceBundle.getBundle not allowing legal concurrent loads");
|
|
}
|
|
|
|
thread1.ping(); //continue thread1
|
|
thread1.join();
|
|
}
|
|
|
|
/**
|
|
* This test ensures that a resource loads correctly (with all its parents)
|
|
* when memory is very low (ex. the cache gets purged during a load).
|
|
*/
|
|
public void testLowMemoryLoad() throws Exception {
|
|
final String[] classToLoad = { "Bug4168625Class" };
|
|
final String[] classToWait = { "Bug4168625Resource3_en_US","Bug4168625Resource3_en","Bug4168625Resource3" };
|
|
final Loader loader = new Loader(classToLoad, classToWait);
|
|
final Class c = loader.loadClass("Bug4168625Class");
|
|
final Bug4168625Getter test = (Bug4168625Getter)c.newInstance();
|
|
causeResourceBundleCacheFlush();
|
|
|
|
ConcurrentLoadingThread thread1 = new ConcurrentLoadingThread(loader, test, Locale.US);
|
|
thread1.start(); //start thread 1
|
|
loader.waitForNotify(1); //wait for thread1 to do getBundle(en_US) & block in loader
|
|
causeResourceBundleCacheFlush(); //cause a cache flush
|
|
thread1.ping(); //kick thread 1
|
|
loader.waitForNotify(2); //wait for thread1 to do getBundle(en) & block in loader
|
|
causeResourceBundleCacheFlush(); //cause a cache flush
|
|
thread1.ping(); //kick thread 1
|
|
loader.waitForNotify(3); //wait for thread1 to do getBundle(en) & block in loader
|
|
causeResourceBundleCacheFlush(); //cause a cache flush
|
|
thread1.ping(); //kick thread 1
|
|
thread1.join(); //wait until thread1 terminates
|
|
|
|
ResourceBundle bundle = (ResourceBundle)thread1.bundle;
|
|
String s1 = bundle.getString("Bug4168625Resource3_en_US");
|
|
String s2 = bundle.getString("Bug4168625Resource3_en");
|
|
String s3 = bundle.getString("Bug4168625Resource3");
|
|
if ((s1 == null) || (s2 == null) || (s3 == null)) {
|
|
errln("Bundle not constructed correctly. The parent chain is incorrect.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A simple class loader that loads classes from the current
|
|
* working directory. The loader will block the current thread
|
|
* of execution before it returns when it tries to load
|
|
* the class "Bug4168625Resource3_en_US".
|
|
*/
|
|
private static final String CLASS_PREFIX = "";
|
|
private static final String CLASS_SUFFIX = ".class";
|
|
|
|
private static final class SimpleLoader extends ClassLoader {
|
|
private boolean network = false;
|
|
|
|
public SimpleLoader() {
|
|
super(SimpleLoader.class.getClassLoader());
|
|
this.network = false;
|
|
}
|
|
public SimpleLoader(boolean simulateNetworkLoad) {
|
|
super(SimpleLoader.class.getClassLoader());
|
|
this.network = simulateNetworkLoad;
|
|
}
|
|
public Class loadClass(final String className, final boolean resolveIt)
|
|
throws ClassNotFoundException {
|
|
Class result;
|
|
synchronized (this) {
|
|
result = findLoadedClass(className);
|
|
if (result == null) {
|
|
if (network) {
|
|
try {
|
|
Thread.sleep(100);
|
|
} catch (java.lang.InterruptedException e) {
|
|
}
|
|
}
|
|
result = getParent().loadClass(className);
|
|
if ((result != null) && resolveIt) {
|
|
resolveClass(result);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private final class Loader extends ClassLoader {
|
|
public final Vector loadedClasses = new Vector();
|
|
private String[] classesToLoad;
|
|
private String[] classesToWaitFor;
|
|
|
|
public Loader() {
|
|
super(Loader.class.getClassLoader());
|
|
classesToLoad = new String[0];
|
|
classesToWaitFor = new String[0];
|
|
}
|
|
|
|
public Loader(final String[] classesToLoadIn, final String[] classesToWaitForIn) {
|
|
super(Loader.class.getClassLoader());
|
|
classesToLoad = classesToLoadIn;
|
|
classesToWaitFor = classesToWaitForIn;
|
|
}
|
|
|
|
/**
|
|
* Load a class. Files we can load take preference over ones the system
|
|
* can load.
|
|
*/
|
|
private byte[] getClassData(final String className) {
|
|
boolean shouldLoad = false;
|
|
for (int i = classesToLoad.length-1; i >= 0; --i) {
|
|
if (className.equals(classesToLoad[i])) {
|
|
shouldLoad = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (shouldLoad) {
|
|
final String name = CLASS_PREFIX+className+CLASS_SUFFIX;
|
|
try {
|
|
final InputStream fi = this.getClass().getClassLoader().getResourceAsStream(name);
|
|
final byte[] result = new byte[fi.available()];
|
|
fi.read(result);
|
|
return result;
|
|
} catch (Exception e) {
|
|
logln("Error loading test class: "+name);
|
|
logln(e.toString());
|
|
return null;
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load a class. Files we can load take preference over ones the system
|
|
* can load.
|
|
*/
|
|
public Class loadClass(final String className, final boolean resolveIt)
|
|
throws ClassNotFoundException {
|
|
Class result;
|
|
synchronized (this) {
|
|
try {
|
|
logln(">>"+threadName()+">load "+className);
|
|
loadedClasses.addElement(className);
|
|
|
|
result = findLoadedClass(className);
|
|
if (result == null) {
|
|
final byte[] classData = getClassData(className);
|
|
if (classData == null) {
|
|
//we don't have a local copy of this one
|
|
logln("Loading system class: "+className);
|
|
result = loadFromSystem(className);
|
|
} else {
|
|
result = defineClass(classData, 0, classData.length);
|
|
if (result == null) {
|
|
//there was an error defining the class
|
|
result = loadFromSystem(className);
|
|
}
|
|
}
|
|
if ((result != null) && resolveIt) {
|
|
resolveClass(result);
|
|
}
|
|
}
|
|
} catch (ClassNotFoundException e) {
|
|
// Ignore loading of Bug4168625ResourceProvider
|
|
if (className.equals("Bug4168625ResourceProvider")) {
|
|
logln("Ignoring " + className);
|
|
loadedClasses.remove(className);
|
|
return null;
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
for (int i = classesToWaitFor.length-1; i >= 0; --i) {
|
|
if (className.equals(classesToWaitFor[i])) {
|
|
rendezvous();
|
|
break;
|
|
}
|
|
}
|
|
logln("<<"+threadName()+"<load "+className);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Delegate loading to its parent class loader that loads the test classes.
|
|
* In othervm mode, the parent class loader is the system class loader;
|
|
* in samevm mode, the parent class loader is the jtreg URLClassLoader.
|
|
*/
|
|
private Class loadFromSystem(String className) throws ClassNotFoundException {
|
|
return getParent().loadClass(className);
|
|
}
|
|
|
|
public void logClasses(String title) {
|
|
logln(title);
|
|
for (int i = 0; i < loadedClasses.size(); i++) {
|
|
logln(" "+loadedClasses.elementAt(i));
|
|
}
|
|
logln("");
|
|
}
|
|
|
|
public int notifyCount = 0;
|
|
public int waitForNotify(int count) {
|
|
return waitForNotify(count, 0);
|
|
}
|
|
public synchronized int waitForNotify(int count, long time) {
|
|
logln(">>"+threadName()+">waitForNotify");
|
|
if (count > notifyCount) {
|
|
try {
|
|
wait(time);
|
|
} catch (InterruptedException e) {
|
|
}
|
|
} else {
|
|
logln(" count("+count+") > notifyCount("+notifyCount+")");
|
|
}
|
|
logln("<<"+threadName()+"<waitForNotify");
|
|
return notifyCount;
|
|
}
|
|
private synchronized void notifyEveryone() {
|
|
logln(">>"+threadName()+">notifyEveryone");
|
|
notifyCount++;
|
|
notifyAll();
|
|
logln("<<"+threadName()+"<notifyEveryone");
|
|
}
|
|
private void rendezvous() {
|
|
final Thread current = Thread.currentThread();
|
|
if (current instanceof ConcurrentLoadingThread) {
|
|
((ConcurrentLoadingThread)current).waitUntilPinged();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static String threadName() {
|
|
return threadName(Thread.currentThread());
|
|
}
|
|
|
|
private static String threadName(Thread t) {
|
|
String temp = t.toString();
|
|
int ndx = temp.indexOf("Thread[");
|
|
temp = temp.substring(ndx + "Thread[".length());
|
|
ndx = temp.indexOf(',');
|
|
temp = temp.substring(0, ndx);
|
|
return temp;
|
|
}
|
|
|
|
/** Fill memory to force all SoftReferences to be GCed */
|
|
private void causeResourceBundleCacheFlush() {
|
|
logln("Filling memory...");
|
|
int allocationSize = 1024;
|
|
Vector memoryHog = new Vector();
|
|
try {
|
|
while (true) {
|
|
memoryHog.addElement(new byte[allocationSize]);
|
|
allocationSize *= 2;
|
|
}
|
|
} catch (Throwable e) {
|
|
logln("Caught "+e+" filling memory");
|
|
} finally{
|
|
memoryHog = null;
|
|
System.gc();
|
|
}
|
|
logln("last allocation size: " + allocationSize);
|
|
}
|
|
|
|
/**
|
|
* NOTE: this problem is not externally testable and can only be
|
|
* verified through code inspection unless special code to force
|
|
* a task switch is inserted into ResourceBundle.
|
|
* The class Bug4168625Resource_sp exists. It's parent bundle
|
|
* (Bug4168625Resource) contains a resource string with the tag
|
|
* "language" but Bug4168625Resource_sp does not.
|
|
* Assume two threads are executing, ThreadA and ThreadB and they both
|
|
* load a resource Bug4168625Resource with from sp locale.
|
|
* ResourceBundle.getBundle adds a bundle to the bundle cache (in
|
|
* findBundle) before it sets the bundle's parent (in getBundle after
|
|
* returning from findBundle).
|
|
* <P>
|
|
* <pre>
|
|
* ThreadA.getBundle("Bug4168625Resource", Locale.of("sp"));
|
|
* A-->load Bug4168625Resource_sp
|
|
* A-->find cached Bug4168625Resource
|
|
* A-->cache Bug4168625Resource_sp as Bug4168625Resource_sp
|
|
* ThreadB.getBundle("Bug4168625Resource", Locale.of("sp"));
|
|
* B-->find cached Bug4168625Resource_sp
|
|
* B-->return Bug4168625Resource_sp
|
|
* ThreadB.bundle.getString("language");
|
|
* B-->try to find "language" in Bug4168625Resource_sp
|
|
* B-->Bug4168625Resource_sp does not have a parent, so return null;
|
|
* ThreadB.System.out.println("Some unknown country");
|
|
* A-->set parent of Bug4168625Resource_sp to Bug4168625Resource
|
|
* A-->return Bug4168625Resource_sp (the same bundle ThreadB got)
|
|
* ThreadA.bundle.getString("language");
|
|
* A-->try to find "language" in Bug4168625Resource_sp
|
|
* A-->try to find "language" in Bug4168625Resource (parent of Bug4168625Resource_sp)
|
|
* A-->return the string
|
|
* ThreadA.System.out.println("Langauge = "+country);
|
|
* ThreadB.bundle.getString("language");
|
|
* B-->try to find "language" in Bug4168625Resource_sp
|
|
* B-->try to find "language" in Bug4168625Resource (parent of Bug4168625Resource_sp)
|
|
* B-->return the string
|
|
* ThreadB.System.out.println("Langauge = "+country);
|
|
* </pre>
|
|
* <P>
|
|
* Note that the first call to getString() by ThreadB returns null, but the second
|
|
* returns a value. Thus to ThreadB, the bundle appears to change. ThreadA gets
|
|
* the expected results right away.
|
|
*/
|
|
}
|