Paul Bininda – Blog
1Dec/09

Weak Listeners

Resource Deallocation in Java

Resource deallocation in Java is no problem since Java has automatic garbage collection.

Right! ...? ...?? ...Wrong!

Since Java has automatic garbage collection, resource deallocation is often taken care of for you,
but when it is not, it becomes a real pain.
Since the language relies so heavily on garbage collection, it seems the language
designers forgot to include good mechanisms for the cases where garbage collection does
not solve the problem (releasing external resources) or just does not happen automatically.

A common scenario for memory leaks in Java is Listener registration and de-registration.
When you want one component to react to events in another component you usually use a listener
pattern. The listening component registers itself as a callback client to an observable component.

Finding the right place to register the listener usually is not a problem.
Often it is done in the constructor of the listening component or in some setup
method.

Unregistering however is often not so obvious since there is no canonical tear down
mechanism in Java. You are often left with listeners staying registered in their
observables and therefore not eligable for garbage collection.

Netbeans Solution

The Netbeans developers have addressed this problem by providing a class called
WeakListener. It works by inserting a WeakListener object between the listener
and the observable. The listener is only referenced weakly (with a WeakReference)
and therefore is automatically collected, when other references to the listener
go away. The WeakListener class also unregisters itself automagically when it
notices that its concrete listener has been collected. Unregistering the WeakListener
is only possible, if the observable conforms to certain conventions.

This solution is great, when you have a given model and want to make sure, that
your listeners do not remain referenced indefinitely.

My Solution

My solution works the other way around. The observable has support for weak listeners
built in. It keeps its list of listeners in a WeakListener support class. Listeners
can register themselves in the usual way. They may unregister themselves explicitly
or just count on being disconnected automatically.

This solution is great, when you are designing a model (observable) and want to
make life easier for your clients (listeners).

Example

Observable

public class NumberModel {

    private WeakNumberChangedListeners listeners =
            new WeakNumberChangedListeners();

    private int theNumber = 0;
    private String name;

    public NumberModel(String name)
    {
        this.name = name;
    }

    public synchronized void inc()
    {
        int oldNumber = theNumber;
        theNumber++;
        listeners.fire (new NumberChangedEvent(this, oldNumber, theNumber));
    }

    public synchronized void dec()
    {
        int oldNumber = theNumber;
        theNumber--;
        listeners.fire (new NumberChangedEvent(this, oldNumber, theNumber));
    }

    public synchronized int getNumber ( )
    {
        return theNumber;
    }

    public String getName()
    {
        return name;
    }

    public void addNumberChangedLisener (NumberChangedListener listener)
    {
        listeners.add (listener);
    }

    public void removeNumberChangedListener (NumberChangedListener listener)
    {
        listeners.remove (listener);
    }
}

Listener Interface

public interface NumberChangedListener {

    void numberChanged (NumberChangedEvent event);

}

WeakListeners class

public class WeakNumberChangedListeners
        extends WeakListeners<NumberChangedListener, NumberChangedEvent>
{

    @Override
    protected void fireOne(NumberChangedListener listener, NumberChangedEvent event)
    {
        listener.numberChanged (event);
    }

}
}

WeakListeners.java

package com.bininda.weaklistener;

import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;

/**
 * A list of listeners that are only referenced weakly.
 * Listeners are registered using the add method. They may be deregistered
 * using the remove method, however if the listener is garbage collected, because
 * it is not hard referenced any more, it is automatically removed.
 * The method fireOne must be overwritten in a derived class.
 * @param I The listener interface
 * @param E The event type
 * @author bininda
 */
public abstract class WeakListeners<I, E> {

    /**
     * Weak map of references to listeners.
     * WeakHashMap has the nice feature of automatically getting rid of
     * stale entries.
     */
    private Set<I> wSet = Collections.newSetFromMap(new WeakHashMap<I, Boolean>());

    /**
     * Create an empty weak listener list.
     */
    public WeakListeners()
    {

    }

    /**
     * Add a new listener to the list of registered listeners.
     * If the listener has already been added, the only effect is diagnostic
     * output.
     * @param listener The new listener
     */
    public synchronized void add (I listener)
    {
        wSet.add (listener);
    }

    /**
     * Remove a registered listener from the list.
     * If the listener has already been removed, output diagnostics.
     * Note that automatic removal can not happen as long as someone still has
     * a hard reference to the listener and is therefore able to call this method.
     * @param listener
     */
    public synchronized void remove (I listener)
    {
        wSet.remove (listener);
    }

    /**
     * Fire one event to one registered listener.
     * @param listener The listener to fire to
     * @param event The event to pass to the listener.
     */
    abstract protected void fireOne(I listener, E event);

    /**
     * Fire an event to all registered listeners.
     * If diagnostics are enabled, listeners that have been collected automatically
     * are printed out.
     * @param event The event to fire to the listeners.
     */
    public synchronized void fire(E event)
    {
        {
            List<I> lcp = new LinkedList<I>(wSet);
            for (I l: lcp)
            {
                try
                {
                    fireOne (l, event);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        }
    }

}

17Sep/09

Image Preview in JFileChooser

I just put together a little code to show an image preview in the JFileChooser. It shows a scaled image on the right side of the file chooser. The image is scaled to completely fit inside the box to the right.

ImageFileChooser
The file chooser does not filter files. It allows the user to select any file but if it can be opened as an image, the preview is shown.
I created a component based on JPanel for the preview which overrides the paintComponent method:

package x.y.z.util;

import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JPanel;

public class ImagePanel extends JPanel
{

    private Image image;
    private Image scaledCache;

    public ImagePanel()
    {
        setBorder(javax.swing.BorderFactory.createEtchedBorder());
    }

    public void setImage (Image image)
    {
        this.image = image;
        scaledCache = null;
        repaint();
    }

    private Image getScaled()
    {
        int iw = image.getWidth(this);
        int ih = image.getHeight(this);
        int pw = getWidth();
        int ph = getHeight();
        double scale;
        if (1.0 * pw / iw < 1.0 * ph / ih)
        {
            scale = 1.0 * pw / iw;
        }
        else
        {
            scale = 1.0 * ph / ih;
        }
        int scaledw = (int) (iw * scale);
        int scaledh = (int) (ih * scale);
        if (scaledCache != null)
        {
            if (scaledCache.getWidth(this) == scaledw &&
                    scaledCache.getHeight(this) == scaledh)
            {
                return scaledCache;
            }
        }
        scaledCache = image.getScaledInstance(scaledw, scaledh, Image.SCALE_DEFAULT);
        return scaledCache;
    }

    @Override
    public void paintComponent(Graphics g)
    {
        if (g != null)
        {
            Graphics scratch = g.create();
            scratch.setColor(getBackground());
            scratch.fillRect(0, 0, getWidth(), getHeight());
            if (image != null)
            {
                Image scaled = getScaled();
                scratch.drawImage(scaled, getWidth() / 2 - scaled.getWidth(this) / 2,
                        getHeight() / 2 - scaled.getHeight(this) / 2, this);
            }
        }
    }
}

Then I created a the class ImageFileChooser derived from JFileChooser:

package x.y.z.util;

import java.awt.Dimension;
import java.awt.Image;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;

public class ImageFileChooser extends JFileChooser {

    public ImageFileChooser()
    {
        final ImagePanel preview = new ImagePanel();
        preview.setPreferredSize(new Dimension (150, 150));
        setAccessory(preview);
        addPropertyChangeListener(new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent e)
            {
                String propertyName = e.getPropertyName();
                if (propertyName.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY))
                {
                    File selection = (File) e.getNewValue();
                    String name;
                    if (selection == null)
                    {
                        return;
                    }
                    else
                    {
                        name = selection.getAbsolutePath();
                    }
                    ImageIcon icon = new ImageIcon(name);
                    Image newImage = icon.getImage();
                    preview.setImage (newImage);
                }
            }
        });
    }
}

Works great.

Tagged as: , No Comments
17Sep/09

Netbeans Debugger Problems

My colleague Chris Andritzky keeps getting bitten by problems of the Netbeans Debugger. In a multi threaded application, threads are stopped by breakpoints but the debugger does not realize it.
It seems there are two bugs behind this, one in JDK6 and one in the Netbeans Debugger.
The bug is described in Issue 167776. The JDK6 bug is supposedly fixed in Update 16. The Netbeans bug will be fixed in Netbeans 6.8. Chris is going to try Netbeans 6.8 Milestone 1.

Update:
Chris tried updating to Update 16 first. This didn't help. Updating to Netbeans 6.8 Milestone 1 solved the problem though.

27Aug/09

Subdocument in Netbeans RCP

In my current Netbeans RCP project, I manage entities called configurations which are persisted in a separate component. Each configuration is defined by an XML file and a bunch of additional resource files. The configuration itself (the XML file) can be edited graphically.

A new requirement came up which essentially defined a new type of additional resource files. These resource files contain Lua scripts and have to be editable in the configuration tool.