diff options
Diffstat (limited to 'tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Callback.java')
-rw-r--r-- | tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Callback.java | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Callback.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Callback.java new file mode 100644 index 000000000..78c7e47bc --- /dev/null +++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/Callback.java @@ -0,0 +1,424 @@ +/******************************************************************************* + * Copyright (c) 2006, 2010 Wind River Systems and others. + * 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: + * Wind River Systems - initial API and implementation + *******************************************************************************/ +package org.eclipse.tcf.debug.test.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CancellationException; + +import org.eclipse.tcf.protocol.Protocol; + +/** + * Copied and apdapted from org.eclipse.cdt.dsf.concurrent. + * + * Used to monitor the result of an asynchronous request. Because of the + * asynchronous nature of DSF code, a very large number of methods needs to + * signal the result of an operation through a call-back. This class is the base + * class for such call backs. + * <p> + * The intended use of this class, is that a client who is calling an asynchronous + * method, will sub-class Callback, and implement the method {@link #handleCompleted()}, + * or any of the other <code>handle...</code> methods, in order to interpret the + * results of the request. The object implementing the asynchronous method is required + * to call the {@link #done()} method on the request monitor object that it received + * as an argument. + * </p> + * <p> + * The error the returned by #getError() can be used to + * determine the success or failure of the asynchronous operation. By convention + * the error value returned by asynchronous method should be interpreted as follows: + * <ul> + * <li><code>null</code> - Result is a success. In DataCallback, getData() should + * return a value.</li> + * <li>non-<code>null</code> - An error condition that should probably be reported + * to the user.</li> + * <li><code>CancellationException</code> - The request was canceled, and the + * asynchronous method was not completed.</li> + * </ul> + * </p> + * <p> + * The Callback constructor accepts an optional "parent" request monitor. If a + * parent monitor is specified, it will automatically be invoked by this monitor when + * the request is completed. The parent option is useful when implementing a method + * which is asynchronous (and accepts a request monitor as an argument) and which itself + * calls another asynchronous method to complete its operation. For example, in the + * request monitor implementation below, the implementation only needs to override + * <code>handleSuccess()</code>, because the base implementation will handle notifying the + * parent <code>rm</code> in case the <code>getIngredients()</code> call fails. + * <pre> + * public void createCupCakes(final DataCallback<CupCake[]> rm) { + * getIngredients(new DataCallback<Ingredients>(fExecutor, rm) { + * public void handleSuccess() { + * rm.setData( new CupCake(getData().getFlour(), getData().getSugar(), + * getData().getBakingPowder())); + * rm.done(); + * } + * }); + * } + * </pre> + * </p> + * + * @since 1.0 + */ +public class Callback { + + /** + * Interface used by Callback to notify when a given request monitor + * is canceled. + * + * @see Callback + */ + public static interface ICanceledListener { + + /** + * Called when the given request monitor is canceled. + */ + public void requestCanceled(Callback rm); + } + + /** + * The request monitor which was used to call into the method that created this + * monitor. + */ + private final Callback fParentCallback; + + private List<ICanceledListener> fCancelListeners; + + /** + * Status + */ + private Throwable fError = null; + private boolean fCanceled = false; + private boolean fDone = false; + + private final ICanceledListener fCanceledListener; + + /** + * This field is never read by any code; its purpose is strictly to assist + * developers debug DPF code. Developer can select this field in the + * Variables view and see a monitor backtrace in the details pane. See + * {@link DsfExecutable#DEBUG_MONITORS}. + * + * <p> + * This field is set only when tracing is enabled. + */ + @SuppressWarnings("unused") + private String fMonitorBacktrace; + + public Callback() { + this(null); + } + + /** + * Constructor with an optional parent monitor. + * + * @param executor + * This executor will be used to invoke the runnable that will + * allow processing the completion code of this request monitor. + * I.e., the runnable will call {@link #handleCompleted()}. + * @param parentCallback + * An optional parent request monitor. By default, our completion + * handlers invoke the parent monitor's <code>done</code> method, + * thus allowing monitors to be daisy chained. If this request is + * unsuccessful, its status is set into the parent monitor. + * Parameter may be null. + */ + public Callback(Callback parentCallback) { + fParentCallback = parentCallback; + + // If the parent rm is not null, add ourselves as a listener so that + // this request monitor will automatically be canceled when the parent + // is canceled. + if (fParentCallback != null) { + fCanceledListener = new ICanceledListener() { + public void requestCanceled(Callback rm) { + cancel(); + } + }; + + fParentCallback.addCancelListener(fCanceledListener); + } else { + fCanceledListener = null; + } + } + + /** + * Sets the status of the result of the request. If status is OK, this + * method does not need to be called. + */ + public synchronized void setError(Throwable error) { + fError = error; + } + + /** Returns the status of the completed method. */ + public synchronized Throwable getError() { + if (isCanceled()) { + return new CancellationException(); + } + return fError; + } + + /** + * Sets this request monitor as canceled and calls the cancel listeners if + * any. + * <p> + * Note: Calling cancel() does not automatically complete the + * Callback. The asynchronous call still has to call done(). + * </p> + * <p> + * Note: logically a request should only be canceled by the client that + * issued the request in the first place. After a request is canceled, the + * method that is fulfilling the request may call + * {@link #setError(Throwable)} with <code>CancelledException</code> + * to indicate that it recognized that the given request was canceled and it + * did not perform the given operation. + * </p> + * <p> + * Canceling a monitor effectively cancels all descendant monitors, by + * virtue of the default implementation of {@link #isCanceled()}, which + * checks not only its own state but that of its parent. However, only the + * cancel listeners of the monitor directly canceled will be called. + * </p> + */ + public void cancel() { + ICanceledListener[] listeners = null; + synchronized (this) { + // Check to make sure the request monitor wasn't previously canceled. + if (!fCanceled) { + fCanceled = true; + if (fCancelListeners != null) { + listeners = fCancelListeners.toArray(new ICanceledListener[fCancelListeners.size()]); + } + } + } + + // Call the listeners outside of a synchronized section to reduce the + // risk of deadlocks. + if (listeners != null) { + for (ICanceledListener listener : listeners) { + listener.requestCanceled(this); + } + } + } + + /** + * Returns whether the request was canceled. Even if the request is + * canceled by the client, the implementor handling the request should + * still call {@link #done()} in order to complete handling + * of the request monitor. + * + * <p> + * A request monitor is considered canceled if either it or its parent was canceled. + * </p> + */ + public boolean isCanceled() { + boolean canceled = false; + + // Avoid holding onto this lock while calling parent RM, which may + // acquire other locks (bug 329488). + synchronized(this) { + canceled = fCanceled; + } + return canceled || (fParentCallback != null && fParentCallback.isCanceled()); + } + + /** + * Adds the given listener to list of listeners that are notified when this + * request monitor is directly canceled. + */ + public synchronized void addCancelListener(ICanceledListener listener) { + if (fCancelListeners == null) { + fCancelListeners = new ArrayList<ICanceledListener>(1); + } + fCancelListeners.add(listener); + } + + /** + * Removes the given listener from the list of listeners that are notified + * when this request monitor is directly canceled. + */ + public synchronized void removeCancelListener(ICanceledListener listener) { + if (fCancelListeners != null) { + fCancelListeners.remove(listener); + } + } + + /** + * Marks this request as completed. Once this method is called, the + * monitor submits a runnable to the DSF Executor to call the + * <code>handle...</code> methods. + * <p> + * Note: This method should be called once and only once, for every request + * issued. Even if the request was canceled. + * </p> + */ + public synchronized void done() { + if (fDone) { + throw new IllegalStateException("Callback: " + this + ", done() method called more than once"); //$NON-NLS-1$//$NON-NLS-2$ + } + fDone = true; + + // This Callback is done, it can no longer be canceled. + // We must clear the list of cancelListeners because it causes a + // circular reference between parent and child Callback, which + // causes a memory leak. + fCancelListeners = null; + + if (fParentCallback != null) { + fParentCallback.removeCancelListener(fCanceledListener); + } + + try { + Protocol.invokeLater(new Runnable() { + public void run() { + Callback.this.handleCompleted(); + } + @Override + public String toString() { + return "Completed: " + Callback.this.toString(); //$NON-NLS-1$ + } + }); + } catch (IllegalStateException e) { + handleEventQueueShutDown(e); + } + } + + @Override + public String toString() { + return "Callback (" + super.toString() + "): " + getError(); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Checks whether the given request monitor completed with success or + * failure result. If the request monitor was canceled it is considered a + * failure, regardless of the status. If the status has a severity higher + * than INFO (i.e., WARNING, ERROR or CANCEL), it is considered a failure. + */ + public synchronized boolean isSuccess() { + return !isCanceled() && getError() == null ; + } + + /** + * First tier handler for the completion of the request. By default, the + * {@link #done()} method drives this method on the executor specified at + * construction time. By default, this handler merely calls a more + * specialized handler, which in turn may call an even more specialized + * handler, and so on, thus giving a subclass the ability to + * compartmentalize its completion logic by overriding specific handlers. + * All handlers are named <code>handleXxxxx</code>. More specifically, the + * base implementation calls {@link #handleSuccess()} if the request + * succeeded, and calls {@link #handleFailure()} otherwise. <br> + * + * The complete hierarchy of handlers is as follows: <br> + * <pre> + * + handleCompleted + * - handleSuccess + * + handleFailure + * - handleCancel + * + handleErrororWarning + * - handleError + * - handleWarning + * </pre> + * + * <p> + * Note: Sub-classes may override this method. + */ + protected void handleCompleted() { + if (isSuccess()) { + handleSuccess(); + } else { + handleFailure(); + } + } + + /** + * Default handler for a successful the completion of a request. If this + * monitor has a parent monitor that was configured by the constructor, that + * parent monitor is notified. Otherwise this method does nothing. + * {@link #handleFailure()} or cancel otherwise. + * <br> + * Note: Sub-classes may override this method. + */ + protected void handleSuccess() { + if (fParentCallback != null) { + fParentCallback.done(); + } + } + + /** + * The default implementation of a cancellation or an error result of a + * request. The implementation delegates to {@link #handleCancel()} and + * {@link #handleErrorOrWarning()} as needed. + * <br> + * Note: Sub-classes may override this method. + */ + protected void handleFailure() { + assert !isSuccess(); + + if (isCanceled()) { + handleCancel(); + } else { + handleError(); + } + } + + /** + * The default implementation of an error result of a request. If this + * monitor has a parent monitor that was configured by the constructor, that + * parent monitor is configured with a new status containing this error. + * Otherwise the error is logged. + * <br> + * Note: Sub-classes may override this method. + */ + protected void handleError() { + if (fParentCallback != null) { + fParentCallback.setError(getError()); + fParentCallback.done(); + } else { + Protocol.log("Unhandled error in callback " + toString(), getError()); + } + } + + + /** + * Default completion handler for a canceled request. If this monitor was + * constructed with a parent monitor, the status is propagated up to it. + * Otherwise this method does nothing. <br> + * Note: Sub-classes may override this method. + */ + protected void handleCancel() { + if (fParentCallback != null) { + if (getError() instanceof CancellationException && !fParentCallback.isCanceled()) { + Protocol.log("Sub-request " + toString() + " was canceled and not handled.'", getError()); + } else { + fParentCallback.setError(getError()); + } + fParentCallback.done(); + } + } + + /** + * Default handler for when the executor supplied in the constructor + * rejects the runnable that is submitted invoke this request monitor. + * This usually happens only when the executor is shutting down. + * <p> + * The default handler creates a new error status for the rejected + * execution and propagates it to the client or logs it. + */ + protected void handleEventQueueShutDown(IllegalStateException e) { + if (fParentCallback != null) { + fParentCallback.setError(e); + fParentCallback.done(); + } else { + Protocol.log("In callback " + toString() + ", unhandled event queue shut down.", e); + } + } +} |