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 */ 017package org.apache.commons.collections4.comparators; 018 019import java.io.Serializable; 020import java.util.ArrayList; 021import java.util.BitSet; 022import java.util.Comparator; 023import java.util.Iterator; 024import java.util.List; 025 026/** 027 * A ComparatorChain is a Comparator that wraps one or more Comparators in 028 * sequence. The ComparatorChain calls each Comparator in sequence until either 029 * 1) any single Comparator returns a non-zero result (and that result is then 030 * returned), or 2) the ComparatorChain is exhausted (and zero is returned). 031 * This type of sorting is very similar to multi-column sorting in SQL, and this 032 * class allows Java classes to emulate that kind of behaviour when sorting a 033 * List. 034 * <p> 035 * To further facilitate SQL-like sorting, the order of any single Comparator in 036 * the list can be reversed. 037 * <p> 038 * Calling a method that adds new Comparators or changes the ascend/descend sort 039 * <i>after compare(Object, Object) has been called</i> will result in an 040 * UnsupportedOperationException. However, <i>take care</i> to not alter the 041 * underlying List of Comparators or the BitSet that defines the sort order. 042 * <p> 043 * Instances of ComparatorChain are not synchronized. The class is not 044 * thread-safe at construction time, but it <i>is</i> thread-safe to perform 045 * multiple comparisons after all the setup operations are complete. 046 * 047 * @param <E> the type of objects compared by this comparator 048 * @since 2.0 049 */ 050public class ComparatorChain<E> implements Comparator<E>, Serializable { 051 052 /** Serialization version from Collections 2.0. */ 053 private static final long serialVersionUID = -721644942746081630L; 054 055 /** The list of comparators in the chain. */ 056 private final List<Comparator<E>> comparatorChain; 057 /** Order - false (clear) = ascend; true (set) = descend. */ 058 private BitSet orderingBits = null; 059 /** Whether the chain has been "locked". */ 060 private boolean isLocked = false; 061 062 //----------------------------------------------------------------------- 063 /** 064 * Construct a ComparatorChain with no Comparators. 065 * You must add at least one Comparator before calling 066 * the compare(Object,Object) method, or an 067 * UnsupportedOperationException is thrown 068 */ 069 public ComparatorChain() { 070 this(new ArrayList<Comparator<E>>(), new BitSet()); 071 } 072 073 /** 074 * Construct a ComparatorChain with a single Comparator, 075 * sorting in the forward order 076 * 077 * @param comparator First comparator in the Comparator chain 078 */ 079 public ComparatorChain(final Comparator<E> comparator) { 080 this(comparator, false); 081 } 082 083 /** 084 * Construct a Comparator chain with a single Comparator, 085 * sorting in the given order 086 * 087 * @param comparator First Comparator in the ComparatorChain 088 * @param reverse false = forward sort; true = reverse sort 089 */ 090 public ComparatorChain(final Comparator<E> comparator, final boolean reverse) { 091 comparatorChain = new ArrayList<>(1); 092 comparatorChain.add(comparator); 093 orderingBits = new BitSet(1); 094 if (reverse == true) { 095 orderingBits.set(0); 096 } 097 } 098 099 /** 100 * Construct a ComparatorChain from the Comparators in the 101 * List. All Comparators will default to the forward 102 * sort order. 103 * 104 * @param list List of Comparators 105 * @see #ComparatorChain(List,BitSet) 106 */ 107 public ComparatorChain(final List<Comparator<E>> list) { 108 this(list, new BitSet(list.size())); 109 } 110 111 /** 112 * Construct a ComparatorChain from the Comparators in the 113 * given List. The sort order of each column will be 114 * drawn from the given BitSet. When determining the sort 115 * order for Comparator at index <i>i</i> in the List, 116 * the ComparatorChain will call BitSet.get(<i>i</i>). 117 * If that method returns <i>false</i>, the forward 118 * sort order is used; a return value of <i>true</i> 119 * indicates reverse sort order. 120 * 121 * @param list List of Comparators. NOTE: This constructor does not perform a 122 * defensive copy of the list 123 * @param bits Sort order for each Comparator. Extra bits are ignored, 124 * unless extra Comparators are added by another method. 125 */ 126 public ComparatorChain(final List<Comparator<E>> list, final BitSet bits) { 127 comparatorChain = list; 128 orderingBits = bits; 129 } 130 131 //----------------------------------------------------------------------- 132 /** 133 * Add a Comparator to the end of the chain using the 134 * forward sort order 135 * 136 * @param comparator Comparator with the forward sort order 137 */ 138 public void addComparator(final Comparator<E> comparator) { 139 addComparator(comparator, false); 140 } 141 142 /** 143 * Add a Comparator to the end of the chain using the 144 * given sort order 145 * 146 * @param comparator Comparator to add to the end of the chain 147 * @param reverse false = forward sort order; true = reverse sort order 148 */ 149 public void addComparator(final Comparator<E> comparator, final boolean reverse) { 150 checkLocked(); 151 152 comparatorChain.add(comparator); 153 if (reverse == true) { 154 orderingBits.set(comparatorChain.size() - 1); 155 } 156 } 157 158 /** 159 * Replace the Comparator at the given index, maintaining 160 * the existing sort order. 161 * 162 * @param index index of the Comparator to replace 163 * @param comparator Comparator to place at the given index 164 * @throws IndexOutOfBoundsException 165 * if index < 0 or index >= size() 166 */ 167 public void setComparator(final int index, final Comparator<E> comparator) throws IndexOutOfBoundsException { 168 setComparator(index, comparator, false); 169 } 170 171 /** 172 * Replace the Comparator at the given index in the 173 * ComparatorChain, using the given sort order 174 * 175 * @param index index of the Comparator to replace 176 * @param comparator Comparator to set 177 * @param reverse false = forward sort order; true = reverse sort order 178 */ 179 public void setComparator(final int index, final Comparator<E> comparator, final boolean reverse) { 180 checkLocked(); 181 182 comparatorChain.set(index,comparator); 183 if (reverse == true) { 184 orderingBits.set(index); 185 } else { 186 orderingBits.clear(index); 187 } 188 } 189 190 /** 191 * Change the sort order at the given index in the 192 * ComparatorChain to a forward sort. 193 * 194 * @param index Index of the ComparatorChain 195 */ 196 public void setForwardSort(final int index) { 197 checkLocked(); 198 orderingBits.clear(index); 199 } 200 201 /** 202 * Change the sort order at the given index in the 203 * ComparatorChain to a reverse sort. 204 * 205 * @param index Index of the ComparatorChain 206 */ 207 public void setReverseSort(final int index) { 208 checkLocked(); 209 orderingBits.set(index); 210 } 211 212 /** 213 * Number of Comparators in the current ComparatorChain. 214 * 215 * @return Comparator count 216 */ 217 public int size() { 218 return comparatorChain.size(); 219 } 220 221 /** 222 * Determine if modifications can still be made to the 223 * ComparatorChain. ComparatorChains cannot be modified 224 * once they have performed a comparison. 225 * 226 * @return true = ComparatorChain cannot be modified; false = 227 * ComparatorChain can still be modified. 228 */ 229 public boolean isLocked() { 230 return isLocked; 231 } 232 233 /** 234 * Throws an exception if the {@link ComparatorChain} is locked. 235 * 236 * @throws UnsupportedOperationException if the {@link ComparatorChain} is locked 237 */ 238 private void checkLocked() { 239 if (isLocked == true) { 240 throw new UnsupportedOperationException( 241 "Comparator ordering cannot be changed after the first comparison is performed"); 242 } 243 } 244 245 /** 246 * Throws an exception if the {@link ComparatorChain} is empty. 247 * 248 * @throws UnsupportedOperationException if the {@link ComparatorChain} is empty 249 */ 250 private void checkChainIntegrity() { 251 if (comparatorChain.size() == 0) { 252 throw new UnsupportedOperationException("ComparatorChains must contain at least one Comparator"); 253 } 254 } 255 256 //----------------------------------------------------------------------- 257 /** 258 * Perform comparisons on the Objects as per 259 * Comparator.compare(o1,o2). 260 * 261 * @param o1 the first object to compare 262 * @param o2 the second object to compare 263 * @return -1, 0, or 1 264 * @throws UnsupportedOperationException if the ComparatorChain does not contain at least one Comparator 265 */ 266 @Override 267 public int compare(final E o1, final E o2) throws UnsupportedOperationException { 268 if (isLocked == false) { 269 checkChainIntegrity(); 270 isLocked = true; 271 } 272 273 // iterate over all comparators in the chain 274 final Iterator<Comparator<E>> comparators = comparatorChain.iterator(); 275 for (int comparatorIndex = 0; comparators.hasNext(); ++comparatorIndex) { 276 277 final Comparator<? super E> comparator = comparators.next(); 278 int retval = comparator.compare(o1,o2); 279 if (retval != 0) { 280 // invert the order if it is a reverse sort 281 if (orderingBits.get(comparatorIndex) == true) { 282 if (retval > 0) { 283 retval = -1; 284 } else { 285 retval = 1; 286 } 287 } 288 return retval; 289 } 290 } 291 292 // if comparators are exhausted, return 0 293 return 0; 294 } 295 296 //----------------------------------------------------------------------- 297 /** 298 * Implement a hash code for this comparator that is consistent with 299 * {@link #equals(Object) equals}. 300 * 301 * @return a suitable hash code 302 * @since 3.0 303 */ 304 @Override 305 public int hashCode() { 306 int hash = 0; 307 if (null != comparatorChain) { 308 hash ^= comparatorChain.hashCode(); 309 } 310 if (null != orderingBits) { 311 hash ^= orderingBits.hashCode(); 312 } 313 return hash; 314 } 315 316 /** 317 * Returns <code>true</code> iff <i>that</i> Object is 318 * is a {@link Comparator} whose ordering is known to be 319 * equivalent to mine. 320 * <p> 321 * This implementation returns <code>true</code> 322 * iff <code><i>object</i>.{@link Object#getClass() getClass()}</code> 323 * equals <code>this.getClass()</code>, and the underlying 324 * comparators and order bits are equal. 325 * Subclasses may want to override this behavior to remain consistent 326 * with the {@link Comparator#equals(Object)} contract. 327 * 328 * @param object the object to compare with 329 * @return true if equal 330 * @since 3.0 331 */ 332 @Override 333 public boolean equals(final Object object) { 334 if (this == object) { 335 return true; 336 } 337 if (null == object) { 338 return false; 339 } 340 if (object.getClass().equals(this.getClass())) { 341 final ComparatorChain<?> chain = (ComparatorChain<?>) object; 342 return (null == orderingBits ? null == chain.orderingBits : orderingBits.equals(chain.orderingBits)) && 343 (null == comparatorChain ? null == chain.comparatorChain : 344 comparatorChain.equals(chain.comparatorChain)); 345 } 346 return false; 347 } 348 349}