You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ni...@apache.org on 2008/04/21 20:05:24 UTC

svn commit: r650215 - in /commons/proper/beanutils/trunk/src: java/org/apache/commons/beanutils/ java/org/apache/commons/beanutils/locale/ test/org/apache/commons/beanutils/memoryleaktests/

Author: niallp
Date: Mon Apr 21 11:05:18 2008
New Revision: 650215

URL: http://svn.apache.org/viewvc?rev=650215&view=rev
Log:
BEANUTILS-291 - fix Memory leaks - thanks to Clebert Suconic reporting, patches and review:

ConvertUtilsBean - use WeakHashMap for converters cache
LocaleConvertUtilsBean - use new WeakFastHashMap for converters cache
MappedPropertyDescriptor - store method references in SoftReference and provide mechanism to re-create
MethodUtils - put MethodDescriptors in WeakReference (within the WeakHashMap)
PropertyUtilsBean - use WeakHashMap for the two property descriptors caches
WrapDynaClass - store the class in a SoftReference

Modified:
    commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/ConvertUtilsBean.java
    commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java
    commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MethodUtils.java
    commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/PropertyUtilsBean.java
    commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/WrapDynaClass.java
    commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/locale/LocaleConvertUtilsBean.java
    commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/memoryleaktests/MemoryLeakTestCase.java

Modified: commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/ConvertUtilsBean.java
URL: http://svn.apache.org/viewvc/commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/ConvertUtilsBean.java?rev=650215&r1=650214&r2=650215&view=diff
==============================================================================
--- commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/ConvertUtilsBean.java (original)
+++ commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/ConvertUtilsBean.java Mon Apr 21 11:05:18 2008
@@ -28,7 +28,8 @@
 import java.util.Calendar;
 import java.util.Collection;
 import java.util.Map;
-import java.util.HashMap;
+import java.util.WeakHashMap;
+
 import org.apache.commons.beanutils.converters.ArrayConverter;
 import org.apache.commons.beanutils.converters.BigDecimalConverter;
 import org.apache.commons.beanutils.converters.BigIntegerConverter;
@@ -150,7 +151,7 @@
      * The set of {@link Converter}s that can be used to convert Strings
      * into objects of a specified Class, keyed by the destination Class.
      */
-    private Map converters = new HashMap();
+    private Map converters = new WeakHashMap();
 
     /**
      * The <code>Log</code> instance for this class.

Modified: commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java
URL: http://svn.apache.org/viewvc/commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java?rev=650215&r1=650214&r2=650215&view=diff
==============================================================================
--- commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java (original)
+++ commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java Mon Apr 21 11:05:18 2008
@@ -20,6 +20,9 @@
 
 import java.beans.IntrospectionException;
 import java.beans.PropertyDescriptor;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 
@@ -49,17 +52,17 @@
     /**
      * The underlying data type of the property we are describing.
      */
-    private Class mappedPropertyType;
+    private Reference mappedPropertyTypeRef;
 
     /**
      * The reader method for this property (if any).
      */
-    private Method mappedReadMethod;
+    private MappedMethodReference mappedReadMethodRef;
 
     /**
      * The writer method for this property (if any).
      */
-    private Method mappedWriteMethod;
+    private MappedMethodReference mappedWriteMethodRef;
 
     /**
      * The parameter types array for the reader method signature.
@@ -98,6 +101,8 @@
         String base = capitalizePropertyName(propertyName);
         
         // Look for mapped read method and matching write method
+        Method mappedReadMethod = null;
+        Method mappedWriteMethod = null;
         try {
             try {
                 mappedReadMethod = getMethod(beanClass, "get" + base,
@@ -124,6 +129,8 @@
                     "' not found on " +
                     beanClass.getName());
         }
+        mappedReadMethodRef  = new MappedMethodReference(mappedReadMethod);
+        mappedWriteMethodRef = new MappedMethodReference(mappedWriteMethod);
         
         findMappedPropertyType();
     }
@@ -159,6 +166,8 @@
         setName(propertyName);
 
         // search the mapped get and set methods
+        Method mappedReadMethod = null;
+        Method mappedWriteMethod = null;
         mappedReadMethod =
             getMethod(beanClass, mappedGetterName, STRING_CLASS_PARAMETER);
 
@@ -170,6 +179,8 @@
             mappedWriteMethod =
                 getMethod(beanClass, mappedSetterName, 2);
         }
+        mappedReadMethodRef  = new MappedMethodReference(mappedReadMethod);
+        mappedWriteMethodRef = new MappedMethodReference(mappedWriteMethod);
 
         findMappedPropertyType();
     }
@@ -200,8 +211,8 @@
         }
 
         setName(propertyName);
-        mappedReadMethod = mappedGetter;
-        mappedWriteMethod = mappedSetter;
+        mappedReadMethodRef  = new MappedMethodReference(mappedGetter);
+        mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
         findMappedPropertyType();
     }
 
@@ -218,7 +229,7 @@
      * This is the type that will be returned by the mappedReadMethod.
      */
     public Class getMappedPropertyType() {
-        return mappedPropertyType;
+        return (Class)mappedPropertyTypeRef.get();
     }
 
     /**
@@ -228,7 +239,7 @@
      * May return null if the property can't be read.
      */
     public Method getMappedReadMethod() {
-        return mappedReadMethod;
+        return mappedReadMethodRef.get();
     }
 
     /**
@@ -240,7 +251,7 @@
      */
     public void setMappedReadMethod(Method mappedGetter)
             throws IntrospectionException {
-        mappedReadMethod = mappedGetter;
+        mappedReadMethodRef = new MappedMethodReference(mappedGetter);
         findMappedPropertyType();
     }
 
@@ -251,7 +262,7 @@
      * May return null if the property can't be written.
      */
     public Method getMappedWriteMethod() {
-        return mappedWriteMethod;
+        return mappedWriteMethodRef.get();
     }
 
     /**
@@ -263,7 +274,7 @@
      */
     public void setMappedWriteMethod(Method mappedSetter)
             throws IntrospectionException {
-        mappedWriteMethod = mappedSetter;
+        mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
         findMappedPropertyType();
     }
 
@@ -275,7 +286,9 @@
      */
     private void findMappedPropertyType() throws IntrospectionException {
         try {
-            mappedPropertyType = null;
+            Method mappedReadMethod  = getMappedReadMethod();
+            Method mappedWriteMethod = getMappedWriteMethod();
+            Class mappedPropertyType = null;
             if (mappedReadMethod != null) {
                 if (mappedReadMethod.getParameterTypes().length != 1) {
                     throw new IntrospectionException
@@ -302,6 +315,7 @@
                 }
                 mappedPropertyType = params[1];
             }
+            mappedPropertyTypeRef = new SoftReference(mappedPropertyType);
         } catch (IntrospectionException ex) {
             throw ex;
         }
@@ -404,4 +418,60 @@
                 "\" with " + parameterCount + " parameter(s) of matching types.");
     }
 
+    /**
+     * Holds a {@link Method} in a {@link SoftReference} so that it
+     * it doesn't prevent any ClassLoader being garbage collected, but
+     * tries to re-create the method if the method reference has been
+     * released.
+     *
+     * See http://issues.apache.org/jira/browse/BEANUTILS-291
+     */
+    private static class MappedMethodReference {
+        private String className;
+        private String methodName;
+        private Reference methodRef;
+        private Reference classRef;
+        private Reference writeParamTypeRef;
+        MappedMethodReference(Method m) {
+            if (m != null) {
+                className = m.getDeclaringClass().getName();
+                methodName = m.getName();
+                methodRef = new SoftReference(m);
+                classRef = new WeakReference(m.getDeclaringClass());
+                Class[] types = m.getParameterTypes();
+                if (types.length == 2) {
+                    writeParamTypeRef = new WeakReference(types[1]);
+                }
+            }
+        }
+        private Method get() {
+            if (methodRef == null) {
+                return null;
+            }
+            Method m = (Method)methodRef.get();
+            if (m == null) {
+                Class clazz = (Class)classRef.get();
+                if (clazz == null) {
+                    throw new RuntimeException("Method " + methodName + " for " +
+                            className + " could not be reconstructed - class reference has gone");
+                }
+                Class[] paramTypes = null;
+                if (writeParamTypeRef != null) {
+                    paramTypes = new Class[] {String.class, (Class)writeParamTypeRef.get()};
+                } else {
+                    paramTypes = STRING_CLASS_PARAMETER;
+                }
+                try {
+                    m = clazz.getMethod(methodName, paramTypes);
+                    // Un-comment following line for testing
+                    // System.out.println("Recreated Method " + methodName + " for " + className);
+                } catch (NoSuchMethodException e) {
+                    throw new RuntimeException("Method " + methodName + " for " +
+                            className + " could not be reconstructed - method not found");
+                }
+                methodRef = new SoftReference(m);
+            }
+            return m;
+        }
+    }
 }

Modified: commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MethodUtils.java
URL: http://svn.apache.org/viewvc/commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MethodUtils.java?rev=650215&r1=650214&r2=650215&view=diff
==============================================================================
--- commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MethodUtils.java (original)
+++ commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/MethodUtils.java Mon Apr 21 11:05:18 2008
@@ -18,6 +18,8 @@
 package org.apache.commons.beanutils;
 
 
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -1262,7 +1264,10 @@
      */
     private static Method getCachedMethod(MethodDescriptor md) {
         if (CACHE_METHODS) {
-            return (Method)cache.get(md);
+            Reference methodRef = (Reference)cache.get(md);
+            if (methodRef != null) {
+                return (Method)methodRef.get();
+            }
         }
         return null;
     }
@@ -1276,7 +1281,7 @@
     private static void cacheMethod(MethodDescriptor md, Method method) {
         if (CACHE_METHODS) {
             if (method != null) {
-                cache.put(md, method);
+                cache.put(md, new WeakReference(method));
             }
         }
     }

Modified: commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/PropertyUtilsBean.java
URL: http://svn.apache.org/viewvc/commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/PropertyUtilsBean.java?rev=650215&r1=650214&r2=650215&view=diff
==============================================================================
--- commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/PropertyUtilsBean.java (original)
+++ commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/PropertyUtilsBean.java Mon Apr 21 11:05:18 2008
@@ -30,6 +30,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.WeakHashMap;
 
 import org.apache.commons.beanutils.expression.DefaultResolver;
 import org.apache.commons.beanutils.expression.Resolver;
@@ -120,8 +121,8 @@
      * The cache of PropertyDescriptor arrays for beans we have already
      * introspected, keyed by the java.lang.Class of this object.
      */
-    private FastHashMap descriptorsCache = null;
-    private FastHashMap mappedDescriptorsCache = null;
+    private Map descriptorsCache = new WeakHashMap();
+    private Map mappedDescriptorsCache = new WeakHashMap();
     private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0];
     private static final Class[] LIST_CLASS_PARAMETER = new Class[] {java.util.List.class};
     
@@ -135,10 +136,6 @@
     
     /** Base constructor */
     public PropertyUtilsBean() {
-        descriptorsCache = new FastHashMap();
-        descriptorsCache.setFast(true);
-        mappedDescriptorsCache = new FastHashMap();
-        mappedDescriptorsCache.setFast(true);
     }
 
 

Modified: commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/WrapDynaClass.java
URL: http://svn.apache.org/viewvc/commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/WrapDynaClass.java?rev=650215&r1=650214&r2=650215&view=diff
==============================================================================
--- commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/WrapDynaClass.java (original)
+++ commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/WrapDynaClass.java Mon Apr 21 11:05:18 2008
@@ -19,6 +19,8 @@
 
 
 import java.beans.PropertyDescriptor;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -61,7 +63,8 @@
      */
     private WrapDynaClass(Class beanClass) {
 
-        this.beanClass = beanClass;
+        this.beanClassRef = new SoftReference(beanClass);
+        this.beanClassName = beanClass.getName();
         introspect();
 
     }
@@ -69,10 +72,21 @@
 
     // ----------------------------------------------------- Instance Variables
 
+    /**
+     * Name of the JavaBean class represented by this WrapDynaClass.
+     */
+    private String beanClassName = null;
+
+    /**
+     * Reference to the JavaBean class represented by this WrapDynaClass.
+     */
+    private Reference beanClassRef = null;
 
     /**
      * The JavaBean <code>Class</code> which is represented by this
      * <code>WrapDynaClass</code>.
+     *
+     * @deprecated No longer initialized, use getBeanClass() method instead
      */
     protected Class beanClass = null;
 
@@ -206,6 +220,14 @@
 
     // ------------------------------------------------------ DynaClass Methods
 
+    /**
+     * Return the class of the underlying wrapped bean.
+     *
+     * @return the class of the underlying wrapped bean
+     */
+    protected Class getBeanClass() {
+        return (Class)beanClassRef.get();
+    }
 
     /**
      * Return the name of this DynaClass (analogous to the
@@ -217,7 +239,7 @@
      */
     public String getName() {
 
-        return (this.beanClass.getName());
+        return beanClassName;
 
     }
 
@@ -289,7 +311,7 @@
     public DynaBean newInstance()
             throws IllegalAccessException, InstantiationException {
 
-        return new WrapDynaBean(beanClass.newInstance());
+        return new WrapDynaBean(getBeanClass().newInstance());
 
     }
 
@@ -353,6 +375,7 @@
     protected void introspect() {
 
         // Look up the property descriptors for this bean class
+        Class beanClass = getBeanClass();
         PropertyDescriptor[] regulars =
                 PropertyUtils.getPropertyDescriptors(beanClass);
         if (regulars == null) {

Modified: commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/locale/LocaleConvertUtilsBean.java
URL: http://svn.apache.org/viewvc/commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/locale/LocaleConvertUtilsBean.java?rev=650215&r1=650214&r2=650215&view=diff
==============================================================================
--- commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/locale/LocaleConvertUtilsBean.java (original)
+++ commons/proper/beanutils/trunk/src/java/org/apache/commons/beanutils/locale/LocaleConvertUtilsBean.java Mon Apr 21 11:05:18 2008
@@ -37,7 +37,12 @@
 import java.lang.reflect.Array;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
 
 /**
  * <p>Utility methods for converting locale-sensitive String scalar values to objects of the
@@ -454,7 +459,7 @@
      */
     protected FastHashMap create(Locale locale) {
 
-        FastHashMap converter = new FastHashMap();
+        FastHashMap converter = new WeakFastHashMap();
         converter.setFast(false);
 
         converter.put(BigDecimal.class, new BigDecimalLocaleConverter(locale, applyLocalized));
@@ -491,5 +496,68 @@
         converter.setFast(true);
 
         return converter;
+    }
+
+    /**
+     * FastHashMap implementation that uses WeakReferences to overcome
+     * memory leak problems.
+     *
+     * This is a hack to retain binary compatibility with previous
+     * releases (where FastHashMap is exposed in the API), but
+     * use WeakHashMap to resolve memory leaks.
+     */
+    private static class WeakFastHashMap extends FastHashMap {
+
+        private Map fastMap = new WeakHashMap();
+        private Map slowMap = Collections.synchronizedMap(fastMap);
+
+        private WeakFastHashMap() {
+            super(0);
+        }
+        public void clear() {
+            getMap().clear();
+        }
+        public boolean containsKey(Object key) {
+            return getMap().containsKey(key);
+        }
+        public boolean containsValue(Object value) {
+            return getMap().containsValue(value);
+        }
+        public Set entrySet() {
+            return getMap().entrySet();
+        }
+        public boolean equals(Object o) {
+            return getMap().equals(o);
+        }
+        public Object get(Object key) {
+            return getMap().get(key);
+        }
+        public int hashCode() {
+            return getMap().hashCode();
+        }
+        public boolean isEmpty() {
+            return getMap().isEmpty();
+        }
+        public Set keySet() {
+            return getMap().keySet();
+        }
+        public Object put(Object key, Object value) {
+            return getMap().put(key, value);
+        }
+        public void putAll(Map m) {
+            getMap().putAll(m);
+        }
+        public Object remove(Object key) {
+            return getMap().remove(key);
+        }
+        public int size() {
+            return getMap().size();
+        }
+        public Collection values() {
+            return getMap().values();
+        }
+        private Map getMap() {
+            return (getFast() ? fastMap : slowMap);
+        }
     }
 }

Modified: commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/memoryleaktests/MemoryLeakTestCase.java
URL: http://svn.apache.org/viewvc/commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/memoryleaktests/MemoryLeakTestCase.java?rev=650215&r1=650214&r2=650215&view=diff
==============================================================================
--- commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/memoryleaktests/MemoryLeakTestCase.java (original)
+++ commons/proper/beanutils/trunk/src/test/org/apache/commons/beanutils/memoryleaktests/MemoryLeakTestCase.java Mon Apr 21 11:05:18 2008
@@ -21,11 +21,13 @@
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.Locale;
+import java.util.StringTokenizer;
 
 import junit.framework.TestCase;
 
 import org.apache.commons.beanutils.BeanUtilsBean;
 import org.apache.commons.beanutils.ConvertUtils;
+import org.apache.commons.beanutils.MappedPropertyDescriptor;
 import org.apache.commons.beanutils.MethodUtils;
 import org.apache.commons.beanutils.PropertyUtils;
 import org.apache.commons.beanutils.WrapDynaBean;
@@ -48,6 +50,9 @@
      * Tests that PropertyUtilsBean's descriptorsCache doesn't cause a memory leak.
      */
     public void testPropertyUtilsBean_descriptorsCache_memoryLeak() throws Exception {
+        if (isPre15JVM()) {
+            return;
+        }
 
         // Clear All BeanUtils caches before the test
         clearAllBeanUtilsCaches();
@@ -94,6 +99,9 @@
      * Tests that PropertyUtilsBean's mappedDescriptorsCache doesn't cause a memory leak.
      */
     public void testPropertyUtilsBean_mappedDescriptorsCache_memoryLeak() throws Exception {
+        if (isPre15JVM()) {
+            return;
+        }
 
         // Clear All BeanUtils caches before the test
         clearAllBeanUtilsCaches();
@@ -143,6 +151,50 @@
     }
 
     /**
+     * Tests that MappedPropertyDescriptor can re-create the Method reference after it
+     * has been garbage collected.
+     */
+    public void testMappedPropertyDescriptor_MappedMethodReference() throws Exception {
+
+        // Clear All BeanUtils caches before the test
+        clearAllBeanUtilsCaches();
+
+        String className = "org.apache.commons.beanutils.memoryleaktests.pojotests.SomeMappedPojo";
+        ClassLoader loader = newClassLoader();
+        Class beanClass    = loader.loadClass(className);
+        Object bean        = beanClass.newInstance();
+        // -----------------------------------------------------------------------------
+
+        // Sanity checks only
+        assertNotNull("ClassLoader is null", loader);
+        assertNotNull("BeanClass is null", beanClass);
+        assertNotSame("ClassLoaders should be different..", getClass().getClassLoader(), beanClass.getClassLoader());
+        assertSame("BeanClass ClassLoader incorrect", beanClass.getClassLoader(), loader);
+
+        MappedPropertyDescriptor descriptor = new MappedPropertyDescriptor("mappedProperty", beanClass);
+        assertNotNull("1-Read Method null", descriptor.getMappedReadMethod());
+        assertNotNull("1-Write Method null", descriptor.getMappedWriteMethod());
+        assertEquals("1-Read Method name", "getMappedProperty", descriptor.getMappedReadMethod().getName());
+        assertEquals("1-Read Write name", "setMappedProperty", descriptor.getMappedWriteMethod().getName());
+
+        forceGarbageCollection(); /* Try to force the garbage collector to run by filling up memory */
+
+        // The aim of this test is to check the functinality in MappedPropertyDescriptor which
+        // re-creates the Method references after they have been garbage collected. However theres no
+        // way of knowing the method references were garbage collected and that code was run, except by
+        // un-commeting the System.out statement in MappedPropertyDescriptor's MappedMethodReference's
+        // get() method.
+
+        assertNotNull("1-Read Method null", descriptor.getMappedReadMethod());
+        assertNotNull("1-Write Method null", descriptor.getMappedWriteMethod());
+        assertEquals("1-Read Method name", "getMappedProperty", descriptor.getMappedReadMethod().getName());
+        assertEquals("1-Read Write name", "setMappedProperty", descriptor.getMappedWriteMethod().getName());
+
+        // Clear All BeanUtils caches after the test
+        clearAllBeanUtilsCaches();
+    }
+
+    /**
      * Tests that MethodUtils's cache doesn't cause a memory leak.
      */
     public void testMethodUtils_cache_memoryLeak() throws Exception {
@@ -192,6 +244,9 @@
      * Tests that WrapDynaClass's dynaClasses doesn't cause a memory leak.
      */
     public void testWrapDynaClass_dynaClasses_memoryLeak() throws Exception {
+        if (isPre15JVM()) {
+            return;
+        }
 
         // Clear All BeanUtils caches before the test
         clearAllBeanUtilsCaches();
@@ -426,5 +481,22 @@
         System.out.println(jvmti.exploreClassReferences(className, 8, true, true, true, false, false));
         System.out.println(" ----------------" + test + " END ------------------");
         */
+    }
+
+    /**
+     * Test for JDK 1.5
+     */
+    private boolean isPre15JVM() {
+        String version = System.getProperty("java.specification.version");
+        StringTokenizer tokenizer = new StringTokenizer(version,".");
+        if (tokenizer.nextToken().equals("1")) {
+            String minorVersion = tokenizer.nextToken();
+            if (minorVersion.equals("0")) return true;
+            if (minorVersion.equals("1")) return true;
+            if (minorVersion.equals("2")) return true;
+            if (minorVersion.equals("3")) return true;
+            if (minorVersion.equals("4")) return true;
+        }
+        return false;
     }
 }