|
|
|
Groovy FSM DSL (toy version)
2
/**
* 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.
*/
* 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.
*/
/**
* 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
}
}




==================================================
state initial:
state submitted:
|-- receive --> processing
state processing:
|-- accept --> accepted
|-- send_for_review --> nonprofit_reviewing
state nonprofit_reviewing:
|-- send_for_review --> processing
|-- accept --> accepted
state accepted:
|-- send_for_review --> nonprofit_reviewing
|-- revise --> processing
----------
submitted
accepted