/** * 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< 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 } }