BeanComparator.java

package org.andromda.utils.beans.comparators;

import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Comparator;
import java.util.Properties;

import org.andromda.core.common.ClassUtils;
import org.andromda.core.common.ExceptionUtils;
import org.andromda.core.common.Introspector;
import org.andromda.utils.beans.BeanSorter;
import org.andromda.utils.beans.SortCriteria;
import org.apache.commons.lang.StringUtils;

/**
 * Used by BeanSorter to provide sorting capabilities for
 * beans.  Currently supports sorting by all simple properties
 *
 * @see BeanSorter
 *
 * @author Chad Brandon
 */
public class BeanComparator
    implements Comparator,
        Serializable
{
    private static final long serialVersionUID = 34L;

    /**
     * Stores the comparator mappings.
     */
    private static final Properties comparators = new Properties();

    static
    {
        InputStream stream = null;
        try
        {
            final String comparatorsFile = "/Comparators.properties";
            final URL comparatorsUri = BeanComparator.class.getResource(comparatorsFile);
            if (comparatorsUri == null)
            {
                throw new BeanComparatorException("The comparators resource '" + comparatorsFile +
                    " could not be loaded");
            }
            stream = comparatorsUri.openStream();
            comparators.load(stream);
        }
        catch (final Throwable throwable)
        {
            throw new RuntimeException(throwable);
        }
        finally
        {
            if (stream != null) try {stream.close();} catch (Exception ex) {}
            stream = null;
        }
    }

    private Comparator comparator = null;
    private SortCriteria sortCriteria;

    /**
     * @param sortCriteria
     */
    public BeanComparator(SortCriteria sortCriteria)
    {
        ExceptionUtils.checkNull(
            "sortCriteria",
            sortCriteria);
        this.sortCriteria = sortCriteria;
    }

    /**
     * @see java.util.Comparator#compare(Object, Object)
     */
    @Override
    public int compare(
        final Object objectA,
        Object objectB)
    {
        ExceptionUtils.checkNull(
            "objectA",
            objectA);
        ExceptionUtils.checkNull(
            "objectB",
            objectB);

        try
        {
            Object aValue;
            Object bValue;
            if (this.sortCriteria.getOrdering().equals(SortCriteria.Ordering.DESCENDING))
            {
                // - since its descending switch the objects we are getting the values from,
                //   so the order will be reversed
                aValue = getProperty(
                        objectB,
                        sortCriteria.getSortBy());
                bValue = getProperty(
                        objectA,
                        sortCriteria.getSortBy());
            }
            else
            {
                // - otherwise we assume its ascending
                aValue = getProperty(
                        objectA,
                        sortCriteria.getSortBy());
                bValue = getProperty(
                        objectB,
                        sortCriteria.getSortBy());
            }

            //we default result to zero (zero result would mean aValue and bValue equal each other)
            int result = 0;

            //first sort for null values, null values will always come last
            if (aValue != null || bValue != null)
            {
                if (aValue == null)
                {
                    if (sortCriteria.isNullsFirst())
                    {
                        result = -1;
                    }
                    else
                    {
                        result = 1;
                    }
                }
                else if (bValue == null)
                {
                    if (sortCriteria.isNullsFirst())
                    {
                        result = 1;
                    }
                    else
                    {
                        result = -1;
                    }
                }
                else
                {
                    result = this.getComparator(aValue.getClass()).compare(
                        aValue,
                        bValue);
                }
            }
            return result;
        }
        catch (final Throwable throwable)
        {
            throw new ComparatorException(throwable);
        }
    }

    /**
     * Gets the property specified by propertyName from the bean.
     * Checks each nested parent to see if its null and if it is
     * returns null.
     * @param bean
     * @param propertyName
     * @return
     */
    private Object getProperty(
        final Object bean,
        String propertyName)
        throws IllegalAccessException, InvocationTargetException, NoSuchMethodException
    {
        Object value = null;
        if (bean != null && StringUtils.isNotBlank(propertyName))
        {
            int index = getNestedIndex(propertyName);
            if (index != -1)
            {
                String simpleProp = propertyName.substring(
                        0,
                        index);
                value = Introspector.instance().getProperty(
                        bean,
                        simpleProp);
                if (value != null)
                {
                    if (getNestedIndex(propertyName) != -1)
                    {
                        propertyName = propertyName.substring(
                                index + 1,
                                propertyName.length());
                        value = getProperty(
                                value,
                                propertyName);
                    }
                }
            }
            else
            {
                value = Introspector.instance().getProperty(
                        bean,
                        propertyName);
            }
        }
        return value;
    }

    /**
     * Gets the index of the given <code>propertyName</code> if
     * its a nested property (nested meaning names separated by
     * a '.').
     * @param propertyName the name of the nested property.
     * @return the index.
     */
    private final int getNestedIndex(final String propertyName)
    {
        int index = -1;
        if (StringUtils.isNotBlank(propertyName))
        {
            index = propertyName.indexOf('.');
        }
        return index;
    }

    /**
     * Retrieves the associated Comparator for the type
     * @param type the class of which to retrieve the comparator.
     * @return appropriate comparator or null if one wasn't defined.
     */
    private final Comparator getComparator(final Class type)
    {
        try
        {
            if (this.comparator == null)
            {
                final String comparatorName = findComparatorName(type);
                if (StringUtils.isNotBlank(comparatorName))
                {
                    this.comparator = (Comparator)ClassUtils.loadClass(comparatorName).newInstance();
                }
                else
                {
                    throw new ComparatorException("No comparator defined for the given type '" + type.getName() + '\'');
                }
            }
            return this.comparator;
        }
        catch (final Throwable throwable)
        {
            throw new ComparatorException(throwable);
        }
    }

    private String findComparatorName(final Class type)
    {
        String comparatorName = comparators.getProperty(type.getName());
        if (StringUtils.isEmpty(comparatorName) && type.getSuperclass() != null)
        {
            comparatorName = findComparatorName(type.getSuperclass());
        }
        return comparatorName;
    }

    /**
     * Returns the current sortCriteria value
     * @return String
     */
    public SortCriteria getSortCriteria()
    {
        return this.sortCriteria;
    }

    /**
     * Sets the new sortCriteria value
     * @param sortCriteria
     */
    public void setSortCriteria(final SortCriteria sortCriteria)
    {
        ExceptionUtils.checkNull(
            "sortCriteria",
            sortCriteria);
        this.sortCriteria = sortCriteria;
    }
}