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 * http://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.lang.reflect.InvocationTargetException; 021import java.util.Comparator; 022 023/** 024 * <p> 025 * This comparator compares two beans by the specified bean property. It is also possible to compare beans based on nested, indexed, combined, mapped bean 026 * properties. Please see the {@link PropertyUtilsBean} documentation for all property name possibilities. 027 * 028 * </p> 029 * <p> 030 * <strong>Note:</strong> The BeanComparator passes the values of the specified bean property to an internal natural order {@link Comparator}, if no comparator 031 * 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 032 * Commons Collection {@code ComparatorChain} should be supplied in the constructor. Note that the passed in {@code Comparator} must be able to handle the 033 * 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 034 * {@code ClassCastException} exceptions can be thrown if unexpected property values occur. 035 * </p> 036 * 037 * @param <T> the type of beans to be compared by this {@code Comparator} 038 * @param <V> the type of property to compare 039 */ 040public class BeanComparator<T, V> implements Comparator<T> { 041 042 /** 043 * A {@link Comparator Comparator} that compares {@link Comparable Comparable} objects. 044 * <p> 045 * This Comparator is useful, for example, for enforcing the natural order in custom implementations of {@link java.util.SortedSet SortedSet} and 046 * {@link java.util.SortedMap SortedMap}. 047 * </p> 048 * 049 * @param <E> the type of objects compared by this comparator 050 * @see java.util.Collections#reverseOrder() 051 */ 052 private static final class NaturalOrderComparator<E extends Comparable<? super E>> implements Comparator<E> { 053 054 /** The singleton instance. */ 055 @SuppressWarnings("rawtypes") 056 public static final NaturalOrderComparator INSTANCE = new NaturalOrderComparator(); 057 058 /** 059 * Private constructor to prevent instantiation. Only use INSTANCE. 060 */ 061 private NaturalOrderComparator() { 062 } 063 064 /** 065 * Compare the two {@link Comparable Comparable} arguments. This method is equivalent to: 066 * 067 * <pre> 068 * ((Comparable) obj1).compareTo(obj2) 069 * </pre> 070 */ 071 @Override 072 public int compare(final E obj1, final E obj2) { 073 return obj1.compareTo(obj2); 074 } 075 076 @Override 077 public boolean equals(final Object object) { 078 return this == object || null != object && object.getClass().equals(this.getClass()); 079 } 080 081 @Override 082 public int hashCode() { 083 return "NaturalOrderComparator".hashCode(); 084 } 085 } 086 087 private static final long serialVersionUID = 1L; 088 089 /** Property. */ 090 private String property; 091 092 /** Comparator, untyped. */ 093 private final Comparator<V> comparator; 094 095 /** 096 * <p> 097 * Constructs a Bean Comparator without a property set. 098 * </p> 099 * <p> 100 * <strong>Note</strong> that this is intended to be used only in bean-centric environments. 101 * </p> 102 * <p> 103 * Until {@link #setProperty} is called with a non-null value. this comparator will compare the Objects only. 104 * </p> 105 */ 106 public BeanComparator() { 107 this(null); 108 } 109 110 /** 111 * <p> 112 * Constructs a property-based comparator for beans. This compares two beans by the property specified in the property parameter. This constructor creates a 113 * {@code BeanComparator} that uses a {@code ComparableComparator} to compare the property values. 114 * </p> 115 * 116 * <p> 117 * Passing "null" to this constructor will cause the BeanComparator to compare objects based on natural order, that is {@link Comparable}. 118 * </p> 119 * 120 * @param property String Name of a bean property, which may contain the name of a simple, nested, indexed, mapped, or combined property. See 121 * {@link PropertyUtilsBean} for property query language syntax. If the property passed in is null then the actual objects will be compared 122 */ 123 public BeanComparator(final String property) { 124 this(property, NaturalOrderComparator.INSTANCE); 125 } 126 127 /** 128 * Constructs a property-based comparator for beans. This constructor creates a BeanComparator that uses the supplied Comparator to compare the property 129 * values. 130 * 131 * @param property Name of a bean property, can contain the name of a simple, nested, indexed, mapped, or combined property. See {@link PropertyUtilsBean} 132 * for property query language syntax. 133 * @param comparator BeanComparator will pass the values of the specified bean property to this Comparator. If your bean property is not a comparable or 134 * contains null values, a suitable comparator may be supplied in this constructor. 135 */ 136 public BeanComparator(final String property, final Comparator<V> comparator) { 137 setProperty(property); 138 this.comparator = comparator != null ? comparator : NaturalOrderComparator.INSTANCE; 139 } 140 141 /** 142 * Compare two JavaBeans by their shared property. If {@link #getProperty} is null then the actual objects will be compared. 143 * 144 * @param o1 Object The first bean to get data from to compare against 145 * @param o2 Object The second bean to get data from to compare 146 * @return int negative or positive based on order 147 */ 148 @Override 149 public int compare(final T o1, final T o2) { 150 151 if (property == null) { 152 // compare the actual objects 153 return internalCompare(o1, o2); 154 } 155 156 try { 157 final Object value1 = PropertyUtils.getProperty(o1, property); 158 final Object value2 = PropertyUtils.getProperty(o2, property); 159 return internalCompare(value1, value2); 160 } catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 161 throw new RuntimeException(e.getClass().getSimpleName() + ": " + e.toString()); 162 } 163 } 164 165 /** 166 * Two {@code BeanComparator}'s are equals if and only if the wrapped comparators and the property names to be compared are equal. 167 * 168 * @param o Comparator to compare to 169 * @return whether the comparators are equal or not 170 */ 171 @Override 172 public boolean equals(final Object o) { 173 if (this == o) { 174 return true; 175 } 176 if (!(o instanceof BeanComparator)) { 177 return false; 178 } 179 180 final BeanComparator<?, ?> beanComparator = (BeanComparator<?, ?>) o; 181 182 if (!comparator.equals(beanComparator.comparator)) { 183 return false; 184 } 185 if (property == null) { 186 return beanComparator.property == null; 187 } 188 189 return property.equals(beanComparator.property); 190 } 191 192 /** 193 * Gets the Comparator being used to compare beans. 194 * 195 * @return the Comparator being used to compare beans 196 */ 197 public Comparator<V> getComparator() { 198 return comparator; 199 } 200 201 /** 202 * Gets the property attribute of the BeanComparator 203 * 204 * @return String method name to call to compare. A null value indicates that the actual objects will be compared 205 */ 206 public String getProperty() { 207 return property; 208 } 209 210 /** 211 * Hashcode compatible with equals. 212 * 213 * @return the hash code for this comparator 214 */ 215 @Override 216 public int hashCode() { 217 return comparator.hashCode(); 218 } 219 220 /** 221 * Compares the given values using the internal {@code Comparator}. <em>Note</em>: This comparison cannot be performed in a type-safe way; so 222 * {@code ClassCastException} exceptions may be thrown. 223 * 224 * @param val1 the first value to be compared 225 * @param val2 the second value to be compared 226 * @return the result of the comparison 227 */ 228 @SuppressWarnings({ "unchecked", "rawtypes" }) 229 private int internalCompare(final Object val1, final Object val2) { 230 return ((Comparator) comparator).compare(val1, val2); 231 } 232 233 /** 234 * Sets the method to be called to compare two JavaBeans 235 * 236 * @param property String method name to call to compare If the property passed in is null then the actual objects will be compared 237 */ 238 public void setProperty(final String property) { 239 this.property = property; 240 } 241}