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