/*
* FSM.java
*
* Copyright (c) 1998-2001, The University of Sheffield.
*
* This file is part of GATE (see http://gate.ac.uk/), and is free
* software, licenced under the GNU Library General Public License,
* Version 2, June 1991 (in the distribution as file licence.html,
* and also available at http://gate.ac.uk/gate/licence.html).
*
* Valentin Tablan, 29/Mar/2000
*
* Minor modifications made by Luc Plamondon, Universit� de Montr�al, 27/11/03:
* - Migrated original file from gate.fsm to
* ca.umontreal.iro.rali.gate.fsm package.
* - Changed processing of Kleene operators in convertComplexPE.
*
* $Id$
*/
package ca.umontreal.iro.rali.gate.fsm;
import java.util.*;
import ca.umontreal.iro.rali.gate.jape.*;
import gate.util.*;
/**
* This class implements a standard Finite State Machine.
* It is used for both deterministic and non-deterministic machines.
*/
public class FSM implements JapeConstants {
/** Debug flag */
private static final boolean DEBUG = false;
/**
* Builds a standalone FSM starting from a single phase transducer.
* @param spt the single phase transducer to be used for building this FSM.
*/
public FSM(SinglePhaseTransducer spt){
initialState = new State();
transducerName = spt.getName();
Iterator rulesEnum = spt.getRules().iterator();
Rule currentRule;
while(rulesEnum.hasNext()){
currentRule = (Rule) rulesEnum.next();
FSM ruleFSM = new FSM(currentRule);
initialState.addTransition(new Transition(null,
ruleFSM.getInitialState()));
}
eliminateVoidTransitions();
//Out.prln("Transducer " + spt.getName() + " converted to " + allStates.size() + " states");
}
/**
* Builds a FSM starting from a rule. This FSM is actually a part of a larger
* one (usually the one that is built based on the single phase transducer
* that contains the rule).
* built by this constructor.
* @param rule the rule to be used for the building process.
*/
public FSM(Rule rule) {
initialState = new State();
LeftHandSide lhs = rule.getLHS();
PatternElement[][] constraints =
lhs.getConstraintGroup().getPatternElementDisjunction();
// the rectangular array constraints is a disjunction of sequences of
// constraints = [[PE]:[PE]...[PE] ||
// [PE]:[PE]...[PE] ||
// ...
// [PE]:[PE]...[PE] ]
//The current and the next state for the current ROW.
State currentRowState, nextRowState;
State finalState = new State();
PatternElement currentPattern;
for(int i = 0; i < constraints.length; i++){
// for each row we have to create a sequence of states that will accept
// the sequence of annotations described by the restrictions on that row.
// The final state of such a sequence will always be a final state which
// will have associated the right hand side of the rule used for this
// constructor.
// For each row we will start from the initial state.
currentRowState = initialState;
for(int j=0; j < constraints[i].length; j++) {
// parse the sequence of constraints:
// For each basic pattern element add a new state and link it to the
// currentRowState.
// The case of kleene operators has to be considered!
currentPattern = constraints[i][j];
State insulator = new State();
currentRowState.addTransition(new Transition(null,insulator));
currentRowState = insulator;
if(currentPattern instanceof BasicPatternElement) {
//the easy case
nextRowState = new State();
currentRowState.addTransition(
new Transition((BasicPatternElement)currentPattern, nextRowState));
currentRowState = nextRowState;
} else if(currentPattern instanceof ComplexPatternElement) {
// the current pattern is a complex pattern element
// ..it will probaly be converted into a sequence of states itself.
currentRowState = convertComplexPE(
currentRowState,
(ComplexPatternElement)currentPattern,
new LinkedList());
} else {
// we got an unknown kind of pattern
throw new RuntimeException("Strange looking pattern: " +
currentPattern);
}
} // for j
//link the end of the current row to the final state using
//an empty transition.
currentRowState.addTransition(new Transition(null,finalState));
finalState.setAction(rule.getRHS());
finalState.setFileIndex(rule.getPosition());
finalState.setPriority(rule.getPriority());
} // for i
}
/**
* Gets the initial state of this FSM
* @return an object of type gate.fsm.State representing the initial state.
*/
public State getInitialState() {
return initialState;
} // getInitialState
/**
* Receives a state to start from and a complex pattern element.
* Parses the complex pattern element and creates all the necessary states
* and transitions for accepting annotations described by the given PE.
* @param state the state to start from
* @param cpe the pattern to be recognized
* @param label the bindings name for all the annotation accepted along
* the way this is actually a listy of Strings. It is necessary to use
* a list becuase of the reccursive definition of ComplexPatternElement.
* @return the final state reached after accepting a sequence of annotations
* as described in the pattern
*/
private State convertComplexPE(State startState,
ComplexPatternElement cpe, LinkedList labels){
//create a copy
LinkedList newBindings = (LinkedList)labels.clone();
String localLabel = cpe.getBindingName ();
if(localLabel != null)newBindings.add(localLabel);
PatternElement[][] constraints =
cpe.getConstraintGroup().getPatternElementDisjunction();
// the rectangular array constraints is a disjunction of sequences of
// constraints = [[PE]:[PE]...[PE] ||
// [PE]:[PE]...[PE] ||
// ...
// [PE]:[PE]...[PE] ]
//The current and the next state for the current ROW.
State currentRowState, nextRowState, endState = new State();
PatternElement currentPattern;
// For the "+" and "*" Kleene operator to be greedy, the loop transition
// must appear before the other transitions of the current state.
// A more complete processing of the Kleene operators will be done at
// the end.
int kleeneOp = cpe.getKleeneOp();
switch (kleeneOp){
case KLEENE_PLUS:{
// allow to return to startState
endState.addTransition(new Transition(null,startState));
break;
}
case KLEENE_STAR:{
// allow to return to startState
endState.addTransition(new Transition(null,startState));
break;
}
} // switch (cpe.getKleeneOp())
for(int i = 0; i < constraints.length; i++) {
// for each row we have to create a sequence of states that will accept
// the sequence of annotations described by the restrictions on that row.
// The final state of such a sequence will always be a finale state which
// will have associated the right hand side of the rule used for this
// constructor.
//For each row we will start from the initial state.
currentRowState = startState;
for(int j=0; j < (constraints[i]).length; j++) {
//parse the sequence of constraints:
//For each basic pattern element add a new state and link it to the
//currentRowState.
//The case of kleene operators has to be considered!
State insulator = new State();
currentRowState.addTransition(new Transition(null,insulator));
currentRowState = insulator;
currentPattern = constraints[i][j];
if(currentPattern instanceof BasicPatternElement) {
//the easy case
nextRowState = new State();
currentRowState.addTransition(
new Transition((BasicPatternElement)currentPattern,
nextRowState,newBindings));
currentRowState = nextRowState;
} else if(currentPattern instanceof ComplexPatternElement) {
// the current pattern is a complex pattern element
// ..it will probaly be converted into a sequence of states itself.
currentRowState = convertComplexPE(
currentRowState,
(ComplexPatternElement)currentPattern,
newBindings);
} else {
//we got an unknown kind of pattern
throw new RuntimeException("Strange looking pattern:"+currentPattern);
}
} // for j
// link the end of the current row to the general end state using
// an empty transition.
currentRowState.addTransition(new Transition(null,endState));
} // for i
// let's take care of the kleene operator
switch (kleeneOp){
case NO_KLEENE_OP:{
break;
}
case KLEENE_QUERY:{
//allow to skip everything via a null transition
startState.addTransition(new Transition(null,endState));
break;
}
case KLEENE_PLUS:{
// allow to return to startState: this has been done above!
// endState.addTransition(new Transition(null,startState));
break;
}
case KLEENE_STAR:{
// allow to skip everything via a null transition
startState.addTransition(new Transition(null,endState));
// allow to return to startState: this has been done above!
// endState.addTransition(new Transition(null,startState));
break;
}
default:{
throw new RuntimeException("Unknown Kleene operator"+kleeneOp);
}
} // switch (cpe.getKleeneOp())
return endState;
} // convertComplexPE
/**
* Converts this FSM from a non-deterministic to a deterministic one by
* eliminating all the unrestricted transitions.
*/
public void eliminateVoidTransitions() {
dStates.clear(); //kalina: replaced from new HashSet()
LinkedList unmarkedDStates = new LinkedList();
AbstractSet currentDState = new HashSet();
//kalina: prefer clear coz faster than init()
newStates.clear();
currentDState.add(initialState);
currentDState = lambdaClosure(currentDState);
dStates.add(currentDState);
unmarkedDStates.add(currentDState);
// create a new state that will take the place the set of states
// in currentDState
initialState = new State();
newStates.put(currentDState, initialState);
// find out if the new state is a final one
Iterator innerStatesIter = currentDState.iterator();
RightHandSide action = null;
while(innerStatesIter.hasNext()){
State currentInnerState = (State)innerStatesIter.next();
if(currentInnerState.isFinal()){
action = (RightHandSide)currentInnerState.getAction();
initialState.setAction(action);
initialState.setFileIndex(currentInnerState.getFileIndex());
initialState.setPriority(currentInnerState.getPriority());
break;
}
}
while(!unmarkedDStates.isEmpty()) {
currentDState = (AbstractSet)unmarkedDStates.removeFirst();
Iterator insideStatesIter = currentDState.iterator();
while(insideStatesIter.hasNext()) {
State innerState = (State)insideStatesIter.next();
Iterator transIter = innerState.getTransitions().iterator();
while(transIter.hasNext()) {
Transition currentTrans = (Transition)transIter.next();
if(currentTrans.getConstraints() !=null) {
State target = currentTrans.getTarget();
AbstractSet newDState = new HashSet();
newDState.add(target);
newDState = lambdaClosure(newDState);
if(!dStates.contains(newDState)) {
dStates.add(newDState);
unmarkedDStates.add(newDState);
State newState = new State();
newStates.put(newDState, newState);
//find out if the new state is a final one
innerStatesIter = newDState.iterator();
while(innerStatesIter.hasNext()) {
State currentInnerState = (State)innerStatesIter.next();
if(currentInnerState.isFinal()) {
newState.setAction(
(RightHandSide)currentInnerState.getAction());
newState.setFileIndex(currentInnerState.getFileIndex());
newState.setPriority(currentInnerState.getPriority());
break;
}
}
}// if(!dStates.contains(newDState))
State currentState = (State)newStates.get(currentDState);
State newState = (State)newStates.get(newDState);
currentState.addTransition(new Transition(
currentTrans.getConstraints(),
newState,
currentTrans.getBindings()));
}// if(currentTrans.getConstraints() !=null)
}// while(transIter.hasNext())
}// while(insideStatesIter.hasNext())
}// while(!unmarkedDstates.isEmpty())
/*
//find final states
Iterator allDStatesIter = dStates.iterator();
while(allDStatesIter.hasNext()){
currentDState = (AbstractSet) allDStatesIter.next();
Iterator innerStatesIter = currentDState.iterator();
while(innerStatesIter.hasNext()){
State currentInnerState = (State) innerStatesIter.next();
if(currentInnerState.isFinal()){
State newState = (State)newStates.get(currentDState);
newState.setAction(currentInnerState.getAction());
break;
}
}
}
*/
allStates = newStates.values();
}//eliminateVoidTransitions
/*
* Computes the lambda-closure (aka epsilon closure) of the given set of
* states, that is the set of states that are accessible from any of the
* states in the given set using only unrestricted transitions.
* @return a set containing all the states accessible from this state via
* transitions that bear no restrictions.
*/
private AbstractSet lambdaClosure(AbstractSet s) {
// the stack/queue used by the algorithm
LinkedList list = new LinkedList(s);
// the set to be returned
AbstractSet lambdaClosure = new HashSet(s);
State top;
Iterator transIter;
Transition currentTransition;
State currentState;
while(!list.isEmpty()){
top = (State)list.removeFirst();
transIter = top.getTransitions().iterator();
while(transIter.hasNext()){
currentTransition = (Transition)transIter.next();
if(currentTransition.getConstraints() == null){
currentState = currentTransition.getTarget();
if(!lambdaClosure.contains(currentState)){
lambdaClosure.add(currentState);
list.addFirst(currentState);
}// if(!lambdaClosure.contains(currentState))
}// if(currentTransition.getConstraints() == null)
}
}
return lambdaClosure;
} // lambdaClosure
/**
* Returns a GML (Graph Modelling Language) representation of the transition
* graph of this FSM.
*/
public String getGML() {
String res = "graph[ \ndirected 1\n";
/// String nodes = "", edges = "";
StringBuffer nodes = new StringBuffer(gate.Gate.STRINGBUFFER_SIZE),
edges = new StringBuffer(gate.Gate.STRINGBUFFER_SIZE);
Iterator stateIter = allStates.iterator();
while (stateIter.hasNext()){
State currentState = (State)stateIter.next();
int stateIndex = currentState.getIndex();
/* nodes += "node[ id " + stateIndex +
" label \"" + stateIndex;
*/
nodes.append("node[ id ");
nodes.append(stateIndex);
nodes.append(" label \"");
nodes.append(stateIndex);
if(currentState.isFinal()){
/* nodes += ",F";
nodes += "\\n" + currentState.getAction().shortDesc();
*/
nodes.append(",F\\n" + currentState.getAction().shortDesc());
}
/// nodes += "\" ]\n";
nodes.append("\" ]\n");
/// edges += currentState.getEdgesGML();
edges.append(currentState.getEdgesGML());
}
res += nodes.toString() + edges.toString() + "]\n";
return res;
} // getGML
/**
* Returns a textual description of this FSM.
*/
public String toString(){
String res = "Starting from:" + initialState.getIndex() + "\n";
Iterator stateIter = allStates.iterator();
while (stateIter.hasNext()){
res += "\n\n" + stateIter.next();
}
return res;
} // toString
/**
* The initial state of this FSM.
*/
private State initialState;
/**
* The set of states for this FSM
*/
private transient Collection allStates = new HashSet();
//kalina: added this member here to minimise HashMap allocation
private transient Map newStates = new HashMap();
private transient Set dStates = new HashSet();
private String transducerName;
} // FSM