org.jtrim2.cancel.Cancellation Maven / Gradle / Ivy
package org.jtrim2.cancel;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import org.jtrim2.concurrent.WaitableSignal;
import org.jtrim2.event.ListenerRef;
import org.jtrim2.event.ListenerRefs;
/**
* Contains static helper methods and fields related cancellation.
*/
public final class Cancellation {
/**
* A {@link CancellationToken} which can never be in the canceled state.
* The {@link CancellationToken#isCanceled() isCanceled} method of
* {@code UNCANCELABLE_TOKEN} will always return {@code false} when checked.
* If a listener is registered with this token to be notified of
* cancellation requests, this {@code CancellationToken} will do nothing but
* return an already unregistered {@code ListenerRef}.
*/
public static final CancellationToken UNCANCELABLE_TOKEN = UncancelableToken.INSTANCE;
/**
* A {@link CancellationToken} which is already in the canceled state.
* The {@link CancellationToken#isCanceled() isCanceled} method of
* {@code UNCANCELABLE_TOKEN} will always return {@code true} when checked.
* If a listener is registered with this token to be notified of
* cancellation requests, this {@code CancellationToken} will immediately
* notify the listener and return an already unregistered
* {@code ListenerRef}.
*
* If you need a {@link CancellationController} as well, use the
* {@link #DO_NOTHING_CONTROLLER}.
*
* @see #DO_NOTHING_CONTROLLER
*/
public static final CancellationToken CANCELED_TOKEN = CanceledToken.INSTANCE;
/**
* A {@link CancellationController} which does nothing when calling
* {@code cancel}.
*
* This {@code CancellationController} is good for tasks, cannot be canceled
* or tasks already canceled.
*
* @see #CANCELED_TOKEN
*/
public static final CancellationController DO_NOTHING_CONTROLLER = () -> { };
/**
* Creates a new {@code CancellationSource} whose {@link CancellationToken}
* is not yet in the canceled state. The only possible way to make the
* {@code CancellationToken} of the returned {@code CancellationSource}
* signal cancellation request is to cancel the
* {@link CancellationController} of the returned
* {@code CancellationSource}.
*
* @return a new {@code CancellationSource} whose {@link CancellationToken}
* is not yet in the canceled state. This method never returns
* {@code null}.
*/
public static CancellationSource createCancellationSource() {
return new SimpleCancellationSource();
}
/**
* Creates a new {@code CancellationSource} which will be notified of the
* cancellation requests of the specified {@code CancellationToken}.
* That is, if the {@code CancellationToken} specified in the argument is
* canceled, the returned {@code CancellationToken} will be canceled as
* well. Of course, the returned {@code CancellationSource} can also be
* canceled by its own {@code CancellationController}
*
* @param cancelToken the {@code CancellationToken}, which when canceled,
* will cause the returned {@code CancellationSource} to be canceled. This
* argument cannot be {@code null}.
* @return the new {@code ChildCancellationSource} which will be notified
* of the cancellation requests of the specified
* {@code CancellationToken}. This method never returns {@code null}.
*
* @throws NullPointerException thrown if the specified argument is
* {@code null}
*/
public static CancellationSource createChildCancellationSource(
CancellationToken cancelToken) {
return new SimpleChildCancellationSource(cancelToken);
}
/**
* Returns a {@code CancellationToken} which signals cancellation if and
* only if at least one of the specified tokens are in canceled state.
*
* If you do not specify any tokens, the returned token will never be in
* the canceled state.
*
* @param tokens the array of {@code CancellationToken} checked for
* cancellation. This array might be empty but cannot contain {@code null}
* elements.
* @return the {@code CancellationToken} which signals cancellation if and
* only if at least one of the specified tokens is in canceled state. This
* method never returns {@code null}.
*/
public static CancellationToken anyToken(CancellationToken... tokens) {
return new CombinedTokenAny(tokens);
}
/**
* Returns a {@code CancellationToken} which signals cancellation if and
* only if all of the specified tokens are in canceled state.
*
* If you do not specify any tokens, the returned token will always be in
* the canceled state.
*
* @param tokens the array of {@code CancellationToken} checked for
* cancellation. This array might be empty but cannot contain {@code null}
* elements.
* @return the {@code CancellationToken} which signals cancellation if and
* only if all of the specified tokens is in canceled state. This
* method never returns {@code null}.
*/
public static CancellationToken allTokens(CancellationToken... tokens) {
return new CombinedTokenAll(tokens);
}
/**
* Adds a {@link CancellationToken#addCancellationListener(Runnable) cancellation listener}
* to the specified {@code CancellationToken} and returns reference which
* can be used to remove the listener and wait until it has been removed.
*
* Warning: It is forbidden to call any of the {@code close} methods
* from within the added listener. Attempting to do so will result in an
* {@code IllegalStateException} to be thrown by the {@code close} method.
*
* Calling the {@code unregisterAndWait} method of the returned reference
* ensures the following:
*
* -
* After the {@code unregisterAndWait} methods returns normally (without
* throwing an exception), the listener is guaranteed not to be executed
* anymore.
*
* -
* Calling the {@code unregisterAndWait} method (with valid argument)
* ensures that the added listener will be unregistered. This is
* {@code true} even if the {@code unregisterAndWait} method gets canceled.
*
* -
* If the passed listener is synchronization transparent, then
* the {@code unregisterAndWait} method is
* synchronization transparent as well.
*
*
*
* Here is an example usage:
* {@code
* CancellationToken cancelToken = ...;
* WaitableListenerRef ref = listenerForCancellation(cancelToken, () -> {
* System.out.println("CANCELED")
* });
* try {
* // ...
* } finally {
* ref.unregisterAndWait(Cancellation.UNCANCELABLE_TOKEN);
* }
* // When execution reaches this line, it is ensured that if "CANCELED"
* // has not been printed yet, it will never be printed.
* }
*
* @param cancelToken the {@code CancellationToken} to which the listener
* is to be added. That is, the listener is registered to be notified of
* the cancellation requests of this token. This argument cannot be
* {@code null}.
* @param listener the listener whose {@code run} method is to be passed
* as a cancellation listener to the specified {@code CancellationToken}.
* This argument cannot be {@code null}.
* @return a {@code CancelableCloseable} whose {@code close} methods can
* be used to unregister the added listener and wait until it can be
* ensured that the listener will never be executed. This method never
* returns {@code null}.
*
* @throws NullPointerException thrown if any of the arguments is
* {@code null}
*/
public static WaitableListenerRef listenForCancellation(
final CancellationToken cancelToken, final Runnable listener) {
Objects.requireNonNull(cancelToken, "cancelToken");
Objects.requireNonNull(listener, "listener");
final AtomicReference doneSignalRef = new AtomicReference<>(null);
final ThreadLocal