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.beanutils2;
019
020import java.lang.reflect.InvocationTargetException;
021import java.util.Comparator;
022
023/**
024 * <p>
025 * This comparator compares two beans by the specified bean property. It is also possible to compare beans based on nested, indexed, combined, mapped bean
026 * properties. Please see the {@link PropertyUtilsBean} documentation for all property name possibilities.
027 *
028 * </p>
029 * <p>
030 * <strong>Note:</strong> The BeanComparator passes the values of the specified bean property to an internal natural order {@link Comparator}, if no comparator
031 * is specified in the constructor. If you are comparing two beans based on a property that could contain "null" values, a suitable {@code Comparator} or Apache
032 * Commons Collection {@code ComparatorChain} should be supplied in the constructor. Note that the passed in {@code Comparator} must be able to handle the
033 * passed in objects. Because the type of the property to be compared is not known at compile time no type checks can be performed by the compiler. Thus
034 * {@code ClassCastException} exceptions can be thrown if unexpected property values occur.
035 * </p>
036 *
037 * @param <T> the type of beans to be compared by this {@code Comparator}
038 * @param <V> the type of property to compare
039 */
040public class BeanComparator<T, V> implements Comparator<T> {
041
042    /**
043     * A {@link Comparator Comparator} that compares {@link Comparable Comparable} objects.
044     * <p>
045     * This Comparator is useful, for example, for enforcing the natural order in custom implementations of {@link java.util.SortedSet SortedSet} and
046     * {@link java.util.SortedMap SortedMap}.
047     * </p>
048     *
049     * @param <E> the type of objects compared by this comparator
050     * @see java.util.Collections#reverseOrder()
051     */
052    private static final class NaturalOrderComparator<E extends Comparable<? super E>> implements Comparator<E> {
053
054        /** The singleton instance. */
055        @SuppressWarnings("rawtypes")
056        public static final NaturalOrderComparator INSTANCE = new NaturalOrderComparator();
057
058        /**
059         * Private constructor to prevent instantiation. Only use INSTANCE.
060         */
061        private NaturalOrderComparator() {
062        }
063
064        /**
065         * Compare the two {@link Comparable Comparable} arguments. This method is equivalent to:
066         *
067         * <pre>
068         * ((Comparable) obj1).compareTo(obj2)
069         * </pre>
070         */
071        @Override
072        public int compare(final E obj1, final E obj2) {
073            return obj1.compareTo(obj2);
074        }
075
076        @Override
077        public boolean equals(final Object object) {
078            return this == object || null != object && object.getClass().equals(this.getClass());
079        }
080
081        @Override
082        public int hashCode() {
083            return "NaturalOrderComparator".hashCode();
084        }
085    }
086
087    private static final long serialVersionUID = 1L;
088
089    /** Property. */
090    private String property;
091
092    /** Comparator, untyped. */
093    private final Comparator<V> comparator;
094
095    /**
096     * <p>
097     * Constructs a Bean Comparator without a property set.
098     * </p>
099     * <p>
100     * <strong>Note</strong> that this is intended to be used only in bean-centric environments.
101     * </p>
102     * <p>
103     * Until {@link #setProperty} is called with a non-null value. this comparator will compare the Objects only.
104     * </p>
105     */
106    public BeanComparator() {
107        this(null);
108    }
109
110    /**
111     * <p>
112     * Constructs a property-based comparator for beans. This compares two beans by the property specified in the property parameter. This constructor creates a
113     * {@code BeanComparator} that uses a {@code ComparableComparator} to compare the property values.
114     * </p>
115     *
116     * <p>
117     * Passing "null" to this constructor will cause the BeanComparator to compare objects based on natural order, that is {@link Comparable}.
118     * </p>
119     *
120     * @param property String Name of a bean property, which may contain the name of a simple, nested, indexed, mapped, or combined property. See
121     *                 {@link PropertyUtilsBean} for property query language syntax. If the property passed in is null then the actual objects will be compared
122     */
123    public BeanComparator(final String property) {
124        this(property, NaturalOrderComparator.INSTANCE);
125    }
126
127    /**
128     * Constructs a property-based comparator for beans. This constructor creates a BeanComparator that uses the supplied Comparator to compare the property
129     * values.
130     *
131     * @param property   Name of a bean property, can contain the name of a simple, nested, indexed, mapped, or combined property. See {@link PropertyUtilsBean}
132     *                   for property query language syntax.
133     * @param comparator BeanComparator will pass the values of the specified bean property to this Comparator. If your bean property is not a comparable or
134     *                   contains null values, a suitable comparator may be supplied in this constructor.
135     */
136    public BeanComparator(final String property, final Comparator<V> comparator) {
137        setProperty(property);
138        this.comparator = comparator != null ? comparator : NaturalOrderComparator.INSTANCE;
139    }
140
141    /**
142     * Compare two JavaBeans by their shared property. If {@link #getProperty} is null then the actual objects will be compared.
143     *
144     * @param o1 Object The first bean to get data from to compare against
145     * @param o2 Object The second bean to get data from to compare
146     * @return int negative or positive based on order
147     */
148    @Override
149    public int compare(final T o1, final T o2) {
150
151        if (property == null) {
152            // compare the actual objects
153            return internalCompare(o1, o2);
154        }
155
156        try {
157            final Object value1 = PropertyUtils.getProperty(o1, property);
158            final Object value2 = PropertyUtils.getProperty(o2, property);
159            return internalCompare(value1, value2);
160        } catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
161            throw new RuntimeException(e.getClass().getSimpleName() + ": " + e.toString());
162        }
163    }
164
165    /**
166     * Two {@code BeanComparator}'s are equals if and only if the wrapped comparators and the property names to be compared are equal.
167     *
168     * @param o Comparator to compare to
169     * @return whether the comparators are equal or not
170     */
171    @Override
172    public boolean equals(final Object o) {
173        if (this == o) {
174            return true;
175        }
176        if (!(o instanceof BeanComparator)) {
177            return false;
178        }
179
180        final BeanComparator<?, ?> beanComparator = (BeanComparator<?, ?>) o;
181
182        if (!comparator.equals(beanComparator.comparator)) {
183            return false;
184        }
185        if (property == null) {
186            return beanComparator.property == null;
187        }
188
189        return property.equals(beanComparator.property);
190    }
191
192    /**
193     * Gets the Comparator being used to compare beans.
194     *
195     * @return the Comparator being used to compare beans
196     */
197    public Comparator<V> getComparator() {
198        return comparator;
199    }
200
201    /**
202     * Gets the property attribute of the BeanComparator
203     *
204     * @return String method name to call to compare. A null value indicates that the actual objects will be compared
205     */
206    public String getProperty() {
207        return property;
208    }
209
210    /**
211     * Hashcode compatible with equals.
212     *
213     * @return the hash code for this comparator
214     */
215    @Override
216    public int hashCode() {
217        return comparator.hashCode();
218    }
219
220    /**
221     * Compares the given values using the internal {@code Comparator}. <em>Note</em>: This comparison cannot be performed in a type-safe way; so
222     * {@code ClassCastException} exceptions may be thrown.
223     *
224     * @param val1 the first value to be compared
225     * @param val2 the second value to be compared
226     * @return the result of the comparison
227     */
228    @SuppressWarnings({ "unchecked", "rawtypes" })
229    private int internalCompare(final Object val1, final Object val2) {
230        return ((Comparator) comparator).compare(val1, val2);
231    }
232
233    /**
234     * Sets the method to be called to compare two JavaBeans
235     *
236     * @param property String method name to call to compare If the property passed in is null then the actual objects will be compared
237     */
238    public void setProperty(final String property) {
239        this.property = property;
240    }
241}