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

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

There is a newer version: 1.3.8
Show 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.*;

import rx.Observable;
import rx.Observable.Operator;
import rx.Observer;
import rx.Subscriber;
import rx.functions.Func1;
import rx.observers.*;
import rx.subjects.UnicastSubject;
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) {
        CompositeSubscription composite = new CompositeSubscription();
        child.add(composite);

        final SourceSubscriber sub = new SourceSubscriber(child, composite);

        Subscriber open = new Subscriber() {

            @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();
            }
        };

        composite.add(sub);
        composite.add(open);

        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 composite;
        final Object guard;
        /** Guarded by guard. */
        final List> chunks;
        /** Guarded by guard. */
        boolean done;
        public SourceSubscriber(Subscriber> child, CompositeSubscription composite) {
            this.child = new SerializedSubscriber>(child);
            this.guard = new Object();
            this.chunks = new LinkedList>();
            this.composite = composite;
        }

        @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) {
            try {
                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);
            } finally {
                composite.unsubscribe();
            }
        }

        @Override
        public void onCompleted() {
            try {
                List> list;
                synchronized (guard) {
                    if (done) {
                        return;
                    }
                    done = true;
                    list = new ArrayList>(chunks);
                    chunks.clear();
                }
                for (SerializedSubject cs : list) {
                    cs.consumer.onCompleted();
                }
                child.onCompleted();
            } finally {
                composite.unsubscribe();
            }
        }

        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) {
                    SourceSubscriber.this.onError(e);
                }

                @Override
                public void onCompleted() {
                    if (once) {
                        once = false;
                        endWindow(s);
                        composite.remove(this);
                    }
                }

            };
            composite.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() {
            UnicastSubject bus = UnicastSubject.create();
            return new SerializedSubject(bus, bus);
        }
    }
}