| /******************************************************************************* |
| * 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); |
| } |
| } |