blob: ee294e847fcee13ad0cb63a01a5d8f6e03527022 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2010 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.utility.internal.synchronizers;
import java.util.concurrent.ThreadFactory;
import org.eclipse.jpt.utility.Command;
import org.eclipse.jpt.utility.internal.ConsumerThreadCoordinator;
import org.eclipse.jpt.utility.internal.SynchronizedBoolean;
import org.eclipse.jpt.utility.synchronizers.Synchronizer;
/**
* This synchronizer will perform synchronizations in a separate thread,
* allowing calls to {@link Synchronizer#synchronize()} to return immediately.
* <p>
* <strong>NB:</strong> The client-supplied command should handle any exceptions
* appropriately (e.g. log the exception and return gracefully so the thread
* can continue the synchronization process).
*/
public class AsynchronousSynchronizer
implements Synchronizer
{
/**
* This flag is shared with the synchronization/consumer thread. Setting it to true
* will trigger the synchronization to begin or, if the synchronization is
* currently executing, to execute again, once the current execution is
* complete.
*/
final SynchronizedBoolean synchronizeFlag = new SynchronizedBoolean(false);
/**
* Most of the thread-related behavior is delegated to this coordinator.
*/
private final ConsumerThreadCoordinator consumerThreadCoordinator;
// ********** construction **********
/**
* Construct an asynchronous synchronizer that uses the specified command to
* perform the synchronization.
* Use simple JDK thread(s) for the synchronization thread(s).
* Allow the synchronization thread(s) to be assigned
* JDK-generated names.
*/
public AsynchronousSynchronizer(Command command) {
this(command, null, null);
}
/**
* Construct an asynchronous synchronizer that uses the specified command to
* perform the synchronization.
* Use the specified thread factory to construct the synchronization thread(s).
* Allow the synchronization thread(s) to be assigned
* JDK-generated names.
*/
public AsynchronousSynchronizer(Command command, ThreadFactory threadFactory) {
this(command, threadFactory, null);
}
/**
* Construct an asynchronous synchronizer that uses the specified command to
* perform the synchronization. Assign the synchronization thread(s) the specified
* name.
* Use simple JDK thread(s) for the synchronization thread(s).
*/
public AsynchronousSynchronizer(Command command, String threadName) {
this(command, null, threadName);
}
/**
* Construct an asynchronous synchronizer that uses the specified command to
* perform the synchronization.
* Use the specified thread factory to construct the synchronization thread(s).
* Assign the synchronization thread(s) the specified
* name.
*/
public AsynchronousSynchronizer(Command command, ThreadFactory threadFactory, String threadName) {
super();
if (command == null) {
throw new NullPointerException();
}
this.consumerThreadCoordinator = new ConsumerThreadCoordinator(this.buildConsumer(command), threadFactory, threadName);
}
ConsumerThreadCoordinator.Consumer buildConsumer(Command command) {
return new Consumer(command);
}
// ********** Synchronizer implementation **********
/**
* Build and start the synchronization thread, but postpone the first
* synchronization until requested, i.e. via a call to
* {@link #synchronize()}.
* <p>
* Note: We don't clear the "synchronize" flag here; so if the flag has
* been set <em>before</em> getting here, the first synchronization will
* start promptly (albeit, asynchronously).
* The "synchronize" flag will be set if:<ul>
* <li>{@link #synchronize()} was called after the synchronizer was
* constructed but before {@link #start()} was called; or
* <li>{@link #synchronize()} was called after {@link #stop()} was called
* but before {@link #start()} was called (to restart the synchronizer); or
* <li>{@link #stop()} was called when there was an outstanding request
* for a synchronization (i.e. the "synchronization" flag was set at
* the time {@link #stop()} was called)
* </ul>
*/
public void start() {
this.consumerThreadCoordinator.start();
}
/**
* Set the "synchronize" flag so the synchronization thread will either<ul>
* <li>if the thread is quiesced, start a synchronization immediately, or
* <li>if the thread is currently executing a synchronization, execute another
* synchronization once the current synchronization is complete
* </ul>
*/
public void synchronize() {
this.synchronizeFlag.setTrue();
}
/**
* Interrupt the synchronization thread so that it stops executing at the
* end of the current synchronization. Suspend the current thread until
* the synchronization thread is finished executing. If any uncaught
* exceptions were thrown while the synchronization thread was executing,
* wrap them in a composite exception and throw the composite exception.
*/
public void stop() {
this.consumerThreadCoordinator.stop();
}
// ********** consumer **********
/**
* This implementation of {@link ConsumerThreadCoordinator.Consumer}
* will execute the client-supplied "synchronize" command.
* It will wait until the shared "synchronize" 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
* synchronization/consumer thread.
*/
private final Command command;
Consumer(Command command) {
super();
this.command = command;
}
/**
* Wait until the "synchronize" flag is set,
* then clear it and allow the "synchronize" command to execute.
*/
public void waitForProducer() throws InterruptedException {
AsynchronousSynchronizer.this.synchronizeFlag.waitToSetFalse();
}
/**
* Execute the client-supplied command.
*/
public void execute() {
this.command.execute();
}
}
}