001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.beanutils; 019 020import java.io.Serializable; 021import java.lang.reflect.InvocationTargetException; 022import java.util.Comparator; 023 024import org.apache.commons.collections.comparators.ComparableComparator; 025 026/** 027 * <p> 028 * This comparator compares two beans by the specified bean property. 029 * It is also possible to compare beans based on nested, indexed, 030 * combined, mapped bean properties. Please see the {@link PropertyUtilsBean} 031 * documentation for all property name possibilities. 032 * 033 * </p><p> 034 * <strong>Note:</strong> The BeanComparator passes the values of the specified 035 * bean property to a ComparableComparator, if no comparator is 036 * specified in the constructor. If you are comparing two beans based 037 * on a property that could contain "null" values, a suitable <code>Comparator</code> 038 * or <code>ComparatorChain</code> should be supplied in the constructor. 039 * Note that the passed in {@code Comparator} must be able to handle the 040 * passed in objects. Because the type of the property to be compared is not 041 * known at compile time no type checks can be performed by the compiler. 042 * Thus {@code ClassCastException} exceptions can be thrown if unexpected 043 * property values occur. 044 * </p> 045 * 046 * @param <T> the type of beans to be compared by this {@code Comparator} 047 */ 048public class BeanComparator<T> implements Comparator<T>, Serializable { 049 050 private static final long serialVersionUID = 1L; 051 052 /** 053 * Method name to call to compare. 054 */ 055 private String property; 056 057 /** 058 * BeanComparator will pass the values of the specified bean property to this Comparator. If your bean property is not a comparable or contains null values, 059 * a suitable comparator may be supplied in this constructor. 060 */ 061 private final Comparator<?> comparator; 062 063 /** 064 * <p>Constructs a Bean Comparator without a property set. 065 * </p><p> 066 * <strong>Note</strong> that this is intended to be used 067 * only in bean-centric environments. 068 * </p><p> 069 * Until {@link #setProperty} is called with a non-null value. 070 * this comparator will compare the Objects only. 071 * </p> 072 */ 073 public BeanComparator() { 074 this(null); 075 } 076 077 /** 078 * <p>Constructs a property-based comparator for beans. 079 * This compares two beans by the property 080 * specified in the property parameter. This constructor creates 081 * a <code>BeanComparator</code> that uses a <code>ComparableComparator</code> 082 * to compare the property values. 083 * </p> 084 * 085 * <p>Passing "null" to this constructor will cause the BeanComparator 086 * to compare objects based on natural order, that is 087 * <code>java.lang.Comparable</code>. 088 * </p> 089 * 090 * @param property String Name of a bean property, which may contain the 091 * name of a simple, nested, indexed, mapped, or combined 092 * property. See {@link PropertyUtilsBean} for property query language syntax. 093 * If the property passed in is null then the actual objects will be compared 094 */ 095 public BeanComparator(final String property) { 096 this(property, ComparableComparator.getInstance()); 097 } 098 099 /** 100 * Constructs a property-based comparator for beans. 101 * This constructor creates 102 * a BeanComparator that uses the supplied Comparator to compare 103 * the property values. 104 * 105 * @param property Name of a bean property, can contain the name 106 * of a simple, nested, indexed, mapped, or combined 107 * property. See {@link PropertyUtilsBean} for property query language 108 * syntax. 109 * @param comparator BeanComparator will pass the values of the 110 * specified bean property to this Comparator. 111 * If your bean property is not a comparable or 112 * contains null values, a suitable comparator 113 * may be supplied in this constructor. 114 */ 115 public BeanComparator(final String property, final Comparator<?> comparator) { 116 setProperty(property); 117 if (comparator != null) { 118 this.comparator = comparator; 119 } else { 120 this.comparator = ComparableComparator.getInstance(); 121 } 122 } 123 124 /** 125 * Compare two JavaBeans by their shared property. 126 * If {@link #getProperty} is null then the actual objects will be compared. 127 * 128 * @param o1 Object The first bean to get data from to compare against 129 * @param o2 Object The second bean to get data from to compare 130 * @return int negative or positive based on order 131 */ 132 @Override 133 public int compare(final T o1, final T o2) { 134 135 if (property == null) { 136 // compare the actual objects 137 return internalCompare(o1, o2); 138 } 139 140 try { 141 final Object value1 = PropertyUtils.getProperty(o1, property); 142 final Object value2 = PropertyUtils.getProperty(o2, property); 143 return internalCompare(value1, value2); 144 } catch (final IllegalAccessException iae) { 145 throw new RuntimeException("IllegalAccessException: " + iae.toString()); 146 } catch (final InvocationTargetException ite) { 147 throw new RuntimeException("InvocationTargetException: " + ite.toString()); 148 } catch (final NoSuchMethodException nsme) { 149 throw new RuntimeException("NoSuchMethodException: " + nsme.toString()); 150 } 151 } 152 153 /** 154 * Two <code>BeanComparator</code>'s are equals if and only if 155 * the wrapped comparators and the property names to be compared 156 * are equal. 157 * @param o Comparator to compare to 158 * @return whether the the comparators are equal or not 159 */ 160 @Override 161 public boolean equals(final Object o) { 162 if (this == o) { 163 return true; 164 } 165 if (!(o instanceof BeanComparator)) { 166 return false; 167 } 168 169 final BeanComparator<?> beanComparator = (BeanComparator<?>) o; 170 171 if (!comparator.equals(beanComparator.comparator)) { 172 return false; 173 } 174 if (property == null) { 175 return beanComparator.property == null; 176 } 177 if (!property.equals(beanComparator.property)) { 178 return false; 179 } 180 181 return true; 182 } 183 184 /** 185 * Gets the Comparator being used to compare beans. 186 * 187 * @return the Comparator being used to compare beans 188 */ 189 public Comparator<?> getComparator() { 190 return comparator; 191 } 192 193 /** 194 * Gets the property attribute of the BeanComparator 195 * 196 * @return String method name to call to compare. 197 * A null value indicates that the actual objects will be compared 198 */ 199 public String getProperty() { 200 return property; 201 } 202 203 /** 204 * Hash code compatible with equals. 205 * @return the hash code for this comparator 206 */ 207 @Override 208 public int hashCode() { 209 return comparator.hashCode(); 210 } 211 212 /** 213 * Compares the given values using the internal {@code Comparator}. 214 * <em>Note</em>: This comparison cannot be performed in a type-safe way; so 215 * {@code ClassCastException} exceptions may be thrown. 216 * 217 * @param val1 the first value to be compared 218 * @param val2 the second value to be compared 219 * @return the result of the comparison 220 */ 221 private int internalCompare(final Object val1, final Object val2) { 222 @SuppressWarnings("rawtypes") 223 final 224 // to make the compiler happy 225 Comparator c = comparator; 226 return c.compare(val1, val2); 227 } 228 229 /** 230 * Sets the method to be called to compare two JavaBeans 231 * 232 * @param property String method name to call to compare 233 * If the property passed in is null then the actual objects will be compared 234 */ 235 public void setProperty(final String property) { 236 this.property = property; 237 } 238}