Browse Source

New class derived from Finite_State_Machine to add writing methods.

Frederic G. Marand 17 years ago
parent
commit
ca86932226
1 changed files with 355 additions and 0 deletions
  1. 355 0
      Fsm_Writer.php

+ 355 - 0
Fsm_Writer.php

@@ -0,0 +1,355 @@
+<?php
+/**
+ * Finite state machine writer.
+ *
+ * Keeping the FSM writer (generation) functions out of the root class minimize
+ * the code volume for FSM readers, which should represent the majority of FSM
+ * users.
+ *
+ * @copyright  (c) 2007 OSI
+ * @author     Frédéric G. MARAND
+ * @license    Licensed under the CeCILL 2.0
+ * @version    CVS: $Id: Fsm_Writer.php,v 1.1 2007-06-10 19:42:35 marand Exp $
+ * @link       http://wiki.audean.com/fsm/fsm
+ * @since      Not applicable yet
+ * @package    fsm
+ * @subpackage fsm.core
+ */
+
+/**
+ * needed notably for autoload and func_name()
+ */
+require_once('misc.php');
+
+$erFiniteStateMachine = error_reporting(E_ALL|E_STRICT);
+
+/**
+ * Read/Write concrete FSM class.
+ *
+ * This (concrete) class extends the default abstract
+ * Finite_State_Machine to add write behaviours. It is to be used by
+ * applications wishing to write FSM files. Other applications should not
+ * use it to avoid code bloat.
+ * @package    fsm
+ * @subpackage fsm.core
+ */
+class Fsm_Writer extends Finite_State_Machine
+  {
+  const VERSION = '$Id: Fsm_Writer.php,v 1.1 2007-06-10 19:42:35 marand Exp $';
+
+  public function __construct()
+    {
+    $this->f_transitions = array();
+    parent::__construct();
+    }
+
+  /**
+   * Return the initial and final states from an abstract FSM graph
+   *
+   * @param array $graph Abstract FSM
+   * @return array
+   */
+  protected function getSpecialStates($graph)
+    {
+    $ret[0] = NULL;
+    $ret[1] = NULL;
+    foreach ($graph['states'] as $state)
+      {
+      if (isset($state['type']) && $state['type'] == 'initial')
+        {
+        $ret[0]  = $state['name'];
+        }
+      if (isset($state['type']) && $state['type'] == 'final')
+        {
+        $ret[1]  = $state['name'];
+        }
+      if (isset($ret[0]) && isset($ret[1]))
+        {
+        break;
+        }
+      }
+    return $ret;
+    }
+
+  /**
+   * Return the transitions (triggers) available from a given state
+   *
+   * @param array $graph Abstract FSM
+   * @param string $id State id in the abstract graph (not id in the XML FSM)
+   */
+  protected function getTransitionsForState($graph, $id)
+    {
+    $ret = array();
+    foreach ($graph['transitions'] as $txid => $transition)
+      {
+      // echo "id = $id, txid = $txid from = " . $transition['from'] . PHP_EOL;
+      if ($transition['from'] == $id)
+        {
+        $ret[] = $txid;
+        }
+      }
+    return $ret;
+    }
+
+  /**
+   * Generate the XML FSM file and optionally PHP stubs file from an abstract
+   * FSM representation as an array of states and transitions
+   *
+   * @param array $graph
+   * @param string $prefix
+   * @param boolean $php
+   * @param boolean $overwrite
+   * @todo take $overwrite and $php into account
+   * @deprecated
+   */
+  public function old_save_fsm($graph, $prefix = 'fsm', $php = FALSE, $overwrite = FALSE)
+    {
+    echo "This is a dump for FSM $prefix"
+      . ', ' . ($php ? 'with' : 'without') . ' PHP generation'
+      . ', ' . ($overwrite ? 'with' : 'without') . ' overwrite mode'
+      . ".\n";
+
+    $doc = new DOMDocument('1.0', 'utf-8');
+    $comment = new DOMComment(' Generated by '
+      . basename(__FILE__, '.php')
+      . " version " . Fsm_Writer::VERSION
+      . " ");
+    $doc->appendChild($comment);
+    $fsm = new DOMElement('fsm');
+    $doc->appendChild($fsm);
+    $fsm->setAttribute('fsm_version',   "1.3");
+    $fsm->setAttribute('idle_work',     "1");
+    $fsm->setAttribute('allow_actions', "1");
+    list($init, $final) = $this->getSpecialStates($graph);
+    $fsm->setAttribute('init', $init);
+    $fsm->setAttribute('final', $final);
+
+    foreach ($graph['states'] as $id => $state)
+      {
+      $state_node = new DOMElement('state');
+      $fsm->appendChild($state_node);
+      $state_node->setAttribute('id', $state['name']); // setIdAttribute not yet implemented in php 5.1
+      $transitions = $this->getTransitionsForState($graph, $id);
+      /**
+       * FGM on ne peut pas simplement boucler ainsi: on a dans le graphe
+       * abstrait une transition par évènement, alors que dans la FSM les
+       * transitions partant du même état sont groupées. En se contentant
+       * de boucler ainsi, on crée un "event" pour chaque transition alors
+       * qu'il faut créer 1 event et "n" next.
+       */
+      foreach ($transitions as $transition_id)
+        {
+        $event_node = new DOMElement('event');
+        $state_node->appendChild($event_node);
+        $transition = $graph['transitions'][$transition_id];
+        $event_node->setAttribute('name', $transition['trigger']);
+        /**
+         * @todo support other event types
+         */
+        $event_node->setAttribute('type', 'string');
+        }
+      }
+    $doc->save("$prefix.xml");
+    print_r($graph['transitions']);
+    }
+
+  /**
+   * Add a new state to the transitions table
+   *
+   * @param string $name
+   * @return integer 0 on success, < 0 on failure
+   */
+  public function addState($name)
+    {
+    // echo func_name() . "($name)";
+
+    if (array_key_exists($name, $this->f_transitions))
+      {
+      $ret = -1;
+      }
+    else
+      {
+      $this->f_transitions[$name] = array();
+      $ret = 0;
+      }
+    // echo ", ret = $ret\n";
+    return $ret;
+    }
+
+  /**
+   * Add an event definition (name + optional type of handler) to an existing state
+   *
+   * @param string $state
+   * @param string $event
+   * @param string $type
+   * @return integer 0 on success, < 0 on failure
+   */
+  public function addEvent($state, $event, $type = 'string')
+    {
+    if (!array_key_exists($state, $this->f_transitions))
+      {
+      $ret = -1;
+      }
+    elseif (array_key_exists($event, $this->f_transitions[$state]))
+      {
+      $ret = -2;
+      }
+    else // state exists, event doesn't: OK
+      {
+      $this->f_transitions[$state][$event] = array();
+      $this->f_transitions[$state][$event]["#type"] = $type;
+      $ret = 0;
+      }
+    return $ret;
+    }
+
+  /**
+   * Add an outcome definition to an existing event:
+   * - event handler result
+   * - next state
+   * - event to fire after transition to next state
+   * All outcome fields are optional
+   *
+   * @param name $state
+   * @param name $event
+   * @return integer 0 on success, < 0 on failure
+   */
+  public function addOutcome($state, $event,
+    $result     = NULL,  // if null, single possible outcome; only possible on void events
+    $next_state = NULL,  // if null, stay on same state
+    $action     = NULL)  // if null, do not send an event after transitioning
+    {
+    $t = &$this->f_transitions;
+    if (!array_key_exists($state, $t))
+      {
+      $ret = -1;
+      }
+    elseif (!array_key_exists($event, $t[$state]))
+      {
+      $ret = -2;
+      }
+    elseif (($result == NULL) && ($t[$state][$event]['#type'] <> 'void'))
+      {
+      $ret = -3; // Undefined, single, results are only available for void events
+      }
+    elseif (($result != NULL) && array_key_exists($result, $t[$state][$event]))
+      {
+      $ret = -4; // results must be unique per event
+      }
+    else
+      {
+      $t[$state][$event][$result] = array($next_state, $action);
+      $ret = 0;
+      }
+    return $ret;
+    }
+
+  /**
+   * Defines the special states in the FSM
+   *
+   * @param string $kind Finite_State_Machine::INIT_STATE or ..FINAL_STATE
+   * @param string $name
+   */
+  public function setSpecialState($kind, $name)
+    {
+    // echo func_name() . "($kind, $name)\n";
+    if (($kind != Finite_State_Machine::INIT_STATE) && ($kind != Finite_State_Machine::FINAL_STATE))
+      {
+      $ret = -1; // unknown special state
+      }
+    elseif (!array_key_exists($name, $this->f_transitions))
+      {
+      $ret = -2; // non existent state declared as special
+      }
+    else
+      {
+      $this->f_transitions["#$kind"] = $name;
+      $ret = 0;
+      }
+    return $ret;
+    }
+
+  /*
+   * Generate the XML FSM file and optionally PHP stubs file from a valid
+   * instanc of Finite_State_Machine
+   *
+   * @param string $prefix
+   * @param boolean $php
+   * @param boolean $overwrite
+   * @todo take $overwrite and $php into account
+   * @throws Exception from within _check_transitions
+   */
+  public function save_fsm($prefix = 'fsm', $php = FALSE, $overwrite = FALSE)
+    {
+    $this->_check_transitions();
+
+    $t = &$this->f_transitions;
+    echo "This is a dump for FSM $prefix"
+      . ', ' . ($php ? 'with' : 'without') . ' PHP generation'
+      . ', ' . ($overwrite ? 'with' : 'without') . ' overwrite mode'
+      . ".\n";
+
+    $doc = new DOMDocument('1.0', 'utf-8');
+    $comment = new DOMComment(' Generated by '
+      . basename(__FILE__, '.php')
+      . " version " . Fsm_Writer::VERSION
+      . " on FSM version " . Finite_State_Machine::VERSION
+      . " ");
+    $doc->appendChild($comment);
+    $fsm = new DOMElement('fsm');
+    $doc->appendChild($fsm);
+    $fsm->setAttribute('fsm_version',   "1.3");
+    $fsm->setAttribute('idle_work',     "1");
+    $fsm->setAttribute('allow_actions', "1");
+
+    $fsm->setAttribute('init',  $t['#' . Finite_State_Machine::INIT_STATE]);
+    $fsm->setAttribute('final', $t['#' . Finite_State_Machine::FINAL_STATE]);
+
+    foreach ($this->f_transitions as $state_name => $state)
+      {
+      if ($state_name[0] == '#')
+        {
+        continue; // ignore special state definitions
+        }
+      $state_node = new DOMElement('state', "\n");
+      $fsm->appendChild($state_node);
+      $state_node->setAttribute('id', $state_name);
+      foreach ($state as $event_name => $event)
+        {
+        $event_node = new DOMElement('event', "\n");
+        $state_node->appendChild($event_node);
+        $event_node->setAttribute('name', $event_name);
+        $event_node->setAttribute('type', $event['#type']);
+        foreach ($event as $outcome_name => $outcome)
+          {
+          if ($outcome_name[0] == '#')
+            {
+            continue; // ignore special outcome definitions (event type)
+            }
+          $outcome_node = new DOMElement('next', "\n");
+          $event_node->appendChild($outcome_node);
+          /**
+           * Generated FSMs currently always use the "string" event handler type,
+           * meaning they always have outcome results. This will not always be
+           * the case, hence this test (think ftp.xml)
+           */
+          if (!empty($outcome_name))
+            {
+            $outcome_node->setAttribute('result', $outcome_name);
+            }
+
+          if (!empty($outcome[0]))
+            {
+            $outcome_node->setAttribute('state', $outcome[0]);
+            }
+          if (!empty($outcome[1]))
+            {
+            $outcome_node->setAttribute('action', $outcome[1]);
+            }
+          }
+        }
+      }
+
+    $doc->save("$prefix.xml");
+    }
+  }