001 /** 002 * Copyright 2005-2013 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 }