| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.lang3.compare; |
| |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * A Comparator which imposes a specific order on a specific set of Objects. |
| * Objects are presented to the FixedOrderComparator in a specified order and |
| * subsequent calls to {@link #compare(Object, Object) compare} yield that order. |
| * For example: |
| * <pre> |
| * String[] planets = {"Mercury", "Venus", "Earth", "Mars"}; |
| * FixedOrderComparator distanceFromSun = new FixedOrderComparator(planets); |
| * Arrays.sort(planets); // Sort to alphabetical order |
| * Arrays.sort(planets, distanceFromSun); // Back to original order |
| * </pre> |
| * <p> |
| * Once <code>compare</code> has been called, the FixedOrderComparator is locked |
| * and attempts to modify it yield an UnsupportedOperationException. |
| * <p> |
| * Instances of FixedOrderComparator are not synchronized. The class is not |
| * thread-safe at construction time, but it is thread-safe to perform |
| * multiple comparisons after all the setup operations are complete. |
| * |
| * @since Commons Collections 3.0 |
| * @version $Revision$ $Date$ |
| */ |
| public class FixedOrderComparator<T> implements Comparator<T> { |
| |
| /** |
| * Unknown object behavior enum. |
| * @since Commons Collections 5 |
| */ |
| public static enum UnknownObjectBehavior { |
| BEFORE, AFTER, EXCEPTION; |
| } |
| |
| /** Internal map of object to position */ |
| private final Map<T, Integer> map = new HashMap<T, Integer>(); |
| |
| /** Counter used in determining the position in the map */ |
| private int counter = 0; |
| |
| /** Is the comparator locked against further change */ |
| private boolean isLocked = false; |
| |
| /** The behaviour in the case of an unknown object */ |
| private UnknownObjectBehavior unknownObjectBehavior = UnknownObjectBehavior.EXCEPTION; |
| |
| // Constructors |
| //----------------------------------------------------------------------- |
| /** |
| * Constructs an empty FixedOrderComparator. |
| */ |
| public FixedOrderComparator() { |
| super(); |
| } |
| |
| /** |
| * Constructs a FixedOrderComparator which uses the order of the given array |
| * to compare the objects. |
| * <p> |
| * The array is copied, so later changes will not affect the comparator. |
| * |
| * @param items the items that the comparator can compare in order |
| * @throws IllegalArgumentException if the array is null |
| */ |
| public FixedOrderComparator(T[] items) { |
| super(); |
| if (items == null) { |
| throw new IllegalArgumentException("The list of items must not be null"); |
| } |
| for (T item : items) { |
| add(item); |
| } |
| } |
| |
| /** |
| * Constructs a FixedOrderComparator which uses the order of the given list |
| * to compare the objects. |
| * <p> |
| * The list is copied, so later changes will not affect the comparator. |
| * |
| * @param items the items that the comparator can compare in order |
| * @throws IllegalArgumentException if the list is null |
| */ |
| public FixedOrderComparator(List<T> items) { |
| super(); |
| if (items == null) { |
| throw new IllegalArgumentException("The list of items must not be null"); |
| } |
| for (T t : items) { |
| add(t); |
| } |
| } |
| |
| // Bean methods / state querying methods |
| //----------------------------------------------------------------------- |
| /** |
| * Returns true if modifications cannot be made to the FixedOrderComparator. |
| * FixedOrderComparators cannot be modified once they have performed a comparison. |
| * |
| * @return true if attempts to change the FixedOrderComparator yield an |
| * UnsupportedOperationException, false if it can be changed. |
| */ |
| public boolean isLocked() { |
| return isLocked; |
| } |
| |
| /** |
| * Checks to see whether the comparator is now locked against further changes. |
| * |
| * @throws UnsupportedOperationException if the comparator is locked |
| */ |
| protected void checkLocked() { |
| if (isLocked()) { |
| throw new UnsupportedOperationException("Cannot modify a FixedOrderComparator after a comparison"); |
| } |
| } |
| |
| /** |
| * Gets the behavior for comparing unknown objects. |
| * |
| * @return {@link UnknownObjectBehavior} |
| */ |
| public UnknownObjectBehavior getUnknownObjectBehavior() { |
| return unknownObjectBehavior; |
| } |
| |
| /** |
| * Sets the behavior for comparing unknown objects. |
| * |
| * @param unknownObjectBehavior the flag for unknown behaviour - |
| * UNKNOWN_AFTER, UNKNOWN_BEFORE or UNKNOWN_THROW_EXCEPTION |
| * @throws UnsupportedOperationException if a comparison has been performed |
| * @throws IllegalArgumentException if the unknown flag is not valid |
| */ |
| public void setUnknownObjectBehavior(UnknownObjectBehavior unknownObjectBehavior) { |
| checkLocked(); |
| if (unknownObjectBehavior == null) { |
| throw new IllegalArgumentException("Unknown object behavior must not be null"); |
| } |
| this.unknownObjectBehavior = unknownObjectBehavior; |
| } |
| |
| // Methods for adding items |
| //----------------------------------------------------------------------- |
| /** |
| * Adds an item, which compares as after all items known to the Comparator. |
| * If the item is already known to the Comparator, its old position is |
| * replaced with the new position. |
| * |
| * @param obj the item to be added to the Comparator. |
| * @return true if obj has been added for the first time, false if |
| * it was already known to the Comparator. |
| * @throws UnsupportedOperationException if a comparison has already been made |
| */ |
| public boolean add(T obj) { |
| checkLocked(); |
| Integer position = map.put(obj, new Integer(counter++)); |
| return (position == null); |
| } |
| |
| /** |
| * Adds a new item, which compares as equal to the given existing item. |
| * |
| * @param existingObj an item already in the Comparator's set of |
| * known objects |
| * @param newObj an item to be added to the Comparator's set of |
| * known objects |
| * @return true if newObj has been added for the first time, false if |
| * it was already known to the Comparator. |
| * @throws IllegalArgumentException if existingObject is not in the |
| * Comparator's set of known objects. |
| * @throws UnsupportedOperationException if a comparison has already been made |
| */ |
| public boolean addAsEqual(T existingObj, T newObj) { |
| checkLocked(); |
| Integer position = map.get(existingObj); |
| if (position == null) { |
| throw new IllegalArgumentException(existingObj + " not known to " + this); |
| } |
| Integer result = map.put(newObj, position); |
| return (result == null); |
| } |
| |
| // Comparator methods |
| //----------------------------------------------------------------------- |
| /** |
| * Compares two objects according to the order of this Comparator. |
| * <p> |
| * It is important to note that this class will throw an IllegalArgumentException |
| * in the case of an unrecognised object. This is not specified in the |
| * Comparator interface, but is the most appropriate exception. |
| * |
| * @param obj1 the first object to compare |
| * @param obj2 the second object to compare |
| * @return negative if obj1 is less, positive if greater, zero if equal |
| * @throws IllegalArgumentException if obj1 or obj2 are not known |
| * to this Comparator and an alternative behavior has not been set |
| * via {@link #setUnknownObjectBehavior(UnknownObjectBehavior)}. |
| */ |
| public int compare(T obj1, T obj2) { |
| isLocked = true; |
| Integer position1 = map.get(obj1); |
| Integer position2 = map.get(obj2); |
| if (position1 == null || position2 == null) { |
| switch (unknownObjectBehavior) { |
| case BEFORE: |
| return position1 == null ? position2 == null ? 0 : -1 : 1; |
| case AFTER: |
| return position1 == null ? position2 == null ? 0 : 1 : -1; |
| case EXCEPTION: |
| Object unknownObj = (position1 == null) ? obj1 : obj2; |
| throw new IllegalArgumentException("Attempting to compare unknown object " |
| + unknownObj); |
| default: //could be null |
| throw new UnsupportedOperationException("Unknown unknownObjectBehavior: " |
| + unknownObjectBehavior); |
| } |
| } |
| return position1.compareTo(position2); |
| } |
| |
| } |