blob: 9bc6f71e6624a28384e5a732ff2597ac9bf5a7f2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013 Boeing.
* 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:
* Boeing - initial API and implementation
*******************************************************************************/
package org.eclipse.ote.statemachine;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
public final class StateMachine {
private StateTransitionTable stateTransitionTable;
private BaseState currentState;
private LinkedBlockingQueue<BaseInput> queue;
private String name;
private KillInput killInput;
private static class KillInput extends BaseInput{
public KillInput(StateMachine stateMachine) {
super(stateMachine);
}
@Override
public Object getType() {
return 0;
}
};
private Thread th;
private StateMachineIdFactory factory;
private BaseState defaultInitialState;
private int nextStateId = 0;
private volatile boolean debug = false;
private final boolean runInInputThread;
/**
* This constructor will have the inputs processed in another thread. Often this is not
* what you want if you are already doing scheduling and would prefer that all inputs are
* processed at your controlled rate.
*
* @param name
*/
public StateMachine(String name){
this(new StateMachineIdFactory(), name, false);
}
/**
*
* @param name
* @param runInInputThread - false to run in a seperate thread
*/
public StateMachine(String name, boolean runInInputThread){
this(new StateMachineIdFactory(), name, runInInputThread);
}
StateMachine(StateMachineIdFactory factory, String name, boolean runInInputThread){
this.name = name;
this.factory = factory;
stateTransitionTable = new StateTransitionTable(50,50);
queue = new LinkedBlockingQueue<BaseInput>();
killInput = new KillInput(this);
this.runInInputThread = runInInputThread;
}
public void setDebugOutput(boolean debug){
this.debug = debug;
}
BaseInput processInput() throws RuntimeException{
BaseInput input = getInput();
if(input != null && input != killInput){
if(currentState == null){
throw new RuntimeException("CurrentState is null, statemachine was not properly initialized. Please ensure that if any child state machines were created and entry() was implemented that you call super.entry().");
}
BaseState nextState = stateTransitionTable.getTransition(input, currentState);
if(debug ) {
System.out.println("======================"+name+"========================");
System.out.println("INPUT: " + input.toString());
System.out.println("CURRENTSTATE: " + currentState.toString());
if(nextState == null){
System.out.println("strange");
}
System.out.println("NEXTSTATE: " + (nextState == null ? "" : nextState.toString()));
}
if(nextState != null){
currentState.run(input);
if(nextState != currentState){
currentState.exit();
nextState.entry();
currentState = nextState;
}
}
if(debug ) {
System.out.println("DONE ======================"+name+"========================");
System.out.println("INPUT: " + input.toString());
System.out.println("CURRENTSTATE: " + currentState.toString());
}
}
return input;
}
public void start(){
currentState.entry();
if(!runInInputThread){
th = new Thread(new Runnable(){
@Override
public void run() {
BaseInput input = null;
while(input != killInput){
try{
input = processInput();
} catch (Throwable th){
th.printStackTrace();
}
}
}
});
th.setName("StateMachine " + name);
th.start();
}
}
public void processUntilEmpty(){
while(processInput() != null){
}
}
public void setDefaultInitialState(BaseState initialState) {
this.defaultInitialState = initialState;
}
public void initialize() throws Exception{
resetToInitialState();
setupChildStateMachines();
}
void setupChildStateMachines() throws Exception{
for(ChildStateMachineState child:stateTransitionTable.getChildren()){
child.setupChildStateMachines();
this.addChildMachine(child);
}
}
public void resetToInitialState(){
this.currentState = defaultInitialState;
}
public BaseState getCurrentState(){
return currentState;
}
private BaseInput getInput() {
if(runInInputThread){
return queue.poll();
} else {
try {
return queue.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public void newTransition(BaseState state, BaseInput input, BaseState nextState) throws Exception{
if(state.getId() == -1){
state.setId(getNextStateId());
}
if(nextState.getId() == -1){
nextState.setId(getNextStateId());
}
stateTransitionTable.put(state, input, nextState);
}
private int getNextStateId() {
return nextStateId ++;
}
void addToQueue(BaseInput input) {
try {
queue.put(input);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void stop() throws InterruptedException {
killInput.addToStateMachineQueue();
}
public void waitForCompletion() throws InterruptedException{
if(th != null && th.isAlive()){
th.join();
}
}
public boolean waitForCompletion(long timeout) throws InterruptedException{
if(th != null && th.isAlive()){
th.join(timeout);
return !th.isAlive();
}
return true;
}
public void murder() throws InterruptedException{
stop();
if(th != null){
th.join(1000);
if(th.isAlive()){
th.interrupt();
th.join(1000);
}
}
}
void addChildMachine(ChildStateMachineState child) throws Exception {
for(BaseInput input:child.getInputs()){
newTransition(child, input, child);
}
}
List<BaseInput> getInputs() {
return stateTransitionTable.getInputs();
}
int queueSize(){
return queue.size();
}
StateMachineIdFactory getIdFactory() {
return this.factory;
}
boolean getRunInInputThread() {
return runInInputThread;
}
}