Bug4168625Test.java revision 2365:4c234c13f66a
1/* 2 * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. 3 * 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 * 19 * 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. 22 */ 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 28 @bug 4168625 29*/ 30/* 31 * 32 * 33 * (C) Copyright IBM Corp. 1999 - All Rights Reserved 34 * 35 * This software is the confidential and proprietary information 36 * of Sun Microsystems, Inc. ("Confidential Information"). You 37 * shall not disclose such Confidential Information and shall use 38 * it only in accordance with the terms of the license agreement 39 * you entered into with Sun. 40 * 41 * The original version of this source code and documentation is 42 * copyrighted and owned by IBM. These materials are provided 43 * under terms of a License Agreement between IBM and Sun. 44 * This technology is protected by multiple US and International 45 * patents. This notice and attribution to IBM may not be removed. 46 * 47 */ 48 49import java.util.*; 50import java.io.*; 51 52/** 53 * This test tries to correct three efficiency problems with the caching 54 * mechanism of ResourceBundle. All tests assume that none of the bundles 55 * have been previously loaded and cached. It also allows concurrent loads 56 * of resource bundles to be performed if the bundles are unrelated (ex. a 57 * load of a local system resource by one thread while another thread is 58 * doing a slow load over a network). 59 */ 60public class Bug4168625Test extends RBTestFmwk { 61 public static void main(String[] args) throws Exception { 62 new Bug4168625Test().run(args); 63 } 64 65 /** 66 * Verify that getBundle will do something reasonable when part of the 67 * resource hierarchy is missing. 68 */ 69 public void testMissingParent() throws Exception { 70 final Locale oldDefault = Locale.getDefault(); 71 Locale.setDefault(new Locale("en", "US")); 72 try { 73 final Locale loc = new Locale("jf", "jf"); 74 ResourceBundle bundle = ResourceBundle.getBundle("Bug4168625Resource2", loc); 75 final String s1 = bundle.getString("name"); 76 if (!s1.equals("Bug4168625Resource2_en_US")) { 77 errln("getBundle did not find leaf bundle: "+bundle.getClass().getName()); 78 } 79 final String s2 = bundle.getString("baseName"); 80 if (!s2.equals("Bug4168625Resource2")) { 81 errln("getBundle did not set up proper inheritance chain"); 82 } 83 } finally { 84 Locale.setDefault(oldDefault); 85 } 86 } 87 88 /** 89 * Previous versions of ResourceBundle have had the following 90 * caching behavior. Assume the classes 91 * Bug4168625Resource_fr_FR, Bug4168625Resource_fr, 92 * Bug4168625Resource_en_US, and Bug4168625Resource_en don't 93 * exist. The class Bug4168625Resource does. Assume the default 94 * locale is en_US. 95 * <P> 96 * <pre> 97 * getBundle("Bug4168625Resource", new Locale("fr", "FR")); 98 * -->try to load Bug4168625Resource_fr_FR 99 * -->try to load Bug4168625Resource_fr 100 * -->try to load Bug4168625Resource_en_US 101 * -->try to load Bug4168625Resource_en 102 * -->load Bug4168625Resource 103 * -->cache Bug4168625Resource as Bug4168625Resource 104 * -->cache Bug4168625Resource as Bug4168625Resource_en 105 * -->cache Bug4168625Resource as Bug4168625Resource_en_US 106 * -->return Bug4168625Resource 107 * getBundle("Bug4168625Resource", new Locale("fr", "FR")); 108 * -->try to load Bug4168625Resource_fr_FR 109 * -->try to load Bug4168625Resource_fr 110 * -->find cached Bug4168625Resource_en_US 111 * -->return Bug4168625Resource_en_US (which is realy Bug4168625Resource) 112 * </pre> 113 * <P> 114 * The second call causes two loads for Bug4168625Resource_fr_FR and 115 * Bug4168625Resource_en which have already been tried and failed. These 116 * two loads should have been cached as Bug4168625Resource by the first 117 * call. 118 * 119 * The following, more efficient behavior is desired: 120 * <P> 121 * <pre> 122 * getBundle("Bug4168625Resource", new Locale("fr", "FR")); 123 * -->try to load Bug4168625Resource_fr_FR 124 * -->try to load Bug4168625Resource_fr 125 * -->try to load Bug4168625Resource_en_US 126 * -->try to load Bug4168625Resource_en 127 * -->load Bug4168625Resource 128 * -->cache Bug4168625Resource as Bug4168625Resource 129 * -->cache Bug4168625Resource as Bug4168625Resource_en 130 * -->cache Bug4168625Resource as Bug4168625Resource_en_US 131 * -->cache Bug4168625Resource as Bug4168625Resource_fr 132 * -->cache Bug4168625Resource as Bug4168625Resource_fr_FR 133 * -->return Bug4168625Resource 134 * getBundle("Bug4168625Resource", new Locale("fr", "FR")); 135 * -->find cached Bug4168625Resource_fr_FR 136 * -->return Bug4168625Resource_en_US (which is realy Bug4168625Resource) 137 * </pre> 138 * <P> 139 * 140 */ 141 public void testCacheFailures() throws Exception { 142 checkResourceLoading("Bug4168625Resource", new Locale("fr", "FR")); 143 } 144 145 /** 146 * Previous versions of ResourceBundle have had the following 147 * caching behavior. Assume the current locale is locale is en_US. 148 * The classes Bug4168625Resource_en_US, and Bug4168625Resource_en don't 149 * exist. The class Bug4168625Resource does. 150 * <P> 151 * <pre> 152 * getBundle("Bug4168625Resource", new Locale("en", "US")); 153 * -->try to load Bug4168625Resource_en_US 154 * -->try to load Bug4168625Resource_en 155 * -->try to load Bug4168625Resource_en_US 156 * -->try to load Bug4168625Resource_en 157 * -->load Bug4168625Resource 158 * -->cache Bug4168625Resource as Bug4168625Resource 159 * -->cache Bug4168625Resource as Bug4168625Resource_en 160 * -->cache Bug4168625Resource as Bug4168625Resource_en_US 161 * -->return Bug4168625Resource 162 * </pre> 163 * <P> 164 * The redundant loads of Bug4168625Resource_en_US and Bug4168625Resource_en 165 * should not occur. The desired behavior is as follows: 166 * <P> 167 * <pre> 168 * getBundle("Bug4168625Resource", new Locale("en", "US")); 169 * -->try to load Bug4168625Resource_en_US 170 * -->try to load Bug4168625Resource_en 171 * -->load Bug4168625Resource 172 * -->cache Bug4168625Resource as Bug4168625Resource 173 * -->cache Bug4168625Resource as Bug4168625Resource_en 174 * -->cache Bug4168625Resource as Bug4168625Resource_en_US 175 * -->return Bug4168625Resource 176 * </pre> 177 * <P> 178 */ 179 public void testRedundantLoads() throws Exception { 180 checkResourceLoading("Bug4168625Resource", Locale.getDefault()); 181 } 182 183 /** 184 * Ensure that resources are only loaded once and are cached correctly 185 */ 186 private void checkResourceLoading(String resName, Locale l) throws Exception { 187 final Loader loader = new Loader( new String[] { "Bug4168625Class" }, new String[] { "Bug4168625Resource3_en_US", "Bug4168625Resource3_en_CA" }); 188 final Class c = loader.loadClass("Bug4168625Class"); 189 Bug4168625Getter test = (Bug4168625Getter)c.newInstance(); 190 final String resClassName; 191 if (l.toString().length() > 0) { 192 resClassName = resName+"_"+l; 193 } else { 194 resClassName = resName; 195 } 196 197 Object bundle = test.getResourceBundle(resName, l); 198 loader.logClasses("Initial lookup of "+resClassName+" generated the following loads:"); 199 200 final Vector lastLoad = new Vector(loader.loadedClasses.size()); 201 boolean dups = false; 202 for (int i = loader.loadedClasses.size() - 1; i >= 0 ; i--) { 203 final Object item = loader.loadedClasses.elementAt(i); 204 loader.loadedClasses.removeElementAt(i); 205 if (loader.loadedClasses.contains(item)) { 206 logln("Resource loaded more than once: "+item); 207 dups = true; 208 } else { 209 lastLoad.addElement(item); 210 } 211 } 212 if (dups) { 213 errln("ResourceBundle loaded some classes multiple times"); 214 } 215 216 loader.loadedClasses.removeAllElements(); 217 bundle = test.getResourceBundle(resName, l); 218 loader.logClasses("Second lookup of "+resClassName+" generated the following loads:"); 219 220 dups = false; 221 for (int i = 0; i < loader.loadedClasses.size(); i++) { 222 Object item = loader.loadedClasses.elementAt(i); 223 if (lastLoad.contains(item)) { 224 logln("ResourceBundle did not cache "+item+" correctly"); 225 dups = true; 226 } 227 } 228 if (dups) { 229 errln("Resource bundle not caching some classes properly"); 230 } 231 } 232 233 /** 234 * Previous versions of ResourceBundle exhibited the following caching behavior. 235 * Assume the class Bug4168625Resource_en exists. Bug4168625Resource_en_US does 236 * not. Two threads, ThreadA and ThreadB both try to get the same bundle. 237 * <P> 238 * <pre> 239 * ThreadA.getBundle("Bug4168625Resource", new Locale("en", "US")); 240 * A-->try to load Bug4168625Resource_en_US 241 * ThreadB.getBundle("Bug4168625Resource", new Locale("en", "US")); 242 * B-->try to load Bug4168625Resource_en_US 243 * B-->load Bug4168625Resource_en (#1) 244 * A-->load Bug4168625Resource_en (#2) 245 * A-->cache Bug4168625Resource_en (#2) as Bug4168625Resource_en 246 * A-->cache Bug4168625Resource_en (#2) as Bug4168625Resource_en_US 247 * A-->return Bug4168625Resource_en (#2) 248 * B-->cache Bug4168625Resource_en (#1) as Bug4168625Resource_en 249 * B-->cache Bug4168625Resource_en (#1) as Bug4168625Resource_en_US 250 * B-->return Bug4168625Resource_en (#1) 251 * </pre> 252 * <P> 253 * Both threads try and fail to load Bug4168625Resource_en_US. Both 254 * threads load Bug4168625Resource_en. Both threads get their own copy 255 * of the Bug4168625Resource_en resource. 256 * 257 * The desired behavior is as follows: 258 * <P> 259 * <pre> 260 * ThreadA.getBundle("Bug4168625Resource", new Locale("en", "US")); 261 * A-->try to load Bug4168625Resource_en_US 262 * ThreadB.getBundle("Bug4168625Resource", new Locale("en", "US")); 263 * B-->try to load Bug4168625Resource_en_US 264 * B-->load Bug4168625Resource_en 265 * A-->load Bug4168625Resource_en (block in ResourceBundle.getBundle) 266 * B-->cache Bug4168625Resource_en as Bug4168625Resource_en 267 * B-->cache Bug4168625Resource_en as Bug4168625Resource_en_US 268 * A-->return Bug4168625Resource_en 269 * B-->return Bug4168625Resource_en 270 * </pre> 271 * <P> 272 * Note that both threads return the same bundle object. 273 */ 274 public void testConcurrentLoading1() throws Exception { 275 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 //both threads want the same resource 280 ConcurrentLoadingThread thread1 = new ConcurrentLoadingThread(loader, test, new Locale("en", "US")); 281 ConcurrentLoadingThread thread2 = new ConcurrentLoadingThread(loader, test, new Locale("en", "US")); 282 283 thread1.start(); //start thread 1 284 loader.waitForNotify(1); //wait for thread1 to do getBundle & block in loader 285 thread2.start(); //start second thread 286 loader.waitForNotify(2, 1000); //wait until thread2 blocks somewhere in getBundle 287 thread1.ping(); //continue both threads 288 thread2.ping(); 289 290 thread1.join(); //wait unitl both threads complete 291 thread2.join(); 292 293 //Now, examine the class loads that were done. 294 loader.logClasses("Classes loaded after completion of both threads:"); 295 296 boolean dups = false; 297 for (int i = loader.loadedClasses.size() - 1; i >= 0 ; i--) { 298 final Object item = loader.loadedClasses.elementAt(i); 299 loader.loadedClasses.removeElementAt(i); 300 if (loader.loadedClasses.contains(item)) { 301 logln("Resource loaded more than once: "+item); 302 dups = true; 303 } 304 } 305 if (dups) { 306 errln("ResourceBundle loaded some classes multiple times"); 307 } 308 } 309 310 private class ConcurrentLoadingThread extends Thread { 311 private Loader loader; 312 public Object bundle; 313 private Bug4168625Getter test; 314 private Locale locale; 315 private String resourceName = "Bug4168625Resource3"; 316 public ConcurrentLoadingThread(Loader loader, Bug4168625Getter test, Locale l, String resourceName) { 317 this.loader = loader; 318 this.test = test; 319 this.locale = l; 320 this.resourceName = resourceName; 321 } 322 public ConcurrentLoadingThread(Loader loader, Bug4168625Getter test, Locale l) { 323 this.loader = loader; 324 this.test = test; 325 this.locale = l; 326 } 327 public void run() { 328 try { 329 logln(">>"+threadName()+">run"); 330 bundle = test.getResourceBundle(resourceName, locale); 331 } catch (Exception e) { 332 errln("TEST CAUGHT UNEXPECTED EXCEPTION: "+e); 333 } finally { 334 logln("<<"+threadName()+"<run"); 335 } 336 } 337 public synchronized void waitUntilPinged() { 338 logln(">>"+threadName()+">waitUntilPinged"); 339 loader.notifyEveryone(); 340 try { 341 wait(30000); //wait 30 seconds max. 342 } catch (InterruptedException e) { 343 logln("Test deadlocked."); 344 } 345 logln("<<"+threadName()+"<waitUntilPinged"); 346 } 347 public synchronized void ping() { 348 logln(">>"+threadName()+">ping "+threadName(this)); 349 notifyAll(); 350 logln("<<"+threadName()+"<ping "+threadName(this)); 351 } 352 }; 353 354 /** 355 * This test ensures that multiple resources can be loading at the same 356 * time as long as they don't depend on each other in some way. 357 */ 358 public void testConcurrentLoading2() throws Exception { 359 final Loader loader = new Loader( new String[] { "Bug4168625Class" }, new String[] { "Bug4168625Resource3_en_US", "Bug4168625Resource3_en_CA" }); 360 final Class c = loader.loadClass("Bug4168625Class"); 361 final Bug4168625Getter test = (Bug4168625Getter)c.newInstance(); 362 363 ConcurrentLoadingThread thread1 = new ConcurrentLoadingThread(loader, test, new Locale("en", "CA")); 364 ConcurrentLoadingThread thread2 = new ConcurrentLoadingThread(loader, test, new Locale("en", "IE")); 365 366 thread1.start(); //start thread 1 367 loader.waitForNotify(1); //wait for thread1 to do getBundle & block in loader 368 thread2.start(); //start second thread 369 thread2.join(1000); //wait until thread2 blocks somewhere in getBundle 370 371 //Thread1 should be blocked inside getBundle at the class loader 372 //Thread2 should have completed its getBundle call and terminated 373 if (!thread1.isAlive() || thread2.isAlive()) { 374 errln("ResourceBundle.getBundle not allowing legal concurrent loads"); 375 } 376 377 thread1.ping(); //continue thread1 378 thread1.join(); 379 thread2.join(); 380 } 381 382 /** 383 * This test ensures that a resource loads correctly (with all its parents) 384 * when memory is very low (ex. the cache gets purged during a load). 385 */ 386 public void testLowMemoryLoad() throws Exception { 387 final String[] classToLoad = { "Bug4168625Class" }; 388 final String[] classToWait = { "Bug4168625Resource3_en_US","Bug4168625Resource3_en","Bug4168625Resource3" }; 389 final Loader loader = new Loader(classToLoad, classToWait); 390 final Class c = loader.loadClass("Bug4168625Class"); 391 final Bug4168625Getter test = (Bug4168625Getter)c.newInstance(); 392 causeResourceBundleCacheFlush(); 393 394 ConcurrentLoadingThread thread1 = new ConcurrentLoadingThread(loader, test, new Locale("en", "US")); 395 thread1.start(); //start thread 1 396 loader.waitForNotify(1); //wait for thread1 to do getBundle(en_US) & block in loader 397 causeResourceBundleCacheFlush(); //cause a cache flush 398 thread1.ping(); //kick thread 1 399 loader.waitForNotify(2); //wait for thread1 to do getBundle(en) & block in loader 400 causeResourceBundleCacheFlush(); //cause a cache flush 401 thread1.ping(); //kick thread 1 402 loader.waitForNotify(3); //wait for thread1 to do getBundle(en) & block in loader 403 causeResourceBundleCacheFlush(); //cause a cache flush 404 thread1.ping(); //kick thread 1 405 thread1.ping(); //kick thread 1 406 thread1.join(1000); //wait until thread2 blocks somewhere in getBundle 407 408 ResourceBundle bundle = (ResourceBundle)thread1.bundle; 409 String s1 = bundle.getString("Bug4168625Resource3_en_US"); 410 String s2 = bundle.getString("Bug4168625Resource3_en"); 411 String s3 = bundle.getString("Bug4168625Resource3"); 412 if ((s1 == null) || (s2 == null) || (s3 == null)) { 413 errln("Bundle not constructed correctly. The parent chain is incorrect."); 414 } 415 } 416 417 /** 418 * A simple class loader that loads classes from the current 419 * working directory. The loader will block the current thread 420 * of execution before it returns when it tries to load 421 * the class "Bug4168625Resource3_en_US". 422 */ 423 private static final String CLASS_PREFIX = ""; 424 private static final String CLASS_SUFFIX = ".class"; 425 426 private static final class SimpleLoader extends ClassLoader { 427 private boolean network = false; 428 429 public SimpleLoader() { 430 this.network = false; 431 } 432 public SimpleLoader(boolean simulateNetworkLoad) { 433 this.network = simulateNetworkLoad; 434 } 435 public Class loadClass(final String className, final boolean resolveIt) 436 throws ClassNotFoundException { 437 Class result; 438 synchronized (this) { 439 result = findLoadedClass(className); 440 if (result == null) { 441 if (network) { 442 try { 443 Thread.sleep(100); 444 } catch (java.lang.InterruptedException e) { 445 } 446 } 447 result = super.findSystemClass(className); 448 if ((result != null) && resolveIt) { 449 resolveClass(result); 450 } 451 } 452 } 453 return result; 454 } 455 } 456 457 private final class Loader extends ClassLoader { 458 public final Vector loadedClasses = new Vector(); 459 private String[] classesToLoad; 460 private String[] classesToWaitFor; 461 462 public Loader() { 463 classesToLoad = new String[0]; 464 classesToWaitFor = new String[0]; 465 } 466 467 public Loader(final String[] classesToLoadIn, final String[] classesToWaitForIn) { 468 classesToLoad = classesToLoadIn; 469 classesToWaitFor = classesToWaitForIn; 470 } 471 472 /** 473 * Load a class. Files we can load take preference over ones the system 474 * can load. 475 */ 476 private byte[] getClassData(final String className) { 477 boolean shouldLoad = false; 478 for (int i = classesToLoad.length-1; i >= 0; --i) { 479 if (className.equals(classesToLoad[i])) { 480 shouldLoad = true; 481 break; 482 } 483 } 484 485 if (shouldLoad) { 486 final String name = CLASS_PREFIX+className+CLASS_SUFFIX; 487 try { 488 final InputStream fi = this.getClass().getClassLoader().getResourceAsStream(name); 489 final byte[] result = new byte[fi.available()]; 490 fi.read(result); 491 return result; 492 } catch (Exception e) { 493 logln("Error loading test class: "+name); 494 logln(e.toString()); 495 return null; 496 } 497 } else { 498 return null; 499 } 500 } 501 502 /** 503 * Load a class. Files we can load take preference over ones the system 504 * can load. 505 */ 506 public Class loadClass(final String className, final boolean resolveIt) 507 throws ClassNotFoundException { 508 Class result; 509 synchronized (this) { 510 logln(">>"+threadName()+">load "+className); 511 loadedClasses.addElement(className); 512 513 result = findLoadedClass(className); 514 if (result == null) { 515 final byte[] classData = getClassData(className); 516 if (classData == null) { 517 //we don't have a local copy of this one 518 logln("Loading system class: "+className); 519 result = loadFromSystem(className); 520 } else { 521 result = defineClass(classData, 0, classData.length); 522 if (result == null) { 523 //there was an error defining the class 524 result = loadFromSystem(className); 525 } 526 } 527 if ((result != null) && resolveIt) { 528 resolveClass(result); 529 } 530 } 531 } 532 for (int i = classesToWaitFor.length-1; i >= 0; --i) { 533 if (className.equals(classesToWaitFor[i])) { 534 rendezvous(); 535 break; 536 } 537 } 538 logln("<<"+threadName()+"<load "+className); 539 return result; 540 } 541 542 /** 543 * Delegate loading to the system loader 544 */ 545 private Class loadFromSystem(String className) throws ClassNotFoundException { 546 return super.findSystemClass(className); 547 } 548 549 public void logClasses(String title) { 550 logln(title); 551 for (int i = 0; i < loadedClasses.size(); i++) { 552 logln(" "+loadedClasses.elementAt(i)); 553 } 554 logln(""); 555 } 556 557 public int notifyCount = 0; 558 public int waitForNotify(int count) { 559 return waitForNotify(count, 0); 560 } 561 public synchronized int waitForNotify(int count, long time) { 562 logln(">>"+threadName()+">waitForNotify"); 563 if (count > notifyCount) { 564 try { 565 wait(time); 566 } catch (InterruptedException e) { 567 } 568 } else { 569 logln(" count("+count+") > notifyCount("+notifyCount+")"); 570 } 571 logln("<<"+threadName()+"<waitForNotify"); 572 return notifyCount; 573 } 574 private synchronized void notifyEveryone() { 575 logln(">>"+threadName()+">notifyEveryone"); 576 notifyCount++; 577 notifyAll(); 578 logln("<<"+threadName()+"<notifyEveryone"); 579 } 580 private void rendezvous() { 581 final Thread current = Thread.currentThread(); 582 if (current instanceof ConcurrentLoadingThread) { 583 ((ConcurrentLoadingThread)current).waitUntilPinged(); 584 } 585 } 586 } 587 588 private static String threadName() { 589 return threadName(Thread.currentThread()); 590 } 591 592 private static String threadName(Thread t) { 593 String temp = t.toString(); 594 int ndx = temp.indexOf("Thread["); 595 temp = temp.substring(ndx + "Thread[".length()); 596 ndx = temp.indexOf(','); 597 temp = temp.substring(0, ndx); 598 return temp; 599 } 600 601 /** Fill memory to force all SoftReferences to be GCed */ 602 private void causeResourceBundleCacheFlush() { 603 logln("Filling memory..."); 604 int allocationSize = 1024; 605 Vector memoryHog = new Vector(); 606 try { 607 while (true) { 608 memoryHog.addElement(new byte[allocationSize]); 609 allocationSize *= 2; 610 } 611 } catch (Throwable e) { 612 logln("Caught "+e+" filling memory"); 613 } finally{ 614 memoryHog = null; 615 System.gc(); 616 } 617 logln("last allocation size: " + allocationSize); 618 } 619 620 /** 621 * NOTE: this problem is not externally testable and can only be 622 * verified through code inspection unless special code to force 623 * a task switch is inserted into ResourceBundle. 624 * The class Bug4168625Resource_sp exists. It's parent bundle 625 * (Bug4168625Resource) contains a resource string with the tag 626 * "language" but Bug4168625Resource_sp does not. 627 * Assume two threads are executing, ThreadA and ThreadB and they both 628 * load a resource Bug4168625Resource with from sp locale. 629 * ResourceBundle.getBundle adds a bundle to the bundle cache (in 630 * findBundle) before it sets the bundle's parent (in getBundle after 631 * returning from findBundle). 632 * <P> 633 * <pre> 634 * ThreadA.getBundle("Bug4168625Resource", new Locale("sp")); 635 * A-->load Bug4168625Resource_sp 636 * A-->find cached Bug4168625Resource 637 * A-->cache Bug4168625Resource_sp as Bug4168625Resource_sp 638 * ThreadB.getBundle("Bug4168625Resource", new Locale("sp")); 639 * B-->find cached Bug4168625Resource_sp 640 * B-->return Bug4168625Resource_sp 641 * ThreadB.bundle.getString("language"); 642 * B-->try to find "language" in Bug4168625Resource_sp 643 * B-->Bug4168625Resource_sp does not have a parent, so return null; 644 * ThreadB.System.out.println("Some unknown country"); 645 * A-->set parent of Bug4168625Resource_sp to Bug4168625Resource 646 * A-->return Bug4168625Resource_sp (the same bundle ThreadB got) 647 * ThreadA.bundle.getString("language"); 648 * A-->try to find "language" in Bug4168625Resource_sp 649 * A-->try to find "language" in Bug4168625Resource (parent of Bug4168625Resource_sp) 650 * A-->return the string 651 * ThreadA.System.out.println("Langauge = "+country); 652 * ThreadB.bundle.getString("language"); 653 * B-->try to find "language" in Bug4168625Resource_sp 654 * B-->try to find "language" in Bug4168625Resource (parent of Bug4168625Resource_sp) 655 * B-->return the string 656 * ThreadB.System.out.println("Langauge = "+country); 657 * </pre> 658 * <P> 659 * Note that the first call to getString() by ThreadB returns null, but the second 660 * returns a value. Thus to ThreadB, the bundle appears to change. ThreadA gets 661 * the expected results right away. 662 */ 663} 664