package com.asteria.service;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Preconditions;
/**
* An asynchronous operation that can be carried out by a specific
* {@link ServiceQueue} context. Given the asynchronous nature of services, they
* are thread safe which means that they can be used across multiple threads
* without external synchronization.
* <p>
* <p>
* There are two types of services, one being a {@code DIRECT} service and the
* other being a {@code SCHEDULED} service. Direct services have a delay of
* {@code 0} and are executed by the service queue right on submission, and
* scheduled services have a delay greater than {@code 0} and are executed
* sometime in the future. {@code cancel()} is automatically called on direct
* services.
* <p>
* <p>
* Please keep in mind that even though the management of services is
* synchronized, that the code within the services must be thread safe or race
* conditions and other concurrency issues can occur.
*
* @author lare96 <http://github.com/lare96/>
*/
public abstract class Service {
/**
* The delay of this service in milliseconds. This is immutable because
* dynamic delay changes are not supported when using executors.
*/
private final long delay;
/**
* The result-bearing action that is used to cancel this service.
*/
private Future<?> future;
/**
* Creates a new {@link Service}.
*
* @param delay
* the delay of this service in milliseconds.
*/
public Service(long delay, TimeUnit unit) {
Preconditions.checkArgument(delay >= 0, "The delay of services must be >= 0.");
this.delay = unit.toMillis(delay);
}
/**
* Creates a new {@link Service}.
*
* @param delay
* the delay of this service in milliseconds.
*/
public Service(long delay) {
this(delay, TimeUnit.MILLISECONDS);
}
/**
* Creates a new {@link Service} with no delay.
*/
public Service() {
this(0);
}
/**
* The logic within this service that will be executed asynchronously.
*
* @param context
* the context that this service is executed in.
*/
public abstract void execute(ServiceQueue context);
/**
* Cancels this service and unregisters it from its service queue context.
*/
public final void cancel() {
if (future == null)
throw new IllegalStateException("Cannot cancel a Service that has not been submitted yet.");
if (!isDone())
future.cancel(false);
}
/**
* Blocks the calling thread until this service completes. It is preferred
* that {@code awaitSilently()} be used instead of this as there is little
* to no reason why one would want to be notified of following exceptions.
*
* @throws InterruptedException
* if the calling thread is interrupted while blocking.
* @throws ExecutionException
* if the computation throws an exception.
* @throws CancellationException
* if the computation was cancelled.
*/
public final void await() throws InterruptedException, ExecutionException, CancellationException {
if (future == null)
throw new IllegalStateException("Cannot await completion for a service that has not been submitted yet.");
future.get();
}
/**
* Silently blocks the calling thread until this service completes,
* discarding any thrown {@link InterruptedException}s,
* {@link CancellationException}s, and {@link ExecutionException}s. This
* should be preferred in use to {@code await()}.
*/
public final void awaitSilently() {
try {
await();
} catch (InterruptedException | CancellationException | ExecutionException ignored) {
}
}
/**
* Determines if this service has been cancelled or completed.
*
* @return {@code true} if this service has is finished, {@code false}
* otherwise.
*/
public final boolean isDone() {
if (future == null)
return false;
return future.isDone();
}
/**
* Sets the value for {@link Service#future}.
*
* @param future
* the new value to set.
*/
final void setFuture(Future<?> future) {
this.future = future;
}
/**
* Gets the delay of this service in milliseconds.
*
* @return the delay of this service.
*/
public final long getDelay() {
return delay;
}
}