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

rx.internal.operators.OperatorGroupByUntil 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.HashMap;
import java.util.List;
import java.util.Map;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Observable.Operator;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Func1;
import rx.observables.GroupedObservable;
import rx.observers.SerializedObserver;
import rx.observers.SerializedSubscriber;
import rx.subjects.PublishSubject;
import rx.subjects.Subject;
import rx.subscriptions.CompositeSubscription;

/**
 * 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
 * 
 * @param  the source value type
 * @param  the group key type
 * @param  the value type of the groups
 * @param  the type of the duration
 */
public class OperatorGroupByUntil implements Operator, T> {
    final Func1 keySelector;
    final Func1 valueSelector;
    final Func1, ? extends Observable> durationSelector;

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

    @Override
    public Subscriber call(Subscriber> child) {
        final SerializedSubscriber> s = new SerializedSubscriber>(child);
        final CompositeSubscription csub = new CompositeSubscription();
        child.add(csub);
        
        return new Subscriber(child) {
            final Object guard = new Object();
            /** Guarded by guard. */
            Map> groups = new HashMap>();
            
            @Override
            public void onStart() {
                /*
                 * This operator does not support backpressure as splitting a stream effectively turns it into a "hot observable" and
                 * blocking any one group would block the entire parent stream. If backpressure is needed on individual groups then
                 * operators such as `onBackpressureDrop` or `onBackpressureBuffer` should be used.
                 */
                request(Long.MAX_VALUE);
            }
            
            final Subscriber self = this;
            @Override
            public void onNext(T t) {
                K key;
                R value;
                try {
                    key = keySelector.call(t);
                    value = valueSelector.call(t);
                } catch (Throwable e) {
                    onError(e);
                    return;
                }
                
                GroupSubject gs;
                boolean newGroup = false;
                synchronized (guard) {
                    if (groups == null) {
                        return;
                    }
                    gs = groups.get(key);
                    if (gs == null) {
                        gs = GroupSubject.create(key);
                        groups.put(key, gs);
                        newGroup = true;
                    }
                }
                
                if (newGroup) {
                    final GroupedObservable groupObs = gs.toObservable();
                    
                    Observable durationObs;
                    try {
                        durationObs = durationSelector.call(groupObs);
                    } catch (Throwable e) {
                        onError(e);
                        return;
                    }
                    
                    s.onNext(groupObs);
                    
                    final K fKey = key;
                    Subscriber durationSub = new Subscriber() {
                        boolean once = true;
                        @Override
                        public void onNext(D t) {
                            onCompleted();
                        }

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

                        @Override
                        public void onCompleted() {
                            if (once) {
                                once = false;
                                expire(fKey, this);
                            }
                        }
                    };
                    csub.add(durationSub);
                    
                    durationObs.unsafeSubscribe(durationSub);
                }
                
                gs.onNext(value);
            }

            void expire(K key, Subscription subscription) {
                GroupSubject g;
                synchronized (guard) {
                    if (groups == null) {
                        return;
                    }
                    g = groups.remove(key);
                }
                if (g != null) {
                    g.onCompleted();
                }
                csub.remove(subscription);
            } 
            
            @Override
            public void onError(Throwable e) {
                List> localGroups;
                synchronized (guard) {
                    if (groups == null) {
                        return;
                    }
                    localGroups = new ArrayList>(groups.values());
                    groups = null;
                }
                for (GroupSubject g : localGroups) {
                    g.onError(e);
                }
                s.onError(e);
                unsubscribe();
            }

            @Override
            public void onCompleted() {
                List> localGroups;
                synchronized (guard) {
                    if (groups == null) {
                        return;
                    }
                    localGroups = new ArrayList>(groups.values());
                    groups = null;
                }
                for (GroupSubject g : localGroups) {
                    g.onCompleted();
                }
                s.onCompleted();
                unsubscribe();
            }
            
        };
    }

    /** 
     * A grouped observable with subject-like behavior. 
     *
     * @param  the key type
     * @param  the value type
     */
    public static final class GroupSubject extends Subscriber {
        
        static  GroupSubject create(K key) {
            Subject publish = BufferUntilSubscriber.create();
            return new GroupSubject(key, publish);
        }
        
        final Observable publishObservable;
        final SerializedObserver publishSerial;
        final K key;

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

        /**
         * @warn javadoc missing
         * @return
         */
        public GroupedObservable toObservable() {
            return new GroupedObservable(key, new OnSubscribe() {
                @Override
                public void call(Subscriber o) {
                    publishObservable.unsafeSubscribe(o);
                }
            });
        }

        @Override
        public void onNext(R args) {
            publishSerial.onNext(args);
        }

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

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

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy