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