Log in Help
Print
Homereleasesgate-8.4-build5748-ALLpluginsJAPE_Plussrcgatejapeplus 〉 Transducer.java
 
/*
 *  Tansducer.java
 *  
 *  Copyright (c) 2009 - 2011, Valentin Tablan.
 *
 *  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, 17 Aug 2009
 *
 *  $Id$
 */
package gate.jape.plus;

import gate.Annotation;
import gate.AnnotationSet;
import gate.Controller;
import gate.Factory;
import gate.Gate;
import gate.LanguageAnalyser;
import gate.Resource;
import gate.creole.AbstractLanguageAnalyser;
import gate.creole.ControllerAwarePR;
import gate.creole.ExecutionException;
import gate.creole.ResourceInstantiationException;
import gate.creole.metadata.CreoleParameter;
import gate.creole.metadata.CreoleResource;
import gate.creole.metadata.Optional;
import gate.creole.metadata.RunTime;
import gate.creole.metadata.Sharable;
import gate.creole.ontology.Ontology;
import gate.event.AnnotationSetEvent;
import gate.event.AnnotationSetListener;
import gate.event.ProgressListener;
import gate.event.StatusListener;
import gate.gui.ActionsPublisher;
import gate.gui.MainFrame;
import gate.jape.ControllerEventBlocksAction;
import gate.jape.DefaultActionContext;
import gate.jape.MultiPhaseTransducer;
import gate.jape.Rule;
import gate.jape.SinglePhaseTransducer;
import gate.jape.constraint.AnnotationAccessor;
import gate.jape.constraint.ConstraintPredicate;
import gate.jape.parser.ParseCpsl;
import gate.jape.parser.ParseException;
import gate.util.Err;
import gate.util.GateClassLoader;
import gate.util.GateException;
import gate.util.Javac;
import gate.util.persistence.PersistenceManager;

import java.awt.event.ActionEvent;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

import org.apache.log4j.Logger;

import com.ontotext.jape.pda.FSMPDA;


/**
 * A JAPE-Plus transducer (with a {@link LanguageAnalyser} interface.
 */
@CreoleResource(name = "JAPE-Plus Transducer", 
    comment = "An optimised, JAPE-compatible transducer.", 
    icon="JapePlus")
@SuppressWarnings("serial")
public class Transducer extends AbstractLanguageAnalyser 
    implements ControllerAwarePR, ProgressListener , ActionsPublisher {

  private static final long serialVersionUID = 4194243737624821476L;

  private static final Logger log = Logger.getLogger(Transducer.class);
  
  private static final boolean DEBUG_DUPLICATION = false;
  
  /**
   * A comparator for annotations based on start offset and inverse length.
   */
  protected class AnnotationComparator implements Comparator<Annotation>{

    public int compare(Annotation a0, Annotation a1) {
      long start0 = a0.getStartNode().getOffset();
      long start1 = a1.getStartNode().getOffset();
      if(start0 < start1) {
        return -1;
      } else if(start0 > start1) {
        return 1;
      } else {
        long end0 = a0.getEndNode().getOffset();
        long end1 = a1.getEndNode().getOffset();
        if(end0 > end1) {
          return -1;
        } else if(end0 < end1) { return 1; }
        return 0;
      }
    }
    
  }
  
  
  /**
   * A listener for the input annotation set, which invalidates the pre-built
   * lists of sorted annotation when they change due to the execution of one of
   * the phases.
   */
  protected class AnnSetListener implements AnnotationSetListener{

    /* (non-Javadoc)
     * @see gate.event.AnnotationSetListener#annotationAdded(gate.event.AnnotationSetEvent)
     */
    @Override
    public void annotationAdded(AnnotationSetEvent e) {
      changedTypes.add(e.getAnnotation().getType());
    }

    /* (non-Javadoc)
     * @see gate.event.AnnotationSetListener#annotationRemoved(gate.event.AnnotationSetEvent)
     */
    @Override
    public void annotationRemoved(AnnotationSetEvent e) {
      changedTypes.add(e.getAnnotation().getType());
    }
  }
  
  
  protected class SerialiseTransducerAction extends AbstractAction {
    public SerialiseTransducerAction() {
      super("Save as binary file");
      putValue(SHORT_DESCRIPTION, 
          "Save this JAPE Plus Transducer as a binary grammar file");
    }

    public void actionPerformed(java.awt.event.ActionEvent evt) {
      Runnable runnable = new Runnable() {
        public void run() {
          JFileChooser fileChooser = MainFrame.getFileChooser();
          fileChooser.setFileFilter(fileChooser.getAcceptAllFileFilter());
          fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
          fileChooser.setMultiSelectionEnabled(false);
          if(fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
            File file = fileChooser.getSelectedFile();
            ObjectOutputStream out = null;
            try {
              MainFrame.lockGUI("Saving binary JAPE Plus Transducer...");
              out = new ObjectOutputStream(
                  new GZIPOutputStream(
                  new BufferedOutputStream(
                  new FileOutputStream(file))));
              out.writeObject(singlePhaseTransducersData);
            } catch(IOException ioe) {
              JOptionPane.showMessageDialog(MainFrame.getInstance(), 
                  "Error!\n" + ioe.toString(), "GATE", 
                  JOptionPane.ERROR_MESSAGE);
              ioe.printStackTrace(Err.getPrintWriter());
            } finally {
              if(out != null) {
                try {
                  out.flush();
                  out.close();
                } catch(IOException e) {
                  log.error("Exception while closing output stream.", e);
                }
              }
              MainFrame.unlockGUI();
            }
          }
        }
      };
      Thread thread = new Thread(runnable, "JAPE Plus binary save thread");
      thread.setPriority(Thread.MIN_PRIORITY);
      thread.start();
    }
  }
  
  /**
   * Modified version of SPT that produces a different type of FSM data. This is
   * used during the parsing of JAPE code.
   */
  protected static class SinglePhaseTransducerPDA extends SinglePhaseTransducer {
    public SinglePhaseTransducerPDA(String name) {
      super(name);
    }

    @Override
    protected FSMPDA createFSM() {
      return new FSMPDA(this);
    }
  }
  
  /**
   * Class used for storing binary representations of JAPE Plus transducers.
   */
  public static class SPTData implements Serializable  {
    
    private static final long serialVersionUID = -3255640456555757114L;

    private String lhsSourceCode;
    
    private String controllerEventsSourceCode;
    
    private String className;
    
    private Rule[] rules;
    
    private Predicate[][] predicatesByType;
    
    private Set<String> inputTypes;
    

    public SPTData(String className, String lhsSourceCode,
        String controllerEventsSourceCode, Rule[] rules,
        Predicate[][] predicatesByType, Set<String> inputTypes) {
      super();
      this.className = className;
      this.lhsSourceCode = lhsSourceCode;
      this.controllerEventsSourceCode = controllerEventsSourceCode;
      this.rules = rules;
      this.predicatesByType = predicatesByType;
      this.inputTypes = inputTypes;
    }
    
    /**
     * Copy constructor used when a Transducer is duplicated, to
     * set up the singlePhaseTransducersData array with the same Rule
     * instances that the duplicated SPTBases are using.
     * @param that the SPTData we are duplicating
     * @param duplicatedRules the Rule[] from the corresponding SPTBase.
     */
    SPTData(SPTData that, Rule[] duplicatedRules) {
      this(that.className, that.lhsSourceCode, that.controllerEventsSourceCode,
          duplicatedRules, that.predicatesByType, that.inputTypes);
    }
    
    @SuppressWarnings("unchecked")
    public SPTBase generateSpt(GateClassLoader classLoader) throws ResourceInstantiationException {
      
      SPTBase optimisedTransducer = null;
      
      try {
        Map<String, String> classes = new HashMap<String, String>(1);
        
        classes.put(className, lhsSourceCode.toString());  
        String ceabClassName = className + "CEAB"; 
        if(controllerEventsSourceCode != null) {
          classes.put(ceabClassName, controllerEventsSourceCode);  
        }
        // compile the class[es]
        if(!classes.isEmpty()) {
          Javac.loadClasses(classes, classLoader);
        }
        
        Class<? extends SPTBase> sptClass = (Class<? extends SPTBase>)
            classLoader.loadClass(className);
        
        Constructor<? extends SPTBase> sptConstructor = sptClass.getConstructor(
          Rule[].class, Predicate[][].class);
        optimisedTransducer = sptConstructor.newInstance(rules, predicatesByType);
        if(controllerEventsSourceCode != null) {
          // attach the events block class
          optimisedTransducer.setControllerEventBlocksAction(
              ((Class<? extends ControllerEventBlocksAction>) 
              classLoader.loadClass(ceabClassName)).newInstance());          
        }
      } catch(SecurityException e) {
        throw new ResourceInstantiationException(e);
      } catch(NoSuchMethodException e) {
        throw new ResourceInstantiationException(e);
      } catch(IllegalArgumentException e) {
        throw new ResourceInstantiationException(e);
      } catch(InstantiationException e) {
        throw new ResourceInstantiationException(e);
      } catch(IllegalAccessException e) {
        throw new ResourceInstantiationException(e);
      } catch(InvocationTargetException e) {
        throw new ResourceInstantiationException(e);
      } catch(GateException e) {
        throw new ResourceInstantiationException(e);
      } catch(ClassNotFoundException e) {
        throw new ResourceInstantiationException(e);
      }
      
      //input types
      optimisedTransducer.inputAnnotationTypes = 
          inputTypes == null || inputTypes.size() == 0 ? 
          null : new String[inputTypes.size()];
      if(optimisedTransducer.inputAnnotationTypes != null){
        optimisedTransducer.inputAnnotationTypes = inputTypes.toArray(
                optimisedTransducer.inputAnnotationTypes);
      }
      
      return optimisedTransducer;
    }
  }
  
  public URL getGrammarURL() {
    return grammarURL;
  }

  
  @CreoleParameter(
      comment="URL for the data from which this transducer should be built.",
      suffixes = ".jape",
      disjunction = "grammar",
      priority = 1)
  public void setGrammarURL(URL source) {
    this.grammarURL = source;
  }
  
  /**
   * The source from which this transducer is created.
   */
  protected URL grammarURL;
  
  /**
   * List of class names for any custom
   * {@link gate.jape.constraint.ConstraintPredicate}.
   */
  protected List<String> operators = null;
  
  /**
   * List of class names for any custom
   * {@link gate.jape.constraint.AnnotationAccessor}s.
   */
  protected List<String> annotationAccessors = null;
  
  protected Boolean enableDebugging;
  
  protected String encoding;
  
  protected String inputASName;
  
  protected String outputASName;

  protected DefaultActionContext actionContext;
  
  protected List<Action> actions;
  
  /**
   * The URL to the serialised jape file used as grammar by this transducer.
   */
  protected java.net.URL binaryGrammarURL;
  
  
  /**
   * Instance of {@link AnnotationComparator} used for sorting annots for the
   * phases.
   */
  protected AnnotationComparator annotationComparator;
  
  /**
   * The sets of annotations (of a given type) that have already been sorted.
   */
  protected Map<String, Annotation[]> sortedAnnotations;
  
  /**
   * A set of annotation types that were modified during the latest execution of
   * a pahse.
   */
  protected Set<String> changedTypes;
  
  /**
   * The listener that keeps track of the annotation types that have changed.
   */
  protected AnnotationSetListener inputASListener;
  
  protected SPTData[] singlePhaseTransducersData;
  
  /**
   * The list of phases used in this transducer.
   */
  protected transient SPTBase[] singlePhaseTransducers;
  
  /**
   * The classloader instance this Transducer will compile jape classes into
   */
  private transient GateClassLoader classLoader = null;
  
  /**
   * <em>This method is public only for the benefit of {@link Factory#duplicate}
   * and should not be called directly</em>
   */
  public GateClassLoader getClassLoader() {
    return classLoader;
  }

  /**
   * <em>This method is public only for the benefit of {@link Factory#duplicate}
   * and should not be called directly</em>
   */
  @Sharable
  public void setClassLoader(GateClassLoader classLoader) {
    this.classLoader = classLoader;
  }
  
  /**
   * Reference counter used when sharing the classLoader among duplicates of
   * this transducer.
   */
  private transient AtomicInteger classLoaderRefCount = new AtomicInteger(0);

  /**
   * <em>This method is public only for the benefit of {@link Factory#duplicate}
   * and should not be called directly</em>
   */
  public AtomicInteger getClassLoaderRefCount() {
    return classLoaderRefCount;
  }

  /**
   * <em>This method is public only for the benefit of {@link Factory#duplicate}
   * and should not be called directly</em>
   */
  @Sharable
  public void setClassLoaderRefCount(AtomicInteger classLoaderRefCount) {
    this.classLoaderRefCount = classLoaderRefCount;
  }

  /**
   * The index in {@link #singlePhaseTransducers} for the SPT currently being
   * executed, if any, -1 otherwise.
   */
  protected int currentSptIndex = -1;
  
  /**
   * Gets the list of class names for any custom boolean operators.
   * Classes must implement {@link gate.jape.constraint.ConstraintPredicate}.
   */
  public List<String> getOperators() {
    return operators;
  }

  /**
   * Sets the list of class names for any custom boolean operators.
   * Classes must implement {@link gate.jape.constraint.ConstraintPredicate}.
   */
  @Optional
  @CreoleParameter(
    comment = "Class names that implement gate.jape.constraint.ConstraintPredicate."
  )
  public void setOperators(List<String> operators) {
    this.operators = operators;
  }
  
  /**
   * Gets the list of class names for any custom
   * {@link gate.jape.constraint.AnnotationAccessor}s.
   */
  public List<String> getAnnotationAccessors() {
    return annotationAccessors;
  }
  
  @Override
  public List<Action> getActions() {
    return new ArrayList<Action>(actions);
  }

  /**
   * Sets the list of class names for any custom
   * {@link gate.jape.constraint.AnnotationAccessor}s.
   */
  @Optional
  @CreoleParameter(
    comment = "Class names that implement gate.jape.constraint.AnnotationAccessor."
  )
  public void setAnnotationAccessors(List<String> annotationAccessors) {
    this.annotationAccessors = annotationAccessors;
  }  
  
  public Boolean getEnableDebugging() {
    return enableDebugging;
  }

  @RunTime
  @CreoleParameter(defaultValue = "false")
  public void setEnableDebugging(Boolean enableDebugging) {
    this.enableDebugging = enableDebugging;
  }
  
  public String getEncoding() {
    return encoding;
  }

  @CreoleParameter(defaultValue="UTF-8", 
          comment="The encoding used for the input .jape files.")
  public void setEncoding(String encoding) {
    this.encoding = encoding;
  }
  
  private Transducer existingTransducer;
  

  /**
   * <em>This method is public only for the benefit of {@link Factory#duplicate}
   * and should not be called directly</em>
   */
  public Transducer getExistingTransducer() {
    if(existingTransducer == null) {
      return this;
    } else {
      return existingTransducer;
    }
  }

  /**
   * <em>This method is public only for the benefit of {@link Factory#duplicate}
   * and should not be called directly</em>
   */
  @Sharable
  public void setExistingTransducer(Transducer existingTransducer) {
    this.existingTransducer = existingTransducer;
  }


  /**
   * 
   */
  public Transducer() {
    sortedAnnotations = new HashMap<String, Annotation[]>();
    changedTypes = new HashSet<String>();
    inputASListener = new AnnSetListener();
    annotationComparator = new AnnotationComparator();
    
    actions = new ArrayList<Action>();
    actions.add(new SerialiseTransducerAction());
    if(DEBUG_DUPLICATION) {
      actions.add(new AbstractAction("Duplicate") {
  
        @Override
        public void actionPerformed(ActionEvent arg0) {
          try {
            Resource res = Factory.duplicate(Transducer.this);
            res.setName(getName() + "-copy");
          } catch(ResourceInstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
        }          
        
      });
    }
  }

  @Override
  public void reInit() throws ResourceInstantiationException {
    existingTransducer = null;
    
    if(classLoaderRefCount.decrementAndGet() == 0) {
      Gate.getClassLoader().forgetClassLoader(classLoader);
    }
    
    classLoaderRefCount = new AtomicInteger(0);
    classLoader = null;
    
    init();
  }
  
  @Override
  public Resource init() throws ResourceInstantiationException {
    try {
      super.init();
      initCustomConstraints();
      
      if(existingTransducer != null) {
        // we are duplicating
        // we are sharing the same class loader, increment its ref count
        classLoaderRefCount.incrementAndGet();
        
        this.singlePhaseTransducers = new SPTBase[existingTransducer.singlePhaseTransducers.length];
        this.singlePhaseTransducersData = new SPTData[existingTransducer.singlePhaseTransducersData.length];
        // spts and sptsData are guaranteed to be non-null and the same length
        for(int i = 0; i < this.singlePhaseTransducers.length; i++) {
          this.singlePhaseTransducers[i] = existingTransducer.singlePhaseTransducers[i].duplicate();
          this.singlePhaseTransducers[i].addProgressListener(this);
          // this duplicate's SPTData needs to refer to the same Rule[] as the
          // *duplicated* SPTBase, not the original one.
          this.singlePhaseTransducersData[i] = new SPTData(
              existingTransducer.singlePhaseTransducersData[i],
              this.singlePhaseTransducers[i].rules);
          
          for (Rule rule : this.singlePhaseTransducers[i].rules) {
            rule.getRHS().finish(classLoader);
          }
        }
      } else {
        // sanity check parameters
        if(binaryGrammarURL == null && grammarURL == null) {
          throw new ResourceInstantiationException(
              "Neither grammarURL or binaryGrammarURL parameters are set!");        
        }
        
        try {
          if(binaryGrammarURL != null){
            ObjectInputStream ois = new ObjectInputStream(
                new GZIPInputStream(
                new BufferedInputStream(binaryGrammarURL.openStream())));
            singlePhaseTransducersData = (SPTData[])ois.readObject();
            classLoader = Gate.getClassLoader().getDisposableClassLoader(ois.toString(),true);
            classLoaderRefCount.incrementAndGet();
          }else if(grammarURL != null) {
            classLoader = Gate.getClassLoader().getDisposableClassLoader(grammarURL.toExternalForm()+System.currentTimeMillis(),true);
            classLoaderRefCount.incrementAndGet();
            parseJape();  
          }
          
          singlePhaseTransducers = new SPTBase[singlePhaseTransducersData.length];
          for(int  i = 0; i < singlePhaseTransducersData.length; i++) {
            singlePhaseTransducers[i] = singlePhaseTransducersData[i].generateSpt(classLoader);
            singlePhaseTransducers[i].addProgressListener(this);  
          }      
        } catch(IOException e) {
          throw new ResourceInstantiationException(e);
        } catch(ParseException e) {
          throw new ResourceInstantiationException(e);
        } catch(ClassNotFoundException e) {
          throw new ResourceInstantiationException(e);
        }
      }
      actionContext = initActionContext();
      return this;
    } catch(Exception e) {
      if(classLoaderRefCount.decrementAndGet() <= 0) {
        Gate.getClassLoader().forgetClassLoader(classLoader);
      }
      if(e instanceof ResourceInstantiationException) {
        throw (ResourceInstantiationException)e;
      } else {
        throw (RuntimeException)e;
      }
    }
  }

  /**
   * Method that initialises the ActionContext. This method can be overridden
   * if somebody wants to extend the Transducer PR class and provide their own
   * subclass of DefaultActionContext to add some functionality.
   * 
   * @return a DefaultActionContext object
   */
  protected DefaultActionContext initActionContext() {
    return new DefaultActionContext();
  }
  
  
  /**
   * Loads any custom operators and annotation accessors into the ConstraintFactory.
   * @throws ResourceInstantiationException
   */
  protected void initCustomConstraints() throws ResourceInstantiationException {
    //Load operators
    if (operators != null) {
      for(String opName : operators) {
        Class<? extends ConstraintPredicate> clazz = null;
        try {
          clazz = Class.forName(opName, true, Gate.getClassLoader())
                        .asSubclass(ConstraintPredicate.class);
        }
        catch(ClassNotFoundException e) {
          //if couldn't find it that way, try with current thread class loader
          try {
            clazz = Class.forName(opName, true,
                Thread.currentThread().getContextClassLoader())
                  .asSubclass(ConstraintPredicate.class);
          }
          catch(ClassNotFoundException e1) {
            throw new ResourceInstantiationException("Cannot load class for operator: " + opName, e1);
          }
        }
        catch(ClassCastException cce) {
          throw new ResourceInstantiationException("Operator class '" + opName + "' must implement ConstraintPredicate");
        }

        //instantiate an instance of the class so can get the operator string
        try {
          ConstraintPredicate predicate = clazz.newInstance();
          String opSymbol = predicate.getOperator();
          //now store it in ConstraintFactory
          Factory.getConstraintFactory().addOperator(opSymbol, clazz);
        }
        catch(Exception e) {
          throw new ResourceInstantiationException("Cannot instantiate class for operator: " + opName, e);
        }
      }
    }

    //Load annotationAccessors
    if (annotationAccessors != null) {
      for(String accessorName : annotationAccessors) {
        Class<? extends AnnotationAccessor> clazz = null;
        try {
          clazz = Class.forName(accessorName, true, Gate.getClassLoader())
                     .asSubclass(AnnotationAccessor.class);
        }
        catch(ClassNotFoundException e) {
          //if couldn't find it that way, try with current thread class loader
          try {
            clazz = Class.forName(accessorName, true,
                Thread.currentThread().getContextClassLoader())
                   .asSubclass(AnnotationAccessor.class);
          }
          catch(ClassNotFoundException e1) {
            throw new ResourceInstantiationException("Cannot load class for accessor: " + accessorName, e1);
          }
        }
        catch(ClassCastException cce) {
          throw new ResourceInstantiationException("Operator class '" + accessorName + "' must implement AnnotationAccessor");
        }

        //instantiate an instance of the class so can get the meta-property name string
        try {
          AnnotationAccessor aa = clazz.newInstance();
          String accSymbol = (String)aa.getKey();
          //now store it in ConstraintFactory
          Factory.getConstraintFactory().addMetaProperty(accSymbol, clazz);
        }
        catch(Exception e) {
          throw new ResourceInstantiationException("Cannot instantiate class for accessor: " + accessorName, e);
        }

      }
    }
  }

  protected void parseJape() 
    throws IOException, ParseException, ResourceInstantiationException
  {		
    ParseCpsl parser = Factory.newJapeParser(grammarURL, encoding);
    parser.setSptClass(SinglePhaseTransducerPDA.class);

    StatusListener listener = new StatusListener(){
      public void statusChanged(String text){
        fireStatusChanged(text);
      }
    };
    parser.addStatusListener(listener);
    MultiPhaseTransducer intermediate =  parser.MultiPhaseTransducer();
    parser.removeStatusListener(listener);
    
    singlePhaseTransducersData = new SPTData[intermediate.getPhases().size()];
    SPTBuilder builder = new SPTBuilder();
    for(int i = 0; i < intermediate.getPhases().size(); i++){
      singlePhaseTransducersData[i] = builder.buildSPT(
          (SinglePhaseTransducer)intermediate.getPhases().get(i),classLoader);
    }
  }
  
  @Override
  public void cleanup() {
    super.cleanup();
    for(SPTBase aSpt : singlePhaseTransducers){
      aSpt.removeProgressListener(this);
      aSpt.cleanup();
    }
    if(classLoaderRefCount.decrementAndGet() == 0) {
      Gate.getClassLoader().forgetClassLoader(classLoader);
    }
  }
  
  @Override
  public void execute() throws ExecutionException {
  	if (singlePhaseTransducers == null) {
  		throw new IllegalStateException("init() was not called.");
  	}
  	interrupted = false;
    AnnotationSet inputAs = (inputASName == null || inputASName.length() == 0) ?
            document.getAnnotations() : document.getAnnotations(inputASName);
    fireProgressChanged(0);
    try {
      inputAs.addAnnotationSetListener(inputASListener);
      sortedAnnotations.clear();
      for(currentSptIndex = 0; currentSptIndex < singlePhaseTransducers.length; 
          currentSptIndex++){
        SPTBase aSpt = singlePhaseTransducers[currentSptIndex];
        changedTypes.clear();
        aSpt.setCorpus(corpus);
        aSpt.setDocument(document);
        aSpt.setInputASName(inputASName);
        aSpt.setOutputASName(outputASName);
        aSpt.setOwner(this);
        actionContext.setCorpus(corpus);
        actionContext.setPR(this);
        actionContext.setPRFeatures(features);
        aSpt.setActionContext(actionContext);
        aSpt.setOntology(ontology);
        aSpt.execute();
        aSpt.setCorpus(null);
        aSpt.setDocument(null);
        aSpt.setInputASName(null);
        aSpt.setOutputASName(null);
        aSpt.setOwner(null);
        for(String type : changedTypes) sortedAnnotations.remove(type);
        changedTypes.clear();
      }
    } finally {
      sortedAnnotations.clear();
      inputAs.removeAnnotationSetListener(inputASListener);
      currentSptIndex = -1;
      fireProcessFinished();
    }
  }

  
  @Override
  public void progressChanged(int i) {
    // event coming from one of our SPTs
    if(currentSptIndex >= 0) {
      fireProgressChanged((currentSptIndex * 100 + i) / singlePhaseTransducers.length);
    }
  }

  @Override
  public void processFinished() {
    // ignore
  }

  /**
   * Get the set of annotations, of a given type, sorted by start offset and
   * inverse length, obtained from the input annotation set of the current 
   * document.
   * 
   * @param type the type of annotations requested. 
   * @return an array of {@link Annotation} values.
   */
  public Annotation[] getSortedAnnotations(String type){
    Annotation[] annots = sortedAnnotations.get(type);
    if(annots == null){
      //not calculated yet
      AnnotationSet inputAS = 
        (inputASName == null || inputASName.trim().length() == 0) ?
        document.getAnnotations() : document.getAnnotations(inputASName);
      ArrayList<Annotation> annOfType = new ArrayList<Annotation>(
              inputAS.get(type));
      Collections.sort(annOfType, annotationComparator);
      annots = annOfType.toArray(new Annotation[annOfType.size()]);
      sortedAnnotations.put(type, annots);
    }
    return annots;
  }
  
  public java.net.URL getBinaryGrammarURL() {
    return binaryGrammarURL;
  }

  @CreoleParameter(
    comment = "The URL to the binary grammar file.",
    suffixes = ".jplus.z",
    disjunction = "grammar",
    priority = 100
  )
  public void setBinaryGrammarURL(java.net.URL binaryGrammarURL) {
    this.binaryGrammarURL = binaryGrammarURL;
  }  
  
  /**
   * @return the inputASName
   */
  public String getInputASName() {
    return inputASName;
  }

  /**
   * @param inputASName the inputASName to set
   */
  @CreoleParameter(comment="The name of the input annotation set.")
  @Optional
  @RunTime
  public void setInputASName(String inputASName) {
    this.inputASName = inputASName;
  }
  
  /**
   * @return the outputASName
   */
  public String getOutputASName() {
    return outputASName;
  }

  /**
   * @param outputASName the outputASName to set
   */
  @CreoleParameter(comment="The name of the output annotation set.")
  @Optional
  @RunTime
  public void setOutputASName(String outputASName) {
    this.outputASName = outputASName;
  }

  @CreoleParameter(comment="The ontology LR to be used by this transducer")
  @Optional
  @RunTime
  public void setOntology(Ontology onto) {
    ontology = onto;
  }

  public Ontology getOntology() {
    return ontology;
  }

  protected Ontology ontology = null;
  

  // methods implementing ControllerAwarePR
  @Override
  public void controllerExecutionStarted(Controller c)
    throws ExecutionException {
    actionContext.setController(c);
    actionContext.setCorpus(corpus);
    actionContext.setPRFeatures(features);
    actionContext.setPRName(this.getName());
    actionContext.setPR(this);
    actionContext.setDebuggingEnabled(enableDebugging);
    for(SPTBase aSpt : singlePhaseTransducers){
      aSpt.runControllerExecutionStartedBlock(actionContext,c,ontology);
    }
  }
  
  @Override
  public void controllerExecutionFinished(Controller c)
    throws ExecutionException {
    for(SPTBase aSpt : singlePhaseTransducers){
      aSpt.runControllerExecutionFinishedBlock(actionContext,c,ontology);
    }
    actionContext.setCorpus(null);
    actionContext.setController(null);
    actionContext.setPR(null);
  }

  @Override
  public void controllerExecutionAborted(Controller c, Throwable t)
    throws ExecutionException {
    for(SPTBase aSpt : singlePhaseTransducers){
      aSpt.runControllerExecutionAbortedBlock(actionContext,c,t,ontology);
    }
    actionContext.setCorpus(null);
    actionContext.setController(null);
    actionContext.setPR(null);
  }

  
  /**
   * This is testing code used during development.
   * TODO: delete it!
   */
  public static void main(String[] args){
    try {
      Gate.init();
      MainFrame.getInstance().setVisible(true);
      Gate.getCreoleRegister().registerDirectories(new File(".").toURI().toURL());
      File session = Gate.getUserSessionFile();
      if(session == null) session = new File(System.getProperty("user.home") + 
              ".gate.session");
      if(session.exists()) PersistenceManager.loadObjectFromFile(session);
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
}