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