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

com.github.davidmoten.rx.internal.operators.OperatorBufferPredicateBoundary Maven / Gradle / Ivy

package com.github.davidmoten.rx.internal.operators;

import java.util.*;
import java.util.concurrent.atomic.*;

import rx.*;
import rx.Observable;
import rx.Observable.*;
import rx.exceptions.*;
import rx.functions.Func1;
import rx.internal.operators.*;
import rx.internal.util.atomic.SpscAtomicArrayQueue;
import rx.internal.util.unsafe.*;

/**
 * Buffers values into a continuous, non-overlapping Lists where the boundary is determined
 * by a predicate returning true.
 *
 * @param  the source and List element type
 */
public final class OperatorBufferPredicateBoundary implements Transformer> {

    final Func1 predicate;
    
    final int prefetch;
    
    final int capacityHint;
    
    final boolean after;
    
    public OperatorBufferPredicateBoundary(Func1 predicate, int prefetch, int capacityHint, boolean after) {
        if (predicate == null) {
            throw new NullPointerException("predicate");
        }
        if (prefetch <= 0) {
            throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch);
        }
        if (capacityHint <= 0) {
            throw new IllegalArgumentException("capacityHint > 0 required but it was " + capacityHint);
        }
        this.predicate = predicate;
        this.prefetch = prefetch;
        this.capacityHint = capacityHint;
        this.after = after;
    }

    @Override
    public Observable> call(Observable source) {
        return source.lift(new Operator, T>() {
            @Override
            public Subscriber call(Subscriber> child) {
                final BoundedSubscriber parent = after 
                        ? new BoundedAfterSubscriber(child, capacityHint, predicate, prefetch)
                        : new BoundedBeforeSubscriber(child, capacityHint, predicate, prefetch);
                        
                child.add(parent);
                child.setProducer(new Producer() {
                    @Override
                    public void request(long n) {
                        parent.requestMore(n);
                    }
                });
                
                return parent;
            }
        });
    }
    
    static abstract class BoundedSubscriber extends Subscriber {
        final Subscriber> actual;
        
        final int capacityHint;
        
        final Func1 predicate;
        
        final Queue queue;
        
        final AtomicLong requested;

        final AtomicInteger wip;
        
        final NotificationLite nl;
        
        final int limit;

        List buffer;
        
        long upstreamConsumed;
        
        volatile boolean done;
        Throwable error;

        public BoundedSubscriber(Subscriber> actual, int capacityHint,
                Func1 predicate, int prefetch) {
            this.actual = actual;
            this.capacityHint = capacityHint;
            this.predicate = predicate;
            Queue q;
            if (UnsafeAccess.isUnsafeAvailable()) {
                q = new SpscArrayQueue(prefetch);
            } else {
                q = new SpscAtomicArrayQueue(prefetch);
            }
            queue = q;
            buffer = new ArrayList(capacityHint);
            requested = new AtomicLong();
            wip = new AtomicInteger();
            nl = NotificationLite.instance();
            limit = prefetch - (prefetch >> 2);
            if (prefetch == Integer.MAX_VALUE) {
                request(Long.MAX_VALUE);
            } else {
                request(prefetch);
            }
        }

        @Override
        public void onNext(T t) {
            if (!queue.offer(nl.next(t))) {
                unsubscribe();
                onError(new MissingBackpressureException());
            } else {
                drain();
            }
        }
        
        @Override
        public void onError(Throwable e) {
            error = e;
            done = true;
            drain();
        }
        
        @Override
        public void onCompleted() {
            done = true;
            drain();
        }
        
        void requestMore(long n) {
            if (n > 0) {
                BackpressureUtils.getAndAddRequest(requested, n);
                drain();
            } else
            if (n < 0) {
                throw new IllegalArgumentException("n >= 0 required but it was " + n);
            }
        }

        abstract void drain();
    }
    
    static final class BoundedAfterSubscriber extends BoundedSubscriber {

        public BoundedAfterSubscriber(Subscriber> actual, int capacityHint,
                Func1 predicate, int prefetch) {
            super(actual, capacityHint, predicate, prefetch);
        }
        
        @Override
        void drain() {
            if (wip.getAndIncrement() != 0) {
                return;
            }
            
            final Subscriber> localSubscriber = actual;
            final Queue localQueue = queue;
            int missed = 1;
            
            for (;;) {

                long localRequested = requested.get();
                long localEmission = 0L;
                long localConsumption = 0L;
                List localBuffer = buffer;
                
                while (localEmission != localRequested) {
                    if (localSubscriber.isUnsubscribed()) {
                        return;
                    }
                    
                    boolean mainDone = done;
                    
                    if (mainDone) {
                        Throwable exception = error;
                        if (exception != null) {
                            buffer = null;
                            localSubscriber.onError(exception);
                            return;
                        }
                    }
                    
                    Object notification = localQueue.poll();
                    boolean empty = notification == null;
                    
                    if (mainDone && empty) {
                        buffer = null;
                        if (!localBuffer.isEmpty()) {
                            localSubscriber.onNext(localBuffer);
                        }
                        localSubscriber.onCompleted();
                        return;
                    }
                    if (empty) {
                        break;
                    }
                    
                    T value = nl.getValue(notification);
                    
                    localBuffer.add(value);
                    localConsumption++;
                    
                    boolean emit;
                    
                    try {
                        emit = predicate.call(value);
                    } catch (Throwable ex) {
                        unsubscribe();
                        buffer = null;
                        Exceptions.throwOrReport(ex, localSubscriber, value);
                        return;
                    }
                    
                    if (emit) {
                        localSubscriber.onNext(localBuffer);
                        localBuffer = new ArrayList(capacityHint);
                        buffer = localBuffer;
                        
                        localEmission++;
                    }
                }
                
                if (localEmission == localRequested) {
                    if (localSubscriber.isUnsubscribed()) {
                        return;
                    }
                    
                    boolean mainDone = done;

                    if (mainDone) {
                        Throwable exception = error;
                        if (exception != null) {
                            buffer = null;
                            localSubscriber.onError(exception);
                            return;
                        } else
                        if (localQueue.isEmpty() && localBuffer.isEmpty()) {
                            buffer = null;
                            localSubscriber.onCompleted();
                            return;
                        }
                    }
                }
                
                if (localEmission != 0L) {
                    BackpressureUtils.produced(requested, localEmission);
                }
                if (localConsumption != 0L) {
                    long p = upstreamConsumed + localConsumption;
                    if (p >= limit) {
                        upstreamConsumed = 0L;
                        request(p);
                    } else {
                        upstreamConsumed = p;
                    }
                }
                
                missed = wip.addAndGet(-missed);
                if (missed == 0) {
                    break;
                }
            }
        }
    }

    static final class BoundedBeforeSubscriber extends BoundedSubscriber {
        public BoundedBeforeSubscriber(Subscriber> actual, int capacityHint,
                Func1 predicate, int prefetch) {
            super(actual, capacityHint, predicate, prefetch);
        }

        @Override
        void drain() {
            if (wip.getAndIncrement() != 0) {
                return;
            }
            
            final Subscriber> localSubscriber = actual;
            final Queue localQueue = queue;
            int missed = 1;
            
            for (;;) {

                long localRequested = requested.get();
                long localEmission = 0L;
                long localConsumption = 0L;
                List localBuffer = buffer;
                
                while (localEmission != localRequested) {
                    if (localSubscriber.isUnsubscribed()) {
                        return;
                    }
                    
                    boolean mainDone = done;
                    
                    if (mainDone) {
                        Throwable exception = error;
                        if (exception != null) {
                            buffer = null;
                            localSubscriber.onError(exception);
                            return;
                        }
                    }
                    
                    Object o = localQueue.poll();
                    boolean empty = o == null;
                    
                    if (mainDone && empty) {
                        buffer = null;
                        if (!localBuffer.isEmpty()) {
                            localSubscriber.onNext(localBuffer);
                        }
                        localSubscriber.onCompleted();
                        return;
                    }
                    if (empty) {
                        break;
                    }
                    
                    T value = nl.getValue(o);
                    
                    boolean emit;
                    
                    try {
                        emit = predicate.call(value);
                    } catch (Throwable ex) {
                        unsubscribe();
                        buffer = null;
                        Exceptions.throwOrReport(ex, localSubscriber, value);
                        return;
                    }
                    
                    if (emit && !localBuffer.isEmpty()) {
                        localSubscriber.onNext(localBuffer);
                        localBuffer = new ArrayList(capacityHint);
                        buffer = localBuffer;
                        
                        localEmission++;
                    }
                    
                    localBuffer.add(value);
                    
                    localConsumption++;
                }
                
                if (localEmission == localRequested) {
                    if (localSubscriber.isUnsubscribed()) {
                        return;
                    }
                    
                    boolean mainDone = done;

                    if (mainDone) {
                        Throwable exception = error;
                        if (exception != null) {
                            buffer = null;
                            localSubscriber.onError(exception);
                            return;
                        } else
                        if (localQueue.isEmpty() && localBuffer.isEmpty()) {
                            buffer = null;
                            localSubscriber.onCompleted();
                            return;
                        }
                    }
                }
                
                if (localEmission != 0L) {
                    BackpressureUtils.produced(requested, localEmission);
                }
                
                if (localConsumption != 0L) {
                    long produced = upstreamConsumed + localConsumption;
                    if (produced >= limit) {
                        upstreamConsumed = 0L;
                        request(produced);
                    } else {
                        upstreamConsumed = produced;
                    }
                }
                
                missed = wip.addAndGet(-missed);
                if (missed == 0) {
                    break;
                }
            }
        }
    }
}