You are receiving this e-mail because you elected to receive e-mail from Sun Microsystems, Inc. To update your communications preferences, please see the link at the bottom of this message. We respect your privacy and post our privacy policy prominently on our Web site http://sun.com/privacy/

Please do not reply to the mailed version of the newsletter, this alias is not monitored. Feedback options are listed in the footer for both content and delivery issues.
  Welcome to the Core Java Technologies Tech Tips.
Core Java Technologies
TECHNICAL TIPS
December 14, 2005
View this issue as simple text
In This Issue
 
Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).

This issue covers:

» Filtering JList Models
» Deques

The Filtering JList Models tip was developed using Java 2 Platform, Standard Edition Development Kit 5.0 (JDK 5.0). You can download JDK 5.0 at http://java.sun.com/j2se/1.5.0/download.jsp.

The Deques tip was developed using a snapshot release of Java Platform, Standard Edition 6 (Java SE 6). You can download a snapshot release of Java SE 6 at http://jdk6.dev.java.net/

This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc.

See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms.

For more Java technology content, visit these sites:

java.sun.com - The latest Java platform releases, tutorials, and newsletters.

java.net - A web forum where enthusiasts of Java technology can collaborate and build solutions together.

java.com - Hot games, cool apps -- Experience the power of Java technology.

FILTERING JLIST MODELS
 

The November 15, 2005 Tech Tip Sorting and Filtering Tables showed you how to use some new features in Java SE 6 for sorting and filtering JTable models. There is no new feature in Java SE 6 for sorting and filtering a JList. However in this tip, you'll learn how to do something similar in J2SE 5.0 with a JList.

A common technique for prompting users to filter elements in a long list is to show a JTextField with the list. As the user types in the JTextField, the elements shown in the list are reduced to the set of matching entries.

Filtering List

The implementation of this type of feature for a JList requires two supporting elements: a model that filters its elements based on some text, and a text component that triggers the filtering action when the user types in a field.

Implementing the input field is the easier of the two, so it will be shown first. With the Swing component set, the model for a JTextField is a Document. To monitor input to the Document, you attach a DocumentListener to the model. There are three methods for this listener, allowing you to react differently for input, removal, and change events:

  • public void insertUpdate(DocumentEvent event)
  • public void removeUpdate(DocumentEvent event)
  • public void changedUpdate(DocumentEvent event)
The changeUpdate() method is related to attribute changes in the model. These can be ignored. Because the same filtering action needs to happen for the two other methods, they just need to call the same method to be created on the custom model. Here's the full definition of the JTextField to be associated with the filtering JList:

   JTextField input = new JTextField();
   String lastSearch = "";
   DocumentListener listener = new DocumentListener() {
     public void insertUpdate(DocumentEvent event) {
       Document doc = event.getDocument();
       lastSearch = doc.getText(0, doc.getLength());
       ((FilteringModel)getModel()).filter(lastSearch);
     }
     public void removeUpdate(DocumentEvent event) {
       Document doc = event.getDocument();
       lastSearch = doc.getText(0, doc.getLength());
       ((FilteringModel)getModel()).filter(lastSearch);
     }
     public void changedUpdate(DocumentEvent event) {
     }
   };  
   input.getDocument().addDocumentListener(listener); 
Instead of limiting the usage to a JTextField created by the JList, an installJTextField() method is provided that associates the listener to the given component, and an uninstall method for removing the listener. This allows the user of the filtering list to offer its own JTextField, instead of creating the default one.

  public void installJTextField(JTextField input) {
     input.getDocument().addDocumentListener(listener);
   }

   public void unnstallJTextField(JTextField input) {
     input.getDocument().removeDocumentListener(listener);
   }
The filtering model comes next, with its filter() method required by the DocumentListener. For this, you simply need to maintain two lists of elements: the source list and the filtered list. With the help of AbstractListModel, you need to implement a few methods, as follows:

  • Constructor
  • Adding method to add elements to the model
  • getSize() to get its size
  • getElementAt() to get an element back.
The constructor creates the two List objects. It doesn't matter what the elements in the List are, so create them as a List of Object types:

    List<Object> list;
    List<Object> filteredList;

    public FilteringModel() {
      list = new ArrayList<Object>();
      filteredList = new ArrayList<Object>();
    }
Adding elements to the model is done by adding them to the source model, and then telling the model to filter itself. This could be optimized to only filter the new element, however for now, adding an element calls the same filter() method as is called to filter the entire list. (Note that input into the Document through the DocumentListener calls filter() to filter the entire list.) So even though you are adding one element, it still clears the entire list, and adds each element that matches the last search term (which might be empty for a new list).

    public void addElement(Object element) {
      list.add(element);
      filter();
    }
The size of the list model is the filtered list size, not the source size:

    public int getSize() {
      return filteredList.size();
    }
As is the case for getting the size, getting an element fetches it from the filtered list, not the original source list. This works provided that you don't go past the end of the list:

    public Object getElementAt(int index) {
      Object returnValue;
      if (index < filteredList.size()) {
        returnValue = filteredList.get(index);
      } else {
        returnValue = null;
      }
      return returnValue;
    }
The final filter() method provides the bulk of the work. Because you won't know if the search string is expanding or contracting the result set, it is easiest to clear out the filtered list and add items from the source list that match the input field. Matching could be done from the start of the string or anywhere in the text. Here is an example of the latter search method. It allows "A" to find both elements that start with "A" or have a capitalized "A" anywhere.

    void filter(String search) {
      filteredList.clear();
      for (Object element: list) {
        if (element.toString().indexOf(search, 0) != -1) {
          filteredList.add(element);
        }
      }
      fireContentsChanged(this, 0, getSize());
    } 
The searching here is case-sensitive. In addition to changing the searching to the start of the string, you can also modify it to be case insensitive.

You can also sort the results after adding the elements to the filtered list. This requires you to know something about the contents of the model. At present, searching uses the toString() results -- it does not assume that the model contains elements of a Comparable type, like a String, that can also be sorted.

The complete filtering JList is shown next with its ListModel as an inner class. The custom ListModel is also the DocumentListener for the text component. This might seem odd at first because the filtering is localized to the model, but it appears to be the best place to define the behavior.

   import javax.swing.*;
   import javax.swing.text.*;
   import javax.swing.event.*;
   import java.util.*;
   
   public class FilteringJList extends JList {
     private JTextField input;
   
   public FilteringJList() {
     FilteringModel model = new FilteringModel();
     setModel(new FilteringModel());
   }
   
   /**
    * Associates filtering document listener to text
    * component.
    */
   
    public void installJTextField(JTextField input) {
      if (input != null) {
        this.input = input;
        FilteringModel model = (FilteringModel)getModel();
        input.getDocument().addDocumentListener(model);
      }
    }
   
   /**
    * Disassociates filtering document listener from text
    * component.
    */
   
    public void uninstallJTextField(JTextField input) {
      if (input != null) {
        FilteringModel model = (FilteringModel)getModel();
        input.getDocument().removeDocumentListener(model);
        this.input = null;
      }
    }
   
   /**
    * Doesn't let model change to non-filtering variety
    */
   
   public void setModel(ListModel model) {
     if (!(model instanceof FilteringModel)) {
       throw new IllegalArgumentException();
     } else {
       super.setModel(model);
     }
   }
   
   /**
    * Adds item to model of list
    */
   public void addElement(Object element) {
     ((FilteringModel)getModel()).addElement(element);
   }
   
   /**
    * Manages filtering of list model
    */
   
   private class FilteringModel extends AbstractListModel
       implements DocumentListener {
     List<Object> list;
     List<Object> filteredList;
     String lastFilter = "";
   
     public FilteringModel() {
       list = new ArrayList<Object>();
       filteredList = new ArrayList<Object>();
     }
   
     public void addElement(Object element) {
       list.add(element);
       filter(lastFilter);
     }
   
     public int getSize() {
       return filteredList.size();
     }
   
     public Object getElementAt(int index) {
       Object returnValue;
       if (index < filteredList.size()) {
         returnValue = filteredList.get(index);
       } else {
         returnValue = null;
       }
       return returnValue;
     }
   
     void filter(String search) {
       filteredList.clear();
       for (Object element: list) {
         if (element.toString().indexOf(search, 0) != -1) {
           filteredList.add(element);
         }
       }
       fireContentsChanged(this, 0, getSize());
     }
   
     // DocumentListener Methods
   
     public void insertUpdate(DocumentEvent event) {
       Document doc = event.getDocument();
       try {
         lastFilter = doc.getText(0, doc.getLength());
         filter(lastFilter);
       } catch (BadLocationException ble) {
         System.err.println("Bad location: " + ble);
       }
     }
   
     public void removeUpdate(DocumentEvent event) {
       Document doc = event.getDocument();
       try {
         lastFilter = doc.getText(0, doc.getLength());
         filter(lastFilter);
       } catch (BadLocationException ble) {
         System.err.println("Bad location: " + ble);
       }
     }
   
     public void changedUpdate(DocumentEvent event) {
     }
    }
   } 
At this point you need a test program. The key part of the program is the following six lines. The lines create the JList, add it to a JScrollPane, and then add that and the associated input field to the screen:

   FilteringJList list = new FilteringJList();
   JScrollPane pane = new JScrollPane(list);
   frame.add(pane, BorderLayout.CENTER);
   JTextField text = new JTextField();
   list.installJTextField(text);
   frame.add(text, BorderLayout.NORTH); 
The bulk of the test program adds elements to the model. Here the model consists of the gifts from the twelve days of Christmas, the names of Santa's reindeer, the London underground railway lines, and the Greek alphabet.

   import javax.swing.*;
   import java.awt.*;
   import java.awt.event.*;   
   
   public class Filters {
     public static void main(String args[]) {
       Runnable runner = new Runnable() {
         public void run() {
           JFrame frame = new JFrame("Filtering List");
           frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);   
   
           FilteringJList list = new FilteringJList();
           JScrollPane pane = new JScrollPane(list);
           frame.add(pane, BorderLayout.CENTER);
           JTextField text = new JTextField();
           list.installJTextField(text);
           frame.add(text, BorderLayout.NORTH);   
   
           String elements[] = {
             "Partridge in a pear tree",
             "Turtle Doves",
             "French Hens",
             "Calling Birds",
             "Golden Rings",
             "Geese-a-laying",
             "Swans-a-swimming",
             "Maids-a-milking",
             "Ladies dancing",
             "Lords-a-leaping",
             "Pipers piping",
             "Drummers drumming",
             "Dasher",
             "Dancer",
             "Prancer",
             "Vixen",
             "Comet",
             "Cupid",
             "Donner",
             "Blitzen",
             "Rudolf",
             "Bakerloo",
             "Center",
             "Circle",
             "District",
             "East London",
             "Hammersmith and City",
             "Jubilee",
             "Metropolitan",
             "Northern",
             "Piccadilly Royal",
             "Victoria",
             "Waterloo and City",
             "Alpha",
             "Beta",
             "Gamma",
             "Delta",
             "Epsilon",
             "Zeta",
             "Eta",
             "Theta",
             "Iota",
             "Kapa",
             "Lamda",
             "Mu",
             "Nu",
             "Xi",
             "Omikron",
             "Pi",
             "Rho",
             "Sigma",
             "Tau",
             "Upsilon",
             "Phi",
             "Chi",
             "Psi",
             "Omega"
           };   
   
           for (String element: elements) {
             list.addElement(element);
           }   
   
           frame.setSize(250, 150);
           frame.setVisible(true);
         }
       };
       EventQueue.invokeLater(runner);
     }
   }
Compile the FilteringJList and Filters classes. Then run Filters. You should get a filtering JList.

Filtering List 2

Provided that your list elements have "good" toString() representations, this approach for a filtering JList, with its associated JTextField, works well. For more complex list elements, consider defining a Filter interface that could be passed into the model for the filtering operation.

One thing not covered in this example is management of selection. By default, the JList doesn't alter the selection when the list model content changes. Depending on the desired behavior, filtering might try to preserve the selected element or always reset it to the first item in the list.

Although the underlying JList component doesn't support filtering directly, it does provide a smart type-ahead option. If you don't like the default behavior, you can override the getNextMatch() method (that method was added in J2SE 1.4).

Back to Top

DEQUES
 

Deque is short for double-ended queues (deque is pronounced like deck, not like de-queue). A queue supports adding from one end and removing from the other. By comparison, double-ended queues support adding and removing from both ends -- it operates like a combined stack and queue. The Deque interface extends from the Queue interface introduced in J2SE 5.0, and is the latest addition to the Java Collections Framework with Java SE 6. (Final inclusion of the feature is subject to JCP approval.) Implementations of the interface include the LinkedList, ArrayDeque, and the concurrent LinkedBlockingDeque.

The LinkedList is probably the most typical usage of a deque. It grows without bounds and has quick add and remove operations at both ends. An ArrayDeque is another typical implementation that has no capacity restrictions. It offers a wraparound index implementation to keep performance at its peak. Like all of the base collection implementations, neither is threadsafe. (Historical collections such as Vector and Hashtable are threadsafe, but not designed for highly concurrent access.) If you need thread safety, use a LinkedBlockingDeque. The LinkedBlockingDeque class implements the new BlockingDeque interface, which extends from Deque. The class can be size-bounded or not. If no capacity is specified, its size limit is Integer.MAX_VALUE.

You can add elements to a deque with one of the following methods:

  • void addFirst(E e)
  • void addLast(E e)
  • boolean add(E e)
The add() method is equivalent to addLast(). Lack of capacity in the deque causes an IllegalStateException to be thrown. You can also offer an element to be added through one of the following methods:

  • boolean offer(E e)
  • boolean offerFirst(E e),
  • boolean offerLast(E e)
Unlike adding elements with the addXXX() methods, if an item can't be added when offered with an offerXXX() method, the method returns false.

There are also a pair of method sets for removing elements:

  • remove(), removeFirst(), and removeLast()
  • poll(), pollFirst(), and pollLast()
The removeXXX() methods throw a NoSuchElementException when the deque is empty. The pollXXX() methods return null when empty. You can even remove a specific object with one of the following methods, although deques are meant for adding and removing from the ends only:

  • boolean remove(Object o)
  • boolean removeFirstOccurrence(Object o)
  • boolean removeLastOccurrence(Object o),
Deque has six methods for examining elements:

  • element()
  • getFirst()
  • getLast()
  • peek()
  • peekFirst()
  • peekLast()
There is no get() method because element() is the interface method inherited from the older Queue interface. The get methods are similar to removeXXX(), and throw a NoSuchElementException when the deque is empty. The peek methods, by comparison, return null when empty. Of course, this means that if a Deque allows adding null values, you won't be able to tell the difference between a null item at the end of the deque or nobody on deck. But that is where the size() method is useful.

A Deque is conceptually doubly linked, so you can traverse through the elements in either order. Use iterator() to go through from front-to-back, and descendingIterator() to go in the reverse order, that is from back-to-front. You cannot, however, access an element by position -- at least not through the Deque interface. Although LinkedList is an implementation of Deque, it does support indexed access through the List interface that it also implements. Without the random access requirement, a Deque implementation can be made more efficient.

Why use a deque? Deques are useful data structures for recursive problems, such as searching through a maze or parsing source. As you move along a path, you save "good" spots, adding more data along the way (that is, while you think the path is good). If the path turns bad, you pop off the bad bits, returning to the last good spot. Here, you would add and remove from the same end, like a stack. Once you find your way through, you start back at the beginning to reveal the solution, which is the other end. Other typical examples include operating system schedulers and bad card dealers who like to deal from the bottom of the deck -- to themselves at least.

The following program, Blocked, demonstrates the use of Deque, or more specifically, LinkedBlockingDeque with its capacity limits. It is certainly not the best use of a deque but demonstrates the API, and what happens when you hit the capacity limit. The program takes the 23 names for months (both short and long) and adds them to the head of a six-element blocking deque, one-at-a-time. In another thread, elements are removed from the head and tail of the deque, based on the number of elements currently in the collection.

   import java.io.*;
   import java.util.*;
   import java.util.concurrent.*;

   public class Blocked {
     public static void main(String args[]) {
       Calendar now = Calendar.getInstance();
       Locale locale = Locale.getDefault();
       final Console console = System.console();
       final Map<String, Integer> names = now.getDisplayNames(
           Calendar.MONTH, Calendar.ALL_STYLES, locale);
       console.printf("Starting names: %s%n", names);
       final Deque<String> deque = 
           new LinkedBlockingDeque<String>(6);
       // Add one at time to beginning of deque
       new Thread() {
         public void run() {
           Set<String> keys = names.keySet();
           Iterator<String> itor = keys.iterator();
           String element = null;
           while (itor.hasNext() || element != null) {
             if (element == null) {
               element = itor.next();
               console.printf("MapGot: %s%n",  element);
             }
             console.printf("Offering: %s%n", element);
             if (deque.offerFirst(element)) {
               console.printf("MapRemoving: %s%n", element);
               itor.remove();
               element = null;
             } else {
               try {
                 Thread.sleep(250);
               } catch (InterruptedException ignored) {
               }
             }
           }
           // Done. Give time to process rest.
           try {
             Thread.sleep(3500);
           } catch (InterruptedException ignored) {
           }
           System.exit(0);
         }
       }.start();
       while (true) {
         if ((deque.size() % 2 == 1)) {
           // remove head
           console.printf(
               "Remove head: %s%n", deque.pollFirst());
         } else {
           // remove tail
           console.printf(
               "Remove tail: %s%n", deque.pollLast());
         }
         // Sleep between loops
         try {
           Thread.sleep(500);
         } catch (InterruptedException ignored) {
         }
       }
     }
   }
As shown below, running the program generates a lot of output due to the printf statements. An output line is generated each time an element is fetched from the source map, removed from the source map, offered to the deque, or removed from the deque. Notice how the act of offering happens multiple times while the deque is full.

   >> java Blocked


   Starting names: {Jun=5, March=2, December=11, April=3, 
   November=10, September=8, October=9, Sep=8, Aug=7, Apr=3, 
   May=4, June=5, Feb=1, Dec=11, Oct=9, Jan=0, Mar=2, Jul=6, 
   August=7, January=0, February=1, July=6, Nov=10}
   Remove tail: null
   MapGot: Jun
   Offering: Jun
   MapRemoving: Jun
   MapGot: March
   Offering: March
   MapRemoving: March
   ...

   Remove tail: Jul
   Remove head: Nov
   Remove tail: August
   Remove head: July
   Remove tail: January
   Remove head: February
   Remove tail: null
Two points are worth mentioning. First, there are 23 short and long names for months (not 24) because "May" counts for both the short and long name of the fifth month. The getDisplayNames() method returns a Map, so "May" can't be the key for two entries, one short and one long. Second, if all you are doing is adding at one end and removing from the other, consider using a Queue implementation in the collections framework instead of a Deque.

For a good comparison of deques and vectors, at least in the Standard Template Library (STL), see An In-Depth Study of the STLDeque Container. It discusses the performance differences between the two container types. Actual numbers on the Java platform will differ, but the general concepts hold true.

Back to Top

Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
If you would like a reply to your comment, please submit your email address:
Note: We may not respond to all submitted comments.
Comments? Send your feedback on the Tech Tips: http://developers.sun.com/contact/feedback.jsp?category=newslet

Subscribe to the following newsletters for the latest information about technologies and products in other Java platforms:
  • Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE).
  • Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).
You can subscribe to these and other Java technology developer newsletters or manage your current newsletter subscriptions on the Sun Developer Network Subscriptions page

ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/developer/JDCTechTips/index.html

IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html

© 2005 Sun Microsystems, Inc. All Rights Reserved. For information on Sun's trademarks see: http://sun.com/suntrademarks
Java, J2EE, J2SE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.

Sun Microsystems, Inc. 10 Network Circle, MPK10-209 Menlo Park, CA 94025 US