All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.landawn.abacus.util.Iterators Maven / Gradle / Ivy

Go to download

A general programming library in Java/Android. It's easy to learn and simple to use with concise and powerful APIs.

There is a newer version: 2.1.12
Show newest version
/*
 * Copyright (c) 2017, Haiyang Li.
 *
 * Licensed 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 com.landawn.abacus.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import com.landawn.abacus.exception.DuplicatedResultException;
import com.landawn.abacus.util.u.Nullable;
import com.landawn.abacus.util.u.Optional;
import com.landawn.abacus.util.function.BiConsumer;
import com.landawn.abacus.util.function.BiFunction;
import com.landawn.abacus.util.function.BiPredicate;
import com.landawn.abacus.util.function.Function;
import com.landawn.abacus.util.function.Predicate;
import com.landawn.abacus.util.function.Supplier;
import com.landawn.abacus.util.function.TriFunction;

// TODO: Auto-generated Javadoc
/**
 * The methods in this class should only read the input {@code Collections/Arrays}, not modify any of them.
 *
 * @author Haiyang Li
 * @since 0.9
 */
public final class Iterators {

    /**
     * Instantiates a new iterators.
     */
    private Iterators() {
        // singleton.
    }

    /**
     *
     * @param iter
     * @param objToFind
     * @return true, if successful
     */
    public static boolean contains(final Iterator iter, final Object objToFind) {
        if (iter == null) {
            return false;
        }

        while (iter.hasNext()) {
            if (N.equals(iter.next(), objToFind)) {
                return true;
            }
        }

        return false;
    }

    /**
     *
     * @param iter
     * @param objToFind
     * @return true, if successful
     */
    public static boolean containsAny(final Iterator iter, final Set objsToFind) {
        if (iter == null || N.isNullOrEmpty(objsToFind)) {
            return false;
        }

        while (iter.hasNext()) {
            if (objsToFind.contains(iter.next())) {
                return true;
            }
        }

        return false;
    }

    /**
     *
     * @param iter
     * @param objToFind
     * @return true, if successful
     */
    public static boolean containsAll(final Iterator iter, final Collection objsToFind) {
        if (N.isNullOrEmpty(objsToFind)) {
            return true;
        } else if (iter == null) {
            return false;
        }

        final Set set = new HashSet<>(objsToFind);

        while (iter.hasNext()) {
            if (set.remove(iter.next())) {
                if (set.size() == 0) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     *
     * @param iter
     * @param objToFind
     * @return
     */
    public static long indexOf(final Iterator iter, final Object objToFind) {
        if (iter == null) {
            return N.INDEX_NOT_FOUND;
        }

        long index = 0;

        while (iter.hasNext()) {
            if (N.equals(iter.next(), objToFind)) {
                return index;
            }

            index++;
        }

        return N.INDEX_NOT_FOUND;
    }

    /**
     *
     * @param iter
     * @param objToFind
     * @return
     */
    public static long occurrencesOf(final Iterator iter, final Object objToFind) {
        if (iter == null) {
            return 0;
        }

        long occurrences = 0;

        while (iter.hasNext()) {
            if (N.equals(iter.next(), objToFind)) {
                occurrences++;
            }
        }

        return occurrences;
    }

    /**
     *
     * @param iter
     * @return
     */
    public static long count(final Iterator iter) {
        if (iter == null) {
            return 0;
        }

        long res = 0;

        while (iter.hasNext()) {
            iter.next();
            res++;
        }

        return res;
    }

    /**
     *
     * @param 
     * @param iter
     * @param filter
     * @return
     */
    public static  long count(final Iterator iter, final Predicate filter) {
        N.checkArgNotNull(filter);

        if (iter == null) {
            return 0;
        }

        long res = 0;

        while (iter.hasNext()) {
            if (filter.test(iter.next())) {
                res++;
            }
        }

        return res;
    }

    /**
     *
     * @param 
     * @param iter
     * @return
     */
    public static  List toList(final Iterator iter) {
        if (iter == null) {
            return new ArrayList<>();
        }

        final List result = new ArrayList<>();

        while (iter.hasNext()) {
            result.add(iter.next());
        }

        return result;
    }

    /**
     *
     * @param 
     * @param iter
     * @return
     */
    public static  Set toSet(final Iterator iter) {
        if (iter == null) {
            return N.newHashSet();
        }

        final Set result = N.newHashSet();

        while (iter.hasNext()) {
            result.add(iter.next());
        }

        return result;
    }

    /**
     *
     * @param 
     * @param 
     * @param iter
     * @param collectionFactory
     * @return
     */
    public static > C toCollection(final Iterator iter, final Supplier collectionFactory) {
        final C c = collectionFactory.get();

        if (iter == null) {
            return c;
        }

        while (iter.hasNext()) {
            c.add(iter.next());
        }

        return c;
    }

    /**
     *
     * @param 
     * @param  the key type
     * @param 
     * @param iter
     * @param keyMapper
     * @return
     * @throws E the e
     */
    public static  Map toMap(final Iterator iter, final Throwables.Function keyMapper) throws E {
        N.checkArgNotNull(keyMapper);

        if (iter == null) {
            return new HashMap<>();
        }

        final Map result = new HashMap<>();
        T e = null;

        while (iter.hasNext()) {
            e = iter.next();
            result.put(keyMapper.apply(e), e);
        }

        return result;
    }

    /**
     *
     * @param 
     * @param  the key type
     * @param  the value type
     * @param 
     * @param 
     * @param iter
     * @param keyMapper
     * @param valueExtractor
     * @return
     * @throws E the e
     * @throws E2 the e2
     */
    public static  Map toMap(final Iterator iter,
            final Throwables.Function keyMapper, final Throwables.Function valueExtractor) throws E, E2 {
        N.checkArgNotNull(keyMapper);
        N.checkArgNotNull(valueExtractor);

        if (iter == null) {
            return new HashMap<>();
        }

        final Map result = new HashMap<>();
        T e = null;

        while (iter.hasNext()) {
            e = iter.next();
            result.put(keyMapper.apply(e), valueExtractor.apply(e));
        }

        return result;
    }

    /**
     *
     * @param 
     * @param  the key type
     * @param  the value type
     * @param 
     * @param 
     * @param 
     * @param iter
     * @param keyMapper
     * @param valueExtractor
     * @param mapSupplier
     * @return
     * @throws E the e
     * @throws E2 the e2
     */
    public static , E extends Exception, E2 extends Exception> M toMap(final Iterator iter,
            final Throwables.Function keyMapper, final Throwables.Function valueExtractor,
            final Supplier mapSupplier) throws E, E2 {
        N.checkArgNotNull(keyMapper);
        N.checkArgNotNull(valueExtractor);

        if (iter == null) {
            return mapSupplier.get();
        }

        final M result = mapSupplier.get();
        T e = null;

        while (iter.hasNext()) {
            e = iter.next();
            result.put(keyMapper.apply(e), valueExtractor.apply(e));
        }

        return result;
    }

    /**
     *
     * @param 
     * @param 
     * @param iter
     * @param action
     * @throws E the e
     */
    public static  void forEach(final Iterator iter, final Throwables.Consumer action) throws E {
        N.checkArgNotNull(action);

        if (iter == null) {
            return;
        }

        while (iter.hasNext()) {
            action.accept(iter.next());
        }
    }

    /**
     *
     * @param 
     * @param 
     * @param iter
     * @param action
     * @throws E the e
     */
    public static  void forEach(final Iterator iter, final Throwables.IndexedConsumer action) throws E {
        N.checkArgNotNull(action);

        if (iter == null) {
            return;
        }

        int idx = 0;

        while (iter.hasNext()) {
            action.accept(idx++, iter.next());
        }
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param 
     * @param iter
     * @param flatMapper
     * @param action
     * @throws E the e
     * @throws E2 the e2
     */
    public static  void forEach(final Iterator iter,
            final Throwables.Function, E> flatMapper, final Throwables.BiConsumer action) throws E, E2 {
        N.checkArgNotNull(flatMapper);
        N.checkArgNotNull(action);

        if (iter == null) {
            return;
        }

        T e = null;

        while (iter.hasNext()) {
            e = iter.next();

            final Collection c2 = flatMapper.apply(e);

            if (N.notNullOrEmpty(c2)) {
                for (U u : c2) {
                    action.accept(e, u);
                }
            }
        }
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param 
     * @param 
     * @param 
     * @param iter
     * @param flatMapper
     * @param flatMapper2
     * @param action
     * @throws E the e
     * @throws E2 the e2
     * @throws E3 the e3
     */
    public static  void forEach(final Iterator iter,
            final Throwables.Function, E> flatMapper, final Throwables.Function, E2> flatMapper2,
            final Throwables.TriConsumer action) throws E, E2, E3 {
        N.checkArgNotNull(flatMapper);
        N.checkArgNotNull(flatMapper2);
        N.checkArgNotNull(action);

        if (iter == null) {
            return;
        }

        T e = null;

        while (iter.hasNext()) {
            e = iter.next();

            final Collection c2 = flatMapper.apply(e);

            if (N.notNullOrEmpty(c2)) {
                for (T2 t2 : c2) {
                    final Collection c3 = flatMapper2.apply(t2);

                    if (N.notNullOrEmpty(c3)) {
                        for (T3 t3 : c3) {
                            action.accept(e, t2, t3);
                        }
                    }
                }
            }
        }
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param a
     * @param b
     * @param action
     * @throws E the e
     */
    public static  void forEach(final Iterator a, final Iterator b, final Throwables.BiConsumer action)
            throws E {
        N.checkArgNotNull(action);

        if (a == null || b == null) {
            return;
        }

        while (a.hasNext() && b.hasNext()) {
            action.accept(a.next(), b.next());
        }
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param 
     * @param a
     * @param b
     * @param c
     * @param action
     * @throws E the e
     */
    public static  void forEach(final Iterator a, final Iterator b, final Iterator c,
            final Throwables.TriConsumer action) throws E {
        N.checkArgNotNull(action);

        if (a == null || b == null || c == null) {
            return;
        }

        while (a.hasNext() && b.hasNext() && c.hasNext()) {
            action.accept(a.next(), b.next(), c.next());
        }
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param a
     * @param b
     * @param valueForNoneA
     * @param valueForNoneB
     * @param action
     * @throws E the e
     */
    public static  void forEach(final Iterator a, final Iterator b, final A valueForNoneA, final B valueForNoneB,
            final Throwables.BiConsumer action) throws E {
        N.checkArgNotNull(action);

        final Iterator iterA = a == null ? ObjIterator. empty() : a;
        final Iterator iterB = b == null ? ObjIterator. empty() : b;

        A nextA = null;
        B nextB = null;

        while (iterA.hasNext() || iterB.hasNext()) {
            nextA = iterA.hasNext() ? iterA.next() : valueForNoneA;
            nextB = iterB.hasNext() ? iterB.next() : valueForNoneB;

            action.accept(nextA, nextB);
        }
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param 
     * @param a
     * @param b
     * @param c
     * @param valueForNoneA
     * @param valueForNoneB
     * @param valueForNoneC
     * @param action
     * @throws E the e
     */
    public static  void forEach(final Iterator a, final Iterator b, final Iterator c, final A valueForNoneA,
            final B valueForNoneB, final C valueForNoneC, final Throwables.TriConsumer action) throws E {
        N.checkArgNotNull(action);

        final Iterator iterA = a == null ? ObjIterator. empty() : a;
        final Iterator iterB = b == null ? ObjIterator. empty() : b;
        final Iterator iterC = b == null ? ObjIterator. empty() : c;

        A nextA = null;
        B nextB = null;
        C nextC = null;

        while (iterA.hasNext() || iterB.hasNext() || iterC.hasNext()) {
            nextA = iterA.hasNext() ? iterA.next() : valueForNoneA;
            nextB = iterB.hasNext() ? iterB.next() : valueForNoneB;
            nextC = iterC.hasNext() ? iterC.next() : valueForNoneC;

            action.accept(nextA, nextB, nextC);
        }
    }

    /**
     * For each non null.
     *
     * @param 
     * @param 
     * @param 
     * @param 
     * @param iter
     * @param flatMapper
     * @param action
     * @throws E the e
     * @throws E2 the e2
     */
    public static  void forEachNonNull(final Iterator iter,
            final Throwables.Function, E> flatMapper, final Throwables.BiConsumer action) throws E, E2 {
        N.checkArgNotNull(flatMapper);
        N.checkArgNotNull(action);

        if (iter == null) {
            return;
        }

        T e = null;

        while (iter.hasNext()) {
            e = iter.next();

            if (e != null) {
                final Collection c2 = flatMapper.apply(e);

                if (N.notNullOrEmpty(c2)) {
                    for (U u : c2) {
                        if (u != null) {
                            action.accept(e, u);
                        }
                    }
                }
            }
        }
    }

    /**
     * For each non null.
     *
     * @param 
     * @param 
     * @param 
     * @param 
     * @param 
     * @param 
     * @param iter
     * @param flatMapper
     * @param flatMapper2
     * @param action
     * @throws E the e
     * @throws E2 the e2
     * @throws E3 the e3
     */
    public static  void forEachNonNull(final Iterator iter,
            final Throwables.Function, E> flatMapper, final Throwables.Function, E2> flatMapper2,
            final Throwables.TriConsumer action) throws E, E2, E3 {
        N.checkArgNotNull(flatMapper);
        N.checkArgNotNull(flatMapper2);
        N.checkArgNotNull(action);

        if (iter == null) {
            return;
        }

        T e = null;

        while (iter.hasNext()) {
            e = iter.next();

            if (e != null) {
                final Collection c2 = flatMapper.apply(e);

                if (N.notNullOrEmpty(c2)) {
                    for (T2 t2 : c2) {
                        if (t2 != null) {
                            final Collection c3 = flatMapper2.apply(t2);

                            if (N.notNullOrEmpty(c3)) {
                                for (T3 t3 : c3) {
                                    if (t3 != null) {
                                        action.accept(e, t2, t3);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * For each pair.
     *
     * @param 
     * @param 
     * @param iter
     * @param action
     * @throws E the e
     */
    public static  void forEachPair(final Iterator iter, final Throwables.BiConsumer action) throws E {
        forEachPair(iter, action, 1);
    }

    /**
     * For each pair.
     *
     * @param 
     * @param 
     * @param iter
     * @param action
     * @param increment
     * @throws E the e
     */
    public static  void forEachPair(final Iterator iter, final Throwables.BiConsumer action, final int increment)
            throws E {
        N.checkArgNotNull(action);
        final int windowSize = 2;
        N.checkArgument(windowSize > 0 && increment > 0, "windowSize=%s and increment=%s must be bigger than 0", windowSize, increment);

        if (iter == null) {
            return;
        }

        boolean isFirst = true;
        T prev = null;

        while (iter.hasNext()) {
            if (increment > windowSize && isFirst == false) {
                int skipNum = increment - windowSize;

                while (skipNum-- > 0 && iter.hasNext()) {
                    iter.next();
                }

                if (iter.hasNext() == false) {
                    break;
                }
            }

            if (increment == 1) {
                action.accept(isFirst ? iter.next() : prev, (prev = (iter.hasNext() ? iter.next() : null)));
            } else {
                action.accept(iter.next(), iter.hasNext() ? iter.next() : null);
            }

            isFirst = false;
        }
    }

    /**
     * For each triple.
     *
     * @param 
     * @param 
     * @param iter
     * @param action
     * @throws E the e
     */
    public static  void forEachTriple(final Iterator iter, final Throwables.TriConsumer action)
            throws E {
        forEachTriple(iter, action, 1);
    }

    /**
     * For each triple.
     *
     * @param 
     * @param 
     * @param iter
     * @param action
     * @param increment
     * @throws E the e
     */
    public static  void forEachTriple(final Iterator iter, final Throwables.TriConsumer action,
            final int increment) throws E {
        N.checkArgNotNull(action);
        final int windowSize = 3;
        N.checkArgument(windowSize > 0 && increment > 0, "windowSize=%s and increment=%s must be bigger than 0", windowSize, increment);

        if (iter == null) {
            return;
        }

        boolean isFirst = true;
        T prev = null;
        T prev2 = null;

        while (iter.hasNext()) {
            if (increment > windowSize && isFirst == false) {
                int skipNum = increment - windowSize;

                while (skipNum-- > 0 && iter.hasNext()) {
                    iter.next();
                }

                if (iter.hasNext() == false) {
                    break;
                }
            }

            if (increment == 1) {
                action.accept(isFirst ? iter.next() : prev2, (prev2 = (isFirst ? (iter.hasNext() ? iter.next() : null) : prev)),
                        (prev = (iter.hasNext() ? iter.next() : null)));
            } else if (increment == 2) {
                action.accept(isFirst ? iter.next() : prev, iter.hasNext() ? iter.next() : null, (prev = (iter.hasNext() ? iter.next() : null)));
            } else {
                action.accept(iter.next(), iter.hasNext() ? iter.next() : null, iter.hasNext() ? iter.next() : null);
            }

            isFirst = false;
        }
    }

    /**
     *
     * @param 
     * @param e
     * @param n
     * @return
     */
    public static  ObjIterator repeat(final T e, final int n) {
        N.checkArgument(n >= 0, "'n' can't be negative: %s", n);

        if (n == 0) {
            return ObjIterator.empty();
        }

        return new ObjIterator() {
            private int cnt = n;

            @Override
            public boolean hasNext() {
                return cnt > 0;
            }

            @Override
            public T next() {
                if (cnt <= 0) {
                    throw new NoSuchElementException();
                }

                cnt--;
                return e;
            }
        };
    }

    /**
     *
     * @param 
     * @param c
     * @param n
     * @return
     */
    public static  ObjIterator repeatEach(final Collection c, final int n) {
        N.checkArgument(n >= 0, "'n' can't be negative: %s", n);

        if (n == 0 || N.isNullOrEmpty(c)) {
            return ObjIterator.empty();
        }

        return new ObjIterator() {
            private Iterator iter = null;
            private T next = null;
            private int cnt = n;

            @Override
            public boolean hasNext() {
                return cnt > 0 || (iter != null && iter.hasNext());
            }

            @Override
            public T next() {
                if (hasNext() == false) {
                    throw new NoSuchElementException();
                }

                if (iter == null) {
                    iter = c.iterator();
                    next = iter.next();
                } else if (cnt <= 0) {
                    next = iter.next();
                    cnt = n;
                }

                cnt--;

                return next;
            }
        };
    }

    /**
     *
     * @param 
     * @param c
     * @param n
     * @return
     */
    public static  ObjIterator repeatAll(final Collection c, final int n) {
        N.checkArgument(n >= 0, "'n' can't be negative: %s", n);

        if (n == 0 || N.isNullOrEmpty(c)) {
            return ObjIterator.empty();
        }

        return new ObjIterator() {
            private Iterator iter = null;
            private int cnt = n;

            @Override
            public boolean hasNext() {
                return cnt > 0 || (iter != null && iter.hasNext());
            }

            @Override
            public T next() {
                if (hasNext() == false) {
                    throw new NoSuchElementException();
                }

                if (iter == null || iter.hasNext() == false) {
                    iter = c.iterator();
                    cnt--;
                }

                return iter.next();
            }
        };
    }

    /**
     * Repeat each to size.
     *
     * @param 
     * @param c
     * @param size
     * @return
     */
    public static  ObjIterator repeatEachToSize(final Collection c, final int size) {
        N.checkArgument(size >= 0, "'size' can't be negative: %s", size);
        N.checkArgument(size == 0 || N.notNullOrEmpty(c), "Collection can't be empty or null when size > 0");

        if (N.isNullOrEmpty(c) || size == 0) {
            return ObjIterator.empty();
        }

        return new ObjIterator() {
            private final int n = size / c.size();
            private int mod = size % c.size();

            private Iterator iter = null;
            private T next = null;
            private int cnt = mod-- > 0 ? n + 1 : n;

            @Override
            public boolean hasNext() {
                return cnt > 0 || ((n > 0 || mod > 0) && (iter != null && iter.hasNext()));
            }

            @Override
            public T next() {
                if (hasNext() == false) {
                    throw new NoSuchElementException();
                }

                if (iter == null) {
                    iter = c.iterator();
                    next = iter.next();
                } else if (cnt <= 0) {
                    next = iter.next();
                    cnt = mod-- > 0 ? n + 1 : n;
                }

                cnt--;

                return next;
            }
        };
    }

    /**
     * Repeat all to size.
     *
     * @param 
     * @param c
     * @param size
     * @return
     */
    public static  ObjIterator repeatAllToSize(final Collection c, final int size) {
        N.checkArgument(size >= 0, "'size' can't be negative: %s", size);
        N.checkArgument(size == 0 || N.notNullOrEmpty(c), "Collection can't be empty or null when size > 0");

        if (N.isNullOrEmpty(c) || size == 0) {
            return ObjIterator.empty();
        }

        return new ObjIterator() {
            private Iterator iter = null;
            private int cnt = size;

            @Override
            public boolean hasNext() {
                return cnt > 0;
            }

            @Override
            public T next() {
                if (hasNext() == false) {
                    throw new NoSuchElementException();
                }

                if (iter == null || iter.hasNext() == false) {
                    iter = c.iterator();
                }

                cnt--;

                return iter.next();
            }
        };
    }

    /**
     *
     * @param 
     * @param a
     * @return
     */
    @SafeVarargs
    public static  ObjIterator concat(final T[]... a) {
        if (N.isNullOrEmpty(a)) {
            return ObjIterator.empty();
        }

        final List> list = new ArrayList<>(a.length);

        for (T[] e : a) {
            if (N.notNullOrEmpty(e)) {
                list.add(ObjIterator.of(e));
            }
        }

        return concat(list);
    }

    /**
     *
     * @param 
     * @param a
     * @return
     */
    @SafeVarargs
    public static  ObjIterator concat(final Collection... a) {
        if (N.isNullOrEmpty(a)) {
            return ObjIterator.empty();
        }

        final List> list = new ArrayList<>(a.length);

        for (Collection e : a) {
            if (N.notNullOrEmpty(e)) {
                list.add(e.iterator());
            }
        }

        return concat(list);
    }

    /**
     *
     * @param 
     * @param c
     * @return
     */
    public static  ObjIterator concatt(final Collection> c) {
        if (N.isNullOrEmpty(c)) {
            return ObjIterator.empty();
        }

        return new ObjIterator() {
            private final Iterator> iter = c.iterator();
            private Iterator cur;

            @Override
            public boolean hasNext() {
                while ((cur == null || cur.hasNext() == false) && iter.hasNext()) {
                    final Collection c = iter.next();
                    cur = N.isNullOrEmpty(c) ? null : c.iterator();
                }

                return cur != null && cur.hasNext();
            }

            @Override
            public T next() {
                if ((cur == null || cur.hasNext() == false) && hasNext() == false) {
                    throw new NoSuchElementException();
                }

                return cur.next();
            }
        };
    }

    /**
     *
     * @param 
     * @param a
     * @return
     */
    @SafeVarargs
    public static  ObjIterator concat(final Iterator... a) {
        if (N.isNullOrEmpty(a)) {
            return ObjIterator.empty();
        }

        return concat(Array.asList(a));
    }

    /**
     *
     * @param 
     * @param c
     * @return
     */
    public static  ObjIterator concat(final Collection> c) {
        if (N.isNullOrEmpty(c)) {
            return ObjIterator.empty();
        }

        return new ObjIterator() {
            private final Iterator> iter = c.iterator();
            private Iterator cur;

            @Override
            public boolean hasNext() {
                while ((cur == null || cur.hasNext() == false) && iter.hasNext()) {
                    cur = iter.next();
                }

                return cur != null && cur.hasNext();
            }

            @Override
            public T next() {
                if ((cur == null || cur.hasNext() == false) && hasNext() == false) {
                    throw new NoSuchElementException();
                }

                return cur.next();
            }
        };
    }

    /**
     *
     * @param 
     * @param 
     * @param a
     * @return
     */
    @SafeVarargs
    public static  BiIterator concat(final BiIterator... a) {
        if (N.isNullOrEmpty(a)) {
            return BiIterator.empty();
        }

        return new BiIterator() {
            private final Iterator> iter = Arrays.asList(a).iterator();
            private BiIterator cur;

            @Override
            public boolean hasNext() {
                while ((cur == null || cur.hasNext() == false) && iter.hasNext()) {
                    cur = iter.next();
                }

                return cur != null && cur.hasNext();
            }

            @Override
            public Pair next() {
                if ((cur == null || cur.hasNext() == false) && hasNext() == false) {
                    throw new NoSuchElementException();
                }

                return cur.next();
            }

            @Override
            public  void forEachRemaining(final Throwables.BiConsumer action) throws E {
                while (hasNext()) {
                    cur.forEachRemaining(action);
                }
            }

            @Override
            public  ObjIterator map(final BiFunction mapper) {
                N.checkArgNotNull(mapper);

                return new ObjIterator() {
                    private ObjIterator mappedIter = null;

                    @Override
                    public boolean hasNext() {
                        if (mappedIter == null || mappedIter.hasNext() == false) {
                            while ((cur == null || cur.hasNext() == false) && iter.hasNext()) {
                                cur = iter.next();
                            }

                            if (cur != null) {
                                mappedIter = cur.map(mapper);
                            }
                        }

                        return mappedIter != null && mappedIter.hasNext();
                    }

                    @Override
                    public R next() {
                        if (hasNext() == false) {
                            throw new NoSuchElementException();
                        }

                        return mappedIter.next();
                    }
                };
            }
        };
    }

    /**
     *
     * @param 
     * @param a
     * @param b
     * @param nextSelector
     * @return
     */
    public static  ObjIterator merge(final Collection a, final Collection b,
            final BiFunction nextSelector) {
        final Iterator iterA = N.isNullOrEmpty(a) ? ObjIterator. empty() : (Iterator) a.iterator();
        final Iterator iterB = N.isNullOrEmpty(b) ? ObjIterator. empty() : (Iterator) b.iterator();

        return merge(iterA, iterB, nextSelector);

    }

    /**
     *
     * @param 
     * @param a
     * @param b
     * @param nextSelector
     * @return
     */
    public static  ObjIterator merge(final Iterator a, final Iterator b,
            final BiFunction nextSelector) {
        N.checkArgNotNull(nextSelector);

        return new ObjIterator() {
            private final Iterator iterA = a == null ? ObjIterator. empty() : a;
            private final Iterator iterB = b == null ? ObjIterator. empty() : b;
            private T nextA = null;
            private T nextB = null;
            private boolean hasNextA = false;
            private boolean hasNextB = false;

            @Override
            public boolean hasNext() {
                return hasNextA || hasNextB || iterA.hasNext() || iterB.hasNext();
            }

            @Override
            public T next() {
                if (hasNextA) {
                    if (iterB.hasNext()) {
                        if (nextSelector.apply(nextA, (nextB = iterB.next())) == Nth.FIRST) {
                            hasNextA = false;
                            hasNextB = true;
                            return nextA;
                        } else {
                            return nextB;
                        }
                    } else {
                        hasNextA = false;
                        return nextA;
                    }
                } else if (hasNextB) {
                    if (iterA.hasNext()) {
                        if (nextSelector.apply((nextA = iterA.next()), nextB) == Nth.FIRST) {
                            return nextA;
                        } else {
                            hasNextA = true;
                            hasNextB = false;
                            return nextB;
                        }
                    } else {
                        hasNextB = false;
                        return nextB;
                    }
                } else if (iterA.hasNext()) {
                    if (iterB.hasNext()) {
                        if (nextSelector.apply((nextA = iterA.next()), (nextB = iterB.next())) == Nth.FIRST) {
                            hasNextB = true;
                            return nextA;
                        } else {
                            hasNextA = true;
                            return nextB;
                        }
                    } else {
                        return iterA.next();
                    }
                } else {
                    return iterB.next();
                }
            }
        };
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param a
     * @param b
     * @param zipFunction
     * @return
     */
    public static  ObjIterator zip(final Collection a, final Collection b, final BiFunction zipFunction) {
        final Iterator iterA = N.isNullOrEmpty(a) ? ObjIterator. empty() : a.iterator();
        final Iterator iterB = N.isNullOrEmpty(b) ? ObjIterator. empty() : b.iterator();

        return zip(iterA, iterB, zipFunction);
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param a
     * @param b
     * @param zipFunction
     * @return
     */
    public static  ObjIterator zip(final Iterator a, final Iterator b, final BiFunction zipFunction) {
        N.checkArgNotNull(zipFunction);

        return new ObjIterator() {
            private final Iterator iterA = a == null ? ObjIterator. empty() : a;
            private final Iterator iterB = b == null ? ObjIterator. empty() : b;

            @Override
            public boolean hasNext() {
                return iterA.hasNext() && iterB.hasNext();
            }

            @Override
            public R next() {
                return zipFunction.apply(iterA.next(), iterB.next());
            }
        };
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param 
     * @param a
     * @param b
     * @param c
     * @param zipFunction
     * @return
     */
    public static  ObjIterator zip(final Collection a, final Collection b, final Collection c,
            final TriFunction zipFunction) {
        final Iterator iterA = N.isNullOrEmpty(a) ? ObjIterator. empty() : a.iterator();
        final Iterator iterB = N.isNullOrEmpty(b) ? ObjIterator. empty() : b.iterator();
        final Iterator iterC = N.isNullOrEmpty(c) ? ObjIterator. empty() : c.iterator();

        return zip(iterA, iterB, iterC, zipFunction);
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param 
     * @param a
     * @param b
     * @param c
     * @param zipFunction
     * @return
     */
    public static  ObjIterator zip(final Iterator a, final Iterator b, final Iterator c,
            final TriFunction zipFunction) {
        N.checkArgNotNull(zipFunction);

        return new ObjIterator() {
            private final Iterator iterA = a == null ? ObjIterator. empty() : a;
            private final Iterator iterB = b == null ? ObjIterator. empty() : b;
            private final Iterator iterC = c == null ? ObjIterator. empty() : c;

            @Override
            public boolean hasNext() {
                return iterA.hasNext() && iterB.hasNext() && iterC.hasNext();
            }

            @Override
            public R next() {
                return zipFunction.apply(iterA.next(), iterB.next(), iterC.next());
            }
        };
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param a
     * @param b
     * @param valueForNoneA
     * @param valueForNoneB
     * @param zipFunction
     * @return
     */
    public static  ObjIterator zip(final Collection a, final Collection b, final A valueForNoneA, final B valueForNoneB,
            final BiFunction zipFunction) {
        final Iterator iterA = N.isNullOrEmpty(a) ? ObjIterator. empty() : a.iterator();
        final Iterator iterB = N.isNullOrEmpty(b) ? ObjIterator. empty() : b.iterator();

        return zip(iterA, iterB, valueForNoneA, valueForNoneB, zipFunction);
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param a
     * @param b
     * @param valueForNoneA
     * @param valueForNoneB
     * @param zipFunction
     * @return
     */
    public static  ObjIterator zip(final Iterator a, final Iterator b, final A valueForNoneA, final B valueForNoneB,
            final BiFunction zipFunction) {
        N.checkArgNotNull(zipFunction);

        return new ObjIterator() {
            private final Iterator iterA = a == null ? ObjIterator. empty() : a;
            private final Iterator iterB = b == null ? ObjIterator. empty() : b;

            @Override
            public boolean hasNext() {
                return iterA.hasNext() || iterB.hasNext();
            }

            @Override
            public R next() {
                return zipFunction.apply(iterA.hasNext() ? iterA.next() : valueForNoneA, iterB.hasNext() ? iterB.next() : valueForNoneB);
            }
        };
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param 
     * @param a
     * @param b
     * @param c
     * @param valueForNoneA
     * @param valueForNoneB
     * @param valueForNoneC
     * @param zipFunction
     * @return
     */
    public static  ObjIterator zip(final Collection a, final Collection b, final Collection c, final A valueForNoneA,
            final B valueForNoneB, final C valueForNoneC, final TriFunction zipFunction) {
        final Iterator iterA = N.isNullOrEmpty(a) ? ObjIterator. empty() : a.iterator();
        final Iterator iterB = N.isNullOrEmpty(b) ? ObjIterator. empty() : b.iterator();
        final Iterator iterC = N.isNullOrEmpty(c) ? ObjIterator. empty() : c.iterator();

        return zip(iterA, iterB, iterC, valueForNoneA, valueForNoneB, valueForNoneC, zipFunction);
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param 
     * @param a
     * @param b
     * @param c
     * @param valueForNoneA
     * @param valueForNoneB
     * @param valueForNoneC
     * @param zipFunction
     * @return
     */
    public static  ObjIterator zip(final Iterator a, final Iterator b, final Iterator c, final A valueForNoneA, final B valueForNoneB,
            final C valueForNoneC, final TriFunction zipFunction) {
        return new ObjIterator() {
            private final Iterator iterA = a == null ? ObjIterator. empty() : a;
            private final Iterator iterB = b == null ? ObjIterator. empty() : b;
            private final Iterator iterC = c == null ? ObjIterator. empty() : c;

            @Override
            public boolean hasNext() {
                return iterA.hasNext() || iterB.hasNext() || iterC.hasNext();
            }

            @Override
            public R next() {
                return zipFunction.apply(iterA.hasNext() ? iterA.next() : valueForNoneA, iterB.hasNext() ? iterB.next() : valueForNoneB,
                        iterC.hasNext() ? iterC.next() : valueForNoneC);
            }
        };
    }

    /**
     *
     * @param 
     * @param 
     * @param 
     * @param iter
     * @param unzip the second parameter is an output parameter.
     * @return
     */
    public static  BiIterator unzip(final Iterator iter, final BiConsumer> unzip) {
        return BiIterator.unzip(iter, unzip);
    }

    /**
     *
     * @param 
     * @param iter
     * @param chunkSize the desired size of each sub sequence (the last may be smaller).
     * @return
     */
    public static  ObjIterator> split(final Iterator iter, final int chunkSize) {
        N.checkArgument(chunkSize > 0, "'chunkSize' must be greater than 0, can't be: %s", chunkSize);

        if (iter == null) {
            return ObjIterator.empty();
        }

        return new ObjIterator>() {
            private final Iterator iterator = iter;

            @Override
            public boolean hasNext() {
                return iterator.hasNext();
            }

            @Override
            public List next() {
                if (hasNext() == false) {
                    throw new NoSuchElementException();
                }

                final List next = new ArrayList<>(chunkSize);

                for (int i = 0; i < chunkSize && iterator.hasNext(); i++) {
                    next.add(iterator.next());
                }

                return next;
            }
        };
    }

    /**
     *
     * @param 
     * @param iter
     * @param index
     * @return
     */
    public static  Nullable get(final Iterator iter, int index) {
        N.checkArgNotNegative(index, "index");

        if (iter == null) {
            return Nullable.empty();
        }

        while (iter.hasNext()) {
            if (index-- == 0) {
                return Nullable.of(iter.next());
            } else {
                iter.next();
            }
        }

        return Nullable.empty();
    }

    /**
     *
     * @param 
     * @param iter
     * @return
     */
    public static  Nullable first(final Iterator iter) {
        return iter != null && iter.hasNext() ? Nullable.of(iter.next()) : Nullable. empty();
    }

    /**
     * First non null.
     *
     * @param 
     * @param iter
     * @return
     */
    public static  Optional firstNonNull(final Iterator iter) {
        if (iter == null) {
            return Optional.empty();
        }

        T e = null;

        while (iter.hasNext()) {
            if ((e = iter.next()) != null) {
                return Optional.of(e);
            }
        }

        return Optional.empty();
    }

    /**
     *
     * @param 
     * @param iter
     * @return
     */
    public static  Nullable last(final Iterator iter) {
        if (iter == null || iter.hasNext() == false) {
            return Nullable.empty();
        }

        T e = null;

        while (iter.hasNext()) {
            e = iter.next();
        }

        return Nullable.of(e);
    }

    /**
     * Last non null.
     *
     * @param 
     * @param iter
     * @return
     */
    public static  Optional lastNonNull(final Iterator iter) {
        if (iter == null) {
            return Optional.empty();
        }

        T e = null;
        T lastNonNull = null;

        while (iter.hasNext()) {
            if ((e = iter.next()) != null) {
                lastNonNull = e;
            }
        }

        return Optional.ofNullable(lastNonNull);
    }

    /**
     * Note: copied from Google Guava under Apache license v2
     * 
* Calls {@code next()} on {@code iterator}, either {@code numberToAdvance} times * or until {@code hasNext()} returns {@code false}, whichever comes first. * * @param iterator * @param numberToAdvance * @return */ public static long advance(Iterator iterator, long numberToAdvance) { N.checkArgNotNegative(numberToAdvance, "numberToAdvance"); long i; for (i = 0; i < numberToAdvance && iterator.hasNext(); i++) { iterator.next(); } return i; } /** * Calls {@code next()} on {@code iterator}, either {@code n} times * or until {@code hasNext()} returns {@code false}, whichever comes first. * * This is a lazy evaluation operation. The {@code skip} action is only triggered when {@code Iterator.hasNext()} or {@code Iterator.next()} is called. * * @param * @param iter * @param n * @return */ public static ObjIterator skip(final Iterator iter, final long n) { N.checkArgNotNegative(n, "n"); if (iter == null || n == 0) { return ObjIterator.of(iter); } return new ObjIterator() { private boolean skipped = false; @Override public boolean hasNext() { if (skipped == false) { skip(); } return iter.hasNext(); } @Override public T next() { if (hasNext() == false) { throw new NoSuchElementException(); } return iter.next(); } private void skip() { long idx = 0; while (idx++ < n && iter.hasNext()) { iter.next(); } skipped = true; } }; } /** * Returns a new {@code Iterator}. * * @param * @param iter * @param count * @return */ public static ObjIterator limit(final Iterator iter, final long count) { N.checkArgNotNegative(count, "count"); if (iter == null || count == 0) { return ObjIterator.empty(); } return new ObjIterator() { private long cnt = count; @Override public boolean hasNext() { return cnt > 0 && iter.hasNext(); } @Override public T next() { if (hasNext() == false) { throw new NoSuchElementException(); } cnt--; return iter.next(); } }; } /** * Calls {@code next()} on {@code iterator}, either {@code offset} times * or until {@code hasNext()} returns {@code false}, whichever comes first. * * This is a lazy evaluation operation. The {@code skip} action is only triggered when {@code Iterator.hasNext()} or {@code Iterator.next()} is called. * * * @param * @param iter * @param offset * @param count * @return */ public static ObjIterator limit(final Iterator iter, final long offset, final long count) { N.checkArgNotNegative(count, "offset"); N.checkArgNotNegative(count, "count"); if (iter == null) { return ObjIterator.empty(); } return new ObjIterator() { private long cnt = count; private boolean skipped = false; @Override public boolean hasNext() { if (skipped == false) { skip(); } return cnt > 0 && iter.hasNext(); } @Override public T next() { if (hasNext() == false) { throw new NoSuchElementException(); } cnt--; return iter.next(); } private void skip() { long idx = 0; while (idx++ < offset && iter.hasNext()) { iter.next(); } skipped = true; } }; } /** * Returns a new {@code ObjIterator} with {@code null} elements removed. * * @param * @param iter * @return */ public static ObjIterator skipNull(final Iterator iter) { if (iter == null) { return ObjIterator.empty(); } return new ObjIterator() { private final Iterator iterator = iter; private T next; @Override public boolean hasNext() { if (next == null && iterator.hasNext()) { next = iterator.next(); if (next == null) { while (iterator.hasNext()) { next = iterator.next(); if (next != null) { break; } } } } return next != null; } @Override public T next() { if (next == null && hasNext() == false) { throw new NoSuchElementException(); } final T result = next; next = null; return result; } }; } /** * Gets the only element. * * @param * @param iter * @return throws DuplicatedResultException if there are more than one elements in the specified {@code iter}. * @throws DuplicatedResultException the duplicated result exception */ public static Nullable getOnlyElement(final Iterator iter) throws DuplicatedResultException { if (iter == null) { return Nullable.empty(); } final T first = iter.next(); if (iter.hasNext()) { throw new DuplicatedResultException("Expected at most one element but was: [" + StringUtil.concat(first, ", ", iter.next(), "...]")); } return Nullable.of(first); } /** * * @param * @param iter * @param filter * @return */ public static ObjIterator filter(final Iterator iter, final Predicate filter) { N.checkArgNotNull(filter, "filter"); if (iter == null) { return ObjIterator.empty(); } return new ObjIterator() { private final T NONE = (T) N.NULL_MASK; private T next = NONE; private T tmp = null; @Override public boolean hasNext() { if (next == NONE) { while (iter.hasNext()) { tmp = iter.next(); if (filter.test(tmp)) { next = tmp; break; } } } return next != NONE; } @Override public T next() { if (hasNext() == false) { throw new NoSuchElementException(); } tmp = next; next = NONE; return tmp; } }; } /** * * @param * @param * @param iter * @param mapper * @return */ public static ObjIterator map(final Iterator iter, final Function mapper) { N.checkArgNotNull(mapper, "mapper"); if (iter == null) { return ObjIterator.empty(); } return new ObjIterator() { @Override public boolean hasNext() { return iter.hasNext(); } @Override public U next() { return mapper.apply(iter.next()); } }; } /** * * @param * @param * @param iter * @param mapper * @return */ public static ObjIterator flatMap(final Iterator iter, final Function> mapper) { N.checkArgNotNull(mapper, "mapper"); if (iter == null) { return ObjIterator.empty(); } return new ObjIterator() { private Collection c = null; private Iterator cur = null; @Override public boolean hasNext() { if (cur == null || cur.hasNext() == false) { while (iter.hasNext()) { c = mapper.apply(iter.next()); cur = c == null || c.size() == 0 ? null : c.iterator(); if (cur != null && cur.hasNext()) { break; } } } return cur != null && cur.hasNext(); } @Override public U next() { if (hasNext() == false) { throw new NoSuchElementException(); } return cur.next(); } }; } /** * * @param * @param * @param init * @param hasNext * @param supplier * @return */ public static ObjIterator generate(final U init, final Predicate hasNext, final Function supplier) { N.checkArgNotNull(hasNext); N.checkArgNotNull(supplier); return new ObjIterator() { @Override public boolean hasNext() { return hasNext.test(init); } @Override public T next() { return supplier.apply(init); } }; } /** * * @param * @param * @param init * @param hasNext * @param supplier * @return */ public static ObjIterator generate(final U init, final BiPredicate hasNext, final BiFunction supplier) { N.checkArgNotNull(hasNext); N.checkArgNotNull(supplier); return new ObjIterator() { private T prev = null; @Override public boolean hasNext() { return hasNext.test(init, prev); } @Override public T next() { return (prev = supplier.apply(init, prev)); } }; } }