FixedOrderComparator.java

  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.collections4.comparators;

  18. import java.io.Serializable;
  19. import java.util.Comparator;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Objects;

  24. /**
  25.  * A Comparator which imposes a specific order on a specific set of Objects.
  26.  * Objects are presented to the FixedOrderComparator in a specified order and
  27.  * subsequent calls to {@link #compare(Object, Object) compare} yield that order.
  28.  * For example:
  29.  * <pre>
  30.  * String[] planets = {"Mercury", "Venus", "Earth", "Mars"};
  31.  * FixedOrderComparator distanceFromSun = new FixedOrderComparator(planets);
  32.  * Arrays.sort(planets);                     // Sort to alphabetical order
  33.  * Arrays.sort(planets, distanceFromSun);    // Back to original order
  34.  * </pre>
  35.  * <p>
  36.  * Once {@code compare} has been called, the FixedOrderComparator is locked
  37.  * and attempts to modify it yield an UnsupportedOperationException.
  38.  * </p>
  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.  * <p>
  45.  * This class is Serializable from Commons Collections 4.0.
  46.  * </p>
  47.  *
  48.  * @param <T> the type of objects compared by this comparator
  49.  * @since 3.0
  50.  */
  51. public class FixedOrderComparator<T> implements Comparator<T>, Serializable {

  52.     /**
  53.      * Enumerates the unknown object behaviors.
  54.      *
  55.      * @since 4.0
  56.      */
  57.     public enum UnknownObjectBehavior {

  58.         /**
  59.          * Before unknown object behaviors.
  60.          */
  61.         BEFORE,

  62.         /**
  63.          * After unknown object behaviors.
  64.          */
  65.         AFTER,

  66.         /**
  67.          * Exception unknown object behaviors.
  68.          */
  69.         EXCEPTION
  70.     }

  71.     /** Serialization version from Collections 4.0. */
  72.     private static final long serialVersionUID = 82794675842863201L;

  73.     /** Internal map of object to position */
  74.     private final Map<T, Integer> map = new HashMap<>();

  75.     /** Counter used in determining the position in the map */
  76.     private int counter;

  77.     /** Is the comparator locked against further change */
  78.     private boolean isLocked;

  79.     /** The behavior in the case of an unknown object */
  80.     private UnknownObjectBehavior unknownObjectBehavior = UnknownObjectBehavior.EXCEPTION;

  81.     // Constructors
  82.     /**
  83.      * Constructs an empty FixedOrderComparator.
  84.      */
  85.     public FixedOrderComparator() {
  86.     }

  87.     /**
  88.      * Constructs a FixedOrderComparator which uses the order of the given list
  89.      * to compare the objects.
  90.      * <p>
  91.      * The list is copied, so later changes will not affect the comparator.
  92.      *
  93.      * @param items  the items that the comparator can compare in order
  94.      * @throws NullPointerException if the list is null
  95.      */
  96.     public FixedOrderComparator(final List<T> items) {
  97.         for (final T t : Objects.requireNonNull(items, "items")) {
  98.             add(t);
  99.         }
  100.     }

  101.     /**
  102.      * Constructs a FixedOrderComparator which uses the order of the given array
  103.      * to compare the objects.
  104.      * <p>
  105.      * The array is copied, so later changes will not affect the comparator.
  106.      *
  107.      * @param items  the items that the comparator can compare in order
  108.      * @throws NullPointerException if the array is null
  109.      */
  110.     public FixedOrderComparator(final T... items) {
  111.         for (final T item : Objects.requireNonNull(items, "items")) {
  112.             add(item);
  113.         }
  114.     }

  115.     // Methods for adding items
  116.     /**
  117.      * Adds an item, which compares as after all items known to the Comparator.
  118.      * If the item is already known to the Comparator, its old position is
  119.      * replaced with the new position.
  120.      *
  121.      * @param obj  the item to be added to the Comparator.
  122.      * @return true if obj has been added for the first time, false if
  123.      *  it was already known to the Comparator.
  124.      * @throws UnsupportedOperationException if a comparison has already been made
  125.      */
  126.     public boolean add(final T obj) {
  127.         checkLocked();
  128.         final Integer position = map.put(obj, Integer.valueOf(counter++));
  129.         return position == null;
  130.     }

  131.     /**
  132.      * Adds a new item, which compares as equal to the given existing item.
  133.      *
  134.      * @param existingObj  an item already in the Comparator's set of
  135.      *  known objects
  136.      * @param newObj  an item to be added to the Comparator's set of
  137.      *  known objects
  138.      * @return true if newObj has been added for the first time, false if
  139.      *  it was already known to the Comparator.
  140.      * @throws IllegalArgumentException if existingObject is not in the
  141.      *  Comparator's set of known objects.
  142.      * @throws UnsupportedOperationException if a comparison has already been made
  143.      */
  144.     public boolean addAsEqual(final T existingObj, final T newObj) {
  145.         checkLocked();
  146.         final Integer position = map.get(existingObj);
  147.         if (position == null) {
  148.             throw new IllegalArgumentException(existingObj + " not known to " + this);
  149.         }
  150.         final Integer result = map.put(newObj, position);
  151.         return result == null;
  152.     }

  153.     /**
  154.      * Checks to see whether the comparator is now locked against further changes.
  155.      *
  156.      * @throws UnsupportedOperationException if the comparator is locked
  157.      */
  158.     protected void checkLocked() {
  159.         if (isLocked()) {
  160.             throw new UnsupportedOperationException("Cannot modify a FixedOrderComparator after a comparison");
  161.         }
  162.     }

  163.     // Comparator methods
  164.     /**
  165.      * Compares two objects according to the order of this Comparator.
  166.      * <p>
  167.      * It is important to note that this class will throw an IllegalArgumentException
  168.      * in the case of an unrecognized object. This is not specified in the
  169.      * Comparator interface, but is the most appropriate exception.
  170.      *
  171.      * @param obj1  the first object to compare
  172.      * @param obj2  the second object to compare
  173.      * @return negative if obj1 is less, positive if greater, zero if equal
  174.      * @throws IllegalArgumentException if obj1 or obj2 are not known
  175.      *  to this Comparator and an alternative behavior has not been set
  176.      *  via {@link #setUnknownObjectBehavior(UnknownObjectBehavior)}.
  177.      */
  178.     @Override
  179.     public int compare(final T obj1, final T obj2) {
  180.         isLocked = true;
  181.         final Integer position1 = map.get(obj1);
  182.         final Integer position2 = map.get(obj2);
  183.         if (position1 == null || position2 == null) {
  184.             switch (unknownObjectBehavior) {
  185.             case BEFORE:
  186.                 return position1 == null ? position2 == null ? 0 : -1 : 1;
  187.             case AFTER:
  188.                 return position1 == null ? position2 == null ? 0 : 1 : -1;
  189.             case EXCEPTION:
  190.                 final Object unknownObj = position1 == null ? obj1 : obj2;
  191.                 throw new IllegalArgumentException("Attempting to compare unknown object "
  192.                         + unknownObj);
  193.             default: //could be null
  194.                 throw new UnsupportedOperationException("Unknown unknownObjectBehavior: "
  195.                         + unknownObjectBehavior);
  196.             }
  197.         }
  198.         return position1.compareTo(position2);
  199.     }

  200.     @Override
  201.     public boolean equals(final Object obj) {
  202.         if (this == obj) {
  203.             return true;
  204.         }
  205.         if (obj == null) {
  206.             return false;
  207.         }
  208.         if (getClass() != obj.getClass()) {
  209.             return false;
  210.         }
  211.         final FixedOrderComparator<?> other = (FixedOrderComparator<?>) obj;
  212.         return counter == other.counter && isLocked == other.isLocked && Objects.equals(map, other.map) && unknownObjectBehavior == other.unknownObjectBehavior;
  213.     }

  214.     /**
  215.      * Gets the behavior for comparing unknown objects.
  216.      *
  217.      * @return {@link UnknownObjectBehavior}
  218.      */
  219.     public UnknownObjectBehavior getUnknownObjectBehavior() {
  220.         return unknownObjectBehavior;
  221.     }

  222.     @Override
  223.     public int hashCode() {
  224.         return Objects.hash(counter, isLocked, map, unknownObjectBehavior);
  225.     }

  226.     // Bean methods / state querying methods
  227.     /**
  228.      * Returns true if modifications cannot be made to the FixedOrderComparator.
  229.      * FixedOrderComparators cannot be modified once they have performed a comparison.
  230.      *
  231.      * @return true if attempts to change the FixedOrderComparator yield an
  232.      *  UnsupportedOperationException, false if it can be changed.
  233.      */
  234.     public boolean isLocked() {
  235.         return isLocked;
  236.     }

  237.     /**
  238.      * Sets the behavior for comparing unknown objects.
  239.      *
  240.      * @param unknownObjectBehavior  the flag for unknown behavior -
  241.      * UNKNOWN_AFTER, UNKNOWN_BEFORE or UNKNOWN_THROW_EXCEPTION
  242.      * @throws UnsupportedOperationException if a comparison has been performed
  243.      * @throws NullPointerException if unknownObjectBehavior is null
  244.      */
  245.     public void setUnknownObjectBehavior(final UnknownObjectBehavior unknownObjectBehavior) {
  246.         checkLocked();
  247.         this.unknownObjectBehavior = Objects.requireNonNull(unknownObjectBehavior, "unknownObjectBehavior");
  248.     }

  249. }