Skip to content

Commit

Permalink
Implemented diagnostic trace listeners on the state machine. Created …
Browse files Browse the repository at this point in the history
…a simple simulation of a coin operated tunrstile to demonstrate the state machine in use.
  • Loading branch information
NameOfTheDragon committed Feb 21, 2014
1 parent 1a8e591 commit 46025fc
Show file tree
Hide file tree
Showing 15 changed files with 755 additions and 43 deletions.
10 changes: 10 additions & 0 deletions .idea/codeStyleSettings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
package uk.co.tigranetworks;

/**
* Created by Tim on 20/02/14.
*/
Expand Down
128 changes: 118 additions & 10 deletions src/StateMachine.java → ...src/uk/co/tigranetworks/StateMachine.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**
package uk.co.tigranetworks; /**
* Created by Tim on 19/02/14.
*/

Expand Down Expand Up @@ -38,9 +38,13 @@
*/
public class StateMachine
{
private final State hiddenStateWithNoTransitions = new State("State Machine Paused");
private final Object transitionLock = new Object(); // protects against simultaneous transitions
private volatile State currentState = hiddenStateWithNoTransitions;
private final State hiddenStateWithNoTransitions = new State("State Machine Paused");
private volatile State currentState = hiddenStateWithNoTransitions;
private final Object transitionLock = new Object(); // protects against simultaneous transitions

// Event sources that produce information about the inner workings of the state machine.
private TraceListener onStateChanged;
private TraceListener onTrigger;

public State getCurrentState()
{
Expand All @@ -54,7 +58,7 @@ public State getCurrentState()
* Until the machine is started, all transitions and triggers
* will be inoperative and the state machine will not function.
* <p/>
* Any subsequent calls result in a FalseStartException, and the
* Any subsequent calls result in a uk.co.tigranetworks.FalseStartException, and the
* state machine will remain unaffected. If the state machine
* needs to be restarted for any reason, then it must be
* destroyed and re-created.
Expand All @@ -67,7 +71,7 @@ public void start(State initialState) throws FalseStartException
{
if (currentState != hiddenStateWithNoTransitions)
throw new FalseStartException();
this.currentState = initialState;
transitionToNewState(hiddenStateWithNoTransitions, initialState);
}

/**
Expand Down Expand Up @@ -97,14 +101,94 @@ private void transitionToNewState(State fromState, State toState)
{
if (fromState != null)
fromState.onExit.action();
} finally
}
finally
{
currentState = toState;
raiseOnStateChanged(fromState.getName(), toState.getName());
}
toState.onEnter.action();
}
}

/**
* Fires the specified trance event and provides the descriptive text
* to the listener.
*
* @param listener The event being raised
* @param description Trace output text.
*/
private void raiseTraceEvent(TraceListener listener, String description)
{
try
{
if (listener != null)
{
listener.trace(description);
}
}
catch (Exception ex)
{
// Trace is not allowed to throw any exceptions.
}
}

/**
* Raises the OnStateChanged trace event.
*
* @param fromState The name of the original state.
* @param toState The name of the destination state.
*/
protected void raiseOnStateChanged(String fromState, String toState)
{
try
{
String description = String.format("State transition [%s]->[%s]", fromState, toState);
raiseTraceEvent(onStateChanged, description);
}
catch (Exception ex)
{
}
}

/**
* Raises the OnTrigger trace event.
*
* @param fromState The name of the original state.
* @param toState The name of the destination state.
*/
protected void raiseOnTrigger(String fromState, String toState, String outcome)
{
try
{
String description = String.format("Triggered transition from [%s] to [%s] outcome: %s", fromState, toState, outcome);
raiseTraceEvent(onTrigger, description);
}
catch (Exception ex)
{
}
}

/**
* Sets a listener for the OnStateChanged event.
*
* @param listener The listener. There can be only one.
*/
public void setOnStateChangedListener(TraceListener listener)
{
onStateChanged = listener;
}

/**
* Sets a listener for the OnTrigger event.
*
* @param listener The listener. There can be only one.
*/
public void setOnTriggerListener(TraceListener listener)
{
onTrigger = listener;
}

/**
* Represents a state that the state machine can be in.
*/
Expand All @@ -127,7 +211,7 @@ public void action()
* The OnExit action for the state, with default null implementation.
* Can be overridden to provide a custom OnExit action.
*/
protected StateTransitionAction onExit = new StateTransitionAction()
protected StateTransitionAction onExit = new StateTransitionAction()
{
@Override
public void action()
Expand All @@ -145,6 +229,25 @@ public State(String name)
this.name = name;
}

/**
* Creates a new state, with action methods for the ONEnter and OnExit actions.
* If either action is null, then the default null action is used.
*
* @param name The name of the state (required; not null or empty).
* @param onEnter The action to be performed on entering this state (or null if none).
* @param onExit The action to be performed on leaving this state (or null if none).
*/
public State(String name, StateTransitionAction onEnter, StateTransitionAction onExit)
{
if (name.isEmpty())
throw new IllegalArgumentException("State name must not be empty or null");
this.name = name;
if (onEnter != null)
this.onEnter = onEnter;
if (onExit != null)
this.onExit = onExit;
}

/**
* Gets the descriptive name of the current state.
*/
Expand All @@ -163,7 +266,7 @@ public String getName()
*/
public class Transition implements ActionListener
{
TransitionRule rule = new TransitionRule()
public TransitionRule rule = new TransitionRule()
{
@Override
public boolean transitionIsAllowed()
Expand Down Expand Up @@ -215,12 +318,17 @@ public void trigger()
// Triggers are only valid if the state machine is in the correct state, otherwise they are ignored.
if (StateMachine.this.currentState != State.this)
{
// ToDo - print some diagnostics about the trigger being ignored.
raiseOnTrigger(State.this.getName(), destinationState.getName(), "disarmed");
return;
}

if (rule.transitionIsAllowed())
{
raiseOnTrigger(State.this.getName(), destinationState.getName(), "armed, executing");
StateMachine.this.transitionToNewState(State.this, destinationState);
}
else
raiseOnTrigger(State.this.getName(), destinationState.getName(), "armed, rejected");
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
package uk.co.tigranetworks;

public interface StateTransitionAction
{
public void action();
Expand Down
11 changes: 11 additions & 0 deletions StateMachine/src/uk/co/tigranetworks/TraceListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package uk.co.tigranetworks;

import java.util.EventListener;

/**
* Created by Tim on 21/02/14.
*/
public interface TraceListener extends EventListener
{
public void trace(String traceOutput);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package uk.co.tigranetworks;

import java.util.EventListener;

public interface TransitionRule extends EventListener
{
public boolean transitionIsAllowed();

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
</library>
</orderEntry>
<orderEntry type="module" module-name="TN.FiniteStateMachine" />
<orderEntry type="module" module-name="StateMachine" />
</component>
</module>

Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import org.junit.Test;
import uk.co.tigranetworks.FalseStartException;
import uk.co.tigranetworks.StateMachine;
import uk.co.tigranetworks.TransitionRule;

/**
* Created by Tim on 20/02/14.
Expand Down Expand Up @@ -114,6 +117,5 @@ public void InitialStateCanOnlyBeSetOnce() throws FalseStartException
machine.start(initialState);
assert machine.getCurrentState() == initialState;
machine.start(initialState); // should throw

}
}
96 changes: 96 additions & 0 deletions TurnstileSample/src/com/codeproject/javaConsole/BlockCaret.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.codeproject.javaConsole;

import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultCaret;
import javax.swing.text.JTextComponent;
import java.awt.*;

/**
* @author David MacDermot
* @brief Custom block caret for the Java Console.
* @par Comments:
* Adapted from http://www.java2s.com/Code/Java/Swing-JFC/Acustomcaretclass.htm
* @date 02-07-2012
* @bug
*/
public class BlockCaret extends DefaultCaret
{

private static final long serialVersionUID = 1L;

/**
* @brief Class Constructor
*/
public BlockCaret()
{
setBlinkRate(500); // half a second
}

/* (non-Javadoc)
* @see javax.swing.text.DefaultCaret#damage(java.awt.Rectangle)
*/
protected synchronized void damage(Rectangle r)
{
if (r == null)
return;

// give values to x,y,width,height (inherited from java.awt.Rectangle)
x = r.x;
y = r.y;
height = r.height;
// A value for width was probably set by paint(), which we leave alone.
// But the first call to damage() precedes the first call to paint(), so
// in this case we must be prepared to set a valid width, or else
// paint()
// will receive a bogus clip area and caret will not get drawn properly.
if (width <= 0)
width = getComponent().getWidth();

repaint(); //Calls getComponent().repaint(x, y, width, height) to erase
repaint(); // previous location of caret. Sometimes one call isn't enough.
}

/* (non-Javadoc)
* @see javax.swing.text.DefaultCaret#paint(java.awt.Graphics)
*/
public void paint(Graphics g)
{
JTextComponent comp = getComponent();

if (comp == null)
return;

int dot = getDot();
Rectangle r = null;
char dotChar;
try
{
r = comp.modelToView(dot);
if (r == null)
return;
dotChar = comp.getText(dot, 1).charAt(0);
}
catch (BadLocationException e)
{
return;
}

if (Character.isWhitespace(dotChar)) dotChar = '_';

if ((x != r.x) || (y != r.y))
{
// paint() has been called directly, without a previous call to
// damage(), so do some cleanup. (This happens, for example, when
// the text component is resized.)
damage(r);
return;
}

g.setColor(comp.getCaretColor());
g.setXORMode(comp.getBackground()); // do this to draw in XOR mode

width = g.getFontMetrics().charWidth(dotChar);
if (isVisible())
g.fillRect(r.x, r.y, width, r.height);
}
}
Loading

0 comments on commit 46025fc

Please sign in to comment.