diff options
Diffstat (limited to 'common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/command/RepeatingCommandState.java')
-rw-r--r-- | common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/command/RepeatingCommandState.java | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/command/RepeatingCommandState.java b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/command/RepeatingCommandState.java new file mode 100644 index 0000000000..20a7463b0b --- /dev/null +++ b/common/plugins/org.eclipse.jpt.common.utility/src/org/eclipse/jpt/common/utility/internal/command/RepeatingCommandState.java @@ -0,0 +1,220 @@ +/******************************************************************************* + * Copyright (c) 2012 Oracle. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0, which accompanies this distribution + * and is available at http://www.eclipse.org/legal/epl-v10.html. + * + * Contributors: + * Oracle - initial API and implementation + ******************************************************************************/ +package org.eclipse.jpt.common.utility.internal.command; + +import org.eclipse.jpt.common.utility.internal.StringTools; +import org.eclipse.jpt.common.utility.internal.SynchronizedObject; + +/** + * Provide the state machine to support minimal repeat command executions. + */ +public class RepeatingCommandState { + /** + * The current state. + */ + private final SynchronizedObject<State> state; + + /** + * The initial {@link #state} is {@link #STOPPED}. + * Clients must call {@link #start()} before the command can be + * executed. + */ + private enum State { + STOPPED, + READY, + PRE_EXECUTION, + EXECUTING, + REPEAT, + STOPPING + } + + + // ********** construction ********** + + /** + * Construct a repeating command state. + */ + public RepeatingCommandState() { + super(); + // use the command wrapper as the mutex so it is freed up by the wait in #stop() + this.state = new SynchronizedObject<State>(State.STOPPED, this); + } + + + /** + * Set the {@link #state} to {@link State#READY READY}. + * @exception IllegalStateException if the command wrapper is not + * {@link State#STOPPED STOPPED}. + */ + public synchronized void start() { + switch (this.state.getValue()) { + case STOPPED: + this.state.setValue(State.READY); + break; + case READY: + case PRE_EXECUTION: + case EXECUTING: + case REPEAT: + case STOPPING: + throw this.buildISE(); + } + } + + /** + * A client has requested an execution. + * Return whether we are ready to begin a new execution "cycle". + * If an execution is already under way, return <code>false</code>; + * but set the {@link #state} to {@link State#REPEAT REPEAT} + * so another execution will occur once the current + * execution is complete. + * <p> + * <strong>NB:</strong> This method has possible side-effects: + * The value of {@link #state} may be changed. + */ + public synchronized boolean isReadyToStartExecutionCycle() { + switch (this.state.getValue()) { + case STOPPED: + // execution is not allowed + return false; + case READY: + // start a new execution, possibly asynchronously + this.state.setValue(State.PRE_EXECUTION); + return true; + case PRE_EXECUTION: + // no need to set 'state' to PRE_EXECUTION again, + // the command has not yet begun executing + return false; + case EXECUTING: + // set 'state' to REPEAT so a new execution will occur once the current one is finished + this.state.setValue(State.REPEAT); + return false; + case REPEAT: + // no need to set 'state' to REPEAT again + return false; + case STOPPING: + // no further executions are allowed + return false; + } + throw this.buildISE(); + } + + /** + * An execution "cycle" is ready to begin. + * Make sure a call to {@link #stop()} did not slip in between the + * dispatching of the initial wrapped command execution (when + * {@link #isReadyToStartExecutionCycle()} returns <code>true</code>) and the + * actual execution of the wrapped command. + * This can happen if the wrapped command was dispatched asynchronously. + * <p> + * This method should be called from the actual execution method, before it + * starts looping. + */ + public synchronized boolean wasStoppedBeforeFirstExecutionCouldStart() { + switch (this.state.getValue()) { + case STOPPED: + // a call to stop() slipped in before the command could start + // executing, probably because it was dispatched asynchronously + // by 'startCommandExecutor' (e.g. in a job) + return true; + case PRE_EXECUTION: + this.state.setValue(State.EXECUTING); + return false; + case READY: + case EXECUTING: + case REPEAT: + case STOPPING: + throw this.buildISE(); + } + throw this.buildISE(); + } + + /** + * The current execution has finished. + * Return whether we should begin another execution because a call to + * execute occurred <em>during</em> the just-completed execution. + */ + public synchronized boolean isRepeat() { + switch (this.state.getValue()) { + case STOPPED: + case READY: + case PRE_EXECUTION: + throw this.buildISE(); + case EXECUTING: + // execution has finished and there are no outstanding requests for another; return to READY + this.state.setValue(State.READY); + return false; + case REPEAT: + // set 'state' back to EXECUTING and begin another execution + this.state.setValue(State.EXECUTING); + return true; + case STOPPING: + // a client initiated a "stop" during the previous execution; + // mark the "stop" complete and perform no more executions + this.state.setValue(State.STOPPED); + return false; + } + throw this.buildISE(); + } + + /** + * Return whether the execution "cycle" is "quiesced" (i.e. there are no + * outstanding execution requests). + */ + public boolean isQuiesced() { + return this.state.getValue() != State.REPEAT; + } + + /** + * Set {@link #state} so no further executions occur. + * @exception IllegalStateException if the command wrapper is already + * {@link State#STOPPED STOPPED} or {@link State#STOPPING STOPPING}. + */ + public synchronized void stop() throws InterruptedException { + switch (this.state.getValue()) { + case READY: + case PRE_EXECUTION: + // simply set 'state' to STOPPED and return + this.state.setValue(State.STOPPED); + break; + case EXECUTING: + case REPEAT: + // set 'state' to STOPPING and wait until the current execution has finished + this.state.setValue(State.STOPPING); + this.waitUntilStopped(); + break; + case STOPPED: + case STOPPING: + throw this.buildISE(); + } + } + + /** + * This wait will free up the command wrapper's synchronized methods + * (since the command wrapper is the mutex for {@link #state}). + * <p> + * If the thread that called {@link #stop()} is interrupted while waiting + * for the current command to finish executing on another thread, + * {@link #state} will still be {@link State#STOPPING STOPPING}, so the loop + * begun by the command wrapper will still stop and set {@link #state} to + * {@link State#STOPPED STOPPED}, we just won't wait around for it.... + */ + private void waitUntilStopped() throws InterruptedException { + this.state.waitUntilValueIs(State.STOPPED); + } + + private IllegalStateException buildISE() { + return new IllegalStateException("state: " + this.state); //$NON-NLS-1$ + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.state); + } +} |