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