«« My Brief History With Technology CD Run »»
blog header image
Making a Listenable Model Out of Vector

To implement the model-view-controller (MVC) design pattern for AudioMan I need a model. The Vector class is good enough but I also need to be able to attach listeners to a Vector so that the user interface (the TableViewer's content provider) is notified when the model changes. To do that I needed to subclass Vector and add the notification code.

The problem with subclassing Vector is the number of mutator methods it has: well over a dozen. Mutator methods change the state of an object. So if I have listeners attached to my subclass of Vector, every time one of those mutator methods is used I have to notify the listeners. Overriding all of those mutator methods would be a big PITA.

So I chose three methods: add(Object), remove(Object) and clear(). I would only use these three methods to mutate my new class VectorModel, a subclass of Vector. I override every other mutator method and make it throw UnsupportedOperationException.

I got this idea from the Collections class, which contains handy static methods for manipulating Collections. In it there are a bunch of methods that return unmodifiable (read only) versions of Collections. For example:

public static List unmodifiableList(List list)

takes a List as input and returns an unmodifiable version of that List. They do it by wrapping the List inside of another List that has all of the mutators throwing UnsupportedOperationException.

To complete VectorModel I need the code to add and remove listeners and to notify the models. Java has a handy class for managing a list of listeners called EventListenerList.

private EventListenerList listenerList;

I wrote public addListener and removeListener methods like:

public void addListener(ModelListener listener)
{
    listenerList.add(ModelListener.class, listener);
}

public void removeListener(ModelListener listener)
{
    listenerList.remove(ModelListener.class, listener);
}

though you may want to do more parameter checking (I've removed it for simplicity). You may have noticed a new class in the above code: ModelListener. It's an interface that extends the EventListener interface, used for event handling. In that interface you write abstract method headers for the events you want to notify the listeners about. These usually correllate with the mutator methods. For example, mine were:

public interface ModelListener extends EventListener
{
    public abstract void itemAdded(ModelEvent me);
    public abstract void itemRemoved(ModelEvent me);
    public abstract void modelCleared(ModelEvent me);
}

When you mutate the VectorModel you notify the listeners by iterating through the list of added listeners, like so:

EventListener[] listeners = listenerList.getListeners(ModelListener.class);

for (int i = 0; i < listeners.length; i++)
{
    ((ModelListener)listeners[i]).itemAdded(new ModelEvent(this, o));
}

The classes you want listening in turn have to implement the ModelListener interface and write some code for the three methods. In my case that was JFace's TableViewer content provider. The TableViewer class has methods to add and remove objects so this was fairly simple.

The last piece of the puzzle is the ModelEvent class, which gets sent with the event to the listener. When I notify the user interface that some object has been added I want to send that object, so that the user interface knows what to add. The ModelEvent constructor takes the object in question as a parameter.

public ModelEvent(Object source, Object item)
{
    super(source);
    this.item = item;
}

The listener then extracts the added or removed object from the immutable ModelEvent object by using the getItem() accessor method.

So that's how I'm doing my model. Any questions or suggestions?

Update 10:12 PM Yeah, it's not quite that simple. The Vector class has a lot of methods that do the same thing, left over from the old days when it didn't implement the List interface. To simplify their lives the implementors of Vector just used other methods sometimes. So I can't use remove(Object) if I override removeElementAt(int) and make it throw UnsupportedOperationException because remove(Object) uses that method. I had to go right to the top and see which methods didn't call any other methods. Those ones turn out to be:

public synchronized void addElement(Object o)
public synchronized boolean removeElement(Object o)
public synchronized void removeElementAt(int index)
public void removeAllElements()

So why do I have two remove...() methods? Well I need to be able to remove objects by reference, but I don't want to have to call indexOf(Object) every time. The other method, removeElementAt(int) actually does the removal and is called by removeElement(Object). So now I have to test it too. Oh joy.

Posted at February 02, 2004 at 03:35 PM EST
Last updated February 02, 2004 at 03:35 PM EST
Comments

Awesome.

A small thing that I do with list iteration (pardon if some of this is .netish..... they all look the same when no compiler is involved)..

instead of..

for (int i=0; i< myList.Count; i++)
{
//play with it
}

i use

for (IEnumerator allElements = myList.GetEnumerator(); allElements.MoveNext();)
{
// play with it
}

(and if foreach is avaialble that works too)...

The main reason is that my enumerator variables scope is now even more restricted - so that if I need to iterate over 2 lists I can use the same variable name without possibly introducing bugs because of scope issues (aka using the wrong variable if you created two similarly named variables.

» Posted by: aforward at February 2, 2004 11:32 PM

I use arrays because they are copies I'm looking at. I don't have to worry about it being threadsafe or anything.

» Posted by: Ryan at February 2, 2004 11:40 PM

Aha, I have never really used arrays... maybe I should start.

» Posted by: aforward at February 2, 2004 11:54 PM
Google
 
Search scope: Web ryanlowe.ca