    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    }