View Javadoc

1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.util;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.collections.comparators.ComparableComparator;
20  import org.kuali.rice.core.api.exception.KualiException;
21  import org.kuali.rice.core.api.util.type.TypeUtils;
22  
23  import java.beans.PropertyDescriptor;
24  import java.io.Serializable;
25  import java.lang.reflect.InvocationTargetException;
26  import java.util.Collections;
27  import java.util.Comparator;
28  import java.util.Iterator;
29  import java.util.List;
30  
31  /**
32   * BeanPropertyComparator compares the two beans using multiple property names
33   * 
34   * 
35   */
36  public class BeanPropertyComparator implements Comparator, Serializable {
37      private static final long serialVersionUID = -2675700473766186018L;
38      boolean ignoreCase;
39      private List propertyNames;
40      private Comparator stringComparator;
41      private Comparator booleanComparator;
42      private Comparator genericComparator;
43  
44      /**
45       * Constructs a PropertyComparator for comparing beans using the properties named in the given List
46       *
47       * <p>if the List is null, the beans will be compared directly
48       * by Properties will be compared in the order in which they are listed. Case will be ignored
49       * in String comparisons.</p>
50       * 
51       * @param propertyNames List of property names (as Strings) used to compare beans
52       */
53      public BeanPropertyComparator(List propertyNames) {
54          this(propertyNames, true);
55      }
56  
57      /**
58       * Constructs a PropertyComparator for comparing beans using the properties named in the given List.
59       *
60       * <p>Properties will be compared
61       * in the order in which they are listed. Case will be ignored if ignoreCase is true.</p>
62       * 
63       * @param propertyNames List of property names (as Strings) used to compare beans
64       * @param ignoreCase if true, case will be ignored during String comparisons
65       */
66      public BeanPropertyComparator(List propertyNames, boolean ignoreCase) {
67          if (propertyNames == null) {
68              throw new IllegalArgumentException("invalid (null) propertyNames list");
69          }
70          if (propertyNames.size() == 0) {
71              throw new IllegalArgumentException("invalid (empty) propertyNames list");
72          }
73          this.propertyNames = Collections.unmodifiableList(propertyNames);
74          this.ignoreCase = ignoreCase;
75  
76          if (ignoreCase) {
77              this.stringComparator = String.CASE_INSENSITIVE_ORDER;
78          }
79          else {
80              this.stringComparator = ComparableComparator.getInstance();
81          }
82          this.booleanComparator = new Comparator() {
83              public int compare(Object o1, Object o2) {
84                  int compared = 0;
85  
86                  Boolean b1 = (Boolean) o1;
87                  Boolean b2 = (Boolean) o2;
88  
89                  if (!b1.equals(b2)) {
90                      if (b1.equals(Boolean.FALSE)) {
91                          compared = -1;
92                      }
93                      else {
94                          compared = 1;
95                      }
96                  }
97  
98                  return compared;
99              }
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 }