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.lang.reflect.InvocationTargetException;
021import java.io.Serializable;
022import java.util.Comparator;
023import 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 */
043public 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}