001: /*
002: * Copyright 2001-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.util.Comparator;
019: import java.util.HashMap;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.Map;
023:
024: /**
025: * A Comparator which imposes a specific order on a specific set of Objects.
026: * Objects are presented to the FixedOrderComparator in a specified order and
027: * subsequent calls to {@link #compare(Object, Object) compare} yield that order.
028: * For example:
029: * <pre>
030: * String[] planets = {"Mercury", "Venus", "Earth", "Mars"};
031: * FixedOrderComparator distanceFromSun = new FixedOrderComparator(planets);
032: * Arrays.sort(planets); // Sort to alphabetical order
033: * Arrays.sort(planets, distanceFromSun); // Back to original order
034: * </pre>
035: * <p>
036: * Once <code>compare</code> has been called, the FixedOrderComparator is locked
037: * and attempts to modify it yield an UnsupportedOperationException.
038: * <p>
039: * Instances of FixedOrderComparator are not synchronized. The class is not
040: * thread-safe at construction time, but it is thread-safe to perform
041: * multiple comparisons after all the setup operations are complete.
042: *
043: * @since Commons Collections 3.0
044: * @version $Revision: 155406 $ $Date: 2005-02-26 12:55:26 +0000 (Sat, 26 Feb 2005) $
045: *
046: * @author David Leppik
047: * @author Stephen Colebourne
048: * @author Janek Bogucki
049: */
050: public class FixedOrderComparator implements Comparator {
051:
052: /**
053: * Behavior when comparing unknown Objects:
054: * unknown objects compare as before known Objects.
055: */
056: public static final int UNKNOWN_BEFORE = 0;
057:
058: /**
059: * Behavior when comparing unknown Objects:
060: * unknown objects compare as after known Objects.
061: */
062: public static final int UNKNOWN_AFTER = 1;
063:
064: /**
065: * Behavior when comparing unknown Objects:
066: * unknown objects cause a IllegalArgumentException to be thrown.
067: * This is the default behavior.
068: */
069: public static final int UNKNOWN_THROW_EXCEPTION = 2;
070:
071: /** Internal map of object to position */
072: private final Map map = new HashMap();
073: /** Counter used in determining the position in the map */
074: private int counter = 0;
075: /** Is the comparator locked against further change */
076: private boolean isLocked = false;
077: /** The behaviour in the case of an unknown object */
078: private int unknownObjectBehavior = UNKNOWN_THROW_EXCEPTION;
079:
080: // Constructors
081: //-----------------------------------------------------------------------
082: /**
083: * Constructs an empty FixedOrderComparator.
084: */
085: public FixedOrderComparator() {
086: super ();
087: }
088:
089: /**
090: * Constructs a FixedOrderComparator which uses the order of the given array
091: * to compare the objects.
092: * <p>
093: * The array is copied, so later changes will not affect the comparator.
094: *
095: * @param items the items that the comparator can compare in order
096: * @throws IllegalArgumentException if the array is null
097: */
098: public FixedOrderComparator(Object[] items) {
099: super ();
100: if (items == null) {
101: throw new IllegalArgumentException(
102: "The list of items must not be null");
103: }
104: for (int i = 0; i < items.length; i++) {
105: add(items[i]);
106: }
107: }
108:
109: /**
110: * Constructs a FixedOrderComparator which uses the order of the given list
111: * to compare the objects.
112: * <p>
113: * The list is copied, so later changes will not affect the comparator.
114: *
115: * @param items the items that the comparator can compare in order
116: * @throws IllegalArgumentException if the list is null
117: */
118: public FixedOrderComparator(List items) {
119: super ();
120: if (items == null) {
121: throw new IllegalArgumentException(
122: "The list of items must not be null");
123: }
124: for (Iterator it = items.iterator(); it.hasNext();) {
125: add(it.next());
126: }
127: }
128:
129: // Bean methods / state querying methods
130: //-----------------------------------------------------------------------
131: /**
132: * Returns true if modifications cannot be made to the FixedOrderComparator.
133: * FixedOrderComparators cannot be modified once they have performed a comparison.
134: *
135: * @return true if attempts to change the FixedOrderComparator yield an
136: * UnsupportedOperationException, false if it can be changed.
137: */
138: public boolean isLocked() {
139: return isLocked;
140: }
141:
142: /**
143: * Checks to see whether the comparator is now locked against further changes.
144: *
145: * @throws UnsupportedOperationException if the comparator is locked
146: */
147: protected void checkLocked() {
148: if (isLocked()) {
149: throw new UnsupportedOperationException(
150: "Cannot modify a FixedOrderComparator after a comparison");
151: }
152: }
153:
154: /**
155: * Gets the behavior for comparing unknown objects.
156: *
157: * @return the flag for unknown behaviour - UNKNOWN_AFTER,
158: * UNKNOWN_BEFORE or UNKNOWN_THROW_EXCEPTION
159: */
160: public int getUnknownObjectBehavior() {
161: return unknownObjectBehavior;
162: }
163:
164: /**
165: * Sets the behavior for comparing unknown objects.
166: *
167: * @param unknownObjectBehavior the flag for unknown behaviour -
168: * UNKNOWN_AFTER, UNKNOWN_BEFORE or UNKNOWN_THROW_EXCEPTION
169: * @throws UnsupportedOperationException if a comparison has been performed
170: * @throws IllegalArgumentException if the unknown flag is not valid
171: */
172: public void setUnknownObjectBehavior(int unknownObjectBehavior) {
173: checkLocked();
174: if (unknownObjectBehavior != UNKNOWN_AFTER
175: && unknownObjectBehavior != UNKNOWN_BEFORE
176: && unknownObjectBehavior != UNKNOWN_THROW_EXCEPTION) {
177: throw new IllegalArgumentException(
178: "Unrecognised value for unknown behaviour flag");
179: }
180: this .unknownObjectBehavior = unknownObjectBehavior;
181: }
182:
183: // Methods for adding items
184: //-----------------------------------------------------------------------
185: /**
186: * Adds an item, which compares as after all items known to the Comparator.
187: * If the item is already known to the Comparator, its old position is
188: * replaced with the new position.
189: *
190: * @param obj the item to be added to the Comparator.
191: * @return true if obj has been added for the first time, false if
192: * it was already known to the Comparator.
193: * @throws UnsupportedOperationException if a comparison has already been made
194: */
195: public boolean add(Object obj) {
196: checkLocked();
197: Object position = map.put(obj, new Integer(counter++));
198: return (position == null);
199: }
200:
201: /**
202: * Adds a new item, which compares as equal to the given existing item.
203: *
204: * @param existingObj an item already in the Comparator's set of
205: * known objects
206: * @param newObj an item to be added to the Comparator's set of
207: * known objects
208: * @return true if newObj has been added for the first time, false if
209: * it was already known to the Comparator.
210: * @throws IllegalArgumentException if existingObject is not in the
211: * Comparator's set of known objects.
212: * @throws UnsupportedOperationException if a comparison has already been made
213: */
214: public boolean addAsEqual(Object existingObj, Object newObj) {
215: checkLocked();
216: Integer position = (Integer) map.get(existingObj);
217: if (position == null) {
218: throw new IllegalArgumentException(existingObj
219: + " not known to " + this );
220: }
221: Object result = map.put(newObj, position);
222: return (result == null);
223: }
224:
225: // Comparator methods
226: //-----------------------------------------------------------------------
227: /**
228: * Compares two objects according to the order of this Comparator.
229: * <p>
230: * It is important to note that this class will throw an IllegalArgumentException
231: * in the case of an unrecognised object. This is not specified in the
232: * Comparator interface, but is the most appropriate exception.
233: *
234: * @param obj1 the first object to compare
235: * @param obj2 the second object to compare
236: * @return negative if obj1 is less, positive if greater, zero if equal
237: * @throws IllegalArgumentException if obj1 or obj2 are not known
238: * to this Comparator and an alternative behavior has not been set
239: * via {@link #setUnknownObjectBehavior(int)}.
240: */
241: public int compare(Object obj1, Object obj2) {
242: isLocked = true;
243: Integer position1 = (Integer) map.get(obj1);
244: Integer position2 = (Integer) map.get(obj2);
245: if (position1 == null || position2 == null) {
246: switch (unknownObjectBehavior) {
247: case UNKNOWN_BEFORE:
248: if (position1 == null) {
249: return (position2 == null) ? 0 : -1;
250: } else {
251: return 1;
252: }
253: case UNKNOWN_AFTER:
254: if (position1 == null) {
255: return (position2 == null) ? 0 : 1;
256: } else {
257: return -1;
258: }
259: case UNKNOWN_THROW_EXCEPTION:
260: Object unknownObj = (position1 == null) ? obj1 : obj2;
261: throw new IllegalArgumentException(
262: "Attempting to compare unknown object "
263: + unknownObj);
264: default:
265: throw new UnsupportedOperationException(
266: "Unknown unknownObjectBehavior: "
267: + unknownObjectBehavior);
268: }
269: } else {
270: return position1.compareTo(position2);
271: }
272: }
273:
274: }
|