package simulation.
finitestatemachine;
/**
* GOAL: Try to create a DSL for a finite state machine.
* Inspired by Chris vanBuskirk version on this site and the Ruby article at
* http://www-128.ibm.com/developerworks/java/library/j-cb03137/index.html,
*/
class FiniteStateMachineTest
extends GroovyTestCase
{
void testNormalProcess
()
{
def fsm = FiniteStateMachine.
newInstance('submitted')
def recorder = fsm.
record()
recorder.
on('accept') {
from
('processing').
to('accepted')
from
('nonprofit_reviewing').
to('accepted')
}
recorder.
on('receive').
from('submitted').
to('processing')
recorder.
on('send_for_review') {
from
('processing').
to('nonprofit_reviewing')
from
('nonprofit_reviewing').
to('processing')
from
('accepted').
to('nonprofit_reviewing')
}
recorder.
on('revise').
from('accepted').
to('processing')
assert 'processing' == fsm.
fire('receive')
assert 'accepted' == fsm.
fire('accept')
}
void testReset
()
{
def fsm = FiniteStateMachine.
newInstance('1')
def recorder = fsm.
record()
recorder.
on('a') {
from
('1').
to('2')
from
('2').
to('3')
}
assert '2' == fsm.
fire('a')
fsm.
reset()
assert '2' == fsm.
fire('a')
}
void testNoTransitionForEvent
()
{
def fsm = FiniteStateMachine.
newInstance('1')
def recorder = fsm.
record()
recorder.
on('a').
from('1').
to('2')
fsm.
fire('a')
shouldFail
{
fsm.
fire('a')
}
}
void testNoInitialState
()
{
shouldFail
{
FiniteStateMachine.
newInstance()
}
}
void testInvalidInitialState
()
{
def fsm = FiniteStateMachine.
newInstance('INVALID')
fsm.
record().
on('1').
from('a').
to('b')
shouldFail
{
fsm.
fire('1')
}
}
void testNoStateForEvent
()
{
def fsm = FiniteStateMachine.
newInstance('a')
fsm.
record().
on('1').
from('a').
to('b')
fsm.
record().
on('2').
from('X').
to('Y')
fsm.
fire('1')
shouldFail
{
fsm.
fire('2')
}
}
void testDupFromState
()
{
def fsm = FiniteStateMachine.
newInstance('a')
def recorder = fsm.
record()
recorder.
on('SAME').
from('DUPLICATESTATE').
to('a')
shouldFail
{
recorder.
on('SAME').
from('DUPLICATESTATE').
to('b')
}
}
void testInvalidToState
()
{
def fsm = FiniteStateMachine.
newInstance('a')
def recorder = fsm.
record()
shouldFail
{
recorder.
on('1').
from('DUPLICATESTATE').
to(null)
}
}
void testIncompleteTransition
()
{
def fsm = FiniteStateMachine.
newInstance('a')
def recorder = fsm.
record()
shouldFail
{
recorder.
on('1').
to('a')
}
}
void testIncompleteTransitionWithClosure
()
{
def fsm = FiniteStateMachine.
newInstance('a')
def recorder = fsm.
record()
shouldFail
{
recorder.
on('1') {
to
('a')
}
}
}
}
class FiniteStateMachine
{
/*
* A map or maps to store events and from states of each
* transition.
*/
def transitions =
[:
]
def initialState
def currentState
FiniteStateMachine
(a_initialState
)
{
assert a_initialState,
"You need to provide an initial state"
initialState = a_initialState
currentState = a_initialState
}
def record
()
{
Grammer.
newInstance(this);
}
def registerTransition
(a_grammer
)
{
assert a_grammer.
isValid(),
"Invalid transition (${a_grammer})"
def transition
def event = a_grammer.
event
def fromState = a_grammer.
fromState
def toState = a_grammer.
toState
if (!transitions
[event
])
{
transitions
[event
] =
[:
]
}
transition = transitions
[event
]
assert !transition
[fromState
],
"Duplicate fromState ${fromState} for transition ${a_grammer}"
transition
[fromState
] = toState
}
def reset
()
{
currentState = initialState
}
def fire
(a_event
)
{
assert currentState,
"Invalid current state '${currentState}': pass into constructor"
assert transitions.
containsKey(a_event
),
"Invalid event '${a_event}', should be one of ${transitions.keySet()}"
def transition = transitions
[a_event
]
def nextState = transition
[currentState
]
assert nextState,
"There is no transition from '${currentState}' to any other state"
currentState = nextState
currentState
}
def isState
(a_state
)
{
currentState == a_state
}
}
class Grammer
{
def fsm
def event
def fromState
def toState
Grammer
(a_fsm
)
{
fsm = a_fsm
}
def on
(a_event
)
{
event = a_event
this
}
def on
(a_event, a_transitioner
)
{
on
(a_event
)
a_transitioner.
delegate =
this
a_transitioner.
call()
this
}
def from
(a_fromState
)
{
fromState = a_fromState
this
}
def to
(a_toState
)
{
assert a_toState,
"Invalid toState: ${a_toState}"
toState = a_toState
fsm.
registerTransition(this)
this
}
def isValid
()
{
event && fromState && toState
}
public String toString
()
{
"${event}:${fromState}=>${toState}"
}
}