blob: d6300d8e713d26a8f3d886e89865cb97ba208804 [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 org.eclipse.jpt.common.utility.command.Command;
import org.eclipse.jpt.common.utility.internal.StringTools;
import org.eclipse.jpt.common.utility.internal.SynchronizedBoolean;
/**
* A <em>synchronizing</em> command can be used to coordinate the execution of
* the {@link Thread thread} that <em>creates</em> the <em>synchronizing</em>
* command (Thread A, the "primary" thread) with another {@link Thread thread}
* that <em>executes</em> the <em>synchronizing</em> command (Thread B, the
* "secondary" thread).
* <p>
* Typically, Thread A dispatches {@Command commands} to a
* {@link org.eclipse.jpt.common.utility.command.CommandExecutor command
* executor}) that executes the {@Command commands} asynchronously on Thread B.
* A <em>synchronizing</em> command allows Thread A to
* dispatch a <em>synchronizing</em> command to Thread B and suspend until
* the {@link #command} held by the <em>synchronizing</em> command
* has executed (on <em>Thread B</em>). (This can be useful when Thread B is the
* UI {@link Thread thread} and the {@link #command} must modify a UI widget.)
* Once the {@link #command} held by the <em>synchronizing</em> command has
* executed, Thread B suspends and notifies Thread A to proceed. Thread A can
* optionally execute yet another {@Command command} (on <em>Thread A</em>).
* Once Thread A has executed its command, it must notify Thread B that it may
* proceed (now independently of Thread A).
* <p>
* <strong>Timeouts:</strong> If Thread A specifies a time-limit
* ({@link #waitForExecution(long)}) for its suspension, and a timeout occurs
* (i.e. it takes too long for the <em>synchronizing</em> command to begin
* executing or its {@link #command} takes too long to execute),
* the {@link #command} held by the <em>synchronizing</em> command will
* <em>still be executed</em>; but Thread A will not be notified when the
* execution is complete. [It seems reasonable that the {@link #command} always
* execute, since, if it is too long-running, it will execute anyway....]
* <p>
* <strong>Synchronous Execution:</strong> If the <em>synchronizing</em> command
* is <em>created</em> and <em>executed</em> on the same {@link Thread thread},
* the assumption is Thread A and Thread B are the same {@link Thread thread}
* and all {@Command commands} are executing synchronously (e.g. during testing
* or "batch" executing).
* In that situation, the <em>synchronizing</em> command will <em>not</em>
* suspend its {@link Thread thread}.
*/
public class SynchronizingCommand
implements Command
{
private final Thread thread = Thread.currentThread(); // Thread A
private final SynchronizedBoolean flag = new SynchronizedBoolean(false);
private volatile boolean expired = false; // guarded by #flag
private final Command command;
/**
* Construct a <em>synchronizing</em> command that does <em>not</em> execute
* a {@link #command} on the "secondary" {@link Thread thread} (Thread B),
* but simply suspends that thread until the <em>synchronizing</em> command
* is {@link #release() released} by the "primary" thread (Thread A).
*/
public SynchronizingCommand() {
this(Command.Null.instance());
}
/**
* Construct a <em>synchronizing</em> command that executes the specified
* a command on the "secondary" {@link Thread thread} (Thread B),
* and then suspends that thread until the <em>synchronizing</em> command
* is {@link #release() released} by the "primary" thread (Thread A).
*/
public SynchronizingCommand(Command command) {
super();
if (command == null) {
throw new NullPointerException();
}
this.command = command;
}
/**
* Typically called by a
* {@link org.eclipse.jpt.common.utility.command.CommandExecutor command
* executor} executing Thread B.
* <p>
* The "synchronizing" command has reached the front of the command queue
* and is executing on Thread B.
* Execute the wrapped command and return control to the Thread A.
* Suspend Thread B until Thread A resets the flag.
* <p>
* If the "synchronizing" command is executed (synchronously) on the same
* thread where the command was created (i.e. Thread A),
* it will simply set the flag appropriately and return.
* Otherwise, the current thread would stall indefinitely.
* <p>
* If Thread A has timed out, skip any further synchronization.
*/
public void execute() {
this.command.execute();
synchronized (this.flag) {
if (this.expired) {
// reset the 'expired' flag and exit (don't wait for Thread A)
this.expired = false;
return;
}
// Thread A is still waiting - notify it
this.flag.setTrue();
}
if (Thread.currentThread() != this.thread) {
try {
this.flag.waitUntilFalse();
} catch (InterruptedException ex) {
// something has interrupted Thread B (the "secondary" thread);
// it must not want us to wait for Thread A to finish executing...
Thread.currentThread().interrupt(); // set the Thread's interrupt status
}
}
}
/**
* Called by Thread A to wait on Thread B to execute its command.
* @see #waitForExecution(long)
*/
public void waitForExecution() throws InterruptedException {
this.flag.waitUntilTrue();
}
/**
* Called by Thread A to wait on Thread B to execute its command.
* @see #waitForExecution()
*/
public boolean waitForExecution(long timeout) throws InterruptedException {
synchronized (this.flag) {
if (this.flag.waitUntilTrue(timeout) ) {
return true;
}
this.expired = true;
return false;
}
}
/**
* Called by Thread A to indicate it has executed its command.
* At this point the threads are independent and no longer synchronized
* with each other.
*/
public void release() {
this.flag.setFalse();
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.flag);
}
}