Groovy FSM DSL (toy version)





2
Date Submitted Thu. Apr. 12th, 2007 11:23 AM
Revision 1 of 1
Beginner vbuskirk
Tags dsl | FSM | groovy | Machines | State
Comments 1 comments
/**
* 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
    }
   
}
 

Chris vanBuskirk

Comments

Comments Example stdout
Thu. Apr. 12th, 2007 2:20 PM    Beginner vbuskirk

Voting