|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 3 of 4
The application shown in Figure 6 contains a table with two columns: one for items and another for price. You can sort columns by clicking on column headers. When the application starts, nothing is sorted, as evidenced by the status bar in the bottom of the left picture in Figure 6. I took the middle picture in Figure 6 after I clicked the Item column header. I took the the right picture after I clicked the Price/Lb. column header.
Figure 6. A sorting decorator. Click on thumbnail to view full-size image.
The application shown in Figure 6 replaces the table's model with a sort decorator. That application is partially listed in Example 4. (You can download the full source code from the Resources section below.)
Example 4. Sorting a Swing table
import java.awt.*;
import java.awt.event.*;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class Test extends JFrame {
public static void main(String args[]) {
SwingApp.launch(new Test(), "A Sort Decorator",
300, 300, 200, 250);
}
public Test() {
// Create the decorator that will decorate the table's
// original model. The reference must be final because it's
// accessed by an inner class below.
final TableSortDecorator decorator =
new TableSortDecorator(table.getModel());
// Set the table's model to the decorator. Because the
// decorator implements TableModel, the table will never
// know the difference between the decorator and it's
// original model.
table.setModel(decorator);
...
// Obtain a reference to the table's header
JTableHeader header = (JTableHeader)table.getTableHeader();
// React to mouse clicks in the table header by calling
// the decorator's sort method.
header.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
TableColumnModel tcm = table.getColumnModel();
int vc = tcm.getColumnIndexAtX(e.getX());
int mc = table.convertColumnIndexToModel(vc);
// Perform the sort
decorator.sort(mc);
// Update the status area
SwingApp.showStatus(headers[mc] + " sorted");
}
});
}
...
}
In the preceding code, a sort decorator decorates the table's original model. When you click on a column header, the application
calls the decorator's sort() method. The interesting code in this example is not the application, but the sort decorator. Before looking at the source
to TableSortDecorator, let's look at its class diagram, shown in Figure 7.

Figure 7. Sorting decorator class diagram
Since instances of TableSortDecorator decorate table models, TableSortDecorator implements the TableModel interface. TableSortDecorator also maintains a reference to the real model that it decorates. That reference can point to any table model. Example 5 lists
the TableSortDecorator class:
Example 5. A table sort decorator
class TableSortDecorator implements TableModel,TableModelListener{
public TableSortDecorator(TableModel model) {
this.realModel = model;
realModel.addTableModelListener(this);
allocate();
}
// The following nine methods are defined by the TableModel
// interface; all of those methods forward to the real model.
public void addTableModelListener(TableModelListener l) {
realModel.addTableModelListener(l);
}
public Class getColumnClass(int columnIndex) {
return realModel.getColumnClass(columnIndex);
}
public int getColumnCount() {
return realModel.getColumnCount();
}
public String getColumnName(int columnIndex) {
return realModel.getColumnName(columnIndex);
}
public int getRowCount() {
return realModel.getRowCount();
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return realModel.isCellEditable(rowIndex, columnIndex);
}
public void removeTableModelListener(TableModelListener l) {
realModel.removeTableModelListener(l);
}
public Object getValueAt(int row, int column) {
return getRealModel().getValueAt(indexes[row], column);
}
public void setValueAt(Object aValue, int row, int column) {
getRealModel().setValueAt(aValue, indexes[row], column);
}
// The getRealModel method is used by subclasses to
// access the real model.
protected TableModel getRealModel() {
return realModel;
}
// tableChanged is defined in TableModelListener, which
// is implemented by TableSortDecorator.
public void tableChanged(TableModelEvent e) {
allocate();
}
// The following methods perform the bubble sort ...
public void sort(int column) {
int rowCount = getRowCount();
for(int i=0; i < rowCount; i++) {
for(int j = i+1; j < rowCount; j++) {
if(compare(indexes[i], indexes[j], column) < 0) {
swap(i,j);
}
}
}
}
private void swap(int i, int j) {
int tmp = indexes[i];
indexes[i] = indexes[j];
indexes[j] = tmp;
}
private int compare(int i, int j, int column) {
TableModel realModel = getRealModel();
Object io = realModel.getValueAt(i,column);
Object jo = realModel.getValueAt(j,column);
int c = jo.toString().compareTo(io.toString());
return (c < 0) ? -1 : ((c > 0) ? 1 : 0);
}
private void allocate() {
indexes = new int[getRowCount()];
for(int i=0; i < indexes.length; ++i) {
indexes[i] = i;
}
}
private TableModel realModel; // We're decorating this model
private int indexes[];
}
Table sort decorators do not change their real model when they sort; instead, decorators maintain an array of integers representing sorted row positions. When a sort decorator, masquerading as a table model, is asked for a value at a specific row and column, it uses the row value as an index into its array, and returns the corresponding value from the array. In that way, sort decorators overlay sorting onto Swing table models without modifying the original model in any way.
TableSortDecorator also implements the TableModelListener interface and registers itself as a listener with the real model. When the real model fires a table changed event, the decorator
reallocates its array of sorted index references. That allocation happens in TableSortDecorator.tableChanged.
TableSortDecorator has 11 public methods. Nine of those methods forward to the real model. Of those nine methods, only two methods -- getValueAt() and setValueAt() -- provide any additional functionality. Such ratios are not unusual among decorators; most decorators add a small amount
of behavior to a decorated object that already performs several functions.
The sort decorator discussed above works as advertised by decorating arbitrary table models with sorting functionality at runtime. This gives you flexibility because the decorated models do not need to be modified, which means that any table model can be decorated with sorting capabilities.
But the TableSortDecorator as implemented above is not designed for reuse because it implements two responsibilities for which it is not well suited.
The first responsibility is forwarding to the real model. Because other table model decorators will need almost exactly the
same code, that responsibility is too general, and should be moved up the class hierarchy. The second responsibility is sorting,
which in this example is a bubble sort. That sorting algorithm is hardcoded into the sort decorator, which means sorting cannot
be changed without rewriting the entire decorator. The sorting algorithm is too specific, so it should be moved down the hierarchy.
Figure 8 shows the result of refactoring the sort decorator as recommended in the preceding paragraph.
Figure 8. Refactored sorting decorator class diagram. Click on thumbnail to view full-size image.
The refactoring split the original TableSortDecorator into three different classes:
TableModelDecorator: Implements TableModel by forwarding to the real model
TableSortDecorator: Extends TableModelDecorator and adds an abstract sort method that subclasses must implement
TableBubbleSortDecorator: Extends TableSortDecorator and implements the bubble sort
By refactoring the sort decorator, we can reuse the code that forwards to the real table model. Encapsulating that code in
TableModelDecorator lets us easily develop other table model decorator types, such as the filter decorators -- which are not discussed in this
article -- shown in Figure 8.
The abstract TableSortDecorator class defers the implementation of its sort() method to subclasses, making it trivial to implement sort decorators with a different sorting algorithm.
Example 6 lists the TableModelDecorator class.
Example 6. TableModelDecorator
import javax.swing.table.TableModel;
import javax.swing.event.TableModelListener;
// TableModelDecorator implements TableModelListener. That
// listener interface defines one method: tableChanged(), which
// is called when the table model is changed. That method is
// not implemented in this abstract class; it's left for
// subclasses to implement.
public abstract class TableModelDecorator
implements TableModel, TableModelListener {
public TableModelDecorator(TableModel model) {
this.realModel = model;
realModel.addTableModelListener(this);
}
// The following 9 methods are defined by the TableModel
// interface; all of those methods forward to the real model.
public void addTableModelListener(TableModelListener l) {
realModel.addTableModelListener(l);
}
public Class getColumnClass(int columnIndex) {
return realModel.getColumnClass(columnIndex);
}
public int getColumnCount() {
return realModel.getColumnCount();
}
public String getColumnName(int columnIndex) {
return realModel.getColumnName(columnIndex);
}
public int getRowCount() {
return realModel.getRowCount();
}
public Object getValueAt(int rowIndex, int columnIndex) {
return realModel.getValueAt(rowIndex, columnIndex);
}
public boolean isCellEditable(int rowIndex, int columnIndex) {
return realModel.isCellEditable(rowIndex, columnIndex);
}
public void removeTableModelListener(TableModelListener l) {
realModel.removeTableModelListener(l);
}
public void setValueAt(Object aValue,
int rowIndex, int columnIndex) {
realModel.setValueAt(aValue, rowIndex, columnIndex);
}
// The getRealModel method is used by subclasses to
// access the real model.
protected TableModel getRealModel() {
return realModel;
}
private TableModel realModel; // We're decorating this model
}
Notice that public TableModelDecorator methods do nothing but forward to the real model. That makes them good defaults for other classes to inherit when they extend
TableModelDecorator. Example 7 lists TableSortDecorator.
Example 7. TableSortDecorator
import javax.swing.table.TableModel;
public abstract class TableSortDecorator extends
TableModelDecorator {
// Extensions of TableSortDecorator must implement the
// abstract sort method, in addition to tableChanged. The
// latter is required because TableModelDecorator
// implements the TableModelListener interface.
abstract public void sort(int column);
public TableSortDecorator(TableModel realModel) {
super(realModel);
}
}
The abstract TableSortDecorator class defines one abstract method -- sort() -- that must be implemented by concrete subclasses. The TableBubbleSortDecorator, listed in Example 8, is one such class.
Example 8. TableBubbleSortDecorator
import javax.swing.table.TableModel;
import javax.swing.event.TableModelEvent;
public class TableBubbleSortDecorator extends TableSortDecorator {
// The lone constructor must be passed a reference to a
// TableModel. This class decorates that model with additional
// sorting functionality.
public TableBubbleSortDecorator(TableModel model) {
super(model);
allocate();
}
// tableChanged is defined in TableModelListener, which
// is implemented by TableSortDecorator.
public void tableChanged(TableModelEvent e) {
allocate();
}
// Two TableModel methods are overridden from
// TableModelDecorator ...
public Object getValueAt(int row, int column) {
return getRealModel().getValueAt(indexes[row], column);
}
public void setValueAt(Object aValue, int row, int column) {
getRealModel().setValueAt(aValue, indexes[row], column);
}
// The following methods perform the bubble sort ...
public void sort(int column) {
int rowCount = getRowCount();
for(int i=0; i < rowCount; i++) {
for(int j = i+1; j < rowCount; j++) {
if(compare(indexes[i], indexes[j], column) < 0) {
swap(i,j);
}
}
}
}
private void swap(int i, int j) {
int tmp = indexes[i];
indexes[i] = indexes[j];
indexes[j] = tmp;
}
private int compare(int i, int j, int column) {
TableModel realModel = getRealModel();
Object io = realModel.getValueAt(i,column);
Object jo = realModel.getValueAt(j,column);
int c = jo.toString().compareTo(io.toString());
return (c < 0) ? -1 : ((c > 0) ? 1 : 0);
}
private void allocate() {
indexes = new int[getRowCount()];
for(int i=0; i < indexes.length; ++i) {
indexes[i] = i;
}
}
private int indexes[];
}
With the refactoring of the original table sort decorator, we now have a hierarchy of table decorators that we can extend at will.
In Java, developers typically use inheritance to add functionality to objects. Base classes implement shared behavior, and
extensions add functionality. For example, instead of the sort and filter decorators discussed above, you could implement
SortModel, FilterModel, and, perhaps, SortFilterModel classes.
Decorators prove more flexible than inheritance because the relationship between decorators and the objects they decorate can change at runtime, but relationships between base classes and their extensions are fixed at compile time.
Of course, both inheritance and the Decorator pattern have their places; in fact, inheritance is used to implement the Decorator pattern. So when should you use the Decorator pattern? It's useful when: