001: /*
002: * $Id: ComparatorChain.java,v 1.1 2005/10/13 02:25:32 ahimanikya Exp $
003: * =======================================================================
004: * Copyright (c) 2003-2005 Axion Development Team. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above
011: * copyright notice, this list of conditions and the following
012: * disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The names "Tigris", "Axion", nor the names of its contributors may
020: * not be used to endorse or promote products derived from this
021: * software without specific prior written permission.
022: *
023: * 4. Products derived from this software may not be called "Axion", nor
024: * may "Tigris" or "Axion" appear in their names without specific prior
025: * written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
030: * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
032: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
033: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
034: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
035: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
036: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
037: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
038: * =======================================================================
039: */
040: package org.axiondb.util;
041:
042: import java.io.Serializable;
043: import java.util.ArrayList;
044: import java.util.BitSet;
045: import java.util.Comparator;
046: import java.util.Iterator;
047: import java.util.List;
048:
049: /**
050: * <p>
051: * A ComparatorChain is a Comparator that wraps one or more Comparators in sequence. The
052: * ComparatorChain calls each Comparator in sequence until either 1) any single Comparator
053: * returns a non-zero result (and that result is then returned), or 2) the ComparatorChain
054: * is exhausted (and zero is returned). This type of sorting is very similar to
055: * multi-column sorting in SQL, and this class allows Java classes to emulate that kind of
056: * behaviour when sorting a List.
057: * </p>
058: * <p>
059: * To further facilitate SQL-like sorting, the order of any single Comparator in the list
060: * can be reversed.
061: * </p>
062: * <p>
063: * Calling a method that adds new Comparators or changes the ascend/descend sort <i>after
064: * compare(Object, Object) has been called</i> will result in an
065: * UnsupportedOperationException. However, <i>take care</i> to not alter the underlying
066: * List of Comparators or the BitSet that defines the sort order.
067: * </p>
068: * <p>
069: * Instances of ComparatorChain are not synchronized. The class is not thread-safe at
070: * construction time, but it <i>is</i> thread-safe to perform multiple comparisons after
071: * all the setup operations are complete.
072: * </p>
073: *
074: * @author Morgan Delagrange
075: * @version $Revision: 1.1 $ $Date: 2005/10/13 02:25:32 $
076: */
077: public class ComparatorChain implements Comparator, Serializable {
078:
079: protected List comparatorChain = null;
080: protected BitSet orderingBits = null;
081:
082: protected boolean isLocked = false;
083:
084: /**
085: * Construct a ComparatorChain with no Comparators. You must add at least one
086: * Comparator before calling the compare(Object,Object) method, or an
087: * UnsupportedOperationException is thrown
088: */
089: public ComparatorChain() {
090: this (new ArrayList(), new BitSet());
091: }
092:
093: /**
094: * Construct a ComparatorChain with a single Comparator, sorting in the forward order
095: *
096: * @param comparator First comparator in the Comparator chain
097: */
098: public ComparatorChain(Comparator comparator) {
099: this (comparator, false);
100: }
101:
102: /**
103: * Construct a Comparator chain with a single Comparator, sorting in the given order
104: *
105: * @param comparator First Comparator in the ComparatorChain
106: * @param reverse false = forward sort; true = reverse sort
107: */
108: public ComparatorChain(Comparator comparator, boolean reverse) {
109: comparatorChain = new ArrayList();
110: comparatorChain.add(comparator);
111: orderingBits = new BitSet(1);
112: if (reverse == true) {
113: orderingBits.set(0);
114: }
115: }
116:
117: /**
118: * Construct a ComparatorChain from the Comparators in the List. All Comparators will
119: * default to the forward sort order.
120: *
121: * @param list List of Comparators
122: * @see #ComparatorChain(List,BitSet)
123: */
124: public ComparatorChain(List list) {
125: this (list, new BitSet(list.size()));
126: }
127:
128: /**
129: * Construct a ComparatorChain from the Comparators in the given List. The sort order
130: * of each column will be drawn from the given BitSet. When determining the sort order
131: * for Comparator at index <i>i</i> in the List, the ComparatorChain will call
132: * BitSet.get(<i>i</i>). If that method returns <i>false</i>, the forward sort
133: * order is used; a return value of <i>true</i> indicates reverse sort order.
134: *
135: * @param list List of Comparators. NOTE: This constructor does not perform a
136: * defensive copy of the list
137: * @param bits Sort order for each Comparator. Extra bits are ignored, unless extra
138: * Comparators are added by another method.
139: */
140: public ComparatorChain(List list, BitSet bits) {
141: comparatorChain = list;
142: orderingBits = bits;
143: }
144:
145: /**
146: * Add a Comparator to the end of the chain using the forward sort order
147: *
148: * @param comparator Comparator with the forward sort order
149: */
150: public void addComparator(Comparator comparator) {
151: addComparator(comparator, false);
152: }
153:
154: /**
155: * Add a Comparator to the end of the chain using the given sort order
156: *
157: * @param comparator Comparator to add to the end of the chain
158: * @param reverse false = forward sort order; true = reverse sort order
159: */
160: public void addComparator(Comparator comparator, boolean reverse) {
161: checkLocked();
162:
163: comparatorChain.add(comparator);
164: if (reverse == true) {
165: orderingBits.set(comparatorChain.size() - 1);
166: }
167: }
168:
169: /**
170: * Replace the Comparator at the given index, maintaining the existing sort order.
171: *
172: * @param index index of the Comparator to replace
173: * @param comparator Comparator to place at the given index
174: * @exception IndexOutOfBoundsException if index < 0 or index >= size()
175: */
176: public void setComparator(int index, Comparator comparator)
177: throws IndexOutOfBoundsException {
178: setComparator(index, comparator, false);
179: }
180:
181: /**
182: * Replace the Comparator at the given index in the ComparatorChain, using the given
183: * sort order
184: *
185: * @param index index of the Comparator to replace
186: * @param comparator Comparator to set
187: * @param reverse false = forward sort order; true = reverse sort order
188: */
189: public void setComparator(int index, Comparator comparator,
190: boolean reverse) {
191: checkLocked();
192:
193: comparatorChain.set(index, comparator);
194: if (reverse == true) {
195: orderingBits.set(index);
196: } else {
197: orderingBits.clear(index);
198: }
199: }
200:
201: /**
202: * Change the sort order at the given index in the ComparatorChain to a forward sort.
203: *
204: * @param index Index of the ComparatorChain
205: */
206: public void setForwardSort(int index) {
207: checkLocked();
208: orderingBits.clear(index);
209: }
210:
211: /**
212: * Change the sort order at the given index in the ComparatorChain to a reverse sort.
213: *
214: * @param index Index of the ComparatorChain
215: */
216: public void setReverseSort(int index) {
217: checkLocked();
218: orderingBits.set(index);
219: }
220:
221: /**
222: * Number of Comparators in the current ComparatorChain.
223: *
224: * @return Comparator count
225: */
226: public int size() {
227: return comparatorChain.size();
228: }
229:
230: /**
231: * Determine if modifications can still be made to the ComparatorChain.
232: * ComparatorChains cannot be modified once they have performed a comparison.
233: *
234: * @return true = ComparatorChain cannot be modified; false = ComparatorChain can
235: * 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: * Perform comparisons on the Objects as per Comparator.compare(o1,o2).
258: *
259: * @param o1 object 1
260: * @param o2 object 2
261: * @return -1, 0, or 1
262: * @exception UnsupportedOperationException if the ComparatorChain does not contain at
263: * least one Comparator
264: */
265: public int compare(Object o1, Object o2)
266: throws UnsupportedOperationException {
267: if (isLocked == false) {
268: checkChainIntegrity();
269: isLocked = true;
270: }
271:
272: // iterate over all comparators in the chain
273: Iterator comparators = comparatorChain.iterator();
274: for (int comparatorIndex = 0; comparators.hasNext(); ++comparatorIndex) {
275:
276: Comparator comparator = (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 (Integer.MIN_VALUE == retval) {
282: retval = Integer.MAX_VALUE;
283: } else {
284: retval *= -1;
285: }
286: }
287:
288: return retval;
289: }
290: }
291:
292: // if comparators are exhausted, return 0
293: return 0;
294: }
295:
296: /**
297: * Implement a hash code for this comparator that is consistent with {@link #equals}.
298: *
299: * @since Commons Collections 3.0
300: */
301: public int hashCode() {
302: int hash = 0;
303: if (null != comparatorChain) {
304: hash ^= comparatorChain.hashCode();
305: }
306: if (null != orderingBits) {
307: hash ^= orderingBits.hashCode();
308: }
309: return hash;
310: }
311:
312: /**
313: * Returns <code>true</code> iff <i>that</i> Object is is a {@link Comparator}
314: * whose ordering is known to be equivalent to mine.
315: * <p>
316: * This implementation returns <code>true</code> iff
317: * <code><i>that</i>.{@link Object#getClass getClass()}</code> equals
318: * <code>this.getClass()</code>, and the underlying comparators and order bits are
319: * equal. Subclasses may want to override this behavior to remain consistent with the
320: * {@link Comparator#equals} contract.
321: *
322: * @since Commons Collections 3.0
323: */
324: public boolean equals(Object that) {
325: if (this == that) {
326: return true;
327: } else if (null == that) {
328: return false;
329: } else if (that.getClass().equals(this .getClass())) {
330: ComparatorChain chain = (ComparatorChain) that;
331: return ((null == orderingBits ? null == chain.orderingBits
332: : orderingBits.equals(chain.orderingBits)) && (null == comparatorChain ? null == chain.comparatorChain
333: : comparatorChain.equals(chain.comparatorChain)));
334: } else {
335: return false;
336: }
337: }
338: }
|