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

nl.vpro.util.MaxOffsetIterator Maven / Gradle / Ivy

There is a newer version: 5.3.1
Show newest version
package nl.vpro.util;

import lombok.Getter;
import lombok.Singular;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.function.Predicate;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.meeuw.functional.Predicates;

import com.google.common.collect.PeekingIterator;

/**
 * An iterator implementing offset and max, for another iterator.
 *
 * @author Michiel Meeuwissen
 * @since 3.1
 */
@SuppressWarnings("UnusedReturnValue")
@Slf4j
public class MaxOffsetIterator implements CloseablePeekingIterator {

    protected final CloseableIterator wrapped;

    protected PeekingIterator peekingWrapped;

    /**
     * The maximal value of count. I.e. offset + max;
     */
    protected final long offsetmax;

    final long max;


    @Getter
    private final long offset;

    private final Predicate countPredicate;

    /**
     * The count of the next element. First value will be the supplied value of offset.
     */
    protected long count = 0;

    protected Boolean hasNext = null;

    protected T next;

    private RuntimeException exception;

    private Runnable callback;

    public MaxOffsetIterator(Iterator wrapped, Number max, boolean countNulls) {
        this(wrapped, max, 0L, countNulls);
    }

    public MaxOffsetIterator(Iterator wrapped, Number max) {
        this(wrapped, max, 0L, true);
    }

    public MaxOffsetIterator(Iterator wrapped, Number max, Number offset) {
        this(wrapped, max, offset, true);
    }

    public MaxOffsetIterator(Iterator wrapped, Number max, Number offset, boolean countNulls) {
        this(wrapped, max, offset, null, countNulls, null, false);
    }

    @lombok.Builder(builderClassName = "Builder")
    protected MaxOffsetIterator(
        @NonNull Iterator wrapped,
        @Nullable Number max,
        @Nullable Number offset,
        @Nullable Predicate countPredicate,
        boolean countNulls,
        @Nullable @Singular  List callbacks,
        boolean autoClose) {
        //noinspection ConstantConditions
        if (wrapped == null) {
            throw new IllegalArgumentException("Cannot wrap null");
        }
        this.wrapped = CloseableIterator.of(wrapped);
        this.offset = offset == null ? 0L : offset.longValue();
        this.max = max == null ? Long.MAX_VALUE : max.longValue();
        this.offsetmax = max == null ? Long.MAX_VALUE : max.longValue() + this.offset;
        this.countPredicate = effectiveCountPredicate(countPredicate, countNulls);
        this.callback = () -> {
            if (callbacks != null) {
                for (Runnable r : callbacks) {
                    try {
                        r.run();
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                }
            }
        };
        if (autoClose) {
            autoClose();
        }

    }

    protected static   Predicate effectiveCountPredicate(Predicate countPredicate, boolean countNulls) {
        Predicate effective = countPredicate == null ? Predicates.alwaysTrue() : countPredicate;
        if (! countNulls) {
            effective = ((Predicate) Objects::nonNull).and(effective);
        }
        return effective;
    }

    public MaxOffsetIterator callBack(Runnable run) {
        callback = run;
        return this;
    }

    public MaxOffsetIterator autoClose(AutoCloseable... closeables) {
        final Runnable prev = callback;
        callback = () -> {
            try {
                if (prev != null) {
                    prev.run();
                }
            } finally {
                for (AutoCloseable closeable : closeables) {
                    try {
                        closeable.close();
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                }
            }
        };
        return this;
    }

    public MaxOffsetIterator autoClose() {
        final Runnable prev = callback;
        callback = () -> {
            try {
                if (prev != null) {
                    prev.run();
                }
            } finally {
                try {
                    wrapped.close();
                } catch(Exception e){
                    log.error(e.getMessage(), e);
                }
            }
        };
        return this;
    }

    @Override
    public boolean hasNext() {
        return findNext();
    }

    @Override
    public T peek() {
        if (!findNext()) {
            throw new NoSuchElementException();
        }
        if (exception != null) {
            throw exception;
        }
        return next;
    }

    @Override
    public T next() {
        if (!findNext()) {
            throw new NoSuchElementException();
        }
        hasNext = null;
        if (exception != null) {
            throw exception;
        }
        return next;
    }

    protected boolean findNext() {
        if (hasNext == null) {
            hasNext = false;

            while(count < offset && wrapped.hasNext()) {
                T n;
                try {
                    n = wrapped.next();
                } catch(RuntimeException runtimeException) {
                    n = null;
                }
                if (countPredicate.test(n)) {
                    count++;
                }
            }

            if (count < offsetmax && wrapped.hasNext()) {
                try {
                    exception = null;
                    next = wrapped.next();
                } catch (RuntimeException e) {
                    exception = e;
                    next = null;

                }
                if (countPredicate.test(next)) {
                    count++;
                }
                hasNext = true;
            }

            if(!hasNext && callback != null) {
                callback.run();
            }
        }
        return hasNext;
    }


    @Override
    public void remove() {
        wrapped.remove();
    }

    @Override
    public void close() throws Exception {
        wrapped.close();
    }

    @Override
    public String toString() {
        return wrapped + "[" + offset + "," + (max < Long.MAX_VALUE ? max : "") + "]";
    }

    /**
     * Access to the (peeking) wrapped iterator.
     * This may be used to look 'beyond' max, to check what would have been the next one.
     */
    public PeekingIterator peekingWrapped() {
        if (peekingWrapped == null) {
            peekingWrapped = wrapped.peeking();
        }
        return peekingWrapped;
    }

    public static  CountedMaxOffsetIterator.Builder countedBuilder() {
        return CountedMaxOffsetIterator._countedBuilder();
    }
}