/* * TestChartPanelMemoryLeak.java of project jchart2d, a * test for a memory leak detected by Pieter-Jan Busschaert. * Copyright (c) 2007 Achim Westermann, created on 23:52:11. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA* * If you modify or optimize the code in a useful way please let me know. * Achim.Westermann@gmx.de * */ package info.monitorenter.gui.chart.layouts; import info.monitorenter.gui.chart.Chart2D; import info.monitorenter.gui.chart.ITrace2D; import info.monitorenter.gui.chart.traces.Trace2DLtd; import info.monitorenter.gui.chart.traces.Trace2DSimple; import info.monitorenter.gui.chart.views.ChartPanel; import info.monitorenter.util.math.MathUtil; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListenerProxy; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.swing.JFrame; import javax.swing.JPanel; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; /** * * TestChartPanelMemoryLeak.java of project jchart2d, a test for a memory leak * detected by Pieter-Jan Busschaert. * <p> * * @author Pieter-Jan Busschaert * @author Holger Brandl * * * @version $Revision: 1.5 $ */ public class TestChartPanel extends TestCase { /** * Main debug / profiler entry, do not drop - it's used from build xml to * profile. * <p> * * @param args * ignored. * * @throws Exception * if sth. goes wrong. */ public static void main(final String[] args) throws Exception { TestChartPanel test = new TestChartPanel(TestChartPanel.class.getName()); test.setUp(); test.testMemoryLeakAddRemoveTrace(); test.tearDown(); } /** * Test suite for this test class. * <p> * * @return the test suite */ public static Test suite() { TestSuite suite = new TestSuite(); suite.setName(TestChartPanel.class.getName()); suite.addTest(new TestChartPanel("testNoTraces")); suite.addTest(new TestChartPanel("testManyTraces")); suite.addTest(new TestChartPanel("testMemoryLeakAddRemoveTrace")); return suite; } /** * Default constructor with test name. * <p> * * @param testName * the test name. */ public TestChartPanel(final String testName) { super(testName); } /** * Creates a <code>{@link ChartPanel}</code> with a * <code>{@link Chart2D}</code> that has many traces and shows it for some * seconds. * <p> * Warning: For now this is only a visual test, especially for seeing if the * space for the labels works correctly. This test will never fail if sth. * is wrong. So watch it! * <p> * */ public void testManyTraces() { Chart2D chart = new Chart2D(); // add many traces: ITrace2D trace; for (int i = 0; i < 10; i++) { trace = new Trace2DSimple(); chart.addTrace(trace); for (int j = 0; j < 20; j++) { trace.addPoint(j, (Math.random() + 1) * j); } trace.setName("trace-" + i); } JPanel p = new JPanel(); p.setLayout(new BorderLayout()); p.add(new ChartPanel(chart)); JFrame frame = new JFrame(); frame.getContentPane().add(p); // frame.validate(); frame.setSize(new Dimension(400, 400)); frame.setVisible(true); try { Thread.sleep(4000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } frame.setVisible(false); frame.dispose(); } /** * Tests a memory leak that was found in jchart2d-1.1.0 and was related to * adding and removing traces with charts wrapped in a {@link ChartPanel}. * <p> * */ public void testMemoryLeakAddRemoveTrace() { Chart2D chart = new Chart2D(); ChartPanel chartPanel = new ChartPanel(chart); Map<String, Integer> listeners2CountBeforeAddTrace = TestChartPanel .getPropertyChangeListenerStatisticsForChartPerProperty(chart); Map<Class<?>, Integer>listenerTypes2CountBeforeAddTrace = getPropertyChangeListenerStatisticsForChartPerListenerInstance(chart); // this call is for fooling checkstyle (unused local variable): chartPanel.setEnabled(false); ITrace2D trace; for (int i = 0; i < 20; i++) { System.out.print("Adding big trace " + i + " (16383 points)..."); trace = new Trace2DLtd(16383); chart.addTrace(trace); System.out.println(" done!"); try { if (i % 20 == 0) { System.out .print("Running garbage collection and finalization (I know: never do this)..."); System.gc(); System.runFinalization(); System.out.println(" done!"); System.out .println("chart.getPropertyChangeListeners().length: " + chart.getPropertyChangeListeners().length); } Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(System.err); } chart.removeTrace(trace); } System.runFinalization(); System.gc(); chart.setBackground(Color.LIGHT_GRAY); // reporting / analysis Map<String, Integer> listeners2CountAfterAddTrace = TestChartPanel .getPropertyChangeListenerStatisticsForChartPerProperty(chart); Map<Class<?>, Integer>listenerTypes2CountAfterAddTrace = getPropertyChangeListenerStatisticsForChartPerListenerInstance(chart); System.out.println(); System.out.println("Before add trace:"); for (Map.Entry<String, Integer> entry : listeners2CountBeforeAddTrace .entrySet()) { System.out.println(entry.getKey() + " : " + entry.getValue()); } System.out.println(); System.out.println("After add trace:"); for (Map.Entry<String, Integer> entry : listeners2CountAfterAddTrace .entrySet()) { System.out.println(entry.getKey() + " : " + entry.getValue()); } /* * Counting classes. This is without assertions as the following tests for properties in question will also fail if * something is wrong here: */ Integer amountBefore,amountAfter; for(Class<?> listenerClass:listenerTypes2CountAfterAddTrace.keySet()) { amountBefore = listenerTypes2CountBeforeAddTrace.get(listenerClass); amountAfter = listenerTypes2CountAfterAddTrace.get(listenerClass); if(amountBefore == null) { amountBefore = Integer.valueOf(0); } if(amountAfter.intValue() > amountBefore.intValue()) { System.err.println("Listener instances of type " + listenerClass.getName() + " on the chart have increased from " + amountBefore + " to " + amountAfter + "."); } else { System.out.println("Listener instances of type " + listenerClass.getName() + " on the chart remained constant before and after adding trace at " + amountBefore + "."); } } /* * Now compare the set of listeners before and after adding and removing the traces. */ Set<String> distinctListenedPropertiesBeforeAddRemoveTrace = listeners2CountBeforeAddTrace.keySet(); Set<String> distinctListenedPropertiesAfterAddRemoveTrace = listeners2CountBeforeAddTrace.keySet(); for(String propertyBefore: distinctListenedPropertiesBeforeAddRemoveTrace){ assertTrue("Property "+propertyBefore+" was listened on (on the chart) before adding and removing a trace, but not afterwards." , distinctListenedPropertiesAfterAddRemoveTrace.contains(propertyBefore)); } for(String propertyAfter : distinctListenedPropertiesAfterAddRemoveTrace){ assertTrue("Property "+propertyAfter+" is listened on (on the chart) after adding and removing a trace, but not before." , distinctListenedPropertiesBeforeAddRemoveTrace.contains(propertyAfter)); } /* * Now compare the amount of listeners for every property before and * after adding and removing the traces. */ for(String property:distinctListenedPropertiesBeforeAddRemoveTrace) { amountBefore = listeners2CountBeforeAddTrace.get(property); amountAfter = listeners2CountAfterAddTrace.get(property); assertEquals( "The amount of listeners on property \"" + property + "\" on the chart before adding and removing traces is not equal.", amountBefore, amountAfter); } chart.destroy(); } /** * Creates a <code>{@link ChartPanel}</code> with a * <code>{@link Chart2D}</code> that has no traces and shows it for some * seconds. * <p> * */ public void testNoTraces() { Chart2D chart = new Chart2D(); JPanel p = new JPanel(); p.setLayout(new BorderLayout()); p.add(new ChartPanel(chart)); JFrame frame = new JFrame(); frame.getContentPane().add(p); frame.validate(); frame.setSize(new Dimension(400, 400)); frame.setVisible(true); try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } frame.setVisible(false); frame.dispose(); } /** * Helper method that analyzes the * <code>{@link PropertyChangeListener}</code> instances of a given chart: * it returns a map that contains the property to listen on (key) along with * the count of listeners of that property (value). * <p> * This may be used to find out, if the amount of listeners on a certain * property increases in-between invocation of a method that affects the * chart. * <p> * * @param chart * the chart to analyze the listeners of. * * @return a map that contains the property to listen on (key) along with * the count of listeners of that property (value). */ private static Map<String, Integer> getPropertyChangeListenerStatisticsForChartPerProperty( final Chart2D chart) { Integer count; PropertyChangeListener[] propertyChangeListeners = chart .getPropertyChangeListeners(); String property; Map<String, Integer> props2count = new HashMap<String, Integer>(); PropertyChangeListenerProxy realTypedListener; for (PropertyChangeListener listener : propertyChangeListeners) { // count the properties: realTypedListener = (PropertyChangeListenerProxy) listener; property = realTypedListener.getPropertyName(); count = props2count.get(property); count = MathUtil.increment(count); props2count.put(property, count); } return props2count; } /** * Helper method that analyzes the * <code>{@link PropertyChangeListener}</code> instances of a given chart: * it returns a map that contains listener <code>{@link Class}</code>(key) * along with the count of instances registered on the given chart (value). * <p> * This may be used to find out the implementation class that messes up, if * the amount of listeners of that type increases in-between invocation of a * method that affects the chart. * <p> * * @param chart * the chart to analyze the listeners of. * * @return a map that contains listener <code>{@link Class}</code>(key) * along with the count of instances registered on the given chart * (value). */ private static Map<Class<?>, Integer> getPropertyChangeListenerStatisticsForChartPerListenerInstance( final Chart2D chart) { Class<?> clazz; Integer count; PropertyChangeListener[] propertyChangeListeners = chart .getPropertyChangeListeners(); Map<Class<?>, Integer> classes2count = new HashMap<Class<?>, Integer>(); for (int i = propertyChangeListeners.length - 1; i >= 0; i--) { clazz = ((PropertyChangeListenerProxy)propertyChangeListeners[i]).getListener().getClass(); count = null; // count the listener classes: count = classes2count.get(clazz); count = MathUtil.increment(count); classes2count.put(clazz, count); } for (Map.Entry<Class<?>, Integer> entry : classes2count.entrySet()) { System.out.println((entry.getKey()).getName() + " : " + entry.getValue()); } return classes2count; } }