Breaking my grand tradition of writing only about cars and about Things That Suck (TM), this post will be about something Java-related (oh, the horror!) as well as cool (you haven't seen this one coming, have you?).
So we have been implementing some MVC-based piece of code lately. The model contains a collection of JIRA issues, the view is a
JTree and its builder, the controller is a bunch of
Swing buttons and some such. Nothing special. The model's interface looks more-less like this (housekeeping and other misc. code omitted for brevity):
public interface JIRAIssueListModel {
Collection<JIRAIssue> getIssues();
void notifyListeners();
void addModelListener(JIRAIssueListModelListener listener);
void removeModelListener(JIRAIssueListModelListener listener);
}The view registers as model's listener and whenever the model calls it, it in return calls
getIssues() and populates the
JTree. Nothing to write home about.
Now, the requirement for this code was to sort the issues in the tree in priority order. The problem is, the model cannot keep the issues sorted in this order, and due to a bunch of factors (the most important being my laziness), I found it problematic to sort the issues within the
JTree-based view. So what have I done instead? I have invented a contraption, which is a class, that from one end (the "upper" end) looks like the model - implements the same interface as the one above) and from the other end (the "lower" end) looks like the view - implements the model listener interface. Like so:
public abstract class SortingJIRAIssueListModel
implements JIRAIssueListModel, JIRAIssueListModelListener {
public SortingJIRAIssueListModel(JIRAIssueListModel parent) {
this.parent = parent;
listeners = new ArrayList<JIRAIssueListModelListener>();
parent.addModelListener(this);
}
...etc...
}
Note the fact that the constructor of the object registers itself as the listener in the "parent" model.
The crucial piece of code in this class consists of two methods:
public Collection<JIRAIssue> getIssues() {
Collection<JIRAIssue> col = parent.getIssues();
List<JIRAIssue> list = new ArrayList<JIRAIssue>();
for (JIRAIssue i : col) {
list.add(i);
}
Collections.sort(list, getComparator());
return list;
}
public void modelChanged(JIRAIssueListModel model) {
notifyListeners();
}When called by the parent model, this sorting model will notify listeners that have registered as
its views. These views will then call this model's
getIssues(), which sorts the list of JIRA issues retrieved from the parent. The
getComparator() method is abstract, so that you can easily switch sorting order by subclassing (this could also be organized by injecting the comparator object in the setter, but setters are evil and inelegant, and I don't want to use them whenever I can find the alternative).
The finishing touch is registering the
JTree-based view in the sorting model, instead of the previous one.
But wait! There is more! What if I want to have an incremental search facility, a'la iTunes search box? It turns out that if you use the "model stack" approach described above, this is super-easy.
What you do is:
- provide a JTextField and add a document listener to it (look it up in the Swing Javadoc if you don't know how :))
- create yet another "model-view" class, whose parent is the previous "sorting: model, with the following crucial pieces of code:
public void setSearchTerm(@NotNull String searchTerm) {
if (this.searchTerm.equals(searchTerm)) {
return;
}
this.searchTerm = searchTerm.toLowerCase();
notifyListeners();
}
public Collection<JIRAIssue> getIssues() {
Collection<JIRAIssue> col = parent.getIssues();
if (searchTerm.length() == 0) {
return col;
}
List<JIRAIssue> list = new ArrayList<JIRAIssue>();
for (JIRAIssue i : col) {
if (i.getKey()
.toLowerCase()
.indexOf(searchTerm) > -1
||
i.getSummary()
.toLowerCase()
.indexOf(searchTerm) > -1) {
list.add(i);
}
}
return list;
}Now, if I:
- register my view in the searching model instead of the sorting model
- invoke this model's setSearchTerm() method whenever the JTextField text changes
I get an instant incremental search in less than 30 lines of sparsely formatted code :).
Now, all of the above probably has a fancy name (probably "decorator pattern" or whatever) and has been known to computer science experts for decades :). But nevertheless, I find this sort of thing quite cool.
If anybody is interested, the whole code can be found
here (you need to register in
JIRA Studio to get access, but the registration is free and everybody is welcome to have a look).