blob: f9a4d7de8ef6e3218c9f325d44fb922cf1a3b5f3 [file] [log] [blame]
/*
* 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.collections4.comparators;
import java.io.Serializable;
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.
* <p>
* This class is Serializable from Commons Collections 4.0.
*
* @since 3.0
* @version $Id$
*/
public class FixedOrderComparator<T> implements Comparator<T>, Serializable {
/** Serialization version from Collections 4.0. */
private static final long serialVersionUID = 82794675842863201L;
/**
* Unknown object behavior enum.
* @since 4.0
*/
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 NullPointerException if the array is null
*/
public FixedOrderComparator(final T... items) {
super();
if (items == null) {
throw new NullPointerException("The list of items must not be null");
}
for (final 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 NullPointerException if the list is null
*/
public FixedOrderComparator(final List<T> items) {
super();
if (items == null) {
throw new NullPointerException("The list of items must not be null");
}
for (final 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 NullPointerException if unknownObjectBehavior is null
*/
public void setUnknownObjectBehavior(final UnknownObjectBehavior unknownObjectBehavior) {
checkLocked();
if (unknownObjectBehavior == null) {
throw new NullPointerException("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(final T obj) {
checkLocked();
final Integer position = map.put(obj, Integer.valueOf(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(final T existingObj, final T newObj) {
checkLocked();
final Integer position = map.get(existingObj);
if (position == null) {
throw new IllegalArgumentException(existingObj + " not known to " + this);
}
final 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 unrecognized 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)}.
*/
@Override
public int compare(final T obj1, final T obj2) {
isLocked = true;
final Integer position1 = map.get(obj1);
final 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:
final 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);
}
//-----------------------------------------------------------------------
/**
* Implement a hash code for this comparator that is consistent with
* {@link #equals(Object) equals}.
*
* @return a hash code for this comparator.
*/
@Override
public int hashCode() {
int total = 17;
total = total*37 + (map == null ? 0 : map.hashCode());
total = total*37 + (unknownObjectBehavior == null ? 0 : unknownObjectBehavior.hashCode());
total = total*37 + counter;
total = total*37 + (isLocked ? 0 : 1);
return total;
}
/**
* Returns <code>true</code> iff <i>that</i> Object is
* is a {@link Comparator} whose ordering is known to be
* equivalent to mine.
* <p>
* This implementation returns <code>true</code>
* iff <code><i>that</i></code> is a {@link FixedOrderComparator}
* whose attributes are equal to mine.
*
* @param object the object to compare to
* @return true if equal
*/
@Override
public boolean equals(final Object object) {
if (this == object) {
return true;
}
if (null == object) {
return false;
}
if (object.getClass().equals(this.getClass())) {
final FixedOrderComparator<?> comp = (FixedOrderComparator<?>) object;
return (null == map ? null == comp.map : map.equals(comp.map)) &&
(null == unknownObjectBehavior ? null == comp.unknownObjectBehavior :
unknownObjectBehavior == comp.unknownObjectBehavior &&
counter == comp.counter &&
isLocked == comp.isLocked &&
unknownObjectBehavior == comp.unknownObjectBehavior);
}
return false;
}
}