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 }