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

io.nextop.rx.RxDebugger Maven / Gradle / Ivy

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 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 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;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy