SinglePhaseTransducer.java
0001 /*
0002  *  SinglePhaseTransducer.java - transducer class
0003  *
0004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
0005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0006  *
0007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0008  *  software, licenced under the GNU Library General Public License,
0009  *  Version 2, June 1991 (in the distribution as file licence.html,
0010  *  and also available at http://gate.ac.uk/gate/licence.html).
0011  *
0012  *  Hamish Cunningham, 24/07/98
0013  *
0014  *  $Id: SinglePhaseTransducer.java 17916 2014-05-05 10:07:28Z markagreenwood $
0015  */
0016 
0017 package gate.jape;
0018 
0019 import gate.Annotation;
0020 import gate.AnnotationSet;
0021 import gate.Controller;
0022 import gate.Corpus;
0023 import gate.CorpusController;
0024 import gate.Document;
0025 import gate.Gate;
0026 import gate.Node;
0027 import gate.annotation.AnnotationSetImpl;
0028 import gate.creole.ExecutionException;
0029 import gate.creole.ExecutionInterruptedException;
0030 import gate.creole.ontology.Ontology;
0031 import gate.event.ProgressListener;
0032 import gate.fsm.FSM;
0033 import gate.fsm.FSMInstance;
0034 import gate.fsm.RuleTime;
0035 import gate.fsm.State;
0036 import gate.fsm.Transition;
0037 import gate.util.Benchmark;
0038 import gate.util.GateClassLoader;
0039 import gate.util.GateRuntimeException;
0040 import gate.util.SimpleSortedSet;
0041 import gate.util.Strings;
0042 
0043 import java.io.IOException;
0044 import java.io.Serializable;
0045 import java.util.ArrayList;
0046 import java.util.Collection;
0047 import java.util.Collections;
0048 import java.util.HashMap;
0049 import java.util.HashSet;
0050 import java.util.Iterator;
0051 import java.util.LinkedList;
0052 import java.util.List;
0053 import java.util.Map;
0054 import java.util.Set;
0055 import java.util.Vector;
0056 import java.util.concurrent.atomic.AtomicInteger;
0057 
0058 import org.apache.log4j.Logger;
0059 
0060 /**
0061  * Represents a complete CPSL grammar, with a phase name, options and
0062  * rule set (accessible by name and by sequence). Implements a transduce
0063  * method taking a Document as input. Constructs from String or File.
0064  */
0065 public class SinglePhaseTransducer extends Transducer implements JapeConstants,
0066                                                      Serializable {
0067 
0068   private static final long serialVersionUID = -2749474684496896114L;
0069 
0070   protected static final Logger log = Logger
0071           .getLogger(SinglePhaseTransducer.class);
0072 
0073   private static AtomicInteger actionClassNumber = new AtomicInteger();
0074 
0075   /*
0076    * A structure to pass information to/from the fireRule() method.
0077    * Since Java won't let us return multiple values, we stuff them into
0078    * a 'state' object that fireRule() can update.
0079    */
0080   protected static class SearchState {
0081     Node startNode;
0082 
0083     long startNodeOff;
0084 
0085     long oldStartNodeOff;
0086 
0087     SearchState(Node startNode, long startNodeOff, long oldStartNodeOff) {
0088       this.startNode = startNode;
0089       this.startNodeOff = startNodeOff;
0090       this.oldStartNodeOff = oldStartNodeOff;
0091     }
0092   }
0093   
0094   private static final class FSMMatcherResult{
0095     /**
0096      @param activeFSMInstances
0097      @param acceptingFSMInstances
0098      */
0099     public FSMMatcherResult(List<FSMInstance> activeFSMInstances,
0100             List<FSMInstance> acceptingFSMInstances) {
0101       this.activeFSMInstances = activeFSMInstances;
0102       this.acceptingFSMInstances = acceptingFSMInstances;
0103     }
0104     
0105     private List<FSMInstance> acceptingFSMInstances;
0106     private List<FSMInstance> activeFSMInstances;
0107     
0108   }
0109 
0110   /** Construction from name. */
0111   public SinglePhaseTransducer(String name) {
0112     this.name = name;
0113     rules = new PrioritisedRuleList();
0114     finishedAlready = false;
0115   // Construction from name
0116 
0117   /** Type of rule application (constants defined in JapeConstants). */
0118   protected int ruleApplicationStyle = BRILL_STYLE;
0119 
0120   /** Set the type of rule application (types defined in JapeConstants). */
0121   public void setRuleApplicationStyle(int style) {
0122     ruleApplicationStyle = style;
0123   }
0124 
0125   /**
0126    * The list of rules in this transducer. Ordered by priority and
0127    * addition sequence (which will be file position if they come from a
0128    * file).
0129    */
0130   protected PrioritisedRuleList rules;
0131 
0132   protected FSM fsm;
0133 
0134   /**
0135    * A list of FSM instances that haven't blocked yet, used during matching.
0136    */
0137   protected List<FSMInstance> activeFSMInstances;
0138   
0139   public FSM getFSM() {
0140     return fsm;
0141   }
0142 
0143   /** Add a rule. */
0144   public void addRule(Rule rule) {
0145     rules.add(rule);
0146   // addRule
0147 
0148   /** The values of any option settings given. */
0149   private Map<String, String> optionSettings = new HashMap<String, String>();
0150 
0151   /**
0152    * Add an option setting. If this option is set already, the new value
0153    * overwrites the previous one.
0154    */
0155   public void setOption(String name, String setting) {
0156     optionSettings.put(name, setting);
0157   // setOption
0158 
0159   /** Get the value for a particular option. */
0160   public String getOption(String name) {
0161     return optionSettings.get(name);
0162   // getOption
0163 
0164   /** Whether the finish method has been called or not. */
0165   protected boolean finishedAlready;
0166 
0167   /**
0168    * Finish: replace dynamic data structures with Java arrays; called
0169    * after parsing.
0170    */
0171   @Override
0172   public void finish(GateClassLoader classLoader) {
0173     // both MPT and SPT have finish called on them by the parser...
0174     if(finishedAlreadyreturn;
0175     finishedAlready = true;
0176 
0177     // each rule has a RHS which has a string for java code
0178     // those strings need to be compiled now
0179     
0180     // TODO: (JP) if we have some binary JAPE grammars loaded and then we
0181     // compile additional action classes here, we can potentially
0182     // get duplicate class names.
0183     // Instead, we should modify and increment the classname until
0184     // we find one that is not already taken.
0185     Map<String,String> actionClasses = new HashMap<String,String>(rules.size());
0186     for(Iterator<Rule> i = rules.iterator(); i.hasNext();) {
0187       Rule rule = i.next();
0188       rule.finish(classLoader);
0189       actionClasses.put(rule.getRHS().getActionClassName(), rule.getRHS()
0190               .getActionClassString());
0191     }
0192     try {
0193       gate.util.Javac.loadClasses(actionClasses, classLoader);
0194     }
0195     catch(Exception e) {
0196       throw new GateRuntimeException(e);
0197     }
0198     compileEventBlocksActionClass(classLoader);
0199 
0200     // build the finite state machine transition graph
0201     fsm = createFSM();
0202     // clear the old style data structures
0203     rules.clear();
0204     rules = null;
0205   // finish
0206 
0207   protected FSM createFSM() {
0208     return new FSM(this);
0209   }
0210 
0211   // dam: was
0212   // private void addAnnotationsByOffset(Map map, SortedSet keys, Set
0213   // annotations){
0214   private void addAnnotationsByOffset(/* Map map, */SimpleSortedSet keys,
0215           Set<Annotation> annotations) {
0216     Iterator<Annotation> annIter = annotations.iterator();
0217     while(annIter.hasNext()) {
0218       Annotation ann = annIter.next();
0219       // ignore empty annotations
0220       long offset = ann.getStartNode().getOffset().longValue();
0221       if(offset == ann.getEndNode().getOffset().longValue()) continue;
0222       // dam: was
0223       /*
0224        * // Long offset = ann.getStartNode().getOffset();
0225        *
0226        * List annsAtThisOffset = null; if(keys.add(offset)){
0227        * annsAtThisOffset = new LinkedList(); map.put(offset,
0228        * annsAtThisOffset); }else{ annsAtThisOffset =
0229        * (List)map.get(offset); } annsAtThisOffset.add(ann);
0230        */
0231       // dam: end
0232       keys.add(offset, ann);
0233     }
0234   }// private void addAnnotationsByOffset()
0235 
0236   /**
0237    * Transduce a document using the annotation set provided and the
0238    * current rule application style.
0239    */
0240   @Override
0241   public void transduce(Document doc, AnnotationSet inputAS,
0242           AnnotationSet outputASthrows JapeException, ExecutionException {
0243     interrupted = false;
0244     log.debug("Start: " + name);
0245     fireProgressChanged(0);
0246 
0247     // the input annotations will be read from this map
0248     // maps offset to list of annotations
0249     SimpleSortedSet offsets = new SimpleSortedSet();
0250     SimpleSortedSet annotationsByOffset = offsets;
0251 
0252     // select only the annotations of types specified in the input list
0253     if(input.isEmpty()) {
0254       addAnnotationsByOffset(offsets, inputAS);
0255     }
0256     else {
0257       Iterator<String> typesIter = input.iterator();
0258       AnnotationSet ofOneType = null;
0259       while(typesIter.hasNext()) {
0260         ofOneType = inputAS.get(typesIter.next());
0261         if(ofOneType != null) {
0262           addAnnotationsByOffset(offsets, ofOneType);
0263         }
0264       }
0265     }
0266 
0267     if(annotationsByOffset.isEmpty()) {
0268       fireProcessFinished();
0269       return;
0270     }
0271 
0272     annotationsByOffset.sort();
0273     // define data structures
0274     // FSM instances that haven't blocked yet
0275     if(activeFSMInstances == null) {
0276       activeFSMInstances = new LinkedList<FSMInstance>();
0277     }
0278     else {
0279       activeFSMInstances.clear();
0280     }
0281 
0282     // FSM instances that have reached a final state
0283     // This is a list and the contained objects are sorted by the length
0284     // of the document content covered by the matched annotations
0285     List<FSMInstance> acceptingFSMInstances = new LinkedList<FSMInstance>();
0286 
0287     // find the first node of the document
0288     @SuppressWarnings("unchecked")
0289     Node startNode = ((List<Annotation>)annotationsByOffset.get(offsets
0290             .first())).get(0).getStartNode();
0291 
0292     // used to calculate the percentage of processing done
0293     long lastNodeOff = doc.getContent().size().longValue();
0294 
0295     // the offset of the node where the matching currently starts
0296     // the value -1 marks no more annotations to parse
0297     long startNodeOff = startNode.getOffset().longValue();
0298 
0299     // The structure that fireRule() will update
0300     SearchState state = new SearchState(startNode, startNodeOff, 0);
0301 
0302     // the big while for the actual parsing
0303     while(state.startNodeOff != -1) {
0304       // while there are more annotations to parse
0305       // create initial active FSM instance starting parsing from new
0306       // startNode
0307       // currentFSM = FSMInstance.getNewInstance(
0308       FSMInstance firstCurrentFSM = new FSMInstance(fsm, 
0309               fsm.getInitialState(),// fresh start
0310               state.startNode,// the matching starts form the current startNode
0311               state.startNode,// current position in AG is the start position
0312               new java.util.HashMap<String, AnnotationSet>(),// no bindings yet!
0313               doc);
0314 
0315       // at this point ActiveFSMInstances should always be empty!
0316       activeFSMInstances.clear();
0317       acceptingFSMInstances.clear();
0318       activeFSMInstances.add(firstCurrentFSM);
0319 
0320       // far each active FSM Instance, try to advance
0321       // while(!finished){
0322       activeFSMWhile: while(!activeFSMInstances.isEmpty()) {
0323         if(interrupted)
0324           throw new ExecutionInterruptedException("The execution of the \""
0325                   + getName()
0326                   "\" Jape transducer has been abruptly interrupted!");
0327 
0328         // take the first active FSM instance
0329         FSMInstance currentFSM = activeFSMInstances.remove(0);
0330         // process the current FSM instance
0331 //        if(currentFSM.getFSMPosition().isFinal()) {
0332 //          // the current FSM is in a final state
0333 //          acceptingFSMInstances.add((FSMInstance)currentFSM.clone());
0334 //          // if we're only looking for the shortest stop here
0335 //          if(ruleApplicationStyle == FIRST_STYLE) break;
0336 //        }
0337 
0338         FSMMatcherResult result = attemptAdvance(currentFSM, offsets,
0339                 annotationsByOffset, doc, inputAS);
0340         if(result != null){
0341           if(result.acceptingFSMInstances != null && 
0342                   !result.acceptingFSMInstances.isEmpty()) {
0343             acceptingFSMInstances.addAll(result.acceptingFSMInstances);
0344             if(ruleApplicationStyle == FIRST_STYLE ||
0345                ruleApplicationStyle == ONCE_STYLEbreak activeFSMWhile;
0346           }
0347           
0348           if(result.activeFSMInstances != null && 
0349                   !result.activeFSMInstances.isEmpty()) {
0350             activeFSMInstances.addAll(result.activeFSMInstances);
0351           }
0352         }
0353       }
0354       boolean keepGoing = fireRule(acceptingFSMInstances, state, lastNodeOff,
0355               offsets, inputAS, outputAS, doc, annotationsByOffset);
0356       if(!keepGoingbreak;
0357       if(((DefaultActionContext)actionContext).isPhaseEnded()) {
0358         ((DefaultActionContext)actionContext).setPhaseEnded(false);
0359         log.debug("Ending phase prematurely");
0360         break;
0361       }
0362     }// while(state.startNodeOff != -1)
0363     // fsmRunnerPool.shutdown();
0364 
0365     fireProcessFinished();
0366     if (Benchmark.isBenchmarkingEnabled()) {
0367       for (RuleTime r : fsm.getRuleTimes()) {
0368         String ruleName = r.getRuleName();
0369         long timeSpentOnRule = r.getTimeSpent();
0370         r.setTimeSpent(0)// Reset time to zero for next document
0371         Benchmark.checkPointWithDuration(timeSpentOnRule, Benchmark.createBenchmarkId("rule__" + ruleName, this.getBenchmarkId()), this, this.benchmarkFeatures);
0372       }
0373     }
0374   // transduce
0375   
0376   
0377 
0378   /**
0379    * Try to advance the activeFSMInstances.
0380    
0381    @return a list of newly created FSMInstances
0382    */
0383   @SuppressWarnings("unchecked")
0384   private FSMMatcherResult attemptAdvance(FSMInstance currentInstance,
0385           SimpleSortedSet offsets, SimpleSortedSet annotationsByOffset,
0386           Document document, AnnotationSet inputAS) {
0387     long startTime = 0;
0388     if (Benchmark.isBenchmarkingEnabled()) {
0389       startTime = Benchmark.startPoint();
0390     }
0391     List<FSMInstance> newActiveInstances = null;
0392     List<FSMInstance> newAcceptingInstances = null;
0393 
0394     // Attempt advancing the current instance.
0395     // While doing that, generate new active FSM instances and 
0396     // new accepting FSM instances, as required
0397 
0398     // create a clone to be used for creating new states
0399     // the actual current instance cannot be used itself, as it may change
0400     FSMInstance currentClone = (FSMInstance)currentInstance.clone();
0401     
0402     // process the current FSM instance
0403     if(currentInstance.getFSMPosition().isFinal()) {
0404       // the current FSM is in a final state
0405       if(newAcceptingInstances == null){
0406         newAcceptingInstances = new ArrayList<FSMInstance>();
0407       }
0408 //        newAcceptingInstances.add((FSMInstance)currentInstance.clone());
0409       newAcceptingInstances.add(currentClone);
0410       // if we're only looking for the shortest stop here
0411       if(ruleApplicationStyle == FIRST_STYLE ||
0412          ruleApplicationStyle == ONCE_STYLE ) {
0413         if (Benchmark.isBenchmarkingEnabled()) {
0414           updateRuleTime(currentInstance, startTime);
0415         }
0416         return new FSMMatcherResult(newActiveInstances, newAcceptingInstances);
0417       }
0418     }
0419 
0420     // get all the annotations that start where the current FSM
0421     // finishes
0422     SimpleSortedSet offsetsTailSet = offsets.tailSet(currentInstance
0423             .getAGPosition().getOffset().longValue());
0424     long theFirst = offsetsTailSet.first();
0425     List<Annotation> paths = (theFirst >= ?
0426             (List<Annotation>)annotationsByOffset.get(theFirstnull;
0427 
0428     if(paths != null && !paths.isEmpty()) {
0429       // get the transitions for the current state of the FSM
0430       State currentState = currentClone.getFSMPosition();
0431       Iterator<Transition> transitionsIter = currentState.getTransitions().iterator();
0432       
0433       // A flag used to indicate when advancing the current instance requires 
0434       // the creation of a clone (i.e. when there are more than 1 ways to advance).
0435       boolean newInstanceRequired = false;
0436   
0437       // for each transition, keep the set of annotations starting at
0438       // current node (the "paths") that match each constraint of the
0439       // transition.
0440       transitionsWhile: while(transitionsIter.hasNext()) {
0441         Transition currentTransition = transitionsIter.next();
0442   
0443         // There will only be multiple constraints if this transition is
0444         // over
0445         // a written constraint that has the "and" operator (comma) and
0446         // the
0447         // parts referr to different annotation types. For example -
0448         // {A,B} would result in 2 constraints in the array, while
0449         // {A.foo=="val", A.bar=="val"} would only be a single
0450         // constraint.
0451         Constraint[] currentConstraints = currentTransition.getConstraints()
0452                 .getConstraints();
0453   
0454         boolean hasPositiveConstraint = false;
0455         @SuppressWarnings("rawtypes")
0456         List<Annotation>[] matchesByConstraint = new List[currentConstraints.length];
0457         for(int i = 0; i < matchesByConstraint.length; i++)
0458           matchesByConstraint[inull;
0459         // Map<Constraint, Collection<Annotation>> matchingMap =
0460         // new LinkedHashMap<Constraint, Collection<Annotation>>();
0461   
0462         // check all negated constraints first. If any annotation
0463         // matches any
0464         // negated constraint, then the transition fails.
0465         for(int i = 0; i < currentConstraints.length; i++) {
0466           // for(Constraint c : currentConstraints) {
0467           Constraint c = currentConstraints[i];
0468           if(!c.isNegated()) {
0469             hasPositiveConstraint = true;
0470             continue;
0471           }
0472           List<Annotation> matchList = c.matches(paths, ontology, inputAS);
0473           if(!matchList.isEmpty()) continue transitionsWhile;
0474         }
0475   
0476         // Now check all non-negated constraints. At least one
0477         // annotation must
0478         // match each constraint.
0479         if(hasPositiveConstraint) {
0480           for(int i = 0; i < currentConstraints.length; i++) {
0481             // for(Constraint c : currentConstraints) {
0482             Constraint c = currentConstraints[i];
0483             if(c.isNegated()) continue;
0484             List<Annotation> matchList = c.matches(paths, ontology, inputAS);
0485             // if no annotations matched, then the transition fails.
0486             if(matchList.isEmpty()) {
0487               continue transitionsWhile;
0488             }
0489             else {
0490               // matchingMap.put(c, matchList);
0491               matchesByConstraint[i= matchList;
0492             }
0493           }
0494         // end if hasPositiveConstraint
0495         else {
0496           // There are no non-negated constraints. Since the negated
0497           // constraints
0498           // did not fail, this means that all of the current
0499           // annotations
0500           // are potentially valid. Add the whole set to the
0501           // matchingMap.
0502           // Use the first negated constraint for the debug trace since
0503           // any will do.
0504           // matchingMap.put(currentConstraints[0], paths);
0505           matchesByConstraint[0= paths;
0506         }
0507   
0508         // We have a match if every positive constraint is met by at
0509         // least one annot.
0510         // Given the sets Sx of the annotations that match constraint x,
0511         // compute all tuples (A1, A2, ..., An) where Ax comes from the
0512         // set Sx and n is the number of constraints
0513         List<List<Annotation>> matchLists = new ArrayList<List<Annotation>>();
0514         for(int i = 0; i < currentConstraints.length; i++) {
0515           // for(Map.Entry<Constraint,Collection<Annotation>> entry :
0516           // matchingMap.entrySet()) {
0517           // seeing the constraint is useful when debugging
0518           @SuppressWarnings("unused")
0519           Constraint c = currentConstraints[i];
0520           // Constraint c = entry.getKey();
0521           List<Annotation> matchList = matchesByConstraint[i];
0522           // Collection<Annotation> matchList = entry.getValue();
0523           if(matchList != null) {
0524             matchLists.add(matchList);
0525           }
0526           // if (matchList instanceof List)
0527           // matchLists.add((List<Annotation>)matchList);
0528           // else
0529           // matchLists.add(new ArrayList<Annotation>(matchList));
0530         }
0531         
0532         List<List<Annotation>> combinations = combine(matchLists, matchLists
0533                 .size()new LinkedList<Annotation>());
0534         // Create a new FSM for every tuple of annot
0535         
0536         for(List<Annotation> tuple : combinations) {
0537           // Find longest annotation and use that to mark the start of
0538           // the
0539           // new FSM
0540           Annotation matchingAnnot = getRightMostAnnotation(tuple);
0541   
0542           // we have a match.
0543           FSMInstance newFSMI;
0544           // create a new FSMInstance, advance it over
0545           // the current
0546           // annotation take care of the bindings and add it to
0547           // ActiveFSM
0548           if(newInstanceRequired){
0549             // we need to create a clone
0550             newFSMI = (FSMInstance)currentClone.clone();
0551             //set the old AG state
0552             //set the old FSM position
0553           }else{
0554             // we're advancing the current instance
0555             newFSMI = currentInstance;
0556             // next time, we'll have to create a new one
0557             newInstanceRequired = true;
0558             //save the old FSM position
0559           }
0560           newFSMI.setAGPosition(matchingAnnot.getEndNode());
0561           newFSMI.setFSMPosition(currentTransition.getTarget());
0562     
0563           // bindings
0564           Map<String,AnnotationSet> binds = newFSMI.getBindings();
0565           Iterator<String> labelsIter = currentTransition.getBindings().iterator();
0566           String oneLabel;
0567           AnnotationSet boundAnnots, newSet;
0568           while(labelsIter.hasNext()) {
0569             oneLabel = labelsIter.next();
0570             boundAnnots = binds.get(oneLabel);
0571             if(boundAnnots != null)
0572               newSet = new AnnotationSetImpl(boundAnnots);
0573             else newSet = new AnnotationSetImpl(document);
0574   
0575             for(Annotation annot : tuple) {
0576               newSet.add(annot);
0577             }
0578   
0579             binds.put(oneLabel, newSet);
0580           }// while(labelsIter.hasNext())
0581           if(newActiveInstances == null) {
0582             newActiveInstances = new ArrayList<FSMInstance>();
0583           }
0584           newActiveInstances.add(newFSMI);
0585         // iter over matching combinations
0586       }// while(transitionsIter.hasNext())
0587     }
0588     if (Benchmark.isBenchmarkingEnabled()) {
0589       updateRuleTime(currentInstance, startTime);
0590     }
0591     return new FSMMatcherResult(newActiveInstances, newAcceptingInstances);
0592   }
0593 
0594   /**
0595    * Increment the time spent by the rule associated with the FSM
0596    @param currentInstance  The FSMInstance which has been running since startTime
0597    @param startTime    The time that the FSMInstance started running
0598    */
0599   private void updateRuleTime(FSMInstance currentInstance, long startTime) {
0600     int index = currentInstance.getFSMPosition().getIndexInRuleList();
0601     currentInstance.getSupportGraph().getRuleTimes().get(index).addTime(Benchmark.startPoint() - startTime);
0602   }
0603 
0604   /**
0605    * Return the annotation with the right-most end node
0606    */
0607   protected Annotation getRightMostAnnotation(Collection<Annotation> annots) {
0608     long maxOffset = -1;
0609     Annotation retVal = null;
0610     for(Annotation annot : annots) {
0611       Long curOffset = annot.getEndNode().getOffset();
0612       if(curOffset > maxOffset) {
0613         maxOffset = curOffset;
0614         retVal = annot;
0615       }
0616     }
0617 
0618     return retVal;
0619   }
0620 
0621   /**
0622    * Computes all tuples (x1, x2, ..., xn) resulting from the linear
0623    * combination of the elements of n lists, where x1 comes from the 1st
0624    * list, x2 comes from the second, etc. This method works recursively.
0625    * The first call should have those parameters:
0626    *
0627    @param sourceLists an array of n lists whose elements will be
0628    *          combined
0629    @param maxTupleSize the number of elements per tuple
0630    @param incompleteTuple an empty list
0631    */
0632 
0633   private static List<List<Annotation>> combine(List<List<Annotation>> sourceLists,
0634           int maxTupleSize, List<Annotation> incompleteTuple) {
0635 
0636     List<List<Annotation>> newTupleList = new LinkedList<List<Annotation>>();
0637 
0638     if(incompleteTuple.size() == maxTupleSize) {
0639       newTupleList.add(incompleteTuple);
0640     }
0641     else {
0642       List<Annotation> currentSourceList = sourceLists.get(incompleteTuple.size());
0643       // use for loop instead of ListIterator to increase speed
0644       // (critical here)
0645       for(int i = 0; i < currentSourceList.size(); i++) {
0646         List<Annotation> augmentedTuple = new LinkedList<Annotation>(incompleteTuple);
0647         augmentedTuple.add(currentSourceList.get(i));
0648         newTupleList.addAll(combine(sourceLists, maxTupleSize, augmentedTuple));
0649       }
0650     }
0651 
0652     return newTupleList;
0653   }
0654 
0655   /**
0656    * Fire the rule that matched.
0657    *
0658    @return true if processing should keep going, false otherwise.
0659    */
0660 
0661   @SuppressWarnings("unchecked")
0662   protected boolean fireRule(List<FSMInstance> acceptingFSMInstances,
0663           SearchState state, long lastNodeOff, SimpleSortedSet offsets,
0664           AnnotationSet inputAS, AnnotationSet outputAS, Document doc,
0665           SimpleSortedSet annotationsByOffsetthrows JapeException,
0666           ExecutionException {
0667 
0668     Node startNode = state.startNode;
0669     long startNodeOff = state.startNodeOff;
0670     long oldStartNodeOff = state.oldStartNodeOff;
0671 
0672     // FIRE THE RULE
0673     long lastAGPosition = -1;
0674     if(acceptingFSMInstances.isEmpty()) {
0675       // no rule to fire, advance to the next input offset
0676       lastAGPosition = startNodeOff + 1;
0677     }
0678     else if(ruleApplicationStyle == BRILL_STYLE
0679             || ruleApplicationStyle == ALL_STYLE) {
0680       // fire the rules corresponding to all accepting FSM instances
0681       Iterator<FSMInstance> accFSMIter = acceptingFSMInstances.iterator();
0682       FSMInstance currentAcceptor;
0683       RightHandSide currentRHS;
0684       lastAGPosition = startNode.getOffset().longValue();
0685 
0686       while(accFSMIter.hasNext()) {
0687         currentAcceptor = accFSMIter.next();
0688         currentRHS = currentAcceptor.getFSMPosition().getAction();
0689 
0690         currentRHS.transduce(doc, currentAcceptor.getBindings(), inputAS,
0691                 outputAS, ontology, actionContext);
0692 
0693         if(ruleApplicationStyle == BRILL_STYLE) {
0694           // find the maximal next position
0695           long currentAGPosition = currentAcceptor.getAGPosition().getOffset()
0696                   .longValue();
0697           if(currentAGPosition > lastAGPosition)
0698             lastAGPosition = currentAGPosition;
0699         }
0700       }
0701       if(ruleApplicationStyle == ALL_STYLE) {
0702         // simply advance to next offset
0703         lastAGPosition = lastAGPosition + 1;
0704       }
0705 
0706     }
0707     else if(ruleApplicationStyle == APPELT_STYLE
0708             || ruleApplicationStyle == FIRST_STYLE
0709             || ruleApplicationStyle == ONCE_STYLE) {
0710 
0711       // AcceptingFSMInstances is an ordered structure:
0712       // just execute the longest (last) rule
0713       Collections.sort(acceptingFSMInstances, Collections.reverseOrder());
0714       Iterator<FSMInstance> accFSMIter = acceptingFSMInstances.iterator();
0715       FSMInstance currentAcceptor = accFSMIter.next();
0716       if(isDebugMode()) {
0717         // see if we have any conflicts
0718         Iterator<FSMInstance> accIter = acceptingFSMInstances.iterator();
0719         FSMInstance anAcceptor;
0720         List<FSMInstance> conflicts = new ArrayList<FSMInstance>();
0721         while(accIter.hasNext()) {
0722           anAcceptor = accIter.next();
0723           if(anAcceptor.equals(currentAcceptor)) {
0724             conflicts.add(anAcceptor);
0725           }
0726           else {
0727             break;
0728           }
0729         }
0730         if(conflicts.size() 1) {
0731           log.info("Conflicts found during matching:"
0732                   "\n================================");
0733           accIter = conflicts.iterator();
0734           int i = 0;
0735           while(accIter.hasNext()) {
0736             if (log.isInfoEnabled())
0737               log.info(i++ + ") " + accIter.next().toString());
0738           }
0739         }
0740       }
0741       RightHandSide currentRHS = currentAcceptor.getFSMPosition().getAction();
0742 
0743       currentRHS.transduce(doc, currentAcceptor.getBindings(), inputAS,
0744               outputAS, ontology, actionContext);
0745 
0746       // if in matchGroup mode check other possible patterns in this
0747       // span
0748       if(isMatchGroupMode()) {
0749         // log.debug("Jape grammar in MULTI application style.");
0750         // ~bp: check for other matching fsm instances with same length,
0751         // priority and rule index : if such execute them also.
0752         String currentAcceptorString = null;
0753         multiModeWhile: while(accFSMIter.hasNext()) {
0754           FSMInstance rivalAcceptor = accFSMIter.next();
0755           // get rivals that match the same document segment
0756           // makes use of the semantic difference between the compareTo
0757           // and equals methods on FSMInstance
0758           if(rivalAcceptor.compareTo(currentAcceptor== 0) {
0759             // gets the rivals that are NOT COMPLETELY IDENTICAL with
0760             // the current acceptor.
0761             if(!rivalAcceptor.equals(currentAcceptor)) {
0762               //depends on the debug option in the transducer
0763               if(isDebugMode()) {
0764                 if(currentAcceptorString == null) {
0765                   // first rival
0766                   currentAcceptorString = currentAcceptor.toString();
0767                   if (log.isInfoEnabled()) {
0768                     log.info("~Jape Grammar Transducer : "
0769                             "\nConcurrent Patterns by length,priority and index (all transduced):");
0770                     log.info(currentAcceptorString);
0771                     log.info("bindings : " + currentAcceptor.getBindings());
0772                     log.info("Rivals Follow: ");
0773                   }
0774                 }
0775                 if (log.isInfoEnabled()) {
0776                   log.info(rivalAcceptor);
0777                   log.info("bindings : " + rivalAcceptor.getBindings());
0778                 }
0779               }// DEBUG
0780               currentRHS = rivalAcceptor.getFSMPosition().getAction();
0781 
0782               currentRHS.transduce(doc, rivalAcceptor.getBindings(), inputAS,
0783                       outputAS, ontology, actionContext);
0784 
0785             // equal rival
0786           }
0787           else {
0788             // if rival is not equal this means that there are no
0789             // further
0790             // equal rivals (since the list is sorted)
0791             break multiModeWhile;
0792           }
0793         // while there are fsm instances
0794       // matchGroupMode
0795 
0796       // if in ONCE mode stop after first match
0797       if(ruleApplicationStyle == ONCE_STYLE) {
0798         state.startNodeOff = startNodeOff;
0799         return false;
0800       }
0801 
0802       // advance in AG
0803       lastAGPosition = currentAcceptor.getAGPosition().getOffset().longValue();
0804     }
0805     else throw new RuntimeException("Unknown rule application style!");
0806 
0807     // advance on input
0808     SimpleSortedSet offsetsTailSet = offsets.tailSet(lastAGPosition);
0809     long theFirst = offsetsTailSet.first();
0810     if(theFirst < 0) {
0811       // no more input, phew! :)
0812       startNodeOff = -1;
0813       fireProcessFinished();
0814     else {
0815       long nextKey = theFirst;
0816       startNode = ((List<Annotation>)annotationsByOffset.get(nextKey))
0817               .get(0)// nextKey
0818               getStartNode();
0819       startNodeOff = startNode.getOffset().longValue();
0820 
0821       // eliminate the possibility for infinite looping
0822       if(oldStartNodeOff == startNodeOff) {
0823         // we are about to step twice in the same place, ...skip ahead
0824         lastAGPosition = startNodeOff + 1;
0825         offsetsTailSet = offsets.tailSet(lastAGPosition);
0826         theFirst = offsetsTailSet.first();
0827         if(theFirst < 0) {
0828           // no more input, phew! :)
0829           startNodeOff = -1;
0830           fireProcessFinished();
0831         }
0832         else {
0833           nextKey = theFirst;
0834           startNode = ((List<Annotation>)annotationsByOffset.get(theFirst))
0835                   .get(0).getStartNode();
0836           startNodeOff = startNode.getOffset().longValue();
0837         }
0838       }// if(oldStartNodeOff == startNodeOff)
0839       // fire the progress event
0840       if(startNodeOff - oldStartNodeOff > 256) {
0841         if(isInterrupted())
0842           throw new ExecutionInterruptedException("The execution of the \""
0843                   + getName()
0844                   "\" Jape transducer has been abruptly interrupted!");
0845 
0846         fireProgressChanged((int)(100 * startNodeOff / lastNodeOff));
0847         oldStartNodeOff = startNodeOff;
0848       }
0849     }
0850     // by Shafirin Andrey start (according to Vladimir Karasev)
0851     // if(gate.Gate.isEnableJapeDebug()) {
0852     // if (null != phaseController) {
0853     // phaseController.TraceTransit(rulesTrace);
0854     // }
0855     // }
0856     // by Shafirin Andrey end
0857 
0858     state.oldStartNodeOff = oldStartNodeOff;
0859     state.startNodeOff = startNodeOff;
0860     state.startNode = startNode;
0861     return true;
0862   // fireRule
0863 
0864   /** Clean up (delete action class files, for e.g.). */
0865   @Override
0866   public void cleanUp() {
0867     // for(DListIterator i = rules.begin(); ! i.atEnd(); i.advance())
0868     // ((Rule) i.get()).cleanUp();
0869   // cleanUp
0870 
0871   /** A string representation of this object. */
0872   @Override
0873   public String toString() {
0874     return toString("");
0875   // toString()
0876 
0877   /** A string representation of this object. */
0878   @Override
0879   public String toString(String pad) {
0880     String newline = Strings.getNl();
0881     String newPad = Strings.addPadding(pad, INDENT_PADDING);
0882 
0883     StringBuffer buf = new StringBuffer(pad + "SPT: name(" + name
0884             "); ruleApplicationStyle(");
0885 
0886     switch(ruleApplicationStyle) {
0887       case APPELT_STYLE:
0888         buf.append("APPELT_STYLE); ");
0889         break;
0890       case BRILL_STYLE:
0891         buf.append("BRILL_STYLE); ");
0892         break;
0893       default:
0894         break;
0895     }
0896 
0897     buf.append("rules(" + newline);
0898     if(rules != null) {
0899       Iterator<Rule> rulesIterator = rules.iterator();
0900         while(rulesIterator.hasNext())
0901           buf.append(rulesIterator.next().toString(newPad" ");
0902     }
0903     buf.append(newline + pad + ")." + newline);
0904 
0905     return buf.toString();
0906   // toString(pad)
0907 
0908   // needed by fsm
0909   public PrioritisedRuleList getRules() {
0910     return rules;
0911   }
0912 
0913   /**
0914    * Adds a new type of input annotations used by this transducer. If
0915    * the list of input types is empty this transducer will parse all the
0916    * annotations in the document otherwise the types not found in the
0917    * input list will be completely ignored! To be used with caution!
0918    */
0919   public void addInput(String ident) {
0920     input.add(ident);
0921   }
0922 
0923   /**
0924    * Checks if this Phase has the annotation type as input. This is the
0925    * case if either no input annotation types were specified, in which case
0926    * all annotation types will be used, or if the annotation type was
0927    * specified.
0928    *
0929    @param ident the type of an annotation to be checked
0930    @return true if the annotation type will be used in this phase
0931    */
0932   public boolean hasInput(String ident) {
0933     return input.isEmpty() || input.contains(ident);
0934   }
0935 
0936   /**
0937    * Check if there is a restriction on the input annotation types
0938    * for this SPT, i.e. if there were annotation types specified for
0939    * the "Input:" declaration of this phase.
0940    *
0941    @return true if only certain annotation types are considered in this
0942    *   phase, false if all are considered.
0943    */
0944   public boolean isInputRestricted() {
0945     return !input.isEmpty();
0946   }
0947 
0948   @Override
0949   public synchronized void removeProgressListener(ProgressListener l) {
0950     if(progressListeners != null && progressListeners.contains(l)) {
0951       @SuppressWarnings("unchecked")
0952       Vector<ProgressListener> v = (Vector<ProgressListener>)progressListeners.clone();
0953       v.removeElement(l);
0954       progressListeners = v;
0955     }
0956   }
0957 
0958   @Override
0959   public synchronized void addProgressListener(ProgressListener l) {
0960     @SuppressWarnings("unchecked")
0961     Vector<ProgressListener> v = progressListeners == null
0962             new Vector<ProgressListener>(2)
0963             (Vector<ProgressListener>)progressListeners.clone();
0964     if(!v.contains(l)) {
0965       v.addElement(l);
0966       progressListeners = v;
0967     }
0968   }
0969 
0970   /**
0971    * Defines the types of input annotations that this transducer reads.
0972    * If this set is empty the transducer will read all the annotations
0973    * otherwise it will only "see" the annotations of types found in this
0974    * list ignoring all other types of annotations.
0975    */
0976   // by Shafirin Andrey start (modifier changed to public)
0977   public Set<String> input = new HashSet<String>();
0978 
0979   // java.util.Set input = new java.util.HashSet();
0980   // by Shafirin Andrey end
0981   private transient Vector<ProgressListener> progressListeners;
0982 
0983   @Override
0984   protected void fireProgressChanged(int e) {
0985     if(progressListeners != null) {
0986       Vector<ProgressListener> listeners = progressListeners;
0987       int count = listeners.size();
0988       for(int i = 0; i < count; i++) {
0989         listeners.elementAt(i).progressChanged(e);
0990       }
0991     }
0992   }
0993 
0994   @Override
0995   protected void fireProcessFinished() {
0996     if(progressListeners != null) {
0997       Vector<ProgressListener> listeners = progressListeners;
0998       int count = listeners.size();
0999       for(int i = 0; i < count; i++) {
1000         listeners.elementAt(i).processFinished();
1001       }
1002     }
1003   }
1004 
1005   public int getRuleApplicationStyle() {
1006     return ruleApplicationStyle;
1007   }
1008 
1009   private transient SourceInfo sourceInfo = null;
1010   private String controllerStartedEventBlock = "";
1011   private String controllerFinishedEventBlock = "";
1012   private String controllerAbortedEventBlock = "";
1013   private String javaImportsBlock = "";
1014   private Object controllerEventBlocksActionClass = null;
1015   private String controllerEventBlocksActionClassName;
1016   private static final String nl = Strings.getNl();
1017   private static final String controllerEventBlocksActionClassSourceTemplate =
1018     "package %%packagename%%;"+nl+
1019     "import java.io.*;"+nl+
1020     "import java.util.*;"+nl+
1021     "import gate.*;"+nl+
1022     "import gate.jape.*;"+nl+
1023     "import gate.creole.ontology.*;"+nl+
1024     "import gate.annotation.*;"+nl+
1025     "import gate.util.*;"+nl+
1026     "%%javaimports%%"+nl+nl+
1027     "public class %%classname%% implements ControllerEventBlocksAction {"+nl+
1028     "  private Ontology ontology;"+nl+
1029     "  public void setOntology(Ontology o) { ontology = o; }"+nl+
1030     "  public Ontology getOntology() { return ontology; }"+nl+
1031     "  private ActionContext ctx;"+nl+
1032     "  public void setActionContext(ActionContext ac) { ctx = ac; }"+nl+
1033     "  public ActionContext getActionContext() { return ctx; }"+nl+
1034     "  private Controller controller;"+nl+
1035     "  public void setController(Controller c) { controller = c; }"+nl+
1036     "  public Controller getController() { return controller; }"+nl+
1037     "  private Corpus corpus;"+nl+
1038     "  public void setCorpus(Corpus c) { corpus = c; }"+nl+
1039     "  public Corpus getCorpus() { return corpus; }"+nl+
1040     "  private Throwable throwable;"+nl+
1041     "  public void setThrowable(Throwable t) { throwable = t; }"+nl+
1042     "  public Throwable getThrowable() { return throwable; }"+nl+
1043     "  public void controllerExecutionStarted() {"+nl+
1044     "    %%started%%"+nl+
1045     "  }"+nl+
1046     "  public void controllerExecutionFinished() {"+nl+
1047     "    %%finished%%"+nl+
1048     "  }"+nl+
1049     "  public void controllerExecutionAborted() {"+nl+
1050     "    %%aborted%%"+nl+
1051     "  }"+nl+
1052     "}"+nl+
1053     ""+nl;
1054   
1055   public void setControllerEventBlocks(
1056     String started,
1057     String finished,
1058     String aborted,
1059     String javaimports) {
1060     controllerStartedEventBlock = started;
1061     controllerFinishedEventBlock = finished;
1062     controllerAbortedEventBlock = aborted;
1063     javaImportsBlock = javaimports;
1064   }
1065   
1066   public String generateControllerEventBlocksCode(String packageName, String className) {
1067     String sourceCode = null;
1068     // if any of the three blocks is not null, set the corpusBlockActionClassSource
1069     // to the final source code of the class
1070     if(controllerStartedEventBlock != null || controllerFinishedEventBlock != null || controllerAbortedEventBlock != null) {
1071             
1072       sourceCode =
1073         controllerEventBlocksActionClassSourceTemplate;
1074 
1075       // if this method is called with a classname, use that (this happens 
1076       // when we are called from gate.jape.plus.SPTBuilder.buildSPT
1077       // If we get null or the empty string as a classname, generate our own
1078       String  ceb_classname = className;
1079       if(className == null || className.isEmpty()) {
1080         boolean neednewclassname = true;
1081         while(neednewclassname) {
1082           ceb_classname =  "ControllerEventBlocksActionClass" +
1083             actionClassNumber.getAndIncrement();
1084           controllerEventBlocksActionClassName = packageName + "." + ceb_classname;
1085           try {
1086             Gate.getClassLoader().loadClass(controllerEventBlocksActionClassName);
1087             neednewclassname = true;
1088           catch (ClassNotFoundException e) {
1089             neednewclassname = false;
1090           }
1091         }
1092       
1093       sourceCode = sourceCode
1094         .replace("%%classname%%",ceb_classname)
1095         .replace("%%packagename%%", packageName);
1096         
1097       
1098       sourceInfo = new SourceInfo(controllerEventBlocksActionClassName,name,"controllerEvents");
1099       
1100       sourceCode =
1101         sourceCode.replace("%%javaimports%%",
1102           javaImportsBlock != null ? javaImportsBlock : "// no 'Imports:' block for more imports defined");
1103       
1104       int index = sourceCode.indexOf("%%started%%");
1105       String previousCode = sourceCode.substring(0, index).trim();
1106       sourceCode =
1107         sourceCode.replace("%%started%%",
1108           controllerStartedEventBlock != null ? sourceInfo.addBlock(previousCode, controllerStartedEventBlock"// no code defined");
1109       
1110       index = sourceCode.indexOf("%%finished%%");
1111       previousCode = sourceCode.substring(0, index).trim();
1112       sourceCode =
1113         sourceCode.replace("%%finished%%",
1114           controllerFinishedEventBlock != null ? sourceInfo.addBlock(previousCode, controllerFinishedEventBlock"// no code defined");
1115       
1116       index = sourceCode.indexOf("%%aborted%%");
1117       previousCode = sourceCode.substring(0, index).trim();
1118       sourceCode =
1119         sourceCode.replace("%%aborted%%",
1120           controllerAbortedEventBlock != null ? sourceInfo.addBlock(previousCode, controllerAbortedEventBlock"// no code defined");
1121     }
1122     return sourceCode;
1123   }
1124 
1125   @Override
1126   public void runControllerExecutionStartedBlock(
1127     ActionContext ac, Controller c, Ontology othrows ExecutionException {
1128     if(controllerEventBlocksActionClass != null) {
1129       ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1130         setController(c);
1131       ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1132         setOntology(o);
1133       ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1134         setActionContext(ac);
1135       if(instanceof CorpusController) {
1136         Corpus corpus = ((CorpusController)c).getCorpus();
1137          ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1138           setCorpus(corpus);
1139       else {
1140         ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1141           setCorpus(null);
1142       }
1143       ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1144         setThrowable(null);
1145       
1146       try {
1147         ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1148           controllerExecutionStarted();
1149       }
1150       catch (Throwable e) {
1151       // if the action class throws an exception, re-throw it with a
1152       // full description of the problem, inc. stack trace and the RHS
1153       // action class code
1154       if (sourceInfo != nullsourceInfo.enhanceTheThrowable(e);
1155       
1156       if(instanceof Error) {
1157         throw (Error)e;
1158       }   
1159       if(instanceof RuntimeException) {
1160         throw (RuntimeException)e;
1161       }
1162       
1163       // shouldn't happen...
1164       throw new ExecutionException(
1165           "Couldn't run controller started action", e);
1166       }
1167     }
1168   }
1169 
1170   @Override
1171   public void runControllerExecutionFinishedBlock(
1172     ActionContext ac, Controller c, Ontology othrows ExecutionException {
1173     if(controllerEventBlocksActionClass != null) {
1174       ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1175         setController(c);
1176       ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1177         setOntology(o);
1178       ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1179         setActionContext(ac);
1180       if(instanceof CorpusController) {
1181         Corpus corpus = ((CorpusController)c).getCorpus();
1182          ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1183           setCorpus(corpus);
1184       else {
1185         ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1186           setCorpus(null);
1187       }
1188       ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1189         setThrowable(null);
1190       
1191       try {
1192         ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1193           controllerExecutionFinished();
1194       }
1195       catch (Throwable e) {
1196       // if the action class throws an exception, re-throw it with a
1197       // full description of the problem, inc. stack trace and the RHS
1198       // action class code
1199       if (sourceInfo != nullsourceInfo.enhanceTheThrowable(e);
1200       
1201       if(instanceof Error) {
1202         throw (Error)e;
1203       }   
1204       if(instanceof RuntimeException) {
1205         throw (RuntimeException)e;
1206       }
1207       
1208       // shouldn't happen...
1209       throw new ExecutionException(
1210           "Couldn't run controller finished action", e);
1211       }
1212     }
1213   }
1214 
1215   @Override
1216   public void runControllerExecutionAbortedBlock(
1217     ActionContext ac, Controller c, Throwable t, Ontology othrows ExecutionException {
1218     if(controllerEventBlocksActionClass != null) {
1219       ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1220         setController(c);
1221       ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1222         setOntology(o);
1223       ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1224         setActionContext(ac);
1225       if(instanceof CorpusController) {
1226         Corpus corpus = ((CorpusController)c).getCorpus();
1227          ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1228           setCorpus(corpus);
1229       else {
1230         ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1231           setCorpus(null);
1232       }
1233       ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1234         setThrowable(t);
1235       
1236       try {
1237         ((ControllerEventBlocksActioncontrollerEventBlocksActionClass).
1238           controllerExecutionAborted();
1239       }
1240       catch (Throwable e) {
1241       // if the action class throws an exception, re-throw it with a
1242       // full description of the problem, inc. stack trace and the RHS
1243       // action class code
1244       if (sourceInfo != nullsourceInfo.enhanceTheThrowable(e);
1245       
1246       if(instanceof Error) {
1247         throw (Error)e;
1248       }   
1249       if(instanceof RuntimeException) {
1250         throw (RuntimeException)e;
1251       }
1252       
1253       // shouldn't happen...
1254       throw new ExecutionException(
1255           "Couldn't run controller aborted action", e);
1256       }
1257     }
1258   }
1259 
1260   private void writeObject(java.io.ObjectOutputStream out)
1261   throws IOException{
1262     Object save = controllerEventBlocksActionClass;
1263     controllerEventBlocksActionClass = null;
1264     out.defaultWriteObject();
1265     controllerEventBlocksActionClass = save;
1266   }
1267 
1268   private void readObject(java.io.ObjectInputStream in)
1269   throws IOException, ClassNotFoundException{
1270     in.defaultReadObject();
1271     compileEventBlocksActionClass(Gate.getClassLoader());
1272   }
1273 
1274   private void compileEventBlocksActionClass(GateClassLoader classloader) {
1275     String sourceCode = generateControllerEventBlocksCode("japeactionclasses","");
1276     if(sourceCode != null) {
1277       Map<String,String> actionClasses = new HashMap<String,String>(1);
1278       actionClasses.put(controllerEventBlocksActionClassName,
1279               sourceCode);
1280       try {
1281           gate.util.Javac.loadClasses(actionClasses, classloader);
1282           controllerEventBlocksActionClass =
1283             classloader.
1284               loadClass(controllerEventBlocksActionClassName).newInstance();
1285         }catch(Exception e1){
1286           throw new GateRuntimeException (e1);
1287         }
1288     }
1289   }
1290 
1291   /**
1292    * This returns any compiled controller event blocks action class that
1293    * may exist at the time of calling or null. This is mainly needed for
1294    * alternate implementations of JAPE that are based on the core JAPE
1295    * classes and want to support controller event blocks too.
1296    *
1297    @return an object that represents the compiled event blocks or null
1298    */
1299   public ControllerEventBlocksAction getControllerEventBlocksActionClass() {
1300     return (ControllerEventBlocksAction)controllerEventBlocksActionClass;
1301   }
1302   
1303   /*
1304    * private void writeObject(ObjectOutputStream oos) throws IOException {
1305    * Out.prln("writing spt"); oos.defaultWriteObject();
1306    * Out.prln("finished writing spt"); } // writeObject
1307    */
1308 
1309 // class SinglePhaseTransducer
1310 
1311 /*
1312  * class SimpleSortedSet {
1313  *
1314  * static final int INCREMENT = 1023; int[] theArray = new
1315  * int[INCREMENT]; Object[] theObject = new Object[INCREMENT]; int
1316  * tsindex = 0; int size = 0; public static int avesize = 0; public
1317  * static int maxsize = 0; public static int avecount = 0; public
1318  * SimpleSortedSet() { avecount++; java.util.Arrays.fill(theArray,
1319  * Integer.MAX_VALUE); }
1320  *
1321  * public Object get(int elValue) { int index =
1322  * java.util.Arrays.binarySearch(theArray, elValue); if (index >=0)
1323  * return theObject[index]; return null; }
1324  *
1325  * public boolean add(int elValue, Object o) { int index =
1326  * java.util.Arrays.binarySearch(theArray, elValue); if (index >=0) {
1327  * ((ArrayList)theObject[index]).add(o); return false; } if (size ==
1328  * theArray.length) { int[] temp = new int[theArray.length + INCREMENT];
1329  * Object[] tempO = new Object[theArray.length + INCREMENT];
1330  * System.arraycopy(theArray, 0, temp, 0, theArray.length);
1331  * System.arraycopy(theObject, 0, tempO, 0, theArray.length);
1332  * java.util.Arrays.fill(temp, theArray.length, temp.length ,
1333  * Integer.MAX_VALUE); theArray = temp; theObject = tempO; } index =
1334  * ~index; System.arraycopy(theArray, index, theArray, index+1, size -
1335  * index ); System.arraycopy(theObject, index, theObject, index+1, size -
1336  * index ); theArray[index] = elValue; theObject[index] = new
1337  * ArrayList(); ((ArrayList)theObject[index]).add(o); size++; return
1338  * true; } public int first() { if (tsindex >= size) return -1; return
1339  * theArray[tsindex]; }
1340  *
1341  * public Object getFirst() { if (tsindex >= size) return null; return
1342  * theObject[tsindex]; }
1343  *
1344  * public SimpleSortedSet tailSet(int elValue) { if (tsindex <
1345  * theArray.length && elValue != theArray[tsindex]) { if (tsindex<(size-1) &&
1346  * elValue > theArray[tsindex] && elValue <= theArray[tsindex+1]) {
1347  * tsindex++; return this; } int index =
1348  * java.util.Arrays.binarySearch(theArray, elValue); if (index < 0)
1349  * index = ~index; tsindex = index; } return this; }
1350  *
1351  * public boolean isEmpty() { return size ==0; } };
1352  */