Problem statement
Let’s say you need to sort a bunch of beans in your enterprise application.Now, first of all and by all means, try to use the underlying database. Database management systems are written and highly optimized for data retrieval and ordering. In pure SQL, ordering is as easy as appending ORDER BY at the end of your SQL statement, and you should only invest some time into considering how to define columns and their direction programmatically. ORM solutions, like Hibernate, make the job even easier with Criteria queries and the like.
But, sometimes you really need to sort some data in memory, before you hand it over to the database. Typical example of this scenario is integration or data import with some other, legacy, system. Of course, Java has a Collections.sort() method, which, in case of non-Comparable entities, requires an implementation of Comparator interface. Its compare() method is used to define ordering of beans in a collection.
Typical comparator implementation, handling some example TestBean, might look something like this:
Here the common comparing functionality has been extracted into a utility method:public class TestBean {
private Integer i;
private String s;
private Date d;
…
}
public class ExampleComparator implements Comparator {
// I, S, D
public int compare(Object o1, Object o2) {
TestBean b1 = (TestBean)o1;
TestBean b2 = (TestBean)o2;
int c1 = ComparatorHelper.compare(b1.getI(), b2.getI());
if (c1 != 0) return c1;
int c2 = ComparatorHelper.compare(b1.getS(), b2.getD());
if (c2 != 0) return -c2; // descending order
return ComparatorHelper.compare(b1.getD(), b2.getD());
}
}
First problem with this approach is when you have several different classes to sort. Furthermore, you might want only particular columns, and in different orders, for each of those classes, to be considered in sorting process. And that should somehow be programmatically determined, rather than being hard-coded.public final class ComparatorHelper {
private ComparatorHelper() {}
public static int compare(Comparable x, Comparable y) {
if (x == y) {
return 0;
}
else if (x == null) {
return Integer.MIN_VALUE;
}
else if (y == null) {
return Integer.MAX_VALUE;
}
else {
return x.compareTo(y);
}
}
}
All of the above would necessitate having to write separate Comparator classes for each and every combination. While you may think that writing a generic comparator with bunch of ifs or such might do the trick, at least for a single class, there is still a performance issue, since compare() method will be invoked extensively in a sorting algorithm.
Enter code generation
Ideally, we would generate our Comparator implementation, according to the desired parameters, on-the-fly. Something like this would be nice:Meaning of the above should be self-explanatory, but just in case: it is expected that the above method creates a comparator for TestBean class, where properties i, s and d are compared in that order, and s property will be descending. The above argument format is, truth be told, very impractical for interfacing with other parts of the system. Better approach would be using a method chaining parameter class, something like this:
Comparator<Testbean> c1 = ComparatorFactory.createComparator(TestBean.class,
new String[] { "i", "s", "d" },
new Class[] { Integer.class, String.class, Date.class },
new int[] { 1, -1, 1 });
But I will be leaving that as an exercise for junior developers… Nah, I’ll just do it later; I really like those fluent interfaces.
Comparator<Testbean> c1 = ComparatorFactory.forClass(TestBean.class)
.addProperty(“i”, Integer.class)
.addProperty(“s”, String.class, false)
.addProperty(“d”, Date.class)
.generate();
Now back to the code generation bit. The algorithm for a comparator is pretty straight-forward: for each property, compare values using ComparatorHelper. If returned value is non-zero, return it, possibly calculating in the descending order. Otherwise, compare the next property.
This post is getting quite long by now… so I have to say goodbye for now. Stay tuned for sequel where I’ll be using the CGLIB library, and will be providing actual code.