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 }