001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.commons.beanutils.locale; 019 020 import java.util.*; 021 022 import java.lang.ref.WeakReference; 023 import java.lang.ref.ReferenceQueue; 024 025 import junit.framework.TestCase; 026 import junit.framework.Test; 027 import junit.framework.TestSuite; 028 029 import org.apache.commons.logging.LogFactory; 030 031 import org.apache.commons.beanutils.ContextClassLoaderLocal; 032 import org.apache.commons.beanutils.PrimitiveBean; 033 import org.apache.commons.beanutils.ConvertUtils; 034 import org.apache.commons.beanutils.ConversionException; 035 import org.apache.commons.beanutils.locale.converters.LongLocaleConverter; 036 037 import java.util.Locale; 038 039 /** 040 * <p> 041 * Test Case for changes made during LocaleBeanutils Beanification. 042 * This is basically a cut-and-correct version of the beanutils beanifications tests. 043 * </p> 044 * 045 * @author Robert Burrell Donkin 046 * @author Juozas Baliuka 047 * @version $Revision: 812183 $ $Date: 2009-09-07 11:08:54 -0400 (Mon, 07 Sep 2009) $ 048 */ 049 050 public class LocaleBeanificationTestCase extends TestCase { 051 052 // ---------------------------------------------------- Constants 053 054 /** Maximum number of iterations before our test fails */ 055 public static final int MAX_GC_ITERATIONS = 50; 056 057 // ---------------------------------------------------- Instance Variables 058 059 060 // ---------------------------------------------------------- Constructors 061 062 063 /** 064 * Construct a new instance of this test case. 065 * 066 * @param name Name of the test case 067 */ 068 public LocaleBeanificationTestCase(String name) { 069 super(name); 070 } 071 072 073 // -------------------------------------------------- Overall Test Methods 074 075 076 /** 077 * Set up instance variables required by this test case. 078 */ 079 public void setUp() { 080 081 LocaleConvertUtils.deregister(); 082 083 } 084 085 086 /** 087 * Return the tests included in this test suite. 088 */ 089 public static Test suite() { 090 return (new TestSuite(LocaleBeanificationTestCase.class)); 091 } 092 093 094 /** 095 * Tear down instance variables required by this test case. 096 */ 097 public void tearDown() { 098 // No action required 099 } 100 101 102 // ------------------------------------------------ Individual Test Methods 103 104 /** Test of the methodology we'll use for some of the later tests */ 105 public void testMemoryTestMethodology() throws Exception { 106 // test methodology 107 // many thanks to Juozas Baliuka for suggesting this method 108 ClassLoader loader = new ClassLoader(this.getClass().getClassLoader()) {}; 109 WeakReference reference = new WeakReference(loader); 110 Class myClass = loader.loadClass("org.apache.commons.beanutils.BetaBean"); 111 112 assertNotNull("Weak reference released early", reference.get()); 113 114 // dereference class loader and class: 115 loader = null; 116 myClass = null; 117 118 int iterations = 0; 119 int bytz = 2; 120 while(true) { 121 System.gc(); 122 if(iterations++ > MAX_GC_ITERATIONS){ 123 fail("Max iterations reached before resource released."); 124 } 125 if( reference.get() == null ) { 126 break; 127 128 } else { 129 // create garbage: 130 byte[] b = new byte[bytz]; 131 bytz = bytz * 2; 132 } 133 } 134 } 135 136 /** Tests whether classloaders and beans are released from memory by the map used by beanutils */ 137 public void testMemoryLeak2() throws Exception { 138 // tests when the map used by beanutils has the right behaviour 139 140 if (isPre14JVM()) { 141 System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM"); 142 return; 143 } 144 145 // many thanks to Juozas Baliuka for suggesting this methodology 146 TestClassLoader loader = new TestClassLoader(); 147 ReferenceQueue queue = new ReferenceQueue(); 148 WeakReference loaderReference = new WeakReference(loader, queue); 149 Integer test = new Integer(1); 150 151 WeakReference testReference = new WeakReference(test, queue); 152 //Map map = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.HARD, true); 153 Map map = new WeakHashMap(); 154 map.put(loader, test); 155 156 assertEquals("In map", test, map.get(loader)); 157 assertNotNull("Weak reference released early (1)", loaderReference.get()); 158 assertNotNull("Weak reference released early (2)", testReference.get()); 159 160 // dereference strong references 161 loader = null; 162 test = null; 163 164 int iterations = 0; 165 int bytz = 2; 166 while(true) { 167 System.gc(); 168 if(iterations++ > MAX_GC_ITERATIONS){ 169 fail("Max iterations reached before resource released."); 170 } 171 map.isEmpty(); 172 173 if( 174 loaderReference.get() == null && 175 testReference.get() == null) { 176 break; 177 178 } else { 179 // create garbage: 180 byte[] b = new byte[bytz]; 181 bytz = bytz * 2; 182 } 183 } 184 } 185 186 /** Tests whether classloaders and beans are released from memory */ 187 public void testMemoryLeak() throws Exception { 188 if (isPre14JVM()) { 189 System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM"); 190 return; 191 } 192 193 // many thanks to Juozas Baliuka for suggesting this methodology 194 TestClassLoader loader = new TestClassLoader(); 195 WeakReference loaderReference = new WeakReference(loader); 196 LocaleBeanUtilsBean.getLocaleBeanUtilsInstance(); 197 198 class GetBeanUtilsBeanThread extends Thread { 199 200 LocaleBeanUtilsBean beanUtils; 201 LocaleConvertUtilsBean convertUtils; 202 203 GetBeanUtilsBeanThread() {} 204 205 public void run() { 206 beanUtils = LocaleBeanUtilsBean.getLocaleBeanUtilsInstance(); 207 convertUtils = LocaleConvertUtilsBean.getInstance(); 208 // XXX Log keeps a reference around! 209 LogFactory.releaseAll(); 210 } 211 212 public String toString() { 213 return "GetBeanUtilsBeanThread"; 214 } 215 } 216 217 218 GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread(); 219 WeakReference threadWeakReference = new WeakReference(thread); 220 thread.setContextClassLoader(loader); 221 222 thread.start(); 223 thread.join(); 224 225 WeakReference beanUtilsReference = new WeakReference(thread.beanUtils); 226 WeakReference convertUtilsReference = new WeakReference(thread.convertUtils); 227 228 assertNotNull("Weak reference released early (1)", loaderReference.get()); 229 assertNotNull("Weak reference released early (2)", beanUtilsReference.get()); 230 assertNotNull("Weak reference released early (4)", convertUtilsReference.get()); 231 232 // dereference strong references 233 loader = null; 234 thread.setContextClassLoader(null); 235 thread = null; 236 237 int iterations = 0; 238 int bytz = 2; 239 while(true) { 240 LocaleBeanUtilsBean.getLocaleBeanUtilsInstance(); 241 System.gc(); 242 if(iterations++ > MAX_GC_ITERATIONS){ 243 fail("Max iterations reached before resource released."); 244 } 245 246 if( 247 loaderReference.get() == null && 248 beanUtilsReference.get() == null && 249 convertUtilsReference.get() == null) { 250 break; 251 252 } else { 253 // create garbage: 254 byte[] b = new byte[bytz]; 255 bytz = bytz * 2; 256 } 257 } 258 } 259 260 /** 261 * Tests whether difference instances are loaded by different 262 * context classloaders. 263 */ 264 public void testGetByContextClassLoader() throws Exception { 265 266 class GetBeanUtilsBeanThread extends Thread { 267 268 private Signal signal; 269 270 GetBeanUtilsBeanThread(Signal signal) { 271 this.signal = signal; 272 } 273 274 public void run() { 275 signal.setSignal(2); 276 signal.setBean(LocaleBeanUtilsBean.getLocaleBeanUtilsInstance()); 277 signal.setConvertUtils(LocaleConvertUtilsBean.getInstance()); 278 } 279 280 public String toString() { 281 return "GetBeanUtilsBeanThread"; 282 } 283 } 284 285 Signal signal = new Signal(); 286 signal.setSignal(1); 287 288 GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread(signal); 289 thread.setContextClassLoader(new TestClassLoader()); 290 291 thread.start(); 292 thread.join(); 293 294 assertEquals("Signal not set by test thread", 2, signal.getSignal()); 295 assertTrue( 296 "Different LocaleBeanUtilsBean instances per context classloader", 297 LocaleBeanUtilsBean.getInstance() != signal.getBean()); 298 assertTrue( 299 "Different LocaleConvertUtilsBean instances per context classloader", 300 LocaleConvertUtilsBean.getInstance() != signal.getConvertUtils()); 301 } 302 303 304 /** 305 * Tests whether difference instances are loaded by different 306 * context classloaders. 307 */ 308 public void testContextClassLoaderLocal() throws Exception { 309 310 class CCLLTesterThread extends Thread { 311 312 private Signal signal; 313 private ContextClassLoaderLocal ccll; 314 315 CCLLTesterThread(Signal signal, ContextClassLoaderLocal ccll) { 316 this.signal = signal; 317 this.ccll = ccll; 318 } 319 320 public void run() { 321 ccll.set(new Integer(1789)); 322 signal.setSignal(2); 323 signal.setMarkerObject(ccll.get()); 324 } 325 326 public String toString() { 327 return "CCLLTesterThread"; 328 } 329 } 330 331 ContextClassLoaderLocal ccll = new ContextClassLoaderLocal(); 332 ccll.set(new Integer(1776)); 333 assertEquals("Start thread sets value", new Integer(1776), ccll.get()); 334 335 Signal signal = new Signal(); 336 signal.setSignal(1); 337 338 CCLLTesterThread thread = new CCLLTesterThread(signal, ccll); 339 thread.setContextClassLoader(new TestClassLoader()); 340 341 thread.start(); 342 thread.join(); 343 344 assertEquals("Signal not set by test thread", 2, signal.getSignal()); 345 assertEquals("Second thread preserves value", new Integer(1776), ccll.get()); 346 assertEquals("Second thread gets value it set", new Integer(1789), signal.getMarkerObject()); 347 } 348 349 /** Tests whether calls are independent for different classloaders */ 350 public void testContextClassloaderIndependence() throws Exception { 351 352 class TestIndependenceThread extends Thread { 353 private Signal signal; 354 private PrimitiveBean bean; 355 356 TestIndependenceThread(Signal signal, PrimitiveBean bean) { 357 this.signal = signal; 358 this.bean = bean; 359 } 360 361 public void run() { 362 try { 363 signal.setSignal(3); 364 LocaleConvertUtils.register(new LocaleConverter() { 365 public Object convert(Class type, Object value) { 366 return new Integer(9); 367 } 368 public Object convert(Class type, Object value, String pattern) { 369 return new Integer(9); 370 } 371 }, Integer.TYPE, Locale.getDefault()); 372 LocaleBeanUtils.setProperty(bean, "int", "1"); 373 } catch (Exception e) { 374 e.printStackTrace(); 375 signal.setException(e); 376 } 377 } 378 379 public String toString() { 380 return "TestIndependenceThread"; 381 } 382 } 383 384 PrimitiveBean bean = new PrimitiveBean(); 385 LocaleBeanUtils.setProperty(bean, "int", new Integer(1)); 386 assertEquals("Wrong property value (1)", 1, bean.getInt()); 387 388 LocaleConvertUtils.register(new LocaleConverter() { 389 public Object convert(Class type, Object value) { 390 return new Integer(5); 391 } 392 public Object convert(Class type, Object value, String pattern) { 393 return new Integer(5); 394 } 395 }, Integer.TYPE, Locale.getDefault()); 396 LocaleBeanUtils.setProperty(bean, "int", "1"); 397 assertEquals("Wrong property value(2)", 5, bean.getInt()); 398 399 Signal signal = new Signal(); 400 signal.setSignal(1); 401 TestIndependenceThread thread = new TestIndependenceThread(signal, bean); 402 thread.setContextClassLoader(new TestClassLoader()); 403 404 thread.start(); 405 thread.join(); 406 407 assertNull("Exception thrown by test thread:" + signal.getException(), signal.getException()); 408 assertEquals("Signal not set by test thread", 3, signal.getSignal()); 409 assertEquals("Wrong property value(3)", 9, bean.getInt()); 410 411 } 412 413 /** Tests whether different threads can set beanutils instances correctly */ 414 public void testBeanUtilsBeanSetInstance() throws Exception { 415 416 class SetInstanceTesterThread extends Thread { 417 418 private Signal signal; 419 private LocaleBeanUtilsBean bean; 420 421 SetInstanceTesterThread(Signal signal, LocaleBeanUtilsBean bean) { 422 this.signal = signal; 423 this.bean = bean; 424 } 425 426 public void run() { 427 LocaleBeanUtilsBean.setInstance(bean); 428 signal.setSignal(21); 429 signal.setBean(LocaleBeanUtilsBean.getLocaleBeanUtilsInstance()); 430 } 431 432 public String toString() { 433 return "SetInstanceTesterThread"; 434 } 435 } 436 437 Signal signal = new Signal(); 438 signal.setSignal(1); 439 440 LocaleBeanUtilsBean beanOne = new LocaleBeanUtilsBean(); 441 LocaleBeanUtilsBean beanTwo = new LocaleBeanUtilsBean(); 442 443 SetInstanceTesterThread thread = new SetInstanceTesterThread(signal, beanTwo); 444 thread.setContextClassLoader(new TestClassLoader()); 445 446 LocaleBeanUtilsBean.setInstance(beanOne); 447 assertEquals("Start thread gets right instance", beanOne, LocaleBeanUtilsBean.getLocaleBeanUtilsInstance()); 448 449 thread.start(); 450 thread.join(); 451 452 assertEquals("Signal not set by test thread", 21, signal.getSignal()); 453 assertEquals("Second thread preserves value", beanOne, LocaleBeanUtilsBean.getLocaleBeanUtilsInstance()); 454 assertEquals("Second thread gets value it set", beanTwo, signal.getBean()); 455 } 456 457 /** Tests whether the unset method works*/ 458 public void testContextClassLoaderUnset() throws Exception { 459 LocaleBeanUtilsBean beanOne = new LocaleBeanUtilsBean(); 460 ContextClassLoaderLocal ccll = new ContextClassLoaderLocal(); 461 ccll.set(beanOne); 462 assertEquals("Start thread gets right instance", beanOne, ccll.get()); 463 ccll.unset(); 464 assertTrue("Unset works", !beanOne.equals(ccll.get())); 465 } 466 467 /** 468 * Test registering a locale-aware converter with the standard ConvertUtils. 469 */ 470 public void testLocaleAwareConverterInConvertUtils() throws Exception { 471 try { 472 // first use the default non-locale-aware converter 473 try { 474 Long data = (Long) ConvertUtils.convert("777", Long.class); 475 assertEquals("Standard format long converted ok", 777, data.longValue()); 476 } 477 catch(ConversionException ex) { 478 fail("Unable to convert non-locale-aware number 777"); 479 } 480 481 // now try default converter with special delimiters 482 try { 483 // This conversion will cause an error. But the default 484 // Long converter is set up to return a default value of 485 // zero on error. 486 Long data = (Long) ConvertUtils.convert("1.000.000", Long.class); 487 assertEquals("Standard format behaved as expected", 0, data.longValue()); 488 } 489 catch(ConversionException ex) { 490 fail("Unexpected exception from standard Long converter."); 491 } 492 493 // Now try using a locale-aware converter together with 494 // locale-specific input string. Note that in the german locale, 495 // large numbers can be split up into groups of three digits 496 // using a dot character (and comma is the decimal-point indicator). 497 try { 498 499 Locale germanLocale = Locale.GERMAN; 500 LongLocaleConverter longLocaleConverter = new LongLocaleConverter(germanLocale); 501 ConvertUtils.register(longLocaleConverter, Long.class); 502 503 Long data = (Long) ConvertUtils.convert("1.000.000", Long.class); 504 assertEquals("German-format long converted ok", 1000000, data.longValue()); 505 } catch(ConversionException ex) { 506 fail("Unable to convert german-format number"); 507 } 508 } finally { 509 ConvertUtils.deregister(); 510 } 511 } 512 513 private boolean isPre14JVM() { 514 // some pre 1.4 JVM have buggy WeakHashMap implementations 515 // this is used to test for those JVM 516 String version = System.getProperty("java.specification.version"); 517 StringTokenizer tokenizer = new StringTokenizer(version,"."); 518 if (tokenizer.nextToken().equals("1")) { 519 String minorVersion = tokenizer.nextToken(); 520 if (minorVersion.equals("0")) return true; 521 if (minorVersion.equals("1")) return true; 522 if (minorVersion.equals("2")) return true; 523 if (minorVersion.equals("3")) return true; 524 } 525 return false; 526 } 527 528 // ---- Auxillary classes 529 530 class TestClassLoader extends ClassLoader { 531 public String toString() { 532 return "TestClassLoader"; 533 } 534 } 535 536 class Signal { 537 private Exception e; 538 private int signal = 0; 539 private LocaleBeanUtilsBean bean; 540 private LocaleConvertUtilsBean convertUtils; 541 private Object marker; 542 543 public Exception getException() { 544 return e; 545 } 546 547 public void setException(Exception e) { 548 this.e = e; 549 } 550 551 public int getSignal() { 552 return signal; 553 } 554 555 public void setSignal(int signal) { 556 this.signal = signal; 557 } 558 559 public Object getMarkerObject() { 560 return marker; 561 } 562 563 public void setMarkerObject(Object marker) { 564 this.marker = marker; 565 } 566 567 public LocaleBeanUtilsBean getBean() { 568 return bean; 569 } 570 571 public void setBean(LocaleBeanUtilsBean bean) { 572 this.bean = bean; 573 } 574 575 public LocaleConvertUtilsBean getConvertUtils() { 576 return convertUtils; 577 } 578 579 public void setConvertUtils(LocaleConvertUtilsBean convertUtils) { 580 this.convertUtils = convertUtils; 581 } 582 } 583 } 584