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

hu.akarnokd.rxjava2.internal.operators.nbp.NbpOnSubscribeZip Maven / Gradle / Ivy

There is a newer version: 2.0.0-RC3
Show newest version
/**
 * Copyright 2015 David Karnok and Netflix, Inc.
 * 
 * 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 hu.akarnokd.rxjava2.internal.operators.nbp;

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

import hu.akarnokd.rxjava2.NbpObservable;
import hu.akarnokd.rxjava2.NbpObservable.*;
import hu.akarnokd.rxjava2.disposables.Disposable;
import hu.akarnokd.rxjava2.functions.Function;
import hu.akarnokd.rxjava2.internal.disposables.EmptyDisposable;
import hu.akarnokd.rxjava2.internal.queue.SpscLinkedArrayQueue;
import hu.akarnokd.rxjava2.plugins.RxJavaPlugins;

public final class NbpOnSubscribeZip implements NbpOnSubscribe {
    
    final NbpObservable[] sources;
    final Iterable> sourcesIterable;
    final Function zipper;
    final int bufferSize;
    final boolean delayError;
    
    public NbpOnSubscribeZip(NbpObservable[] sources,
            Iterable> sourcesIterable,
            Function zipper,
            int bufferSize,
            boolean delayError) {
        this.sources = sources;
        this.sourcesIterable = sourcesIterable;
        this.zipper = zipper;
        this.bufferSize = bufferSize;
        this.delayError = delayError;
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public void accept(NbpSubscriber s) {
        NbpObservable[] sources = this.sources;
        int count = 0;
        if (sources == null) {
            sources = new NbpObservable[8];
            for (NbpObservable p : sourcesIterable) {
                if (count == sources.length) {
                    NbpObservable[] b = new NbpObservable[count + (count >> 2)];
                    System.arraycopy(sources, 0, b, 0, count);
                    sources = b;
                }
                sources[count++] = p;
            }
        } else {
            count = sources.length;
        }
        
        if (count == 0) {
            EmptyDisposable.complete(s);
            return;
        }
        
        ZipCoordinator zc = new ZipCoordinator(s, zipper, count, delayError);
        zc.subscribe(sources, bufferSize);
    }
    
    static final class ZipCoordinator extends AtomicInteger implements Disposable {
        /** */
        private static final long serialVersionUID = 2983708048395377667L;
        final NbpSubscriber actual;
        final Function zipper;
        final ZipSubscriber[] subscribers;
        final Object[] row;
        final boolean delayError;
        
        volatile boolean cancelled;
        
        @SuppressWarnings("unchecked")
        public ZipCoordinator(NbpSubscriber actual, 
                Function zipper, 
                int count, boolean delayError) {
            this.actual = actual;
            this.zipper = zipper;
            this.subscribers = new ZipSubscriber[count];
            this.row = new Object[count];
            this.delayError = delayError;
        }
        
        public void subscribe(NbpObservable[] sources, int bufferSize) {
            ZipSubscriber[] s = subscribers;
            int len = s.length;
            for (int i = 0; i < len; i++) {
                s[i] = new ZipSubscriber(this, bufferSize);
            }
            // this makes sure the contents of the subscribers array is visible
            this.lazySet(0);
            actual.onSubscribe(this);
            for (int i = 0; i < len; i++) {
                if (cancelled) {
                    return;
                }
                sources[i].subscribe(s[i]);
            }
        }
        
        @Override
        public void dispose() {
            if (!cancelled) {
                cancelled = true;
                if (getAndIncrement() == 0) {
                    clear();
                }
            }
        }
        
        void clear() {
            for (ZipSubscriber zs : subscribers) {
                zs.dispose();
                zs.queue.clear();
            }
        }
        
        public void drain() {
            if (getAndIncrement() != 0) {
                return;
            }
            
            int missing = 1;
            
            final ZipSubscriber[] zs = subscribers;
            final NbpSubscriber a = actual;
            final Object[] os = row;
            final boolean delayError = this.delayError;
            
            for (;;) {

                for (;;) {
                    int i = 0;
                    int emptyCount = 0;
                    for (ZipSubscriber z : zs) {
                        boolean d = z.done;
                        T v = z.queue.peek();
                        boolean empty = v == null;
                        
                        if (checkTerminated(d, empty, a, delayError, z)) {
                            return;
                        }
                        
                        if (empty) {
                            emptyCount++;
                            continue;
                        }
                        
                        os[i] = v;
                        i++;
                    }
                    
                    if (emptyCount != 0) {
                        break;
                    }
                    // consume the row
                    for (ZipSubscriber z : zs) {
                        z.queue.poll();
                    }

                    R v;
                    try {
                        v = zipper.apply(os.clone());
                    } catch (Throwable ex) {
                        clear();
                        a.onError(ex);
                        return;
                    }
                    
                    if (v == null) {
                        clear();
                        a.onError(new NullPointerException("The zipper returned a null value"));
                        return;
                    }
                    
                    a.onNext(v);
                }
                
                missing = addAndGet(-missing);
                if (missing == 0) {
                    return;
                }
            }
        }
        
        boolean checkTerminated(boolean d, boolean empty, NbpSubscriber a, boolean delayError, ZipSubscriber source) {
            if (cancelled) {
                clear();
                return true;
            }
            
            if (d) {
                if (delayError) {
                    if (empty) {
                        Throwable e = source.error;
                        clear();
                        if (e != null) {
                            a.onError(e);
                        } else {
                            a.onComplete();
                        }
                        return true;
                    }
                } else {
                    Throwable e = source.error;
                    if (e != null) {
                        clear();
                        a.onError(e);
                        return true;
                    } else
                    if (empty) {
                        clear();
                        a.onComplete();
                        return true;
                    }
                }
            }
            
            return false;
        }
    }
    
    static final class ZipSubscriber implements NbpSubscriber, Disposable {
        
        final ZipCoordinator parent;
        final Queue queue;
        
        volatile boolean done;
        Throwable error;
        
        volatile Disposable s;
        @SuppressWarnings("rawtypes")
        static final AtomicReferenceFieldUpdater S =
                AtomicReferenceFieldUpdater.newUpdater(ZipSubscriber.class, Disposable.class, "s");
        
        static final Disposable CANCELLED = new Disposable() {
            @Override
            public void dispose() { }
        };
        
        public ZipSubscriber(ZipCoordinator parent, int bufferSize) {
            this.parent = parent;
            this.queue = new SpscLinkedArrayQueue(bufferSize);
        }
        @Override
        public void onSubscribe(Disposable s) {
            
            for (;;) {
                Disposable current = this.s;
                if (current == CANCELLED) {
                    s.dispose();
                    return;
                }
                if (current != null) {
                    s.dispose();
                    RxJavaPlugins.onError(new IllegalStateException("Subscription already set!"));
                    return;
                }
                if (S.compareAndSet(this, null, s)) {
                    return;
                } else {
                    s.dispose();
                }
            }
            
        }
        
        @Override
        public void onNext(T t) {
            if (t == null) {
                s.dispose();
                onError(new NullPointerException());
                return;
            }
            if (!queue.offer(t)) {
                s.dispose();
                onError(new IllegalStateException("Queue full?!"));
                return;
            }
            parent.drain();
        }
        
        @Override
        public void onError(Throwable t) {
            error = t;
            done = true;
            parent.drain();
        }
        
        @Override
        public void onComplete() {
            done = true;
            parent.drain();
        }
        
        @Override
        public void dispose() {
            Disposable s = this.s;
            if (s != CANCELLED) {
                s = S.getAndSet(this, CANCELLED);
                if (s != CANCELLED && s != null) {
                    s.dispose();
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy