/**
* GOAL: Write a quick-and-dirty DSL in Groovy that emulates Bruce Tate's
* example "acts_as_statemachine" Ruby code at
* http://www-128.ibm.com/developerworks/java/library/j-cb03137/index.html,
* within 20 minutes or less (i.e. steer clear of MOP-magic engineering ;).
*
* For more context, see the Apr'07 "Symbols" thread on the groovy-user mailing
* list.
*/
////////////////////////////////////////////////////////////
// Initialize
////////////////////////////////////////////////////////////
def fsm =
new StateMachine
()
fsm.
current_state =
'submitted' // pretend to look up something with state from dBase
////////////////////////////////////////////////////////////
// Define
////////////////////////////////////////////////////////////
fsm.
state 'submitted'
fsm.
state 'processing'
fsm.
state 'nonprofit_reviewing'
fsm.
state 'accepted'
fsm.
event('accept',
[
[from:
'processing', to:
'accepted'],
[from:
'nonprofit_reviewing', to:
'accepted']
])
fsm.
event('receive',
[
[from:
'submitted', to:
'processing']
])
fsm.
event('send_for_review',
[
[from:
'processing', to:
'nonprofit_reviewing'],
[from:
'nonprofit_reviewing', to:
'processing'],
[from:
'accepted', to:
'nonprofit_reviewing']
])
fsm.
event('revise',
[
[from:
'accepted', to:
'processing']
])
println fsm
println '----------'
println ()
////////////////////////////////////////////////////////////
// Use:
////////////////////////////////////////////////////////////
println fsm.
current_state
assert fsm.
current_state ==
'submitted'
fsm.
fire 'receive'
fsm.
fire 'accept'
println fsm.
current_state
assert fsm.
current_state ==
'accepted'
/** A list/map-based implementation of a StateMachine DSL-ish thingy */
public class StateMachine
{
def current_state =
'initial' // literals are intern()ed automatically
def valid_states =
['initial']
def transitions =
[:
] // a LUT from events to defined transitions
/** add a new valid state to the machine */
def state =
{symbolic_name -> valid_states<<symbolic_name; valid_states.
unique()}
/** define $transitions, which maps from (state,event) --> state */
def event
(event,tuples
) {
tuples.
each { transition ->
assert valid_states.
contains(transition.
from)
assert valid_states.
contains(transition.
to)
def trigger =
[event:event, from:transition.
from]
transitions
[trigger
] = transition.
to
}
}
/** have the machine process an event */
def fire
(event
) {
def context =
[event:event, from:current_state
]
//BUG: Map::find() doesn't work with two-param Closures?
// def transition = transitions.find {trigger,_ -> trigger==context }
def transition = transitions.
findAll {trigger,_ -> trigger==context
}
if (transition
) {
current_state = transition
[context
] // findAll() version
// current_state = transition.value // find() version
}
}
/** pretty-print the states and their defined transitions */
public String toString
() {
def result =
""
valid_states.
each { state ->
result
<<=
"state ${state}:\n" // using <<= to coerce to StringBuffer
transitions.
each { transition ->
def from = transition.
key.
from
def event = transition.
key.
event
def to = transition.
value
if (from==state
) {result<<
"\t |-- ${event} --> ${to}\n" }
}
}
return result
}
}