package org.csstudio.dal.proxy; import java.util.EnumSet; import org.csstudio.dal.DynamicValueCondition; import org.csstudio.dal.DynamicValueState; import org.csstudio.dal.context.ConnectionState; /** * This is state machine pattern implementation which helps plug implementator * to properly track of changes of connection state * @author ikriznar * */ public class ConnectionStateMachine { private ConnectionState connectionState= ConnectionState.INITIAL; /* * matrix of allowed state transitions */ private static boolean[][] transitions={ /* to INITIA READY CNTING CNCTED OPERAL CNLOST CNFAIL DSCING DSCTED DSTRED*/ /* from INITIAL*/ {false, true, true, false, false, false, false, false, false, true}, /*READY*/ {true, false, true, false, false, false, false, false, false, true}, /*CONNECTING*/ {false, false, false, true, false, false, true, false, false, true}, /*CONNECTED*/ {false, false, false, false, true, true, false, true, false, false}, /*OPERATIONAL*/ {false, false, false, false, false, true, false, true, false, false}, /*CONNECTION_LOST*/ {false, false, false, true, true, false, false, true, false, false}, /*CONNECTION_FAILED*/ {true, false, false, true, true, false, false, true, false, true}, /*DISCONNECTING*/ {false, false, false, false, false, false, false, false, true, false}, /*DISCONNECTED*/ {true, true, true, false, false, false, false, false, false, true}, /*DESTROYED*/ {false, false, false, false, false, false, false, false, false, false} }; /** * Returns <code>true</code> if transition from state1 to state2 is allowed. * @param state1 initial state * @param state2 target state * @return <code>true</code> if transition from state1 to state2 is allowed */ public static final boolean isTransitionAllowed(ConnectionState state1, ConnectionState state2) { return transitions[state1.ordinal()][state2.ordinal()]; } /** * Returns current connection state. * @return */ public ConnectionState getConnectionState() { return connectionState; } public boolean isTransitionAllowed(ConnectionState state) { return transitions[connectionState.ordinal()][state.ordinal()]; } /** * Requests from this state machine to change connection state to provided paramter. * If request was illegal regarding the current state, it will throw exception. * If sconnection wtate was changes, <code>true</code> is returned. * @param state requested new state * @return <code>true</code> if state was accepted and changed * @throws IllegalStateException if request is illegal */ public synchronized boolean requestNextConnectionState(ConnectionState state) throws IllegalStateException { if (connectionState==state) return false; if (isTransitionAllowed(state)) { connectionState=state; return true; } else { connectionState=state; return true; } } /** * Move state one state closer toward connected state. If this called changed internal state, then <code>true</code> is returned. * @return <code>true</code> if this operation changed current state of machine */ public synchronized boolean moveTowardConnected() { if (connectionState.ordinal()>=ConnectionState.CONNECTED.ordinal()) return false; ConnectionState con= ConnectionState.values()[connectionState.ordinal()+1]; if (isTransitionAllowed(con)) { connectionState=con; return true; } return false; } /** * Return true if connection was successfully established. Actual connection * state might be CONNECTED, CONNECTION_LOST or OPERATIONAL. * @return if connection process has been successfully completed. */ public boolean isConnected() { return connectionState.isConnected(); } public boolean isOperational() { return connectionState == ConnectionState.OPERATIONAL; } public boolean isDestroyed() { return connectionState == ConnectionState.DESTROYED; } public boolean isConnectionAlive() { return connectionState.isConnectionAlive(); } public boolean isConnectionFailed() { return connectionState == ConnectionState.CONNECTION_FAILED; } public boolean isConnecting() { return connectionState == ConnectionState.CONNECTING; } /** * Returns updated condition which has states that corresponds to connection state. * If no change is necessary, then returned condition is same as provided. * @param condition the condition to be copied and updated * @return updated condition or same condition, if no update necessary */ public DynamicValueCondition deriveUpdatedCondition(DynamicValueCondition condition) { EnumSet<DynamicValueState> set = EnumSet.copyOf(condition.getStates()); ConnectionState s= connectionState; boolean change= false; if (s==ConnectionState.CONNECTED) { change |= set.remove(DynamicValueState.LINK_NOT_AVAILABLE); change |= set.remove(DynamicValueState.ERROR); change |= set.add(DynamicValueState.NORMAL); } else if (s==ConnectionState.DISCONNECTED) change |= set.add(DynamicValueState.LINK_NOT_AVAILABLE); else if (s==ConnectionState.DESTROYED) change |= set.add(DynamicValueState.LINK_NOT_AVAILABLE); else if (s==ConnectionState.CONNECTION_FAILED) { change |= set.add(DynamicValueState.LINK_NOT_AVAILABLE); change |= set.add(DynamicValueState.ERROR); } else if (s==ConnectionState.CONNECTION_LOST) change |= set.add(DynamicValueState.LINK_NOT_AVAILABLE); if (change) return new DynamicValueCondition(set,null,DynamicValueCondition.CONNECTION_STATE_UPDATE_MESSAGE); else return condition; } /** * Checks state condition set and sets operation connection state if possible. * @param set to be tested for operation * @return <code>true</code> if operation changed state machine */ public boolean requestOperationalState(EnumSet<DynamicValueState> set) { if (connectionState == ConnectionState.OPERATIONAL || set.contains(DynamicValueState.LINK_NOT_AVAILABLE)) return false; if (isTransitionAllowed(ConnectionState.OPERATIONAL) && set.contains(DynamicValueState.HAS_LIVE_DATA) && set.contains(DynamicValueState.HAS_METADATA)) { requestNextConnectionState(ConnectionState.OPERATIONAL); return true; } return false; } @Override public String toString() { return this.getClass().getName()+':'+connectionState.name(); } }