MVC Mediator - Joy of Java Client Programming


Abstract

Today we can see strong progress in client side Java, both in performance and deployment area. However, although we have plenty of user interface toolkits (like Swing or SWT), miscellaneous beautifully looking component suites and many advanced development environments, developers still face dificulties of client-side application development. Long awaited Java 1.5.0 unfortunatelly does not simplify Swing programmimg. Ease of use in GUI development still remains to be adressed.

This paper analyses reasons, why programming clients in Java is not very popular. In the second part introduces MVC Mediator, lightweight easy to use binding and validating framework, which fills the gap between low level UI components and application domain models. The framework solves real problems and annoyances present in typical Java GUI programming and encourages to rapid prototyping and development of applications with high levels of usability as well as enforces good design and programming practices.


Java client development challenges

This section discusses why it is not easy to to create perfect client side applications. We will see this problem from three perspectives: user, system architect and developer.


What are user expectations?

There is widespread belief that users take care about look and feel of user interface. There is too much focus and emotions in Java community about this. However, except few colleagues programmers I never met user who would have some principal objections against default Swing look and feel. You may not agree, but it is really minor issue. The real problem is not on component level, but on application level: application look, feel and usability.

Users exploit our applications to run their business and much more then excelent look and feel they expect good application usability:

  • Logical, intuitive, nondistracting user interface with functional and reasonably aesthetic layout. Such interface does not contain unnecessary and useless items, supports user productivity, remembers frequently accessed objects (directories, files, etc.)

  • Safety - makes difficult to corrupt or loose data. User is human beeing and makes accidental mistakes. Changes to data should be buffered and aplied after explicit commit. User should have posibility to undo made changes.

  • Data entry validation with nonintrusive error and warning feedback. It prevents users from commiting wrong or supiscious data, while leaving enough freedom and comfort to prepare data for commit.

  • Application look and feeel consistency - number and date formats, keyboard shortcuts, used icons, fonts, button order, default actions and so one

In-depth discussion of above features is behind scope of this paper. Good tips about usability can be found in [ 1].


System architect point of view

Any serious maintenable application has separated user interface from application model and logic. There are many patterns devoted to help solve this problem, like most popular MVC (Model View Controller) pattern family (HMVC (Hierarchical MVC), MVP (Model View Presenter) or Model-UI).

Classic MVC pattern schema is shown on Figure 1. The model contains data and commands. The data are displayed by the view, which register itself as subscriber to the model events, which model publish when its state changes and the view needs be updated. Controller updates state of the model depending on user interaction with view (it subscribes to events published by the view).

Figure 1. Classic MVC (Model-View--Controller) Pattern

Well it is nice in theory and in Smalltalk, but does not work in Java. After so many years we still have no free, lightweight, easy to use and widely accepted MVC type toolbox for rapid development of high quality Java client applications. There are few implementations (like Scope framework [4]) but they did not reached wider audience.

Important note presents Steven J. Metsker in [2]: "The creators of MVC envisioned that the look of a component-its 'view'-might be separable from its feel-its 'controller.' In practice, the appearance of a GUI component and its support for user interaction are tightly coupled, and Swing does not divide views from controllers. The value of MVC is to push the 'model' out of an application into its own domain."

Swing creators slightly modified MVC pattern, joining controller with view. This pattern, called Model-UI works well in Java also on application level to create windows, forms, etc. The pattern is ilustrated on Figure 2.

Figure 2. MVC Pattern aplied in Swing Applications

Controller is now joined with user interface code and is responsible for updating model and UI controls. It acts as Mediator in the design pattern with the same name (see [2]).

Detailed description of Swing MVC architecture can be found in articles [7] and [8].


Model - pardon, what a model?

There is useful to recognize two kinds of models:

  • Domain model, which contains business logic and/or data

  • Application model, which contains user interaction logic implementation

User interaction is business problem like any other, with its own requirements (application workflow, validation, undo management, interaction buffering, persistent configuration, guidance with wizards, common application services etc.) Good application interaction definitely requires its model - an application model. Underestimated application models tend to totally desintegrated applications with bad usability.

An application model holds data reflecting an application state and set of commands available to the user. But why remember state if components like checkbox or textfield holds their state by themselves? Isn't this breaking of basic rule, that everything in a system should be stored only in one place? Definitely no!

Think of application model as data about application, active modules, editors, panels etc. For example it can contain list of opened windows, their coordinates, last used files and so on. Application model should be serializable (preferably to some text format like XML). When user closes an application the saved model allows recreate application state when she opens application next time. All this things are important and system architect must choose reasonable level of detail - the more complex application, the more advanced will be an application model.

Domain model can be totally separated from application (like J2EE server side EJB components, SQL data and stored procedures, a server game engine or UNIX command line find program) or it can be set of Java objects which run directly in client program (some data structures and algorithms).

For both kind of models application designer must solve following issues:

  • How to receive events from models?

  • How to reflect changes in models into current user interface?

  • How to update models?

  • How to invoke model commands?

  • When and how to validate data?

  • In case of distributed application, what protocols use - JMS, CORBA, RMI, JDBC, SOAP,... ?


What are programer headakes?

Although the Model-UI pattern works well, there is one drawback: yellow box on Figure 2 in the Section called System architect point of view is assumed not only to synchronize user interface state with model, which itself is not easy, but also provide validation, buffering, undo and other stuff contributing to application usability. It is ideal candidate to become "spaghetti code" and best place for bugs.

Author perceives this as main reason why writing client programs in Java is difficult and unpopular. Modern development environments helps You to layout forms and then pollute code with event listeners, calling this often "very clean code". Java Community used to criticize Visual Basic for its insane approach to application design and development, but in fact after so many years Java IDEs show off the same weaknesses.

Java contains wonderfully designed Swing toolkit with many available beatiful look and feels and sofisticated layout managers. Unfortunately Java does not contain any higher level support in language or library for rapid and reliable GUI application programming. The problem is ilustrated on Figure 3.

Figure 3. Gap between model and UI domains in Java


What is wrong with Swing?

Nothing. Well, almost. But this is not probably answer You expect from this paper, right?

List model, combo box model, spinner model, selection model, table model - sounds familiar? With Swing GUI toolkit, You cannot have neutral relationship to it: You either love it or hate it. The programmer must create component, set desired properties, write and manage appropriate event listeners and often component specific adapters providing data from domain/application models. If we take programmer as a user of the Swing library, we must conclude, that Swing from his point of view has poor usability.

Swing provides extremely flexible abstraction of common user inteface toolkit controls. Like POSIX or WindowsNT APIs provide abstraction of computer hardware the Swing abstractions provides foundation for higher level layers. The problem and drawback is, that JDK does not contain any such layer on top of the Swing.

Smalltalk programmers enjoy legendary productivity comparing to their Java colleagues not only because of language itself, but also thanks to higher level libraries supporting application programming. See Cincom Smalltalk (former VisualWorks) documentation [6] or play with Smalltalk/X [5].

What are implications of low-level Swing usage, as one can see in miscellaneous tutorials and articles on the net?

  • Proliferation of inner listener classes. Flawed listener management, often leading to memory leaks (yes, also in Java!)

  • Proliferation of combo box, table and list model inner classes. Together with other inner classes they increase total application memory consumption.

  • Source code full of synchronizing user interface with application/domain model ("back and forth" formatting, parsing and value copying).

  • Absence of application level concepts like value binding, validation, buffering, undo.

  • Not very maintenable source code

  • Frustrated programmer

  • Possible performance/memory issues


Ensure application accuracy with Swing is not always straightforward

To make things even worse, Swing toolkit not always behaves as one would expect. Programmer often has to implement miscellaneous workarounds which further decrease code readability.

To illustrate the problem, below are few screenshots of very trivial application which implements typical use case: user enter some data and presses OK or Enter to close the dialog with good faith that data which she sees are data that really go to the application logic. She expects behavior shown on Figure 4. The first text field is instance of JFormattedTextField, the second is instance of JTextField managed by Mediator framework (the example is part of MVC Mediator Demo application which is freely available including source code on the web - see [3]).

Figure 4. Application with numeric text fields

Lets assume that the field in the application is optional and user decides to clear the value she entered. She expects null will be commited. But here is surprise: commited will be old value as shown on Figure 5! How it is possible? Class JFormattedTextField uses JDK NumberFormat to parse numbers, but this parsing class considers empty string to be illegal and throws an exception (this "feature" is one of annoyances of JDK libraries).

Figure 5. Bug: after closing the window, getValue() does not return null

Unfortunatelly the text field story does not end here. User enters some really big number, which does not fit to Integer boundaries. She can make a typo and add one more zero or just paste some big number from other application. Application programmer would consider this as an error, but JFormattedTextField silently lets overflow the value and commits complete nonsense. The bug is shown on Figure 6. Correct solution would be for example to disable show command (as can be seen with text field managed by Mediator).

Figure 6. Bug: after closing the window, getValue() returns complete nonsense


Introducing MVC Mediator

Chellenges mentioned in the first part of this paper requires reusable and robust and solution. In this section we present detailed overview of MVC Mediator with code examples. You will see, how easy and with few lines of code You can get required application features.


Example application

For illustration purposes lets consider hypothetical application which manages computer user accounts, as shown on Figure 7. The example is part of MVC Mediator Demo application which is freely available including source code on the web - see [3].

Figure 7. Users account management

Main classes of the application are shown on following listings:

Example 1. Listing of UserInfo.java

import java.beans.PropertyChangeSupport;

/**
 * Java Bean class holding basic information about user computer
 * account. It holds four values and one commad.
 */
public class UserInfo implements Serializable {
    private static final Integer DEFAULT_DISK_QUOTA = new Integer(512);
	
    private String fFullName;
    private String fLoginName;
    private String fHomeDir;
    private Integer fDiskQuota;
    private PropertyChangeSupport fChangeSupport =
                                      new PropertyChangeSupport(this);
    
    public void addPropertyChangeListener(PropertyChangeListener l) {
    	fChangeSupport.addPropertyChangeListener(l);
    }
    
    public void removePropertyChangeListener(PropertyChangeListener l) {
    	fChangeSupport.removePropertyChangeListener(l);
    }
    
    public String getFullName() {
    	return fFullName;
    }
    
    public void setFullName(String fullName) {
    	fFullName = fullName;
    }
    
    public String getLoginName() {
    	return fLoginName;
    }
    
    public void setLoginName(String loginName) {
    	fLoginName = loginName;
    }
    
    public String getHomeDir() {
    	return fHomeDir;
    }
    
    public void setHomeDir(String homeDir) {
    	fHomeDir = homeDir;
    }
    
    public Integer getDiskQuota() {
    	return fDiskQuota;
    }
    
    public void setDiskQuota(Integer diskQuota) {
    	fDiskQuota = diskQuota;
    	fChangeSupport.firePropertyChange("diskQuota", 
    	                                  null, diskQuota);    	
    }
    
    /**
     * Command - setup default quota.
     */
    public void useDefaultQuota() {
    	setDiskQuota(DEFAULT_DISK_QUOTA);
    }    
}	

Second model class (application model) of editor holds list of system users and details of current selected user. The class is incomplete (contains TODO: items) and will be refined later:

Example 2. Listing of UsersEditorModel.java

import java.util.Collections;
import java.util.List;
import java.beans.PropertyChangeSupport;

/**
 * Application model of user editor. Contains list of users to display,
 * single selected user and command to remove selected user.
 *
 * This class will be refined later.
 */
public class UsersEditorModel {

    private List fUsers = Collections.EMPTY_LIST;
    private UserInfo fSelectedUser;
    private PropertyChangeSupport fChangeSupport =
                                      new PropertyChangeSupport(this);
    
    public void addPropertyChangeListener(PropertyChangeListener l) {
    	fChangeSupport.addPropertyChangeListener(l);
    }
    
    public void removePropertyChangeListener(PropertyChangeListener l) {
    	fChangeSupport.removePropertyChangeListener(l);
    }
	
    public List getUsers() {
        return fUsers;
    }
    
    public UserInfo getSelectedUser() {
        return fSelectedUser;
    }

    public void setSelectedUser(UserInfo user) {
        fSelectedUser = user;
        fChangeSupport.firePropertyChange("selectedUser", 
    	                                  null, user);    	    
    	// TODO: what if user is null ????    	
    	//       what if user unremovable, like administrator
    	//       command for remove should be disabled in some way
    }
    
    public void removeSelectedUser() {
    	if (getSelectedUser() != null) {
            fUsers.remove(getSelectedUser());
    	}
    	// TODO: what about event ???
    	// TODO: what if selected user is administrator? 
    	//       System administrator cannot be removed!
    }
}
View is represented by UsersEditorUI class which will be shown later.


Basic concepts

Below we explain basic concepts needed to understand advantages of Mediator framework. Whenever it is possible, the concepts are illustrated with real code snippets from our example application.


Bound Property

Bound Property is term used in Java Beans specification to describe such value provided by bean which when changed, PropertyChangeEvent is fired.

Property selectedUser provided by getSelectedUser and setSelectedUser methods in listing of UsersEditorModel is example of bound property.


Binding

A binding is a connection betweeen application or domain model element and user interface element. We can recognize two kinds of bindings: value binding and command binding.

Main task of the value binding is to synchronize model and user interface: when model changes, user interface element is updated and when user changes user interface element, the change is propagated back to the model. In our example we want bind diskQuota property value to a text field or selectedUser property to selected row in a list.

A value binding can be buffered: user modification is stored in temporary buffer and model change is delayed until explicit commit. This allows to perform revert - abandon modified value and to re-fetch original value from the model. Note that buffering can be applied to any user actions: editing value in a textfield, selection of item in combo box, selection of multiply items in list, scrollbar movement or window resizing.

The value binding can also perform validation. Since it is responsible for setting values to the model, it can hold error and warning rules which examines before model updating. In case of error it should notify some handler that commit is not possible. In our example there is much to validate:

  • User loginName cannot be empty and its length must be in reasonable bounds (lets say 3-15). It can contain only ASCII characters and digits.

  • User diskQuota can be empty, but when filled it must be valid integer and also in reasonable bounds (between 10MB and 1GB).

  • User homeDir home directory should exist - if not, than it could be considered as warning.

A command binding connects action-type user interface item (like button, menu item, pressing OK or Esc on a keyboard) to the command implemented by model. The binding should trace command state and enable/disable given user interface item. Commands will be explained in more detail below. In our example commands are implemented as removeSelectedUser() and useDefaultQuota().


Binder

Binder is facade that builds and manages bindings. It centralizes validation, value commiting, contains single point for destroying of all bindings.

Binder is implemented in class com.danmich.bindf.Binder. In code snippets below binder will be presented by variable name binder .

Example of binder creation:

Example 3. Binder creation and clearing

public final class UsersEditorUI extends JPanel {

    /**
     * This binder is used for binding of currently selected user.
     */
    private Binder fUserBinder = Binder.createBuffered();  (1)
    
    /**
     * This is main binder, used for {@link UsersEditorModel}
     */
    private Binder fBinder = Binder.createUnbuffered();    (1)

    // Components, layout, etc.
    ...

    public void setModel(UsersEditorModel m) {
        fBinder.unbind();                                  (2)
        
        /* Bind "selectedUser" property from model to private 
         * "setSelected" value in this panel.
         * Because the method is private, we must to specify class name
         * too lookup method. And because this class is not designed 
         * for inheritance we can use "this" instead of full qualified 
         * class name.
         */ 
        fBinder.bindValues(                                (3)
                fBinder.valArgs(m).boundProp("selectedUser"),
                fBinder.valArgs(this).setter("this.setSelected")
                );
        
        ...
    }

    private void setSelected(UserInfo user) {        
        /* Remove bindings for previously selected user */
        fUserBinder.unbind();                              (2)
        
        // binding code
        ...
    }
    
    /**
     * Removes all links to model. Call this method to avoid memory leaks.
     */
    public void destroy() {
        fBinder.unbind();                                  (2)
        fUserBinder.unbind();                              (2)
    }
    
}
(1)
Here we create binders. Use of static factory method is preferred way for code readability reasons.
(2)
Clearing all bindings. This ensures, that all internally registered listeners are unregistered. After this call instance of the editor is gargbage collectable.
(3)
Binding definition example. When value of selected user in model changes, setter setSelected will be called.


Value access

User interface does not know, how to obtain value to display. The value can be stored somewhere as a bean property, item in a list or map, element of a xml document, result of SQL query etc. Value Access is abstraction used by binding to get value from a model, set value to the model and validate the value. Schema of IValueAccess interface is shown on Figure 8.

Value access needs model instance as a context for its accessing logic. For example method needs object on which it can be called (unless it is static) or XPath evaluation needs XML document. In the framework such object is called root.

Figure 8. Value access

Very important feature of value access is ability to form a chain, as shown on Figure 9. In this way it is possible to:

  • define arbitrary complex logic for value accessing. For example get hash map from the model, get number stored under some key in the hash map and format the number to the string. Note that this works also in opposite direction: parse given string to a number and put to the same map under the same key.

  • add behavior to the value accessing - like buffering (see explanantion above), adding validation rules, advanced type conversion and so one.

Figure 9. Value access chain

In our example root can be UsersEditorModel instance and value access chain of methods corresponding to root.getSelectedUser().getLoginName() or more complex root.getSelectedUser().getDiskQuota()<->Int2StringConverter

Example 4. Value binding

public final class UsersEditorUI extends JPanel {

    private JTextField fLoginName = new JTextField();
    private JTextField fFullName = new JTextField();
    private JTextField fHomeDir = new JTextField();
    private JTextField fDiskQuota = new JTextField();
    
    ...

    private void setSelected(UserInfo user) {        
        fUserBinder.unbind();
        if (user == null) {
            user = UserInfo.EMPTY;
        }
        BindingArgs.Builder valArgs = fUserBinder.valArgs(user);(1)
        fUserBinder.bindSingleValue(
                valArgs.boundProp("fullName"),             (2)
                fFullName
                );
        fUserBinder.bindSingleValue(
                valArgs.boundProp("homeDir")
                    .warn(new ValidDirectoryExists()),     (3)
                fHomeDir
                );        
        fUserBinder.bindSingleValue(
                valArgs.boundProp("loginName")
                    .err(new ValidStringLengthMax("Login name", 15))(4)
                    .err(new ValidStringLengthMin("Login name", 3)),(5)
                fLoginName
                );
        fUserBinder.bindSingleValue(
                valArgs.boundProp("diskQuota")
                    .name("Disk Quota")                    (6)
                    .errCompareTo(">=", new Integer(10))   (7)
                    .errCompareTo("<=", new Integer(1000)) (8)
                    .int2Str(),                            (9)
                fDiskQuota
                );
                
        ...
    }

    
}
(1)
Here we create builder for binding description for given model user. We reuse the builder for other bindings.
(2)
The simplest binding - just bind property fullName to text field fFullName.
(3)
Here we chain value access to bound property homeDir with custom validation warning which tests if given directory exists:
public class ValidDirectoryExists implements IStrFunction {

    public String eval(Object arg) {
        File f = new File((String)arg);
        if (f.exists()) {
            return (f.isDirectory()) ? null : arg + " is not directory.";
        }
        
        return arg + " does not not exist.";
    }

}    
(4)(5)
Binging of property loginName chained with with 2 validations controlling string size. String used in constructors is used for formatting error message.
(6)(7)(8)(9)
Binging of property diskQuota chained with with 2 validations controlling integer bounds. Finally chained with converter which converts between Integer and String (using int2String; there are other convertes which uses java.text.NumberFormat instances). Use of name is for formatting of error messages.


Commands

Domain or application model contains not only data but also implements commands (open, save, close, compute something...).

Command is presented on Figure 10. Command is invoked simply by executing method execute(). More advanced command is IAppCommand, which holds bound property enabled. When model simply disables the command it will be not execucted (even accidentaly) and appropriate user interface item to which the command is bound will be disabled. Binder holds list of commit commands (typically associated with OK button) and depending on validation state enables/disables them.)

Figure 10. Command

Commands in Java can be implemented as inner classes or methods. Most common aproach (at least in miscellaneous tutorials) is use of inner classes, but it is ineficient and inconvenient for programmer (in terms of amount of source code and readability). Framework supports both implementation styles with many helpers. Method style implementation is more convenient, does not lead to proliferation of listener classes, but there is price for it: they are not type safe (there is need to specify method name as string and there is no chance to verify at compile time if the method really exists).

In our example commands are implemented with methods

  • UserInfo.useDefaultQuota() - when invoked model is updated with default quota setting and the change is propagated to the corresponding text field

  • UsersEditorModel.removeSelectedUser() - this command should be enabled only if user in a list is selected and it is not unremovable user like administrator.

Command binding is showm on following listing:

Example 5. Command binding

public class UsersEditorModel implements Serializable {
    public final IAppCommand cmdRemoveUser =               (1)
        MethodCommand.simpleCall(this,
    			"this.removeSelectedUser").setEnabled(false);
    ...
    public void setSelectedUser(UserInfo user) {
        ...
        cmdRemoveUser.setEnabled(                          (2)
           user != null && user.isEditable());
    }
        
    private void removeSelectedUser() {
        fUsers.remove(getSelectedUser());
    }
}


public final class UsersEditorUI extends JPanel {

    private JButton fApply = new JButton("Apply");
    private JButton fRemoveUser = new JButton("Remove");
    private JButton fDefaultQuota = new JButton("Set default");    
    
    ...
    public void setModel(UsersEditorModel m) {
        ...                
        fBinder.bindCommand(m.cmdRemoveUser,               (3)
                fRemoveUser);
    }

    private void setSelected(UserInfo user) {        
        ...
        fUserBinder.bindCommand(                           (4)
                MethodCommand.simpleCall(user,"useDefaultQuota"),
                fDefaultQuota);
        fUserBinder.bindCommand(                           (5)
                fUserBinder.commitCmd(), 
                fApply);
        
        fDefaultQuota.setEnabled(user.isEditable());
        fApply.setEnabled(user.isEditable());
    }

    
}
(1)
The command is defined in model class as public final field. Method removeSelectedUser is for safety reasons made private.
(2)
Depending on user object editability, the cmdRemoveUser command is enabled or disabled.
(3)
Binding of cmdRemoveUser command to a button.
(4)
Binding of command which sets default quota. We define here command as method call adapter.
(5)
Binder provides default command for commiting of buffered values. We bind the command to the fApply button. If the binder validation will be in error state, the button will be automatically disabled.

Commands are not just pointers to zero argument methods. They can have bound arguments for method invocation. This allows to any method adapt to the command interface. In following listing user example

Example 6. Command with bound argument

public class FahrenheitToCelsiusDemoWindow extends WindowModel {    
    private FahrenheitToCelsiusModel fModel = new FahrenheitToCelsiusModel();
    
    public JComponent createContent() {
        FahrenheitToCelsiusUI ui = new FahrenheitToCelsiusUI();
        ui.setModel(fModel, 
                MethodCommand.simpleCall(this, "this.cmdClose", ui));(1)
        return ui;
    }
    
    private void cmdClose(FahrenheitToCelsiusUI ui) {
        ui.destroy();
        ...
    }
}

public class FahrenheitToCelsiusUI extends JPanel {
   private JButton fQuit = new JButton("Quit");
   ...
   public void setModel(FahrenheitToCelsiusModel model, IAppCommand close) {
      ...
      binder.onEvent(fQuit, SwingEvents.ACTION_PERFORMED, close);(2)
   }
}
(1)
We define command as call to cmdClose method with bound argument ui
(2)
Binding of the close command.


Observer strategy

There is one more element in the puzzle for binding to work: how to recognize, that model or user interface element changed and that update on opposite side of the binding should be performed. This problem solves design pattern called Observer design pattern (known also as Publisher/Subscriber). Binding framework can use any observer strategy which helps easily integration with existing solutions.

Framework strictly separates change event handling from model and value accessing. It means, that a model is not required to be an event source. For example hash map, list or XML document are perfect models, but they do not fire any events when they are updated. Object beeing an event source can be different than model and can implement any observer pattern You like.

In our example both UserInfo and UsersEditorModel are also event sources. For simple property changes they use java.beans.PropertyChangeEvent to signal property value changes.

Java has 2 observer patterns: java.util.Observable, java.util.Observer pair and widely used Java Beans style event source/listener pattern.

Framework contains event handling abstraction defined by IObserverPatternStrategy, with implementations for java.util.Observable, java.util.Observer and generic listener support for Java Beans style event handling. Other implementations are possible. Observer strategy is shown on Figure 11.

Figure 11. Observer Strategy

Framework contains definitions for most often used events in Swing and AWT. Adding support for other (custom) event listener types is matter of definition within 3 lines code. This is illustrated on Figure 12.

Figure 12. Support for miscellaneous event types

Lets return to our example and see, how to define binding which uses some event on source side:

Example 7. Explicit definition of observer strategy

    public void setModel(UsersEditorModel m) {
        fBinder.unbind();
        
        /* First, most convenient way
        fBinder.bindValues(
                fBinder.valArgs(m).boundProp("selectedUser"),
                fBinder.valArgs(this).setter("this.setSelected")
                );
         */ 
        
        /* Equivalent code, more explicit: */
        fBinder.bindValues(
                fBinder.valArgs(m).getter("getSelectedUser")(1)
                    .observerStrategy(JavaEvents.PROPERTY_CHANGE)(2)
                    .evtArg("selectedUser"),               (3)
                fBinder.valArgs(this).setter("this.setSelected")
                );
        ...
    }    
    
(1)
Value is accessed using public method getSelectedUser
(2)
Value needs to be refreshed, when java.beans.PropertyChangeEvent is fired.
(3)
We are interested only in java.beans.PropertyChangeEvent instances which have property name set to selectedUser.


Value Binding

We can summarize concepts explained above in schema of value binding, shown on Figure 13.

Figure 13. Value Binding


Working with lists

It is natural to represent list of items with arrays or implementation of java.util.List. On user interface side there are many components which conceptually works with lists (combo boxes, lists, tables, spin boxes, check box groups, radio button groups etc.). Binding framework implements mechanisms which allow seamlessly use of lists and arrays with any such components. For simplicity in further text we will discuss only lists, since framework uses list adapter for arrays.

Thanks to java.util.AbstractList it is very easy to create list adapter classes for existing structures and concepts. For example integer spin box with numbers 0..255 could use RangeList(0,255) object to define sequence of successive spin box values.


Value Converter

Lists often contain items which instances of complex classes, not just strings or numbers. In our example list of user names only would be useless. Instead we have a list of User instances. However list user interface element does not know, how to display User. Default Swing implementation uses Object.toString() method, which is not very good design idea.

List binding contains value converter which converts one value to another. The converters are mostly bidirectional and can be also chained like Value Acces to provide almost any desired conversion. Of course, You are free to implement Your own converters.

Binding framework contains 24 converters. There are following categories of converters:

  • string converters which converts numbers and dates (including SQL their variants) to/from string. This converters does not have annoyances of standard JDK formats - they do not throw exceptions for null values or empty strings. They work with validation part of framework, generates localizable error messages.

  • collection converters which converts between arrays and lists, tokenize strings into lists

  • casting converters converting generic java.lang.Number to concrete numeric types.

  • other converters like map based converter, method call based converter or generic converter chain.


List Selection

Each list type component support selection. Selection brings to model some value (values), in our example currently edited UserInfo object. Selection can be categorized from two points of view:

  • single - multiple:

    • single selection allows user to select zero or one item (combobox, spinbox, menu, radio button, list or table in single selection mode)

    • multiple selection allows user to select any number of items (list or table with multiple selection mode checboxes within group in menu or panel)

  • value - index based:

    • value based selection is used for models which are interested in selected values rather then indices (in our example it is currently selected UserInfo object). In case of multiple selection java.util.List instance is used to get/set from model (optionally array).

    • index based selection is used when model is interested in index of selected item (items). For single selection is used int number (-1 for empty selection), for multiple selection is used int [] array.

Framework supports all kinds of selections described above.


List Observer Strategy

For list binding there are 2 posibilities of observing models:

  • treat list instance like other model values and use any event to notify that list changed, without description of change details. In this case user interface component will be refreshed without any optimization. This is simple, easy to use solution and in most cases without performance problems. Below is version of our example which uses this technique:

    Example 8. List changes handled as other value changes

        /**
         * - class UsersEditorModel -
         * Refined method version, which now fires change 
         * event when user is removed
         */
        public void removeSelectedUser() {
            if (getSelected() != null) {
                fUsers.remove(getSelected());
                fChangeSupport.firePropertyChange(             (1)
                                   "users", null, fUsers);
            }
         }
    (1)
    Notifies about list content change using PropertyChangeEvent.

  • Sometimes for performance reasons must be information about list change more specific - what interval was changed, removed or inserted. For such structural change description framework defines ListChangeEvent and ListChangeListener with contract specified by IListObserverStrategy interface and default implementation. There are 3 ways, how to support this kind of list observing on model side:

    1. Most convenient way. Good news is, that You can use provided list decorator which takes care of event notification:

      Example 9. Transparent List changes through ListChangeDecorator

      public class UsersEditorModel {
      
          private List fUsers = Collections.EMPTY_LIST;
      	
          public UsersEditorModel() {
              List list = readListFromSomewhere();
              fUsers = new ListChangeDecorator(list);            (1)
          }
      	
          public void removeSelectedUser() {
              if (getSelected() != null) {
                  fUsers.remove(getSelected());                  (2)
              }
           }
      	
      (1)
      This is creation of list decorator which itself implements java.util.List interface. It forwards all calls to underlying (decorated) list but additionally fires appropriate ListChangeEvents when the list is modified.
      (2)
      This method removes the item from decorated list and fires ListChangeEvent.

    2. You can have full control how and when list change events are fired:

      Example 10. Explicit list event handling in model

      public class UsersEditorModel {
      
          private List fUsers = Collections.EMPTY_LIST;
          private ListChangeSupport fListChangeSupport =         (1)
                                       new ListChangeSupport(this);
      	
          public void addListChangeListener(ListChangeListener listener) {
              fListChangeSupport.addListChangeListener(listener);
          }
          
          public void removeListChangeListener(ListChangeListener listener) {
              fListChangeSupport.removeListChangeListener(listener);
          }
          	
          public void removeSelectedUser() {
              if (getSelectedUser() != null) {
                  int index = fUsers.indexOf(getSelectedUser());
                  fUsers.remove(index);
                  fListChangeSupport.fireIntervalRemoved(        (2)
                                         "users", index, index);
              }
           }
      	
      (1)
      This is instantiation of support class which handles listener management and contains approriate fireXXX methods.
      (2)
      This method fires ListChangeEvent to notify registered listeners (binder) about structural change in the list of users.

    3. Since IListObserverStrategy is interface You can implement support for Your own list type events with custom strategy.


List binding example

Below is example, how to bind list of users to a list component (the same binding code would work for combo box or spinner):

Example 11. List binding

public final class UsersEditorUI extends JPanel {

    private JList fUserList = new JList();
    ...

    public void setModel(UsersEditorModel m) {
        fBinder.unbind();
        
        fBinder.bindListSingle(
                fBinder.listArgs(m).prop("users")          (1)
                    .itemGetter("getLoginName")            (2)
                    .selBoundProp("selectedUser"),         (3)
                fUserList);
        
        ...
    }

    
}
(1)
We set model with listArgs(m) and define list of users as model property users.
(2)
We chain default converter with getter getLoginName to define that list will show user login names instead of default toString() values.
(3)
Selected user is model bound property selectedUser. If the property would be provided by another model instance we had to add selRoot(anotherModel) to the binding chain.


Framework extensilibility

All previous examples used easy, functional type facade API. Using several simple chained method calls with self describing method names is much more readable and robust then creating and wiring many small objects together manually. Furthermore, it decreases amount of classes needed to learn by programmer.

Data binding domain contains four orthogonal concepts: data access, data types, event handling and user interface components. So if there is N ways of accessing data source, M ways of event handling, K data types and L user interface komponents than total amount of possible binding scenarios is N*M*K*L! Mediator algorithms works with interfaces modeling each of this orthogonal "axes":

  • IValueAccess for data source accessing

  • IObserverStrategy for event handling

  • IValueConverter for data type conversion

  • several interfaces specifying component adapters

In result problem of N*M*K*L cases is reduced to N+M+K+L implementing classes. If You add support for another user interface komponent, You immediately gain binding support for all N data sources. This framework components can be seen on the overall schema shown on Figure 14 (blue boxes represents classes of Your interest).

Figure 14. Mediator Framework schema

Open architecture of the framework is solid foundation for implementation of enhancements in any direction:

  • other user interface toolkits or component suites (SWT)

  • other data models (web services, SQL databases)

  • other event models (enterprise messaging like JMS)


Conclusion

We could see that MVC Mediator solves many problems which client programmers encounter in their daily work.

But it is just beginning. The framework was designed with long term plans. We would like extend framework more toward enterprise features - accessing SQL data bases and Web Services. If there will be demand we will implement bindings to SWT toolkit, to well know graphics and chart libraries. Everything is possible, but it depends solely on You: potential framework user. By licensing framework and providing valuable feedback You support its further development. See [3] website to consider licensing options.


References

Books

[1] Sun Microsystems, Inc., 2001, 0-201-77582-4, Addison-Wesley, JavaTM Look and Feel Design Guidelines: Advanced Topics, http://java.sun.com/docs/books/jlfadv/.

[2] Steven John Metsker, 2002, 0-201-74397-3, Addison-Wesley, Design Patterns JavaTM Workbook.


Web Resources

[3] MVC Mediator Homepage, http://www.danmich.com/mvcmediator .

[4] Scope HMVC framework Homepage, http://scope.sourceforge.net .

[5] Smalltalk/X Homepage, http://www.exept.de/exept/english/Smalltalk/frame_uebersicht.html .

[6] Cincom Smalltalk Homepage, http://smalltalk.cincom.com/index.ssp .

[7] Swing Architecture, MVC pattern explanation, http://java.sun.com/products/jfc/tsc/articles/architecture .

[8] Swing Short Course part 2, MVC pattern explanation, http://java.sun.com/developer/onlineTraining/GUI/Swing2/shortcourse.html .