/*******************************************************************************
* Copyright (c) 2011, 2014 Wind River Systems, Inc. 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.te.runtime.callback;
import java.util.concurrent.ExecutionException;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback;
import org.eclipse.tcf.te.runtime.utils.StatusHelper;
/**
* The asynchronous callback collector is an extension to the asynchronous callback handler. The
* difference is that the collector is not blocking a thread for waiting till all associated
* callback's have been finished, the collector will by itself call an asynchronous callback if all
* other callback's have been removed from the collector.
*
* In case of an error, all outstanding asynchronous callback's are ignored and the master callback
* is invoked directly.
*
* Note: The creator of the asynchronous callback collector must call <code>AsyncCallbackCollector.initDone()</code>
* if all initialization work and chaining of the target callback's are done. This will do avoid that
* the collector is fired before all pending callback could have been added!
*/
public class AsyncCallbackCollector extends AsyncCallbackHandler {
// The final master callback to send if all other callback have been removed.
final ICallback callback;
// Once the master callback has been fired, the collector is marked finish.
private boolean isFinished;
private boolean initDone;
// The reference to the callback invocation delegate
private ICallbackInvocationDelegate delegate = null;
/**
* Delegation interfaces used by the asynchronous callback collector to
* invoke the final callback.
*/
public static interface ICallbackInvocationDelegate {
/**
* Invokes the given runnable.
*
* @param runnable The runnable. Must not be <code>null</code>.
*/
public void invoke(Runnable runnable);
}
/**
* Simple target callback handling an asynchronous callback collector parent itself and remove
* themselves from the collector after callback has done.
* <p>
* Errors are handled using the collector.
*/
public static class SimpleCollectorCallback extends Callback {
private final AsyncCallbackCollector collector;
/**
* Constructor.
*
* @param collector The parent asynchronous callback collector. Must not be
* <code>null</code>.
*/
public SimpleCollectorCallback(AsyncCallbackCollector collector) {
Assert.isNotNull(collector);
// Remember the callback collector instance
this.collector = collector;
// Add ourself to the callback collector
this.collector.addCallback(this);
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.runtime.callback.Callback#internalDone(java.lang.Object, org.eclipse.core.runtime.IStatus)
*/
@Override
protected void internalDone(Object caller, IStatus status) {
// If an error occurred, pass on to the collector and
// let the collector handle the error.
if (status.getException() != null) {
collector.handleError(status.getMessage(), status.getException());
}
else {
collector.removeCallback(this);
}
}
/**
* Return the collector using this callback.
*/
protected AsyncCallbackCollector getCollector() {
return collector;
}
}
/**
* Constructor.
*/
public AsyncCallbackCollector() {
this(null, null);
}
/**
* Constructor.
*
* @param callback The final callback to invoke if the collector enters the finished state.
* @param delegate The callback invocation delegate. Must not be <code>null</code> if the callback is not <code>null</code>.
*/
public AsyncCallbackCollector(ICallback callback, ICallbackInvocationDelegate delegate) {
super();
if (callback != null) {
Assert.isNotNull(delegate);
}
// We have to add our master callback to the list of callback to avoid that
// the collector is running empty to early!
addCallback(callback);
this.callback = callback;
this.delegate = delegate;
// We are not finished yet.
isFinished = false;
initDone = false;
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.core.async.AsyncCallbackHandler#addCallback(org.eclipse.tcf.te.runtime.interfaces.callback.ICallback)
*/
@Override
public final synchronized void addCallback(ICallback callback) {
Assert.isTrue(!isFinished() || getError() != null);
super.addCallback(callback);
}
/**
* Checks if the collector run empty and we can fire the master callback.
*/
protected final synchronized void checkAndFireCallback() {
if (!isEmpty() || isFinished()) {
return;
}
isFinished = true;
onCollectorFinished();
}
/**
* Called from {@link #checkAndFireCallback()} once the collector has been marked finished.
* Subclasses may override this method for any necessary finished handling necessary.<br>
* The default implementation is just firing the collectors final callback.
* <p>
* Note: The method does not need to be explicitly synchronized. It's called from inside a
* <code>synchronized(this)</code> block!
*/
protected void onCollectorFinished() {
if (callback != null) {
Assert.isNotNull(delegate);
delegate.invoke(new Runnable() {
@Override
public void run() {
callback.done(this, StatusHelper.getStatus(getError()));
}
});
}
}
/* (non-Javadoc)
* @see org.eclipse.tcf.te.core.async.AsyncCallbackHandler#removeCallback(org.eclipse.tcf.te.runtime.interfaces.callback.ICallback)
*/
@Override
public final synchronized void removeCallback(ICallback callback) {
super.removeCallback(callback);
checkAndFireCallback();
}
/**
* Returns if or if not all pending callback's have been invoked and the collector entered the
* finished state. Once in the finished state, the state cannot be reversed anymore!
*/
public final boolean isFinished() {
return isFinished;
}
/**
* Method to be called if the initialization of the collector is finished. The creator of the
* collector must call this method in order to "activate" the collector finally.
*/
public final synchronized void initDone() {
Assert.isTrue(initDone == false);
if (callback != null) {
removeCallback(callback);
}
initDone = true;
}
/**
* Creates and {@link ExecutionException} for the given error message and the given cause. Calls
* {@link #handleError(Throwable)} afterwards. If the given cause is an
* {@link OperationCanceledException}, the cause will be passed on untouched.
*
* @param errMsg The error message or <code>null</code>.
* @param cause The cause or <code>null</code>.
*/
public final synchronized void handleError(final String errMsg, Throwable cause) {
// In case of an error, isFinished() can be set before all callback's are in.
// Callback's that come later do not change the result, they are silently ignored.
Assert.isTrue(!isFinished() || getError() != null);
Throwable error = cause;
// In case the incoming error is itself an OperationCanceledException, re-throw it as is.
// Otherwise re-package the cause to an ExecutionException.
//
// In all cases the exceptions will be thrown here in order to create a useful back trace.
if (error instanceof OperationCanceledException) {
// leave everything as is
}
else {
error = new ExecutionException(errMsg, error);
}
error.fillInStackTrace();
handleError(error);
}
/**
* Handles the given error. If the collector is not yet finished and not error has been set yet,
* the collector is finished and the error is set as the error to pass on with the callback. If
* the collector is already finished, the method will return immediately.
*
* @param error The error to handle. Must not be <code>null</code>.
*/
public final synchronized void handleError(final Throwable error) {
Assert.isNotNull(error);
// In case of an error, isFinished() can be set before all callback's are in.
// Callback's that come later do not change the result, they are silently ignored.
Assert.isTrue(!isFinished() || getError() != null);
if (!isFinished()) {
setError(error);
clear();
checkAndFireCallback();
}
// Re-throw any Error except assertion errors ! NEVER REMOVE THIS !
if ((error instanceof Error) && !(error instanceof AssertionError)) {
throw (Error) error;
}
}
}