| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.beanutils2.bugs; |
| |
| import java.beans.IntrospectionException; |
| import java.lang.ref.SoftReference; |
| import java.lang.reflect.Method; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.ArrayList; |
| |
| import org.apache.commons.beanutils2.MappedPropertyDescriptor; |
| import org.apache.commons.beanutils2.memoryleaktests.MemoryLeakTestCase; |
| |
| import junit.framework.TestCase; |
| |
| /** |
| * Test case for Jira issue# BEANUTILS-347. |
| * |
| * @version $Id$ |
| * @see <a href="https://issues.apache.org/jira/browse/BEANUTILS-347">https://issues.apache.org/jira/browse/BEANUTILS-347</a> |
| */ |
| public class Jira347TestCase extends TestCase { |
| |
| /** |
| * Tests that MappedPropertyDescriptor does not throw an exception while re-creating a Method reference after it |
| * has been garbage collected under the following circumstances. |
| * - a class has a property 'mappedProperty' |
| * - there is no getter for this property |
| * - there is method setMappedProperty(MappedPropertyTestBean,MappedPropertyTestBean) |
| * |
| * In this case getMappedWriteMethod should not throw an exception after the method reference has been garbage collected. |
| * It is essential that in both cases the same method is returned or in both cases null. |
| * If the constructor of the MappedPropertyDescriptor would recognize the situation (of the first param not being of type String) |
| * this would be fine as well. |
| */ |
| public void testMappedPropertyDescriptor_AnyArgsProperty() throws Exception { |
| final String className = "org.apache.commons.beanutils2.MappedPropertyTestBean"; |
| final ClassLoader loader = newClassLoader(); |
| final Class<?> beanClass = loader.loadClass(className); |
| 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); |
| |
| // now start the test |
| MappedPropertyDescriptor descriptor = null; |
| try { |
| descriptor = new MappedPropertyDescriptor("anyMapped", beanClass); |
| } |
| catch (final IntrospectionException e) { |
| // this would be fine as well |
| } |
| |
| if (descriptor != null) { |
| final String m1 = getMappedWriteMethod(descriptor); |
| forceGarbageCollection(); |
| try { |
| final String m2 = getMappedWriteMethod(descriptor); |
| assertEquals("Method returned post garbage collection differs from Method returned prior to gc", m1, m2); |
| } |
| catch (final RuntimeException e) { |
| fail("getMappedWriteMethod threw an exception after garbage collection " + e); |
| } |
| } |
| } |
| |
| /** |
| * Retrieves the string representation of the mapped write method |
| * for the given descriptor. |
| * This conversion is needed as there must not be strong reference to the |
| * Method object outside of this method as otherwise the garbage collector will not |
| * clean up the soft reference within the MappedPropertyDescriptor. |
| * |
| * @return the string representation or null if mapped write method does not exist |
| */ |
| private String getMappedWriteMethod(final MappedPropertyDescriptor descriptor) { |
| final Method m = descriptor.getMappedWriteMethod(); |
| return m == null ? null : m.toString(); |
| } |
| |
| /** |
| * Try to force the garbage collector to run by filling up memory and calling System.gc(). |
| */ |
| private void forceGarbageCollection() throws Exception { |
| // Fill up memory |
| final SoftReference<Object> ref = new SoftReference<>(new Object()); |
| int count = 0; |
| while(ref.get() != null && count++ < 5) { |
| ArrayList<Object> list = new ArrayList<>(); |
| try { |
| long i = 0; |
| while (true && ref.get() != null) { |
| 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++); |
| } |
| } catch (final Throwable ignored) { |
| } |
| list.clear(); |
| list = null; |
| // System.out.println("Count " + count + " : " + getMemoryStats()); |
| System.gc(); |
| Thread.sleep(1000); |
| } |
| // System.out.println("After GC: " + getMemoryStats()); |
| |
| if (ref.get() != null) { |
| throw new IllegalStateException("Your JVM is not releasing SoftReference, try running the testcase with less memory (-Xmx)"); |
| } |
| } |
| |
| /** |
| * Create a new class loader instance. |
| */ |
| private static URLClassLoader newClassLoader() throws MalformedURLException { |
| |
| final String dataFilePath = MemoryLeakTestCase.class.getResource("pojotests").getFile(); |
| //System.out.println("dataFilePath: " + dataFilePath); |
| final String location = "file://" + dataFilePath.substring(0,dataFilePath.length()-"org.apache.commons.beanutils2.memoryleaktests.pojotests".length()); |
| //System.out.println("location: " + location); |
| |
| final StringBuilder newString = new StringBuilder(); |
| for (int i=0;i<location.length();i++) { |
| if (location.charAt(i)=='\\') { |
| newString.append("/"); |
| } else { |
| newString.append(location.charAt(i)); |
| } |
| } |
| final String classLocation = newString.toString(); |
| //System.out.println("classlocation: " + classLocation); |
| |
| final URLClassLoader theLoader = URLClassLoader.newInstance(new URL[]{new URL(classLocation)},null); |
| return theLoader; |
| } |
| } |