1/* 2 * Copyright (c) 2007, 2015, 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 6993339 29*/ 30/* 31 * 32 * 33 * (C) Copyright IBM Corp. 1999 - All Rights Reserved 34 * 35 * 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/** 47 * This test tries to correct two efficiency problems with the caching 48 * mechanism of ResourceBundle. It also allows concurrent loads 49 * 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 226 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 */ 274 public void testConcurrentLoading() 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 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(); //wait until thread2 terminates. 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 } 296 297 /** 298 * This test ensures that a resource loads correctly (with all its parents) 299 * when memory is very low (ex. the cache gets purged during a load). 300 */ 301 public void testLowMemoryLoad() throws Exception { 302 final String[] classToLoad = { "Bug4168625Class" }; 303 final String[] classToWait = { "Bug4168625Resource3_en_US","Bug4168625Resource3_en","Bug4168625Resource3" }; 304 final Loader loader = new Loader(classToLoad, classToWait); 305 final Class c = loader.loadClass("Bug4168625Class"); 306 final Bug4168625Getter test = (Bug4168625Getter)c.newInstance(); 307 causeResourceBundleCacheFlush(); 308 309 ConcurrentLoadingThread thread1 = new ConcurrentLoadingThread(loader, test, new Locale("en", "US")); 310 thread1.start(); //start thread 1 311 loader.waitForNotify(1); //wait for thread1 to do getBundle(en_US) & block in loader 312 causeResourceBundleCacheFlush(); //cause a cache flush 313 thread1.ping(); //kick thread 1 314 loader.waitForNotify(2); //wait for thread1 to do getBundle(en) & block in loader 315 causeResourceBundleCacheFlush(); //cause a cache flush 316 thread1.ping(); //kick thread 1 317 loader.waitForNotify(3); //wait for thread1 to do getBundle(en) & block in loader 318 causeResourceBundleCacheFlush(); //cause a cache flush 319 thread1.ping(); //kick thread 1 320 thread1.join(); //wait until thread1 terminates 321 322 ResourceBundle bundle = (ResourceBundle)thread1.bundle; 323 String s1 = bundle.getString("Bug4168625Resource3_en_US"); 324 String s2 = bundle.getString("Bug4168625Resource3_en"); 325 String s3 = bundle.getString("Bug4168625Resource3"); 326 if ((s1 == null) || (s2 == null) || (s3 == null)) { 327 errln("Bundle not constructed correctly. The parent chain is incorrect."); 328 } 329 } 330 331 /** 332 * A simple class loader that loads classes from the current 333 * working directory. The loader will block the current thread 334 * of execution before it returns when it tries to load 335 * the class "Bug4168625Resource3_en_US". 336 */ 337 private static final String CLASS_PREFIX = ""; 338 private static final String CLASS_SUFFIX = ".class"; 339 340 private static final class SimpleLoader extends ClassLoader { 341 private boolean network = false; 342 343 public SimpleLoader() { 344 super(SimpleLoader.class.getClassLoader()); 345 this.network = false; 346 } 347 public SimpleLoader(boolean simulateNetworkLoad) { 348 super(SimpleLoader.class.getClassLoader()); 349 this.network = simulateNetworkLoad; 350 } 351 public Class loadClass(final String className, final boolean resolveIt) 352 throws ClassNotFoundException { 353 Class result; 354 synchronized (this) { 355 result = findLoadedClass(className); 356 if (result == null) { 357 if (network) { 358 try { 359 Thread.sleep(100); 360 } catch (java.lang.InterruptedException e) { 361 } 362 } 363 result = getParent().loadClass(className); 364 if ((result != null) && resolveIt) { 365 resolveClass(result); 366 } 367 } 368 } 369 return result; 370 } 371 } 372 373 private final class Loader extends ClassLoader { 374 public final Vector loadedClasses = new Vector(); 375 private String[] classesToLoad; 376 private String[] classesToWaitFor; 377 378 public Loader() { 379 super(Loader.class.getClassLoader()); 380 classesToLoad = new String[0]; 381 classesToWaitFor = new String[0]; 382 } 383 384 public Loader(final String[] classesToLoadIn, final String[] classesToWaitForIn) { 385 super(Loader.class.getClassLoader()); 386 classesToLoad = classesToLoadIn; 387 classesToWaitFor = classesToWaitForIn; 388 } 389 390 /** 391 * Load a class. Files we can load take preference over ones the system 392 * can load. 393 */ 394 private byte[] getClassData(final String className) { 395 boolean shouldLoad = false; 396 for (int i = classesToLoad.length-1; i >= 0; --i) { 397 if (className.equals(classesToLoad[i])) { 398 shouldLoad = true; 399 break; 400 } 401 } 402 403 if (shouldLoad) { 404 final String name = CLASS_PREFIX+className+CLASS_SUFFIX; 405 try { 406 final InputStream fi = this.getClass().getClassLoader().getResourceAsStream(name); 407 final byte[] result = new byte[fi.available()]; 408 fi.read(result); 409 return result; 410 } catch (Exception e) { 411 logln("Error loading test class: "+name); 412 logln(e.toString()); 413 return null; 414 } 415 } else { 416 return null; 417 } 418 } 419 420 /** 421 * Load a class. Files we can load take preference over ones the system 422 * can load. 423 */ 424 public Class loadClass(final String className, final boolean resolveIt) 425 throws ClassNotFoundException { 426 Class result; 427 synchronized (this) { 428 try { 429 logln(">>"+threadName()+">load "+className); 430 loadedClasses.addElement(className); 431 432 result = findLoadedClass(className); 433 if (result == null) { 434 final byte[] classData = getClassData(className); 435 if (classData == null) { 436 //we don't have a local copy of this one 437 logln("Loading system class: "+className); 438 result = loadFromSystem(className); 439 } else { 440 result = defineClass(classData, 0, classData.length); 441 if (result == null) { 442 //there was an error defining the class 443 result = loadFromSystem(className); 444 } 445 } 446 if ((result != null) && resolveIt) { 447 resolveClass(result); 448 } 449 } 450 } catch (ClassNotFoundException e) { 451 // Ignore loading of Bug4168625ResourceProvider 452 if (className.equals("Bug4168625ResourceProvider")) { 453 logln("Ignoring " + className); 454 loadedClasses.remove(className); 455 return null; 456 } 457 throw e; 458 } 459 } 460 for (int i = classesToWaitFor.length-1; i >= 0; --i) { 461 if (className.equals(classesToWaitFor[i])) { 462 rendezvous(); 463 break; 464 } 465 } 466 logln("<<"+threadName()+"<load "+className); 467 return result; 468 } 469 470 /** 471 * Delegate loading to its parent class loader that loads the test classes. 472 * In othervm mode, the parent class loader is the system class loader; 473 * in samevm mode, the parent class loader is the jtreg URLClassLoader. 474 */ 475 private Class loadFromSystem(String className) throws ClassNotFoundException { 476 return getParent().loadClass(className); 477 } 478 479 public void logClasses(String title) { 480 logln(title); 481 for (int i = 0; i < loadedClasses.size(); i++) { 482 logln(" "+loadedClasses.elementAt(i)); 483 } 484 logln(""); 485 } 486 487 public int notifyCount = 0; 488 public int waitForNotify(int count) { 489 return waitForNotify(count, 0); 490 } 491 public synchronized int waitForNotify(int count, long time) { 492 logln(">>"+threadName()+">waitForNotify"); 493 if (count > notifyCount) { 494 try { 495 wait(time); 496 } catch (InterruptedException e) { 497 } 498 } else { 499 logln(" count("+count+") > notifyCount("+notifyCount+")"); 500 } 501 logln("<<"+threadName()+"<waitForNotify"); 502 return notifyCount; 503 } 504 private synchronized void notifyEveryone() { 505 logln(">>"+threadName()+">notifyEveryone"); 506 notifyCount++; 507 notifyAll(); 508 logln("<<"+threadName()+"<notifyEveryone"); 509 } 510 private void rendezvous() { 511 final Thread current = Thread.currentThread(); 512 if (current instanceof ConcurrentLoadingThread) { 513 ((ConcurrentLoadingThread)current).waitUntilPinged(); 514 } 515 } 516 } 517 518 private static String threadName() { 519 return threadName(Thread.currentThread()); 520 } 521 522 private static String threadName(Thread t) { 523 String temp = t.toString(); 524 int ndx = temp.indexOf("Thread["); 525 temp = temp.substring(ndx + "Thread[".length()); 526 ndx = temp.indexOf(','); 527 temp = temp.substring(0, ndx); 528 return temp; 529 } 530 531 /** Fill memory to force all SoftReferences to be GCed */ 532 private void causeResourceBundleCacheFlush() { 533 logln("Filling memory..."); 534 int allocationSize = 1024; 535 Vector memoryHog = new Vector(); 536 try { 537 while (true) { 538 memoryHog.addElement(new byte[allocationSize]); 539 allocationSize *= 2; 540 } 541 } catch (Throwable e) { 542 logln("Caught "+e+" filling memory"); 543 } finally{ 544 memoryHog = null; 545 System.gc(); 546 } 547 logln("last allocation size: " + allocationSize); 548 } 549 550 /** 551 * NOTE: this problem is not externally testable and can only be 552 * verified through code inspection unless special code to force 553 * a task switch is inserted into ResourceBundle. 554 * The class Bug4168625Resource_sp exists. It's parent bundle 555 * (Bug4168625Resource) contains a resource string with the tag 556 * "language" but Bug4168625Resource_sp does not. 557 * Assume two threads are executing, ThreadA and ThreadB and they both 558 * load a resource Bug4168625Resource with from sp locale. 559 * ResourceBundle.getBundle adds a bundle to the bundle cache (in 560 * findBundle) before it sets the bundle's parent (in getBundle after 561 * returning from findBundle). 562 * <P> 563 * <pre> 564 * ThreadA.getBundle("Bug4168625Resource", new Locale("sp")); 565 * A-->load Bug4168625Resource_sp 566 * A-->find cached Bug4168625Resource 567 * A-->cache Bug4168625Resource_sp as Bug4168625Resource_sp 568 * ThreadB.getBundle("Bug4168625Resource", new Locale("sp")); 569 * B-->find cached Bug4168625Resource_sp 570 * B-->return Bug4168625Resource_sp 571 * ThreadB.bundle.getString("language"); 572 * B-->try to find "language" in Bug4168625Resource_sp 573 * B-->Bug4168625Resource_sp does not have a parent, so return null; 574 * ThreadB.System.out.println("Some unknown country"); 575 * A-->set parent of Bug4168625Resource_sp to Bug4168625Resource 576 * A-->return Bug4168625Resource_sp (the same bundle ThreadB got) 577 * ThreadA.bundle.getString("language"); 578 * A-->try to find "language" in Bug4168625Resource_sp 579 * A-->try to find "language" in Bug4168625Resource (parent of Bug4168625Resource_sp) 580 * A-->return the string 581 * ThreadA.System.out.println("Langauge = "+country); 582 * ThreadB.bundle.getString("language"); 583 * B-->try to find "language" in Bug4168625Resource_sp 584 * B-->try to find "language" in Bug4168625Resource (parent of Bug4168625Resource_sp) 585 * B-->return the string 586 * ThreadB.System.out.println("Langauge = "+country); 587 * </pre> 588 * <P> 589 * Note that the first call to getString() by ThreadB returns null, but the second 590 * returns a value. Thus to ThreadB, the bundle appears to change. ThreadA gets 591 * the expected results right away. 592 */ 593} 594