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
018 package org.apache.commons.beanutils;
019
020 import java.lang.reflect.InvocationTargetException;
021 import java.io.Serializable;
022 import java.util.Comparator;
023 import org.apache.commons.collections.comparators.ComparableComparator;
024
025 /**
026 * <p>
027 * This comparator compares two beans by the specified bean property.
028 * It is also possible to compare beans based on nested, indexed,
029 * combined, mapped bean properties. Please see the {@link PropertyUtilsBean}
030 * documentation for all property name possibilities.
031 *
032 * </p><p>
033 * <strong>Note:</strong> The BeanComparator passes the values of the specified
034 * bean property to a ComparableComparator, if no comparator is
035 * specified in the constructor. If you are comparing two beans based
036 * on a property that could contain "null" values, a suitable <code>Comparator</code>
037 * or <code>ComparatorChain</code> should be supplied in the constructor.
038 * </p>
039 *
040 * @author <a href"mailto:epugh@upstate.com">Eric Pugh</a>
041 * @author Tim O'Brien
042 */
043 public class BeanComparator implements Comparator, Serializable {
044
045 private String property;
046 private Comparator comparator;
047
048 /**
049 * <p>Constructs a Bean Comparator without a property set.
050 * </p><p>
051 * <strong>Note</strong> that this is intended to be used
052 * only in bean-centric environments.
053 * </p><p>
054 * Until {@link #setProperty} is called with a non-null value.
055 * this comparator will compare the Objects only.
056 * </p>
057 */
058 public BeanComparator() {
059 this( null );
060 }
061
062 /**
063 * <p>Constructs a property-based comparator for beans.
064 * This compares two beans by the property
065 * specified in the property parameter. This constructor creates
066 * a <code>BeanComparator</code> that uses a <code>ComparableComparator</code>
067 * to compare the property values.
068 * </p>
069 *
070 * <p>Passing "null" to this constructor will cause the BeanComparator
071 * to compare objects based on natural order, that is
072 * <code>java.lang.Comparable</code>.
073 * </p>
074 *
075 * @param property String Name of a bean property, which may contain the
076 * name of a simple, nested, indexed, mapped, or combined
077 * property. See {@link PropertyUtilsBean} for property query language syntax.
078 * If the property passed in is null then the actual objects will be compared
079 */
080 public BeanComparator( String property ) {
081 this( property, ComparableComparator.getInstance() );
082 }
083
084 /**
085 * Constructs a property-based comparator for beans.
086 * This constructor creates
087 * a BeanComparator that uses the supplied Comparator to compare
088 * the property values.
089 *
090 * @param property Name of a bean property, can contain the name
091 * of a simple, nested, indexed, mapped, or combined
092 * property. See {@link PropertyUtilsBean} for property query language
093 * syntax.
094 * @param comparator BeanComparator will pass the values of the
095 * specified bean property to this Comparator.
096 * If your bean property is not a comparable or
097 * contains null values, a suitable comparator
098 * may be supplied in this constructor.
099 */
100 public BeanComparator( String property, Comparator comparator ) {
101 setProperty( property );
102 if (comparator != null) {
103 this.comparator = comparator;
104 } else {
105 this.comparator = ComparableComparator.getInstance();
106 }
107 }
108
109 /**
110 * Sets the method to be called to compare two JavaBeans
111 *
112 * @param property String method name to call to compare
113 * If the property passed in is null then the actual objects will be compared
114 */
115 public void setProperty( String property ) {
116 this.property = property;
117 }
118
119
120 /**
121 * Gets the property attribute of the BeanComparator
122 *
123 * @return String method name to call to compare.
124 * A null value indicates that the actual objects will be compared
125 */
126 public String getProperty() {
127 return property;
128 }
129
130
131 /**
132 * Gets the Comparator being used to compare beans.
133 *
134 * @return the Comparator being used to compare beans
135 */
136 public Comparator getComparator() {
137 return comparator;
138 }
139
140
141 /**
142 * Compare two JavaBeans by their shared property.
143 * If {@link #getProperty} is null then the actual objects will be compared.
144 *
145 * @param o1 Object The first bean to get data from to compare against
146 * @param o2 Object The second bean to get data from to compare
147 * @return int negative or positive based on order
148 */
149 public int compare( Object o1, Object o2 ) {
150
151 if ( property == null ) {
152 // compare the actual objects
153 return comparator.compare( o1, o2 );
154 }
155
156 try {
157 Object value1 = PropertyUtils.getProperty( o1, property );
158 Object value2 = PropertyUtils.getProperty( o2, property );
159 return comparator.compare( value1, value2 );
160 }
161 catch ( IllegalAccessException iae ) {
162 throw new RuntimeException( "IllegalAccessException: " + iae.toString() );
163 }
164 catch ( InvocationTargetException ite ) {
165 throw new RuntimeException( "InvocationTargetException: " + ite.toString() );
166 }
167 catch ( NoSuchMethodException nsme ) {
168 throw new RuntimeException( "NoSuchMethodException: " + nsme.toString() );
169 }
170 }
171
172 /**
173 * Two <code>BeanComparator</code>'s are equals if and only if
174 * the wrapped comparators and the property names to be compared
175 * are equal.
176 * @param o Comparator to compare to
177 * @return whether the the comparators are equal or not
178 */
179 public boolean equals(Object o) {
180 if (this == o) {
181 return true;
182 }
183 if (!(o instanceof BeanComparator)) {
184 return false;
185 }
186
187 final BeanComparator beanComparator = (BeanComparator) o;
188
189 if (!comparator.equals(beanComparator.comparator)) {
190 return false;
191 }
192 if (property != null)
193 {
194 if (!property.equals(beanComparator.property)) {
195 return false;
196 }
197 }
198 else
199 {
200 return (beanComparator.property == null);
201 }
202
203 return true;
204 }
205
206 /**
207 * Hashcode compatible with equals.
208 * @return the hash code for this comparator
209 */
210 public int hashCode() {
211 int result;
212 result = comparator.hashCode();
213 return result;
214 }
215 }