io.nextop.rx.RxDebugger Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of android Show documentation
Show all versions of android Show documentation
Nextop client for Android 10+
The newest version!
package io.nextop.rx;
import android.util.Log;
import android.view.View;
import immutablecollections.ImSet;
import rx.Notification;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Action0;
import rx.functions.Action2;
import rx.subjects.BehaviorSubject;
import rx.subscriptions.Subscriptions;
import javax.annotation.Nullable;
import java.util.*;
// must be called on the main thread
public final class RxDebugger {
private static final RxDebugger global = new RxDebugger();
public static RxDebugger get() {
return global;
}
static final String TAG = "RxDebugger";
private final Map, Stats> statsMap;
private ImSet> subscribers;
/** non-null when stepping */
@Nullable
private Queue stepping = null;
private final BehaviorSubject stepStatePublish;
private long headSubscriberId = 0;
private boolean suppressed = false;
RxDebugger() {
statsMap = new LinkedHashMap, Stats>(8);
subscribers = ImSet.empty();
stepStatePublish = BehaviorSubject.create(new StepState(false, 0));
}
public boolean isEnabled() {
return !suppressed;
}
void update(final Stats stats) {
if (suppressed) {
assert false : "Callers must check isEnabled.";
return;
}
if (!stats.active) {
throw new IllegalArgumentException();
}
@Nullable Stats r = statsMap.put(stats.subscriber, stats);
if (null != r) {
r.active = false;
stats.subscriberId = r.subscriberId;
} else {
stats.subscriberId = headSubscriberId++;
}
stats.nanos = System.nanoTime();
// do this to avoid an infinite recursion where dispatching to the debugger observers
// triggers more debug stats
suppressed = true;
try {
for (Subscriber super Stats> subscriber : subscribers) {
// check if the debug subscribers triggered a newer update on the stats subscriber
// if so, do not publish old stats
// this should probably trigger a warning (debug observer has side effects)
if (!stats.active) {
break;
}
try {
subscriber.onNext(stats);
} catch (Throwable t) {
// FIXME severe
Log.e(TAG, null, t);
}
}
} finally {
suppressed = false;
}
if (stats.active && stats.removed) {
stats.active = false;
statsMap.remove(stats.subscriber);
}
}
void deliver(Subscriber subscriber, Notification notification, Action2 action) {
if (null != stepping) {
stepping.add(new Step(subscriber, notification, action));
publishStepState();
} else {
action.call(subscriber, notification);
}
}
// STEPPING
public boolean step() {
if (null != stepping) {
@Nullable Step step = stepping.poll();
if (null != step) {
step.action.call(step.subscriber, step.notification);
publishStepState();
return true;
}
}
return false;
}
public void setStepping(boolean s) {
if (s) {
if (null == stepping) {
stepping = new LinkedList();
publishStepState();
}
} else {
if (null != stepping) {
for (@Nullable Step step; null != (step = stepping.poll()); ) {
step.action.call(step.subscriber, step.notification);
}
assert stepping.isEmpty();
stepping = null;
publishStepState();
}
}
}
private void publishStepState() {
stepStatePublish.onNext(createStepState());
}
private StepState createStepState() {
if (null != stepping) {
return new StepState(true, stepping.size());
} else {
return new StepState(false, 0);
}
}
// INTROSPECTION
public Observable getStepState() {
return stepStatePublish;
}
/**
* a stream of stats, one object per subscriber.
* Newer stats replace older stats for the same subscriber.
*/
public Observable getStats() {
return Observable.create(new Observable.OnSubscribe() {
@Override
public void call(final Subscriber super Stats> subscriber) {
subscriber.add(Subscriptions.create(new Action0() {
@Override
public void call() {
subscribers = subscribers.removing(subscriber);
}
}));
subscribers = subscribers.adding(subscriber);
for (Stats stats : new ArrayList(statsMap.values())) {
// this should probably trigger a warning (debug observer has side effects)
if (stats.active) {
try {
subscriber.onNext(stats);
} catch (Throwable t) {
// FIXME severe
Log.e(TAG, null, t);
}
}
}
}
});
}
public static final class Stats {
public static final int F_NEXT = 0x01;
public static final int F_COMPLETED = 0x02;
public static final int F_ERROR = 0x04;
public static final int F_FAILED = 0x08;
public static final int F_CONNECTED = 0x10;
public static final int F_DISCONNECTED = 0x20;
public final int flags;
public final Subscriber> subscriber;
@Nullable
public final View view;
public final boolean removed;
public final boolean connected;
public final int onNextCount;
public final int onCompletedCount;
public final int onErrorCount;
@Nullable
public final Notification mostRecentNotification;
// FAILED NOTIFICATIONS
// this is where a call to oNext, onCompleted, onError failed
public final int failedNotificationCount;
@Nullable
public final Notification mostRecentFailedNotification;
@Nullable
public final Throwable mostRecentFailedNotificationReason;
// internally updated
public long subscriberId;
public long nanos;
// internal
boolean active = true;
Stats(int flags, Subscriber> subscriber, @Nullable View view, boolean removed,
boolean connected,
int onNextCount, int onCompletedCount, int onErrorCount, @Nullable Notification mostRecentNotification,
int failedNotificationCount, @Nullable Notification mostRecentFailedNotification, @Nullable Throwable mostRecentFailedNotificationReason) {
this.flags = flags;
this.subscriber = subscriber;
this.view = view;
this.removed = removed;
this.connected = connected;
this.onNextCount = onNextCount;
this.onCompletedCount = onCompletedCount;
this.onErrorCount = onErrorCount;
this.mostRecentNotification = mostRecentNotification;
this.failedNotificationCount = failedNotificationCount;
this.mostRecentFailedNotification = mostRecentFailedNotification;
this.mostRecentFailedNotificationReason = mostRecentFailedNotificationReason;
}
}
public static final class StepState {
public final boolean stepping;
public final int stepCount;
StepState(boolean stepping, int stepCount) {
this.stepping = stepping;
this.stepCount = stepCount;
}
}
// internal
private static final class Step {
Subscriber subscriber;
Notification notification;
Action2 action;
Step(Subscriber subscriber, Notification notification, Action2 action) {
this.subscriber = subscriber;
this.notification = notification;
this.action = action;
}
}
}