blob: 7baa423bb8f58ae524845268f2b2748eff92c1a3 [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 java.util.concurrent.ThreadFactory;
import org.eclipse.jpt.common.utility.ExceptionHandler;
import org.eclipse.jpt.common.utility.command.Command;
import org.eclipse.jpt.common.utility.command.RepeatingCommand;
import org.eclipse.jpt.common.utility.internal.ConsumerThreadCoordinator;
import org.eclipse.jpt.common.utility.internal.SimpleThreadFactory;
import org.eclipse.jpt.common.utility.internal.StackTrace;
import org.eclipse.jpt.common.utility.internal.StringTools;
import org.eclipse.jpt.common.utility.internal.SynchronizedBoolean;
/**
* This repeating command will perform a client-supplied command in a separate
* thread, allowing calls to {@link org.eclipse.jpt.common.utility.command.Command#execute()}
* to return immediately.
*/
public class AsynchronousRepeatingCommandWrapper
implements RepeatingCommand
{
/**
* This flag is shared with the execution/consumer thread. Setting it to
* <code>true</code> will trigger the execution to begin or, if the
* execution is currently under way, to execute again, once the current
* execution is complete.
*/
final SynchronizedBoolean executeFlag = new SynchronizedBoolean(false);
/**
* Most of the thread-related behavior is delegated to this coordinator.
*/
private final ConsumerThreadCoordinator consumerThreadCoordinator;
/**
* 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 AsynchronousRepeatingCommandWrapperTests.testDEBUG()
private static final boolean DEBUG = false;
// ********** construction **********
/**
* Construct an asynchronous repeating command that executes the specified
* command.
* Use simple JDK thread(s) for the execution thread(s).
* Allow the execution thread(s) to be assigned JDK-generated names.
* Any exceptions thrown by the consumer will be handled by the
* specified exception handler.
*/
public AsynchronousRepeatingCommandWrapper(Command command, ExceptionHandler exceptionHandler) {
this(command, null, null, exceptionHandler);
}
/**
* Construct an asynchronous repeating command that executes the specified
* command. Assign the execution thread(s) the specified name.
* Use simple JDK thread(s) for the execution thread(s).
* Any exceptions thrown by the consumer will be handled by the
* specified exception handler.
*/
public AsynchronousRepeatingCommandWrapper(Command command, String threadName, ExceptionHandler exceptionHandler) {
this(command, null, threadName, exceptionHandler);
}
/**
* Construct an asynchronous repeating command that executes the specified
* command.
* Use the specified thread factory to construct the synchronization thread(s).
* Assign the synchronization thread(s) the specified name.
* Any exceptions thrown by the consumer will be handled by the
* specified exception handler.
*/
public AsynchronousRepeatingCommandWrapper(Config config) {
this(config.getCommand(), config.getThreadFactory(), config.getThreadName(), config.getExceptionHandler());
}
/**
* Construct an asynchronous repeating command that executes the specified
* command.
* Use the specified thread factory to construct the synchronization thread(s).
* Assign the synchronization thread(s) the specified name.
* Any exceptions thrown by the consumer will be handled by the
* specified exception handler.
*/
public AsynchronousRepeatingCommandWrapper(Command command, ThreadFactory threadFactory, String threadName, ExceptionHandler exceptionHandler) {
super();
this.consumerThreadCoordinator = new ConsumerThreadCoordinator(this.buildConsumer(command), threadFactory, threadName, exceptionHandler);
}
ConsumerThreadCoordinator.Consumer buildConsumer(Command command) {
return new Consumer(command);
}
// ********** RepeatingCommand implementation **********
/**
* Build and start the execution thread, but postpone the first
* execution until requested, i.e. via a call to {@link #execute()}.
* <p>
* Note: We don't clear the "execute" flag here; so if the flag has
* been set to <code>true</code> <em>before</em> getting here, the first
* execution will start promptly (albeit, asynchronously).
* The "execute" flag will be set if:<ul>
* <li>{@link #execute()} was called after the repeating command was
* constructed but before {@link #start()} was called; or
* <li>{@link #execute()} was called after {@link #stop()} was called
* but before {@link #start()} was called (to restart the repeating
* command); or
* <li>{@link #stop()} was called when there was an outstanding request
* for an execution (i.e. the "execute" flag was <code>true</code> at
* the time {@link #stop()} was called)
* </ul>
*
* @exception IllegalStateException if the command has already been started
*/
public void start() {
this.consumerThreadCoordinator.start();
}
/**
* Set the "execute" flag so the execution thread will either<ul>
* <li>if the thread is quiesced, start an execution immediately, or
* <li>if the thread is currently executing, execute again,
* once the current execution is complete
* </ul>
*/
public void execute() {
synchronized (this.executeFlag) {
if (DEBUG) {
if (this.executeFlag.isFalse()) {
this.stackTraces.clear();
}
this.stackTraces.add(new StackTrace());
}
this.executeFlag.setTrue();
}
}
/**
* Interrupt the execution thread so that it stops executing at the
* end of the current execution. Suspend the current thread until
* the execution thread is finished executing. If any uncaught
* exceptions were thrown while the execution thread was executing,
* wrap them in a composite exception and throw the composite exception.
*
* @exception IllegalStateException if the coordinator has not been started
*/
public void stop() throws InterruptedException {
this.consumerThreadCoordinator.stop();
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.consumerThreadCoordinator);
}
// ********** consumer **********
/**
* This implementation of
* {@link org.eclipse.jpt.common.utility.internal.ConsumerThreadCoordinator.Consumer}
* will execute the client-supplied command.
* It will wait until the shared "execute" flag is set to execute the
* command. Once the comand is executed, the thread will quiesce until
* the flag is set again. If the flag was set during the execution of the
* command (either recursively by the command itself or by another thread),
* the command will be re-executed immediately. Stop the thread by calling
* {@link Thread#interrupt()}.
*/
class Consumer
implements ConsumerThreadCoordinator.Consumer
{
/**
* The client-supplied command that executes on the
* execution/consumer thread.
*/
private final Command command;
Consumer(Command command) {
super();
if (command == null) {
throw new NullPointerException();
}
this.command = command;
}
/**
* Wait until the "execute" flag is set,
* then clear it and allow the command to execute.
*/
public void waitForProducer() throws InterruptedException {
AsynchronousRepeatingCommandWrapper.this.executeFlag.waitToSetFalse();
}
/**
* Execute the client-supplied command.
*/
public void consume() {
this.command.execute();
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.command);
}
}
// ********** config **********
/**
* Config useful for instantiating an {@link AbstractAsynchronousCommandExecutor}.
*/
public interface Config {
Command getCommand();
ThreadFactory getThreadFactory();
String getThreadName();
ExceptionHandler getExceptionHandler();
}
/**
* Config useful for instantiating an {@link AbstractAsynchronousCommandExecutor}.
*/
protected abstract static class SimpleConfig
implements Config
{
private volatile Command command;
private volatile ThreadFactory threadFactory;
private volatile String threadName;
private volatile ExceptionHandler exceptionHandler;
protected SimpleConfig() {
super();
this.command = this.buildDefaultCommand();
this.threadFactory = this.buildDefaultThreadFactory();
this.threadName = this.buildDefaultThreadName();
this.exceptionHandler = this.buildDefaultExceptionHandler();
}
protected SimpleConfig(Command command, ThreadFactory threadFactory, String threadName, ExceptionHandler exceptionHandler) {
super();
this.command = command;
this.threadFactory = threadFactory;
this.threadName = threadName;
this.exceptionHandler = exceptionHandler;
}
public void setCommand(Command command) {
this.command = command;
}
public Command getCommand() {
return this.command;
}
protected abstract Command buildDefaultCommand();
public void setThreadFactory(ThreadFactory threadFactory) {
this.threadFactory = threadFactory;
}
public ThreadFactory getThreadFactory() {
return this.threadFactory;
}
protected ThreadFactory buildDefaultThreadFactory() {
return SimpleThreadFactory.instance();
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
public String getThreadName() {
return this.threadName;
}
protected String buildDefaultThreadName() {
return null;
}
public void setExceptionHandler(ExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
public ExceptionHandler getExceptionHandler() {
return this.exceptionHandler;
}
protected ExceptionHandler buildDefaultExceptionHandler() {
return ExceptionHandler.Runtime.instance();
}
}
}