HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

Data-driven - or data-aware - components are objects that listen for changes in the data and notify other data-driven components that have requested to listen. This design is a powerful means of maintaining an application throughout not only the first development cycle, but also subsequent cycles as your product becomes more robust and refined.

A component can be either a "behind-the-scenes working object" that loads and manipulates data directly or a "user interface tool" that you can click on or type in. Classes that need to send or receive data change notifications should be made into data-driven components.

Some basic overhead is involved in accomplishing this, including superclassing specific core Swing data models that are needed for your application. The alternatives often lack the flexibility to easily maintain your application and the robustness to create a professional program.

In this article, I'll discuss Swing data models along with a custom data model to handle our business objects and interact with our middleware. Because of the scope of this topic and the infinite requirements possible, a specific code-complete implementation won't be offered. However, I will attempt to describe the design process in-depth enough to empower you with working tools. The overall architecture used in this article is three-tier. I won't cover the use of CORBA, RMI or other middleware implementations for your business logic, but the design is intended to handle each of these multitier designs.

I would like to note that this article isn't directed toward a person unfamiliar with Java or Swing, but rather a developer inexperienced with business application design in Java and Swing.

From Problem to Solution
Every business application requires data to be connected to the user interface. Before you write any code, you have to determine what is appropriate for the task and the team. A solution that can be programmed fairly quickly and offers good performance might be inflexible and time consuming to maintain. I'm not implying that a robust, maintainable solution has to be difficult to design and develop, but it needs to be thought out carefully. One of the most challenging aspects of data-driven components is proper design. Poor implementation can lead to elusive bugs and poor performance.

A programmer who's developing an application alone might choose to create a handful of methods and event handlers in the body of the application that are responsible for populating the user interface and delegating data. Figure 1 demonstrates this simple design. As the application grows, these methods become unwieldy with business logic and presentation preparation. Eventually, the programmer will be faced with the challenges of an inflexible design.

Figure 1
Figure 1:

Java is object-oriented, and offers a solution - move data and user interface elements of the application into their own classes. Each user interface component is responsible for presenting its own data properly. The data component is responsible for offering the means for other components to readily access the data. In Swing, the components are a bit more sophisticated and offer us an easy way to separate the data aspect of the visual components. The "data component" of the interface is the Swing model for the specific Swing view and controller.

A team of programmers confronted with the same problem as the lone programmer might choose to divide the work by individual interface and data components. The developers creating the data objects are responsible for managing the content and defining the methods others will use to access the data. The other team members will code the visual components and add public methods for other classes to update or change the content. The components are responsible for staying current with their own data object, and every data object is responsible for tracking its own data. Figure 2 illustrates the design concept.

Figure 2
Figure 2:

This approach has a flaw. If the team needs to add and remove components, multiple developers are required to make changes in numerous places. The programmer who creates new components must wait for the person responsible for their data component to add knowledge of the new object. If the design is fluid and has the potential to change, as nearly all designs do, days or even weeks can be wasted with minor component swapping. Another serious issue lies in keeping the data distributed. When multiple client applications are accessing data and the data changes, messages need to be sent to the graphical interface to show the new information. These messages in the above approach would have to travel through the methods that are hard-coded into the data objects.

You need a common means by which the components can communicate. The data objects need to have a common inherited ancestor, along with the base class that gives them their core functionality. Java doesn't support multiple inheritance, like C++, but there is a solution. An interface can be written that puts in methods you know will be present in all your data-aware components. These methods will do the core work of getting and setting information in a way that's compatible with the specific object that contains it.

You now wield a common means for creating each data component, but you still need to reduce the work of swapping components in and out, and build a highway for messages to travel. A custom event can trigger listeners to call methods in the event's source. You can use the event to transfer notifications between the data-aware components.

Every object that receives data should be both a listener and an event notifier to its own listeners. The components can inherit an interface to be an event listener and hold an instance of a component to fire events to listening objects.

Your graphical interface is composed of sets of visual components. Swing components have three pieces: model, view and controller. The model is what we're interested in at this point. We can override the core models used by the Swing components in our application so they are data listeners to our custom data model. The custom data model is responsible for creating and storing business objects gathered from the middleware (which might be built around CORBA, RMI, custom socket servers, etc.) and reflecting data messages to the Swing models. An application-specific challenge of overriding the Swing data models involves giving them the constructors and methods that allow you to point at select business objects from the custom data model and choose the proper criteria to narrow that selection.

Business objects are also inherited from a common ancestor that allows our custom data model to manipulate, transfer and inspect them in a consistent means. These data objects are responsible for maintaining relationships with other business classes and displaying information through their own business logic. They are key to our design, since they allow our Swing models to be constructed with specific business constructs in mind.

Now you have the basic concepts of the data model.

Getting Technical
Data-driven components allow for a more flexible and robust means of creating visual components, as well as data objects, without prior knowledge of what the end combination of objects will be. By creating a generic data event model capable of firing events to any component the developer chooses to tie in, the application design becomes more flexible and, over the development cycle, more robust. Both the custom data model and the overridden Swing data models are considered data components. A data component fires events to update, destroy, reload or filter information while the receiving data component can communicate back through the data component interface methods. If you have only a few graphical components and one data object, you never need to change the design, and the data never changes unless the component tells it to; component-driven data is acceptable. However, in a distributed environment where data resides in a central network location and is manipulated by multiple users at multiple locations, the data model must be capable of notifying all the components tied to it. Figure 3 depicts a typical environment for real-time Java applications. If a client application sends an event that updates the data, the middleware must signal the other clients' custom data models to update. CORBA developers refer to this as a callback. In an application not using real-time updating, the responsibility for data synchronization sits on the client side, where it must periodically poll the database. The balance is between concurrency - the number of users who can access the data at one time - and consistency - the accuracy of the data at any moment in time.

Figure 3
Figure 3:

Every component that deals with data implements two interfaces that contain the common methods required to communicate. The first interface needs to give us a way of identifying our components as "data-driven" and the common methods to communicate. The definition might begin:

public interface dataComponent extends dataListener {
public void addDataListener(dataComponent l);
public Vector getData();
public void setData( Vector data );

The needs of each application will differ, but the basic ability to get and set data is essential for both our Swing models and our custom data model. You can add methods to accomplish any functionality that other dataComponents might want to perform on each other. For example, you might add methods to reset the component to its initial state or clear all data. The method for addDataListener will be described later, but for now you must accept that you need the ability to add dataListeners.

The other interface your components will require is an event listener. Listing 1 shows an interface that requires your components to define methods for data destruction, updates, loads or filters. The exact events that you need will vary, but a few examples are provided to make it easier to understand. The dataComponent extends the dataListener, since we want to ensure that all components are also listeners.

All of the dataListener methods receive a dataEvent. Listing 2 shows an example definition. The event is a simple object that tells the listener what change has occurred and where. The source is a very useful object since it allows us to invoke methods from the caller. Listing 3 has the code for the dataNotifier. Notice that it won't fire the event if the source doesn't belong to a dataComponent. Thus, you have methods at your disposal from the source object that will exist because only a dataComponent can create a dataEvent. Since Java does not support multiple inheritance, the code for firing events was encapsulated in an object that I named a dataNotifier, which can be instantiated in every dataComponent. Rewriting the code in all of the data-aware components would be tedious and error-prone if it needed to be changed later - and just plain time consuming to type in.

The constructors of our Swing data models are an important aspect of our design. We need the ability to choose the type of business objects we're interested in, and what criteria they must meet. Our custom data model is responsible for being a data proxy and distributor of all the business objects that we've already loaded. It also retrieves and distributes new business objects from the middleware.

When building your business objects, take into account that they will eventually need to be turned into displayable information, and it may help to have standard methods that all of your business classes contain. The Swing TableModel is an easy case to remember, but the TreeModel might give you some serious headaches. I recommend adding methods that can retrieve a value at a specified column as well as the column name, and also return the type of that column - very similar to the getColumnClass(), getValueAt(), and getColumnName() methods of the TableModel. If you plan on tackling the TreeModel, plan carefully because this is one of the most challenging of the Swing models.

Finally, we need to wire the components together. Every dataComponent requires the method addDataListener. In one central location, where the models are instantiated, you can call addDataListener with another dataComponent - your custom data model, which is a superclass of a dataListener. Your Swing GUI components can instantiate data objects with parameters that specify the business objects we are interested in and the criteria they must meet. You can create very complex designs where custom data objects connect to each other, and multiple overridden Swing models; however, that is beyond the scope of this article.

Basic Design Considerations
When you tackle the design of your application, be certain to review it closely. Often bugs are created because events reflect back from the data object to the event caller, which the programmer didn't anticipate. Check to make sure only those components that need to be listening are connected, since it's possible to generate a considerable amount of traffic with multiple data objects and hundreds of user interface components. Be wary of creating infinite event loops: dataComponent A triggers an event that dataComponent B receives and sends back to dataComponent A, and dataComponent A sends the same event to dataComponent B again. In most cases, you need only one overridden Swing model of a given type, since it's designed to handle data from a generic dataComponent. Your constructors should be doing the work of picking data constraints and converting the business objects to be displayed for your specific needs.

Java is object-oriented. Use it. Design modularity into your application and this will keep bugs from replicating themselves. You might laugh, but I've seen the same bug-ridden code cut and pasted everywhere in an application. Certainly, there is a performance cost to calling methods, but there are severe development costs to creating spaghetti code.

One major performance consideration is object creation. Java is very slow at instantiating new objects. I have seen static declarations of the different events in each component so they don't need to be reallocated with every change. I have not benchmarked this design myself, but I can guess that in very busy components that fire many events, allocating the event objects once might save you significant processing time.

Last Words
Solving the issues brought about by the need for rapid development and creating maintainable code is difficult. "Hard-coding" knowledge of specific components throughout your application is a dangerous task, since it's possible the components might not survive the duration of the project before they're swapped out in favor of better or different objects. Data-driven components handle this by creating a highway for events to travel back and forth without regard to the specified objects tied in. This generic interface allows developers freedom from delays while specific interfaces are built for their individual components.

Finally, data-driven components give us the power to update information from the middleware all the way to the user's screen in a seamless and elegant fashion.

About the Author
Erik Hyrkas is a full-time consultant in St. Paul, Minnesota, and has been developing Java intranet applications and applets for the business community since the release of the language. He can be reached at [email protected]


Listing 1.
public interface dataListener 
extends java.util.EventListener { 
  public void dataDestroy(dataEvent event); 
  public void dataUpdated(dataEvent event); 
  public void dataLoad(dataEvent event); 
  public void dataFilter(dataEvent event); 

Listing 2.
public class dataEvent 
extends java.util.EventObject { 
  protected int id; 
  public static final int DESTROY = 0, 
              UPDATED = 1, 
              LOAD = 2, 
              FILTER = 3; 

  public dataEvent(Object source, int id) { 
   this.id = id; 

  public int getID() { return id; } 

Listing 3.
public class dataNotifier { 
  protected Vector dataListeners = new Vector(); 

  public dataNotifier() { } 

  public void addDataListener( dataListener listener ) { 
   dataListeners.addElement( listener ); 

  public void removeDataListener( dataListener listener ) { 
   dataListeners.removeElement( listener ); 

  public void notifyListeners( dataEvent event ) { 
   // only dataComponents can fire dataEvents, 
      since dataEvents will need to call 
   // methods from the dataComponent interface. 
   if ((event.getSource() instanceof dataComponent) == false) 

   // check if there are listeners 
   if (dataListeners == null || dataListeners.size() == 0) 

   // protect the state of the vector of listeners 
   Vector listeners = (Vector)dataListeners.clone(); 

   // tell each listener an event has occured 
   for (int current = 0; current < listeners.size(); current++) { 
     switch(event.getID()) { 
     case dataEvent.DESTROY: 
      ((dataListener)listeners.elementAt( current )).dataDestroy( event ); 
     case dataEvent.UPDATED: 
      ((dataListener)listeners.elementAt( current )).dataUpdated( event ); 
     case dataEvent.LOAD: 
      ((dataListener)listeners.elementAt( current )).dataLoad( event ); 
     case dataEvent.FILTER: 
      ((dataListener)listeners.elementAt( current )).dataFilter( event ); 



All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.