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

rx.operators.OperationGroupByUntil Maven / Gradle / Ivy

There is a newer version: 0.20.7
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.operators;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Observable.OnSubscribeFunc;
import rx.Observer;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Func1;
import rx.observables.GroupedObservable;
import rx.subjects.PublishSubject;
import rx.subjects.Subject;
import rx.subscriptions.CompositeSubscription;
import rx.subscriptions.SerialSubscription;

/**
 * Groups the elements of an observable sequence according to a specified key selector, value selector and duration selector function.
 * 
 * @see MSDN: Observable.GroupByUntil
 * @see MSDN: Observable.GroupByUntil
 */
public class OperationGroupByUntil implements OnSubscribeFunc> {
    final Observable source;
    final Func1 keySelector;
    final Func1 valueSelector;
    final Func1, ? extends Observable> durationSelector;

    public OperationGroupByUntil(Observable source,
            Func1 keySelector,
            Func1 valueSelector,
            Func1, ? extends Observable> durationSelector) {
        this.source = source;
        this.keySelector = keySelector;
        this.valueSelector = valueSelector;
        this.durationSelector = durationSelector;
    }

    @Override
    public Subscription onSubscribe(Observer> t1) {
        SerialSubscription cancel = new SerialSubscription();
        ResultSink sink = new ResultSink(t1, cancel);
        cancel.set(sink.run());
        return cancel;
    }

    /** The source value sink and group manager. */
    class ResultSink implements Observer {
        /** Guarded by gate. */
        protected final Observer> observer;
        protected final Subscription cancel;
        protected final CompositeSubscription group = new CompositeSubscription();
        protected final Object gate = new Object();
        /** Guarded by gate. */
        protected final Map> map = new HashMap>();

        public ResultSink(Observer> observer, Subscription cancel) {
            this.observer = observer;
            this.cancel = cancel;
        }

        /** Prepare the subscription tree. */
        public Subscription run() {
            SerialSubscription toSource = new SerialSubscription();
            group.add(toSource);

            toSource.set(source.subscribe(this));

            return group;
        }

        @Override
        public void onNext(TSource args) {
            TKey key;
            TResult value;
            try {
                key = keySelector.call(args);
                value = valueSelector.call(args);
            } catch (Throwable t) {
                onError(t);
                return;
            }

            GroupSubject g;
            boolean newGroup = false;
            synchronized (gate) {
                g = map.get(key);
                if (g == null) {
                    g = create(key);
                    map.put(key, g);
                    newGroup = true;
                }
            }

            if (newGroup) {
                Observable duration;
                try {
                    duration = durationSelector.call(g.toObservable());
                } catch (Throwable t) {
                    onError(t);
                    return;
                }

                synchronized (gate) {
                    observer.onNext(g.toObservable());
                }

                SerialSubscription durationHandle = new SerialSubscription();
                group.add(durationHandle);

                DurationObserver durationObserver = new DurationObserver(key, durationHandle);
                durationHandle.set(duration.subscribe(durationObserver));

            }

            synchronized (gate) {
                g.onNext(value);
            }
        }

        @Override
        public void onError(Throwable e) {
            synchronized (gate) {
                List> gs = new ArrayList>(map.values());
                map.clear();
                for (GroupSubject g : gs) {
                    g.onError(e);
                }
                observer.onError(e);
            }
            cancel.unsubscribe();
        }

        @Override
        public void onCompleted() {
            synchronized (gate) {
                List> gs = new ArrayList>(map.values());
                map.clear();
                for (GroupSubject g : gs) {
                    g.onCompleted();
                }
                observer.onCompleted();
            }
            cancel.unsubscribe();
        }

        /** Create a new group. */
        public GroupSubject create(TKey key) {
            PublishSubject publish = PublishSubject.create();
            return new GroupSubject(key, publish);
        }

        /** Terminate a group. */
        public void expire(TKey key, Subscription handle) {
            synchronized (gate) {
                GroupSubject g = map.remove(key);
                if (g != null) {
                    g.onCompleted();
                }
            }
            handle.unsubscribe();
        }

        /** Observe the completion of a group. */
        class DurationObserver implements Observer {
            final TKey key;
            final Subscription handle;

            public DurationObserver(TKey key, Subscription handle) {
                this.key = key;
                this.handle = handle;
            }

            @Override
            public void onNext(TDuration args) {
                expire(key, handle);
            }

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

            @Override
            public void onCompleted() {
                expire(key, handle);
            }

        }
    }

    /** A grouped observable with subject-like behavior. */
    public static class GroupSubject implements Observer {
        protected final Subject publish;
        private final K key;

        public GroupSubject(K key, final Subject publish) {
            this.key = key;
            this.publish = publish;
        }

        public GroupedObservable toObservable() {
            return new GroupedObservable(key, new OnSubscribe() {
                @Override
                public void call(Subscriber o) {
                    publish.subscribe(o);
                }
            });
        }

        @Override
        public void onNext(V args) {
            publish.onNext(args);
        }

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

        @Override
        public void onCompleted() {
            publish.onCompleted();
        }

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy