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}