001/*
002 * Copyright 2008 The Kuali Foundation
003 * 
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * 
008 * http://www.opensource.org/licenses/ecl2.php
009 * 
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.ole.sys;
017
018import java.io.Serializable;
019import java.lang.reflect.InvocationTargetException;
020import java.util.Collections;
021import java.util.Comparator;
022import java.util.List;
023
024import org.apache.commons.beanutils.PropertyUtils;
025import org.apache.commons.lang.ObjectUtils;
026
027/**
028 * The comparator can dynamically implement java.util.Comparator and facilitate to sort a given colletion. This implementation is
029 * based on an article by York Davis, which was published in Java Developer's Journal (http://java.sys-con.com/read/45837.htm).
030 */
031public class DynamicCollectionComparator<T> implements Comparator<T>, Serializable {
032    private List<T> list;
033    private String[] fieldNames;
034    private SortOrder sortOrder;
035
036    /**
037     * enumerate the valid values of sort order
038     */
039    public enum SortOrder {
040        ASC, DESC
041    }
042
043    /**
044     * private constructs a DynamicCollectionComparator.java.
045     * 
046     * @param list the given collection that needs to be sorted
047     * @param fieldName the field name ordered by
048     * @param sortOrder the given sort order, either ascending or descending
049     */
050    private DynamicCollectionComparator(List<T> list, SortOrder sortOrder, String... fieldNames) {
051        super();
052
053        if (fieldNames == null || fieldNames.length <= 0) {
054            throw new IllegalArgumentException("The input field names cannot be null or empty");
055        }
056
057        this.list = list;
058        this.fieldNames = fieldNames;
059        this.sortOrder = sortOrder;
060    }
061
062    /**
063     * sort the given collection ordered by the given field name. Ascending order is used.
064     * 
065     * @param list the given collection that needs to be sorted
066     * @param fieldName the field name ordered by
067     */
068    public static <C> void sort(List<C> list, String... fieldNames) {
069        sort(list, SortOrder.ASC, fieldNames);
070    }
071
072    /**
073     * sort the given collection ordered by the given field name
074     * 
075     * @param list the given collection that needs to be sorted
076     * @param fieldName the field name ordered by
077     * @param sortOrder the given sort order, either ascending or descending
078     */
079    public static <C> void sort(List<C> list, SortOrder sortOrder, String... fieldNames) {
080        Comparator<C> comparator = new DynamicCollectionComparator<C>(list, sortOrder, fieldNames);
081        Collections.sort(list, comparator);
082    }
083
084    /**
085     * compare the two given objects for order. Returns a negative integer, zero, or a positive integer as this object is less than,
086     * equal to, or greater than the specified object. If the objects implement Comparable interface, the objects compare with each
087     * other based on the implementation; otherwise, the objects will be converted into Strings and compared as String.
088     */
089    public int compare(T object0, T object1) {
090        int comparisonResult = 0;
091
092        for (String fieldName : fieldNames) {
093            comparisonResult = this.compare(object0, object1, fieldName);
094
095            if (comparisonResult != 0) {
096                break;
097            }
098        }
099
100        return comparisonResult;
101    }
102
103    /**
104     * compare the two given objects for order. Returns a negative integer, zero, or a positive integer as this object is less than,
105     * equal to, or greater than the specified object. If the objects implement Comparable interface, the objects compare with each
106     * other based on the implementation; otherwise, the objects will be converted into Strings and compared as String.
107     */
108    public int compare(T object0, T object1, String fieldName) {
109        int comparisonResult = 0;
110        
111        try {
112            Object propery0 = PropertyUtils.getProperty(object0, fieldName);
113            Object propery1 = PropertyUtils.getProperty(object1, fieldName);
114
115            if(propery0 == null && propery1 == null) {
116                comparisonResult = 0;
117            }
118            else if(propery0 == null) {
119                comparisonResult = -1;
120            }
121            else if(propery1 == null) {
122                comparisonResult = 1;
123            }            
124            else if (propery0 instanceof Comparable) {
125                Comparable comparable0 = (Comparable) propery0;
126                Comparable comparable1 = (Comparable) propery1;
127
128                comparisonResult = comparable0.compareTo(comparable1);
129            }
130            else {
131                String property0AsString = ObjectUtils.toString(propery0);
132                String property1AsString = ObjectUtils.toString(propery1);
133
134                comparisonResult = property0AsString.compareTo(property1AsString);
135            }
136        }
137        catch (IllegalAccessException e) {
138            throw new RuntimeException("unable to compare property: " + fieldName, e);
139        }
140        catch (InvocationTargetException e) {
141            throw new RuntimeException("unable to compare property: " + fieldName, e);
142        }
143        catch (NoSuchMethodException e) {
144            throw new RuntimeException("unable to compare property: " + fieldName, e);
145        }
146
147        return comparisonResult * this.getSortOrderAsNumber();
148    }
149
150    /**
151     * convert the sort order as an interger. If the sort order is "DESC" (descending order), reutrn -1; otherwise, return 1.
152     * 
153     * @return -1 if the sort order is "DESC" (descending order); otherwise, return 1.
154     */
155    public int getSortOrderAsNumber() {
156        return sortOrder.equals(SortOrder.ASC) ? 1 : -1;
157    }
158}