rx.internal.operators.OperatorPivot Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rxjava-core Show documentation
Show all versions of rxjava-core Show documentation
rxjava-core developed by Netflix
/**
* 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.Collections;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReference;
import rx.Observable.OnSubscribe;
import rx.Observable.Operator;
import rx.Observer;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Action0;
import rx.observables.GroupedObservable;
import rx.observers.SerializedObserver;
import rx.subscriptions.Subscriptions;
public final class OperatorPivot implements Operator>, GroupedObservable>> {
@Override
public Subscriber super GroupedObservable>> call(final Subscriber super GroupedObservable>> child) {
final AtomicReference state = new AtomicReference(State.create());
final PivotSubscriber pivotSubscriber = new PivotSubscriber(child, state);
child.add(Subscriptions.create(new Action0() {
@Override
public void call() {
State current;
State newState = null;
do {
current = state.get();
newState = current.unsubscribe();
} while (!state.compareAndSet(current, newState));
// If all outer/inner groups are completed/unsubscribed then we can shut it all down
if (newState.shouldComplete()) {
pivotSubscriber.groups.completeAll(newState);
}
// otherwise it is just marked as unsubscribed and groups being completed/unsubscribed will allow it to cleanup
}
}));
return pivotSubscriber;
}
private static final class PivotSubscriber extends Subscriber>> {
private final Subscriber super GroupedObservable>> child;
private final AtomicReference state;
private final GroupState groups;
private PivotSubscriber(Subscriber super GroupedObservable>> child, AtomicReference state) {
this.child = child;
this.state = state;
this.groups = new GroupState(this, child);
}
@Override
public void onCompleted() {
State current;
State newState = null;
do {
current = state.get();
newState = current.complete();
} while (!state.compareAndSet(current, newState));
// special case for empty (no groups emitted) or all groups already done
if (newState.shouldComplete()) {
groups.completeAll(newState);
}
}
@Override
public void onError(Throwable e) {
// we immediately tear everything down if we receive an error
child.onError(e);
}
@Override
public void onNext(final GroupedObservable> k1Group) {
groups.startK1Group(state, k1Group.getKey());
k1Group.unsafeSubscribe(new Subscriber>(this) {
@Override
public void onCompleted() {
groups.completeK1Group(state, k1Group.getKey());
}
@Override
public void onError(Throwable e) {
child.onError(e);
}
@Override
public void onNext(final GroupedObservable k2Group) {
/*
* In this scope once pivoted, k2 == outer and k2.k1 == inner
*/
final Inner inner = groups.getOrCreateFor(state, child, k1Group.getKey(), k2Group.getKey());
if (inner == null) {
// we have been unsubscribed
return;
}
k2Group.unsafeSubscribe(new Subscriber(this) {
@Override
public void onCompleted() {
/*
* we don't actually propagate onCompleted to the 'inner.subscriber' here
* since multiple upstream groups will be sent to a single downstream
* and a single upstream group completing does not indicate total completion
*/
}
@Override
public void onError(Throwable e) {
inner.subscriber.onError(e);
}
@Override
public void onNext(T t) {
inner.subscriber.onNext(t);
}
});
}
});
}
}
private static final class GroupState {
private final ConcurrentHashMap, Inner> innerSubjects = new ConcurrentHashMap, Inner>();
private final ConcurrentHashMap> outerSubjects = new ConcurrentHashMap>();
private final Subscription parentSubscription;
private final Subscriber super GroupedObservable>> child;
/** Indicates a terminal state. */
volatile int completed;
/** Field updater for completed. */
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater COMPLETED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(GroupState.class, "completed");
public GroupState(Subscription parentSubscription, Subscriber super GroupedObservable>> child) {
this.parentSubscription = parentSubscription;
this.child = child;
}
public void startK1Group(AtomicReference state, K1 key) {
State current;
State newState;
do {
current = state.get();
newState = current.addK1(key);
} while (!state.compareAndSet(current, newState));
}
public void completeK1Group(AtomicReference state, K1 key) {
State current;
State newState = null;
do {
current = state.get();
newState = current.removeK1(key);
} while (!state.compareAndSet(current, newState));
if (newState.shouldComplete()) {
completeAll(newState);
}
}
public void startK1K2Group(AtomicReference state, KeyPair keyPair) {
State current;
State newState;
do {
current = state.get();
newState = current.addK1k2(keyPair);
} while (!state.compareAndSet(current, newState));
}
public void completeK1K2Group(AtomicReference state, KeyPair keyPair) {
State current;
State newState = null;
do {
current = state.get();
newState = current.removeK1k2(keyPair);
} while (!state.compareAndSet(current, newState));
if (newState.shouldComplete()) {
completeAll(newState);
}
}
public void completeAll(State state) {
if (COMPLETED_UPDATER.compareAndSet(this, 0, 1)) {
/*
* after we are completely done emitting we can now shut down the groups
*/
for (Entry> outer : outerSubjects.entrySet()) {
outer.getValue().subscriber.onCompleted();
}
for (Entry, Inner> inner : innerSubjects.entrySet()) {
inner.getValue().subscriber.onCompleted();
}
// unsubscribe eagerly
if (state.unsubscribed) {
parentSubscription.unsubscribe(); // unsubscribe from parent
}
child.onCompleted();
}
}
private Inner getOrCreateFor(final AtomicReference state, final Subscriber super GroupedObservable>> child, K1 key1, K2 key2) {
Outer outer = getOrCreateOuter(state, child, key2);
if (outer == null) {
// we have been unsubscribed
return null;
}
Inner orCreateInnerSubject = getOrCreateInnerSubject(state, outer, key1, key2);
return orCreateInnerSubject;
}
private Inner getOrCreateInnerSubject(final AtomicReference state, final Outer outer, final K1 key1, final K2 key2) {
KeyPair keyPair = new KeyPair(key1, key2);
Inner innerSubject = innerSubjects.get(keyPair);
if (innerSubject != null) {
return innerSubject;
} else {
Inner newInner = Inner.create(this, state, outer, keyPair);
Inner existing = innerSubjects.putIfAbsent(keyPair, newInner);
if (existing != null) {
// we lost the race to create so return the one that did
return existing;
} else {
startK1K2Group(state, keyPair);
outer.subscriber.onNext(newInner.group);
return newInner;
}
}
}
private Outer getOrCreateOuter(final AtomicReference state, final Subscriber super GroupedObservable>> child, final K2 key2) {
Outer outerSubject = outerSubjects.get(key2);
if (outerSubject != null) {
return outerSubject;
} else {
// this group doesn't exist
if (child.isUnsubscribed()) {
// we have been unsubscribed on the outer so won't send any more groups
return null;
}
Outer newOuter = Outer. create(key2);
Outer existing = outerSubjects.putIfAbsent(key2, newOuter);
if (existing != null) {
// we lost the race to create so return the one that did
return existing;
} else {
child.onNext(newOuter.group);
return newOuter;
}
}
}
}
private static final class Inner {
private final Observer subscriber;
private final GroupedObservable group;
private Inner(BufferUntilSubscriber subscriber, GroupedObservable group) {
// since multiple threads are being pivoted we need to make sure this is serialized
this.subscriber = new SerializedObserver(subscriber);
this.group = group;
}
public static Inner create(final GroupState groupState, final AtomicReference state, final Outer outer, final KeyPair keyPair) {
final BufferUntilSubscriber subject = BufferUntilSubscriber.create();
GroupedObservable group = new GroupedObservable(keyPair.k1, new OnSubscribe() {
@Override
public void call(final Subscriber super T> o) {
o.add(Subscriptions.create(new Action0() {
@Override
public void call() {
groupState.completeK1K2Group(state, keyPair);
}
}));
subject.unsafeSubscribe(new Subscriber(o) {
@Override
public void onCompleted() {
groupState.completeK1K2Group(state, keyPair);
o.onCompleted();
}
@Override
public void onError(Throwable e) {
o.onError(e);
}
@Override
public void onNext(T t) {
if (!isUnsubscribed()) {
o.onNext(t);
}
}
});
}
});
return new Inner(subject, group);
}
}
private static final class Outer {
private final Observer> subscriber;
private final GroupedObservable> group;
private Outer(BufferUntilSubscriber> subscriber, GroupedObservable> group) {
// since multiple threads are being pivoted we need to make sure this is serialized
this.subscriber = new SerializedObserver>(subscriber);
this.group = group;
}
public static Outer create(final K2 key2) {
final BufferUntilSubscriber> subject = BufferUntilSubscriber.create();
GroupedObservable> group = new GroupedObservable>(key2, new OnSubscribe>() {
@Override
public void call(final Subscriber super GroupedObservable> o) {
subject.unsafeSubscribe(new Subscriber>(o) {
@Override
public void onCompleted() {
o.onCompleted();
}
@Override
public void onError(Throwable e) {
o.onError(e);
}
@Override
public void onNext(GroupedObservable t) {
if (!isUnsubscribed()) {
o.onNext(t);
}
}
});
}
});
return new Outer(subject, group);
}
}
private static final class State {
private final boolean unsubscribed;
private final boolean completed;
private final Set
© 2015 - 2024 Weber Informatics LLC | Privacy Policy