View Javadoc
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    *      https://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  
18  package org.apache.commons.beanutils2;
19  
20  import java.util.Comparator;
21  
22  /**
23   * <p>
24   * This comparator compares two beans by the specified bean property. It is also possible to compare beans based on nested, indexed, combined, mapped bean
25   * properties. Please see the {@link PropertyUtilsBean} documentation for all property name possibilities.
26   *
27   * </p>
28   * <p>
29   * <strong>Note:</strong> The BeanComparator passes the values of the specified bean property to an internal natural order {@link Comparator}, if no comparator
30   * 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
31   * Commons Collection {@code ComparatorChain} should be supplied in the constructor. Note that the passed in {@code Comparator} must be able to handle the
32   * 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
33   * {@code ClassCastException} exceptions can be thrown if unexpected property values occur.
34   * </p>
35   *
36   * @param <T> the type of beans to be compared by this {@code Comparator}
37   * @param <V> the type of property to compare
38   */
39  public class BeanComparator<T, V> implements Comparator<T> {
40  
41      /**
42       * A {@link Comparator Comparator} that compares {@link Comparable Comparable} objects.
43       * <p>
44       * This Comparator is useful, for example, for enforcing the natural order in custom implementations of {@link java.util.SortedSet SortedSet} and
45       * {@link java.util.SortedMap SortedMap}.
46       * </p>
47       *
48       * @param <E> the type of objects compared by this comparator
49       * @see java.util.Collections#reverseOrder()
50       */
51      private static final class NaturalOrderComparator<E extends Comparable<? super E>> implements Comparator<E> {
52  
53          /** The singleton instance. */
54          @SuppressWarnings("rawtypes")
55          public static final NaturalOrderComparator INSTANCE = new NaturalOrderComparator();
56  
57          /**
58           * Private constructor to prevent instantiation. Only use INSTANCE.
59           */
60          private NaturalOrderComparator() {
61          }
62  
63          /**
64           * Compare the two {@link Comparable Comparable} arguments. This method is equivalent to:
65           *
66           * <pre>
67           * ((Comparable) obj1).compareTo(obj2)
68           * </pre>
69           */
70          @Override
71          public int compare(final E obj1, final E obj2) {
72              return obj1.compareTo(obj2);
73          }
74  
75          @Override
76          public boolean equals(final Object object) {
77              return this == object || null != object && object.getClass().equals(this.getClass());
78          }
79  
80          @Override
81          public int hashCode() {
82              return "NaturalOrderComparator".hashCode();
83          }
84      }
85  
86      private static final long serialVersionUID = 1L;
87  
88      /** Property. */
89      private String property;
90  
91      /** Comparator, untyped. */
92      private final Comparator<V> comparator;
93  
94      /**
95       * <p>
96       * Constructs a Bean Comparator without a property set.
97       * </p>
98       * <p>
99       * <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 }