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    }