
io.nextop.rx.RxManager Maven / Gradle / Ivy
package io.nextop.rx;
import com.google.common.collect.Sets;
import io.nextop.Id;
import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.functions.Func2;
import rx.subscriptions.BooleanSubscription;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/** Base for model or view model managers. Provides a stream of objects out,
* based on a stream of updates applied to a persistent state.
*
* TODO provide controls for caching the persistent state
*/
// FIXME never expose ManagedState, just thread id+subscriptions or vm+subscriptions
public abstract class RxManager {
private final Map> states = new HashMap>(32);
public Observable peek(Id id) {
@Nullable ManagedState state = states.get(id);
if (null != state && state.complete) {
return Observable.just(state.m);
}
return Observable.empty();
}
public Observable get(Id id) {
return getCompleteState(id).map(new Func1, M>() {
@Override
public M call(ManagedState state) {
return state.m;
}
});
}
// returned objects here are not guaranteed to have startUpdates/stopUpdates called before ejecting
// creating these objects should have no side effects
protected abstract M create(Id id);
protected void startUpdates(M m, RxState state) {
// the default action is to publish the state in memory
// in cases where the state has to be read from
complete(m.id);
}
protected void stopUpdates(Id id) {
// Do nothing
}
// FIXME close callback for state
// FIXME close callback on RxManaged
// protected void publish(Id id) {
// @Nullable MM state = states.get(id);
// if (null != state) {
// publish(state);
// }
// }
private void publish(ManagedState state) {
int publishCount = ++state.publishCount;
for (Subscriber super ManagedState> subscriber : state.subscribers) {
subscriber.onNext(state);
// a nested publish cut off this publish
// don't publish an older notification
if (publishCount != state.publishCount) {
break;
}
}
}
protected void complete(Id id) {
updateState(id, new Action1>() {
@Override
public void call(ManagedState mm) {
mm.complete = true;
}
});
}
protected void updateComplete(final Id id, final Func2 updater) {
updateCompleteState(id, new UpdateAdapter(updater));
}
// updates the view model
// publishes an update
protected void update(final Id id, final Func2 updater) {
updateState(id, new UpdateAdapter(updater));
}
final class UpdateAdapter implements Action1> {
final Func2 updater;
UpdateAdapter(Func2 updater) {
this.updater = updater;
}
@Override
public void call(ManagedState state) {
state.m = updater.call(state.m, state);
}
}
private void updateCompleteState(final Id id, final Action1> updater) {
getCompleteState(id).take(1).subscribe(new UpdateStateAdapter(updater));
}
// updates the view model
// publishes an update
private void updateState(final Id id, final Action1> updater) {
getState(id).take(1).subscribe(new UpdateStateAdapter(updater));
}
final class UpdateStateAdapter implements Observer> {
final Action1> updater;
@Nullable ManagedState state = null;
UpdateStateAdapter(Action1> updater) {
this.updater = updater;
}
@Override
public void onNext(ManagedState state) {
// apply at most once
if (null != this.state) {
throw new IllegalStateException();
}
this.state = state;
updater.call(state);
verifyState(state);
}
@Override
public void onCompleted() {
if (null != state) {
publish(state);
}
}
@Override
public void onError(Throwable e) {
// TODO log
}
}
private Observable> getCompleteState(final Id id) {
return getState(id).filter(new Func1, Boolean>() {
@Override
public Boolean call(ManagedState state) {
return state.complete;
}
});
}
private Observable> getState(final Id id) {
return Observable.create(new Observable.OnSubscribe>() {
@Override
public void call(final Subscriber super ManagedState> subscriber) {
final ManagedState state = createState(id);
subscriber.add(BooleanSubscription.create(new Action0() {
@Override
public void call() {
state.subscribers.remove(subscriber);
--state.refCount;
if (0 == state.refCount) {
state.binder.reset();
stopUpdates(state.id);
}
}
}));
int publishCount = state.publishCount;
state.subscribers.add((Subscriber>) subscriber);
if (1 == ++state.refCount) {
startUpdates(state.m, state);
}
// a nested publish cut off this publish
// don't publish an older notification
if (publishCount == state.publishCount) {
publish(state);
}
}
});
}
private ManagedState createState(Id id) {
ManagedState state = states.get(id);
if (null != state) {
return state;
}
M m = create(id);
if (!id.equals(m.id)) {
throw new IllegalStateException("#create must return a managed object with the same id as input.");
}
state = new ManagedState(m);
verifyState(state);
states.put(id, state);
return state;
}
private void verifyState(ManagedState state) {
if (null == state.m) {
throw new IllegalStateException();
}
if (!state.id.equals(state.m.id)) {
throw new IllegalStateException();
}
}
private static final class ManagedState implements RxState {
public final Id id;
public M m;
// mark this true when the version in memory has caught up with the upstream
// when complete, the state will be exposed via get/peek
public boolean complete = false;
Set>> subscribers = Sets.newIdentityHashSet();
int refCount = 0;
int publishCount = 0;
final RxLifecycleBinder binder = new RxLifecycleBinder.Lifted();
public ManagedState(M m) {
this.m = m;
id = m.id;
}
/////// RxState ///////
@Override
public Observable add(Observable source) {
return binder.bind(source);
}
@Override
public void add(Subscription s) {
binder.bind(s);
}
}
public static interface RxState {
Observable add(Observable source);
void add(Subscription s);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy