blob: f9f58fba46bc2acc5191eba8b88b3ce4c4d902e7 [file] [log] [blame]
duke6e45e102007-12-01 00:00:00 +00001/*
ohairf5857212010-12-28 15:53:50 -08002 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
duke6e45e102007-12-01 00:00:00 +00003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
ohair2283b9d2010-05-25 15:58:33 -070019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
duke6e45e102007-12-01 00:00:00 +000022 */
23/*
24 @test
25 @summary test Resource Bundle for bug 4168625
26 @build Bug4168625Class Bug4168625Getter Bug4168625Resource Bug4168625Resource3 Bug4168625Resource3_en Bug4168625Resource3_en_CA Bug4168625Resource3_en_IE Bug4168625Resource3_en_US Bug4168625Resource2_en_US Bug4168625Resource2
27 @run main/timeout=600 Bug4168625Test
naoto2071ff62010-10-22 11:32:26 -070028 @bug 4168625 6993339
duke6e45e102007-12-01 00:00:00 +000029*/
30/*
31 *
32 *
33 * (C) Copyright IBM Corp. 1999 - All Rights Reserved
34 *
duke6e45e102007-12-01 00:00:00 +000035 * The original version of this source code and documentation is
36 * copyrighted and owned by IBM. These materials are provided
37 * under terms of a License Agreement between IBM and Sun.
38 * This technology is protected by multiple US and International
39 * patents. This notice and attribution to IBM may not be removed.
40 *
41 */
42
43import java.util.*;
44import java.io.*;
45
46/**
naoto2071ff62010-10-22 11:32:26 -070047 * This test tries to correct two efficiency problems with the caching
48 * mechanism of ResourceBundle. It also allows concurrent loads
duke6e45e102007-12-01 00:00:00 +000049 * of resource bundles to be performed if the bundles are unrelated (ex. a
50 * load of a local system resource by one thread while another thread is
51 * doing a slow load over a network).
52 */
53public class Bug4168625Test extends RBTestFmwk {
54 public static void main(String[] args) throws Exception {
55 new Bug4168625Test().run(args);
56 }
57
58 /**
59 * Verify that getBundle will do something reasonable when part of the
60 * resource hierarchy is missing.
61 */
62 public void testMissingParent() throws Exception {
63 final Locale oldDefault = Locale.getDefault();
64 Locale.setDefault(new Locale("en", "US"));
65 try {
66 final Locale loc = new Locale("jf", "jf");
67 ResourceBundle bundle = ResourceBundle.getBundle("Bug4168625Resource2", loc);
68 final String s1 = bundle.getString("name");
69 if (!s1.equals("Bug4168625Resource2_en_US")) {
70 errln("getBundle did not find leaf bundle: "+bundle.getClass().getName());
71 }
72 final String s2 = bundle.getString("baseName");
73 if (!s2.equals("Bug4168625Resource2")) {
74 errln("getBundle did not set up proper inheritance chain");
75 }
76 } finally {
77 Locale.setDefault(oldDefault);
78 }
79 }
80
81 /**
82 * Previous versions of ResourceBundle have had the following
83 * caching behavior. Assume the classes
84 * Bug4168625Resource_fr_FR, Bug4168625Resource_fr,
85 * Bug4168625Resource_en_US, and Bug4168625Resource_en don't
86 * exist. The class Bug4168625Resource does. Assume the default
87 * locale is en_US.
88 * <P>
89 * <pre>
90 * getBundle("Bug4168625Resource", new Locale("fr", "FR"));
91 * -->try to load Bug4168625Resource_fr_FR
92 * -->try to load Bug4168625Resource_fr
93 * -->try to load Bug4168625Resource_en_US
94 * -->try to load Bug4168625Resource_en
95 * -->load Bug4168625Resource
96 * -->cache Bug4168625Resource as Bug4168625Resource
97 * -->cache Bug4168625Resource as Bug4168625Resource_en
98 * -->cache Bug4168625Resource as Bug4168625Resource_en_US
99 * -->return Bug4168625Resource
100 * getBundle("Bug4168625Resource", new Locale("fr", "FR"));
101 * -->try to load Bug4168625Resource_fr_FR
102 * -->try to load Bug4168625Resource_fr
103 * -->find cached Bug4168625Resource_en_US
104 * -->return Bug4168625Resource_en_US (which is realy Bug4168625Resource)
105 * </pre>
106 * <P>
107 * The second call causes two loads for Bug4168625Resource_fr_FR and
108 * Bug4168625Resource_en which have already been tried and failed. These
109 * two loads should have been cached as Bug4168625Resource by the first
110 * call.
111 *
112 * The following, more efficient behavior is desired:
113 * <P>
114 * <pre>
115 * getBundle("Bug4168625Resource", new Locale("fr", "FR"));
116 * -->try to load Bug4168625Resource_fr_FR
117 * -->try to load Bug4168625Resource_fr
118 * -->try to load Bug4168625Resource_en_US
119 * -->try to load Bug4168625Resource_en
120 * -->load Bug4168625Resource
121 * -->cache Bug4168625Resource as Bug4168625Resource
122 * -->cache Bug4168625Resource as Bug4168625Resource_en
123 * -->cache Bug4168625Resource as Bug4168625Resource_en_US
124 * -->cache Bug4168625Resource as Bug4168625Resource_fr
125 * -->cache Bug4168625Resource as Bug4168625Resource_fr_FR
126 * -->return Bug4168625Resource
127 * getBundle("Bug4168625Resource", new Locale("fr", "FR"));
128 * -->find cached Bug4168625Resource_fr_FR
129 * -->return Bug4168625Resource_en_US (which is realy Bug4168625Resource)
130 * </pre>
131 * <P>
132 *
133 */
134 public void testCacheFailures() throws Exception {
135 checkResourceLoading("Bug4168625Resource", new Locale("fr", "FR"));
136 }
137
138 /**
139 * Previous versions of ResourceBundle have had the following
140 * caching behavior. Assume the current locale is locale is en_US.
141 * The classes Bug4168625Resource_en_US, and Bug4168625Resource_en don't
142 * exist. The class Bug4168625Resource does.
143 * <P>
144 * <pre>
145 * getBundle("Bug4168625Resource", new Locale("en", "US"));
146 * -->try to load Bug4168625Resource_en_US
147 * -->try to load Bug4168625Resource_en
148 * -->try to load Bug4168625Resource_en_US
149 * -->try to load Bug4168625Resource_en
150 * -->load Bug4168625Resource
151 * -->cache Bug4168625Resource as Bug4168625Resource
152 * -->cache Bug4168625Resource as Bug4168625Resource_en
153 * -->cache Bug4168625Resource as Bug4168625Resource_en_US
154 * -->return Bug4168625Resource
155 * </pre>
156 * <P>
157 * The redundant loads of Bug4168625Resource_en_US and Bug4168625Resource_en
158 * should not occur. The desired behavior is as follows:
159 * <P>
160 * <pre>
161 * getBundle("Bug4168625Resource", new Locale("en", "US"));
162 * -->try to load Bug4168625Resource_en_US
163 * -->try to load Bug4168625Resource_en
164 * -->load Bug4168625Resource
165 * -->cache Bug4168625Resource as Bug4168625Resource
166 * -->cache Bug4168625Resource as Bug4168625Resource_en
167 * -->cache Bug4168625Resource as Bug4168625Resource_en_US
168 * -->return Bug4168625Resource
169 * </pre>
170 * <P>
171 */
172 public void testRedundantLoads() throws Exception {
173 checkResourceLoading("Bug4168625Resource", Locale.getDefault());
174 }
175
176 /**
177 * Ensure that resources are only loaded once and are cached correctly
178 */
179 private void checkResourceLoading(String resName, Locale l) throws Exception {
180 final Loader loader = new Loader( new String[] { "Bug4168625Class" }, new String[] { "Bug4168625Resource3_en_US", "Bug4168625Resource3_en_CA" });
181 final Class c = loader.loadClass("Bug4168625Class");
182 Bug4168625Getter test = (Bug4168625Getter)c.newInstance();
183 final String resClassName;
184 if (l.toString().length() > 0) {
185 resClassName = resName+"_"+l;
186 } else {
187 resClassName = resName;
188 }
189
190 Object bundle = test.getResourceBundle(resName, l);
191 loader.logClasses("Initial lookup of "+resClassName+" generated the following loads:");
192
193 final Vector lastLoad = new Vector(loader.loadedClasses.size());
194 boolean dups = false;
195 for (int i = loader.loadedClasses.size() - 1; i >= 0 ; i--) {
196 final Object item = loader.loadedClasses.elementAt(i);
197 loader.loadedClasses.removeElementAt(i);
198 if (loader.loadedClasses.contains(item)) {
199 logln("Resource loaded more than once: "+item);
200 dups = true;
201 } else {
202 lastLoad.addElement(item);
203 }
204 }
205 if (dups) {
206 errln("ResourceBundle loaded some classes multiple times");
207 }
208
209 loader.loadedClasses.removeAllElements();
210 bundle = test.getResourceBundle(resName, l);
211 loader.logClasses("Second lookup of "+resClassName+" generated the following loads:");
212
213 dups = false;
214 for (int i = 0; i < loader.loadedClasses.size(); i++) {
215 Object item = loader.loadedClasses.elementAt(i);
216 if (lastLoad.contains(item)) {
217 logln("ResourceBundle did not cache "+item+" correctly");
218 dups = true;
219 }
220 }
221 if (dups) {
222 errln("Resource bundle not caching some classes properly");
223 }
224 }
225
duke6e45e102007-12-01 00:00:00 +0000226 private class ConcurrentLoadingThread extends Thread {
227 private Loader loader;
228 public Object bundle;
229 private Bug4168625Getter test;
230 private Locale locale;
231 private String resourceName = "Bug4168625Resource3";
232 public ConcurrentLoadingThread(Loader loader, Bug4168625Getter test, Locale l, String resourceName) {
233 this.loader = loader;
234 this.test = test;
235 this.locale = l;
236 this.resourceName = resourceName;
237 }
238 public ConcurrentLoadingThread(Loader loader, Bug4168625Getter test, Locale l) {
239 this.loader = loader;
240 this.test = test;
241 this.locale = l;
242 }
243 public void run() {
244 try {
245 logln(">>"+threadName()+">run");
246 bundle = test.getResourceBundle(resourceName, locale);
247 } catch (Exception e) {
248 errln("TEST CAUGHT UNEXPECTED EXCEPTION: "+e);
249 } finally {
250 logln("<<"+threadName()+"<run");
251 }
252 }
253 public synchronized void waitUntilPinged() {
254 logln(">>"+threadName()+">waitUntilPinged");
255 loader.notifyEveryone();
256 try {
257 wait(30000); //wait 30 seconds max.
258 } catch (InterruptedException e) {
259 logln("Test deadlocked.");
260 }
261 logln("<<"+threadName()+"<waitUntilPinged");
262 }
263 public synchronized void ping() {
264 logln(">>"+threadName()+">ping "+threadName(this));
265 notifyAll();
266 logln("<<"+threadName()+"<ping "+threadName(this));
267 }
268 };
269
270 /**
271 * This test ensures that multiple resources can be loading at the same
272 * time as long as they don't depend on each other in some way.
273 */
naoto2071ff62010-10-22 11:32:26 -0700274 public void testConcurrentLoading() throws Exception {
duke6e45e102007-12-01 00:00:00 +0000275 final Loader loader = new Loader( new String[] { "Bug4168625Class" }, new String[] { "Bug4168625Resource3_en_US", "Bug4168625Resource3_en_CA" });
276 final Class c = loader.loadClass("Bug4168625Class");
277 final Bug4168625Getter test = (Bug4168625Getter)c.newInstance();
278
279 ConcurrentLoadingThread thread1 = new ConcurrentLoadingThread(loader, test, new Locale("en", "CA"));
280 ConcurrentLoadingThread thread2 = new ConcurrentLoadingThread(loader, test, new Locale("en", "IE"));
281
282 thread1.start(); //start thread 1
283 loader.waitForNotify(1); //wait for thread1 to do getBundle & block in loader
284 thread2.start(); //start second thread
285 thread2.join(1000); //wait until thread2 blocks somewhere in getBundle
286
287 //Thread1 should be blocked inside getBundle at the class loader
288 //Thread2 should have completed its getBundle call and terminated
289 if (!thread1.isAlive() || thread2.isAlive()) {
290 errln("ResourceBundle.getBundle not allowing legal concurrent loads");
291 }
292
293 thread1.ping(); //continue thread1
294 thread1.join();
295 thread2.join();
296 }
297
298 /**
299 * This test ensures that a resource loads correctly (with all its parents)
300 * when memory is very low (ex. the cache gets purged during a load).
301 */
302 public void testLowMemoryLoad() throws Exception {
303 final String[] classToLoad = { "Bug4168625Class" };
304 final String[] classToWait = { "Bug4168625Resource3_en_US","Bug4168625Resource3_en","Bug4168625Resource3" };
305 final Loader loader = new Loader(classToLoad, classToWait);
306 final Class c = loader.loadClass("Bug4168625Class");
307 final Bug4168625Getter test = (Bug4168625Getter)c.newInstance();
308 causeResourceBundleCacheFlush();
309
310 ConcurrentLoadingThread thread1 = new ConcurrentLoadingThread(loader, test, new Locale("en", "US"));
311 thread1.start(); //start thread 1
312 loader.waitForNotify(1); //wait for thread1 to do getBundle(en_US) & block in loader
313 causeResourceBundleCacheFlush(); //cause a cache flush
314 thread1.ping(); //kick thread 1
315 loader.waitForNotify(2); //wait for thread1 to do getBundle(en) & block in loader
316 causeResourceBundleCacheFlush(); //cause a cache flush
317 thread1.ping(); //kick thread 1
318 loader.waitForNotify(3); //wait for thread1 to do getBundle(en) & block in loader
319 causeResourceBundleCacheFlush(); //cause a cache flush
320 thread1.ping(); //kick thread 1
321 thread1.ping(); //kick thread 1
322 thread1.join(1000); //wait until thread2 blocks somewhere in getBundle
323
324 ResourceBundle bundle = (ResourceBundle)thread1.bundle;
325 String s1 = bundle.getString("Bug4168625Resource3_en_US");
326 String s2 = bundle.getString("Bug4168625Resource3_en");
327 String s3 = bundle.getString("Bug4168625Resource3");
328 if ((s1 == null) || (s2 == null) || (s3 == null)) {
329 errln("Bundle not constructed correctly. The parent chain is incorrect.");
330 }
331 }
332
333 /**
334 * A simple class loader that loads classes from the current
335 * working directory. The loader will block the current thread
336 * of execution before it returns when it tries to load
337 * the class "Bug4168625Resource3_en_US".
338 */
339 private static final String CLASS_PREFIX = "";
340 private static final String CLASS_SUFFIX = ".class";
341
342 private static final class SimpleLoader extends ClassLoader {
343 private boolean network = false;
344
345 public SimpleLoader() {
mchung019ce7d2010-06-15 20:34:49 -0700346 super(SimpleLoader.class.getClassLoader());
duke6e45e102007-12-01 00:00:00 +0000347 this.network = false;
348 }
349 public SimpleLoader(boolean simulateNetworkLoad) {
mchung019ce7d2010-06-15 20:34:49 -0700350 super(SimpleLoader.class.getClassLoader());
duke6e45e102007-12-01 00:00:00 +0000351 this.network = simulateNetworkLoad;
352 }
353 public Class loadClass(final String className, final boolean resolveIt)
354 throws ClassNotFoundException {
355 Class result;
356 synchronized (this) {
357 result = findLoadedClass(className);
358 if (result == null) {
359 if (network) {
360 try {
361 Thread.sleep(100);
362 } catch (java.lang.InterruptedException e) {
363 }
364 }
mchung019ce7d2010-06-15 20:34:49 -0700365 result = getParent().loadClass(className);
duke6e45e102007-12-01 00:00:00 +0000366 if ((result != null) && resolveIt) {
367 resolveClass(result);
368 }
369 }
370 }
371 return result;
372 }
373 }
374
375 private final class Loader extends ClassLoader {
376 public final Vector loadedClasses = new Vector();
377 private String[] classesToLoad;
378 private String[] classesToWaitFor;
379
380 public Loader() {
mchung019ce7d2010-06-15 20:34:49 -0700381 super(Loader.class.getClassLoader());
duke6e45e102007-12-01 00:00:00 +0000382 classesToLoad = new String[0];
383 classesToWaitFor = new String[0];
384 }
385
386 public Loader(final String[] classesToLoadIn, final String[] classesToWaitForIn) {
mchung019ce7d2010-06-15 20:34:49 -0700387 super(Loader.class.getClassLoader());
duke6e45e102007-12-01 00:00:00 +0000388 classesToLoad = classesToLoadIn;
389 classesToWaitFor = classesToWaitForIn;
390 }
391
392 /**
393 * Load a class. Files we can load take preference over ones the system
394 * can load.
395 */
396 private byte[] getClassData(final String className) {
397 boolean shouldLoad = false;
398 for (int i = classesToLoad.length-1; i >= 0; --i) {
399 if (className.equals(classesToLoad[i])) {
400 shouldLoad = true;
401 break;
402 }
403 }
404
405 if (shouldLoad) {
406 final String name = CLASS_PREFIX+className+CLASS_SUFFIX;
407 try {
408 final InputStream fi = this.getClass().getClassLoader().getResourceAsStream(name);
409 final byte[] result = new byte[fi.available()];
410 fi.read(result);
411 return result;
412 } catch (Exception e) {
413 logln("Error loading test class: "+name);
414 logln(e.toString());
415 return null;
416 }
417 } else {
418 return null;
419 }
420 }
421
422 /**
423 * Load a class. Files we can load take preference over ones the system
424 * can load.
425 */
426 public Class loadClass(final String className, final boolean resolveIt)
427 throws ClassNotFoundException {
428 Class result;
429 synchronized (this) {
430 logln(">>"+threadName()+">load "+className);
431 loadedClasses.addElement(className);
432
433 result = findLoadedClass(className);
434 if (result == null) {
435 final byte[] classData = getClassData(className);
436 if (classData == null) {
437 //we don't have a local copy of this one
438 logln("Loading system class: "+className);
439 result = loadFromSystem(className);
440 } else {
441 result = defineClass(classData, 0, classData.length);
442 if (result == null) {
443 //there was an error defining the class
444 result = loadFromSystem(className);
445 }
446 }
447 if ((result != null) && resolveIt) {
448 resolveClass(result);
449 }
450 }
451 }
452 for (int i = classesToWaitFor.length-1; i >= 0; --i) {
453 if (className.equals(classesToWaitFor[i])) {
454 rendezvous();
455 break;
456 }
457 }
458 logln("<<"+threadName()+"<load "+className);
459 return result;
460 }
461
462 /**
mchung019ce7d2010-06-15 20:34:49 -0700463 * Delegate loading to its parent class loader that loads the test classes.
464 * In othervm mode, the parent class loader is the system class loader;
465 * in samevm mode, the parent class loader is the jtreg URLClassLoader.
duke6e45e102007-12-01 00:00:00 +0000466 */
467 private Class loadFromSystem(String className) throws ClassNotFoundException {
mchung019ce7d2010-06-15 20:34:49 -0700468 return getParent().loadClass(className);
duke6e45e102007-12-01 00:00:00 +0000469 }
470
471 public void logClasses(String title) {
472 logln(title);
473 for (int i = 0; i < loadedClasses.size(); i++) {
474 logln(" "+loadedClasses.elementAt(i));
475 }
476 logln("");
477 }
478
479 public int notifyCount = 0;
480 public int waitForNotify(int count) {
481 return waitForNotify(count, 0);
482 }
483 public synchronized int waitForNotify(int count, long time) {
484 logln(">>"+threadName()+">waitForNotify");
485 if (count > notifyCount) {
486 try {
487 wait(time);
488 } catch (InterruptedException e) {
489 }
490 } else {
491 logln(" count("+count+") > notifyCount("+notifyCount+")");
492 }
493 logln("<<"+threadName()+"<waitForNotify");
494 return notifyCount;
495 }
496 private synchronized void notifyEveryone() {
497 logln(">>"+threadName()+">notifyEveryone");
498 notifyCount++;
499 notifyAll();
500 logln("<<"+threadName()+"<notifyEveryone");
501 }
502 private void rendezvous() {
503 final Thread current = Thread.currentThread();
504 if (current instanceof ConcurrentLoadingThread) {
505 ((ConcurrentLoadingThread)current).waitUntilPinged();
506 }
507 }
508 }
509
510 private static String threadName() {
511 return threadName(Thread.currentThread());
512 }
513
514 private static String threadName(Thread t) {
515 String temp = t.toString();
516 int ndx = temp.indexOf("Thread[");
517 temp = temp.substring(ndx + "Thread[".length());
518 ndx = temp.indexOf(',');
519 temp = temp.substring(0, ndx);
520 return temp;
521 }
522
523 /** Fill memory to force all SoftReferences to be GCed */
524 private void causeResourceBundleCacheFlush() {
525 logln("Filling memory...");
526 int allocationSize = 1024;
527 Vector memoryHog = new Vector();
528 try {
529 while (true) {
530 memoryHog.addElement(new byte[allocationSize]);
531 allocationSize *= 2;
532 }
533 } catch (Throwable e) {
534 logln("Caught "+e+" filling memory");
535 } finally{
536 memoryHog = null;
537 System.gc();
538 }
539 logln("last allocation size: " + allocationSize);
540 }
541
542 /**
543 * NOTE: this problem is not externally testable and can only be
544 * verified through code inspection unless special code to force
545 * a task switch is inserted into ResourceBundle.
546 * The class Bug4168625Resource_sp exists. It's parent bundle
547 * (Bug4168625Resource) contains a resource string with the tag
548 * "language" but Bug4168625Resource_sp does not.
549 * Assume two threads are executing, ThreadA and ThreadB and they both
550 * load a resource Bug4168625Resource with from sp locale.
551 * ResourceBundle.getBundle adds a bundle to the bundle cache (in
552 * findBundle) before it sets the bundle's parent (in getBundle after
553 * returning from findBundle).
554 * <P>
555 * <pre>
556 * ThreadA.getBundle("Bug4168625Resource", new Locale("sp"));
557 * A-->load Bug4168625Resource_sp
558 * A-->find cached Bug4168625Resource
559 * A-->cache Bug4168625Resource_sp as Bug4168625Resource_sp
560 * ThreadB.getBundle("Bug4168625Resource", new Locale("sp"));
561 * B-->find cached Bug4168625Resource_sp
562 * B-->return Bug4168625Resource_sp
563 * ThreadB.bundle.getString("language");
564 * B-->try to find "language" in Bug4168625Resource_sp
565 * B-->Bug4168625Resource_sp does not have a parent, so return null;
566 * ThreadB.System.out.println("Some unknown country");
567 * A-->set parent of Bug4168625Resource_sp to Bug4168625Resource
568 * A-->return Bug4168625Resource_sp (the same bundle ThreadB got)
569 * ThreadA.bundle.getString("language");
570 * A-->try to find "language" in Bug4168625Resource_sp
571 * A-->try to find "language" in Bug4168625Resource (parent of Bug4168625Resource_sp)
572 * A-->return the string
573 * ThreadA.System.out.println("Langauge = "+country);
574 * ThreadB.bundle.getString("language");
575 * B-->try to find "language" in Bug4168625Resource_sp
576 * B-->try to find "language" in Bug4168625Resource (parent of Bug4168625Resource_sp)
577 * B-->return the string
578 * ThreadB.System.out.println("Langauge = "+country);
579 * </pre>
580 * <P>
581 * Note that the first call to getString() by ThreadB returns null, but the second
582 * returns a value. Thus to ThreadB, the bundle appears to change. ThreadA gets
583 * the expected results right away.
584 */
585}