Transducer.java
001 /*
002  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
003  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
004  *
005  *  This file is part of GATE (see http://gate.ac.uk/), and is free
006  *  software, licenced under the GNU Library General Public License,
007  *  Version 2, June 1991 (in the distribution as file licence.html,
008  *  and also available at http://gate.ac.uk/gate/licence.html).
009  *
010  *  Valentin Tablan, 01 Feb 2000
011  *
012  *  $Id: Transducer.java 17821 2014-04-11 19:19:46Z markagreenwood $
013  */
014 package gate.creole;
015 
016 import gate.*;
017 import gate.creole.metadata.CreoleParameter;
018 import gate.creole.metadata.CreoleResource;
019 import gate.creole.metadata.Optional;
020 import gate.creole.metadata.RunTime;
021 import gate.gui.MainFrame;
022 import gate.jape.Batch;
023 import gate.jape.DefaultActionContext;
024 import gate.jape.JapeException;
025 import gate.jape.constraint.AnnotationAccessor;
026 import gate.jape.constraint.ConstraintPredicate;
027 import gate.util.Benchmarkable;
028 import gate.util.Err;
029 import java.io.File;
030 import java.io.FileOutputStream;
031 import java.io.IOException;
032 import java.io.ObjectInputStream;
033 import java.io.ObjectOutputStream;
034 import java.util.*;
035 
036 import javax.swing.*;
037 
038 /**
039  * A cascaded multi-phase transducer using the Jape language which is a variant
040  * of the CPSL language.
041  */
042 @CreoleResource(name = "JAPE Transducer",
043   comment = "A module for executing Jape grammars.",
044   helpURL = "http://gate.ac.uk/userguide/chap:jape",
045   icon = "jape"
046   )
047 public class Transducer
048   extends AbstractLanguageAnalyser
049   implements gate.gui.ActionsPublisher, Benchmarkable, ControllerAwarePR
050 {
051   private static final long serialVersionUID = -8789395272116846595L;
052 
053   public static final String TRANSD_DOCUMENT_PARAMETER_NAME = "document";
054 
055   public static final String TRANSD_INPUT_AS_PARAMETER_NAME = "inputASName";
056 
057   public static final String TRANSD_OUTPUT_AS_PARAMETER_NAME = "outputASName";
058 
059   public static final String TRANSD_ENCODING_PARAMETER_NAME = "encoding";
060 
061   public static final String TRANSD_GRAMMAR_URL_PARAMETER_NAME = "grammarURL";
062 
063   public static final String TRANSD_BINARY_GRAMMAR_URL_PARAMETER_NAME = "binaryGrammarURL";
064 
065   public static final String TRANSD_OPERATORS_PARAMETER_NAME = "operators";
066 
067   public static final String TRANSD_ANNOTATION_ACCESSORS_PARAMETER_NAME = "annotationAccessors";
068 
069 
070   protected List<Action> actionList;
071   protected DefaultActionContext actionContext;
072 
073   /**
074    * Default constructor. Does nothing apart from calling the default
075    * constructor from the super class. The actual object initialisation is done
076    * via the {@link #init} method.
077    */
078   public Transducer() {
079     actionList = new ArrayList<Action>();
080     actionList.add(null);
081     actionList.add(new SerializeTransducerAction());
082   }
083 
084   /*
085    * private void writeObject(ObjectOutputStream oos) throws IOException {
086    * Out.prln("writing transducer"); oos.defaultWriteObject();
087    * Out.prln("finished writing transducer"); } // writeObject
088    */
089   /**
090    * This method is the one responsible for initialising the transducer. It
091    * assumes that all the needed parameters have been already set using the
092    * appropiate setXXX() methods.
093    *
094    @return a reference to <b>this</b>
095    */
096   @Override
097   public Resource init() throws ResourceInstantiationException {
098     try {
099       fireProgressChanged(0);
100 
101       initCustomConstraints();
102 
103       if(binaryGrammarURL != null) {
104         ObjectInputStream s = new ObjectInputStream(binaryGrammarURL
105                 .openStream());
106         batch = (gate.jape.Batch)s.readObject();
107       else if(grammarURL != null) {
108         if(encoding != null) {
109           batch = new Batch(grammarURL, encoding, new InternalStatusListener());
110           if(enableDebugging != null) {
111             batch.setEnableDebugging(enableDebugging.booleanValue());
112           else {
113             batch.setEnableDebugging(false);
114           }
115           batch.setOntology(ontology);
116         else {
117           throw new ResourceInstantiationException("encoding is not set!");
118         }
119       else {
120         throw new ResourceInstantiationException(
121                 "Neither grammarURL or binaryGrammarURL parameters are set!");
122       }
123     catch(Exception e) {
124       String message = "Error while parsing the grammar ";
125       if(grammarURL != nullmessage += "(" + grammarURL.toExternalForm() ")";
126       message += ":";
127       throw new ResourceInstantiationException(message, e);
128     finally {
129       fireProcessFinished();
130     }
131     actionContext = initActionContext();
132     batch.setActionContext(actionContext);
133     batch.addProgressListener(new IntervalProgressListener(0100));
134     return this;
135   }
136 
137   /**
138    * Method that initialises the ActionContext. This method can be overridden
139    * if somebody wants to extend the Transducer PR class and provide their own
140    * subclass of DefaultActionContext to add some functionality.
141    
142    @return a DefaultActionContext object
143    */
144   protected DefaultActionContext initActionContext() {
145     return new DefaultActionContext();
146   }
147   
148   
149   
150   /**
151    * Implementation of the run() method from {@link java.lang.Runnable}. This
152    * method is responsible for doing all the processing of the input document.
153    */
154   @Override
155   public void execute() throws ExecutionException {
156     interrupted = false;
157     if(document == nullthrow new ExecutionException("No document provided!");
158     if(inputASName != null && inputASName.equals("")) inputASName = null;
159     if(outputASName != null && outputASName.equals("")) outputASName = null;
160     // the action context always reflects, for each document executed,
161     // the current PR features and the corpus, if present
162     actionContext.setCorpus(corpus);
163     actionContext.setPRFeatures(features);
164     actionContext.setPR(this);
165     try {
166       batch.transduce(document, inputASName == null
167               ? document.getAnnotations()
168               : document.getAnnotations(inputASName), outputASName == null
169               ? document.getAnnotations()
170               : document.getAnnotations(outputASName));
171     catch(JapeException je) {
172       throw new ExecutionException(je);
173     }
174   }
175 
176   /**
177    * Gets the list of actions that can be performed on this resource.
178    *
179    @return a List of Action objects (or null values)
180    */
181   @Override
182   public List<Action> getActions() {
183     List<Action> result = new ArrayList<Action>();
184     result.addAll(actionList);
185     return result;
186   }
187 
188   /**
189    * Loads any custom operators and annotation accessors into the ConstraintFactory.
190    @throws ResourceInstantiationException
191    */
192   protected void initCustomConstraints() throws ResourceInstantiationException {
193     //Load operators
194     if (operators != null) {
195       for(String opName : operators) {
196         Class<? extends ConstraintPredicate> clazz = null;
197         try {
198           clazz = Class.forName(opName, true, Gate.getClassLoader())
199                         .asSubclass(ConstraintPredicate.class);
200         }
201         catch(ClassNotFoundException e) {
202           //if couldn't find it that way, try with current thread class loader
203           try {
204             clazz = Class.forName(opName, true,
205                 Thread.currentThread().getContextClassLoader())
206                   .asSubclass(ConstraintPredicate.class);
207           }
208           catch(ClassNotFoundException e1) {
209             throw new ResourceInstantiationException("Cannot load class for operator: " + opName, e1);
210           }
211         }
212         catch(ClassCastException cce) {
213           throw new ResourceInstantiationException("Operator class '" + opName + "' must implement ConstraintPredicate");
214         }
215 
216         //instantiate an instance of the class so can get the operator string
217         try {
218           ConstraintPredicate predicate = clazz.newInstance();
219           String opSymbol = predicate.getOperator();
220           //now store it in ConstraintFactory
221           Factory.getConstraintFactory().addOperator(opSymbol, clazz);
222         }
223         catch(Exception e) {
224           throw new ResourceInstantiationException("Cannot instantiate class for operator: " + opName, e);
225         }
226       }
227     }
228 
229     //Load annotationAccessors
230     if (annotationAccessors != null) {
231       for(String accessorName : annotationAccessors) {
232         Class<? extends AnnotationAccessor> clazz = null;
233         try {
234           clazz = Class.forName(accessorName, true, Gate.getClassLoader())
235                      .asSubclass(AnnotationAccessor.class);
236         }
237         catch(ClassNotFoundException e) {
238           //if couldn't find it that way, try with current thread class loader
239           try {
240             clazz = Class.forName(accessorName, true,
241                 Thread.currentThread().getContextClassLoader())
242                    .asSubclass(AnnotationAccessor.class);
243           }
244           catch(ClassNotFoundException e1) {
245             throw new ResourceInstantiationException("Cannot load class for accessor: " + accessorName, e1);
246           }
247         }
248         catch(ClassCastException cce) {
249           throw new ResourceInstantiationException("Operator class '" + accessorName + "' must implement AnnotationAccessor");
250         }
251 
252         //instantiate an instance of the class so can get the meta-property name string
253         try {
254           AnnotationAccessor aa = clazz.newInstance();
255           String accSymbol = (String)aa.getKey();
256           //now store it in ConstraintFactory
257           Factory.getConstraintFactory().addMetaProperty(accSymbol, clazz);
258         }
259         catch(Exception e) {
260           throw new ResourceInstantiationException("Cannot instantiate class for accessor: " + accessorName, e);
261         }
262 
263       }
264     }
265   }
266 
267   /**
268    * Sends a serialized (binary) copy of this transducer to the specified output stream.
269    * Note that this is the same function used by the "Serialize Transducer" menu item
270    * allowing the same functionality to be accessed via code as well as the GUI.
271    **/
272   public void serialize(ObjectOutputStream outthrows IOException {
273     out.writeObject(batch);
274     out.flush();
275   }
276 
277   /**
278    * Saves the Jape Transuder to the binary file.
279    *
280    @author niraj
281    */
282   protected class SerializeTransducerAction extends javax.swing.AbstractAction {
283 
284     private static final long serialVersionUID = 4248612378452393237L;
285 
286     public SerializeTransducerAction() {
287       super("Serialize Transducer");
288       putValue(SHORT_DESCRIPTION, "Serializes the Transducer as binary file");
289     }
290 
291     @Override
292     public void actionPerformed(java.awt.event.ActionEvent evt) {
293       Runnable runnable = new Runnable() {
294         @Override
295         public void run() {
296           JFileChooser fileChooser = MainFrame.getFileChooser();
297           fileChooser.setFileFilter(fileChooser.getAcceptAllFileFilter());
298           fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
299           fileChooser.setMultiSelectionEnabled(false);
300           if(fileChooser.showSaveDialog(null== JFileChooser.APPROVE_OPTION) {
301             File file = fileChooser.getSelectedFile();
302             try {
303               MainFrame.lockGUI("Serializing JAPE Transducer...");
304               FileOutputStream out = new FileOutputStream(file);
305               ObjectOutputStream s = new ObjectOutputStream(out);
306            serialize(s);
307               s.close();
308               out.close();
309             catch(IOException ioe) {
310               JOptionPane.showMessageDialog(MainFrame.getInstance()"Error!\n" + ioe.toString(),
311                       "GATE", JOptionPane.ERROR_MESSAGE);
312               ioe.printStackTrace(Err.getPrintWriter());
313             finally {
314               MainFrame.unlockGUI();
315             }
316           }
317         }
318       };
319       Thread thread = new Thread(runnable, "Transduer Serialization");
320       thread.setPriority(Thread.MIN_PRIORITY);
321       thread.start();
322     }
323   }
324 
325   /**
326    * Notifies all the PRs in this controller that they should stop their
327    * execution as soon as possible.
328    */
329   @Override
330   public synchronized void interrupt() {
331     interrupted = true;
332     batch.interrupt();
333   }
334 
335   /**
336    * Sets the grammar to be used for building this transducer.
337    *
338    @param newGrammarURL
339    *          an URL to a file containing a Jape grammar.
340    */
341   @CreoleParameter(
342     comment = "The URL to the grammar file.",
343     suffixes = "jape",
344     disjunction = "grammar",
345     priority = 1
346   )
347   public void setGrammarURL(java.net.URL newGrammarURL) {
348     grammarURL = newGrammarURL;
349   }
350 
351   /**
352    * Gets the URL to the grammar used to build this transducer.
353    *
354    @return {@link java.net.URL} pointing to the grammar file.
355    */
356   public java.net.URL getGrammarURL() {
357     return grammarURL;
358   }
359 
360   /**
361    *
362    * Sets the encoding to be used for reding the input file(s) forming the Jape
363    * grammar. Note that if the input grammar is a multi-file one than the same
364    * encoding will be used for reding all the files. Multi file grammars with
365    * different encoding across the composing files are not supported!
366    *
367    @param newEncoding
368    *          a {link String} representing the encoding.
369    */
370   @CreoleParameter(
371     comment = "The encoding used for reading the grammar",
372     defaultValue = "UTF-8"
373   )
374   public void setEncoding(String newEncoding) {
375     encoding = newEncoding;
376   }
377 
378   /**
379    * Gets the encoding used for reding the grammar file(s).
380    */
381   public String getEncoding() {
382     return encoding;
383   }
384 
385   /**
386    * Sets the {@link gate.AnnotationSet} to be used as input for the transducer.
387    *
388    @param newInputASName
389    *          a {@link gate.AnnotationSet}
390    */
391   @RunTime
392   @Optional
393   @CreoleParameter(
394     comment = "The annotation set to be used as input for the transducer"
395   )
396   public void setInputASName(String newInputASName) {
397     inputASName = newInputASName;
398   }
399 
400   /**
401    * Gets the {@link gate.AnnotationSet} used as input by this transducer.
402    *
403    @return {@link gate.AnnotationSet}
404    */
405   public String getInputASName() {
406     return inputASName;
407   }
408 
409   /**
410    * Sets the {@link gate.AnnotationSet} to be used as output by the transducer.
411    *
412    @param newOutputASName
413    *          a {@link gate.AnnotationSet}
414    */
415   @RunTime
416   @Optional
417   @CreoleParameter(
418     comment = "The annotation set to be used as output for the transducer"
419   )
420   public void setOutputASName(String newOutputASName) {
421     outputASName = newOutputASName;
422   }
423 
424   /**
425    * Gets the {@link gate.AnnotationSet} used as output by this transducer.
426    *
427    @return {@link gate.AnnotationSet}
428    */
429   public String getOutputASName() {
430     return outputASName;
431   }
432 
433   public Boolean getEnableDebugging() {
434     return enableDebugging;
435   }
436 
437   @RunTime
438   @CreoleParameter(defaultValue = "false")
439   public void setEnableDebugging(Boolean enableDebugging) {
440     this.enableDebugging = enableDebugging;
441   }
442 
443   /**
444    * Gets the list of class names for any custom boolean operators.
445    * Classes must implement {@link gate.jape.constraint.ConstraintPredicate}.
446    */
447   public List<String> getOperators() {
448     return operators;
449   }
450 
451   /**
452    * Sets the list of class names for any custom boolean operators.
453    * Classes must implement {@link gate.jape.constraint.ConstraintPredicate}.
454    */
455   @Optional
456   @CreoleParameter(
457     comment = "Class names that implement gate.jape.constraint.ConstraintPredicate."
458   )
459   public void setOperators(List<String> operators) {
460     this.operators = operators;
461   }
462 
463   /**
464    * Gets the list of class names for any custom
465    {@link gate.jape.constraint.AnnotationAccessor}s.
466    */
467   public List<String> getAnnotationAccessors() {
468     return annotationAccessors;
469   }
470 
471   /**
472    * Sets the list of class names for any custom
473    {@link gate.jape.constraint.AnnotationAccessor}s.
474    */
475   @Optional
476   @CreoleParameter(
477     comment = "Class names that implement gate.jape.constraint.AnnotationAccessor."
478   )
479   public void setAnnotationAccessors(List<String> annotationAccessors) {
480     this.annotationAccessors = annotationAccessors;
481   }
482 
483   /**
484    * Get the benchmark ID of this Transducers batch.
485    */
486   @Override
487   public String getBenchmarkId() {
488     return batch.getBenchmarkId();
489   }
490 
491   /**
492    * Set the benchmark ID of this PR.
493    */
494   @Override
495   public void setBenchmarkId(String benchmarkId) {
496     batch.setBenchmarkId(benchmarkId);
497   }
498 
499   /**
500    * The URL to the jape file used as grammar by this transducer.
501    */
502   protected java.net.URL grammarURL;
503 
504   /**
505    * The URL to the serialized jape file used as grammar by this transducer.
506    */
507   protected java.net.URL binaryGrammarURL;
508 
509   /**
510    * The actual JapeTransducer used for processing the document(s).
511    */
512   protected Batch batch;
513 
514   /**
515    * The encoding used for reding the grammar file(s).
516    */
517   protected String encoding;
518 
519   /**
520    * The {@link gate.AnnotationSet} used as input for the transducer.
521    */
522   protected String inputASName;
523 
524   /**
525    * The {@link gate.AnnotationSet} used as output by the transducer.
526    */
527   protected String outputASName;
528 
529   /**
530    * The ontology that will be available on the RHS of JAPE rules.
531    */
532   protected gate.creole.ontology.Ontology ontology;
533 
534   /**
535    * List of class names for any custom
536    {@link gate.jape.constraint.ConstraintPredicate}.
537    */
538   protected List<String> operators = null;
539 
540   /**
541    * List of class names for any custom
542    {@link gate.jape.constraint.AnnotationAccessor}s.
543    */
544   protected List<String> annotationAccessors = null;
545 
546   /**
547    * Gets the ontology used by this transducer.
548    *
549    @return an {@link gate.creole.ontology.Ontology} value.
550    */
551   public gate.creole.ontology.Ontology getOntology() {
552     return ontology;
553   }
554 
555   /**
556    * Sets the ontology used by this transducer.
557    *
558    @param ontology
559    *          an {@link gate.creole.ontology.Ontology} value.
560    */
561   @RunTime
562   @Optional
563   @CreoleParameter(
564     comment = "The ontology to be used by this transducer"
565   )
566   public void setOntology(gate.creole.ontology.Ontology ontology) {
567     this.ontology = ontology;
568     //ontology is now a run-time param so we need to propagate it down to the
569     //actual SPTs included in this transducer.
570     if(batch!= nullbatch.setOntology(ontology);
571   }
572 
573   /**
574    * A switch used to activate the JAPE debugger.
575    */
576   protected Boolean enableDebugging = Boolean.FALSE;
577 
578 
579   public java.net.URL getBinaryGrammarURL() {
580     return binaryGrammarURL;
581   }
582 
583   @CreoleParameter(
584     comment = "The URL to the binary grammar file.",
585     suffixes = "jape",
586     disjunction = "grammar",
587     priority = 100
588   )
589   public void setBinaryGrammarURL(java.net.URL binaryGrammarURL) {
590     this.binaryGrammarURL = binaryGrammarURL;
591   }
592 
593   // methods implemeting ControllerAwarePR
594   @Override
595   public void controllerExecutionStarted(Controller c)
596     throws ExecutionException {
597     actionContext.setController(c);
598     actionContext.setCorpus(corpus);
599     actionContext.setPRFeatures(features);
600     actionContext.setPRName(this.getName());
601     actionContext.setPR(this);
602     actionContext.setDebuggingEnabled(enableDebugging);
603     batch.runControllerExecutionStartedBlock(actionContext,c,ontology);
604   }
605 
606   @Override
607   public void controllerExecutionFinished(Controller c)
608     throws ExecutionException {
609     batch.runControllerExecutionFinishedBlock(actionContext,c,ontology);
610     actionContext.setCorpus(null);
611     actionContext.setController(null);
612     actionContext.setPR(null);
613   }
614 
615   @Override
616   public void controllerExecutionAborted(Controller c, Throwable t)
617     throws ExecutionException {
618     batch.runControllerExecutionAbortedBlock(actionContext,c,t,ontology);
619     actionContext.setCorpus(null);
620     actionContext.setController(null);
621     actionContext.setPR(null);
622   }
623 
624 
625 }