    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