001package org.andromda.utils.beans.comparators;
002
003import java.io.InputStream;
004import java.io.Serializable;
005import java.lang.reflect.InvocationTargetException;
006import java.net.URL;
007import java.util.Comparator;
008import java.util.Properties;
009
010import org.andromda.core.common.ClassUtils;
011import org.andromda.core.common.ExceptionUtils;
012import org.andromda.core.common.Introspector;
013import org.andromda.utils.beans.BeanSorter;
014import org.andromda.utils.beans.SortCriteria;
015import org.apache.commons.lang.StringUtils;
016
017/**
018 * Used by BeanSorter to provide sorting capabilities for
019 * beans.  Currently supports sorting by all simple properties
020 *
021 * @see BeanSorter
022 *
023 * @author Chad Brandon
024 */
025public class BeanComparator
026    implements Comparator,
027        Serializable
028{
029    private static final long serialVersionUID = 34L;
030
031    /**
032     * Stores the comparator mappings.
033     */
034    private static final Properties comparators = new Properties();
035
036    static
037    {
038        InputStream stream = null;
039        try
040        {
041            final String comparatorsFile = "/Comparators.properties";
042            final URL comparatorsUri = BeanComparator.class.getResource(comparatorsFile);
043            if (comparatorsUri == null)
044            {
045                throw new BeanComparatorException("The comparators resource '" + comparatorsFile +
046                    " could not be loaded");
047            }
048            stream = comparatorsUri.openStream();
049            comparators.load(stream);
050        }
051        catch (final Throwable throwable)
052        {
053            throw new RuntimeException(throwable);
054        }
055        finally
056        {
057            if (stream != null) try {stream.close();} catch (Exception ex) {}
058            stream = null;
059        }
060    }
061
062    private Comparator comparator = null;
063    private SortCriteria sortCriteria;
064
065    /**
066     * @param sortCriteria
067     */
068    public BeanComparator(SortCriteria sortCriteria)
069    {
070        ExceptionUtils.checkNull(
071            "sortCriteria",
072            sortCriteria);
073        this.sortCriteria = sortCriteria;
074    }
075
076    /**
077     * @see java.util.Comparator#compare(Object, Object)
078     */
079    @Override
080    public int compare(
081        final Object objectA,
082        Object objectB)
083    {
084        ExceptionUtils.checkNull(
085            "objectA",
086            objectA);
087        ExceptionUtils.checkNull(
088            "objectB",
089            objectB);
090
091        try
092        {
093            Object aValue;
094            Object bValue;
095            if (this.sortCriteria.getOrdering().equals(SortCriteria.Ordering.DESCENDING))
096            {
097                // - since its descending switch the objects we are getting the values from,
098                //   so the order will be reversed
099                aValue = getProperty(
100                        objectB,
101                        sortCriteria.getSortBy());
102                bValue = getProperty(
103                        objectA,
104                        sortCriteria.getSortBy());
105            }
106            else
107            {
108                // - otherwise we assume its ascending
109                aValue = getProperty(
110                        objectA,
111                        sortCriteria.getSortBy());
112                bValue = getProperty(
113                        objectB,
114                        sortCriteria.getSortBy());
115            }
116
117            //we default result to zero (zero result would mean aValue and bValue equal each other)
118            int result = 0;
119
120            //first sort for null values, null values will always come last
121            if (aValue != null || bValue != null)
122            {
123                if (aValue == null)
124                {
125                    if (sortCriteria.isNullsFirst())
126                    {
127                        result = -1;
128                    }
129                    else
130                    {
131                        result = 1;
132                    }
133                }
134                else if (bValue == null)
135                {
136                    if (sortCriteria.isNullsFirst())
137                    {
138                        result = 1;
139                    }
140                    else
141                    {
142                        result = -1;
143                    }
144                }
145                else
146                {
147                    result = this.getComparator(aValue.getClass()).compare(
148                        aValue,
149                        bValue);
150                }
151            }
152            return result;
153        }
154        catch (final Throwable throwable)
155        {
156            throw new ComparatorException(throwable);
157        }
158    }
159
160    /**
161     * Gets the property specified by propertyName from the bean.
162     * Checks each nested parent to see if its null and if it is
163     * returns null.
164     * @param bean
165     * @param propertyName
166     * @return
167     */
168    private Object getProperty(
169        final Object bean,
170        String propertyName)
171        throws IllegalAccessException, InvocationTargetException, NoSuchMethodException
172    {
173        Object value = null;
174        if (bean != null && StringUtils.isNotBlank(propertyName))
175        {
176            int index = getNestedIndex(propertyName);
177            if (index != -1)
178            {
179                String simpleProp = propertyName.substring(
180                        0,
181                        index);
182                value = Introspector.instance().getProperty(
183                        bean,
184                        simpleProp);
185                if (value != null)
186                {
187                    if (getNestedIndex(propertyName) != -1)
188                    {
189                        propertyName = propertyName.substring(
190                                index + 1,
191                                propertyName.length());
192                        value = getProperty(
193                                value,
194                                propertyName);
195                    }
196                }
197            }
198            else
199            {
200                value = Introspector.instance().getProperty(
201                        bean,
202                        propertyName);
203            }
204        }
205        return value;
206    }
207
208    /**
209     * Gets the index of the given <code>propertyName</code> if
210     * its a nested property (nested meaning names separated by
211     * a '.').
212     * @param propertyName the name of the nested property.
213     * @return the index.
214     */
215    private final int getNestedIndex(final String propertyName)
216    {
217        int index = -1;
218        if (StringUtils.isNotBlank(propertyName))
219        {
220            index = propertyName.indexOf('.');
221        }
222        return index;
223    }
224
225    /**
226     * Retrieves the associated Comparator for the type
227     * @param type the class of which to retrieve the comparator.
228     * @return appropriate comparator or null if one wasn't defined.
229     */
230    private final Comparator getComparator(final Class type)
231    {
232        try
233        {
234            if (this.comparator == null)
235            {
236                final String comparatorName = findComparatorName(type);
237                if (StringUtils.isNotBlank(comparatorName))
238                {
239                    this.comparator = (Comparator)ClassUtils.loadClass(comparatorName).newInstance();
240                }
241                else
242                {
243                    throw new ComparatorException("No comparator defined for the given type '" + type.getName() + '\'');
244                }
245            }
246            return this.comparator;
247        }
248        catch (final Throwable throwable)
249        {
250            throw new ComparatorException(throwable);
251        }
252    }
253
254    private String findComparatorName(final Class type)
255    {
256        String comparatorName = comparators.getProperty(type.getName());
257        if (StringUtils.isEmpty(comparatorName) && type.getSuperclass() != null)
258        {
259            comparatorName = findComparatorName(type.getSuperclass());
260        }
261        return comparatorName;
262    }
263
264    /**
265     * Returns the current sortCriteria value
266     * @return String
267     */
268    public SortCriteria getSortCriteria()
269    {
270        return this.sortCriteria;
271    }
272
273    /**
274     * Sets the new sortCriteria value
275     * @param sortCriteria
276     */
277    public void setSortCriteria(final SortCriteria sortCriteria)
278    {
279        ExceptionUtils.checkNull(
280            "sortCriteria",
281            sortCriteria);
282        this.sortCriteria = sortCriteria;
283    }
284}