/*
* Copyright (C) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package interactivespaces.util.concurrency;
import interactivespaces.InteractiveSpacesException;
import interactivespaces.system.InteractiveSpacesEnvironment;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
/**
* An interruptable loop.
*
* This should be run with the {@link ExecutorService} obtained from
* {@link InteractiveSpacesEnvironment#getExecutorService()}.
*
* @author Keith M. Hughes
*/
public abstract class CancellableLoop implements Runnable {
/**
* The {@link Thread} the code will be running in.
*/
private Thread thread;
/**
* Any exception which may have happened during execution.
*/
private AtomicReference<Exception> exception = new AtomicReference<Exception>();
/**
* Runtime state of the loop.
*/
private AtomicReference<RuntimeState> runtimeState = new AtomicReference<RuntimeState>(
RuntimeState.READY);
@Override
public void run() {
synchronized (this) {
if (thread != null) {
throw new InteractiveSpacesException("Loop already running");
}
thread = Thread.currentThread();
runtimeState.set(RuntimeState.READY);
}
try {
setup();
while (!thread.isInterrupted()) {
loop();
}
runtimeState.set(RuntimeState.SHUTDOWN);
} catch (InterruptedException e) {
runtimeState.set(RuntimeState.SHUTDOWN);
handleInterruptedException(e);
} catch (Exception e) {
exception.set(e);
runtimeState.set(RuntimeState.CRASH);
handleException(e);
} finally {
thread = null;
cleanup();
}
}
/**
* The setup block for the loop. This will be called exactly once before the
* first call to {@link #loop()}.
*/
protected void setup() {
// Do nothing by default.
}
/**
* The cleanup block for the loop. This will be called exactly once after the
* loop has exited for any reason.
*/
protected void cleanup() {
// Do nothing by default.
}
/**
* The body of the loop. This will run continuously until the
* {@link CancellableLoop} has been interrupted externally or by calling
* {@link #cancel()}.
*/
protected abstract void loop() throws InterruptedException;
/**
* An {@link InterruptedException} was thrown.
*/
protected void handleInterruptedException(InterruptedException e) {
// Ignore InterruptedExceptions by default.
}
/**
* An {@link Exception} other than an {@link InterruptedException} was thrown.
*/
protected void handleException(Exception e) {
// Ignore Exceptions by default.
}
/**
* Interrupts the loop.
*/
public void cancel() {
if (thread != null) {
thread.interrupt();
}
}
/**
* @return {@code true} if the loop is running
*/
public synchronized boolean isRunning() {
return thread != null && !thread.isInterrupted();
}
/**
* Get the exception that was thrown during the loop running.
*
* @return the exception thrown, can be {@code null} if no exception has yet
* been thrown
*/
public Exception getException() {
return exception.get();
}
}