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

com.landawn.abacus.util.Observer 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.

The 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
 *
 * https://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.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 *
 * @param 
 */
@com.landawn.abacus.annotation.Immutable
public abstract class Observer implements Immutable {

    private static final Object NONE = ClassUtil.createNullMask();

    private static final Object COMPLETE_FLAG = ClassUtil.createNullMask();

    protected static final double INTERVAL_FACTOR = 3;

    protected static final Runnable EMPTY_ACTION = () -> {
        // Do nothing;
    };

    protected static final Consumer ON_ERROR_MISSING = t -> {
        throw new RuntimeException(t);
    };

    protected static final Executor asyncExecutor;

    static {
        if (IOUtil.IS_PLATFORM_ANDROID) {
            asyncExecutor = AndroidUtil.getThreadPoolExecutor();
        } else {
            final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(//
                    N.max(64, IOUtil.CPU_CORES * 8), // coreThreadPoolSize
                    N.max(128, IOUtil.CPU_CORES * 16), // // maxThreadPoolSize
                    180L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

            asyncExecutor = threadPoolExecutor;

            MoreExecutors.addDelayedShutdownHook(threadPoolExecutor, 120, TimeUnit.SECONDS);
        }
    }

    protected static final ScheduledThreadPoolExecutor schedulerForIntermediateOp = new ScheduledThreadPoolExecutor(
            IOUtil.IS_PLATFORM_ANDROID ? Math.max(8, IOUtil.CPU_CORES) : N.max(64, IOUtil.CPU_CORES * 8));

    protected static final ScheduledThreadPoolExecutor schedulerForObserveOp = new ScheduledThreadPoolExecutor(
            IOUtil.IS_PLATFORM_ANDROID ? Math.max(8, IOUtil.CPU_CORES) : N.max(64, IOUtil.CPU_CORES * 8));

    static {
        //    schedulerForIntermediateOp.setRemoveOnCancelPolicy(true);
        //    schedulerForObserveOp.setRemoveOnCancelPolicy(true);

        MoreExecutors.addDelayedShutdownHook(schedulerForIntermediateOp, 120, TimeUnit.SECONDS);
        MoreExecutors.addDelayedShutdownHook(schedulerForObserveOp, 120, TimeUnit.SECONDS);
    }

    protected final Map, Long> scheduledFutures = new LinkedHashMap<>();

    protected final Dispatcher dispatcher;

    protected boolean hasMore = true;

    protected Observer() {
        this(new Dispatcher<>());
    }

    protected Observer(final Dispatcher dispatcher) {
        this.dispatcher = dispatcher;
    }

    /**
     *
     * @param queue
     */
    @SuppressWarnings("rawtypes")
    public static void complete(final BlockingQueue queue) {
        ((Queue) queue).offer(COMPLETE_FLAG);
    }

    /**
     *
     * @param 
     * @param queue
     * @return
     * @throws IllegalArgumentException
     */
    public static  Observer of(final BlockingQueue queue) throws IllegalArgumentException {
        N.checkArgNotNull(queue, cs.queue);

        return new BlockingQueueObserver<>(queue);
    }

    /**
     *
     * @param 
     * @param c
     * @return
     */
    public static  Observer of(final Collection c) {
        return of(N.isEmpty(c) ? ObjIterator.empty() : c.iterator());
    }

    /**
     *
     * @param 
     * @param iter
     * @return
     * @throws IllegalArgumentException
     */
    public static  Observer of(final Iterator iter) throws IllegalArgumentException {
        N.checkArgNotNull(iter, cs.iterator);

        return new IteratorObserver<>(iter);
    }

    /**
     *
     * @param delayInMillis
     * @return
     * @see RxJava#timer
     */
    public static Observer timer(final long delayInMillis) {
        return timer(delayInMillis, TimeUnit.MILLISECONDS);
    }

    /**
     *
     * @param delay
     * @param unit
     * @return
     * @throws IllegalArgumentException
     * @see RxJava#timer
     */
    public static Observer timer(final long delay, final TimeUnit unit) throws IllegalArgumentException {
        N.checkArgument(delay >= 0, "delay can't be negative");
        N.checkArgNotNull(unit, "Time unit can't be null"); //NOSONAR

        return new TimerObserver<>(delay, unit);
    }

    /**
     *
     * @param periodInMillis
     * @return
     * @see RxJava#interval
     */
    public static Observer interval(final long periodInMillis) {
        return interval(0, periodInMillis, TimeUnit.MILLISECONDS);
    }

    /**
     *
     * @param initialDelayInMillis
     * @param periodInMillis
     * @return
     * @see RxJava#interval
     */
    public static Observer interval(final long initialDelayInMillis, final long periodInMillis) {
        return interval(initialDelayInMillis, periodInMillis, TimeUnit.MILLISECONDS);
    }

    /**
     *
     * @param period
     * @param unit
     * @return
     * @see RxJava#interval
     */
    public static Observer interval(final long period, final TimeUnit unit) {
        return interval(0, period, unit);
    }

    /**
     *
     * @param initialDelay
     * @param period
     * @param unit
     * @return
     * @throws IllegalArgumentException
     * @see RxJava#interval
     */
    public static Observer interval(final long initialDelay, final long period, final TimeUnit unit) throws IllegalArgumentException {
        N.checkArgument(initialDelay >= 0, "initialDelay can't be negative");
        N.checkArgument(period > 0, "period can't be 0 or negative");
        N.checkArgNotNull(unit, "Time unit can't be null");

        return new IntervalObserver<>(initialDelay, period, unit);
    }

    /**
     *
     * @param intervalDurationInMillis
     * @return this instance.
     * @see RxJava#debounce
     */
    public Observer debounce(final long intervalDurationInMillis) {
        return debounce(intervalDurationInMillis, TimeUnit.MILLISECONDS);
    }

    /**
     *
     * @param intervalDuration
     * @param unit
     * @return this instance.
     * @throws IllegalArgumentException
     * @see RxJava#debounce
     */
    public Observer debounce(final long intervalDuration, final TimeUnit unit) throws IllegalArgumentException {
        N.checkArgument(intervalDuration >= 0, "Interval can't be negative");
        N.checkArgNotNull(unit, "Time unit can't be null");

        if (intervalDuration == 0) {
            return this;
        }

        final long intervalDurationInMillis = unit.toMillis(intervalDuration);

        dispatcher.append(new Dispatcher<>() {
            private long prevTimestamp = 0;
            private long lastScheduledTime = 0;

            @Override
            public void onNext(final Object param) {
                synchronized (holder) {
                    final long now = System.currentTimeMillis();

                    if (holder.value() == NONE || now - lastScheduledTime > intervalDurationInMillis * INTERVAL_FACTOR) {
                        holder.setValue(param);
                        prevTimestamp = now;

                        schedule(intervalDuration, unit);
                    } else {
                        holder.setValue(param);
                        prevTimestamp = now;
                    }
                }
            }

            private void schedule(final long delay, final TimeUnit unit) {
                try {
                    schedulerForIntermediateOp.schedule(() -> {
                        final long pastIntervalInMills = System.currentTimeMillis() - prevTimestamp;

                        if (pastIntervalInMills >= intervalDurationInMillis) {
                            Object lastParam = null;

                            synchronized (holder) {
                                lastParam = holder.value();
                                holder.setValue(NONE);
                            }

                            if (lastParam != NONE && downDispatcher != null) {
                                downDispatcher.onNext(lastParam);
                            }
                        } else {
                            schedule(intervalDurationInMillis - pastIntervalInMills, TimeUnit.MILLISECONDS);
                        }
                    }, delay, unit);

                    lastScheduledTime = System.currentTimeMillis();
                } catch (final Exception e) {
                    holder.setValue(NONE);

                    if (downDispatcher != null) {
                        downDispatcher.onError(e);
                    }
                }
            }
        });

        return this;
    }

    /**
     *
     * @param intervalDurationInMillis
     * @return this instance.
     * @see RxJava#throttleFirst
     */
    public Observer throttleFirst(final long intervalDurationInMillis) {
        return throttleFirst(intervalDurationInMillis, TimeUnit.MILLISECONDS);
    }

    /**
     *
     * @param intervalDuration
     * @param unit
     * @return this instance.
     * @throws IllegalArgumentException
     * @see RxJava#throttleFirst
     */
    public Observer throttleFirst(final long intervalDuration, final TimeUnit unit) throws IllegalArgumentException {
        N.checkArgument(intervalDuration >= 0, "Interval can't be negative");
        N.checkArgNotNull(unit, "Time unit can't be null");

        if (intervalDuration == 0) {
            return this;
        }

        final long intervalDurationInMillis = unit.toMillis(intervalDuration);

        dispatcher.append(new Dispatcher<>() {
            private long lastScheduledTime = 0;

            @Override
            public void onNext(final Object param) {
                synchronized (holder) {
                    final long now = System.currentTimeMillis();

                    if (holder.value() == NONE || now - lastScheduledTime > intervalDurationInMillis * INTERVAL_FACTOR) {
                        holder.setValue(param);

                        try {
                            schedulerForIntermediateOp.schedule(() -> {
                                Object firstParam = null;

                                synchronized (holder) {
                                    firstParam = holder.value();
                                    holder.setValue(NONE);
                                }

                                if (firstParam != NONE && downDispatcher != null) {
                                    downDispatcher.onNext(firstParam);
                                }
                            }, intervalDuration, unit);

                            lastScheduledTime = now;
                        } catch (final Exception e) {
                            holder.setValue(NONE);

                            if (downDispatcher != null) {
                                downDispatcher.onError(e);
                            }
                        }
                    }
                }
            }
        });

        return this;
    }

    /**
     *
     * @param intervalDurationInMillis
     * @return this instance.
     * @see RxJava#throttleLast
     */
    public Observer throttleLast(final long intervalDurationInMillis) {
        return throttleLast(intervalDurationInMillis, TimeUnit.MILLISECONDS);
    }

    /**
     *
     * @param intervalDuration
     * @param unit
     * @return this instance.
     * @throws IllegalArgumentException
     * @see RxJava#throttleLast
     */
    public Observer throttleLast(final long intervalDuration, final TimeUnit unit) throws IllegalArgumentException {
        N.checkArgument(intervalDuration >= 0, "Delay can't be negative");
        N.checkArgNotNull(unit, "Time unit can't be null");

        if (intervalDuration == 0) {
            return this;
        }

        final long intervalDurationInMillis = unit.toMillis(intervalDuration);

        dispatcher.append(new Dispatcher<>() {
            private long lastScheduledTime = 0;

            @Override
            public void onNext(final Object param) {
                synchronized (holder) {
                    final long now = System.currentTimeMillis();

                    if (holder.value() == NONE || now - lastScheduledTime > intervalDurationInMillis * INTERVAL_FACTOR) {
                        holder.setValue(param);

                        try {
                            schedulerForIntermediateOp.schedule(() -> {
                                Object lastParam = null;

                                synchronized (holder) {
                                    lastParam = holder.value();
                                    holder.setValue(NONE);
                                }

                                if (lastParam != NONE && downDispatcher != null) {
                                    downDispatcher.onNext(lastParam);
                                }
                            }, intervalDuration, unit);

                            lastScheduledTime = now;
                        } catch (final Exception e) {
                            holder.setValue(NONE);

                            if (downDispatcher != null) {
                                downDispatcher.onError(e);
                            }
                        }
                    } else {
                        holder.setValue(param);
                    }
                }
            }
        });

        return this;
    }

    /**
     *
     * @param delayInMillis
     * @return this instance.
     * @see RxJava#delay
     */
    public Observer delay(final long delayInMillis) {
        return delay(delayInMillis, TimeUnit.MILLISECONDS);
    }

    /**
     *
     * @param delay
     * @param unit
     * @return this instance.
     * @throws IllegalArgumentException
     * @see RxJava#delay
     */
    public Observer delay(final long delay, final TimeUnit unit) throws IllegalArgumentException {
        N.checkArgument(delay >= 0, "Delay can't be negative");
        N.checkArgNotNull(unit, "Time unit can't be null");

        if (delay == 0) {
            return this;
        }

        dispatcher.append(new Dispatcher<>() {
            private final long startTime = System.currentTimeMillis();
            private boolean isDelayed = false;

            @Override
            public void onNext(final Object param) {
                if (!isDelayed) {
                    N.sleepUninterruptibly(unit.toMillis(delay) - (System.currentTimeMillis() - startTime));
                    isDelayed = true;
                }

                if (downDispatcher != null) {
                    downDispatcher.onNext(param);
                }
            }
        });

        return this;
    }

    /**
     *
     * @return this instance.
     * @see RxJava#timeInterval
     */
    public Observer> timeInterval() {
        dispatcher.append(new Dispatcher<>() {
            private long startTime = System.currentTimeMillis();

            @Override
            public synchronized void onNext(final Object param) {
                if (downDispatcher != null) {
                    final long now = System.currentTimeMillis();
                    final long intervalInMillis = now - startTime;
                    startTime = now;

                    downDispatcher.onNext(Timed.of(param, intervalInMillis));
                }
            }
        });

        return (Observer>) this;
    }

    /**
     *
     * @return this instance.
     * @see RxJava#timestamp
     */
    public Observer> timestamp() {
        dispatcher.append(new Dispatcher<>() {
            @Override
            public void onNext(final Object param) {
                if (downDispatcher != null) {
                    downDispatcher.onNext(Timed.of(param, System.currentTimeMillis()));
                }
            }
        });

        return (Observer>) this;
    }

    /**
     *
     * @param n
     * @return
     * @throws IllegalArgumentException
     */
    public Observer skip(final long n) throws IllegalArgumentException {
        N.checkArgNotNegative(n, cs.n);

        if (n > 0) {
            dispatcher.append(new Dispatcher<>() {
                private final AtomicLong counter = new AtomicLong();

                @Override
                public void onNext(final Object param) {
                    if (downDispatcher != null && counter.incrementAndGet() > n) {
                        downDispatcher.onNext(param);
                    }
                }
            });
        }

        return this;
    }

    /**
     *
     * @param maxSize
     * @return
     * @throws IllegalArgumentException
     */
    public Observer limit(final long maxSize) throws IllegalArgumentException {
        N.checkArgNotNegative(maxSize, cs.maxSize);

        dispatcher.append(new Dispatcher<>() {
            private final AtomicLong counter = new AtomicLong();

            @Override
            public void onNext(final Object param) {
                if (downDispatcher != null && counter.incrementAndGet() <= maxSize) {
                    downDispatcher.onNext(param);
                } else {
                    hasMore = false;
                }
            }
        });

        return this;
    }

    public Observer distinct() {
        dispatcher.append(new Dispatcher<>() {
            private final Set set = N.newHashSet();

            @Override
            public void onNext(final Object param) {
                if (downDispatcher != null && set.add((T) param)) {
                    downDispatcher.onNext(param);
                }
            }
        });

        return this;
    }

    /**
     *
     * @param keyExtractor
     * @return
     */
    public Observer distinctBy(final Function keyExtractor) {
        dispatcher.append(new Dispatcher<>() {
            private final Set set = N.newHashSet();

            @Override
            public void onNext(final Object param) {
                if (downDispatcher != null && set.add(keyExtractor.apply((T) param))) { // onError if keyExtractor.apply throws exception?
                    downDispatcher.onNext(param);
                }
            }
        });

        return this;
    }

    /**
     *
     * @param filter
     * @return
     */
    public Observer filter(final Predicate filter) {
        dispatcher.append(new Dispatcher<>() {
            @Override
            public void onNext(final Object param) {
                if (downDispatcher != null && filter.test((T) param)) { // onError if filter.test throws exception?
                    downDispatcher.onNext(param);
                }
            }
        });

        return this;
    }

    /**
     *
     * @param 
     * @param mapper
     * @return
     */
    public  Observer map(final Function mapper) {
        dispatcher.append(new Dispatcher<>() {
            @Override
            public void onNext(final Object param) {
                if (downDispatcher != null) {
                    downDispatcher.onNext(mapper.apply((T) param)); // onError if map.apply throws exception?
                }
            }
        });

        return (Observer) this;
    }

    /**
     *
     * @param 
     * @param mapper
     * @return
     */
    public  Observer flatMap(final Function> mapper) {
        dispatcher.append(new Dispatcher<>() {
            @Override
            public void onNext(final Object param) {
                if (downDispatcher != null) {
                    final Collection c = mapper.apply((T) param); // onError if map.apply throws exception?

                    if (N.notEmpty(c)) {
                        for (final R u : c) {
                            downDispatcher.onNext(u);
                        }
                    }
                }
            }
        });

        return (Observer) this;
    }

    /**
     *
     * @param timespan
     * @param unit
     * @return this instance
     * @see RxJava#window(long, java.util.concurrent.TimeUnit)
     */
    public Observer> buffer(final long timespan, final TimeUnit unit) {
        return buffer(timespan, unit, Integer.MAX_VALUE);
    }

    /**
     *
     * @param timespan
     * @param unit
     * @param count
     * @return this instance
     * @throws IllegalArgumentException
     * @see RxJava#window(long, java.util.concurrent.TimeUnit, int)
     */
    public Observer> buffer(final long timespan, final TimeUnit unit, final int count) throws IllegalArgumentException {
        N.checkArgument(timespan > 0, "timespan can't be 0 or negative");
        N.checkArgNotNull(unit, "Time unit can't be null");
        N.checkArgument(count > 0, "count can't be 0 or negative");

        dispatcher.append(new Dispatcher<>() {
            private final List queue = new ArrayList<>();

            { //NOSONAR
                scheduledFutures.put(schedulerForIntermediateOp.scheduleAtFixedRate(() -> {
                    List list = null;
                    synchronized (queue) {
                        list = new ArrayList<>(queue);
                        queue.clear();
                    }

                    if (downDispatcher != null) {
                        downDispatcher.onNext(list);
                    }
                }, timespan, timespan, unit), timespan);
            }

            @Override
            public void onNext(final Object param) {
                List list = null;

                synchronized (queue) {
                    queue.add((T) param);

                    if (queue.size() == count) {
                        list = new ArrayList<>(queue);
                        queue.clear();
                    }
                }

                if (list != null && downDispatcher != null) {
                    downDispatcher.onNext(list);
                }
            }
        });

        return (Observer>) this;
    }

    /**
     *
     * @param timespan
     * @param timeskip
     * @param unit
     * @return
     * @see RxJava#window(long, long, java.util.concurrent.TimeUnit)
     */
    public Observer> buffer(final long timespan, final long timeskip, final TimeUnit unit) {
        return buffer(timespan, timeskip, unit, Integer.MAX_VALUE);
    }

    /**
     *
     * @param timespan
     * @param timeskip
     * @param unit
     * @param count
     * @return
     * @throws IllegalArgumentException
     * @see RxJava#window(long, long, java.util.concurrent.TimeUnit)
     */
    public Observer> buffer(final long timespan, final long timeskip, final TimeUnit unit, final int count) throws IllegalArgumentException {
        N.checkArgument(timespan > 0, "timespan can't be 0 or negative");
        N.checkArgument(timeskip > 0, "timeskip can't be 0 or negative");
        N.checkArgNotNull(unit, "Time unit can't be null");
        N.checkArgument(count > 0, "count can't be 0 or negative");

        dispatcher.append(new Dispatcher<>() {
            private final long startTime = System.currentTimeMillis();
            private final long interval = timespan + timeskip;
            private final List queue = new ArrayList<>();

            { //NOSONAR
                scheduledFutures.put(schedulerForIntermediateOp.scheduleAtFixedRate(() -> {
                    List list = null;
                    synchronized (queue) {
                        list = new ArrayList<>(queue);
                        queue.clear();
                    }

                    if (downDispatcher != null) {
                        downDispatcher.onNext(list);
                    }
                }, timespan, interval, unit), interval);
            }

            @Override
            public void onNext(final Object param) {
                if ((System.currentTimeMillis() - startTime) % interval <= timespan) {
                    List list = null;

                    synchronized (queue) {
                        queue.add((T) param);

                        if (queue.size() == count) {
                            list = new ArrayList<>(queue);
                            queue.clear();
                        }
                    }

                    if (list != null && downDispatcher != null) {
                        downDispatcher.onNext(list);
                    }
                }
            }
        });

        return (Observer>) this;
    }

    /**
     *
     * @param action
     */
    public void observe(final Consumer action) {
        observe(action, ON_ERROR_MISSING);
    }

    /**
     *
     * @param action
     * @param onError
     */
    public void observe(final Consumer action, final Consumer onError) {
        observe(action, onError, EMPTY_ACTION);
    }

    /**
     *
     * @param action
     * @param onError
     * @param onComplete
     */
    public abstract void observe(final Consumer action, final Consumer onError, final Runnable onComplete);

    /**
     * Cancel scheduled futures.
     */
    void cancelScheduledFutures() {
        final long startTime = System.currentTimeMillis();

        if (N.notEmpty(scheduledFutures)) {
            for (final Map.Entry, Long> entry : scheduledFutures.entrySet()) {
                final long delay = entry.getValue();

                N.sleepUninterruptibly(delay - (System.currentTimeMillis() - startTime)
                        + delay /* Extending another delay just wants to make sure the last schedule can be completed before the schedule task is canceled*/);

                entry.getKey().cancel(false);
            }
        }
    }

    //    /**
    //     * The Class Node.
    //     *
    //     * @param 
    //     */
    //    protected static class Node {
    //
    //        /** The value. */
    //        protected final T value;
    //
    //        /** The next. */
    //        protected Node next;
    //
    //        /**
    //         * Instantiates a new node.
    //         *
    //         * @param value
    //         */
    //        public Node(final T value) {
    //            this(value, null);
    //        }
    //
    //        /**
    //         * Instantiates a new node.
    //         *
    //         * @param value
    //         * @param next
    //         */
    //        public Node(final T value, final Node next) {
    //            this.value = value;
    //            this.next = next;
    //        }
    //    }

    /**
     * The Class Dispatcher.
     *
     * @param 
     */
    protected static class Dispatcher {

        /** The holder. */
        protected final Holder holder = Holder.of(NONE);

        /** The down dispatcher. */
        protected Dispatcher downDispatcher;

        /**
         *
         * @param value
         */
        public void onNext(final T value) {
            if (downDispatcher != null) {
                downDispatcher.onNext(value);
            }
        }

        /**
         * Signal an exception.
         * @param error the Exception to signal, not null
         */
        public void onError(final Exception error) {
            if (downDispatcher != null) {
                downDispatcher.onError(error);
            }
        }

        /**
         * Signal a completion.
         */
        public void onComplete() {
            if (downDispatcher != null) {
                downDispatcher.onComplete();
            }
        }

        /**
         *
         * @param downDispatcher
         */
        public void append(final Dispatcher downDispatcher) {
            Dispatcher tmp = this;

            while (tmp.downDispatcher != null) {
                tmp = tmp.downDispatcher;
            }

            tmp.downDispatcher = downDispatcher;
        }
    }

    /**
     * The Class DispatcherBase.
     *
     * @param 
     */
    protected abstract static class DispatcherBase extends Dispatcher {

        /** The on error. */
        private final Consumer onError;

        /** The on complete. */
        private final Runnable onComplete;

        /**
         * Instantiates a new dispatcher base.
         *
         * @param onError
         * @param onComplete
         */
        protected DispatcherBase(final Consumer onError, final Runnable onComplete) {
            this.onError = onError;
            this.onComplete = onComplete;
        }

        /**
         *
         * @param error
         */
        @Override
        public void onError(final Exception error) {
            onError.accept(error);
        }

        /**
         * On complete.
         */
        @Override
        public void onComplete() {
            onComplete.run();
        }
    }

    /**
     * The Class ObserverBase.
     *
     * @param 
     */
    protected abstract static class ObserverBase extends Observer {

        /**
         * Instantiates a new observer base.
         */
        protected ObserverBase() {

        }
    }

    /**
     * An asynchronous update interface for receiving notifications
     * about BlockingQueue information as the BlockingQueue is constructed.
     *
     * @param 
     */
    static final class BlockingQueueObserver extends ObserverBase {

        /** The queue. */
        private final BlockingQueue queue;

        /**
         * This method is called when information about a BlockingQueue
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param queue
         */
        BlockingQueueObserver(final BlockingQueue queue) {
            this.queue = queue;
        }

        /**
         * This method is called when information about a BlockingQueue
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param action
         * @param onError
         * @param onComplete
         */
        @Override
        public void observe(final Consumer action, final Consumer onError, final Runnable onComplete)
                throws IllegalArgumentException {
            N.checkArgNotNull(action, "action"); //NOSONAR

            dispatcher.append(new DispatcherBase<>(onError, onComplete) {
                @Override
                public void onNext(final Object param) {
                    action.accept((T) param);
                }
            });

            asyncExecutor.execute(() -> {
                T next = null;
                boolean isOnError = true;

                try {
                    while (hasMore && (next = queue.poll(Long.MAX_VALUE, TimeUnit.MILLISECONDS)) != COMPLETE_FLAG) {
                        isOnError = false;

                        dispatcher.onNext(next);

                        isOnError = true;
                    }

                    isOnError = false;

                    onComplete.run();
                } catch (final Exception e) {
                    if (isOnError) {
                        onError.accept(e);
                    } else {
                        throw ExceptionUtil.toRuntimeException(e, true);
                    }
                } finally {
                    cancelScheduledFutures();
                }
            });
        }
    }

    /**
     * An asynchronous update interface for receiving notifications
     * about Iterator information as the Iterator is constructed.
     *
     * @param 
     */
    static final class IteratorObserver extends ObserverBase {

        /** The iter. */
        private final Iterator iter;

        /**
         * This method is called when information about an Iterator
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param iter
         */
        IteratorObserver(final Iterator iter) {
            this.iter = iter;
        }

        /**
         * This method is called when information about an Iterator
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param action
         * @param onError
         * @param onComplete
         */
        @Override
        public void observe(final Consumer action, final Consumer onError, final Runnable onComplete)
                throws IllegalArgumentException {
            N.checkArgNotNull(action, cs.action);

            dispatcher.append(new DispatcherBase<>(onError, onComplete) {
                @Override
                public void onNext(final Object param) {
                    action.accept((T) param);
                }
            });

            asyncExecutor.execute(() -> {
                boolean isOnError = true;

                try {
                    while (hasMore && iter.hasNext()) {
                        isOnError = false;

                        dispatcher.onNext(iter.next());

                        isOnError = true;
                    }

                    isOnError = false;

                    onComplete.run();
                } catch (final Exception e) {
                    if (isOnError) {
                        onError.accept(e);
                    } else {
                        throw ExceptionUtil.toRuntimeException(e, true);
                    }
                } finally {
                    cancelScheduledFutures();
                }
            });
        }

    }

    /**
     * An asynchronous update interface for receiving notifications
     * about Timer information as the Timer is constructed.
     *
     * @param 
     */
    static final class TimerObserver extends ObserverBase {

        /** The delay. */
        private final long delay;

        /** The unit. */
        private final TimeUnit unit;

        /**
         * This method is called when information about a Timer
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param delay
         * @param unit
         */
        TimerObserver(final long delay, final TimeUnit unit) {
            this.delay = delay;
            this.unit = unit;
        }

        /**
         * This method is called when information about a Timer
         * which was previously requested using an asynchronous
         * interface becomes available.
         *
         * @param action
         * @param onError
         * @param onComplete
         */
        @Override
        public void observe(final Consumer action, final Consumer onError, final Runnable onComplete)
                throws IllegalArgumentException {
            N.checkArgNotNull(action, cs.action);

            dispatcher.append(new DispatcherBase<>(onError, onComplete) {
                @Override
                public void onNext(final Object param) {
                    action.accept((T) param);
                }
            });

            schedulerForObserveOp.schedule(() -> {
                try {
                    dispatcher.onNext(0L);

                    onComplete.run();
                } finally {
                    cancelScheduledFutures();
                }
            }, delay, unit);
        }
    }

    /**
     * An asynchronous update interface for receiving notifications
     * about Interval information as the Interval is constructed.
     *
     * @param 
     */
    static final class IntervalObserver extends ObserverBase {

        /** The initial delay. */
        private final long initialDelay;

        /** The period. */
        private final long period;

        /** The unit. */
        private final TimeUnit unit;

        /** The future. */
        private ScheduledFuture future = null;

        /**
         * This method is called when information about an Interval
         * which was previously requested using an asynchronous interface becomes available.
         *
         * @param initialDelay
         * @param period
         * @param unit
         */
        IntervalObserver(final long initialDelay, final long period, final TimeUnit unit) {
            this.initialDelay = initialDelay;
            this.period = period;
            this.unit = unit;
        }

        /**
         * This method is called when information about an Interval
         * which was previously requested using an asynchronous interface becomes available.
         *
         * @param action
         * @param onError
         * @param onComplete
         */
        @Override
        public void observe(final Consumer action, final Consumer onError, final Runnable onComplete)
                throws IllegalArgumentException {
            N.checkArgNotNull(action, cs.action);

            dispatcher.append(new DispatcherBase<>(onError, onComplete) {
                @Override
                public void onNext(final Object param) {
                    action.accept((T) param);
                }
            });

            future = schedulerForObserveOp.scheduleAtFixedRate(new Runnable() {
                private long val = 0;

                @Override
                public void run() {
                    if (!hasMore) {
                        try {
                            dispatcher.onComplete();
                        } finally {
                            try {
                                future.cancel(true);
                            } finally {
                                cancelScheduledFutures();
                            }
                        }
                    } else {
                        try {
                            dispatcher.onNext(val++);
                        } catch (final Exception e) {
                            try {
                                future.cancel(true);
                            } finally {
                                cancelScheduledFutures();
                            }
                        }
                    }
                }
            }, initialDelay, period, unit);
        }
    }
}