blob: fcdb26b33bf3770d92013e7985420c06c1761896 [file] [log] [blame]
/*******************************************************************************
* 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 java.util.ArrayList;
import org.eclipse.jpt.common.utility.ExceptionHandler;
import org.eclipse.jpt.common.utility.command.Command;
import org.eclipse.jpt.common.utility.command.CommandExecutor;
import org.eclipse.jpt.common.utility.command.RepeatingCommand;
import org.eclipse.jpt.common.utility.internal.StackTrace;
import org.eclipse.jpt.common.utility.internal.StringTools;
/**
* Wrap a repeating {@link Command}.
*/
public class RepeatingCommandWrapper
implements RepeatingCommand
{
/**
* The client-supplied command that performs the execution. It may
* trigger further calls to {@link #execute()} (i.e. the <em>wrapped</em>
* command's execution may recurse back to the client code that executes the
* <em>wrapper</em> command).
*/
/* CU private */ final Command command;
/**
* The command executed whenever the wrapped {@link #command} must be
* executed and it is not already executing. If the wrapped {@link #command}
* is already executing, it will simply be re-executed directly (once it has
* completed its current execution), as opposed to calling the start
* command.
*/
private final Command startCommand;
/**
* The client-supplied command executor that provides the context for the
* {@link #startCommand start command}. By default, the start command is
* executed directly; but this executor provides a hook for executing the
* {@link #startCommand start command} asynchronously; after which,
* subsequent overlapping executions are executed synchronously.
*/
private final CommandExecutor startCommandExecutor;
/**
* This handles the exceptions thrown by the <em>wrapped</em> command.
*/
final ExceptionHandler exceptionHandler;
/**
* The command wrapper's state.
*/
final RepeatingCommandState state;
/**
* List of stack traces for each (repeating) invocation of the command,
* starting with the initial invocation. The list is cleared with each
* initial invocation of the command.
*/
private final ArrayList<StackTrace> stackTraces = DEBUG ? new ArrayList<StackTrace>() : null;
// see RepeatingCommandWrapperTests.testDEBUG()
private static final boolean DEBUG = false;
// ********** construction **********
/**
* Construct a repeating command wrapper that executes the specified
* command. Any exceptions thrown by the command will be handled by the
* specified exception handler.
*/
public RepeatingCommandWrapper(Command command, ExceptionHandler exceptionHandler) {
this(command, CommandExecutor.Default.instance(), exceptionHandler);
}
/**
* Construct a repeating command wrapper that executes the specified
* command and uses the specified command executor to execute the wrapped
* command whenever it is not already executing.
* Any exceptions thrown by the command will be handled by the
* specified exception handler.
*/
public RepeatingCommandWrapper(Command command, CommandExecutor startCommandExecutor, ExceptionHandler exceptionHandler) {
super();
if ((command == null) || (startCommandExecutor == null) || (exceptionHandler == null)) {
throw new NullPointerException();
}
this.command = command;
this.startCommandExecutor = startCommandExecutor;
this.startCommand = this.buildStartCommand();
this.exceptionHandler = exceptionHandler;
this.state = this.buildState();
}
private Command buildStartCommand() {
return new StartCommand();
}
private RepeatingCommandState buildState() {
return new RepeatingCommandState();
}
// ********** RepeatingCommand implementation **********
public void start() {
this.state.start();
}
/**
* It is possible to come back here if the wrapped command recurses
* to the client and triggers another execution.
*/
public synchronized void execute() {
if (this.state.isReadyToStartExecutionCycle()) {
if (DEBUG) {
this.stackTraces.clear();
this.stackTraces.add(new StackTrace());
}
this.executeStartCommand();
} else {
if (DEBUG) {
this.stackTraces.add(new StackTrace());
}
}
}
/* private protected */ void executeStartCommand() {
this.startCommandExecutor.execute(this.startCommand);
}
public void stop() throws InterruptedException {
this.state.stop();
}
/**
* The start command.
* @see #startCommandExecutor
*/
/* CU private */ class StartCommand
implements Command
{
public void execute() {
RepeatingCommandWrapper.this.execute_();
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, RepeatingCommandWrapper.this.command);
}
}
/**
* This method will be called only once per execution cycle.
* Any further calls to {@link #execute()} will
* simply set the {@link #state} to "repeat",
* causing the command to execute again.
*/
/* CU private */ void execute_() {
if (this.state.wasStoppedBeforeFirstExecutionCouldStart()) {
return;
}
do {
this.executeCommand();
} while (this.state.isRepeat());
}
/**
* Execute the client-supplied command. Do not allow any unhandled
* exceptions to kill the wrapper. Pass to the exception handler.
* @see NotifyingRepeatingCommandWrapper
*/
/* private protected */ void executeCommand() {
try {
this.command.execute();
} catch (Throwable ex) {
this.exceptionHandler.handleException(ex);
}
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.command);
}
}