001    /*
002     * Copyright 2011 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     */
016    package org.kuali.kfs.sys;
017    
018    import java.io.Serializable;
019    import java.lang.reflect.InvocationTargetException;
020    import java.util.Collections;
021    import java.util.Comparator;
022    import java.util.List;
023    
024    import org.apache.commons.beanutils.PropertyUtils;
025    import 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     */
031    public 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    }