Morph.java
001 package gate.creole.morph;
002 
003 
004 /*
005  *  Morph.java
006  *
007  * Copyright (c) 1998-2005, The University of Sheffield.
008  *
009  * This file is part of GATE (see http://gate.ac.uk/), and is free
010  * software, licenced under the GNU Library General Public License,
011  * Version 2, June1991.
012  *
013  * A copy of this licence is included in the distribution in the file
014  * licence.html, and is also available at http://gate.ac.uk/gate/licence.html.
015  *
016  *  Niraj Aswani, 13/10/2003
017  *
018  *  $Id: Morph.java 17594 2014-03-08 12:07:09Z markagreenwood $
019  */
020 
021 
022 import gate.Annotation;
023 import gate.AnnotationSet;
024 import gate.Factory;
025 import gate.Factory.DuplicationContext;
026 import gate.FeatureMap;
027 import gate.ProcessingResource;
028 import gate.Resource;
029 import gate.Utils;
030 import gate.creole.AbstractLanguageAnalyser;
031 import gate.creole.CustomDuplication;
032 import gate.creole.ExecutionException;
033 import gate.creole.ResourceInstantiationException;
034 import gate.creole.metadata.CreoleParameter;
035 import gate.creole.metadata.CreoleResource;
036 import gate.creole.metadata.Optional;
037 import gate.creole.metadata.RunTime;
038 import gate.util.GateRuntimeException;
039 
040 import java.net.URL;
041 import java.util.Iterator;
042 
043 import org.apache.log4j.Level;
044 import org.apache.log4j.Logger;
045 
046 /**
047  * Description: This class is a wrapper for {@link gate.creole.morph.Interpret},
048  * the Morphological Analyzer.
049  */
050 @CreoleResource(name = "GATE Morphological analyser",
051         helpURL = "http://gate.ac.uk/userguide/sec:parsers:morpher",
052         comment = "Morphological Analyzer for the English Language")
053 public class Morph
054     extends AbstractLanguageAnalyser
055     implements ProcessingResource, CustomDuplication {
056 
057   private static final long serialVersionUID = 6964689654685956128L;
058 
059   /** File which contains rules to be processed */
060   protected URL rulesFile;
061 
062   /** Instance of BaseWord class - English Morpher */
063   protected Interpret interpret;
064 
065   /** Feature Name that should be displayed for the root word */
066   protected String rootFeatureName;
067 
068   /** Feature Name that should be displayed for the affix */
069   protected String affixFeatureName;
070 
071   /** The name of the annotation set used for input */
072   protected String annotationSetName;
073 
074   /** Boolean value that tells if parser should behave in caseSensitive mode */
075   protected Boolean caseSensitive;
076 
077   protected Boolean considerPOSTag;
078   
079   /**
080    * If this Morph PR is a duplicate of an existing PR, this property
081    * will hold a reference to the original PR's Interpret instance.
082    */
083   protected Interpret existingInterpret;
084 
085   @RunTime
086   @Optional
087   @CreoleParameter(
088     comment = "Throw and exception when there are none of the required input annotations",
089     defaultValue = "true")  
090   public void setFailOnMissingInputAnnotations(Boolean fail) {
091     failOnMissingInputAnnotations = fail;
092   }
093   public Boolean getFailOnMissingInputAnnotations() {
094     return failOnMissingInputAnnotations;
095   }
096   protected Boolean failOnMissingInputAnnotations = false;
097   
098   protected Logger logger = Logger.getLogger(this.getClass().getName());  
099   
100   /** Default Constructor */
101   public Morph() {
102   }
103 
104   /**
105    * This method creates the instance of the BaseWord - English Morpher and
106    * returns the instance of current class with different attributes and
107    * the instance of BaseWord class wrapped into it.
108    @return Resource
109    @throws ResourceInstantiationException
110    */
111   @Override
112   public Resource init() throws ResourceInstantiationException {
113     interpret = new Interpret();
114     if(existingInterpret != null) {
115       interpret.init(existingInterpret);
116     }
117     else {
118       if (rulesFile == null) {
119         // no rule file is there, simply run the interpret to interpret it and
120         throw new ResourceInstantiationException("\n\n No Rule File Provided");
121       }
122   
123       fireStatusChanged("Reading Rule File...");
124       // compile the rules
125       interpret.init(rulesFile);
126       fireStatusChanged("Morpher created!");
127       fireProcessFinished();
128     }
129     return this;
130   }
131 
132   /**
133    * Method is executed after the init() method has finished its execution.
134    <BR>Method does the following operations:
135    <OL type="1">
136    <LI> creates the annotationSet
137    <LI> fetches word tokens from the document, one at a time
138    <LI> runs the morpher on each individual word token
139    <LI> finds the root and the affix for that word
140    <LI> adds them as features to the current token
141    @throws ExecutionException
142    */
143   @Override
144   public void execute() throws ExecutionException {
145     // lets start the progress and initialize the progress counter
146     fireProgressChanged(0);
147 
148     // If no document provided to process throw an exception
149     if (document == null) {
150       fireProcessFinished();
151       throw new GateRuntimeException("No document to process!");
152     }
153 
154     // get the annotationSet name provided by the user, or otherwise use the
155     // default method
156     AnnotationSet inputAs = (annotationSetName == null ||
157         annotationSetName.length() == 0?
158         document.getAnnotations() :
159         document.getAnnotations(annotationSetName);
160 
161     // Morpher requires English tokenizer to be run before running the Morpher
162     // Fetch tokens from the document
163     AnnotationSet tokens = inputAs.get(TOKEN_ANNOTATION_TYPE);
164     if (tokens == null || tokens.isEmpty()) {
165       fireProcessFinished();
166       if(failOnMissingInputAnnotations) {
167         throw new ExecutionException("Either "+document.getName()+" does not have any contents or \n run the POS Tagger first and then Morpher");
168       else {
169         Utils.logOnce(logger,Level.INFO,"Morphological analyser: either a document does not have any contents or run the POS Tagger first - see debug log for details.");
170         logger.debug("No input annotations in document "+document.getName());
171         return;
172       }
173       //javax.swing.JOptionPane.showMessageDialog(MainFrame.getInstance(), "Either "+document.getName()+" does not have any contents or \n run the POS Tagger first and then Morpher"); ;
174       //return;
175     }
176 
177     // create iterator to get access to each and every individual token
178     Iterator<Annotation> tokensIter = tokens.iterator();
179 
180     // variables used to keep track on progress
181     int tokenSize = tokens.size();
182     int tokensProcessed = 0;
183     int lastReport = 0;
184 
185     //lets process each token one at a time
186     while (tokensIter != null && tokensIter.hasNext()) {
187       Annotation currentToken = tokensIter.next();
188       String tokenValue = (String) (currentToken.getFeatures().
189                                     get(TOKEN_STRING_FEATURE_NAME));
190       if(considerPOSTag != null && considerPOSTag.booleanValue() && !currentToken.getFeatures().containsKey(TOKEN_CATEGORY_FEATURE_NAME)) {
191         fireProcessFinished();
192         if(failOnMissingInputAnnotations) {
193           throw new ExecutionException("please run the POS Tagger first and then Morpher");
194         else {
195           Utils.logOnce(logger,Level.INFO,"Morphological analyser: no input annotations, run the POS Tagger first - see debug log for details.");
196           logger.debug("No input annotations in document "+document.getName());
197           return;
198         }
199         //javax.swing.JOptionPane.showMessageDialog(MainFrame.getInstance(), "please run the POS Tagger first and then Morpher"); ;
200         //return;
201       }
202 
203       String posCategory = (String) (currentToken.getFeatures().get(TOKEN_CATEGORY_FEATURE_NAME));
204       if(posCategory == null) {
205         posCategory = "*";
206       }
207 
208       if(considerPOSTag == null || !considerPOSTag.booleanValue()) {
209         posCategory = "*";
210       }
211 
212       // run the Morpher
213       if(!caseSensitive.booleanValue()) {
214         tokenValue = tokenValue.toLowerCase();
215       }
216 
217       String baseWord = interpret.runMorpher(tokenValue, posCategory);
218       String affixWord = interpret.getAffix();
219 
220       // no need to add affix feature if it is null
221       if (affixWord != null) {
222         currentToken.getFeatures().put(affixFeatureName, affixWord);
223       }
224       // add the root word as a feature
225       currentToken.getFeatures().put(rootFeatureName, baseWord);
226 
227       // measure the progress and update every after 100 tokens
228       tokensProcessed++;
229       if(tokensProcessed - lastReport > 100){
230         lastReport = tokensProcessed;
231         fireProgressChanged(tokensProcessed * 100 /tokenSize);
232       }
233     }
234     // process finished, acknowledge user about this.
235     fireProcessFinished();
236   }
237 
238   /**
239    * This method should only be called after init()
240    @param word
241    @return the rootWord
242    */
243   public String findBaseWord(String word, String cat) {
244     return interpret.runMorpher(word, cat);
245   }
246 
247   /**
248    * This method should only be called after init()
249    @param word
250    @return the afix of the rootWord
251    */
252   public String findAffix(String word, String cat) {
253     interpret.runMorpher(word, cat);
254     return interpret.getAffix();
255   }
256 
257   /**
258    * Sets the rule file to be processed
259    @param rulesFile - rule File name to be processed
260    */
261   public void setRulesFile(URL rulesFile) {
262     this.rulesFile = rulesFile;
263   }
264 
265   /**
266    * Returns the document under process
267    */
268   public URL getRulesFile() {
269     return this.rulesFile;
270   }
271 
272   /**
273    * Returns the feature name that has been currently set to display the root
274    * word
275    */
276   public String getRootFeatureName() {
277     return rootFeatureName;
278   }
279 
280   /**
281    * Sets the feature name that should be displayed for the root word
282    @param rootFeatureName
283    */
284   public void setRootFeatureName(String rootFeatureName) {
285     this.rootFeatureName = rootFeatureName;
286   }
287 
288   /**
289    * Returns the feature name that has been currently set to display the affix
290    * word
291    */
292   public String getAffixFeatureName() {
293     return affixFeatureName;
294   }
295 
296   /**
297    * Sets the feature name that should be displayed for the affix
298    @param affixFeatureName
299    */
300   public void setAffixFeatureName(String affixFeatureName) {
301     this.affixFeatureName = affixFeatureName;
302   }
303 
304   /**
305    * Returns the name of the AnnotationSet that has been provided to create
306    * the AnnotationSet
307    */
308   public String getAnnotationSetName() {
309     return annotationSetName;
310   }
311 
312   /**
313    * Sets the AnnonationSet name, that is used to create the AnnotationSet
314    @param annotationSetName
315    */
316   public void setAnnotationSetName(String annotationSetName) {
317     this.annotationSetName = annotationSetName;
318   }
319 
320   /**
321    * A method which returns if the parser is in caseSenstive mode
322    @return {@link Boolean} value.
323    */
324   public Boolean getCaseSensitive() {
325     return this.caseSensitive;
326   }
327 
328   /**
329    * Sets the caseSensitive value, that is used to tell parser if it should
330    * convert document to lowercase before parsing
331    */
332   public void setCaseSensitive(java.lang.Boolean value) {
333     this.caseSensitive = value;
334   }
335 
336   public Boolean getConsiderPOSTag() {
337     return this.considerPOSTag;
338   }
339 
340   public void setConsiderPOSTag(Boolean value) {
341     this.considerPOSTag = value;
342   }
343   
344   /**
345    * Only for use by the duplication mechanism.
346    */
347   public void setExistingInterpret(Interpret existingInterpret) {
348     this.existingInterpret = existingInterpret;
349   }
350 
351   /**
352    * Duplicate this morpher, sharing the compiled regular expression
353    * patterns and finite state machine with the duplicate.
354    */
355   @Override
356   public Resource duplicate(DuplicationContext ctx)
357           throws ResourceInstantiationException {
358     String className = this.getClass().getName();
359     String resName = this.getName();
360     FeatureMap initParams = getInitParameterValues();
361     initParams.put("existingInterpret", interpret);
362     Resource res = Factory.createResource(className, initParams, this.getFeatures(), resName);
363     res.setParameterValues(getRuntimeParameterValues());
364     return res;
365   }
366 }