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