Tip: How to generically sort unknown objects by their nested properties

In this post, I’ll show you how to sort an array of unknown objects by their nested properties. The objects doesnt need to implement the Comparable interface.

I recently had a problem were I needed to sort an array of domain objects containing some nested properties. The requirement for the functionality I was coding, is the possibilty to sort a table where the columns is the nested properties of the input array. The domain objects doesnt implement the Comparable interface so sorting with standard utilities is not possible and without access to the source code, I needed to find some alternative way to handle the sorting.

The first approach I tried was a wrapper: I simply created a wrapper class implementing the Comparable interface around a domain object. The wrapper then uses the domain object and its getters to do the sorting. This might have worked, but, there are several problems. First of all, once implemented, there’s no easy way of changing the solution later, without having to recompile and change the interals of the classes involved. Second, this is a static approach in terms of possible key combinations (you sort on a predetermind key). The third and most critial reason: what if your domain objects doesnt implement an interface at all? If your application doesnt build upon the design principle of interface-programming, then you’r in big trouble… A correct use of the Wrapper design pattern is to let the wrapper implement the same interface as the object it is wrapping. So without an interface, you have a hell of a job reimplementing your entire application.

My second approach was to change my perspective: instead of scattering and duplicating sorting functions of known objects, I tried focusing on unknown objects. I have a bunch of unknown objects, but, I know they have some nested properties. “Unknown knowns” as someone would have said (not worth mentioning who)… The first thing you could have done is to collect every thing in an object array, then trying to sort on some property. But then, you will still have to handle casting and reindexing in a manual fashing. What I did, however, is the following:

  1. wrap every object in an unknown input array with a known wrapper-class that implements the Comparable interface
  2. for every wrapper class, assign a sorting key (can be any nested property) and collect everthing in a list
  3. extract the sorting key for every object in the list using reflection and preform the sorting
  4. generically create a new instance of the same type as the input array and finally populate this array with elements from the newly sorted list.

The results of this: no casting, no manual sorting, no scattered or duplicated code. The above recipe uses Apache Commons for property extration, hence, enabling us to get any nested key (indexed and unindexed). The full source of the generic sort util is as follows:


public class GenericSortUtil
{
    /**
     * Preforms an array sort on a generic array of elements of type T. The sort key
     * specified by 'sortPropertyKey' can be located in any location of the arrays object
     * hierarchy. The sorting is done by wrapping each object with a comparable object,
     * collecting and sorting them in their natural order and ultimately, genericly
     * reinstantiating a new array of type T.
     *
     * @param <T> A generic type, which can be of any type.
     * @param elements Any kind of array containing objects with comparable properties
     * @param sortPropertyKey A comparable property
     * @return The sorted array
     *
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     * @throws IllegalArgumentException Thrown if the given sortPropertyKey doesnt implement the Comparable
     *                                  interface.
     */
    public static <T> T[] genericArraySort(T[] elements, String sortPropertyKey)
    throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, IllegalArgumentException
    {
        ArrayList<ComparableObject> listToSort = new ArrayList<ComparableObject>();
        for(Object o : elements)
        {
            Object ret = PropertyUtils.getProperty( o, sortPropertyKey );
            if (ret instanceof Comparable<?>)
            {
                ComparableObject co = new ComparableObject(o, (Comparable<?>) ret);
                listToSort.add(co);
            }
            else
            {
                throw new IllegalArgumentException("The specified property is not comparable.");
            }
        }
        Collections.sort(listToSort);

        Class<?> ct = elements[0].getClass();
        T[] contents = contents = (T[]) Array.newInstance(ct, elements.length);
        for ( int i = 0; i < contents.length; i++ )
        {
            contents[i] = (T) listToSort.get( i ).getObject();
        }

        return contents;
    }

    /**
     * A wrapper object used to sort uncomparable objects.
     *
     */
    public static class ComparableObject implements Comparable<ComparableObject>{
        private Object object;
        private Comparable key;
        public ComparableObject(Object o, Comparable<?> key)
        {
            super();
            this.object = o;
            this.key = key;
        }
        public Comparable getKey()
        {
            return key;
        }
        public int compareTo( ComparableObject o )
        {
            return getKey().compareTo(o.getKey());
        }
        public Object getObject()
        {
            return object;
        }
    }
}

On the next page I’ll show you an example usage.

This entry was posted in Java and tagged . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>