ComparatorChain.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.ArrayList;
  20. import java.util.BitSet;
  21. import java.util.Comparator;
  22. import java.util.Iterator;
  23. import java.util.List;
  24. import java.util.Objects;

  25. /**
  26.  * A ComparatorChain is a Comparator that wraps one or more Comparators in
  27.  * sequence. The ComparatorChain calls each Comparator in sequence until either
  28.  * 1) any single Comparator returns a non-zero result (and that result is then
  29.  * returned), or 2) the ComparatorChain is exhausted (and zero is returned).
  30.  * This type of sorting is very similar to multi-column sorting in SQL, and this
  31.  * class allows Java classes to emulate that kind of behavior when sorting a
  32.  * List.
  33.  * <p>
  34.  * To further facilitate SQL-like sorting, the order of any single Comparator in
  35.  * the list can be reversed.
  36.  * </p>
  37.  * <p>
  38.  * Calling a method that adds new Comparators or changes the ascend/descend sort
  39.  * <em>after compare(Object, Object) has been called</em> will result in an
  40.  * UnsupportedOperationException. However, <em>take care</em> to not alter the
  41.  * underlying List of Comparators or the BitSet that defines the sort order.
  42.  * </p>
  43.  * <p>
  44.  * Instances of ComparatorChain are not synchronized. The class is not
  45.  * thread-safe at construction time, but it <em>is</em> thread-safe to perform
  46.  * multiple comparisons after all the setup operations are complete.
  47.  * </p>
  48.  *
  49.  * @param <E> the type of objects compared by this comparator
  50.  * @since 2.0
  51.  */
  52. public class ComparatorChain<E> implements Comparator<E>, Serializable {

  53.     /** Serialization version from Collections 2.0. */
  54.     private static final long serialVersionUID = -721644942746081630L;

  55.     /** The list of comparators in the chain. */
  56.     private final List<Comparator<E>> comparatorChain;
  57.     /** Order - false (clear) = ascend; true (set) = descend. */
  58.     private final BitSet orderingBits;
  59.    /** Whether the chain has been "locked". */
  60.     private boolean isLocked;

  61.     /**
  62.      * Constructs a ComparatorChain with no Comparators.
  63.      * You must add at least one Comparator before calling
  64.      * the compare(Object,Object) method, or an
  65.      * UnsupportedOperationException is thrown
  66.      */
  67.     public ComparatorChain() {
  68.         this(new ArrayList<>(), new BitSet());
  69.     }

  70.     /**
  71.      * Constructs a ComparatorChain with a single Comparator,
  72.      * sorting in the forward order
  73.      *
  74.      * @param comparator First comparator in the Comparator chain
  75.      */
  76.     public ComparatorChain(final Comparator<E> comparator) {
  77.         this(comparator, false);
  78.     }

  79.     /**
  80.      * Constructs a Comparator chain with a single Comparator,
  81.      * sorting in the given order
  82.      *
  83.      * @param comparator First Comparator in the ComparatorChain
  84.      * @param reverse    false = forward sort; true = reverse sort
  85.      */
  86.     public ComparatorChain(final Comparator<E> comparator, final boolean reverse) {
  87.         comparatorChain = new ArrayList<>(1);
  88.         comparatorChain.add(comparator);
  89.         orderingBits = new BitSet(1);
  90.         if (reverse) {
  91.             orderingBits.set(0);
  92.         }
  93.     }

  94.     /**
  95.      * Constructs a ComparatorChain from the Comparators in the
  96.      * List.  All Comparators will default to the forward
  97.      * sort order.
  98.      *
  99.      * @param list   List of Comparators
  100.      * @see #ComparatorChain(List,BitSet)
  101.      */
  102.     public ComparatorChain(final List<Comparator<E>> list) {
  103.         this(list, new BitSet(list.size()));
  104.     }

  105.     /**
  106.      * Constructs a ComparatorChain from the Comparators in the
  107.      * given List.  The sort order of each column will be
  108.      * drawn from the given BitSet.  When determining the sort
  109.      * order for Comparator at index <em>i</em> in the List,
  110.      * the ComparatorChain will call BitSet.get(<em>i</em>).
  111.      * If that method returns <em>false</em>, the forward
  112.      * sort order is used; a return value of <em>true</em>
  113.      * indicates reverse sort order.
  114.      *
  115.      * @param list   List of Comparators.  NOTE: This constructor does not perform a
  116.      *               defensive copy of the list
  117.      * @param bits   Sort order for each Comparator.  Extra bits are ignored,
  118.      *               unless extra Comparators are added by another method.
  119.      */
  120.     public ComparatorChain(final List<Comparator<E>> list, final BitSet bits) {
  121.         comparatorChain = list;
  122.         orderingBits = bits;
  123.     }

  124.     /**
  125.      * Add a Comparator to the end of the chain using the
  126.      * forward sort order
  127.      *
  128.      * @param comparator Comparator with the forward sort order
  129.      */
  130.     public void addComparator(final Comparator<E> comparator) {
  131.         addComparator(comparator, false);
  132.     }

  133.     /**
  134.      * Add a Comparator to the end of the chain using the
  135.      * given sort order
  136.      *
  137.      * @param comparator Comparator to add to the end of the chain
  138.      * @param reverse    false = forward sort order; true = reverse sort order
  139.      */
  140.     public void addComparator(final Comparator<E> comparator, final boolean reverse) {
  141.         checkLocked();

  142.         comparatorChain.add(comparator);
  143.         if (reverse) {
  144.             orderingBits.set(comparatorChain.size() - 1);
  145.         }
  146.     }

  147.     /**
  148.      * Throws an exception if the {@link ComparatorChain} is empty.
  149.      *
  150.      * @throws UnsupportedOperationException if the {@link ComparatorChain} is empty
  151.      */
  152.     private void checkChainIntegrity() {
  153.         if (comparatorChain.isEmpty()) {
  154.             throw new UnsupportedOperationException("ComparatorChains must contain at least one Comparator");
  155.         }
  156.     }

  157.     /**
  158.      * Throws an exception if the {@link ComparatorChain} is locked.
  159.      *
  160.      * @throws UnsupportedOperationException if the {@link ComparatorChain} is locked
  161.      */
  162.     private void checkLocked() {
  163.         if (isLocked) {
  164.             throw new UnsupportedOperationException(
  165.                     "Comparator ordering cannot be changed after the first comparison is performed");
  166.         }
  167.     }

  168.     /**
  169.      * Perform comparisons on the Objects as per
  170.      * Comparator.compare(o1,o2).
  171.      *
  172.      * @param o1  the first object to compare
  173.      * @param o2  the second object to compare
  174.      * @return -1, 0, or 1
  175.      * @throws UnsupportedOperationException if the ComparatorChain does not contain at least one Comparator
  176.      */
  177.     @Override
  178.     public int compare(final E o1, final E o2) throws UnsupportedOperationException {
  179.         if (!isLocked) {
  180.             checkChainIntegrity();
  181.             isLocked = true;
  182.         }

  183.         // iterate over all comparators in the chain
  184.         final Iterator<Comparator<E>> comparators = comparatorChain.iterator();
  185.         for (int comparatorIndex = 0; comparators.hasNext(); ++comparatorIndex) {

  186.             final Comparator<? super E> comparator = comparators.next();
  187.             int retval = comparator.compare(o1, o2);
  188.             if (retval != 0) {
  189.                 // invert the order if it is a reverse sort
  190.                 if (orderingBits.get(comparatorIndex)) {
  191.                     if (retval > 0) {
  192.                         retval = -1;
  193.                     } else {
  194.                         retval = 1;
  195.                     }
  196.                 }
  197.                 return retval;
  198.             }
  199.         }

  200.         // if comparators are exhausted, return 0
  201.         return 0;
  202.     }

  203.     /**
  204.      * Returns {@code true} iff <em>that</em> Object is
  205.      * a {@link Comparator} whose ordering is known to be
  206.      * equivalent to mine.
  207.      * <p>
  208.      * This implementation returns {@code true}
  209.      * iff {@code <em>object</em>.{@link Object#getClass() getClass()}}
  210.      * equals {@code this.getClass()}, and the underlying
  211.      * comparators and order bits are equal.
  212.      * Subclasses may want to override this behavior to remain consistent
  213.      * with the {@link Comparator#equals(Object)} contract.
  214.      *
  215.      * @param object  the object to compare with
  216.      * @return true if equal
  217.      * @since 3.0
  218.      */
  219.     @Override
  220.     public boolean equals(final Object object) {
  221.         if (this == object) {
  222.             return true;
  223.         }
  224.         if (null == object) {
  225.             return false;
  226.         }
  227.         if (object.getClass().equals(this.getClass())) {
  228.             final ComparatorChain<?> chain = (ComparatorChain<?>) object;
  229.             return Objects.equals(orderingBits, chain.orderingBits) &&
  230.                    Objects.equals(comparatorChain, chain.comparatorChain);
  231.         }
  232.         return false;
  233.     }

  234.     /**
  235.      * Implement a hash code for this comparator that is consistent with
  236.      * {@link #equals(Object) equals}.
  237.      *
  238.      * @return a suitable hash code
  239.      * @since 3.0
  240.      */
  241.     @Override
  242.     public int hashCode() {
  243.         int hash = 0;
  244.         if (null != comparatorChain) {
  245.             hash ^= comparatorChain.hashCode();
  246.         }
  247.         if (null != orderingBits) {
  248.             hash ^= orderingBits.hashCode();
  249.         }
  250.         return hash;
  251.     }

  252.     /**
  253.      * Determine if modifications can still be made to the
  254.      * ComparatorChain.  ComparatorChains cannot be modified
  255.      * once they have performed a comparison.
  256.      *
  257.      * @return true = ComparatorChain cannot be modified; false =
  258.      *         ComparatorChain can still be modified.
  259.      */
  260.     public boolean isLocked() {
  261.         return isLocked;
  262.     }

  263.     /**
  264.      * Replace the Comparator at the given index, maintaining
  265.      * the existing sort order.
  266.      *
  267.      * @param index      index of the Comparator to replace
  268.      * @param comparator Comparator to place at the given index
  269.      * @throws IndexOutOfBoundsException
  270.      *                   if index &lt; 0 or index &gt;= size()
  271.      */
  272.     public void setComparator(final int index, final Comparator<E> comparator) throws IndexOutOfBoundsException {
  273.         setComparator(index, comparator, false);
  274.     }

  275.     /**
  276.      * Replace the Comparator at the given index in the
  277.      * ComparatorChain, using the given sort order
  278.      *
  279.      * @param index      index of the Comparator to replace
  280.      * @param comparator Comparator to set
  281.      * @param reverse    false = forward sort order; true = reverse sort order
  282.      */
  283.     public void setComparator(final int index, final Comparator<E> comparator, final boolean reverse) {
  284.         checkLocked();

  285.         comparatorChain.set(index, comparator);
  286.         if (reverse) {
  287.             orderingBits.set(index);
  288.         } else {
  289.             orderingBits.clear(index);
  290.         }
  291.     }

  292.     /**
  293.      * Change the sort order at the given index in the
  294.      * ComparatorChain to a forward sort.
  295.      *
  296.      * @param index  Index of the ComparatorChain
  297.      */
  298.     public void setForwardSort(final int index) {
  299.         checkLocked();
  300.         orderingBits.clear(index);
  301.     }

  302.     /**
  303.      * Change the sort order at the given index in the
  304.      * ComparatorChain to a reverse sort.
  305.      *
  306.      * @param index  Index of the ComparatorChain
  307.      */
  308.     public void setReverseSort(final int index) {
  309.         checkLocked();
  310.         orderingBits.set(index);
  311.     }

  312.     /**
  313.      * Number of Comparators in the current ComparatorChain.
  314.      *
  315.      * @return Comparator count
  316.      */
  317.     public int size() {
  318.         return comparatorChain.size();
  319.     }

  320. }