BeanComparator.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.beanutils2;

  18. import java.lang.reflect.InvocationTargetException;
  19. import java.util.Comparator;

  20. /**
  21.  * <p>
  22.  * This comparator compares two beans by the specified bean property. It is also possible to compare beans based on nested, indexed, combined, mapped bean
  23.  * properties. Please see the {@link PropertyUtilsBean} documentation for all property name possibilities.
  24.  *
  25.  * </p>
  26.  * <p>
  27.  * <strong>Note:</strong> The BeanComparator passes the values of the specified bean property to an internal natural order {@link Comparator}, if no comparator
  28.  * 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
  29.  * Commons Collection {@code ComparatorChain} should be supplied in the constructor. Note that the passed in {@code Comparator} must be able to handle the
  30.  * 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
  31.  * {@code ClassCastException} exceptions can be thrown if unexpected property values occur.
  32.  * </p>
  33.  *
  34.  * @param <T> the type of beans to be compared by this {@code Comparator}
  35.  * @param <V> the type of property to compare
  36.  */
  37. public class BeanComparator<T, V> implements Comparator<T> {

  38.     /**
  39.      * A {@link Comparator Comparator} that compares {@link Comparable Comparable} objects.
  40.      * <p>
  41.      * This Comparator is useful, for example, for enforcing the natural order in custom implementations of {@link java.util.SortedSet SortedSet} and
  42.      * {@link java.util.SortedMap SortedMap}.
  43.      * </p>
  44.      *
  45.      * @param <E> the type of objects compared by this comparator
  46.      * @see java.util.Collections#reverseOrder()
  47.      */
  48.     private static final class NaturalOrderComparator<E extends Comparable<? super E>> implements Comparator<E> {

  49.         /** The singleton instance. */
  50.         @SuppressWarnings("rawtypes")
  51.         public static final NaturalOrderComparator INSTANCE = new NaturalOrderComparator();

  52.         /**
  53.          * Private constructor to prevent instantiation. Only use INSTANCE.
  54.          */
  55.         private NaturalOrderComparator() {
  56.         }

  57.         /**
  58.          * Compare the two {@link Comparable Comparable} arguments. This method is equivalent to:
  59.          *
  60.          * <pre>
  61.          * ((Comparable) obj1).compareTo(obj2)
  62.          * </pre>
  63.          */
  64.         @Override
  65.         public int compare(final E obj1, final E obj2) {
  66.             return obj1.compareTo(obj2);
  67.         }

  68.         @Override
  69.         public boolean equals(final Object object) {
  70.             return this == object || null != object && object.getClass().equals(this.getClass());
  71.         }

  72.         @Override
  73.         public int hashCode() {
  74.             return "NaturalOrderComparator".hashCode();
  75.         }
  76.     }

  77.     private static final long serialVersionUID = 1L;

  78.     /** Property. */
  79.     private String property;

  80.     /** Comparator, untyped. */
  81.     private final Comparator<V> comparator;

  82.     /**
  83.      * <p>
  84.      * Constructs a Bean Comparator without a property set.
  85.      * </p>
  86.      * <p>
  87.      * <strong>Note</strong> that this is intended to be used only in bean-centric environments.
  88.      * </p>
  89.      * <p>
  90.      * Until {@link #setProperty} is called with a non-null value. this comparator will compare the Objects only.
  91.      * </p>
  92.      */
  93.     public BeanComparator() {
  94.         this(null);
  95.     }

  96.     /**
  97.      * <p>
  98.      * Constructs a property-based comparator for beans. This compares two beans by the property specified in the property parameter. This constructor creates a
  99.      * {@code BeanComparator} that uses a {@code ComparableComparator} to compare the property values.
  100.      * </p>
  101.      *
  102.      * <p>
  103.      * Passing "null" to this constructor will cause the BeanComparator to compare objects based on natural order, that is {@link Comparable}.
  104.      * </p>
  105.      *
  106.      * @param property String Name of a bean property, which may contain the name of a simple, nested, indexed, mapped, or combined property. See
  107.      *                 {@link PropertyUtilsBean} for property query language syntax. If the property passed in is null then the actual objects will be compared
  108.      */
  109.     public BeanComparator(final String property) {
  110.         this(property, NaturalOrderComparator.INSTANCE);
  111.     }

  112.     /**
  113.      * Constructs a property-based comparator for beans. This constructor creates a BeanComparator that uses the supplied Comparator to compare the property
  114.      * values.
  115.      *
  116.      * @param property   Name of a bean property, can contain the name of a simple, nested, indexed, mapped, or combined property. See {@link PropertyUtilsBean}
  117.      *                   for property query language syntax.
  118.      * @param comparator BeanComparator will pass the values of the specified bean property to this Comparator. If your bean property is not a comparable or
  119.      *                   contains null values, a suitable comparator may be supplied in this constructor.
  120.      */
  121.     public BeanComparator(final String property, final Comparator<V> comparator) {
  122.         setProperty(property);
  123.         this.comparator = comparator != null ? comparator : NaturalOrderComparator.INSTANCE;
  124.     }

  125.     /**
  126.      * Compare two JavaBeans by their shared property. If {@link #getProperty} is null then the actual objects will be compared.
  127.      *
  128.      * @param o1 Object The first bean to get data from to compare against
  129.      * @param o2 Object The second bean to get data from to compare
  130.      * @return int negative or positive based on order
  131.      */
  132.     @Override
  133.     public int compare(final T o1, final T o2) {

  134.         if (property == null) {
  135.             // compare the actual objects
  136.             return internalCompare(o1, o2);
  137.         }

  138.         try {
  139.             final Object value1 = PropertyUtils.getProperty(o1, property);
  140.             final Object value2 = PropertyUtils.getProperty(o2, property);
  141.             return internalCompare(value1, value2);
  142.         } catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
  143.             throw new RuntimeException(e.getClass().getSimpleName() + ": " + e.toString());
  144.         }
  145.     }

  146.     /**
  147.      * Two {@code BeanComparator}'s are equals if and only if the wrapped comparators and the property names to be compared are equal.
  148.      *
  149.      * @param o Comparator to compare to
  150.      * @return whether the comparators are equal or not
  151.      */
  152.     @Override
  153.     public boolean equals(final Object o) {
  154.         if (this == o) {
  155.             return true;
  156.         }
  157.         if (!(o instanceof BeanComparator)) {
  158.             return false;
  159.         }

  160.         final BeanComparator<?, ?> beanComparator = (BeanComparator<?, ?>) o;

  161.         if (!comparator.equals(beanComparator.comparator)) {
  162.             return false;
  163.         }
  164.         if (property == null) {
  165.             return beanComparator.property == null;
  166.         }

  167.         return property.equals(beanComparator.property);
  168.     }

  169.     /**
  170.      * Gets the Comparator being used to compare beans.
  171.      *
  172.      * @return the Comparator being used to compare beans
  173.      */
  174.     public Comparator<V> getComparator() {
  175.         return comparator;
  176.     }

  177.     /**
  178.      * Gets the property attribute of the BeanComparator
  179.      *
  180.      * @return String method name to call to compare. A null value indicates that the actual objects will be compared
  181.      */
  182.     public String getProperty() {
  183.         return property;
  184.     }

  185.     /**
  186.      * Hashcode compatible with equals.
  187.      *
  188.      * @return the hash code for this comparator
  189.      */
  190.     @Override
  191.     public int hashCode() {
  192.         return comparator.hashCode();
  193.     }

  194.     /**
  195.      * Compares the given values using the internal {@code Comparator}. <em>Note</em>: This comparison cannot be performed in a type-safe way; so
  196.      * {@code ClassCastException} exceptions may be thrown.
  197.      *
  198.      * @param val1 the first value to be compared
  199.      * @param val2 the second value to be compared
  200.      * @return the result of the comparison
  201.      */
  202.     @SuppressWarnings({ "unchecked", "rawtypes" })
  203.     private int internalCompare(final Object val1, final Object val2) {
  204.         return ((Comparator) comparator).compare(val1, val2);
  205.     }

  206.     /**
  207.      * Sets the method to be called to compare two JavaBeans
  208.      *
  209.      * @param property String method name to call to compare If the property passed in is null then the actual objects will be compared
  210.      */
  211.     public void setProperty(final String property) {
  212.         this.property = property;
  213.     }
  214. }