    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 *      http://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.iterators;
    018
    019import java.util.ArrayList;
    020import java.util.BitSet;
    021import java.util.Collection;
    022import java.util.Comparator;
    023import java.util.Iterator;
    024import java.util.List;
    025import java.util.NoSuchElementException;
    026import java.util.Objects;
    027
    028import org.apache.commons.collections4.list.UnmodifiableList;
    029
    030/**
    031 * Provides an ordered iteration over the elements contained in a collection of
    032 * ordered Iterators.
    033 * <p>
    034 * Given two ordered {@link Iterator} instances {@code A} and
    035 * {@code B}, the {@link #next} method on this iterator will return the
    036 * lesser of {@code A.next()} and {@code B.next()}.
    037 * </p>
    038 *
    039 * @param <E> the type of elements returned by this iterator.
    040 * @since 2.1
    041 */
    042public class CollatingIterator<E> implements Iterator<E> {
    043
    044    /** The {@link Comparator} used to evaluate order. */
    045    private Comparator<? super E> comparator;
    046
    047    /** The list of {@link Iterator}s to evaluate. */
    048    private final List<Iterator<? extends E>> iterators;
    049
    050    /** {@link Iterator#next Next} objects peeked from each iterator. */
    051    private List<E> values;
    052
    053    /** Whether or not each {@link #values} element has been set. */
    054    private BitSet valueSet;
    055
    056    /**
    057     * Index of the {@link #iterators iterator} from whom the last returned
    058     * value was obtained.
    059     */
    060    private int lastReturned = -1;
    061
    062    /**
    063     * Constructs a new {@code CollatingIterator}. A comparator must be
    064     * set by calling {@link #setComparator(Comparator)} before invoking
    065     * {@link #hasNext()}, or {@link #next()} for the first time. Child
    066     * iterators will have to be manually added using the
    067     * {@link #addIterator(Iterator)} method.
    068     */
    069    public CollatingIterator() {
    070        this(null, 2);
    071    }
    072
    073    /**
    074     * Constructs a new {@code CollatingIterator} that will use the
    075     * specified comparator for ordering. Child iterators will have to be
    076     * manually added using the {@link #addIterator(Iterator)} method.
    077     *
    078     * @param comp the comparator to use to sort; must not be null,
    079     *   unless you'll be invoking {@link #setComparator(Comparator)} later on.
    080     */
    081    public CollatingIterator(final Comparator<? super E> comp) {
    082        this(comp, 2);
    083    }
    084
    085    /**
    086     * Constructs a new {@code CollatingIterator} that will use the
    087     * specified comparator to provide ordered iteration over the collection of
    088     * iterators.
    089     *
    090     * @param comp the comparator to use to sort; must not be null,
    091     *   unless you'll be invoking {@link #setComparator(Comparator)} later on.
    092     * @param iterators the collection of iterators
    093     * @throws NullPointerException if the iterators collection is or contains null
    094     * @throws ClassCastException if the iterators collection contains an
    095     *   element that's not an {@link Iterator}
    096     */
    097    public CollatingIterator(final Comparator<? super E> comp, final Collection<Iterator<? extends E>> iterators) {
    098        this(comp, iterators.size());
    099        for (final Iterator<? extends E> iterator : iterators) {
    100            addIterator(iterator);
    101        }
    102    }
    103
    104    /**
    105     * Constructs a new {@code CollatingIterator} that will use the
    106     * specified comparator for ordering and have the specified initial
    107     * capacity. Child iterators will have to be manually added using the
    108     * {@link #addIterator(Iterator)} method.
    109     *
    110     * @param comp the comparator to use to sort; must not be null,
    111     *   unless you'll be invoking {@link #setComparator(Comparator)} later on.
    112     * @param initIterCapacity the initial capacity for the internal list of
    113     *   child iterators
    114     */
    115    public CollatingIterator(final Comparator<? super E> comp, final int initIterCapacity) {
    116        iterators = new ArrayList<>(initIterCapacity);
    117        setComparator(comp);
    118    }
    119
    120    /**
    121     * Constructs a new {@code CollatingIterator} that will use the
    122     * specified comparator to provide ordered iteration over the two given
    123     * iterators.
    124     *
    125     * @param comp the comparator to use to sort; must not be null,
    126     *   unless you'll be invoking {@link #setComparator(Comparator)} later on.
    127     * @param a the first child ordered iterator
    128     * @param b the second child ordered iterator
    129     * @throws NullPointerException if either iterator is null
    130     */
    131    public CollatingIterator(final Comparator<? super E> comp, final Iterator<? extends E> a,
    132                             final Iterator<? extends E> b) {
    133        this(comp, 2);
    134        addIterator(a);
    135        addIterator(b);
    136    }
    137
    138    /**
    139     * Constructs a new {@code CollatingIterator} that will use the
    140     * specified comparator to provide ordered iteration over the array of
    141     * iterators.
    142     *
    143     * @param comp the comparator to use to sort; must not be null,
    144     *   unless you'll be invoking {@link #setComparator(Comparator)} later on.
    145     * @param iterators the array of iterators
    146     * @throws NullPointerException if iterators array is or contains null
    147     */
    148    public CollatingIterator(final Comparator<? super E> comp, final Iterator<? extends E>[] iterators) {
    149        this(comp, iterators.length);
    150        for (final Iterator<? extends E> iterator : iterators) {
    151            addIterator(iterator);
    152        }
    153    }
    154
    155    /**
    156     * Adds the given {@link Iterator} to the iterators being collated.
    157     *
    158     * @param iterator the iterator to add to the collation, must not be null
    159     * @throws IllegalStateException if iteration has started
    160     * @throws NullPointerException if the iterator is null
    161     */
    162    public void addIterator(final Iterator<? extends E> iterator) {
    163        checkNotStarted();
    164        Objects.requireNonNull(iterator, "iterator");
    165        iterators.add(iterator);
    166    }
    167
    168    /**
    169     * Returns {@code true} iff any {@link Iterator} in the given list has
    170     * a next value.
    171     */
    172    private boolean anyHasNext(final List<Iterator<? extends E>> iterators) {
    173        for (final Iterator<? extends E> iterator : iterators) {
    174            if (iterator.hasNext()) {
    175                return true;
    176            }
    177        }
    178        return false;
    179    }
    180
    181    /**
    182     * Returns {@code true} iff any bit in the given set is
    183     * {@code true}.
    184     */
    185    private boolean anyValueSet(final BitSet set) {
    186        for (int i = 0; i < set.size(); i++) {
    187            if (set.get(i)) {
    188                return true;
    189            }
    190        }
    191        return false;
    192    }
    193
    194    /**
    195     * Throws {@link IllegalStateException} if iteration has started via
    196     * {@link #start}.
    197     *
    198     * @throws IllegalStateException if iteration started
    199     */
    200    private void checkNotStarted() throws IllegalStateException {
    201        if (values != null) {
    202            throw new IllegalStateException("Can't do that after next or hasNext has been called.");
    203        }
    204    }
    205
    206    /**
    207     * Clears the {@link #values} and {@link #valueSet} attributes at position
    208     * <em>i</em>.
    209     */
    210    private void clear(final int i) {
    211        values.set(i, null);
    212        valueSet.clear(i);
    213    }
    214
    215    /**
    216     * Gets the {@link Comparator} by which collation occurs.
    217     *
    218     * @return the {@link Comparator}
    219     */
    220    public Comparator<? super E> getComparator() {
    221        return comparator;
    222    }
    223
    224    /**
    225     * Gets the index of the iterator that returned the last element.
    226     *
    227     * @return the index of the iterator that returned the last element
    228     * @throws IllegalStateException if there is no last returned element
    229     */
    230    public int getIteratorIndex() {
    231        if (lastReturned == -1) {
    232            throw new IllegalStateException("No value has been returned yet");
    233        }
    234
    235        return lastReturned;
    236    }
    237
    238    /**
    239     * Gets the list of Iterators (unmodifiable).
    240     *
    241     * @return the unmodifiable list of iterators added
    242     */
    243    public List<Iterator<? extends E>> getIterators() {
    244        return UnmodifiableList.unmodifiableList(iterators);
    245    }
    246
    247    /**
    248     * Returns {@code true} if any child iterator has remaining elements.
    249     *
    250     * @return true if this iterator has remaining elements
    251     */
    252    @Override
    253    public boolean hasNext() {
    254        start();
    255        return anyValueSet(valueSet) || anyHasNext(iterators);
    256    }
    257
    258    /**
    259     * Returns the index of the least element in {@link #values},
    260     * {@link #set(int) setting} any uninitialized values.
    261     *
    262     * @throws NullPointerException if no comparator is set
    263     */
    264    private int least() {
    265        int leastIndex = -1;
    266        E leastObject = null;
    267        for (int i = 0; i < values.size(); i++) {
    268            if (!valueSet.get(i)) {
    269                set(i);
    270            }
    271            if (valueSet.get(i)) {
    272                if (leastIndex == -1) {
    273                    leastIndex = i;
    274                    leastObject = values.get(i);
    275                } else {
    276                    final E curObject = values.get(i);
    277                    Objects.requireNonNull(comparator, "You must invoke setComparator() to set a comparator first.");
    278                    if (comparator.compare(curObject, leastObject) < 0) {
    279                        leastObject = curObject;
    280                        leastIndex = i;
    281                    }
    282                }
    283            }
    284        }
    285        return leastIndex;
    286    }
    287
    288    /**
    289     * Returns the next ordered element from a child iterator.
    290     *
    291     * @return the next ordered element
    292     * @throws NoSuchElementException if no child iterator has any more elements
    293     */
    294    @Override
    295    public E next() throws NoSuchElementException {
    296        if (!hasNext()) {
    297            throw new NoSuchElementException();
    298        }
    299        final int leastIndex = least();
    300        if (leastIndex == -1) {
    301            throw new NoSuchElementException();
    302        }
    303        final E val = values.get(leastIndex);
    304        clear(leastIndex);
    305        lastReturned = leastIndex;
    306        return val;
    307    }
    308
    309    /**
    310     * Removes the last returned element from the child iterator that produced it.
    311     *
    312     * @throws IllegalStateException if there is no last returned element, or if
    313     * the last returned element has already been removed
    314     */
    315    @Override
    316    public void remove() {
    317        if (lastReturned == -1) {
    318            throw new IllegalStateException("No value can be removed at present");
    319        }
    320        iterators.get(lastReturned).remove();
    321    }
    322
    323    /**
    324     * Sets the {@link #values} and {@link #valueSet} attributes at position
    325     * <em>i</em> to the next value of the {@link #iterators iterator} at position
    326     * <em>i</em>, or clear them if the <em>i</em><sup>th</sup> iterator has no next
    327     * value.
    328     *
    329     * @return {@code false} iff there was no value to set
    330     */
    331    private boolean set(final int index) {
    332        final Iterator<? extends E> it = iterators.get(index);
    333        if (it.hasNext()) {
    334            values.set(index, it.next());
    335            valueSet.set(index);
    336            return true;
    337        }
    338        values.set(index, null);
    339        valueSet.clear(index);
    340        return false;
    341    }
    342
    343    /**
    344     * Sets the {@link Comparator} by which collation occurs. If you
    345     * would like to use the natural sort order (or, in other words,
    346     * if the elements in the iterators are implementing the
    347     * {@link Comparable} interface), then use the
    348     * {@link org.apache.commons.collections4.comparators.ComparableComparator}.
    349     *
    350     * @param comp the {@link Comparator} to set
    351     * @throws IllegalStateException if iteration has started
    352     */
    353    public void setComparator(final Comparator<? super E> comp) {
    354        checkNotStarted();
    355        comparator = comp;
    356    }
    357
    358    /**
    359     * Sets the iterator at the given index.
    360     *
    361     * @param index index of the Iterator to replace
    362     * @param iterator Iterator to place at the given index
    363     * @throws IndexOutOfBoundsException if index &lt; 0 or index &gt;= size()
    364     * @throws IllegalStateException if iteration has started
    365     * @throws NullPointerException if the iterator is null
    366     */
    367    public void setIterator(final int index, final Iterator<? extends E> iterator) {
    368        checkNotStarted();
    369        Objects.requireNonNull(iterator, "iterator");
    370        iterators.set(index, iterator);
    371    }
    372
    373    /**
    374     * Initializes the collating state if it hasn't been already.
    375     */
    376    private void start() {
    377        if (values == null) {
    378            values = new ArrayList<>(iterators.size());
    379            valueSet = new BitSet(iterators.size());
    380            for (int i = 0; i < iterators.size(); i++) {
    381                values.add(null);
    382                valueSet.clear(i);
    383            }
    384        }
    385    }
    386
    387}