1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.collections.comparators;
  17. import java.util.Comparator;
  18. import java.util.HashMap;
  19. import java.util.Iterator;
  20. import java.util.List;
  21. import java.util.Map;
  22. /**
  23. * A Comparator which imposes a specific order on a specific set of Objects.
  24. * Objects are presented to the FixedOrderComparator in a specified order and
  25. * subsequent calls to {@link #compare(Object, Object) compare} yield that order.
  26. * For example:
  27. * <pre>
  28. * String[] planets = {"Mercury", "Venus", "Earth", "Mars"};
  29. * FixedOrderComparator distanceFromSun = new FixedOrderComparator(planets);
  30. * Arrays.sort(planets); // Sort to alphabetical order
  31. * Arrays.sort(planets, distanceFromSun); // Back to original order
  32. * </pre>
  33. * <p>
  34. * Once <code>compare</code> has been called, the FixedOrderComparator is locked
  35. * and attempts to modify it yield an UnsupportedOperationException.
  36. * <p>
  37. * Instances of FixedOrderComparator are not synchronized. The class is not
  38. * thread-safe at construction time, but it is thread-safe to perform
  39. * multiple comparisons after all the setup operations are complete.
  40. *
  41. * @since Commons Collections 3.0
  42. * @version $Revision: 1.10 $ $Date: 2004/05/15 13:24:11 $
  43. *
  44. * @author David Leppik
  45. * @author Stephen Colebourne
  46. * @author Janek Bogucki
  47. */
  48. public class FixedOrderComparator implements Comparator {
  49. /**
  50. * Behavior when comparing unknown Objects:
  51. * unknown objects compare as before known Objects.
  52. */
  53. public static final int UNKNOWN_BEFORE = 0;
  54. /**
  55. * Behavior when comparing unknown Objects:
  56. * unknown objects compare as after known Objects.
  57. */
  58. public static final int UNKNOWN_AFTER = 1;
  59. /**
  60. * Behavior when comparing unknown Objects:
  61. * unknown objects cause a IllegalArgumentException to be thrown.
  62. * This is the default behavior.
  63. */
  64. public static final int UNKNOWN_THROW_EXCEPTION = 2;
  65. /** Internal map of object to position */
  66. private final Map map = new HashMap();
  67. /** Counter used in determining the position in the map */
  68. private int counter = 0;
  69. /** Is the comparator locked against further change */
  70. private boolean isLocked = false;
  71. /** The behaviour in the case of an unknown object */
  72. private int unknownObjectBehavior = UNKNOWN_THROW_EXCEPTION;
  73. // Constructors
  74. //-----------------------------------------------------------------------
  75. /**
  76. * Constructs an empty FixedOrderComparator.
  77. */
  78. public FixedOrderComparator() {
  79. super();
  80. }
  81. /**
  82. * Constructs a FixedOrderComparator which uses the order of the given array
  83. * to compare the objects.
  84. * <p>
  85. * The array is copied, so later changes will not affect the comparator.
  86. *
  87. * @param items the items that the comparator can compare in order
  88. * @throws IllegalArgumentException if the array is null
  89. */
  90. public FixedOrderComparator(Object[] items) {
  91. super();
  92. if (items == null) {
  93. throw new IllegalArgumentException("The list of items must not be null");
  94. }
  95. for (int i = 0; i < items.length; i++) {
  96. add(items[i]);
  97. }
  98. }
  99. /**
  100. * Constructs a FixedOrderComparator which uses the order of the given list
  101. * to compare the objects.
  102. * <p>
  103. * The list is copied, so later changes will not affect the comparator.
  104. *
  105. * @param items the items that the comparator can compare in order
  106. * @throws IllegalArgumentException if the list is null
  107. */
  108. public FixedOrderComparator(List items) {
  109. super();
  110. if (items == null) {
  111. throw new IllegalArgumentException("The list of items must not be null");
  112. }
  113. for (Iterator it = items.iterator(); it.hasNext();) {
  114. add(it.next());
  115. }
  116. }
  117. // Bean methods / state querying methods
  118. //-----------------------------------------------------------------------
  119. /**
  120. * Returns true if modifications cannot be made to the FixedOrderComparator.
  121. * FixedOrderComparators cannot be modified once they have performed a comparison.
  122. *
  123. * @return true if attempts to change the FixedOrderComparator yield an
  124. * UnsupportedOperationException, false if it can be changed.
  125. */
  126. public boolean isLocked() {
  127. return isLocked;
  128. }
  129. /**
  130. * Checks to see whether the comparator is now locked against further changes.
  131. *
  132. * @throws UnsupportedOperationException if the comparator is locked
  133. */
  134. protected void checkLocked() {
  135. if (isLocked()) {
  136. throw new UnsupportedOperationException("Cannot modify a FixedOrderComparator after a comparison");
  137. }
  138. }
  139. /**
  140. * Gets the behavior for comparing unknown objects.
  141. *
  142. * @return the flag for unknown behaviour - UNKNOWN_AFTER,
  143. * UNKNOWN_BEFORE or UNKNOWN_THROW_EXCEPTION
  144. */
  145. public int getUnknownObjectBehavior() {
  146. return unknownObjectBehavior;
  147. }
  148. /**
  149. * Sets the behavior for comparing unknown objects.
  150. *
  151. * @param unknownObjectBehavior the flag for unknown behaviour -
  152. * UNKNOWN_AFTER, UNKNOWN_BEFORE or UNKNOWN_THROW_EXCEPTION
  153. * @throws UnsupportedOperationException if a comparison has been performed
  154. * @throws IllegalArgumentException if the unknown flag is not valid
  155. */
  156. public void setUnknownObjectBehavior(int unknownObjectBehavior) {
  157. checkLocked();
  158. if (unknownObjectBehavior != UNKNOWN_AFTER
  159. && unknownObjectBehavior != UNKNOWN_BEFORE
  160. && unknownObjectBehavior != UNKNOWN_THROW_EXCEPTION) {
  161. throw new IllegalArgumentException("Unrecognised value for unknown behaviour flag");
  162. }
  163. this.unknownObjectBehavior = unknownObjectBehavior;
  164. }
  165. // Methods for adding items
  166. //-----------------------------------------------------------------------
  167. /**
  168. * Adds an item, which compares as after all items known to the Comparator.
  169. * If the item is already known to the Comparator, its old position is
  170. * replaced with the new position.
  171. *
  172. * @param obj the item to be added to the Comparator.
  173. * @return true if obj has been added for the first time, false if
  174. * it was already known to the Comparator.
  175. * @throws UnsupportedOperationException if a comparison has already been made
  176. */
  177. public boolean add(Object obj) {
  178. checkLocked();
  179. Object position = map.put(obj, new Integer(counter++));
  180. return (position == null);
  181. }
  182. /**
  183. * Adds a new item, which compares as equal to the given existing item.
  184. *
  185. * @param existingObj an item already in the Comparator's set of
  186. * known objects
  187. * @param newObj an item to be added to the Comparator's set of
  188. * known objects
  189. * @return true if newObj has been added for the first time, false if
  190. * it was already known to the Comparator.
  191. * @throws IllegalArgumentException if existingObject is not in the
  192. * Comparator's set of known objects.
  193. * @throws UnsupportedOperationException if a comparison has already been made
  194. */
  195. public boolean addAsEqual(Object existingObj, Object newObj) {
  196. checkLocked();
  197. Integer position = (Integer) map.get(existingObj);
  198. if (position == null) {
  199. throw new IllegalArgumentException(existingObj + " not known to " + this);
  200. }
  201. Object result = map.put(newObj, position);
  202. return (result == null);
  203. }
  204. // Comparator methods
  205. //-----------------------------------------------------------------------
  206. /**
  207. * Compares two objects according to the order of this Comparator.
  208. * <p>
  209. * It is important to note that this class will throw an IllegalArgumentException
  210. * in the case of an unrecognised object. This is not specified in the
  211. * Comparator interface, but is the most appropriate exception.
  212. *
  213. * @param obj1 the first object to compare
  214. * @param obj2 the second object to compare
  215. * @return negative if obj1 is less, positive if greater, zero if equal
  216. * @throws IllegalArgumentException if obj1 or obj2 are not known
  217. * to this Comparator and an alternative behavior has not been set
  218. * via {@link #setUnknownObjectBehavior(int)}.
  219. */
  220. public int compare(Object obj1, Object obj2) {
  221. isLocked = true;
  222. Integer position1 = (Integer) map.get(obj1);
  223. Integer position2 = (Integer) map.get(obj2);
  224. if (position1 == null || position2 == null) {
  225. switch (unknownObjectBehavior) {
  226. case UNKNOWN_BEFORE :
  227. if (position1 == null) {
  228. return (position2 == null) ? 0 : -1;
  229. } else {
  230. return 1;
  231. }
  232. case UNKNOWN_AFTER :
  233. if (position1 == null) {
  234. return (position2 == null) ? 0 : 1;
  235. } else {
  236. return -1;
  237. }
  238. case UNKNOWN_THROW_EXCEPTION :
  239. Object unknownObj = (position1 == null) ? obj1 : obj2;
  240. throw new IllegalArgumentException("Attempting to compare unknown object " + unknownObj);
  241. default :
  242. throw new UnsupportedOperationException("Unknown unknownObjectBehavior: " + unknownObjectBehavior);
  243. }
  244. } else {
  245. return position1.compareTo(position2);
  246. }
  247. }
  248. }