New jtimeseries project on sourceforge

May 20th, 2010 Nick Posted in java, jtimeseries, web, xml No Comments »

For the last year and a half I have been hard at work at a new open source project jtimeseries.

This has finally made it onto sourceforge!

So what can you do with this jtimeseries thing?

Quite a lot, already. Let me explain how this project was conceived….

Initially, I was looking for a java based API which I could use to capture metrics for a Swing application I was working on. Rather than store the raw values I was collecting, I needed a way to capture, for example, the mean value of a certain measurement over a time period (e.g. the mean price latency over the last five minutes, or the 90th percentile heap memory). As well as capturing and storing the stats, I also needed ui visualizer components to enable viewing of the data, and wanted to also show the stats collected via an embedded web server in the application.

how about storing the data?

A logical extension once the core API was up and running was to provide a lightweight database component to persist the timeseries data on the server side, in a round robin file format (noting here that there is no java api for rddtool presently, which gave me ample license to write one myself I think!). The server can store stats from multiple sources – and you can subscribe to view the series from the ui.

collecting stats from jmx

The other really neat feature of the database, is that it can be configured to read in metrics data directly via the jmx management service of a running java app. This means that you can capture details, such as the memory and cpu load of your other java components without having to change their code and re-release them! You just need to have the jvm jmx management feature enabled, so that you can point a jconsole at the app and see values.

New release

So that’s how it all came about. We now have a stable release, 1.0.10, which you can find here

At present I am consulting for a company which now has two instances of the timeseries server, currently maintaining nearly 50,000 time series, which provide stats on the performance of dozens of our production and UAT components going back over a few months. The benefits of this are massive. It’s easy to spot performance problems before they become a major issue. Most importantly, before a release takes place, it’s easy to see from the stats whether a new component version has worse performance than its predecessor – without even having to fire up the profiler.

I’ll be adding more documentation on jtimeseries over the next couple of weeks



AddThis Social Bookmark Button

How to find the pid of a java process from code

March 3rd, 2010 Nick Posted in java No Comments »

This blog has been quiet for a while, that’s about to change, but in the meantime I came across a very interesting way to get hold of the PID for your running java process from code, which I thought I’d put up straight away.

This makes use of the Management JMX beans to find the information, but the good news is that it seems you don’t have to do anything special, such as add VM system properties, to make it work.

Not sure this will work on every OS/vm, but on the ones I have tried there’s no problem.
Seems to work on both Windows on Linux

For me, the below produces:
ProcessName=[1136@iblongsw253311]
PID=[1136]

public static void main(String[] args) {
        try {
            RuntimeMXBean mx = ManagementFactory.getRuntimeMXBean();
            String s = mx.getName();
            System.out.println("ProcessName=["+s+"]");
            String[] pidAndHost = s.split("@");
            if ( pidAndHost.length == 2) {
                int pid = Integer.valueOf(pidAndHost[0]);
                System.out.println("PID=[" + pid + "]");
            }
        }
        catch( Exception e) {
            System.out.println("Whoops, can't find a PID");
        }
    }
AddThis Social Bookmark Button

JTable setRowHeight causes slow repainting

June 6th, 2009 Nick Posted in java, swing 5 Comments »

I’ve spent way too much time in the last few months fixing and optimising table rendering performance for a high performance Java application I’m working on at the moment. The problems were particularly hard to find becuase the app supports customization of almost every aspect of JTable UI, with custom rendering for rows, columns and individual cells according to user preferences. Some users would get terrible performance on some days, which would seem to disappear the next.

Some of the issues were hardware and graphics driver related, and there have been various minor and major victories along the way (more about these later), but just yesterday I found a very significant defect in JTable.tableChanged().

The problem occurs when you call setRowHeight(int row, int height) on a JTable, to set a customised table row height. You only need to do this once, and that causes JTable to initialize its internal rowModel to maintain the custom row heights. From that point onwards, any update to the TableModel (even if it is just an update event for a single cell()) will be processed by JTable.tableChanged() in such a way that the entire table component is marked as dirty. So this forces the RepaintManager to repaint the entire table every time, even for single cell updates.

The point at which everything appears to go wrong is at JTable line 4396 (in jdk 1.6.0_14) and line 3021 (in jdk 1.5.0_17), which is part of the processing for TableModel update events

// The totalRowHeight calculated below will be incorrect if
// there are variable height rows. Repaint the visible region,
// but don't return as a revalidate may be necessary as well.
if (rowModel != null) {
    repaint();
}

Essentially, if there is an internal rowModel set up (i.e the user has configured some custom row heights), then the call to repaint() in this snippit causes the entire JTable to be marked as dirty – so the RepaintManager repaints the entire table component rather than just the cells affected by an update.

The effect of this is dramatic. When I run my test class (included below):

***** Running test for table Standard JTable under 1.5.0_19
With all rows default height total time spent repainting table 24 millis
Setting custom row height for one row
With one custom row height total time spent repainting table 5427 millis
Thats 226 times slower than it should be to paint the table!

Here is a Fix for the issue

I’m pretty surprised this issue hasn’t been fixed already, since custom row heights can’t be all that rare. I have produced a workaround by extending JTable to override the tableChanged() handling for update events. Fixing this has solved major problems for our users whose tables were configured to require custom row heights.

This works fine for for me in 1.5.*.
It will also work for jdk 1.6.* but it does not support the 1.6 table row sorting (you’d need to find a way to fire sortedTableChanged() events to the sortManager to do this when the updates are processed)

   import javax.swing.table.TableModel;
import javax.swing.event.TableModelEvent;
import javax.swing.*;
import java.awt.*;


/**
 * Extends JTable to fix the broken repainting for updates when there are custom height rows
 * See http://www.objectdefinitions.com/odblog/2009/jtable-setrowheight-causes-slow-repainting/
 *
 * This fix does not support row sorting in jdk1.6 -
 * to support that would require firing sortedTableChanged to sortManager
 */

public class FixedForRowHeightJTable extends JTable {

    public FixedForRowHeightJTable() {
    }

    public FixedForRowHeightJTable(TableModel tableModel) {
        super(tableModel);
    }

    public void tableChanged(TableModelEvent e) {
        //if just an update, and not a data or structure changed event or an insert or delete, use the fixed row update handling
        //otherwise call super.tableChanged to let the standard JTable update handling manage it
        if ( e != null &&
            e.getType() == TableModelEvent.UPDATE &&
            e.getFirstRow() != TableModelEvent.HEADER_ROW &&
            e.getLastRow() != Integer.MAX_VALUE) {

            handleRowUpdate(e);
        } else {
            super.tableChanged(e);
        }
    }

    /**
     * This borrows most of the logic from the superclass handling of update events, but changes the calculation of the height
     * for the dirty region to provide proper handling for repainting custom height rows
     */

    private void handleRowUpdate(TableModelEvent e) {
        int modelColumn = e.getColumn();
        int start = e.getFirstRow();
        int end = e.getLastRow();

        Rectangle dirtyRegion;
        if (modelColumn == TableModelEvent.ALL_COLUMNS) {
            // 1 or more rows changed
            dirtyRegion = new Rectangle(0, start * getRowHeight(),
                                        getColumnModel().getTotalColumnWidth(), 0);
        }
        else {
            // A cell or column of cells has changed.
            // Unlike the rest of the methods in the JTable, the TableModelEvent
            // uses the coordinate system of the model instead of the view.
            // This is the only place in the JTable where this "reverse mapping"
            // is used.
            int column = convertColumnIndexToView(modelColumn);
            dirtyRegion = getCellRect(start, column, false);
        }

        // Now adjust the height of the dirty region
        dirtyRegion.height = 0;
        for ( int row=start; row <= end; row ++ ) {
            dirtyRegion.height += getRowHeight(row);  //THIS IS CHANGED TO CALCULATE THE DIRTY REGION HEIGHT CORRECTLY
        }
        repaint(dirtyRegion.x, dirtyRegion.y, dirtyRegion.width, dirtyRegion.height);
    }

}

The following Test demonstrates the problem, and that FixedForRowHeightJTable solves it

This test pops up a JTable in a frame and then changes cell values to trigger repaints.
While doing this we replace the standard RepaintManager with a TimingRepaintManager, which keeps track of the total time spent by the Swing event thread painting the table
We first run the test for a standard JTable, then for my fixed version, with the following results:

***** Running test for table Standard JTable under 1.5.0_19
With all rows default height total time spent repainting table 24 millis
Setting custom row height for one row
With one custom row height total time spent repainting table 5427 millis

***** Running test for table FixedForRowHeight JTable under 1.5.0_19
With all rows default height total time spent repainting table 2 millis
Setting custom row height for one row
With one custom row height total time spent repainting table 25 millis

Needless to say, I’ve submitted a bug report for this one!
(although technically once might argue its an RFE, since the table still gets painted, anything which degrades performance this much is a bug in my book!)

import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.*;
import javax.swing.event.TableModelEvent;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;

/**
 * Created by IntelliJ IDEA.
 * User: Nick Ebbutt
 * Date: 06-Jun-2009
 * Time: 13:34:58
 *
 * A demonstration of JTable repainting bug for custom row heights
 */

public class DemoForJTableCustomRowHeightRepaintBug {

    private static final int COL_COUNT = 40;
    private static final int ROW_COUNT = 50;
    private static TimingRepaintManager timingRepaintManager;
    private static JTable table;
    private static FixedForRowHeightJTable fixedTable;

    public static void main(String[] args) throws Exception {
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                timingRepaintManager = new TimingRepaintManager();
                RepaintManager.setCurrentManager(timingRepaintManager);
                table = new JTable(new TestTableModel());
                fixedTable = new FixedForRowHeightJTable(new TestTableModel());
            }
        });

        runTestForTable("Standard JTable", table);
        runTestForTable("FixedForRowHeight JTable", fixedTable);
    }

    private static void runTestForTable(String description, final JTable table) throws Exception {
        System.out.println("\n***** Running test for table " + description + " under " + System.getProperty("java.version"));
        showTable(table, description);
        timingRepaintManager.resetTotalRepaintTime();
        runTableModelUpdates(table.getModel());

        System.out.println("With all rows default height total time spent repainting table " + timingRepaintManager.getTotalRepaintTime() + " millis");

        System.out.println("Setting custom row height for one row");
        setRandomRowHeight(table);

        timingRepaintManager.resetTotalRepaintTime();
        runTableModelUpdates(table.getModel());
        System.out.println("With one custom row height total time spent repainting table " + timingRepaintManager.getTotalRepaintTime() + " millis");
    }

    private static void setRandomRowHeight(final JTable table) throws InterruptedException, InvocationTargetException {
        //now just set one row to a custom height and run the repaint timing test again
        SwingUtilities.invokeAndWait(
            new Runnable() {
                public void run() {
                    table.setRowHeight(getRandomRowOrCol(ROW_COUNT), 100);
                }
            }
        );
    }

    public static void showTable(final JTable t, final String name) throws Exception {
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                JFrame f = new JFrame(name);
                f.getContentPane().add(new JScrollPane(t));
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setSize(new Dimension(1024,768));
                f.setVisible(true);
            }
        });
    }

    public static void runTableModelUpdates(final TableModel tableModel) {
        for ( int loop=0; loop < 500; loop++) {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        tableModel.setValueAt("Wibble", getRandomRowOrCol(ROW_COUNT), getRandomRowOrCol(COL_COUNT));
                    }
                });
                Thread.sleep(10);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //record the total time spent repainting
    private static class TimingRepaintManager extends RepaintManager {
        private volatile long totalTime;

        public void paintDirtyRegions() {
            long startTime = System.currentTimeMillis();
            super.paintDirtyRegions();
            totalTime += System.currentTimeMillis() - startTime;
        }

        public long getTotalRepaintTime() {
            return totalTime;
        }

        public void resetTotalRepaintTime() {
            this.totalTime = 0;
        }
    }


    private static class TestTableModel extends DefaultTableModel {
        public TestTableModel() {
            setColumnCount(COL_COUNT);
            for (int row=0; row < ROW_COUNT; row++) {
                addRow(createRow(row));
            }
        }

        private Object[] createRow(int row) {
            String[] colNames = new String[COL_COUNT];
            for ( int col=0; col<COL_COUNT; col++) {
                colNames[col] = "Cell " + row + ":" + col;
            }
            return colNames;
        }
    }

    public static int getRandomRowOrCol(int maxVal) {
        return (int)(Math.random() * maxVal);
    }

    /**
     * Extends JTable to fix the broken repainting for updates when there are custom height rows
     *
     * This fix does not support row sorting in jdk1.6 -
     * to support that would require firing sortedTableChanged to sortManager
     */

    private static class FixedForRowHeightJTable extends JTable {

        public FixedForRowHeightJTable(TableModel tableModel) {
            super(tableModel);
        }

        public void tableChanged(TableModelEvent e) {

            //if just an update, and not a data or structure changed event or an insert or delete, use the fixed row update handling
            //otherwise call super.tableChanged to let the standard JTable update handling manage it
            if ( e != null &&
                e.getType() == TableModelEvent.UPDATE &&
                e.getFirstRow() != TableModelEvent.HEADER_ROW &&
                e.getLastRow() != Integer.MAX_VALUE) {

                handleRowUpdate(e);
            } else {
                super.tableChanged(e);
            }
        }

        /**
         * This borrows most of the logic from the superclass handling of update events, but changes the calculation of the height
         * for the dirty region to provide proper handling for repainting custom height rows
         */

        private void handleRowUpdate(TableModelEvent e) {
            int modelColumn = e.getColumn();
            int start = e.getFirstRow();
            int end = e.getLastRow();

            Rectangle dirtyRegion;
            if (modelColumn == TableModelEvent.ALL_COLUMNS) {
                // 1 or more rows changed
                dirtyRegion = new Rectangle(0, start * getRowHeight(),
                                            getColumnModel().getTotalColumnWidth(), 0);
            }
            else {
                // A cell or column of cells has changed.
                // Unlike the rest of the methods in the JTable, the TableModelEvent
                // uses the coordinate system of the model instead of the view.
                // This is the only place in the JTable where this "reverse mapping"
                // is used.
                int column = convertColumnIndexToView(modelColumn);
                dirtyRegion = getCellRect(start, column, false);
            }

            // Now adjust the height of the dirty region
            dirtyRegion.height = 0;
            for ( int row=start; row <= end; row ++ ) {
                dirtyRegion.height += getRowHeight(row);  //THIS IS CHANGED TO CALCULATE THE DIRTY REGION HEIGHT CORRECTLY
            }
            repaint(dirtyRegion.x, dirtyRegion.y, dirtyRegion.width, dirtyRegion.height);
        }
    }

}
AddThis Social Bookmark Button

Lower bound wildcards in java – how to apply the get and put principle

February 27th, 2009 Nick Posted in java No Comments »

I recently saw an answer to a generics question on stack overflow where an incorrect (or at least misleading) answer has been voted up to the top. Sometimes generics are just a bit unintuitive.

Here is the example in a nutshell -

   addToList(List<? super Shape> l) {
    l.add(new Object());
}

 // That should work, right? After all List<? super Shape> is defined so that it can hold
 // any objects which implement a superclass of Shape....

In fact, the code above would not compile. That’s the wrong way to interpret the lower bound wildcard. The variable l actually refers to a List which may be restricted to contain any valid superclass of Shape – and we don’t know exactly what type of List it is from the lower bound reference. It may actually be a List of Drawable (if Shape extends Drawable), in which case adding an Object to it would not be safe – since an Object is not necessarily Drawable. It may also be a List of Shape instances (since Shape is the lower bound), in which case adding an Object to it would also be invalid. That’s why the example above does not compile.

There is an important principle set out in Java Generics and Collections, called the ‘Get and Put Principle’ which helps to explain when to use lower and upper bound wildcards

Use an extends wildcard when you only get values out of a structure, use super wildcard when you only put values into a structure, and don’t use a wildcard when you both get and put.

Proper use of this rule can make your classes more flexible. Partly to make sure I’ve got this fully straightened out in my own head, I’ve put together an example which illustrates the get and put principle, and helps to explain upper and lower bound wildcards:

// Example of the Get and Put Principle

    private class Drawable {}

    private class Shape extends Drawable {}

    private class Circle extends Shape {}

    public void testPut() {
        putShapeInto(new ArrayList<Object>());
        putShapeInto(new ArrayList<Drawable>());
        putShapeInto(new ArrayList<Shape>());
    }

    //flexible - can take a List of any superclass of Shape
    public void putShapeInto(List<? super Shape> l) {
        // We don't know what the type of list l is, but we know it is Shape or a superclass of Shape
        // therefore, it is always a valid destination for Shape instances
        l.add(new Shape());

        // l.add(new Object());
        // ...will not compile. The type of l may be any valid superclass of Shape
        // e.g. It may be <Drawable>, not all Objects are Drawable

        // Shape s = l.get(0);
        // ...will not compile. l may be List<Drawable>, and not all Drawables are necessarily Shapes
    }

    public void testGet() {
        getShapeFrom(new ArrayList<Shape>());
        getShapeFrom(new ArrayList<Circle>());
    }

    //flexible - can take a List of any subclass of Shape
    public void getShapeFrom(List<? extends Shape> l) {
        //  because the upper bound of list l is Shape, l is always a valid source for Shape instances
        Shape s = l.get(0);

        //  l.add(new Shape());
        //  ...will not compile, the type of l may be List<Circle>. Shapes are not all Circles
    }

    // Can add and get, but not very flexible - the method is limited to accepting Lists of type <Shape>
    public void testPutAndGet(List<Shape> l) {
        Shape s = l.get(0);
        Drawable d = l.get(0);
        l.add(new Shape());        
        l.add(new Circle());
    }
AddThis Social Bookmark Button

Careful how you synchronize toArray()

January 21st, 2009 Nick Posted in java No Comments »

This is a fuller version of the code snippit I submitted to the interesting thread on java synchronization issues on stack overflow

In general it can be tempting to assume too much thread safety when using synchronized collection classes.
It can be easy to think they somehow grant you more protection than they actually do, and forget to hold the lock between calls.

If have seen this mistake a few times:

 List<String> l = Collections.synchronizedList(new ArrayList<String>());
 String[] s = l.toArray(new String[l.size()]);

In the second line above, the toArray and size() methods are both thread safe in their own right, but the size() is evaluated separately from the toArray(), and the lock on the List is not held between these two calls. If you run this code with another thread concurrently removing items from the list, sooner or later you will end up with a new String[] returned which is larger than required to hold all the elements in the list, and has null values in the tail, which will quite possibly result in a NullPointerException eventually.

It is easy to think that because the two method calls to the List occur in a single line of code this is somehow an atomic operation, but it is not. The best way to fix this is to hold the lock explicitly :

 List<String> l = Collections.synchronizedList(new ArrayList<String>());

 synchronized(l) {
    String[] s = l.toArray(new String[l.size()]);
 }
AddThis Social Bookmark Button

Workaround for bug id 6753651 – find path to jar in cache under webstart

October 2nd, 2008 Nick Posted in java, swing 5 Comments »

Here is a workaround for the changes in 1.5.0_16 and 1.6.0_07, which prevent you from obtaining a valid URL using Class.getResource(myClassName) under webstart.

This is related to bug id 6753651 raised here

URLs acquired in this way used to include a filesystem path to the jar file in the local webstart cache containing the resource. Some apps relied on this filesystem path.

The behaviour of Class.getResource() under webstart was changed in 1.5.0_16 and 1.6.0_07 due to a security patch.

  • In 1.5.0_16 this breaks the URL (i.e. the URL returned by url.toString() is no longer valid, the file path is removed, which breaks some apps).
  • In 1.6.0_07 the url now uses the http protocol to refer to the jar file on the server – although in this case under the covers webstart will actually use the jar in the local cache if you call URL.openConnection()

One of our apps relied on the file path information in the URL, so we were forced to find a workaround.

This workaround does not fix the broken URL – but it does at least let you use the URL to find a valid path to the jar file on the local filesystem

update Nov 28th 08 – if you want to get a valid URL you might check out David’s suggestions in the comments below this post

To use the workaround first call Class.getResource(myResource) to get a (broken) URL , and then pass that URL into getJarFilePath(URL url). You should get back the path to the jar file in the local webstart cache which contains the resource.

It should work for all JDK/webstart versions up to and including 1.5.0_16 and 1.6.0_07 (subsequent versions have not been tested)

This workaround will only work for signed apps running with all permissions, since it uses reflection to access private methods and fields in classes within webstart.jar.

This workaround is pretty horrid, it certainly feels sinful, but it is the best solution I can find at present, having to fix a mission critical application within the next 24 hours. Since it relies on private implementation details within the webstart jar it may break with future jre versions if that implementation is changed. It is not part of the public API, so changes made in subsequent jdks may break the workaround at any time.

n.b. you may need to call System.setSecurityManager(null) before you call this routine to allow the private field access to work.

     /**
     * This method will return the path to the jar file containing the resource which this URL references
     *
     * It should work with URLs returned by class.getResource() under java 1.5.0_16
     * and 1.6.0_07, as well as maintaining backwards compatibility with previous jres
     *
     * The two jre above contain security patches which make the file path of the jar
     * inaccessible under webstart. This patch works around that by using reflection to access
     * private fields in the webstart.jar where required. This will only work for signed webstart
     * apps running with all security permissions
     *
     * @param jarUrl - url which has jar as the protocol
     * @return path to Jar file for this jarURL
     */

    public static String getJarFilePath(URL jarUrl) {
        JarFile jarFile = getJarFile(jarUrl);
        return findJarPath(jarFile);
    }

    public static JarFile getJarFile(URL jarUrl) {
        try {
            JarURLConnection jarUrlConnection = (JarURLConnection)jarUrl.openConnection();

            //try the getJarFile method first.
            //Under webstart in 1.5.0_16 this is overriden to return null
            JarFile jarFile = jarUrlConnection.getJarFile();

            if ( jarFile == null) {
                jarFile = getJarFileByReflection(jarUrlConnection);
            }
            return jarFile;
        } catch (Throwable t) {
            throw new RuntimeException("Failed to get JarFile from jarUrlConnection", t);
        }
    }

    private static JarFile getJarFileByReflection(JarURLConnection jarUrlConnection) throws Exception {
        //this class only exists in webstart.jar for 1.5.0_16 and later
        Class jnlpConnectionClass = Class.forName("com.sun.jnlp.JNLPCachedJarURLConnection");
        Field jarFileField;
        try {
            jarFileField = jnlpConnectionClass.getDeclaredField("jarFile");
        } catch ( Throwable t) {
            jarFileField = jnlpConnectionClass.getDeclaredField("_jarFile");
        }
        jarUrlConnection.connect(); //this causes the connection to set the jarFile field
        jarFileField.setAccessible(true);
        return (JarFile)jarFileField.get(jarUrlConnection);
    }

    private static String findJarPath(JarFile cachedJarFile) {
        try {
            String name = cachedJarFile.getName();

            //getName is overridden to return "" under 1.6.0_7 so use reflection
            if ( name == null || name.trim().equals("")) {
                Class c = ZipFile.class;
                Field field = c.getDeclaredField("name");
                field.setAccessible(true);
                name = (String)field.get(cachedJarFile);
            }
            return name;
        } catch (Throwable t) {
            throw new RuntimeException("Failed to get find name from jarFile", t);
        }
    }
AddThis Social Bookmark Button

New Open Source SwingCommand library released

September 29th, 2008 Nick Posted in java, swing No Comments »

I finally completed open sourcing SwingCommand – an open source library which provides support for the command pattern in Swing. In addition to synchronous commands, it provides support for asynchronous commands, which hides away the gory threading details in a similar way to the SwingWorker class – but in SwingCommand the asynchronous commands are integrated into the overall framework, and there are a few extra features, such as composite command support and command execution observers, which I hope make it more flexible.

Reusable Commands

One other key difference from SwingWorker is that in SwingCommand each command instance is reusable (a command can be executed more than once). Each time you call myCommand.execute() a ‘CommandExecution’ instance, which encapsulates the state for that execution. In the SwingWorker provided as part of jdk 1.6, each SwingWorker instance is designed to be executed only once. In SwingCommand there is nothing to stop you creating a new Command instance each time if you prefer, but you also have the flexibility to create a shared Command instance up front, pass references to it around your application and execute it many times if necessary.

Why a new Command library?

There is nothing fundamentally new about using the command pattern in Swing apps, but arguably there is a need for a reusable library to address this problem in a focused manner, and provide a richer set of features than SwingWorker, without attempting to solve every other ui programming issue along the way. SwingCommand has certainly been a useful library in my projects already. It does hide away a lot of the more complex threading issues, typically produces ‘clean code’, and make it easy to build a Swing application around chunks of reusable logic wrapped as Command classes. Integration with java.util.concurrent framework makes it really easy to handle threading issues which would otherwise be complex (e.g to prevent two instances of the same asynchronous command from running simulatenously would be a one line change, by using Executors.newSingleThreadExecutor()).

A milestone

This is the first of Object Definitions’ libraries which I have open sourced. It is fair to say it has been more work than I anticipated to get it to this stage – so it is probably time to crack open a bottle of something fizzy. Once I have recovered from the hangover I’ll post more about it here. In the meantime there is a lot more information on the website and you can download swingcommand library here

AddThis Social Bookmark Button

Fix for log4j bug 45704 – Failed to load logging.xml for JRE 1.5.0_16 and Webstart

September 26th, 2008 Nick Posted in java, swing 1 Comment »

This bug affects webstart applications which use log4j and xml configuration files (e.g. log.xml rather than log4j.properties) in JDK 1.5.0_16 (and later..?)

I think this problem has been occurring since a new security patch was released by Sun, which seems to have changed the behaviour of the URL class for webstart apps.

This security patch affects jdk 1.5 from 1.5.0_16 onwards, and also affects Java SE 6 Update 7 onwards
So this problem probably also exists for log4j under webstart 1.6 update 7 +

What seems to have happened is that the patch has stopped webstart apps from being able to obtain a valid URL object using MyClass.class.getResource().

If you obtain a URL instance in this way when running under webstart from 1.5.0_16, then calling toString on the URL instance gives you a malformed URL without the jar file path information. You only get back the path of the class within the Jar file – the part which usually appears after the exclamation mark – and not the path to the jar file itself. See this thread here for an example of what this looks like

I think the path to the jar file in the webstart cache has been removed from URL.toString() due to the security issues addressed by the patch. However if you call myUrl.openConnection() on the URL instance this still works OK, due to the way URL is implemented internally.

It is very bad that the only way to solve this security issue was to break the behaviour of URL class in this way.
It will surely create no end of problems for many webstart users.

Because of this issue, log4j is currently broken for webstart apps running under the latest jre, at version 1.2.15
There is an open bug id 45704 here.

There is now also a bug raised on Sun’s site

Luckily I think I have a solution (I will confirm this next Monday) and it is a one line change to log4j
The class to change is the DOMConfigurator.

Whereas the PropertyConfigurator does url.openConnection to get a stream (so this should work), the DOMConfigurator uses url.toString() – and gets back the bad webstart URL
This can be fixed at line 762 in DOMConfigurator.java simply by replacing parser.parse(url.toString()) with parser.parse(url.openConnection().getInputStream())

I have created a patch of log4j to make this change for my clients, and this seems to fix the issue with their webstart apps. I will submit the change to the log4j team for consideration tonight

Here is a link to the patched file
DOMConfigurator.java

AddThis Social Bookmark Button

Don’t be lazy

June 18th, 2008 Nick Posted in java, rants and opinions No Comments »

I’ve seen far too many bugs lately involving lazy initialization.

In theory lazy initialization might seem a good idea. Why do extra work up front which might not be needed?

The problem is that it is simply too easy to introduce bugs when state is not completely loaded after construction. For example, a project I recently worked on made extensive use of init() methods to lazily load state on demand. The constructor would not do the initialization. Instead, each getter method would check whether initialization was complete, and if not kick off an init() process before returning data.

The reasons for this were fairly convincing. The loading of the state required a call to a database, which was slow. But the problem was that this lazy initialization pattern soon spread throughout the codebase like a rash. Soon it was rare to find a class which was guaranteed to be ready for use after construction.

Still other classes used lazy loading as a speculative performance optimization. Table models received events which invalidated their internal state. Instead of recalculating immediately, they propagated events to their listeners, delaying their own recalculation. The models would recalculate only when a client class called a method to ask for data, and forced a the model to check its state and recalculate. Again, it sounded good – why take on the overhead of immediate recalculation if your class has no active listeners?

The original coders left. The code base gradually expanded to more than a million lines of code. New programmers arrived. Predictable problems emerged.

In many cases, the new programmers would simply fail to realise that an object was not ready to use until its init method had been called. Init methods had become a part of some key abstractions – although in the vast majority of cases subclasses did not actually require any special initialization. So most objects ended up with an init method, just to cater for the few places where it was really necessary. Confusion reigned as to when and where initialization should take place.

Interfaces changed and classes needed to be modified. It was all too easy to forget to check the initialization status when adding methods to existing classes. Strange errors occurred in the table models. The data had changed some time before, but since the recalculations were delayed it was hard to work out from the logs what had actually triggered the problem. Sometimes the original coders had made mistakes and forgotten to check the initialization state for some methods. In other subclasses had overridden methods and forgotten to include the check. Problems could emerge quite unexpectedly, such as when the ordering of client class method calls changed. This often caused strange data corruption or null pointer exceptions at runtime, which brought down components.

The basic OO tenet that an object should be ready for use after construction really does make a lot of sense – try very hard to avoid lazy initialization wherever possible. It may seem clever, but it probably isn’t. If it seems that lazy initialization is the only way, reconsider every alternative design. Simplicity is key, and lazy initialization or lazy recalculation is rarely, if ever, simple. You’ll likely save yourself some headaches in the short term, and in the long term you’ll save even more for those who inherit the code once you have moved on.

AddThis Social Bookmark Button

Calculating a color gradient

May 12th, 2008 Nick Posted in java, swing No Comments »

For a recent project I needed some code to calculate a colour gradient for different points on a scatter chart. The class below performs a simple interpolation between two colours, if you feed it a value between 0 and 1.

 public class GradientCalculator {
        private Color c1;
        private Color c2;

        public GradientCalculator(Color c1, Color c2) {
            this.c1 = c1;
            this.c2 = c2;
        }

        /**
         * @param ratio - value between 0 and 1
         */

        public Color getColor(double ratio) {
            int red = (int) (c1.getRed() * (1 - ratio) + c2.getRed() * ratio);
            int green = (int) (c1.getGreen() * (1 - ratio) + c2.getGreen() * ratio);
            int blue = (int) (c1.getBlue() * (1 - ratio) + c2.getBlue() * ratio);
            int alpha  =(int) (c1.getAlpha() * (1 - ratio) + c2.getAlpha() * ratio);
            return new Color(red, green, blue, alpha);
        }
    }
AddThis Social Bookmark Button

JOptionPain – JOptionPane hidden behind always on top window crashes Swing UI

April 5th, 2008 Nick Posted in java 1 Comment »

nb. this is now bug id 6690019

There is a bug in the java bug database (6519416) related to a similar issue, but it is closed and not fixed – perhaps the reviewer didn’t realise how serious this can be. I think this needs to be looked at again, since it has caused several terminal crashes in production systems I have been working on.

The problem is to do with the use of the ‘always on top’ feature for java.awt.Window. If JFrames in the application are set to always on top, a JOptionPane can be obscured by an always on top frame, and hence be inaccessible to the user. Since the JOptionPane is modal, you can’t interact with or move the always on top frame – so there is no way to reveal the JOptionPane in order to dismiss it. The whole UI just hangs. You can’t even alt-tab to reveal the option pane. The only way out is to kill the process.

There is no problem with the always on top frame is the parent of the JOptionPane – the problem only occurs if the parent is a normal frame, or the JOptionPane has a null parent set.
The following code replicates the problem

public class TestJOptionPaneAlwaysOnTopBug
{
    public static void main(String[] args) {
        SwingUtilities.invokeLater(
                new Runnable() {
                    public void run()
                    {
                        JFrame alwaysOnTopFrame = new JFrame("Test JOptionPane");
                        alwaysOnTopFrame.setSize(600, 400);
                        centreFrame(alwaysOnTopFrame);
                        alwaysOnTopFrame.setAlwaysOnTop(true);
                        alwaysOnTopFrame.setVisible(true);

                        JFrame normalFrame = new JFrame();
                        normalFrame.setSize(800, 600);
                        centreFrame(normalFrame);
                        normalFrame.setVisible(true);

                        JOptionPane.showMessageDialog(normalFrame, "Can't you see me?");
                    }
                });
    }

    private static void centreFrame(JFrame myFrame)
    {
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        int x = (d.width - myFrame.getWidth()) / 2;
        int y = (d.height - myFrame.getHeight()) / 2;
        myFrame.setLocation(new Point(x, y));
    }
}

There is a workaround, described in the bug report above, which involves creating the dialog for the JOptionPane explicitly and setting it to be always on top before it is displayed. This seems to fix things, at least on the Windows platform. However, if you use the standard JOptionPane static methods to show a dialog, always on top is not set. I think it possibly should be – it seems unlikely that one would ever want to show an option pane which is not on the top. Perhaps there is a corner case here which is not obvious?

AddThis Social Bookmark Button

Doubled up

April 4th, 2008 Nick Posted in java No Comments »

Can you guess the output of the code below? And no, it isn’t a compilation failure, even I can write code which compiles (at least after enough coffee)

    public static void main(String[] args) {
        double d1 = Double.NaN;
        double d2 = Double.NaN;
        String verdict = "Not guilty";

        if ( d1 == d2 ) {
            if ( ! new Double(d1).equals(d2) ) {
                    verdict =  0 < Double.MAX_VALUE ?
                         "less than MAX_VALUE" :
                         "greater than MAX_VALUE";
            }
        } else {
            verdict = 0 > Double.MIN_VALUE ?
                    "greater than MIN_VALUE" :
                    "less than MIN_VALUE";
        }
        System.out.println(verdict);
    }



Well in fact it prints out ‘less then MIN_VALUE’, so if you got that result – well done!
There are two slightly counter intuituve aspects to this example.

Firstly Double.NaN != Double.NaN. although new Double(Double.NaN).equals(Double.NaN);

This is reasonably widely known, but also the cause of many bugs, since it is all too easy to forget the == comparator does not work in this case. To be safe you have to remember to use the isNaN() method on Double, if testing NaN conditions.

Secondly, since Double.MAX_VALUE is the maximum value a Double can take (apart from inifinity), it might be natural to think that Double.MIN_VALUE is the minimum value a Double can take. In fact, it is not – it is actually the minimum positive value a Double can take! Easy to overlook, and once again, a potential cause of many bugs.

We hit a bug with this recently when writing a max value calculator which took MIN_VALUE as its starting point, and iterated through a set of values. During the iteration, if a subsequent value was higher than the currently stored max value, its value was taken as the new max. This all worked brilliantly – until we ran the calculator against a number set consisting of just negative values, in which case the result was returned as MIN_VALUE!

Surely a better name for this constant would be MIN_POSITIVE_VALUE.




AddThis Social Bookmark Button

Structure 101 – my new favourite code quality tool

March 19th, 2008 Nick Posted in java, rants and opinions No Comments »

Jetbrains recently sent out an email about a new companion product to Intellij idea – Structure 101. Structure 101 is a tool developed by headway software, which can be used to analyse the architecture and complexity of java applications. This is a very welcome addition to the marketplace for java development tools. (I say new – in fact it seems Structure 101 has been around for a couple of years now, so clearly I’ve not been paying attention!).

Structural problems in code

I am known to be opinionated about this and friends and ex-colleagues will already be bored with me going on about it! The majority of software is not well architected and well structured internally (perhaps in part because the tools to visualise structure at a package level have not been widely available). Some apps are structurally akin to a bowl of spaghetti, with all the negative implications of tight coupling between packages and cyclic dependencies. This is bad news for reuse – if software is full of dependency cycles, classes and packages can’t easily be reused in another context without sucking in everything else too. An acyclic structure is enormously important at the package and class level – not just at the level of the release unit. After all, a release unit today may need to be split into two in the future – to enable better reuse of some of the logic it contains.

Improving structure to remove dependency cycles between packages and classes can be hard – but it is not insurmountably hard, especially if you have the right tools for the job. And it pays major dividends, resulting in far easier reuse and a more intelligible project in the long term. Changes made to projects developed with these principles in mind tend to be more deterministic in their impact, since the ripple-through effect is minimised. And the great thing is that the methods required to achieve this decoupling are well known and accepted as good programming practice in their own right – chief among these, that concrete classes should depend upon abstractions. Introducing an interface is the most common way to decouple a cyclic dependency between two classes.

Avoiding bad structure

This is where tools like Structure 101 come in. It allows you to zoom in to view dependency problems within your application, and makes it far easier to visualize the layering and package structure within your project. Not only that, but it gives a summary of complexity, highlighting classes and areas which need simplification.

This is not a radically new goal. Other tools have attempted the same thing in the past, for example ‘Pasta’ (which became OptimalAdvisor), and ‘SmallWorlds’ (which became SA4J). Both of those were promising products which were swallowed up by big companies and disappeared for a time, stagnated or became prohibitively expensive. However, there is some genuine innovation in Structure 101, over and above the others. I have found it to be effective in zooming to the areas of applications which are the most problematic. It lacks the refactoring support which OptimalAdvisor provides – but then most developers would use their IDE for refactoring in any case, so it is not clear this is a significant disadvantage.

One feature I particularly like is the ability to upload structure diagrams to a shared repository. IDE plugins are then provided which allow developers to validate their changes against a shared model – without having to purchase a license. This is a great idea, which will facilitate much wider use of the tool, and allow teams to collaborate much more easily to maintain a desired architecture. This may help to overcome the natural entropy shared by all multi-developer projects. However things are still not perfect – licenses are node locked, which means a developer may need two licenses to allow him/her to work from home and in the office.

All things considered, Structure 101 is a tool it is certainly well worth checking out – it is my new number one tool for validating the quality of code.

AddThis Social Bookmark Button

Getting proxy server details in webstart app

March 11th, 2008 Nick Posted in java 2 Comments »

I had a devil of a job finding a way to do this.

A couple of apps I have recently worked on needed to make an apache httpclient or xmlrpc connection back to the server once the webstart app started. Although webstart clearly has the proxy server info (which it must use when downloading the application jars from the server), it is not immediately apparent how to access this from the app itself. The app then fails when trying to call back to the server.

I thought the proxyHost and proxyPort system properties might contain the required information – but this is not the case. In fact this information is not available from system properties at all.

Well the trick is to get the http proxy details using the java.net.ProxySelector class.

String theURIToConnectTo="http://www.yourServerURIHere.com"

String proxyHost, proxyPort;
java.util.List<Proxy> proxies = java.net.ProxySelector.getDefault().select(new java.net.URI(theURIToConnectTo));
for ( Proxy proxy : proxies ) {

    if ( proxy.type() == Proxy.Type.HTTP ) {
        SocketAddress proxyAddress = proxy.address();
        if ( proxyAddress instanceof InetSocketAddress) {
             proxyHost = ((InetSocketAddress)proxyAddress).getHostName();
             proxyPort = String.valueOf(((InetSocketAddress)proxyAddress).getPort());
        }
        break;
    }
 }

This still won’t handle the case where the proxy server requires username and password for authentication.
I don’t have a suitably authenticating proxy to test with right now – anybody know how to handle that?




AddThis Social Bookmark Button

jmap and jstack – getting a heap dump and stack trace from a running JVM

February 21st, 2008 Nick Posted in java, unix No Comments »

The project I’m working on currently has many server side java components running on linux. One of these in particular has been experiencing severe memory leaks when running in our production environment (but not UAT, surprisingly!)

It turns out there is a tool provided with sun’s jdk 1.5 + which will allow you to get a snapshot of the heap for a running process, without you having to have started the jvm with any particular options set. This tool is called jmap

running:
jmap -histo pid
where pid is the process id on a linux box

will give you a snapshot of the heap which looks like the below:

java -histo pid

Object Histogram:

Size    Count   Class description
-------------------------------------------------------
77928   583 char[]
55696   131 byte[]
8816    29  * ObjArrayKlassKlass
8520    355 java.lang.String
8272    215 java.lang.Object[]
4080    85  java.nio.HeapByteBuffer
3984    83  java.nio.HeapCharBuffer
3608    41  java.lang.Class
2456    19  int[]
1368    57  java.util.Hashtable$Entry
1288    16  * ConstMethodKlass
1208    31  java.lang.String[]
1120    14  java.util.HashMap$Entry[]
1040    10  java.util.Hashtable$Entry[]
1008    14  java.lang.reflect.Field
960 3   * InstanceKlassKlass
etc.

The most useful thing here is probably the object class count. There is a chance this could help you identify an area of your app which is producing an unexpected number of instances of a given class. Other jmap options give you a dump file in different formats – so this is worth investigating too.

There is an equivalent tool jstack (jstack pid), which dumps stack information for each of the running threads. Probably useful for diagnosing deadlocks etc.

The nice thing about both of these tools is that you don’t need to have started the jvm with any specific options. These tools just connect and work – so you don’t need to have planned your memory leak in advance :)

They only work on unix right now, unfortunately.
There is a good article on new tools with jdk1.6 here

nb. if jstack fails you may be able to get a thread dump on unix using kill -3 {pid}
Here is another useful link for suggestions on diagnosing performance problems

And here is a good link to help diagnose java app problems in general

AddThis Social Bookmark Button

Drag and drop into empty table not working?

January 25th, 2008 Nick Posted in java, swing No Comments »

This is a known gotcha from the early days of Swing, related to bug id 4310721

The symptom is that while drag and drop works fine into tables which are populated, trying to drop into a table with no rows, or drop data beneath the populated rows, does not work. The cause of this is that the table does not automatically expand to fill the area available to it in the JScrollpane viewport.

As of jdk1.6 this is fixed on JTable itself. You just need to call myJTable.setFillsViewportHeight(true)

If you do not have the great glory of 1.6 available to you, you will have to extend JTable and override the getScrollableTracksViewportHeight() method as below:

public class TrackViewportJTable extends JTable {

    public TrackViewportJTable(TableModel tableModel, TableColumnModel tableColumnModel) {
        super(tableModel, tableColumnModel);
    }

    public TrackViewportJTable(TableModel tableModel) {
        super(tableModel);
    }

    public boolean getScrollableTracksViewportHeight() {
        // fetch the table's parent
        Container viewport = getParent();

        // if the parent is not a viewport, calling this isn't useful
        if (! (viewport instanceof JViewport)) {
            return false;
        }

        // return true if the table's preferred height is smaller
        // than the viewport height, else false
        return getPreferredSize().height < viewport.getHeight();
    }
}

The above is based on the example in Shannon Hickey’s blog.
For a more complete description of the problem, and its solution, see his blog entry here

AddThis Social Bookmark Button

Tricked by short circuit operators

January 17th, 2008 Nick Posted in java No Comments »

I am currently feeling suitably humbled, having fallen into a nasty coding trap. At least in this case it didn’t cause a major industrial disaster (please shoot me if I ever start work in the aviation industry!)

So just to make the humiliation public, the following was the cause of my disgrace – I had a test, similar to the below:

return myString != null && ! myString.equals("wibble") && ! myString.equals("squork");

So that’s all OK, isn’t it, even if it isn’t particularly nice code? – if the string is null the first test will evaluate to false, and then the short circuit operator will prevent the other two tests from being evaluated. Hence a null pointer cannot occur.

Well yes. In fact, that is correct – the above works OK. My problem was that I duplicated and changed the logic, without thinking through the consequences, and introduced an or operator into the mix:

return myString != null && myString.equals("wibble") || myString.equals("squork");

Looks similar, at a glance, and again the short circuit operator should prevent any null pointer. Or should it?

In fact in this case, no. It all goes pear shaped, and horribly so. The reason is to do with operator precedence. What actually happens is that since && has a higher precedence than ||, the way the compiler actually interprets the above is this:

return ( myString != null && myString.equals("wibble") ) || myString.equals("squork");

which, if myString is null, becomes:

return false || myString.equals("squork");

So the second of the myString.equals tests is still evaluated, and a null pointer results. Luckily these lines of code weren’t wired into flight control.

I think I had been lulled into a false sense of security by the short circuit operator construct. Well, that’s one more mistake I’ll never make again. But clearly, I needed to refresh my knowledge of the rules of operator precedence !





AddThis Social Bookmark Button

Maven presentation

December 5th, 2007 Nick Posted in java, musings No Comments »

Why do so many projects reinvent the wheel when it comes to build scripts? Maven applies the principles of convention over configuration to project build and setup. It also provides management of dependencies between modules, something which can rapidly get out of hand in a project environment of any size or scale.

I was immediately hooked on Maven when I first started using it several years ago, and Maven has come a long way since then. The new Maven 2 integration in Intellij 7 is a fantastic step forward. This should really reduce the learning curve for developers moving between projects.

Here is a copy of the powerpoint for a recent introduction to Maven presentation I gave at work, covering some of the basics.

MavenPresentation.ppt

The general links below may also be helpful:

Maven Project Front Page
Build lifecycle phases in Maven, and mappings from lifecycle phases to maven plugins
Maven the definitive guide – online book
Search for maven repository artifacts at www.mvnrepository.com
Setting up a maven repository

also the release plugin docs:
prepare-release
perform-release

AddThis Social Bookmark Button

More on swing garbage collection – static references

September 24th, 2007 Nick Posted in java, swing No Comments »

Static references are another common cause of memory leaks, both in Swing applications and other components.

If a static field holds a reference to an object instance, that instance will not be garbage collected until such time as its owning Class instance is garbage collected (which may be never). If have seen several applications which held Maps or other data structures at a static level, and over time the number of objects referenced by the collection gradually grew, resulting in a memory leak. Often items are placed in such collections without the developer realising that the item being added will never be released.

In many cases static fields are overused, and it is always best to question whether a field really needs to be static. When trying to find the cause of a memory leak, it is often a good idea to start by having a look at the tree of references which emanate from each static field reference, and see if this might be the cause.

AddThis Social Bookmark Button

Swing garbage collection – problems with the observer pattern

September 24th, 2007 Nick Posted in java, swing 2 Comments »

Clearly the observer pattern is very important in Swing programming, it is used every time you register a Swing model with a UI component, so that the component can update itself as the model data changes. However, there are inherent dangers in using this pattern without considering garbage collection.

The problem is that most standard implementations of swing models hold strong references to the listeners which register with them. This prevents the listeners from being garbage collected, until the model itself becomes available to the garbage collector. Some important models in an application may have a long expected life time (they may be created when the application starts up and dereferenced only when it exits). If these models hold on to all their listeners, some of which may be temporary screens or dialogs which the user opens and closes, the impact on memory usage can be far-reaching. This issue has been at the core of the most of the severe memory management issues I have seen so far with Swing).

Most solutions to the problem suggest that the correct approach to solving it would be to modify the models in order to store the listener objects as weak references.

Upgrading models to use weak references

This approach works, but has at least one severe drawback – although you can use the techique freely in your own models, the standard models which Swing provides do not use weak references. This means that you will probably need to extend them, and then modify your code to use the extended versions of the models, or implement a totally redesigned model class. This is non-trivial, and will involve careful analysis of the way the existing models access and update their listener lists. Furthermore, it will be necessary to check the implementation of the models on subsequent updates to the swing core libraries.

Additionally, fixing this problem by updating the models has another drawback – it is inflexible. In most implementations the ‘weak reference model’ wraps all of its registered listeners in a weak reference indescriminately. There may be occasions when you don’t want this to be the case – some listeners may still be functionally important even though they have no references from elsewhere in the application, and you don’t want these to be garbage collected. However there is no way for client classes to opt out when they add their listeners – all listeners added to the ‘weak reference models’ will be held using weak references. There may be workaround to this, but overall it adds to the potential problems.

WeakReferenceListener wrapper class

A preferable solution is to provide a way for the client classes to decide whether weak references are required at the point they add a listener to the model. The following techniques can be used without modifying the model classes at all.

Both techniques below involve wrapping the real listener instance using a weak reference wrapper class. The wrapper class is added to the model, where it acts as a proxy for the real listener instance. The weak reference proxy first tests whether the real listener has been garbage collected, before delegating event method calls.

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.lang.ref.WeakReference;

/**
 * WeakRefActionListener delegates the handling of ActionEvents to an ActionListener wrapped in a weak reference
 */

public class WeakRefActionListener implements ActionListener
{

  private WeakReference<ActionListener> actionListenerDelegate;

  public WeakRefActionListener(ActionListener actionListener)
  {
    this.actionListenerDelegate = new WeakReference<ActionListener>(actionListener);
  }

  public void actionPerformed(ActionEvent e)
  {
    ActionListener delegate = actionListenerDelegate.get();
    if ( delegate  != null )
    {
      delegate.actionPerformed(e);
    }
  }

}

The WeakRefActionListener can be used in the following manner

this.actionListener = new ActionListener()
{
    public void actionPerformed(ActionEvent e)
    {
        System.out.println("Button clicked!");
    }
}

button.addActionListener(
  new WeakRefActionListener( actionListener )
);

There are two things to note:

  • The client class must keep a reference to the listener it creates. It will not work with anonymous listeners. Unless the client class or another object instance holds a reference to the listener, the listener will be eligable for garbage collection immediately once its weak reference proxy has been added to the model.
  • The model will still retain a reference to the weak reference proxy, even when the wrapped listener instance has been garbage collected. This will not create any problems unless many such listeners are added to the model, but in this case performance could degrade as the model iterates through a list of weak reference listeners which are mostly no longer valid. A possible solution to this might make use of a background thread to clean up the invalid WeakRef listeners
AddThis Social Bookmark Button