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 * @since 2.0 048 * @version $Id: ComparatorChain.html 972421 2015-11-14 20:00:04Z tn $ 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<Comparator<E>>(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 * @exception 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 public int compare(final E o1, final E o2) throws UnsupportedOperationException { 267 if (isLocked == false) { 268 checkChainIntegrity(); 269 isLocked = true; 270 } 271 272 // iterate over all comparators in the chain 273 final Iterator<Comparator<E>> comparators = comparatorChain.iterator(); 274 for (int comparatorIndex = 0; comparators.hasNext(); ++comparatorIndex) { 275 276 final Comparator<? super E> comparator = comparators.next(); 277 int retval = comparator.compare(o1,o2); 278 if (retval != 0) { 279 // invert the order if it is a reverse sort 280 if (orderingBits.get(comparatorIndex) == true) { 281 if (retval > 0) { 282 retval = -1; 283 } else { 284 retval = 1; 285 } 286 } 287 return retval; 288 } 289 } 290 291 // if comparators are exhausted, return 0 292 return 0; 293 } 294 295 //----------------------------------------------------------------------- 296 /** 297 * Implement a hash code for this comparator that is consistent with 298 * {@link #equals(Object) equals}. 299 * 300 * @return a suitable hash code 301 * @since 3.0 302 */ 303 @Override 304 public int hashCode() { 305 int hash = 0; 306 if (null != comparatorChain) { 307 hash ^= comparatorChain.hashCode(); 308 } 309 if (null != orderingBits) { 310 hash ^= orderingBits.hashCode(); 311 } 312 return hash; 313 } 314 315 /** 316 * Returns <code>true</code> iff <i>that</i> Object is 317 * is a {@link Comparator} whose ordering is known to be 318 * equivalent to mine. 319 * <p> 320 * This implementation returns <code>true</code> 321 * iff <code><i>object</i>.{@link Object#getClass() getClass()}</code> 322 * equals <code>this.getClass()</code>, and the underlying 323 * comparators and order bits are equal. 324 * Subclasses may want to override this behavior to remain consistent 325 * with the {@link Comparator#equals(Object)} contract. 326 * 327 * @param object the object to compare with 328 * @return true if equal 329 * @since 3.0 330 */ 331 @Override 332 public boolean equals(final Object object) { 333 if (this == object) { 334 return true; 335 } 336 if (null == object) { 337 return false; 338 } 339 if (object.getClass().equals(this.getClass())) { 340 final ComparatorChain<?> chain = (ComparatorChain<?>) object; 341 return (null == orderingBits ? null == chain.orderingBits : orderingBits.equals(chain.orderingBits)) && 342 (null == comparatorChain ? null == chain.comparatorChain : 343 comparatorChain.equals(chain.comparatorChain)); 344 } 345 return false; 346 } 347 348}