/**
* 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 <a href="http://msdn.microsoft.com/en-us/library/hh211932.aspx">MSDN: Observable.GroupByUntil</a>
* @see <a href="http://msdn.microsoft.com/en-us/library/hh229433.aspx">MSDN: Observable.GroupByUntil</a>
*
* @param <T> the source value type
* @param <K> the group key type
* @param <R> the value type of the groups
* @param <D> the type of the duration
*/
public class OperatorGroupByUntil<T, K, R, D> implements Operator<GroupedObservable<K, R>, T> {
final Func1<? super T, ? extends K> keySelector;
final Func1<? super T, ? extends R> valueSelector;
final Func1<? super GroupedObservable<K, R>, ? extends Observable<? extends D>> durationSelector;
public OperatorGroupByUntil(
Func1<? super T, ? extends K> keySelector,
Func1<? super T, ? extends R> valueSelector,
Func1<? super GroupedObservable<K, R>, ? extends Observable<? extends D>> durationSelector) {
this.keySelector = keySelector;
this.valueSelector = valueSelector;
this.durationSelector = durationSelector;
}
@Override
public Subscriber<? super T> call(Subscriber<? super GroupedObservable<K, R>> child) {
final SerializedSubscriber<GroupedObservable<K, R>> s = new SerializedSubscriber<GroupedObservable<K, R>>(child);
final CompositeSubscription csub = new CompositeSubscription();
child.add(csub);
return new Subscriber<T>(child) {
final Object guard = new Object();
/** Guarded by guard. */
Map<K, GroupSubject<K, R>> groups = new HashMap<K, GroupSubject<K, R>>();
@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<T> 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<K, R> 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<K, R> groupObs = gs.toObservable();
Observable<? extends D> durationObs;
try {
durationObs = durationSelector.call(groupObs);
} catch (Throwable e) {
onError(e);
return;
}
s.onNext(groupObs);
final K fKey = key;
Subscriber<D> durationSub = new Subscriber<D>() {
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<K, R> 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<GroupSubject<K, R>> localGroups;
synchronized (guard) {
if (groups == null) {
return;
}
localGroups = new ArrayList<GroupSubject<K, R>>(groups.values());
groups = null;
}
for (GroupSubject<K, R> g : localGroups) {
g.onError(e);
}
s.onError(e);
unsubscribe();
}
@Override
public void onCompleted() {
List<GroupSubject<K, R>> localGroups;
synchronized (guard) {
if (groups == null) {
return;
}
localGroups = new ArrayList<GroupSubject<K, R>>(groups.values());
groups = null;
}
for (GroupSubject<K, R> g : localGroups) {
g.onCompleted();
}
s.onCompleted();
unsubscribe();
}
};
}
/**
* A grouped observable with subject-like behavior.
*
* @param <K> the key type
* @param <R> the value type
*/
public static final class GroupSubject<K, R> extends Subscriber<R> {
static <K, R> GroupSubject<K, R> create(K key) {
Subject<R, R> publish = BufferUntilSubscriber.create();
return new GroupSubject<K, R>(key, publish);
}
final Observable<R> publishObservable;
final SerializedObserver<R> publishSerial;
final K key;
public GroupSubject(K key, final Subject<R, R> publish) {
this.key = key;
this.publishObservable = publish;
this.publishSerial = new SerializedObserver<R>(publish);
}
/**
* @warn javadoc missing
* @return
*/
public GroupedObservable<K, R> toObservable() {
return new GroupedObservable<K, R>(key, new OnSubscribe<R>() {
@Override
public void call(Subscriber<? super R> 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();
}
}
}