package org.appwork.controlling; import java.util.ArrayList; import java.util.HashMap; import org.appwork.utils.logging.Log; public class StateMachine { private static State checkState(final State state) { State finalState = null; for (final State s : state.getChildren()) { final State ret = StateMachine.checkState(s); if (finalState == null) { finalState = ret; } if (finalState != ret) { throw new StateConflictException("States do not all result in one common final state"); } } if (finalState == null) { throw new StateConflictException(state + " is a blind state (has no children)"); } return finalState; } /** * validates a statechain and checks if all states can be reached, and if * all chans result in one common finalstate * * @param initState * @throws StateConflictException */ public static void validateStateChain(final State initState) { if (initState.getParents().size() > 0) { throw new StateConflictException("initState must not have a parent"); } StateMachine.checkState(initState); } private final State initState; private volatile State currentState; private final StateEventsender eventSender; private final State finalState; private final ArrayList<StatePathEntry> path; private final StateMachineInterface owner; private final Object lock = new Object(); private final Object lock2 = new Object(); private final HashMap<State, Throwable> exceptionMap; public StateMachine(final StateMachineInterface interfac, final State startState, final State endState) { this.owner = interfac; this.initState = startState; this.currentState = startState; this.finalState = endState; this.exceptionMap = new HashMap<State, Throwable>(); this.eventSender = new StateEventsender(); this.path = new ArrayList<StatePathEntry>(); this.path.add(new StatePathEntry(this.initState)); } public void addListener(final StateEventListener listener) { this.eventSender.addListener(listener); } /** * synchronized execution of a runnable if statemachine is currently in a * given state * * @param run * @param state * @return */ public boolean executeIfOnState(final Runnable run, final State state) { if (run == null || state == null) { return false; } synchronized (this.lock) { if (this.isState(state)) { run.run(); return true; } } return false; } /* * synchronized hasPassed/addListener to start run when state has * reached/passed */ public void executeOnceOnState(final Runnable run, final State state) { if (run == null || state == null) { return; } boolean reached = false; synchronized (this.lock) { if (this.hasPassed(state)) { reached = true; } else { this.addListener(new StateListener(state) { @Override public void onStateReached(final StateEvent event) { StateMachine.this.removeListener(this); run.run(); } }); } if (reached) { new Thread(run, "AsyncOnStateWorker").start(); } } } public void fireUpdate(final State currentState) { if (currentState != null) { synchronized (this.lock) { if (this.currentState != currentState) { throw new StateConflictException("Cannot update state " + currentState + " because current state is " + this.currentState); } } } final StateEvent event = new StateEvent(this, StateEvent.Types.UPDATED, currentState, currentState); this.eventSender.fireEvent(event); } public void forceState(final State newState) { StateEvent event; synchronized (this.lock) { if (this.currentState == newState) { return; } event = new StateEvent(this, StateEvent.Types.CHANGED, this.currentState, newState); synchronized (this.lock2) { this.path.add(new StatePathEntry(newState)); } Log.L.finest(this.owner + " State changed " + this.currentState + " -> " + newState); this.currentState = newState; } this.eventSender.fireEvent(event); } public Throwable getCause(final State newState) { return this.exceptionMap.get(newState); } /** * TODO: not synchronized * * @param failedState * @return */ public StatePathEntry getLatestStateEntry(final State failedState) { try { StatePathEntry entry = null; synchronized (this.lock2) { for (int i = this.path.size() - 1; i >= 0; i--) { entry = this.path.get(i); if (entry.getState() == failedState) { return entry; } } } } catch (final Exception e) { } return null; } public StateMachineInterface getOwner() { return this.owner; } /** * @return the path */ public ArrayList<StatePathEntry> getPath() { return this.path; } public State getState() { return this.currentState; } // public void forceState(int id) { // // State newState; // synchronized (lock) { // newState = getStateById(this.initState, id, null); // if (newState == null) throw new // StateConflictException("No State with ID " + id); // } // forceState(newState); // } public boolean hasPassed(final State... states) { synchronized (this.lock2) { for (final State s : states) { for (final StatePathEntry e : this.path) { if (e.getState() == s) { return true; } } } } return false; } // private State getStateById(State startState, int id, ArrayList<State> // foundStates) { // // if (foundStates == null) foundStates = new ArrayList<State>(); // if (foundStates.contains(startState)) return null; // foundStates.add(startState); // State ret = null; // for (State s : startState.getChildren()) { // // if (s.getID() == id) return s; // ret = getStateById(s, id, foundStates); // if (ret != null) return ret; // } // return null; // } public boolean isFinal() { synchronized (this.lock) { return this.finalState == this.currentState; } } /** * returns if the statemachine is in startstate currently */ public boolean isStartState() { synchronized (this.lock) { return this.currentState == this.initState; } } public boolean isState(final State... states) { synchronized (this.lock) { for (final State s : states) { if (s == this.currentState) { return true; } } } return false; } public void removeListener(final StateEventListener listener) { this.eventSender.removeListener(listener); } public void reset() { StateEvent event; synchronized (this.lock) { if (this.currentState == this.initState) { return; } if (this.finalState != this.currentState) { throw new StateConflictException("Cannot reset from state " + this.currentState); } event = new StateEvent(this, StateEvent.Types.CHANGED, this.currentState, this.initState); Log.L.finest(this.owner + " State changed (reset) " + this.currentState + " -> " + this.initState); this.currentState = this.initState; synchronized (this.lock2) { this.path.clear(); this.path.add(new StatePathEntry(this.initState)); } } this.eventSender.fireEvent(event); } public void setCause(final State failedState, final Throwable e) { this.exceptionMap.put(failedState, e); } public void setStatus(final State newState) { synchronized (this.lock) { if (this.currentState == newState) { return; } if (!this.currentState.getChildren().contains(newState)) { throw new StateConflictException("Cannot change state from " + this.currentState + " to " + newState); } } this.forceState(newState); } /** * Throws a StateViolationException if the current state is not state * * @param downloadBranchlist */ public void validateState(final State state) { if (!this.isState(state)) { throw new StateViolationException(state); } } }