001 /**
002 * Copyright 2005-2012 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.rice.krad.util;
017
018 import org.apache.commons.beanutils.PropertyUtils;
019 import org.apache.commons.collections.comparators.ComparableComparator;
020 import org.kuali.rice.core.api.exception.KualiException;
021 import org.kuali.rice.core.api.util.type.TypeUtils;
022
023 import java.beans.PropertyDescriptor;
024 import java.io.Serializable;
025 import java.lang.reflect.InvocationTargetException;
026 import java.util.Collections;
027 import java.util.Comparator;
028 import java.util.Iterator;
029 import java.util.List;
030
031 /**
032 * BeanPropertyComparator compares the two beans using multiple property names
033 *
034 *
035 */
036 public class BeanPropertyComparator implements Comparator, Serializable {
037 private static final long serialVersionUID = -2675700473766186018L;
038 boolean ignoreCase;
039 private List propertyNames;
040 private Comparator stringComparator;
041 private Comparator booleanComparator;
042 private Comparator genericComparator;
043
044 /**
045 * Constructs a PropertyComparator for comparing beans using the properties named in the given List
046 *
047 * <p>if the List is null, the beans will be compared directly
048 * by Properties will be compared in the order in which they are listed. Case will be ignored
049 * in String comparisons.</p>
050 *
051 * @param propertyNames List of property names (as Strings) used to compare beans
052 */
053 public BeanPropertyComparator(List propertyNames) {
054 this(propertyNames, true);
055 }
056
057 /**
058 * Constructs a PropertyComparator for comparing beans using the properties named in the given List.
059 *
060 * <p>Properties will be compared
061 * in the order in which they are listed. Case will be ignored if ignoreCase is true.</p>
062 *
063 * @param propertyNames List of property names (as Strings) used to compare beans
064 * @param ignoreCase if true, case will be ignored during String comparisons
065 */
066 public BeanPropertyComparator(List propertyNames, boolean ignoreCase) {
067 if (propertyNames == null) {
068 throw new IllegalArgumentException("invalid (null) propertyNames list");
069 }
070 if (propertyNames.size() == 0) {
071 throw new IllegalArgumentException("invalid (empty) propertyNames list");
072 }
073 this.propertyNames = Collections.unmodifiableList(propertyNames);
074 this.ignoreCase = ignoreCase;
075
076 if (ignoreCase) {
077 this.stringComparator = String.CASE_INSENSITIVE_ORDER;
078 }
079 else {
080 this.stringComparator = ComparableComparator.getInstance();
081 }
082 this.booleanComparator = new Comparator() {
083 public int compare(Object o1, Object o2) {
084 int compared = 0;
085
086 Boolean b1 = (Boolean) o1;
087 Boolean b2 = (Boolean) o2;
088
089 if (!b1.equals(b2)) {
090 if (b1.equals(Boolean.FALSE)) {
091 compared = -1;
092 }
093 else {
094 compared = 1;
095 }
096 }
097
098 return compared;
099 }
100
101 };
102 this.genericComparator = ComparableComparator.getInstance();
103 }
104
105
106 /**
107 * Compare two JavaBeans by the properties given to the constructor.
108 *
109 * @param o1 Object The first bean to get data from to compare against
110 * @param o2 Object The second bean to get data from to compare
111 * @return int negative or positive based on order
112 */
113 public int compare(Object o1, Object o2) {
114 int compared = 0;
115
116 try {
117 for (Iterator i = propertyNames.iterator(); (compared == 0) && i.hasNext();) {
118 String currentProperty = i.next().toString();
119
120 // choose appropriate comparator
121 Comparator currentComparator = null;
122 try {
123 PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(o1, currentProperty);
124 Class propertyClass = propertyDescriptor.getPropertyType();
125 if (propertyClass.equals(String.class)) {
126 currentComparator = this.stringComparator;
127 }
128 else if (TypeUtils.isBooleanClass(propertyClass)) {
129 currentComparator = this.booleanComparator;
130 }
131 else {
132 currentComparator = this.genericComparator;
133 }
134 }
135 catch (NullPointerException e) {
136 throw new BeanComparisonException("unable to find property '" + o1.getClass().getName() + "." + currentProperty + "'", e);
137 }
138
139 // compare the values
140 Object value1 = PropertyUtils.getProperty(o1, currentProperty);
141 Object value2 = PropertyUtils.getProperty(o2, currentProperty);
142 /* Fix for KULRICE-5170 : BeanPropertyComparator throws exception when a null value is found in sortable non-string data type column */
143 if ( value1 == null && value2 == null)
144 return 0;
145 else if ( value1 == null)
146 return -1;
147 else if ( value2 == null )
148 return 1;
149 /* End KULRICE-5170 Fix*/
150 compared = currentComparator.compare(value1, value2);
151 }
152 }
153 catch (IllegalAccessException e) {
154 throw new BeanComparisonException("unable to compare property values", e);
155 }
156 catch (NoSuchMethodException e) {
157 throw new BeanComparisonException("unable to compare property values", e);
158 }
159 catch (InvocationTargetException e) {
160 throw new BeanComparisonException("unable to compare property values", e);
161 }
162
163 return compared;
164 }
165
166 public static class BeanComparisonException extends KualiException {
167 private static final long serialVersionUID = 2622379680100640029L;
168
169 /**
170 * @param message
171 * @param t
172 */
173 public BeanComparisonException(String message, Throwable t) {
174 super(message, t);
175 }
176 }
177 }