View Javadoc

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