View Javadoc

1   /**
2    * Copyright 2005-2012 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   * This class 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; if the List is null, the
46       * beans will be compared directly (by Properties will be compared in the order in which they are listed. Case will be ignored
47       * in String comparisons.
48       * 
49       * @param propertyNames List of property names (as Strings) used to compare beans
50       */
51      public BeanPropertyComparator(List propertyNames) {
52          this(propertyNames, true);
53      }
54  
55      /**
56       * Constructs a PropertyComparator for comparing beans using the properties named in the given List. Properties will be compared
57       * in the order in which they are listed. Case will be ignored if ignoreCase is true.
58       * 
59       * @param propertyNames List of property names (as Strings) used to compare beans
60       * @param ignoreCase if true, case will be ignored during String comparisons
61       */
62      public BeanPropertyComparator(List propertyNames, boolean ignoreCase) {
63          if (propertyNames == null) {
64              throw new IllegalArgumentException("invalid (null) propertyNames list");
65          }
66          if (propertyNames.size() == 0) {
67              throw new IllegalArgumentException("invalid (empty) propertyNames list");
68          }
69          this.propertyNames = Collections.unmodifiableList(propertyNames);
70          this.ignoreCase = ignoreCase;
71  
72          if (ignoreCase) {
73              this.stringComparator = String.CASE_INSENSITIVE_ORDER;
74          }
75          else {
76              this.stringComparator = ComparableComparator.getInstance();
77          }
78          this.booleanComparator = new Comparator() {
79              public int compare(Object o1, Object o2) {
80                  int compared = 0;
81  
82                  Boolean b1 = (Boolean) o1;
83                  Boolean b2 = (Boolean) o2;
84  
85                  if (!b1.equals(b2)) {
86                      if (b1.equals(Boolean.FALSE)) {
87                          compared = -1;
88                      }
89                      else {
90                          compared = 1;
91                      }
92                  }
93  
94                  return compared;
95              }
96  
97          };
98          this.genericComparator = ComparableComparator.getInstance();
99      }
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 }