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