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

com.stanfy.enroscar.goro.BoundGoro Maven / Gradle / Ivy

There is a newer version: 3.0.0-RC5
Show newest version
package com.stanfy.enroscar.goro;

import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
import android.os.IBinder;

import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import static com.stanfy.enroscar.goro.GoroFuture.IMMEDIATE;
import static com.stanfy.enroscar.goro.Util.checkMainThread;

/**
 * Handles tasks in multiple queues using Android service.
 * @see com.stanfy.enroscar.goro.GoroService
 */
public abstract class BoundGoro extends Goro implements ServiceConnection {

  /** Bind to {@link com.stanfy.enroscar.goro.GoroService}. */
  public abstract void bind();

  /** Unbind from {@link com.stanfy.enroscar.goro.GoroService}. */
  public abstract void unbind();

  /** Bind to {@link com.stanfy.enroscar.goro.GoroService} and unbind as soon as tasks are delegated. */
  public abstract void bindOneshot();

  /** Implementation. */
  static class BoundGoroImpl extends BoundGoro implements ServiceConnection {

    /** Instance of the context used to bind to GoroService. */
    private final Context context;

    /** Temporal array of listeners that must be added after getting service connection. */
    private final BaseListenersHandler scheduledListeners = new BaseListenersHandler(2);

    /** Postponed data. */
    private final ArrayList postponed = new ArrayList<>(7);

    /** Protects service instance. */
    private final Object lock = new Object();

    /** Instance from service. */
    private Goro service;

    /** Oneshot binding flag. */
    private boolean oneshot;

    BoundGoroImpl(final Context context) {
      this.context = context;
    }

    @Override
    public void bind() {
      synchronized (lock) {
        oneshot = false;
      }
      GoroService.bind(context, this);
    }

    @Override
    public void unbind() {
      synchronized (lock) {
        if (oneshot) {
          throw new IllegalStateException("bindOneshot() was already called. "
              + "You must call bind() to be able to call unbind()");
        }
        doUnbindLocked();
      }
    }

    @Override
    public void bindOneshot() {
      synchronized (lock) {
        oneshot = true;
      }
      GoroService.bind(context, this);
    }

    @Override
    public void onServiceConnected(final ComponentName name, final IBinder binder) {
      synchronized (lock) {
        if (service != null) {
          throw new GoroException("Already bound and got onServiceConnected from " + name);
        }
        service = Goro.from(binder);

        // delegate listeners
        if (!scheduledListeners.taskListeners.isEmpty()) {
          for (GoroListener listener : scheduledListeners.taskListeners) {
            service.addTaskListener(listener);
          }
          scheduledListeners.taskListeners.clear();
        }

        // delegate tasks
        if (!postponed.isEmpty()) {
          for (Postponed p : postponed) {
            p.act(service);
          }
          postponed.clear();
        }

        if (oneshot) {
          doUnbindLocked();
        }
      }
    }

    private void doUnbindLocked() {
      service = null;
      GoroService.unbind(context, this);
    }

    Goro getServiceObject() {
      return service;
    }

    boolean cancelPostponed(final Postponed p) {
      synchronized (lock) {
        return postponed.remove(p);
      }
    }

    @Override
    public void onServiceDisconnected(final ComponentName name) {
      synchronized (lock) {
        if (service != null) {
          throw new GoroException("GoroService disconnected while we are using it");
        }
      }
    }

    @Override
    public void addTaskListener(final GoroListener listener) {
      // main thread => no sync
      Goro service = this.service;
      if (service != null) {
        service.addTaskListener(listener);
      } else {
        scheduledListeners.addTaskListener(listener);
      }
    }

    @Override
    public void removeTaskListener(final GoroListener listener) {
      // main thread => no sync
      Goro service = this.service;
      if (service != null) {
        service.removeTaskListener(listener);
      } else {
        scheduledListeners.removeTaskListener(listener);
      }
    }

    @Override
    public  ObservableFuture schedule(final Callable task) {
      return schedule(DEFAULT_QUEUE, task);
    }

    @Override
    public  ObservableFuture schedule(String queueName, Callable task) {
      synchronized (lock) {
        if (service != null) {
          return service.schedule(queueName, task);
        } else {
          BoundFuture future = new BoundFuture<>(queueName, task);
          postponed.add(future);
          return future;
        }
      }
    }

    @Override
    public Executor getExecutor(final String queueName) {
      synchronized (lock) {
        if (service != null) {
          return service.getExecutor(queueName);
        }
        return new PostponeExecutor(queueName);
      }
    }

    @Override
    protected void removeTasksInQueue(final String queueName) {
      synchronized (lock) {
        if (service != null) {
          service.clear(queueName);
        }
        postponed.add(new ClearAction(queueName));
      }
    }

    /** Some postponed action. */
    private interface Postponed {
      void act(Goro goro);
    }

    /** Recorded task info. */
    private static final class RunnableData implements Postponed {
      /** Queue name. */
      final String queue;
      /** Runnable action. */
      final Runnable command;

      RunnableData(final String queue, final Runnable command) {
        this.queue = queue;
        this.command = command;
      }

      @Override
      public void act(final Goro goro) {
        goro.getExecutor(queue).execute(command);
      }
    }

    /** Postponed clear call. */
    private static final class ClearAction implements Postponed {
      /** Queue name. */
      private final String queueName;

      ClearAction(final String queueName) {
        this.queueName = queueName;
      }

      @Override
      public void act(final Goro goro) {
        goro.clear(queueName);
      }
    }

    /** Executor implementation. */
    private final class PostponeExecutor implements Executor {

      /** Queue name. */
      private final String queueName;

      private PostponeExecutor(final String queueName) {
        this.queueName = queueName;
      }

      @Override
      public void execute(@SuppressWarnings("NullableProblems") final Runnable command) {
        synchronized (lock) {
          if (service != null) {
            service.getExecutor(queueName).execute(command);
          } else {
            postponed.add(new RunnableData(queueName, command));
          }
        }
      }
    }

    /** Postponed scheduled future. */
    private final class BoundFuture implements ObservableFuture, Postponed {

      /** Queue name. */
      final String queue;
      /** Task instance. */
      final Callable task;

      /** Attached Goro future. */
      private GoroFuture goroFuture;

      /** Cancel flag. */
      private boolean canceled;

      /** Observers list. */
      private PendingObserversList pendingObservers;

      private BoundFuture(final String queue, final Callable task) {
        this.queue = queue;
        this.task = task;
      }

      @Override
      public synchronized void act(final Goro goro) {
        goroFuture = (GoroFuture) goro.schedule(queue, task);
        if (pendingObservers != null) {
          pendingObservers.execute();
          pendingObservers = null;
        }
        notifyAll();
      }

      @Override
      public synchronized boolean cancel(boolean mayInterruptIfRunning) {
        if (goroFuture != null) {
          return goroFuture.cancel(mayInterruptIfRunning);
        }
        if (!canceled) {
          cancelPostponed(this);
          pendingObservers = null;
          canceled = true;
        }
        notifyAll();
        return true;
      }

      @Override
      public synchronized boolean isCancelled() {
        if (goroFuture != null) {
          return goroFuture.isCancelled();
        }
        return canceled;
      }

      @Override
      public synchronized boolean isDone() {
        return canceled || goroFuture != null && goroFuture.isDone();
      }

      @Override
      public synchronized T get() throws InterruptedException, ExecutionException {
        // delegate
        if (goroFuture != null) {
          return goroFuture.get();
        }
        if (checkMainThread()) {
          throw new GoroException("Blocking main thread here will lead to a deadlock");
        }

        if (canceled) {
          throw new CancellationException("Task was canceled");
        }

        // wait for act() or cancel()
        wait();

        // either we got a delegate
        if (goroFuture != null) {
          return goroFuture.get();
        }
        // or we are canceled
        if (canceled) {
          throw new CancellationException("Task was canceled");
        }

        // wtf?
        throw new IllegalStateException("get() is unblocked but there is neither result"
            + " nor cancellation");
      }

      @Override
      public synchronized T get(final long timeout, @SuppressWarnings("NullableProblems") final TimeUnit unit)
          throws InterruptedException, ExecutionException, TimeoutException {
        // delegate
        if (goroFuture != null) {
          return goroFuture.get(timeout, unit);
        }
        if (checkMainThread()) {
          throw new GoroException("Blocking main thread here will lead to a deadlock");
        }

        if (canceled) {
          throw new CancellationException("Task was canceled");
        }

        // wait for act() or cancel()
        wait(unit.toMillis(timeout));

        // either we got a delegate
        if (goroFuture != null) {
          return goroFuture.get();
        }
        // or we are canceled
        if (canceled) {
          throw new CancellationException("Task was canceled");
        }

        // otherwise it's a timeout
        throw new TimeoutException();
      }

      @Override
      public synchronized void subscribe(final Executor executor, final FutureObserver observer) {
        if (goroFuture != null) {
          goroFuture.subscribe(executor, observer);
          return;
        }
        if (canceled) {
          return;
        }

        if (pendingObservers == null) {
          pendingObservers = new PendingObserversList();
        }
        pendingObservers.add(new GoroFuture.ObserverRunnable<>(observer, null), executor);
      }

      @Override
      public void subscribe(final FutureObserver observer) {
        subscribe(IMMEDIATE, observer);
      }

      /** List of pending observers. */
      private final class PendingObserversList extends ExecutionObserversList {
        @Override
        protected void executeObserver(final Executor executor, final Runnable what) {
          GoroFuture.ObserverRunnable runnable = (GoroFuture.ObserverRunnable) what;
          runnable.future = goroFuture;
          goroFuture.observers.add(what, executor);
        }
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy