package org.goko.core.execution.monitor.executor;
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.goko.core.common.exception.GkException;
import org.goko.core.gcode.element.GCodeLine;
import org.goko.core.gcode.execution.ExecutionState;
import org.goko.core.gcode.execution.IExecutionToken;
import org.goko.core.gcode.execution.IExecutionTokenState;
import org.goko.core.gcode.execution.IExecutor;
import org.goko.core.gcode.service.IExecutionService;
import org.goko.core.log.GkLog;
/**
* Defines an executor that streams the content of its provider to a distant executor.
*
* @author Psyko
*
* @param <S> the template for the IExecutionState
* @param <T> the template for the IExecutionToken
*/
public abstract class AbstractStreamingExecutor<S extends IExecutionTokenState, T extends IExecutionToken<S>> implements IExecutor<S, T> {
/** LOG */
private static final GkLog LOG = GkLog.getLogger(AbstractStreamingExecutor.class);
/** Lock object for token pause monitoring */
private Object executionPausedLock = new Object();
/** Time to wait for pause check in milliseconds */
private int tokenPauseTimeout = 500;
/** The execution service */
private IExecutionService<S, T> executionService;
/** Weak reference to the use token */
private WeakReference<T> tokenWeakReference;
/** The lock indicating that the executor is waiting to be able to execute the next line */
private final Lock readyForNextLineLock = new ReentrantLock();
/** The condition indicating that the executor is waiting to be able to execute the next line */
private final Condition readyForNextLineCondition = readyForNextLineLock.newCondition();
/** The lock indicating that the executor is waiting for the token execution to be complete */
private final Lock tokenCompleteLock = new ReentrantLock();
/** The condition indicating that the executor is waiting for the token execution to be complete */
private final Condition tokenCompleteCondition = tokenCompleteLock.newCondition();
/** The total number of commands */
private AtomicInteger remainingCommands;
/** Token complete indicator */
private boolean tokenComplete;
/** The state of the execution */
private ExecutionState state;
/** (inheritDoc)
* @see org.goko.core.gcode.execution.IExecutor#executeToken(org.goko.core.gcode.execution.IExecutionToken)
*/
@Override
public void executeToken(T token) throws GkException {
tokenWeakReference = new WeakReference<T>(token);
setTokenComplete(false);
updateState(ExecutionState.RUNNING);
getExecutionService().notifyExecutionStart(token);
remainingCommands = new AtomicInteger(token.getLineCount());
while(token.hasMoreLine()){
waitReadyForNextLine();
waitExecutionUnpaused(token);
// We have a stop
if(state == ExecutionState.STOPPED){
break;
}
GCodeLine nextLine = token.takeNextLine();
send(nextLine);
getExecutionService().notifyCommandStateChanged(getToken(), nextLine.getId());
}
waitTokenComplete();
if(state == ExecutionState.STOPPED){
getExecutionService().notifyExecutionCanceled(getToken());
}else{
updateState(ExecutionState.COMPLETE);
getExecutionService().notifyExecutionComplete(getToken());
}
}
/** (inheritDoc)
* @see org.goko.core.gcode.execution.IExecutor#waitTokenComplete()
*/
@Override
public void waitTokenComplete() throws GkException {
while(!isTokenComplete() && state != ExecutionState.STOPPED){
tokenCompleteLock.lock();
try{
tokenCompleteCondition.await(100, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
LOG.error(e);
}finally{
tokenCompleteLock.unlock();
}
}
}
/**
* Notify this object that the token is completely executed
*/
protected final void notifyTokenComplete(){
LOG.info("!!! Token complete !!!");
tokenCompleteLock.lock();
try{
setTokenComplete(true);
tokenCompleteCondition.signal();
}finally{
tokenCompleteLock.unlock();
}
}
/**
* Sends the given line for execution
* @param line the line to send
* @throws GkException GkException
*/
protected abstract void send(GCodeLine line) throws GkException;
/**
* Test if the executor is ready for the execution of the next line.
* @return <code>true</code> if the executor is ready, <code>false</code> otherwise
* @throws GkException GkException
*/
protected abstract boolean isReadyForNextLine() throws GkException;
/**
* Wait while the current token is paused.
*/
private void waitExecutionUnpaused(T token) throws GkException{
while(state == ExecutionState.PAUSED){
try {
synchronized (executionPausedLock) {
// We don't need extra fast reaction on this so synchronization will wait for the next check (after at most, tokenPauseTimeout milliseconds)
executionPausedLock.wait(tokenPauseTimeout);
}
} catch (InterruptedException e) {
LOG.error(e);
}
}
}
/**
* Wait until the executor is ready for sending the next line.
* Can be interrupted by calling <code>notifyReadyForNextLine</code> method
* @throws GkException GkException
*/
private void waitReadyForNextLine() throws GkException{
while(!isReadyForNextLine() && getToken().hasMoreLine()){
readyForNextLineLock.lock();
try{
// We perform a while loop to make sure the notification is not missed
readyForNextLineCondition.await(100, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
LOG.error(e);
}finally{
readyForNextLineLock.unlock();
}
}
}
/**
* Notify this object that the it's ready for the execution of the next line
*/
protected final void notifyReadyForNextLine(){
readyForNextLineLock.lock();
try{
readyForNextLineCondition.signal();
}finally{
readyForNextLineLock.unlock();
}
}
/**
*
* @param state
* @throws GkException
*/
private void updateState(ExecutionState state) throws GkException{
setState(state);
getToken().setState(state);
}
/** (inheritDoc)
* @see org.goko.core.gcode.execution.IExecutor#getToken()
*/
@Override
public T getToken() throws GkException {
return tokenWeakReference.get();
}
/** (inheritDoc)
* @see org.goko.core.gcode.execution.IExecutor#getExecutionService()
*/
@Override
public IExecutionService<S, T> getExecutionService() throws GkException{
return executionService;
}
/** (inheritDoc)
* @see org.goko.core.gcode.execution.IExecutor#setExecutionService(org.goko.core.gcode.service.IExecutionService)
*/
@Override
public void setExecutionService(IExecutionService<S, T> executionService) throws GkException{
this.executionService = executionService;
}
/**
* @return the tokenComplete
*/
@Override
public boolean isTokenComplete() throws GkException{
return tokenComplete;
}
/**
* @param tokenComplete the tokenComplete to set
*/
public void setTokenComplete(boolean tokenComplete) {
this.tokenComplete = tokenComplete;
}
/** (inheritDoc)
* @see org.goko.core.gcode.execution.IExecutionControl#start()
*/
@Override
public void start() throws GkException {
updateState(ExecutionState.RUNNING);
}
/** (inheritDoc)
* @see org.goko.core.gcode.execution.IExecutionControl#resume()
*/
@Override
public void resume() throws GkException {
updateState(ExecutionState.RUNNING);
}
/** (inheritDoc)
* @see org.goko.core.gcode.execution.IExecutionControl#pause()
*/
@Override
public void pause() throws GkException {
updateState(ExecutionState.PAUSED);
}
/** (inheritDoc)
* @see org.goko.core.gcode.execution.IExecutionControl#stop()
*/
@Override
public void stop() throws GkException {
updateState(ExecutionState.STOPPED);
notifyReadyForNextLine();
}
/**
* @return the state
*/
@Override
public ExecutionState getState() {
return state;
}
/**
* @param state the state to set
*/
public void setState(ExecutionState state) {
this.state = state;
}
/**
* @return the remainingCommands
*/
public int getRemainingCommands() {
return remainingCommands.get();
}
/**
* Decrement the number of remaining command by 1
*/
protected void decrementRemainingCommandCount(){
remainingCommands.decrementAndGet();
}
}