File: Bug4168625Test.java

package info (click to toggle)
openjdk-11 11.0.4%2B11-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 757,028 kB
  • sloc: java: 5,016,041; xml: 1,191,974; cpp: 934,731; ansic: 555,697; sh: 24,299; objc: 12,703; python: 3,602; asm: 3,415; makefile: 2,772; awk: 351; sed: 172; perl: 114; jsp: 24; csh: 3
file content (593 lines) | stat: -rw-r--r-- 25,170 bytes parent folder | download | duplicates (9)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
/*
 * Copyright (c) 2007, 2015, 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(new Locale("en", "US"));
        try {
            final Locale loc = new Locale("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", new Locale("fr", "FR"));
     *      -->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", new Locale("fr", "FR"));
     *      -->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", new Locale("fr", "FR"));
     *      -->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", new Locale("fr", "FR"));
     *      -->find cached Bug4168625Resource_fr_FR
     *      -->return Bug4168625Resource_en_US (which is realy Bug4168625Resource)
     *  </pre>
     *  <P>
     *
     */
    public void testCacheFailures() throws Exception {
        checkResourceLoading("Bug4168625Resource", new Locale("fr", "FR"));
    }

    /**
     *  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", new Locale("en", "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", new Locale("en", "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, new Locale("en", "CA"));
        ConcurrentLoadingThread thread2 = new ConcurrentLoadingThread(loader, test, new Locale("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, new Locale("en", "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", new Locale("sp"));
     *      A-->load Bug4168625Resource_sp
     *      A-->find cached Bug4168625Resource
     *      A-->cache Bug4168625Resource_sp as Bug4168625Resource_sp
     *  ThreadB.getBundle("Bug4168625Resource", new Locale("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.
     */
}