1   /*
2    *  FSM.java
3    *
4    *  Copyright (c) 1998-2001, The University of Sheffield.
5    *
6    *  This file is part of GATE (see http://gate.ac.uk/), and is free
7    *  software, licenced under the GNU Library General Public License,
8    *  Version 2, June 1991 (in the distribution as file licence.html,
9    *  and also available at http://gate.ac.uk/gate/licence.html).
10   *
11   *  Valentin Tablan, 29/Mar/2000
12   *
13   *  $Id: FSM.java,v 1.21 2001/11/01 15:49:08 valyt Exp $
14   */
15  
16  package gate.fsm;
17  
18  import java.util.*;
19  
20  import gate.jape.*;
21  import gate.util.*;
22  
23  /**
24    * This class implements a standard Finite State Machine.
25    * It is used for both deterministic and non-deterministic machines.
26    */
27  public class FSM implements JapeConstants {
28  
29    /** Debug flag */
30    private static final boolean DEBUG = false;
31  
32    /**
33      * Builds a standalone FSM starting from a single phase transducer.
34      * @param spt the single phase transducer to be used for building this FSM.
35      */
36    public FSM(SinglePhaseTransducer spt){
37      initialState = new State();
38      transducerName = spt.getName();
39      Iterator rulesEnum = spt.getRules().iterator();
40      Rule currentRule;
41  
42      while(rulesEnum.hasNext()){
43        currentRule = (Rule) rulesEnum.next();
44        FSM ruleFSM = new FSM(currentRule);
45        initialState.addTransition(new Transition(null,
46                                                  ruleFSM.getInitialState()));
47      }
48  
49      eliminateVoidTransitions();
50  //Out.prln("Transducer " + spt.getName() + " converted to " + allStates.size() + " states");
51  
52    }
53  
54    /**
55      * Builds a FSM starting from a rule. This FSM is actually a part of a larger
56      * one (usually the one that is built based on the single phase transducer
57      * that contains the rule).
58      * built by this constructor.
59      * @param rule the rule to be used for the building process.
60      */
61    public FSM(Rule rule) {
62  
63      initialState = new State();
64      LeftHandSide lhs = rule.getLHS();
65      PatternElement[][] constraints =
66                         lhs.getConstraintGroup().getPatternElementDisjunction();
67      // the rectangular array constraints is a disjunction of sequences of
68      // constraints = [[PE]:[PE]...[PE] ||
69      //                [PE]:[PE]...[PE] ||
70      //                ...
71      //                [PE]:[PE]...[PE] ]
72  
73      //The current and the next state for the current ROW.
74      State currentRowState, nextRowState;
75      State finalState = new State();
76      PatternElement currentPattern;
77  
78      for(int i = 0; i < constraints.length; i++){
79        // for each row we have to create a sequence of states that will accept
80        // the sequence of annotations described by the restrictions on that row.
81        // The final state of such a sequence will always be a final state which
82        // will have associated the right hand side of the rule used for this
83        // constructor.
84  
85        // For each row we will start from the initial state.
86        currentRowState = initialState;
87        for(int j=0; j < constraints[i].length; j++) {
88  
89          // parse the sequence of constraints:
90          // For each basic pattern element add a new state and link it to the
91          // currentRowState.
92          // The case of kleene operators has to be considered!
93          currentPattern = constraints[i][j];
94          State insulator = new State();
95          currentRowState.addTransition(new Transition(null,insulator));
96          currentRowState = insulator;
97          if(currentPattern instanceof BasicPatternElement) {
98            //the easy case
99            nextRowState = new State();
100           currentRowState.addTransition(
101             new Transition((BasicPatternElement)currentPattern, nextRowState));
102           currentRowState = nextRowState;
103         } else if(currentPattern instanceof ComplexPatternElement) {
104 
105           // the current pattern is a complex pattern element
106           // ..it will probaly be converted into a sequence of states itself.
107           currentRowState =  convertComplexPE(
108                               currentRowState,
109                               (ComplexPatternElement)currentPattern,
110                               new LinkedList());
111         } else {
112           // we got an unknown kind of pattern
113           throw new RuntimeException("Strange looking pattern: " +
114                                      currentPattern);
115         }
116 
117       } // for j
118 
119       //link the end of the current row to the final state using
120       //an empty transition.
121       currentRowState.addTransition(new Transition(null,finalState));
122       finalState.setAction(rule.getRHS());
123       finalState.setFileIndex(rule.getPosition());
124       finalState.setPriority(rule.getPriority());
125     } // for i
126   }
127 
128   /**
129     * Gets the initial state of this FSM
130     * @return an object of type gate.fsm.State representing the initial state.
131     */
132   public State getInitialState() {
133     return initialState;
134   } // getInitialState
135 
136   /**
137     * Receives a state to start from and a complex pattern element.
138     * Parses the complex pattern element and creates all the necessary states
139     * and transitions for accepting annotations described by the given PE.
140     * @param state the state to start from
141     * @param cpe the pattern to be recognized
142     * @param label the bindings name for all the annotation accepted along
143     * the way this is actually a listy of Strings. It is necessary to use
144     * a list becuase of the reccursive definition of ComplexPatternElement.
145     * @return the final state reached after accepting a sequence of annotations
146     * as described in the pattern
147     */
148   private State convertComplexPE(State startState,
149                                 ComplexPatternElement cpe, LinkedList labels){
150     //create a copy
151     LinkedList newBindings = (LinkedList)labels.clone();
152     String localLabel = cpe.getBindingName ();
153 
154     if(localLabel != null)newBindings.add(localLabel);
155 
156     PatternElement[][] constraints =
157                        cpe.getConstraintGroup().getPatternElementDisjunction();
158 
159     // the rectangular array constraints is a disjunction of sequences of
160     // constraints = [[PE]:[PE]...[PE] ||
161     //                [PE]:[PE]...[PE] ||
162     //                ...
163     //                [PE]:[PE]...[PE] ]
164 
165     //The current and the next state for the current ROW.
166     State currentRowState, nextRowState, endState = new State();
167     PatternElement currentPattern;
168 
169     for(int i = 0; i < constraints.length; i++) {
170       // for each row we have to create a sequence of states that will accept
171       // the sequence of annotations described by the restrictions on that row.
172       // The final state of such a sequence will always be a finale state which
173       // will have associated the right hand side of the rule used for this
174       // constructor.
175 
176       //For each row we will start from the initial state.
177       currentRowState = startState;
178       for(int j=0; j < (constraints[i]).length; j++) {
179 
180         //parse the sequence of constraints:
181         //For each basic pattern element add a new state and link it to the
182         //currentRowState.
183         //The case of kleene operators has to be considered!
184         State insulator = new State();
185         currentRowState.addTransition(new Transition(null,insulator));
186         currentRowState = insulator;
187         currentPattern = constraints[i][j];
188         if(currentPattern instanceof BasicPatternElement) {
189 
190           //the easy case
191           nextRowState = new State();
192           currentRowState.addTransition(
193             new Transition((BasicPatternElement)currentPattern,
194                             nextRowState,newBindings));
195           currentRowState = nextRowState;
196         } else if(currentPattern instanceof ComplexPatternElement) {
197 
198           // the current pattern is a complex pattern element
199           // ..it will probaly be converted into a sequence of states itself.
200           currentRowState =  convertComplexPE(
201                               currentRowState,
202                               (ComplexPatternElement)currentPattern,
203                               newBindings);
204         } else {
205 
206           //we got an unknown kind of pattern
207           throw new RuntimeException("Strange looking pattern:"+currentPattern);
208         }
209 
210       } // for j
211         // link the end of the current row to the general end state using
212         // an empty transition.
213         currentRowState.addTransition(new Transition(null,endState));
214     } // for i
215 
216     // let's take care of the kleene operator
217     int kleeneOp = cpe.getKleeneOp();
218     switch (kleeneOp){
219       case NO_KLEENE_OP:{
220         break;
221       }
222       case KLEENE_QUERY:{
223         //allow to skip everything via a null transition
224         startState.addTransition(new Transition(null,endState));
225         break;
226       }
227       case KLEENE_PLUS:{
228 
229         // allow to return to startState
230         endState.addTransition(new Transition(null,startState));
231         break;
232       }
233       case KLEENE_STAR:{
234 
235         // allow to skip everything via a null transition
236         startState.addTransition(new Transition(null,endState));
237 
238         // allow to return to startState
239         endState.addTransition(new Transition(null,startState));
240         break;
241       }
242       default:{
243         throw new RuntimeException("Unknown Kleene operator"+kleeneOp);
244       }
245     } // switch (cpe.getKleeneOp())
246     return endState;
247   } // convertComplexPE
248 
249   /**
250     * Converts this FSM from a non-deterministic to a deterministic one by
251     * eliminating all the unrestricted transitions.
252     */
253   public void eliminateVoidTransitions() {
254 
255     dStates.clear(); //kalina: replaced from new HashSet()
256     LinkedList unmarkedDStates = new LinkedList();
257     AbstractSet currentDState = new HashSet();
258     //kalina: prefer clear coz faster than init()
259     newStates.clear();
260 
261     currentDState.add(initialState);
262     currentDState = lambdaClosure(currentDState);
263     dStates.add(currentDState);
264     unmarkedDStates.add(currentDState);
265 
266     // create a new state that will take the place the set of states
267     // in currentDState
268     initialState = new State();
269     newStates.put(currentDState, initialState);
270 
271     // find out if the new state is a final one
272     Iterator innerStatesIter = currentDState.iterator();
273     RightHandSide action = null;
274 
275     while(innerStatesIter.hasNext()){
276       State currentInnerState = (State)innerStatesIter.next();
277       if(currentInnerState.isFinal()){
278         action = (RightHandSide)currentInnerState.getAction();
279         initialState.setAction(action);
280         initialState.setFileIndex(currentInnerState.getFileIndex());
281         initialState.setPriority(currentInnerState.getPriority());
282         break;
283       }
284     }
285 
286     while(!unmarkedDStates.isEmpty()) {
287       currentDState = (AbstractSet)unmarkedDStates.removeFirst();
288       Iterator insideStatesIter = currentDState.iterator();
289 
290       while(insideStatesIter.hasNext()) {
291         State innerState = (State)insideStatesIter.next();
292         Iterator transIter = innerState.getTransitions().iterator();
293 
294         while(transIter.hasNext()) {
295           Transition currentTrans = (Transition)transIter.next();
296 
297           if(currentTrans.getConstraints() !=null) {
298             State target = currentTrans.getTarget();
299             AbstractSet newDState = new HashSet();
300             newDState.add(target);
301             newDState = lambdaClosure(newDState);
302 
303             if(!dStates.contains(newDState)) {
304               dStates.add(newDState);
305               unmarkedDStates.add(newDState);
306               State newState = new State();
307               newStates.put(newDState, newState);
308 
309               //find out if the new state is a final one
310               innerStatesIter = newDState.iterator();
311               while(innerStatesIter.hasNext()) {
312                 State currentInnerState = (State)innerStatesIter.next();
313 
314                 if(currentInnerState.isFinal()) {
315                   newState.setAction(
316                           (RightHandSide)currentInnerState.getAction());
317                   newState.setFileIndex(currentInnerState.getFileIndex());
318                   newState.setPriority(currentInnerState.getPriority());
319                   break;
320                 }
321               }
322             }// if(!dStates.contains(newDState))
323 
324             State currentState = (State)newStates.get(currentDState);
325             State newState = (State)newStates.get(newDState);
326             currentState.addTransition(new Transition(
327                                         currentTrans.getConstraints(),
328                                         newState,
329                                         currentTrans.getBindings()));
330           }// if(currentTrans.getConstraints() !=null)
331 
332         }// while(transIter.hasNext())
333 
334       }// while(insideStatesIter.hasNext())
335 
336     }// while(!unmarkedDstates.isEmpty())
337 
338     /*
339     //find final states
340     Iterator allDStatesIter = dStates.iterator();
341     while(allDStatesIter.hasNext()){
342       currentDState = (AbstractSet) allDStatesIter.next();
343       Iterator innerStatesIter = currentDState.iterator();
344       while(innerStatesIter.hasNext()){
345         State currentInnerState = (State) innerStatesIter.next();
346         if(currentInnerState.isFinal()){
347           State newState = (State)newStates.get(currentDState);
348 
349           newState.setAction(currentInnerState.getAction());
350           break;
351         }
352       }
353 
354     }
355     */
356     allStates = newStates.values();
357   }//eliminateVoidTransitions
358 
359   /*
360     * Computes the lambda-closure (aka epsilon closure) of the given set of
361     * states, that is the set of states that are accessible from any of the
362     * states in the given set using only unrestricted transitions.
363     * @return a set containing all the states accessible from this state via
364     * transitions that bear no restrictions.
365     */
366   private AbstractSet lambdaClosure(AbstractSet s) {
367     // the stack/queue used by the algorithm
368     LinkedList list = new LinkedList(s);
369 
370     // the set to be returned
371     AbstractSet lambdaClosure = new HashSet(s);
372     State top;
373     Iterator transIter;
374     Transition currentTransition;
375     State currentState;
376     while(!list.isEmpty()){
377       top = (State)list.removeFirst();
378       transIter = top.getTransitions().iterator();
379 
380       while(transIter.hasNext()){
381         currentTransition = (Transition)transIter.next();
382 
383         if(currentTransition.getConstraints() == null){
384           currentState = currentTransition.getTarget();
385           if(!lambdaClosure.contains(currentState)){
386             lambdaClosure.add(currentState);
387             list.addFirst(currentState);
388           }// if(!lambdaClosure.contains(currentState))
389 
390         }// if(currentTransition.getConstraints() == null)
391 
392       }
393     }
394     return lambdaClosure;
395   } // lambdaClosure
396 
397   /**
398     * Returns a GML (Graph Modelling Language) representation of the transition
399     * graph of this FSM.
400     */
401   public String getGML() {
402 
403     String res = "graph[ \ndirected 1\n";
404 ///    String nodes = "", edges = "";
405     StringBuffer nodes = new StringBuffer(gate.Gate.STRINGBUFFER_SIZE),
406                  edges = new StringBuffer(gate.Gate.STRINGBUFFER_SIZE);
407 
408     Iterator stateIter = allStates.iterator();
409     while (stateIter.hasNext()){
410       State currentState = (State)stateIter.next();
411       int stateIndex = currentState.getIndex();
412 /*      nodes += "node[ id " + stateIndex +
413                " label \"" + stateIndex;
414 */
415         nodes.append("node[ id ");
416         nodes.append(stateIndex);
417         nodes.append(" label \"");
418         nodes.append(stateIndex);
419 
420              if(currentState.isFinal()){
421 /*              nodes += ",F";
422               nodes += "\\n" + currentState.getAction().shortDesc();
423 */
424               nodes.append(",F\\n" + currentState.getAction().shortDesc());
425              }
426 ///             nodes +=  "\"  ]\n";
427              nodes.append("\"  ]\n");
428 ///      edges += currentState.getEdgesGML();
429       edges.append(currentState.getEdgesGML());
430     }
431     res += nodes.toString() + edges.toString() + "]\n";
432     return res;
433   } // getGML
434 
435   /**
436     * Returns a textual description of this FSM.
437     */
438   public String toString(){
439     String res = "Starting from:" + initialState.getIndex() + "\n";
440     Iterator stateIter = allStates.iterator();
441     while (stateIter.hasNext()){
442       res += "\n\n" + stateIter.next();
443     }
444     return res;
445   } // toString
446 
447   /**
448     * The initial state of this FSM.
449     */
450   private State initialState;
451 
452   /**
453     * The set of states for this FSM
454     */
455   private transient Collection allStates =  new HashSet();
456 
457   //kalina: added this member here to minimise HashMap allocation
458   private transient Map newStates = new HashMap();
459   private transient Set dStates = new HashSet();
460 
461   private String transducerName;
462 
463 } // FSM
464