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