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    package org.apache.commons.beanutils.bugs;
018    
019    import java.beans.IntrospectionException;
020    import java.lang.ref.SoftReference;
021    import java.lang.reflect.Method;
022    import java.net.MalformedURLException;
023    import java.net.URL;
024    import java.net.URLClassLoader;
025    
026    import junit.framework.TestCase;
027    
028    import org.apache.commons.beanutils.MappedPropertyDescriptor;
029    import org.apache.commons.beanutils.memoryleaktests.MemoryLeakTestCase;
030    
031    /**
032     * Test case for Jira issue# BEANUTILS-347.
033     * <br/>
034     * See https://issues.apache.org/jira/browse/BEANUTILS-347
035     *
036     * @version $Revision: 806915 $ $Date: 2009-08-23 01:50:23 +0100 (Sun, 23 Aug 2009) $
037     */
038    public class Jira347TestCase extends TestCase {
039        
040        /**
041         * Tests that MappedPropertyDescriptor does not throw an exception while re-creating a Method reference after it
042         * has been garbage collected under the following circumstances.
043         * - a class has a property 'mappedProperty'
044         * - there is no getter for this property
045         * - there is method setMappedProperty(MappedPropertyTestBean,MappedPropertyTestBean)
046         * 
047         * In this case getMappedWriteMethod should not throw an exception after the method reference has been garbage collected.
048         * It is essential that in both cases the same method is returned or in both cases null. 
049         * If the constructor of the MappedPropertyDescriptor would recognize the situation (of the first param not being of type String)
050         * this would be fine as well.          
051         */
052        public void testMappedPropertyDescriptor_AnyArgsProperty() throws Exception {
053            String className = "org.apache.commons.beanutils.MappedPropertyTestBean";
054            ClassLoader loader = newClassLoader();
055            Class beanClass    = loader.loadClass(className);
056            Object bean        = beanClass.newInstance();
057            // -----------------------------------------------------------------------------
058    
059            // Sanity checks only
060            assertNotNull("ClassLoader is null", loader);
061            assertNotNull("BeanClass is null", beanClass);
062            assertNotSame("ClassLoaders should be different..", getClass().getClassLoader(), beanClass.getClassLoader());
063            assertSame("BeanClass ClassLoader incorrect", beanClass.getClassLoader(), loader);
064    
065            // now start the test
066            MappedPropertyDescriptor descriptor = null;
067            try {
068              descriptor = new MappedPropertyDescriptor("anyMapped", beanClass);
069            }
070            catch (IntrospectionException e) {
071              // this would be fine as well
072            }
073            
074            if (descriptor != null) {
075                String m1 = getMappedWriteMethod(descriptor);
076                 forceGarbageCollection();
077                 try {
078                     String m2 = getMappedWriteMethod(descriptor);
079                     assertEquals("Method returned post garbage collection differs from Method returned prior to gc", m1, m2);
080                 }
081                 catch (RuntimeException e) {
082                     fail("getMappedWriteMethod threw an exception after garbage collection " + e);
083                 }
084            }
085        }
086        
087        /**
088         * Retrieves the string representation of the mapped write method
089         * for the given descriptor.
090         * This conversion is needed as there must not be strong reference to the 
091         * Method object outside of this method as otherwise the garbage collector will not
092         * clean up the soft reference within the MappedPropertyDescriptor. 
093         * 
094         * @return the string representation or null if mapped write method does not exist
095         */
096        private String getMappedWriteMethod(MappedPropertyDescriptor descriptor) {
097            Method m = descriptor.getMappedWriteMethod();
098            return m == null ? null : m.toString();
099        }
100        
101        /**
102         * Try to force the garbage collector to run by filling up memory and calling System.gc().
103         */
104        private void forceGarbageCollection() throws Exception {
105            // Fill up memory
106            SoftReference ref = new SoftReference(new Object());
107            int count = 0;
108            while(ref.get() != null && count++ < 5) {
109                java.util.ArrayList list = new java.util.ArrayList();
110                try {
111                    long i = 0;
112                    while (true && ref.get() != null) {
113                        list.add("A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String " + (i++));
114                    }
115                } catch (Throwable ignored) {
116                }
117                list.clear();
118                list = null;
119                // System.out.println("Count " + count + " : " + getMemoryStats());
120                System.gc(); 
121                Thread.sleep(1000);
122            }
123            // System.out.println("After GC: " + getMemoryStats());
124            
125            if (ref.get() != null) {
126                throw new IllegalStateException("Your JVM is not releasing SoftReference, try running the testcase with less memory (-Xmx)");
127            }
128        }
129        
130        /**
131         * Create a new class loader instance.
132         */
133        private static URLClassLoader newClassLoader() throws MalformedURLException {
134    
135            String dataFilePath = MemoryLeakTestCase.class.getResource("pojotests").getFile();
136            //System.out.println("dataFilePath: " + dataFilePath);
137            String location = "file://" + dataFilePath.substring(0,dataFilePath.length()-"org.apache.commons.beanutils.memoryleaktests.pojotests".length());
138            //System.out.println("location: " + location);
139    
140            StringBuffer newString = new StringBuffer();
141            for (int i=0;i<location.length();i++) {
142                if (location.charAt(i)=='\\') {
143                    newString.append("/");
144                } else {
145                    newString.append(location.charAt(i));
146                }
147            }
148            String classLocation = newString.toString();
149            //System.out.println("classlocation: " + classLocation);
150    
151            URLClassLoader theLoader = URLClassLoader.newInstance(new URL[]{new URL(classLocation)},null);
152            return theLoader;
153        }
154    }