http://www.developer.com/

Back to article

Manipulating User Data at the Model Layer in Swing


June 1, 2005

Most of the graphical user interfaces (GUIs) developed with the Java Swing toolkit accept user input and validate it. Swing is built on the principle of the separable model architecture, a version of Model-View-Controller design where all of the graphical components have corresponding data model objects. The user's data has to be stored in the model layer before it can be accessed or presented in the view layer. It can be also manipulated, changed, or checked before it is put in the model objects. Please see my other article, "Creating Interactive GUIs with Swing's MVC Architecture," for more information about separable model architecture.

In this article, I will show how to use Java Swing data models for components that accept user input, to automatically manipulate it before it is put in the model. In particular, I will cover the limitation of its input to predefined subsets of characters, memory management for large data sets, and filtering of the input data. All of the principles are based on the object inheritance and can be applied to any component that follows the MVC paradigm.

Note that even though Swing is inheritably not thread safe, some atomic operations have been added to it. An atomic operation or code section will be finished by the current thread and not interrupted by other threads. Assignment to variables smaller than 32 bits is an atomic operation, which excludes variables of types double and long (both are 64 bits). As a result, atomic operations do not need synchronization. Because any enterprise-scale Swing program should be and often is multithreaded, data updates of the GUI are likely to be done by multiple threads. Therefore, Swing developers have added atomic components that have synchronized assignment operation already built in. But, I will assume a single thread data assignment in this article.

Limiting User Input

One of the most common problems with GUIs is limiting or filtering user entered data. For instance, consider a text field that accepts only numeric values, or a password field that accepts character values only. To solve this problem, you need to understand the data flow in the Swing framework. In general, when data comes in to the model layer from any source such as database, file, or I/O stream, it is put in the model via a public API of that object. As soon as model object's content is changed, it fires a notification event and the corresponding View object(s) update their presentation after receiving the event.

Therefore, there is a point where the public API of any model object is called to update the model. To limit character sets or establish an allowable subset, several steps need to be taken. First, you need to pre-empt the data at the place where it is entered (the call to the API). Second, you need to manipulate it before it is put in the model. For example, a JTextField uses a Document model that has a public insertString(int offset, String str, AttributeSet a) method. Please see the JDK API doc for more information on this method.

If you want to manipulate the character set that is coming into the JTextField or filter it, you can subclass the Document object or any of its sub-classes and overwrite the insertString method. For example, create a new "Document" type model and set it to a JTextField:

// PlainDocument extends Document
public class NumberDocument extends PlainDocument
{
...
public void insertString(int offset, String stringToInsert,
                         AttributeSet attributes)
                        throws BadLocationException {
   //This rejects the entire insertion if it is not an integer
   //between the min and max values inclusive.

   try {
      Integer.parseInt(stringToInsert);
   }
   catch (NumberFormatException e) {
      return;    //or beep here
   }

      if ( ((getLength()+stringToInsert.length()) >= mMinInteger)
          && ((getLength()+stringToInsert.length()) <= mMaxInteger)) {
      super.insertString(offset, stringToInsert, attributes);
   }
}
...
JTextField tf = new JTextField();
tf.setDocument(new NumberDocument(1, 10));

See the included source code for a complete listing.

This text field will essentially not let the user to type in anything but numeric characters into it. Additional checks can be done as well, such as the range, value, or sign of the number. The model can even be made to beep on incorrect data entry as is the case of the user pressing a non-numeric key on keyboard. Overwriting the insert API can be certainly extended to other components as well.

Memory Management for Large Textual Data Models

Using the same approach for larger text components, such as JTextArea, can be beneficial not only when dealing with data manipulation, but also with memory management. The API that I overwrote to demonstrate this example is actually not from the model object itself, but from the component. However, it checks or consults the model before an operation so the principle is the same. Consider a main log window of a large GUI application, where there is a JTextArea into which all application-generated messages are posted. An application with a heavy operational load, running for an extensive period of time, can generate a lot of log information. If text area's data model object does not implement any memory management, JVM will run out of memory over time. The default API for the insert operation into the JTextArea is the append() method. Checking the size of the data in the model and decreasing or removing it periodically before appending will solve the memory hungry text area problem. The removal of data does not have to be total; it could be some percentage of the total number of characters. The text model would behave as a FIFO—first in, first out—queue.

For example, here is text area component that will remove the first 5000 characters if the total length of the data exceeds 10000 characters. And, it conveniently sets the CaretPosition to the end, so that if a scroll pane is used, it will automatically scroll to the end. Note that this object is also a singleton.

public class GlobalLogBean extends JTextArea {
   private static GlobalLogBean globalMB;
   private int DOC_MAX = 10000;

   private GlobalLogBean() {
      super();
      this.setEditable(false);
   }

public static GlobalLogBean getGlobalMBean()
{
   if (globalMB == null) {
      globalMB = new GlobalLogBean ();
   }
   return globalMB;
}

public void append(String text)
{
   try
   {
      if (GlobalLogBean.this.getDocument().getLength() >= DOC_MAX)
      {
         GlobalLogBean.this.getDocument().remove(0, 5000);
      }
      super.append(text+"\n");
      GlobalLogBean.this.setCaretPosition(
         GlobalLogBean.this.getDocument().getLength());
   }
   catch(Exception e)
   {
      e.printStackTrace();
   }
}

Filtering Data in Tables

Some Java APIs already make the concept of overwriting methods in API sub-classes for filtering purposes a requirement or useful extension to their functionalities. Consider a Java.io.FileFilter interface; it provides an accept method that has to be overwritten to filter a directory structure on the file system by files only.

For example:

public class FileOnlyFilter implements FileFilter {
   public FileOnlyFilter() {  }
   public boolean accept(File f) {
      if (f != null) {
         return f.isFile();
      }
      else {
         return false;
      }
   }
}

This can be used in components such as file choosers, but it's not limited to them. For instance, if an application has a table (or other component) that needs to show a list of only files and not directories, FileOnlyFilter can be used in a model class for that component when browsing for files or creating the list.

// some where in code for DirectoryModel that extends
// AbstractTableModel
public void setDirectory(File dir) {
   if (dir != null) {
      directory = dir;
      File[] files = directory.listFiles(new FileOnlyFilter());
      children = new String[files.length];
      for (int i = 0; i < files.length; i++) {
         children[i] = files[i].getName();
      }
   
fireTableDataChanged();
...

Conclusion

The ability to modify data before it is put in the model object, is a very powerful concept. In this article, I've described several applications of this technique, in particular filtering and memory management. I believe that they are the most useful for a Swing developer and any GUI application can benefit from this method because the concept of overwriting key API can be applied to most of the model objects.

Download the code

You can download the source code used in this article here.

About the Author

Vlad Kofman is a Senior System Architect working on projects under government defense contracts. He has also been involved with enterprise-level projects for major Wall Street firms and the U.S. government. His main interests are object-oriented programming methodologies and design patterns.

Reference

Java Swing, 2nd Edition, by Marc Loy, Robert Eckstein, Dave Wood, James Elliott, Brian Cole. 2nd Edition. November 2002. ISBN: 0-596-00408-7

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date