| /******************************************************************************* |
| * Copyright (c) 2008, 2009 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.Vector; |
| |
| import org.eclipse.jpt.utility.Command; |
| import org.eclipse.jpt.utility.internal.CompositeException; |
| import org.eclipse.jpt.utility.internal.StringTools; |
| import org.eclipse.jpt.utility.internal.SynchronizedBoolean; |
| |
| /** |
| * 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 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); |
| |
| /** |
| * The runnable passed to the synchronization thread each time it is built. |
| */ |
| private final Runnable runnable; |
| |
| /** |
| * Optional, client-supplied name for the synchronization thread. |
| * If null, allow the JDK to assign a name. |
| */ |
| private final String threadName; |
| |
| /** |
| * The synchronization is performed on this thread. A new thread is built |
| * for every start/stop cycle (since a thread cannot be started more than |
| * once). |
| */ |
| private Thread thread; |
| |
| /** |
| * A list of the uncaught exceptions thrown by the command. |
| */ |
| final Vector<Throwable> exceptions = new Vector<Throwable>(); |
| |
| |
| // ********** construction ********** |
| |
| /** |
| * Construct an asynchronous synchronizer that uses the specified command to |
| * perform the synchronization. Allow the generated thread(s) to be assigned |
| * JDK-generated names. |
| */ |
| public AsynchronousSynchronizer(Command command) { |
| this(command, null); |
| } |
| |
| /** |
| * Construct an asynchronous synchronizer that uses the specified command to |
| * perform the synchronization. Assign the generated thread(s) the specified |
| * name. |
| */ |
| public AsynchronousSynchronizer(Command command, String threadName) { |
| super(); |
| this.runnable = this.buildRunnable(command); |
| this.threadName = threadName; |
| } |
| |
| Runnable buildRunnable(Command command) { |
| return new RunnableSynchronization(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 synchronized void start() { |
| if (this.thread != null) { |
| throw new IllegalStateException("The Synchronizer was not stopped."); //$NON-NLS-1$ |
| } |
| this.thread = this.buildThread(); |
| this.thread.start(); |
| } |
| |
| private Thread buildThread() { |
| Thread t = new Thread(this.runnable); |
| if (this.threadName != null) { |
| t.setName(this.threadName); |
| } |
| return t; |
| } |
| |
| /** |
| * 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 synchronized void stop() { |
| if (this.thread == null) { |
| throw new IllegalStateException("The Synchronizer was not started."); //$NON-NLS-1$ |
| } |
| this.thread.interrupt(); |
| try { |
| this.thread.join(); |
| } catch (InterruptedException ex) { |
| // the thread that called #stop() was interrupted while waiting to |
| // join the synchronization thread - ignore; |
| // 'thread' is still "interrupted", so its #run() loop will still stop |
| // after its current execution - we just won't wait around for it... |
| } |
| this.thread = null; |
| |
| if (this.exceptions.size() > 0) { |
| Throwable[] temp = this.exceptions.toArray(new Throwable[this.exceptions.size()]); |
| this.exceptions.clear(); |
| throw new CompositeException(temp); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return StringTools.buildToStringFor(this, this.thread); |
| } |
| |
| |
| // ********** synchronization thread runnable ********** |
| |
| /** |
| * This implementation of {@link Runnable} will execute a client-supplied command. |
| * It will wait until a shared "synchronize" flag is set to execute the |
| * command. Once the 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 RunnableSynchronization |
| implements Runnable |
| { |
| /** The client-supplied command that executes on this thread. */ |
| private final Command command; |
| |
| |
| RunnableSynchronization(Command command) { |
| super(); |
| if (command == null) { |
| throw new NullPointerException(); |
| } |
| this.command = command; |
| } |
| |
| /** |
| * Loop while this thread has not been interrupted by another thread. |
| * In each loop: Wait until the "synchronize" flag is set, |
| * then clear it and execute the command. If the |
| * "synchronize" flag was set <em>during</em> the synchronization, |
| * there will be no "wait" before beginning the next synchronization |
| * (thus the call to {@link Thread#isInterrupted()} before each cycle). |
| * <p> |
| * If this thread is interrupted <em>during</em> the synchronization, the |
| * call to {@link Thread#interrupted()} will stop the loop. If this thread is |
| * interrupted during the call to {@link SynchronizedBoolean#waitToSetFalse()}, |
| * we will catch the {@link InterruptedException} and stop the loop. |
| */ |
| public void run() { |
| while ( ! Thread.interrupted()) { |
| try { |
| AsynchronousSynchronizer.this.synchronizeFlag.waitToSetFalse(); |
| } catch (InterruptedException ex) { |
| // we were interrupted while waiting, must be Quittin' Time |
| return; |
| } |
| this.execute(); |
| } |
| } |
| |
| /** |
| * Execute the client-supplied command. Do not allow any unhandled |
| * exceptions to kill the thread. Store them up for later pain. |
| */ |
| private void execute() { |
| try { |
| this.execute_(); |
| } catch (Throwable ex) { |
| AsynchronousSynchronizer.this.exceptions.add(ex); |
| } |
| } |
| |
| /** |
| * Execute the client-supplied command. |
| */ |
| void execute_() { |
| this.command.execute(); |
| } |
| |
| } |
| |
| } |