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

rx.internal.operators.OperatorWindowWithStartEndObservable Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2014 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 rx.internal.operators;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import rx.Observable;
import rx.Observable.Operator;
import rx.Observer;
import rx.Subscriber;
import rx.functions.Func1;
import rx.observers.SerializedObserver;
import rx.observers.SerializedSubscriber;
import rx.subscriptions.CompositeSubscription;

/**
 * Creates potentially overlapping windows of the source items where each window is
 * started by a value emitted by an observable and closed when an associated Observable emits 
 * a value or completes.
 * 
 * @param  the value type
 * @param  the type of the window opening event
 * @param  the type of the window closing event
 */
public final class OperatorWindowWithStartEndObservable implements Operator, T> {
    final Observable windowOpenings;
    final Func1> windowClosingSelector;

    public OperatorWindowWithStartEndObservable(Observable windowOpenings, 
            Func1> windowClosingSelector) {
        this.windowOpenings = windowOpenings;
        this.windowClosingSelector = windowClosingSelector;
    }
    
    @Override
    public Subscriber call(Subscriber> child) {
        final SourceSubscriber sub = new SourceSubscriber(child);
        
        Subscriber open = new Subscriber(child) {

            @Override
            public void onStart() {
                request(Long.MAX_VALUE);
            }
            
            @Override
            public void onNext(U t) {
                sub.beginWindow(t);
            }

            @Override
            public void onError(Throwable e) {
                sub.onError(e);
            }

            @Override
            public void onCompleted() {
                sub.onCompleted();
            }
        };
           
        windowOpenings.unsafeSubscribe(open);
        
        return sub;
    }
    /** Serialized access to the subject. */
    static final class SerializedSubject {
        final Observer consumer;
        final Observable producer;

        public SerializedSubject(Observer consumer, Observable producer) {
            this.consumer = new SerializedObserver(consumer);
            this.producer = producer;
        }
        
    }
    final class SourceSubscriber extends Subscriber {
        final Subscriber> child;
        final CompositeSubscription csub;
        final Object guard;
        /** Guarded by guard. */
        final List> chunks;
        /** Guarded by guard. */
        boolean done;
        public SourceSubscriber(Subscriber> child) {
            super(child);
            this.child = new SerializedSubscriber>(child);
            this.guard = new Object();
            this.chunks = new LinkedList>();
            this.csub = new CompositeSubscription();
            child.add(csub);
        }
        
        @Override
        public void onStart() {
            request(Long.MAX_VALUE);
        }
        
        @Override
        public void onNext(T t) {
            List> list;
            synchronized (guard) {
                if (done) {
                    return;
                }
                list = new ArrayList>(chunks);
            }
            for (SerializedSubject cs : list) {
                cs.consumer.onNext(t);
            }
        }

        @Override
        public void onError(Throwable e) {
            List> list;
            synchronized (guard) {
                if (done) {
                    return;
                }
                done = true;
                list = new ArrayList>(chunks);
                chunks.clear();
            }
            for (SerializedSubject cs : list) {
                cs.consumer.onError(e);
            }
            child.onError(e);
        }

        @Override
        public void onCompleted() {
            List> list;
            synchronized (guard) {
                if (done) {
                    return;
                }
                done = true;
                list = new ArrayList>(chunks);
                chunks.clear();
            }
            for (SerializedSubject cs : list) {
                cs.consumer.onCompleted();
            }
            child.onCompleted();
        }
        
        void beginWindow(U token) {
            final SerializedSubject s = createSerializedSubject();
            synchronized (guard) {
                if (done) {
                    return;
                }
                chunks.add(s);
            }
            child.onNext(s.producer);
            
            Observable end;
            try {
                end = windowClosingSelector.call(token);
            } catch (Throwable e) {
                onError(e);
                return;
            }
            
            Subscriber v = new Subscriber() {
                boolean once = true;
                @Override
                public void onNext(V t) {
                    onCompleted();
                }

                @Override
                public void onError(Throwable e) {
                    
                }

                @Override
                public void onCompleted() {
                    if (once) {
                        once = false;
                        endWindow(s);
                        csub.remove(this);
                    }
                }
                
            };
            csub.add(v);
            
            end.unsafeSubscribe(v);
        }
        void endWindow(SerializedSubject window) {
            boolean terminate = false;
            synchronized (guard) {
                if (done) {
                    return;
                }
                Iterator> it = chunks.iterator();
                while (it.hasNext()) {
                    SerializedSubject s = it.next();
                    if (s == window) {
                        terminate = true;
                        it.remove();
                        break;
                    }
                }
            }
            if (terminate) {
                window.consumer.onCompleted();
            }
        }
        SerializedSubject createSerializedSubject() {
            BufferUntilSubscriber bus = BufferUntilSubscriber.create();
            return new SerializedSubject(bus, bus);
        }
    }
}