FSM.java
001 /*
002  *  FSM.java
003  *
004  *  Copyright (c) 1995-2010, The University of Sheffield. See the file
005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  *
007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
008  *  software, licenced under the GNU Library General Public License,
009  *  Version 2, June 1991 (in the distribution as file licence.html,
010  *  and also available at http://gate.ac.uk/gate/licence.html).
011  *
012  *  Valentin Tablan, 29/Mar/2000
013  *
014  *  $Id: FSM.java 12006 2009-12-01 17:24:28Z thomas_heitz $
015  */
016 
017 package gate.fsm;
018 
019 import java.util.*;
020 
021 import gate.jape.*;
022 import gate.util.Benchmark;
023 import gate.util.SimpleArraySet;
024 import static gate.jape.KleeneOperator.Type.*;
025 
026 /**
027   * This class implements a standard Finite State Machine.
028   * It is used for both deterministic and non-deterministic machines.
029   */
030 public class FSM implements JapeConstants {
031   
032   private ArrayList<RuleTime> ruleTimes = new ArrayList<RuleTime>();
033   
034   public ArrayList<RuleTime> getRuleTimes() {
035     return ruleTimes;
036   }
037 
038   private void decorateStates() {
039     HashMap<String,Integer>  temporaryRuleNameToIndexMap = new HashMap<String,Integer>();
040     ruleTimes.add(new RuleTime(0,State.INITIAL_RULE));
041     ruleTimes.add(new RuleTime(0,State.UNKNOWN_RULE));
042     ruleTimes.add(new RuleTime(0,State.UNVISITED_RULE));
043     int ruleIndex = State.INITIAL_INDEX;
044     for (Transition t : this.getInitialState().getTransitions()) {
045       ruleIndex = t.getTarget().getRuleForState(temporaryRuleNameToIndexMap, ruleTimes);
046       assert (ruleIndex != State.UNVISITED_INDEX&& (ruleIndex != State.UNKNOWN_INDEX);
047     }
048     this.getInitialState().setIndexInRuleList(State.INITIAL_INDEX);
049   }
050 
051 
052   /** Debug flag */
053   private static final boolean DEBUG = false;
054 
055   /**
056    * The constructor that all the other constructors should call.
057    */
058   protected FSM() {
059     initialState = new State();
060   }
061 
062   /**
063     * Builds a standalone FSM starting from a single phase transducer.
064     @param spt the single phase transducer to be used for building this FSM.
065     */
066   public FSM(SinglePhaseTransducer spt){
067     this();
068     addRules(spt.getRules());
069     if (Benchmark.isBenchmarkingEnabled()) {
070       this.decorateStates();
071     }
072   }
073 
074   /**
075    * Do the work involved in creating an FSM from a PrioritisedRuleList.
076    */
077   protected void addRules(PrioritisedRuleList rules) {
078     Iterator rulesEnum = rules.iterator();
079 
080     while(rulesEnum.hasNext()){
081       FSM ruleFSM = spawn((RulerulesEnum.next());
082 
083       //added by Karter start -> JapeDebugger
084       ruleHash.putAll(ruleFSM.ruleHash);
085       //added by Karter end
086 
087       initialState.addTransition(new Transition(null,
088                                                 ruleFSM.getInitialState()));
089     }
090 
091     eliminateVoidTransitions();
092   }
093 
094   /**
095     * Builds a FSM starting from a rule. This FSM is actually a part of a larger
096     * one (usually the one that is built based on the single phase transducer
097     * that contains the rule).
098     * built by this constructor.
099     @param rule the rule to be used for the building process.
100     */
101   public FSM(Rule rule) {
102     this();
103     setRule(rule);
104   }
105 
106 
107   /**
108    * Do the work involved in creating an FSM from a Rule.
109    */
110   protected void setRule(Rule rule) {
111 
112     LeftHandSide lhs = rule.getLHS();
113 
114     //added by Karter start -> JapeDebugger
115     LinkedList<String> ll = new LinkedList<String>();
116     String label = currentLHSBinding(lhs);
117     ll.add(label);
118     ruleHash.put(rule.getName(), label);
119     //added by Karter end
120 
121 
122     PatternElement[][] constraints =
123                        lhs.getConstraintGroup().getPatternElementDisjunction();
124     // the rectangular array constraints is a disjunction of sequences of
125     // constraints = [[PE]:[PE]...[PE] ||
126     //                [PE]:[PE]...[PE] ||
127     //                ...
128     //                [PE]:[PE]...[PE] ]
129 
130     //The current and the next state for the current ROW.
131     State currentRowState, nextRowState;
132     State finalState = new State();
133     PatternElement currentPattern;
134 
135     for(int i = 0; i < constraints.length; i++){
136       // for each row we have to create a sequence of states that will accept
137       // the sequence of annotations described by the restrictions on that row.
138       // The final state of such a sequence will always be a final state which
139       // will have associated the right hand side of the rule used for this
140       // constructor.
141 
142       // For each row we will start from the initial state.
143       currentRowState = initialState;
144       for(int j=0; j < constraints[i].length; j++) {
145 
146         // parse the sequence of constraints:
147         // For each basic pattern element add a new state and link it to the
148         // currentRowState.
149         // The case of kleene operators has to be considered!
150         currentPattern = constraints[i][j];
151         State insulator = new State();
152         currentRowState.addTransition(new Transition(null,insulator));
153         currentRowState = insulator;
154         if(currentPattern instanceof BasicPatternElement) {
155           //the easy case
156           nextRowState = new State();
157 
158           //added by Karter start -> JapeDebugger
159           LinkedList<String> sll = new LinkedList<String>();
160           sll.add(currentBasicBinding( (BasicPatternElementcurrentPattern));
161           currentRowState.addTransition(
162               new Transition( (BasicPatternElementcurrentPattern,
163                              nextRowState
164                              /*added by Karter*/, sll));
165           //added by Karter end
166 
167           currentRowState = nextRowState;
168         else if(currentPattern instanceof ComplexPatternElement) {
169 
170           // the current pattern is a complex pattern element
171           // ..it will probaly be converted into a sequence of states itself.
172 
173           //  -> JapeDebugger
174           currentRowState = convertComplexPE(
175               currentRowState,
176               (ComplexPatternElementcurrentPattern,
177               /*changed by Karter "new LinkedList()"*/ll);
178 
179         else {
180           // we got an unknown kind of pattern
181           throw new RuntimeException("Strange looking pattern: " +
182                                      currentPattern);
183         }
184 
185       // for j
186 
187       //link the end of the current row to the final state using
188       //an empty transition.
189       currentRowState.addTransition(new Transition(null,finalState));
190       finalState.setAction(rule.getRHS());
191       finalState.setFileIndex(rule.getPosition());
192       finalState.setPriority(rule.getPriority());
193     // for i
194   }
195 
196   /**
197    * Builds a FSM starting from a ComplexPatternElement. This FSM is usually
198    * part of a larger FSM based on the Rule that contains the
199    * ComplexPatternElement.
200    *
201    @param cpe
202    *            the ComplexPatternElement to be used for the building process.
203    */
204   protected FSM(ComplexPatternElement cpe) {
205       this();
206       finalState = convertComplexPE(initialState, cpe, new LinkedList<String>());
207       finalState.isFinal = true;
208   }
209 
210   /**
211    * A factory method for new FSMs like this one, given a Rule object.
212    */
213   protected FSM spawn(Rule r) {
214     return new FSM(r);
215   }
216 
217   /**
218    * A factory method for new FSMs like this one, given a ComplexPatternElement
219    * object.
220    */
221   protected FSM spawn(ComplexPatternElement currentPattern) {
222       return new FSM(currentPattern);
223   }
224 
225   /**
226     * Gets the initial state of this FSM
227     @return an object of type gate.fsm.State representing the initial state.
228     */
229   public State getInitialState() {
230     return initialState;
231   // getInitialState
232 
233   /**
234     * Receives a state to start from and a complex pattern element.
235     * Parses the complex pattern element and creates all the necessary states
236     * and transitions for accepting annotations described by the given PE.
237     @param startState the state to start from
238     @param cpe the pattern to be recognized
239     @param labels the bindings name for all the annotation accepted along
240     * the way. This is actually a list of Strings. It is necessary to use
241     * a list because of the recursive definition of ComplexPatternElement.
242     @return the final state reached after accepting a sequence of annotations
243     * as described in the pattern
244     */
245   private State convertComplexPE(State startState,
246           ComplexPatternElement cpe, LinkedList<String> labels){
247 
248     State endState = generateStates(startState, cpe, labels);
249 
250     // now take care of the kleene operator
251     KleeneOperator kleeneOp = cpe.getKleeneOp();
252     KleeneOperator.Type type = kleeneOp.getType();
253     if (type == OPTIONAL) {
254       //allow to skip everything via a null transition
255       startState.addTransition(new Transition(null,endState));
256     }
257     else if (type == PLUS) {
258       // allow to return to startState
259       endState.addTransition(new Transition(null,startState));
260     }
261     else if (type == STAR) {
262       // allow to skip everything via a null transition
263       startState.addTransition(new Transition(null,endState));
264 
265       // allow to return to startState
266       endState.addTransition(new Transition(null,startState));
267     }
268     else if (type == RANGE) {
269       Integer min = kleeneOp.getMin();
270       Integer max = kleeneOp.getMax();
271 
272       // keep a list of the start states for each possible optional sets so can make
273       // direct transitions from them to the final end state
274       List<State> startStateList = new ArrayList<State>();
275 
276       if (min == null || min == 0) {
277         //if min is empty or 0, allow to skip everything via a null transition
278         startStateList.add(startState);
279       }
280       else if (min > 1) {
281         // add min-1 copies of the set of states for the CPE.  It's -1 because
282         // one set was already added by the first generateStates call
283         int numCopies = min - 1;
284         for(int i = 1; i <= numCopies; i++) {
285           // the end state of the previous set always moves up to be the
286           // start state of the next set.
287           startState = endState;
288           endState = generateStates(startState, cpe, labels);
289         }
290       }
291 
292       if (max == null) {
293         // if there is no defined max, allow to return to startState any
294         // number of times.  Start state may be the original start or, if
295         // min > 1, then it's the start of the last set of states added.
296         // Example: A range with min 3 and max = unbounded will look like
297         // this:
298         //                                  v------|
299         // start1...end1->start2...end2->start3...end3->...
300         //
301         endState.addTransition(new Transition(null,startState));
302       }
303       else if (max > min) {
304         // there are some optional state sets.  Make a copy of the state
305         // set for each.
306         int numCopies = max-min;
307 
308         //if min == 0 then reduce numCopies by one since we already added
309         //one set of states that are optional
310         if (min == 0)
311           numCopies--;
312 
313         for(int i = 1; i <= numCopies; i++) {
314           startState = endState;
315           startStateList.add(startState);
316           endState = generateStates(startState, cpe, labels);
317         }
318       }
319 
320       //each of the optional stages can transition directly to the final end
321       for(State state : startStateList) {
322         state.addTransition(new Transition(null,endState));
323       }
324 
325     //end if type == RANGE
326 
327     return endState;
328   // convertComplexPE
329 
330   /**
331    * Receives a state to start from and a complex pattern element.
332    * Parses the complex pattern element and creates all the necessary states
333    * and transitions for traversing the annotations described by the given PE
334    * exactly once.  Does not add any transitions for kleene operators.
335    @param startState the state to start from
336    @param cpe the pattern to be recognized
337    @param labels the bindings name for all the annotation accepted along
338    * the way. This is actually a list of Strings. It is necessary to use
339    * a list because of the recursive definition of ComplexPatternElement.
340    @return the final state reached after accepting a sequence of annotations
341    * as described in the pattern
342    */
343   private State generateStates(State startState, ComplexPatternElement cpe,
344           LinkedList<String> labels) {
345     //create a copy
346     LinkedList<String> newBindings = (LinkedList<String>)labels.clone();
347     String localLabel = cpe.getBindingName ();
348 
349     if(localLabel != null)newBindings.add(localLabel);
350 
351     ConstraintGroup constraintGroup = cpe.getConstraintGroup();
352     PatternElement[][] constraints =
353                        constraintGroup.getPatternElementDisjunction();
354 
355     // the rectangular array constraints is a disjunction of sequences of
356     // constraints = [[PE]:[PE]...[PE] ||
357     //                [PE]:[PE]...[PE] ||
358     //                ...
359     //                [PE]:[PE]...[PE] ]
360 
361     //The current and the next state for the current ROW.
362     State currentRowState, nextRowState, endState = new State();
363     PatternElement currentPattern;
364 
365     for(int i = 0; i < constraints.length; i++) {
366       // for each row we have to create a sequence of states that will accept
367       // the sequence of annotations described by the restrictions on that row.
368       // The final state of such a sequence will always be a finale state which
369       // will have associated the right hand side of the rule used for this
370       // constructor.
371 
372       //For each row we will start from the initial state.
373       currentRowState = startState;
374       for(int j=0; j < (constraints[i]).length; j++) {
375 
376         //parse the sequence of constraints:
377         //For each basic pattern element add a new state and link it to the
378         //currentRowState.
379         //The case of kleene operators has to be considered!
380         State insulator = new State();
381         currentRowState.addTransition(new Transition(null,insulator));
382         currentRowState = insulator;
383         currentPattern = constraints[i][j];
384         if(currentPattern instanceof BasicPatternElement) {
385 
386           //the easy case
387           nextRowState = new State();
388 
389           //added by Karter start -> JapeDebugger
390           newBindings.add(currentBasicBinding( (BasicPatternElement)
391                                               currentPattern));
392           //added by Karter end
393 
394 
395           currentRowState.addTransition(
396             new Transition((BasicPatternElement)currentPattern,
397                             nextRowState,newBindings));
398           currentRowState = nextRowState;
399         else if(currentPattern instanceof ComplexPatternElement) {
400 
401           // the current pattern is a complex pattern element
402           // ..it will probaly be converted into a sequence of states itself.
403           currentRowState =  convertComplexPE(
404                               currentRowState,
405                               (ComplexPatternElement)currentPattern,
406                               newBindings);
407         else {
408 
409           //we got an unknown kind of pattern
410           throw new RuntimeException("Strange looking pattern:"+currentPattern);
411         }
412 
413       // for j
414         // link the end of the current row to the general end state using
415         // an empty transition.
416         currentRowState.addTransition(new Transition(null,endState));
417     // for i
418     return endState;
419   }
420 
421   /**
422     * Converts this FSM from a non-deterministic to a deterministic one by
423     * eliminating all the unrestricted transitions.
424     */
425   public void eliminateVoidTransitions() {
426 
427     dStates.clear()//kalina: replaced from new HashSet()
428     LinkedList<AbstractSet<State>> unmarkedDStates = new LinkedList<AbstractSet<State>>();
429     AbstractSet<State> currentDState = new HashSet<State>();
430     //kalina: prefer clear coz faster than init()
431     newStates.clear();
432 
433     currentDState.add(initialState);
434     currentDState = lambdaClosure(currentDState);
435     dStates.add(currentDState);
436     unmarkedDStates.add(currentDState);
437 
438     // create a new state that will take the place the set of states
439     // in currentDState
440     initialState = new State();
441     newStates.put(currentDState, initialState);
442 
443     // find out if the new state is a final one
444     Iterator innerStatesIter = currentDState.iterator();
445     RightHandSide action = null;
446 
447     while(innerStatesIter.hasNext()){
448       State currentInnerState = (State)innerStatesIter.next();
449       if(currentInnerState.isFinal()){
450         action = (RightHandSide)currentInnerState.getAction();
451         initialState.setAction(action);
452         initialState.setFileIndex(currentInnerState.getFileIndex());
453         initialState.setPriority(currentInnerState.getPriority());
454         break;
455       }
456     }
457 
458     while(!unmarkedDStates.isEmpty()) {
459       currentDState = unmarkedDStates.removeFirst();
460       Iterator insideStatesIter = currentDState.iterator();
461 
462       while(insideStatesIter.hasNext()) {
463         State innerState = (State)insideStatesIter.next();
464         Iterator transIter = innerState.getTransitions().iterator();
465 
466         while(transIter.hasNext()) {
467           Transition currentTrans = (Transition)transIter.next();
468 
469           if(currentTrans.getConstraints() !=null) {
470             State target = currentTrans.getTarget();
471             AbstractSet<State> newDState = new HashSet<State>();
472             newDState.add(target);
473             newDState = lambdaClosure(newDState);
474 
475             if(!dStates.contains(newDState)) {
476               dStates.add(newDState);
477               unmarkedDStates.add(newDState);
478               State newState = new State();
479               newStates.put(newDState, newState);
480 
481               //find out if the new state is a final one
482               innerStatesIter = newDState.iterator();
483               while(innerStatesIter.hasNext()) {
484                 State currentInnerState = (State)innerStatesIter.next();
485 
486                 if(currentInnerState.isFinal()) {
487                   newState.setAction(
488                           (RightHandSide)currentInnerState.getAction());
489                   newState.setFileIndex(currentInnerState.getFileIndex());
490                   newState.setPriority(currentInnerState.getPriority());
491                   break;
492                 }
493               }
494             }// if(!dStates.contains(newDState))
495 
496             State currentState = (State)newStates.get(currentDState);
497             State newState = (State)newStates.get(newDState);
498             currentState.addTransition(new Transition(
499                                         currentTrans.getConstraints(),
500                                         newState,
501                                         currentTrans.getBindings()));
502           }// if(currentTrans.getConstraints() !=null)
503 
504         }// while(transIter.hasNext())
505 
506       }// while(insideStatesIter.hasNext())
507 
508     }// while(!unmarkedDstates.isEmpty())
509 
510     /*
511     //find final states
512     Iterator allDStatesIter = dStates.iterator();
513     while(allDStatesIter.hasNext()){
514       currentDState = (AbstractSet) allDStatesIter.next();
515       Iterator innerStatesIter = currentDState.iterator();
516       while(innerStatesIter.hasNext()){
517         State currentInnerState = (State) innerStatesIter.next();
518         if(currentInnerState.isFinal()){
519           State newState = (State)newStates.get(currentDState);
520 
521           newState.setAction(currentInnerState.getAction());
522           break;
523         }
524       }
525 
526     }
527     */
528     allStates = newStates.values();
529   }//eliminateVoidTransitions
530 
531   /*
532     * Computes the lambda-closure (aka epsilon closure) of the given set of
533     * states, that is the set of states that are accessible from any of the
534     * states in the given set using only unrestricted transitions.
535     * @return a set containing all the states accessible from this state via
536     * transitions that bear no restrictions.
537     */
538   private AbstractSet<State> lambdaClosure(AbstractSet<State> s) {
539     // the stack/queue used by the algorithm
540     LinkedList<State> list = new LinkedList<State>(s);
541 
542     // the set to be returned
543     AbstractSet<State> lambdaClosure = new HashSet<State>(s);
544     State top;
545     Iterator transIter;
546     Transition currentTransition;
547     State currentState;
548     while(!list.isEmpty()){
549       top = (State)list.removeFirst();
550       transIter = top.getTransitions().iterator();
551 
552       while(transIter.hasNext()){
553         currentTransition = (Transition)transIter.next();
554 
555         if(currentTransition.getConstraints() == null){
556           currentState = currentTransition.getTarget();
557           if(!lambdaClosure.contains(currentState)){
558             lambdaClosure.add(currentState);
559             list.addFirst(currentState);
560           }// if(!lambdaClosure.contains(currentState))
561 
562         }// if(currentTransition.getConstraints() == null)
563 
564       }
565     }
566     return lambdaClosure;
567   // lambdaClosure
568 
569 
570   /**
571    * Two members used by forEachState().
572    */
573   protected State currentState;
574   protected Transition currentTransition;
575 
576   /**
577    * Iterates over all the states in this FSM, setting currentState and
578    * currentTransition, then calling the given Runnable callback.
579    */
580   protected void forEachState (java.lang.Runnable r) {
581     Set<State> stackToProcess = new HashSet<State>();
582     Set<State> processed = new HashSet<State>();
583 
584     stackToProcess.add(initialState);
585     while (!stackToProcess.isEmpty()) {
586       currentState = (StatestackToProcess.iterator().next();
587       stackToProcess.remove(currentState);
588       processed.add(currentState);
589 
590       for(Transition t : currentState.getTransitions()) {
591         currentTransition = t;
592         State target = currentTransition.getTarget();
593         if (processed.contains(target|| stackToProcess.contains(target)) continue;
594         stackToProcess.add(target);
595 
596         r.run();
597       }
598     }
599   }
600 
601   /**
602    @return a Map whose keys contain the states of this FSM, and whose values
603    *         contain their corresponding transitions. This method actually walks
604    *         the FSM, so it may be called before the FSM is finalized with
605    *         compactTransitions().
606    */
607   public Map<State,SimpleArraySet<Transition>> getAllStates() {
608     /*
609      * This method can't use the allStates data member, since it's sometimes
610      * called before allStates is populated.
611      */
612 
613     Map<State,SimpleArraySet<Transition>> statesToReturn = new HashMap<State,SimpleArraySet<Transition>>();
614     Set<State> stackToProcess = new HashSet<State>();
615     Set<State> processed = new HashSet<State>();
616 
617     stackToProcess.add(initialState);
618     while (!stackToProcess.isEmpty()) {
619       currentState = (StatestackToProcess.iterator().next();
620       stackToProcess.remove(currentState);
621       processed.add(currentState);
622 
623 
624       SimpleArraySet<Transition> transitions = currentState.getTransitions();
625       statesToReturn.put(currentState, transitions);
626       for (Iterator<Transition> iter = transitions.iterator(); iter.hasNext();) {
627         currentTransition = iter.next();
628         State target = currentTransition.getTarget();
629         if (processed.contains(target|| stackToProcess.contains(target)) continue;
630         stackToProcess.add(target);
631       }
632     }
633 
634     return statesToReturn;
635   }
636 
637   /**
638    * Returns a representation of this FSM in the GraphViz graph-visualization
639    * language. We use the "digraph" (directed graph) format. Nodes are labeled
640    * by their numerical indexes. A node's shape is a diamond if it's the initial
641    * state, and round otherwise. A node is green if it's an initial state, red
642    * if it's a final state, and black otherwise. Final states are also marked
643    * with a double-line outline.
644    *
645    @see <a href="http://www.graphviz.org/">the GraphViz web site </a> for
646    *      software to translate the output of this method into pretty pictures.
647    *
648    @param includeConstraints
649    *            whether to include a stringified representation of each
650    *            transition object as part of its label. The default is false.
651    @return
652    */
653   public String asGraphViz(boolean includeConstraints) {
654     StringBuffer result = new StringBuffer();
655 
656     result.append("digraph G {\n");
657 
658     for(State currentState : getAllStates().keySet()) {
659       int stateIndex = currentState.getIndex();
660       Map<String,String> opts = new HashMap<String,String>();
661       opts.put("shape", currentState == initialState ? "diamond" "circle");
662       opts.put("color", currentState == initialState ? "green" : currentState.isFinal() "red" "black");
663       if (currentState.isFinal()) {
664         opts.put("peripheries""2");
665         if (DEBUG) {
666           opts.put("shape""rectangle");
667           opts.put("label""" + stateIndex + "-" + currentState.getAction());
668         }
669       }
670 
671       result.append("  " + stateIndex + " [" + encodeForGraphViz(opts"]" ";\n");
672 
673       for(Transition t : currentState.getTransitions()) {
674         String extraText = includeConstraints
675         " [label=\"" + t.toString(false"\"]"
676                 "";
677         result.append("  " + stateIndex + " -> " + t.getTarget().getIndex()
678                 + extraText + ";\n");
679       }
680     }
681 
682     result.append("}\n");
683 
684     return result.toString();
685   }
686 
687   /**
688    * Given a Map, encodes its keys and values as strings suitable for use as a
689    * GraphViz label. Embedded "\r\n" sequences are replaced by "\\l" to create
690    * line feeds, and embedded backslashes are escaped. The returned String takes
691    * the form "key1=value1, key2=value2, ...".
692    */
693   String encodeForGraphViz (Map<String,String> m) {
694     ArrayList<String> temp = new ArrayList<String>(m.size());
695     for(String k : m.keySet()) {
696       String v = m.get(k);
697       v = v.replaceAll("\r\n""\\\\l");
698       v = v.replaceAll("\"""\\\\\"");
699       temp.add(k + "=\"" + v + "\"");
700     }
701 
702     StringBuffer toReturn = new StringBuffer();
703     for (int i = 0; i < temp.size(); i++)
704     {
705       if (i != 0toReturn.append(",");
706       toReturn.append(temp.get(i));
707     }
708     return toReturn.toString();
709   }
710 
711 
712   /**
713     * Returns a GML (Graph Modelling Language) representation of the transition
714     * graph of this FSM.
715     */
716   public String getGML() {
717 
718     String res = "graph[ \ndirected 1\n";
719 
720     StringBuffer nodes = new StringBuffer(gate.Gate.STRINGBUFFER_SIZE),
721                  edges = new StringBuffer(gate.Gate.STRINGBUFFER_SIZE);
722 
723     Iterator stateIter = allStates.iterator();
724     while (stateIter.hasNext()){
725       State currentState = (State)stateIter.next();
726       int stateIndex = currentState.getIndex();
727         nodes.append("node[ id ");
728         nodes.append(stateIndex);
729         nodes.append(" label \"");
730         nodes.append(stateIndex);
731 
732              if(currentState.isFinal()){
733               nodes.append(",F\\n" + currentState.getAction().shortDesc());
734              }
735              nodes.append("\"  ]\n");
736       edges.append(currentState.getEdgesGML());
737     }
738     res += nodes.toString() + edges.toString() "]\n";
739     return res;
740   // getGML
741 
742   /**
743     * Returns a textual description of this FSM.
744     */
745   public String toString(){
746     String res = "Starting from:" + initialState.getIndex() "\n";
747     Iterator stateIter = allStates.iterator();
748     while (stateIter.hasNext()){
749       res += "\n\n" + stateIter.next();
750     }
751     return res;
752   // toString
753 
754   /**
755     * The initial state of this FSM.
756     */
757   private State initialState;
758 
759  /**
760    * The final state of this FSM (usually only valid during construction).
761    */
762   protected State finalState;
763 
764   /**
765     * The set of states for this FSM
766     */
767   private transient Collection allStates =  new HashSet();
768 
769   //kalina: added this member here to minimise HashMap allocation
770   private transient Map<AbstractSet,State> newStates = new HashMap<AbstractSet,State>();
771   private transient Set<AbstractSet> dStates = new HashSet<AbstractSet>();
772 
773 
774   //added by Karter start
775   private String currentBinding(ComplexPatternElement cpe, int indent) {
776     if (indent == 0)
777       bpeId = 0;
778     String ind = "";
779     for (int i = 0; i < indent; i++) {
780       ind += "   ";
781     }
782     String binds = ind + "(\n";
783     PatternElement[][] pe = cpe.getConstraintGroup().
784         getPatternElementDisjunction();
785     for (int i = 0; i < pe.length; i++) {
786       PatternElement[] patternElements = pe[i];
787       for (int j = 0; j < patternElements.length; j++) {
788         PatternElement patternElement = patternElements[j];
789         if (patternElement instanceof ComplexPatternElement) {
790           ComplexPatternElement complexPatternElement = (ComplexPatternElement)
791               patternElement;
792           binds += currentBinding(complexPatternElement, indent + 1);
793 
794         }
795         else {
796           binds += ind + "   ";
797           binds += currentBasicBinding((BasicPatternElementpatternElement);
798           binds += "\n";
799         }
800       }
801       binds += ind + "   |\n";
802     }
803     binds = binds.substring(0, binds.length() 5);
804     binds += ")" + cpe.getKleeneOp().toString() "\n";
805     if (indent == 0)
806       bpeId = 0;
807     return binds;
808   }
809 
810   private String currentBasicBinding(BasicPatternElement bpe) {
811     StringBuilder sb = new StringBuilder("{");
812     Constraint[] cons = bpe.getConstraints();
813     for (int k = 0; k < cons.length; k++) {
814       sb.append(cons[k].getDisplayString(""));
815       if (k < cons.length - 1)
816         sb.append(",");
817     }
818     sb.append("}").append(" *").append(bpeId++).append("*");
819     return sb.toString();
820   }
821 
822   private String currentLHSBinding(LeftHandSide lhs) {
823     String binds = "(\n";
824     PatternElement[][] pe = lhs.getConstraintGroup().
825         getPatternElementDisjunction();
826     for (int i = 0; i < pe.length; i++) {
827       PatternElement[] patternElements = pe[i];
828       for (int j = 0; j < patternElements.length; j++) {
829         PatternElement patternElement = patternElements[j];
830         if (patternElement instanceof ComplexPatternElement) {
831           ComplexPatternElement complexPatternElement = (ComplexPatternElement)
832               patternElement;
833           binds += currentBinding(complexPatternElement, 1);
834 
835         }
836         else {
837           binds += "   ";
838           binds += currentBasicBinding((BasicPatternElementpatternElement);
839           binds += "\n";
840         }
841       }
842       binds += "   |\n";
843     }
844     binds = binds.substring(0, binds.length() 5);
845     binds += ")\n";
846     bpeId = 0;
847     return binds;
848   }
849 
850   int bpeId = 0;
851   public HashMap<String,String> ruleHash = new HashMap<String,String>();
852   //added by Karter end
853 // FSM