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