Log in Help
Print
Homereleasesgate-5.1-beta2-build3402-ALLpluginsObsoleteMontreal_Transducersrccaumontrealiroraligatejape 〉 SinglePhaseTransducer.java
 
/*
 *  SinglePhaseTransducer.java - transducer class
 *
 *  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).
 *
 *  Hamish Cunningham, 24/07/98
 *
 *  Modifications by Luc Plamondon, Universit� de Montr�al, 20/11/03:
 *  - Migrated original file from gate.jape to ca.umontreal.iro.rali.gate.jape
 *  - When testing for equality between a constraint and a path (an annotation),
 *    check whether the constraint is negated.  If yes, succeed if annotation
 *    type is NOT equal to constraint annotation type.  Also compare attributes
 *    when the negated constraint has ones.
 *  - When testing for equality between the attributes and values, call a
 *    "subsume" method that is aware that the comparison operator for each 
 *    attribute may be different from equality.
 *  - Allows constraints on different types of annotation
 *
 *  $Id$
 */


package ca.umontreal.iro.rali.gate.jape;

import java.io.*;

import gate.annotation.*;
import gate.util.*;
import gate.*;
import gate.gui.*;
import gate.creole.*;
import gate.event.*;

import java.util.*;

import ca.umontreal.iro.rali.gate.fsm.*;

/**
  * Represents a complete CPSL grammar, with a phase name, options and
  * rule set (accessible by name and by sequence).
  * Implements a transduce method taking a Document as input.
  * Constructs from String or File.
  */
public class SinglePhaseTransducer
extends Transducer implements JapeConstants, java.io.Serializable
{
  /** Debug flag */
  private static final boolean DEBUG = false;

  /** Construction from name. */
  public SinglePhaseTransducer(String name) {
    this.name = name;
    rules = new PrioritisedRuleList();
    finishedAlready = false;
  } // Construction from name

  /** Type of rule application (constants defined in JapeConstants). */
  private int ruleApplicationStyle = BRILL_STYLE;

  /** Set the type of rule application (types defined in JapeConstants). */
  public void setRuleApplicationStyle(int style) {
    ruleApplicationStyle = style;
  }

  /** The list of rules in this transducer. Ordered by priority and
    * addition sequence (which will be file position if they come from
    * a file).
    */
  private PrioritisedRuleList rules;

  FSM fsm;

  public FSM getFSM(){
    return fsm;
  }

  /** Add a rule. */
  public void addRule(Rule rule) {
    rules.add(rule);
  } // addRule

  /** The values of any option settings given. */
  private java.util.HashMap optionSettings = new java.util.HashMap();

  /** Add an option setting. If this option is set already, the new
    * value overwrites the previous one.
    */
  public void setOption(String name, String setting) {
    optionSettings.put(name, setting);
  } // setOption

  /** Get the value for a particular option. */
  public String getOption(String name) {
    return (String) optionSettings.get(name);
  } // getOption

  /** Whether the finish method has been called or not. */
  private boolean finishedAlready;

  /** Finish: replace dynamic data structures with Java arrays; called
    * after parsing.
    */
  public void finish(){
    // both MPT and SPT have finish called on them by the parser...
    if(finishedAlready) return;
    else finishedAlready = true;

    //each rule has a RHS which has a string for java code
    //those strings need to be compiled now
    Map actionClasses = new HashMap(rules.size());
    for(Iterator i = rules.iterator(); i.hasNext(); ){
      Rule rule = (Rule)i.next();
      rule.finish();
      actionClasses.put(rule.getRHS().getActionClassName(),
                        rule.getRHS().getActionClassString());
    }
    try{
      Javac.loadClasses(actionClasses);
    }catch(Exception e){
      Err.prln("Compile error:\n" + e.getMessage());
//e.printStackTrace();
    }

    //build the finite state machine transition graph
    fsm = new FSM(this);
    //clear the old style data structures
    rules.clear();
    rules = null;
  } // finish

  private void addAnnotationsByOffset(SimpleSortedSet keys, Set annotations){
    Iterator annIter = annotations.iterator();
    while(annIter.hasNext()){
      Annotation ann = (Annotation)annIter.next();
      //ignore empty annotations
      long offset = ann.getStartNode().getOffset().longValue();
      if(offset == ann.getEndNode().getOffset().longValue())
        continue;
//dam: was
/*
//      Long offset = ann.getStartNode().getOffset();

      List annsAtThisOffset = null;
      if(keys.add(offset)){
        annsAtThisOffset = new LinkedList();
        map.put(offset, annsAtThisOffset);
      }else{
        annsAtThisOffset = (List)map.get(offset);
      }
      annsAtThisOffset.add(ann);
*/
//dam: end
      keys.add(offset, ann);
    }
  }//private void addAnnotationsByOffset()


  /**
    * Transduce a document using the annotation set provided and the current
    * rule application style.
    */
  public void transduce(Document doc, AnnotationSet inputAS,
                        AnnotationSet outputAS) throws JapeException,
                                                       ExecutionException {
    interrupted = false;
    fireProgressChanged(0);

    //the input annotations will be read from this map
    //maps offset to list of annotations

//dam was
/*
    Map annotationsByOffset = new HashMap();

    SortedSet offsets = new TreeSet();
*/
//dam: now
    SimpleSortedSet offsets = new SimpleSortedSet();
    SimpleSortedSet annotationsByOffset = offsets;
//dam: end

    //select only the annotations of types specified in the input list
//    Out.println("Input:" + input);
    if(input.isEmpty())
    {
//dam: was
//        addAnnotationsByOffset(annotationsByOffset, offsets, inputAS);
//dam: now
        addAnnotationsByOffset(offsets, inputAS);
//dam: end
    } else {
      Iterator typesIter = input.iterator();
      AnnotationSet ofOneType = null;
      while(typesIter.hasNext()){
        ofOneType = inputAS.get((String)typesIter.next());
        if(ofOneType != null){
//dam: was
//        addAnnotationsByOffset(annotationsByOffset, offsets, ofOneType);
//dam: now
          addAnnotationsByOffset(offsets, ofOneType);
//dam: end
        }
      }
    }

    if(annotationsByOffset.isEmpty()){
      fireProcessFinished();
      return;
    }

    annotationsByOffset.sort();
    //define data structures
    //FSM instances that haven't blocked yet
    java.util.ArrayList activeFSMInstances = new java.util.ArrayList();

    // FSM instances that have reached a final state
    // This set will be sorted later by the length
    // of the document content covered by the matched annotations
    java.util.ArrayList acceptingFSMInstances = new ArrayList();
    FSMInstance currentFSM;


    //find the first node of the document
    Node startNode = ((Annotation)
                      ((ArrayList)annotationsByOffset.
                             get(offsets.first())).get(0)).
                      getStartNode();

    //used to calculate the percentage of processing done
    long lastNodeOff = doc.getContent().size().longValue();

    //the offset of the node where the matching currently starts
    //the value -1 marks no more annotations to parse
    long startNodeOff = startNode.getOffset().longValue();

    //used to decide when to fire progress events
    long oldStartNodeOff = 0;

    //the big while for the actual parsing
    while(startNodeOff != -1){
//Out.prln();
//Out.pr("Start: " + startNodeOff);
      //while there are more annotations to parse
      //create initial active FSM instance starting parsing from new startNode
      //currentFSM = FSMInstance.getNewInstance(
      currentFSM = new FSMInstance(
                  fsm,
                  fsm.getInitialState(),//fresh start
                  startNode,//the matching starts form the current startNode
                  startNode,//current position in AG is the start position
                  new java.util.HashMap(),//no bindings yet!
                  doc
                  );

      // at this point ActiveFSMInstances should always be empty!
      activeFSMInstances.clear();
      acceptingFSMInstances.clear();
//dam: was used LinkedList
//      activeFSMInstances.addLast(currentFSM);
//dam: now used ArrayList
      activeFSMInstances.add(currentFSM);
//dam: end

      //far each active FSM Instance, try to advance
      whileloop2:
      while(!activeFSMInstances.isEmpty()){
        if(interrupted) throw new ExecutionInterruptedException(
          "The execution of the \"" + getName() +
          "\" Jape transducer has been abruptly interrupted!");

//Out.pr(" <" + acceptingFSMInstances.size() + "/" +
//              activeFSMInstances.size() +">");
        // take the first active FSM instance
        currentFSM = (FSMInstance)activeFSMInstances.remove(0);

        // process the current FSM instance
        if(currentFSM.getFSMPosition().isFinal()){
          //the current FSM is in a final state
//dam: was LinkedList
//          acceptingFSMInstances.addLast(currentFSM.clone());
//dam: now
          acceptingFSMInstances.add(currentFSM.clone());
//dam: end
//          //if we are in APPELT mode clear all the accepting instances
//          //apart from the longest one
//          if(ruleApplicationStyle == APPELT_STYLE &&
//             acceptingFSMInstances.size() > 1){
//            Object longestAcceptor = acceptingFSMInstances.last();
//            acceptingFSMInstances.clear();
//            acceptingFSMInstances.add(longestAcceptor);
//          }
          //if we're only looking for the shortest stop here
          if(ruleApplicationStyle == FIRST_STYLE) break whileloop2;
        }

        // get all the annotations that start where the current FSM finishes
        SimpleSortedSet offsetsTailSet = offsets.tailSet(
			currentFSM.getAGPosition().getOffset().longValue());

        ArrayList paths; 
        long theFirst = offsetsTailSet.first();
        if(theFirst <0)
          continue;
	paths = (ArrayList)annotationsByOffset.get(theFirst);
	//System.out.println("Paths: " + paths + "\n^localInputIndex: " + localInputIndex);
        if(paths.isEmpty()) continue;

	// get the transitions for the current state of the FSM
	State currentState = currentFSM.getFSMPosition();	
	Iterator transitionsIter = currentState.getTransitions().iterator();

	// for each transition, keep the set of annotations starting at current
	// node (the "paths") that match each constraint of the transition
	transitionsWhile:
	while(transitionsIter.hasNext()){
	  Transition currentTransition = (Transition)transitionsIter.next();
	  Constraint[] currentConstraints = 
	               currentTransition.getConstraints().getConstraints();

	  // separate negated from "positive" constraints
	  ArrayList negatedConstraints = new ArrayList(3);
	  ArrayList positiveConstraints = new ArrayList(3);
	  for (int i=0; i<currentConstraints.length; i++) {
	    if (currentConstraints[i].isNegated())
	      negatedConstraints.add(currentConstraints[i]);
	    else
	      positiveConstraints.add(currentConstraints[i]);
	  }

	  // First test against negative constraints; skip this transition
	  // as soon as there is an unwanted match
	  for (Iterator pathsIter=paths.iterator(); pathsIter.hasNext(); ) {
            Annotation onePath = (Annotation)pathsIter.next();
	    for (int i=0, size=negatedConstraints.size(); i<size; i++) {
	      Constraint theConstraint = (Constraint) negatedConstraints.get(i);
	      if (theConstraint.getAnnotType().equals(onePath.getType()))
		// if type is the same, also check attributes
		if (theConstraint.subsumesOne((SimpleFeatureMapImpl) onePath.getFeatures())) {
		  continue transitionsWhile;
		}
	    }
	  }

	  // Then test against positive constraints and keep track of the
	  // annots that match each constraint.
	  
	  // First create empty lists for each constraint...
	  ArrayList matchingAnnot = new ArrayList(positiveConstraints.size());
	  for (int i=0, size=positiveConstraints.size(); i<size; i++) {
	    matchingAnnot.add(new LinkedList());
	  }

	  // for each annot. at the current node in the annot graph...
	  for (Iterator pathsIter=paths.iterator(); pathsIter.hasNext(); ) {
	    Annotation onePath = (Annotation)pathsIter.next();
	    
	    // for each positive constraint...
	    for (int i=0, size=positiveConstraints.size(); i<size; i++) {
	      Constraint theConstraint = (Constraint) positiveConstraints.get(i);
	      if (theConstraint.getAnnotType().equals(onePath.getType())) {
		// if type is the same, also check attributes
		if (theConstraint.subsumes((SimpleFeatureMapImpl) onePath.getFeatures())) {
		  ((LinkedList) matchingAnnot.get(i)).add(onePath);
		}
	      }
	    } // for each positive constraint
	  } // for each annot.

	  // If there are only negative constraints and no positive constraints,
	  // let the first list of matchingAnnot contain all annots at this node
	  if (negatedConstraints.size() != 0 && matchingAnnot.size() == 0) {
	    matchingAnnot.add(new LinkedList(paths));
	  }

	  // We have a match if every constraint is met by at least one annot.
	  // Given the sets Sx of the annotations that match constraint x, 
          // compute all tuples (A1, A2, ..., An) where Ax comes from the 
          // set Sx and n is the number of constraints
	  LinkedList combinations = combine(
		     matchingAnnot, matchingAnnot.size(), new LinkedList());
	    
	  // Create a new FSM for every tuple of annot
	  for (ListIterator tuplesIter = combinations.listIterator(); 
	       tuplesIter.hasNext(); ) {

	    LinkedList tuple = (LinkedList) tuplesIter.next();
	    //we have a match
	    //System.out.println("Match!");
	    //create a new FSMInstance, advance it over the current annotation
	    //take care of the bindings  and add it to ActiveFSM
	    FSMInstance newFSMI = (FSMInstance)currentFSM.clone();

	    // Find rightmost node offset
	    Node endNode = null;
	    for (ListIterator annotsInTupleIter = tuple.listIterator();
		 annotsInTupleIter.hasNext(); ) {

	      Annotation annot = (Annotation) annotsInTupleIter.next();
	      if (endNode == null) {
		// first loop: initialise
		endNode = annot.getEndNode();
	      }
	      else {
		// check for a rightmost offset
		if (annot.getEndNode().getOffset().compareTo(endNode.getOffset()) > 0) {
		  endNode = annot.getEndNode();
		}
	      } // else
	    } // ListIterator annotsInTupleIter

	    newFSMI.setAGPosition(endNode);
	    newFSMI.setFSMPosition(currentTransition.getTarget());

	    //bindings
	    java.util.Map binds = newFSMI.getBindings();
	    java.util.Iterator labelsIter =
                                   currentTransition.getBindings().iterator();
	    String oneLabel;
	    AnnotationSet boundAnnots, newSet;
	    while(labelsIter.hasNext()){
	      oneLabel = (String)labelsIter.next();
	      boundAnnots = (AnnotationSet)binds.get(oneLabel);
	      if(boundAnnots != null)
		newSet = new AnnotationSetImpl((AnnotationSet)boundAnnots);
	      else
		newSet = new AnnotationSetImpl(doc);

	      // newSet.addAll(tuple);  Ok with Gate 2.1, but buggy with 2.2!!!
	      // So call newSet.add one at a time so that annots keep their IDs
	      for (ListIterator annotsInTupleIter = tuple.listIterator();
		   annotsInTupleIter.hasNext(); ) {
		newSet.add((Annotation) annotsInTupleIter.next());
	      }

	      binds.put(oneLabel, newSet);

	    }//while(labelsIter.hasNext())
	    activeFSMInstances.add(newFSMI);
	    //Out.pr("^(" + newFSMI.getStartAGPosition().getOffset() +
	    //                               "->" + newFSMI.getAGPosition().getOffset() + ")");
	  } //while combinationsIter.hasNext()

	}//while(transitionsIter.hasNext())
      }//while(!activeFSMInstances.isEmpty())


      //FIRE THE RULE
//dam: use long
//      Long lastAGPosition = null;
//dam: now
      long lastAGPosition = -1;
//dam: end
      if(acceptingFSMInstances.isEmpty()){
        //no rule to fire, advance to the next input offset
        lastAGPosition = startNodeOff + 1;
      } else if(ruleApplicationStyle == BRILL_STYLE) {
      //System.out.println("Brill acceptor");
        // fire the rules corresponding to all accepting FSM instances
        java.util.Iterator accFSMs = acceptingFSMInstances.iterator();
        FSMInstance currentAcceptor;
        RightHandSide currentRHS;
        lastAGPosition = startNode.getOffset().longValue();

        while(accFSMs.hasNext()){
          currentAcceptor = (FSMInstance) accFSMs.next();

          currentRHS = currentAcceptor.getFSMPosition().getAction();
          currentRHS.transduce(doc, currentAcceptor.getBindings(),
                               inputAS, outputAS, ontology);
//dam: use long
//          Long currentAGPosition = currentAcceptor.getAGPosition().getOffset();
//dam: now
          long currentAGPosition = 
	       currentAcceptor.getAGPosition().getOffset().longValue();
//dam: end
          if(currentAGPosition > lastAGPosition)
            lastAGPosition = currentAGPosition;
        }

      } else if(ruleApplicationStyle == APPELT_STYLE ||
                ruleApplicationStyle == FIRST_STYLE ||
                ruleApplicationStyle == ONCE_STYLE) {

//System.out.println("Appelt acceptor");
	
	// We want the rule that matches the longest document region, so use
	// a reverse comparator based on the length to the region and rule
	// priority (implemented in the compareTo method of FSMInstance).  
	// However, the FSM that are equal (same length and priority) must
	// stay in the same order, because the transitions were added in a
	// a specific order by FSM.convertComplexPE to implement the greediness
	// of "*" and "+" Kleene operators (loop transitions must be explored
	// before the others)
        Collections.sort(acceptingFSMInstances, Collections.reverseOrder());

        FSMInstance currentAcceptor =(FSMInstance)acceptingFSMInstances.get(0);
        if(isDebugMode()){
          //see if we have any conflicts
          Iterator accIter = acceptingFSMInstances.iterator();
          FSMInstance anAcceptor;
          List conflicts = new ArrayList();
          while(accIter.hasNext()){
            anAcceptor = (FSMInstance)accIter.next();
            if(anAcceptor.equals(currentAcceptor)){
              conflicts.add(anAcceptor);
            }else{
              break;
            }
          }
          if(conflicts.size() > 1){
            Out.prln("\nConflicts found during matching:" +
                     "\n================================");
            accIter = conflicts.iterator();
            int i = 0;
            while(accIter.hasNext()){
              Out.prln(i++ + ") " + accIter.next().toString());
            }
          }
        }

        RightHandSide currentRHS = currentAcceptor.getFSMPosition().getAction();
        currentRHS.transduce(doc, currentAcceptor.getBindings(),
                             inputAS, outputAS, ontology);

        //if in ONCE mode stop after first match
        if(ruleApplicationStyle == ONCE_STYLE) return;

        //advance in AG
        lastAGPosition = currentAcceptor.getAGPosition().getOffset().longValue();
      }
//      else if(ruleApplicationStyle == FIRST_STYLE) {
//        // AcceptingFSMInstances is an ordered structure:
//        // just execute the shortest (first) rule
//
//        FSMInstance currentAcceptor =(FSMInstance)acceptingFSMInstances.first();
//        RightHandSide currentRHS = currentAcceptor.getFSMPosition().getAction();
//        currentRHS.transduce(doc, outputAS, currentAcceptor.getBindings());
//        //advance in AG
//        long lastAGPosition = currentAcceptor.getAGPosition().
//                              getOffset().longValue();
//        //advance the index on input
//        while(inputIndex < annotations.size() &&
//              ((Annotation)annotations.get(inputIndex)).
//              getStartNode().getOffset().longValue() < lastAGPosition){
//          inputIndex++;
//        }
//      }
      else throw new RuntimeException("Unknown rule application style!");


      //advance on input
//      SortedSet OffsetsTailSet = offsets.tailSet(lastAGPosition);
      SimpleSortedSet OffsetsTailSet = offsets.tailSet(lastAGPosition);
//<<< DAM: isEmpty speedup
/*
      if(OffsetsTailSet.isEmpty()){
*/
//=== DAM: now
        long theFirst = OffsetsTailSet.first();
      if( theFirst < 0){
//>>> DAM: end
        //no more input, phew! :)
        startNodeOff = -1;
        fireProcessFinished();
      }else{
//<<< DAM: use long
/*
        Long nextKey = (Long)OffsetsTailSet.first();
*/
//=== DAM: now
        long nextKey = theFirst;
//>>> DAM: end
        startNode = ((Annotation)
                      ((ArrayList)annotationsByOffset.get(nextKey)).get(0)). //nextKey
                    getStartNode();
        startNodeOff = startNode.getOffset().longValue();

        //eliminate the possibility for infinite looping
        if(oldStartNodeOff == startNodeOff){
//Out.prln("");
//Out.pr("SKIP " + startNodeOff);
          //we are about to step twice in the same place, ...skip ahead
          lastAGPosition = startNodeOff + 1;
          OffsetsTailSet = offsets.tailSet(lastAGPosition);
//<<< DAM: isEmpty speedup
/*
          if(OffsetsTailSet.isEmpty()){
*/
//=== DAM: now
          theFirst = OffsetsTailSet.first();
          if(theFirst < 0){
//>>> DAM: end
            //no more input, phew! :)
            startNodeOff = -1;
            fireProcessFinished();
          }else{
//<<< DAM: use long
//            nextKey = (Long)OffsetsTailSet.first();
//=== DAM: now
            nextKey = theFirst;
//>>> DAM: end
            startNode = ((Annotation)
                          ((List)annotationsByOffset.get(theFirst)).get(0)).
                        getStartNode();
            startNodeOff =startNode.getOffset().longValue();
          }
//Out.prln(" ->" + startNodeOff);
        }//if(oldStartNodeOff == startNodeOff)


        //fire the progress event
        if(startNodeOff - oldStartNodeOff > 256){
          if(isInterrupted()) throw new ExecutionInterruptedException(
            "The execution of the \"" + getName() +
            "\" Jape transducer has been abruptly interrupted!");

          fireProgressChanged((int)(100 * startNodeOff / lastNodeOff));
          oldStartNodeOff = startNodeOff;
        }
      }
    }//while(startNodeOff != -1)
    fireProcessFinished();
  } // transduce


  /** Clean up (delete action class files, for e.g.). */
  public void cleanUp() {
//    for(DListIterator i = rules.begin(); ! i.atEnd(); i.advance())
//      ((Rule) i.get()).cleanUp();
  } // cleanUp

  /** A string representation of this object. */
  public String toString() {
    return toString("");
  } // toString()

  /** A string representation of this object. */
  public String toString(String pad) {
    String newline = Strings.getNl();
    String newPad = Strings.addPadding(pad, INDENT_PADDING);

    StringBuffer buf =
      new StringBuffer(pad + "SPT: name(" + name + "); ruleApplicationStyle(");

    switch(ruleApplicationStyle) {
      case APPELT_STYLE: buf.append("APPELT_STYLE); "); break;
      case BRILL_STYLE:  buf.append("BRILL_STYLE); ");  break;
      default: break;
    }

    buf.append("rules(" + newline);
    Iterator rulesIterator = rules.iterator();
    while(rulesIterator.hasNext())
      buf.append(((Rule) rulesIterator.next()).toString(newPad) + " ");

    buf.append(newline + pad + ")." + newline);

    return buf.toString();
  } // toString(pad)

  //needed by fsm
  public PrioritisedRuleList getRules() {
    return rules;
  }

  /**
    * Adds a new type of input annotations used by this transducer.
    * If the list of input types is empty this transducer will parse all the
    * annotations in the document otherwise the types not found in the input
    * list will be completely ignored! To be used with caution!
    */
  public void addInput(String ident) {
    input.add(ident);
  }
  public synchronized void removeProgressListener(ProgressListener l) {
    if (progressListeners != null && progressListeners.contains(l)) {
      Vector v = (Vector) progressListeners.clone();
      v.removeElement(l);
      progressListeners = v;
    }
  }
  public synchronized void addProgressListener(ProgressListener l) {
    Vector v = progressListeners == null ? new Vector(2) : (Vector) progressListeners.clone();
    if (!v.contains(l)) {
      v.addElement(l);
      progressListeners = v;
    }
  }

  /**
    * Defines the types of input annotations that this transducer reads. If this
    * set is empty the transducer will read all the annotations otherwise it
    * will only "see" the annotations of types found in this list ignoring all
    * other types of annotations.
    */
  java.util.Set input = new java.util.HashSet();
  private transient Vector progressListeners;

  protected void fireProgressChanged(int e) {
    if (progressListeners != null) {
      Vector listeners = progressListeners;
      int count = listeners.size();
      for (int i = 0; i < count; i++) {
        ((ProgressListener) listeners.elementAt(i)).progressChanged(e);
      }
    }
  }
  protected void fireProcessFinished() {
    if (progressListeners != null) {
      Vector listeners = progressListeners;
      int count = listeners.size();
      for (int i = 0; i < count; i++) {
        ((ProgressListener) listeners.elementAt(i)).processFinished();
      }
    }
  }
  public int getRuleApplicationStyle() {
    return ruleApplicationStyle;
  }

  /*
  private void writeObject(ObjectOutputStream oos) throws IOException {
    Out.prln("writing spt");
    oos.defaultWriteObject();
    Out.prln("finished writing spt");
  } // writeObject
  */

  /**
     Computes all tuples (x1, x2, ..., xn) resulting from the linear
     combination of the elements of n lists, where x1 comes from the 1st list,
     x2 comes from the second, etc.  This method works recursively.  The first
     call should have those parameters:

     @param sourceLists an array of n lists whose elements will be combined
     @param maxTupleSize the number of elements per tuple
     @param incompleteTuple an empty list
  */

  private static LinkedList combine(ArrayList sourceLists, 
			    int maxTupleSize, 
			    LinkedList incompleteTuple) {

    LinkedList newTupleList = new LinkedList();

    if (incompleteTuple.size() == maxTupleSize) {
      newTupleList.add(incompleteTuple);
    }
    else {
      LinkedList currentSourceList = (LinkedList) sourceLists.get(incompleteTuple.size());
      // use for loop instead of ListIterator to increase speed (critical here)
      for (int i=0; i<currentSourceList.size(); i++) {
	LinkedList augmentedTuple = (LinkedList) incompleteTuple.clone();
	augmentedTuple.add(currentSourceList.get(i));
	newTupleList.addAll(combine(sourceLists, maxTupleSize, augmentedTuple));
      }
    }

    return newTupleList;
  }




} // class SinglePhaseTransducer

/*
class SimpleSortedSet {

    static final int INCREMENT = 1023;
    int[] theArray = new int[INCREMENT];
    Object[] theObject = new Object[INCREMENT];
    int tsindex = 0;
    int size = 0;
    public static int avesize = 0;
    public static int maxsize = 0;
    public static int avecount = 0;
    public SimpleSortedSet()
    {
        avecount++;
        java.util.Arrays.fill(theArray, Integer.MAX_VALUE);
    }

    public Object get(int elValue)
    {
        int index = java.util.Arrays.binarySearch(theArray, elValue);
        if (index >=0)
            return theObject[index];
        return null;
    }

    public boolean add(int elValue, Object o)
    {
        int index = java.util.Arrays.binarySearch(theArray, elValue);
        if (index >=0)
        {
            ((ArrayList)theObject[index]).add(o);
            return false;
        }
        if (size == theArray.length)
        {
            int[] temp = new int[theArray.length + INCREMENT];
            Object[] tempO = new Object[theArray.length + INCREMENT];
            System.arraycopy(theArray, 0, temp, 0, theArray.length);
            System.arraycopy(theObject, 0, tempO, 0, theArray.length);
            java.util.Arrays.fill(temp, theArray.length, temp.length , Integer.MAX_VALUE);
            theArray = temp;
            theObject = tempO;
        }
        index = ~index;
        System.arraycopy(theArray, index, theArray, index+1, size - index );
        System.arraycopy(theObject, index, theObject, index+1, size - index );
        theArray[index] = elValue;
        theObject[index] = new ArrayList();
        ((ArrayList)theObject[index]).add(o);
        size++;
        return true;
    }
    public int first()
    {
        if (tsindex >= size) return -1;
        return theArray[tsindex];
    }

    public Object getFirst()
    {
        if (tsindex >= size) return null;
        return theObject[tsindex];
    }

    public SimpleSortedSet tailSet(int elValue)
    {
        if (tsindex < theArray.length && elValue != theArray[tsindex])
        {
            if (tsindex<(size-1) && elValue > theArray[tsindex] &&
                elValue <= theArray[tsindex+1])
                {
                    tsindex++;
                   return this;
                }
            int index = java.util.Arrays.binarySearch(theArray, elValue);
            if (index < 0)
                index = ~index;
            tsindex = index;
        }
        return this;
    }

    public boolean isEmpty()
    {
        return size ==0;
    }
};
*/