001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.collections4.comparators;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.BitSet;
022import java.util.Comparator;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Objects;
026
027/**
028 * A ComparatorChain is a Comparator that wraps one or more Comparators in
029 * sequence. The ComparatorChain calls each Comparator in sequence until either
030 * 1) any single Comparator returns a non-zero result (and that result is then
031 * returned), or 2) the ComparatorChain is exhausted (and zero is returned).
032 * This type of sorting is very similar to multi-column sorting in SQL, and this
033 * class allows Java classes to emulate that kind of behavior when sorting a
034 * List.
035 * <p>
036 * To further facilitate SQL-like sorting, the order of any single Comparator in
037 * the list can be reversed.
038 * </p>
039 * <p>
040 * Calling a method that adds new Comparators or changes the ascend/descend sort
041 * <em>after compare(Object, Object) has been called</em> will result in an
042 * UnsupportedOperationException. However, <em>take care</em> to not alter the
043 * underlying List of Comparators or the BitSet that defines the sort order.
044 * </p>
045 * <p>
046 * Instances of ComparatorChain are not synchronized. The class is not
047 * thread-safe at construction time, but it <em>is</em> thread-safe to perform
048 * multiple comparisons after all the setup operations are complete.
049 * </p>
050 *
051 * @param <E> The type of objects compared by this comparator
052 * @since 2.0
053 */
054public class ComparatorChain<E> implements Comparator<E>, Serializable {
055
056    /** Serialization version from Collections 2.0. */
057    private static final long serialVersionUID = -721644942746081630L;
058
059    /** The list of comparators in the chain. */
060    private final List<Comparator<E>> comparatorChain;
061
062    /** Order - false (clear) = ascend; true (set) = descend. */
063    private final BitSet orderingBits;
064
065    /** Whether the chain has been "locked". */
066    private boolean isLocked;
067
068    /**
069     * Constructs a ComparatorChain with no Comparators.
070     * You must add at least one Comparator before calling
071     * the compare(Object, Object) method, or an
072     * UnsupportedOperationException is thrown
073     */
074    public ComparatorChain() {
075        this(new ArrayList<>(), new BitSet());
076    }
077
078    /**
079     * Constructs a ComparatorChain with a single Comparator,
080     * sorting in the forward order
081     *
082     * @param comparator First comparator in the Comparator chain
083     */
084    public ComparatorChain(final Comparator<E> comparator) {
085        this(comparator, false);
086    }
087
088    /**
089     * Constructs a Comparator chain with a single Comparator,
090     * sorting in the given order
091     *
092     * @param comparator First Comparator in the ComparatorChain
093     * @param reverse    false = forward sort; true = reverse sort
094     */
095    public ComparatorChain(final Comparator<E> comparator, final boolean reverse) {
096        comparatorChain = new ArrayList<>(1);
097        comparatorChain.add(comparator);
098        orderingBits = new BitSet(1);
099        if (reverse) {
100            orderingBits.set(0);
101        }
102    }
103
104    /**
105     * Constructs a ComparatorChain from the Comparators in the
106     * List.  All Comparators will default to the forward
107     * sort order.
108     *
109     * @param list   List of Comparators
110     * @see #ComparatorChain(List,BitSet)
111     */
112    public ComparatorChain(final List<Comparator<E>> list) {
113        this(list, new BitSet(list.size()));
114    }
115
116    /**
117     * Constructs a ComparatorChain from the Comparators in the
118     * given List.  The sort order of each column will be
119     * drawn from the given BitSet.  When determining the sort
120     * order for Comparator at index <em>i</em> in the List,
121     * the ComparatorChain will call BitSet.get(<em>i</em>).
122     * If that method returns <em>false</em>, the forward
123     * sort order is used; a return value of <em>true</em>
124     * indicates reverse sort order.
125     *
126     * @param list   List of Comparators.  NOTE: This constructor does not perform a
127     *               defensive copy of the list
128     * @param bits   Sort order for each Comparator.  Extra bits are ignored,
129     *               unless extra Comparators are added by another method.
130     */
131    public ComparatorChain(final List<Comparator<E>> list, final BitSet bits) {
132        comparatorChain = list;
133        orderingBits = bits;
134    }
135
136    /**
137     * Add a Comparator to the end of the chain using the
138     * forward sort order
139     *
140     * @param comparator Comparator with the forward sort order
141     */
142    public void addComparator(final Comparator<E> comparator) {
143        addComparator(comparator, false);
144    }
145
146    /**
147     * Add a Comparator to the end of the chain using the
148     * given sort order
149     *
150     * @param comparator Comparator to add to the end of the chain
151     * @param reverse    false = forward sort order; true = reverse sort order
152     */
153    public void addComparator(final Comparator<E> comparator, final boolean reverse) {
154        checkLocked();
155
156        comparatorChain.add(comparator);
157        if (reverse) {
158            orderingBits.set(comparatorChain.size() - 1);
159        }
160    }
161
162    /**
163     * Throws an exception if the {@link ComparatorChain} is empty.
164     *
165     * @throws UnsupportedOperationException if the {@link ComparatorChain} is empty
166     */
167    private void checkChainIntegrity() {
168        if (comparatorChain.isEmpty()) {
169            throw new UnsupportedOperationException("ComparatorChains must contain at least one Comparator");
170        }
171    }
172
173    /**
174     * Throws an exception if the {@link ComparatorChain} is locked.
175     *
176     * @throws UnsupportedOperationException if the {@link ComparatorChain} is locked
177     */
178    private void checkLocked() {
179        if (isLocked) {
180            throw new UnsupportedOperationException(
181                    "Comparator ordering cannot be changed after the first comparison is performed");
182        }
183    }
184
185    /**
186     * Perform comparisons on the Objects as per
187     * Comparator.compare(o1, o2).
188     *
189     * @param o1  The first object to compare
190     * @param o2  The second object to compare
191     * @return -1, 0, or 1
192     * @throws UnsupportedOperationException if the ComparatorChain does not contain at least one Comparator
193     */
194    @Override
195    public int compare(final E o1, final E o2) throws UnsupportedOperationException {
196        if (!isLocked) {
197            checkChainIntegrity();
198            isLocked = true;
199        }
200
201        // iterate over all comparators in the chain
202        final Iterator<Comparator<E>> comparators = comparatorChain.iterator();
203        for (int comparatorIndex = 0; comparators.hasNext(); ++comparatorIndex) {
204
205            final Comparator<? super E> comparator = comparators.next();
206            int retval = comparator.compare(o1, o2);
207            if (retval != 0) {
208                // invert the order if it is a reverse sort
209                if (orderingBits.get(comparatorIndex)) {
210                    if (retval > 0) {
211                        retval = -1;
212                    } else {
213                        retval = 1;
214                    }
215                }
216                return retval;
217            }
218        }
219
220        // if comparators are exhausted, return 0
221        return 0;
222    }
223
224    /**
225     * Returns {@code true} iff <em>that</em> Object is
226     * a {@link Comparator} whose ordering is known to be
227     * equivalent to mine.
228     * <p>
229     * This implementation returns {@code true}
230     * iff {@code <em>object</em>.{@link Object#getClass() getClass()}}
231     * equals {@code this.getClass()}, and the underlying
232     * comparators and order bits are equal.
233     * Subclasses may want to override this behavior to remain consistent
234     * with the {@link Comparator#equals(Object)} contract.
235     *
236     * @param object  The object to compare with
237     * @return true if equal
238     * @since 3.0
239     */
240    @Override
241    public boolean equals(final Object object) {
242        if (this == object) {
243            return true;
244        }
245        if (object == null) {
246            return false;
247        }
248        if (object.getClass().equals(this.getClass())) {
249            final ComparatorChain<?> chain = (ComparatorChain<?>) object;
250            return Objects.equals(orderingBits, chain.orderingBits) &&
251                   Objects.equals(comparatorChain, chain.comparatorChain);
252        }
253        return false;
254    }
255
256    /**
257     * Implement a hash code for this comparator that is consistent with
258     * {@link #equals(Object) equals}.
259     *
260     * @return A suitable hash code
261     * @since 3.0
262     */
263    @Override
264    public int hashCode() {
265        int hash = 0;
266        if (comparatorChain != null) {
267            hash ^= comparatorChain.hashCode();
268        }
269        if (orderingBits != null) {
270            hash ^= orderingBits.hashCode();
271        }
272        return hash;
273    }
274
275    /**
276     * Determine if modifications can still be made to the
277     * ComparatorChain.  ComparatorChains cannot be modified
278     * once they have performed a comparison.
279     *
280     * @return true = ComparatorChain cannot be modified; false =
281     *         ComparatorChain can still be modified.
282     */
283    public boolean isLocked() {
284        return isLocked;
285    }
286
287    /**
288     * Replace the Comparator at the given index, maintaining
289     * the existing sort order.
290     *
291     * @param index      index of the Comparator to replace
292     * @param comparator Comparator to place at the given index
293     * @throws IndexOutOfBoundsException
294     *                   if index &lt; 0 or index &gt;= size()
295     */
296    public void setComparator(final int index, final Comparator<E> comparator) throws IndexOutOfBoundsException {
297        setComparator(index, comparator, false);
298    }
299
300    /**
301     * Replace the Comparator at the given index in the
302     * ComparatorChain, using the given sort order
303     *
304     * @param index      index of the Comparator to replace
305     * @param comparator Comparator to set
306     * @param reverse    false = forward sort order; true = reverse sort order
307     */
308    public void setComparator(final int index, final Comparator<E> comparator, final boolean reverse) {
309        checkLocked();
310
311        comparatorChain.set(index, comparator);
312        if (reverse) {
313            orderingBits.set(index);
314        } else {
315            orderingBits.clear(index);
316        }
317    }
318
319    /**
320     * Change the sort order at the given index in the
321     * ComparatorChain to a forward sort.
322     *
323     * @param index  Index of the ComparatorChain
324     */
325    public void setForwardSort(final int index) {
326        checkLocked();
327        orderingBits.clear(index);
328    }
329
330    /**
331     * Change the sort order at the given index in the
332     * ComparatorChain to a reverse sort.
333     *
334     * @param index  Index of the ComparatorChain
335     */
336    public void setReverseSort(final int index) {
337        checkLocked();
338        orderingBits.set(index);
339    }
340
341    /**
342     * Number of Comparators in the current ComparatorChain.
343     *
344     * @return Comparator count
345     */
346    public int size() {
347        return comparatorChain.size();
348    }
349
350}