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 }