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

jalse.actions.AbstractManualActionContext Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
package jalse.actions;

import static jalse.actions.Actions.emptyActionBindings;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A abstract implementation of {@link MutableActionContext} that is designed to be used manually.
 * This class should be used whenever controlling the execution state of work is important. The
 * action should be performed with {@link #performAction()}. This is a convenience class for
 * creating an {@link ActionEngine}.
 *
 * @author Elliot Ford
 *
 * @param 
 *            Actor type.
 *
 * @see ManualWorkQueue
 * @see ManualActionEngine
 * @see ForkJoinActionEngine
 */
public abstract class AbstractManualActionContext extends BaseActionContext implements
	Comparable> {

    private static final Logger logger = Logger.getLogger(AbstractManualActionContext.class.getName());

    private final Lock lock;
    private final Condition ran;
    private final AtomicBoolean cancelled;
    private final AtomicBoolean done;
    private final AtomicBoolean performing;
    private final AtomicLong estimated;

    /**
     * Creates a new instance of AbstractManualActionContext with the supplied engine and action.
     *
     * @param engine
     *            Parent engine.
     * @param action
     *            Action this context is for.
     */
    protected AbstractManualActionContext(final ActionEngine engine, final Action action) {
	this(engine, action, emptyActionBindings());
    }

    /**
     * Creates a new instance of AbstractManualActionContext with the supplied engine, action and
     * source bindings.
     *
     * @param engine
     *            Parent engine.
     * @param action
     *            The action this context is for.
     * @param sourceBindings
     *            Bindings to shallow copy.
     */
    protected AbstractManualActionContext(final ActionEngine engine, final Action action,
	    final ActionBindings sourceBindings) {
	super(engine, action, sourceBindings);
	lock = new ReentrantLock();
	ran = lock.newCondition();
	done = new AtomicBoolean();
	performing = new AtomicBoolean();
	cancelled = new AtomicBoolean();
	estimated = new AtomicLong();
    }

    /**
     * Used to add this context as work to a queue.
     */
    protected abstract void addAsWork();

    @Override
    public void await() throws InterruptedException {
	if (isPeriodic()) {
	    throw new UnsupportedOperationException("Cannot await periodic actions");
	}

	lock.lockInterruptibly();
	try {
	    while (!isDone()) {
		ran.await(); // Await signal
	    }
	} finally {
	    lock.unlock();
	}
    }

    @Override
    public boolean cancel() {
	if (isDone()) {
	    return false;
	}

	cancelled.set(true);
	done.set(true);

	removeAsWork(); // Remove from engines work queue

	if (!performing.get()) { // Wait if currently executing
	    signalRan();
	}

	return true;
    }

    @Override
    public int compareTo(final AbstractManualActionContext o) {
	final long estimated = getEstimated();
	final long otherEstimated = o.getEstimated();
	return estimated < otherEstimated ? -1 : estimated == otherEstimated ? 0 : 1;
    }

    /**
     * Gets the ideal estimated execution time (nanos).
     *
     * @return gets the estimated time of execution.
     *
     * @see System#nanoTime()
     */
    public long getEstimated() {
	return estimated.get();
    }

    @Override
    public boolean isCancelled() {
	return cancelled.get();
    }

    @Override
    public boolean isDone() {
	return done.get();
    }

    /**
     * Whether the action is currently being performed.
     *
     * @return Whether performing action.
     */
    public boolean isPeforming() {
	return performing.get();
    }

    /**
     * Performs the action (setting context state).
     *
     * @throws InterruptedException
     *             If action throws this or this is interrupted.
     */
    public void performAction() throws InterruptedException {
	if (isDone()) {
	    return;
	}

	performing.set(true);

	try {
	    getAction().perform(this); // Execute action
	} catch (final InterruptedException e) {
	    cancelled.set(true);
	    throw e;
	} catch (final Exception e) { // Continue
	    logger.log(Level.WARNING, "Error performing action", e);
	} finally {
	    performing.set(false);
	    done.set(true);
	    signalRan(); // Wake up awaiting
	}

	if (isCancelled()) {
	    return;
	}

	if (isPeriodic()) {
	    estimated.set(System.nanoTime() + getPeriod(TimeUnit.NANOSECONDS));
	    done.set(false);
	    addAsWork(); // Add to engines work queue
	}
    }

    /**
     * Used to remove context as work from a queue.
     */
    protected abstract void removeAsWork();

    /**
     * Resets the context to its starting state.
     */
    protected void reset() {
	done.set(false);
	performing.set(false);
	cancelled.set(false);
	estimated.set(0L);
    }

    @Override
    public void schedule() {
	if (!isDone()) {
	    estimated.set(System.nanoTime() + getInitialDelay(TimeUnit.NANOSECONDS));
	    addAsWork(); // Add to engines work queue
	}
    }

    @Override
    public void scheduleAndAwait() throws InterruptedException {
	schedule();
	await();
    }

    private void signalRan() {
	lock.lock();
	try {
	    ran.signalAll();
	} finally {
	    lock.unlock();
	}
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy