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 }