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