SimpleTokeniser.java
001 /*
002  *  DefaultTokeniser.java
003  *
004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  *
007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
008  *  software, licenced under the GNU Library General Public License,
009  *  Version 2, June 1991 (in the distribution as file licence.html,
010  *  and also available at http://gate.ac.uk/gate/licence.html).
011  *
012  *  Valentin Tablan, 2000
013  *
014  *  $Id: SimpleTokeniser.java 18317 2014-09-11 18:21:24Z ian_roberts $
015  */
016 
017 package gate.creole.tokeniser;
018 
019 import gate.AnnotationSet;
020 import gate.Factory;
021 import gate.FeatureMap;
022 import gate.Gate;
023 import gate.Resource;
024 import gate.creole.AbstractLanguageAnalyser;
025 import gate.creole.ExecutionException;
026 import gate.creole.ExecutionInterruptedException;
027 import gate.creole.ResourceInstantiationException;
028 import gate.creole.metadata.CreoleParameter;
029 import gate.creole.metadata.CreoleResource;
030 import gate.creole.metadata.Optional;
031 import gate.creole.metadata.RunTime;
032 import gate.util.BomStrippingInputStreamReader;
033 import gate.util.Err;
034 import gate.util.GateRuntimeException;
035 import gate.util.InvalidOffsetException;
036 import gate.util.LuckyException;
037 
038 import java.io.BufferedReader;
039 import java.lang.reflect.Field;
040 import java.lang.reflect.Modifier;
041 import java.util.AbstractSet;
042 import java.util.Collection;
043 import java.util.Collections;
044 import java.util.HashMap;
045 import java.util.HashSet;
046 import java.util.Iterator;
047 import java.util.LinkedList;
048 import java.util.List;
049 import java.util.Map;
050 import java.util.Set;
051 import java.util.StringTokenizer;
052 
053 import org.apache.commons.io.IOUtils;
054 
055 /** Implementation of a Unicode rule based tokeniser.
056  * The tokeniser gets its rules from a file an {@link java.io.InputStream
057  * InputStream} or a {@link java.io.Reader Reader} which should be sent to one
058  * of the constructors.
059  * The implementations is based on a finite state machine that is built based
060  * on the set of rules.
061  * A rule has two sides, the left hand side (LHS)and the right hand side (RHS)
062  * that are separated by the ">" character. The LHS represents a
063  * regular expression that will be matched against the input while the RHS
064  * describes a Gate2 annotation in terms of annotation type and attribute-value
065  * pairs.
066  * The matching is done using Unicode enumarated types as defined by the {@link
067  * java.lang.Character Character} class. At the time of writing this class the
068  * suported Unicode categories were:
069  <ul>
070  <li>UNASSIGNED
071  <li>UPPERCASE_LETTER
072  <li>LOWERCASE_LETTER
073  <li>TITLECASE_LETTER
074  <li>MODIFIER_LETTER
075  <li>OTHER_LETTER
076  <li>NON_SPACING_MARK
077  <li>ENCLOSING_MARK
078  <li>COMBINING_SPACING_MARK
079  <li>DECIMAL_DIGIT_NUMBER
080  <li>LETTER_NUMBER
081  <li>OTHER_NUMBER
082  <li>SPACE_SEPARATOR
083  <li>LINE_SEPARATOR
084  <li>PARAGRAPH_SEPARATOR
085  <li>CONTROL
086  <li>FORMAT
087  <li>PRIVATE_USE
088  <li>SURROGATE
089  <li>DASH_PUNCTUATION
090  <li>START_PUNCTUATION
091  <li>END_PUNCTUATION
092  <li>CONNECTOR_PUNCTUATION
093  <li>OTHER_PUNCTUATION
094  <li>MATH_SYMBOL
095  <li>CURRENCY_SYMBOL
096  <li>MODIFIER_SYMBOL
097  <li>OTHER_SYMBOL
098  </ul>
099  * The accepted operators for the LHS are "+", "*" and "|" having the usual
100  * interpretations of "1 to n occurences", "0 to n occurences" and
101  * "boolean OR".
102  * For instance this is a valid LHS:
103  <br>"UPPERCASE_LETTER" "LOWERCASE_LETTER"+
104  <br>meaning an uppercase letter followed by one or more lowercase letters.
105  *
106  * The RHS describes an annotation that is to be created and inserted in the
107  * annotation set provided in case of a match. The new annotation will span the
108  * text that has been recognised. The RHS consists in the annotation type
109  * followed by pairs of attributes and associated values.
110  * E.g. for the LHS above a possible RHS can be:<br>
111  * Token;kind=upperInitial;<br>
112  * representing an annotation of type &quot;Token&quot; having one attribute
113  * named &quot;kind&quot; with the value &quot;upperInitial&quot;<br>
114  * The entire rule willbe:<br>
115  <pre>"UPPERCASE_LETTER" "LOWERCASE_LETTER"+ > Token;kind=upperInitial;</pre>
116  <br>
117  * The tokeniser ignores all the empty lines or the ones that start with # or
118  * //.
119  *
120  */
121 @CreoleResource(name="GATE Unicode Tokeniser", comment="A customisable Unicode tokeniser.", helpURL="http://gate.ac.uk/userguide/sec:annie:tokeniser", icon="tokeniser")
122 public class SimpleTokeniser extends AbstractLanguageAnalyser{
123 
124   private static final long serialVersionUID = 1411111968361716069L;
125 
126   public static final String
127     SIMP_TOK_DOCUMENT_PARAMETER_NAME = "document";
128 
129   public static final String
130     SIMP_TOK_ANNOT_SET_PARAMETER_NAME = "annotationSetName";
131 
132   public static final String
133     SIMP_TOK_RULES_URL_PARAMETER_NAME = "rulesURL";
134 
135   public static final String
136     SIMP_TOK_ENCODING_PARAMETER_NAME = "encoding";
137 
138   /**
139    * Creates a tokeniser
140    */
141   public SimpleTokeniser(){
142   }
143 
144   /**
145    * Initialises this tokeniser by reading the rules from an external source (provided through an URL) and building
146    * the finite state machine at the core of the tokeniser.
147    *
148    @exception ResourceInstantiationException
149    */
150   @Override
151   public Resource init() throws ResourceInstantiationException{
152     BufferedReader bRulesReader = null;
153     try{
154       if(rulesURL != null){
155         bRulesReader = new BufferedReader(new BomStrippingInputStreamReader(rulesURL.openStream(), encoding));
156       }else{
157         //no init data, Scream!
158         throw new ResourceInstantiationException(
159           "No URL provided for the rules!");
160       }
161       initialState = new FSMState(this);
162       String line = bRulesReader.readLine();
163       ///String toParse = "";
164       StringBuffer toParse = new StringBuffer(Gate.STRINGBUFFER_SIZE);
165 
166       while (line != null){
167         if(line.endsWith("\\")){
168           ///toParse += line.substring(0,line.length()-1);
169           toParse.append(line.substring(0,line.length()-1));
170         }else{
171           /*toParse += line;
172           parseRule(toParse);
173           toParse = "";
174           */
175           toParse.append(line);
176           parseRule(toParse.toString());
177           toParse.delete(0,toParse.length());
178         }
179         line = bRulesReader.readLine();
180       }
181       eliminateVoidTransitions();
182     }catch(java.io.IOException ioe){
183       throw new ResourceInstantiationException(ioe);
184     }catch(TokeniserException te){
185       throw new ResourceInstantiationException(te);
186     }
187     finally {
188       IOUtils.closeQuietly(bRulesReader);
189     }
190     return this;
191   }
192 
193   /**
194    * Prepares this Processing resource for a new run.
195    */
196   public void reset(){
197     document = null;
198   }
199 
200   /** Parses one input line containing a tokeniser rule.
201    * This will create the necessary FSMState objects and the links
202    * between them.
203    *
204    @param line the string containing the rule
205    */
206   void parseRule(String line)throws TokeniserException{
207     //ignore comments
208     if(line.startsWith("#")) return;
209 
210     if(line.startsWith("//")) return;
211 
212     StringTokenizer st = new StringTokenizer(line, "()+*|\" \t\f>"true);
213     FSMState newState = new FSMState(this);
214 
215     initialState.put(null, newState);
216     FSMState finalState = parseLHS(newState, st, LHStoRHS);
217     String rhs = "";
218 
219     if(st.hasMoreTokens()) rhs = st.nextToken("\f");
220 
221     if(rhs.length() 0)finalState.setRhs(rhs);
222   // parseRule
223 
224   /** Parses a part or the entire LHS.
225    *
226    @param startState a FSMState object representing the initial state for
227    *     the small FSM that will recognise the (part of) the rule parsed by this
228    *     method.
229    @param st a {@link java.util.StringTokenizer StringTokenizer} that
230    *     provides the input
231    @param until the string that marks the end of the section to be
232    *     recognised. This method will first be called by {@link
233    *     #parseRule(String)} with &quot; &gt;&quot; in order to parse the entire
234    *     LHS. when necessary it will make itself another call to {@link #parseLHS
235    *     parseLHS} to parse a region of the LHS (e.g. a
236    *     &quot;(&quot;,&quot;)&quot; enclosed part.
237    */
238   FSMState parseLHS(FSMState startState, StringTokenizer st, String until)
239        throws TokeniserException{
240 
241     FSMState currentState = startState;
242     boolean orFound = false;
243     List<FSMState> orList = new LinkedList<FSMState>();
244     String token;
245     token = skipIgnoreTokens(st);
246 
247     if(null == tokenreturn currentState;
248 
249     FSMState newState;
250     Integer typeId;
251     UnicodeType uType;
252 
253     bigwhile: while(!token.equals(until)){
254       if(token.equals("(")){//(..)
255         newState = parseLHS(currentState, st,")");
256       else if(token.equals("\"")){//"unicode_type"
257         String sType = parseQuotedString(st, "\"");
258         newState = new FSMState(this);
259         typeId = stringTypeIds.get(sType);
260 
261         if(null == typeId)
262           throw new InvalidRuleException("Invalid type: \"" + sType + "\"");
263         else uType = new UnicodeType(typeId.intValue());
264 
265         currentState.put(uType ,newState);
266       else {// a type with no quotes
267         String sType = token;
268         newState = new FSMState(this);
269         typeId = stringTypeIds.get(sType);
270 
271         if(null == typeId)
272           throw new InvalidRuleException("Invalid type: \"" + sType + "\"");
273         else uType = new UnicodeType(typeId.intValue());
274 
275         currentState.put(uType ,newState);
276       }
277       //treat the operators
278       token = skipIgnoreTokens(st);
279       if(null == tokenthrow
280         new InvalidRuleException("Tokeniser rule ended too soon!");
281 
282       if(token.equals("|")) {
283 
284         orFound = true;
285         orList.add(newState);
286         token = skipIgnoreTokens(st);
287         if(null == tokenthrow
288           new InvalidRuleException("Tokeniser rule ended too soon!");
289 
290         continue bigwhile;
291       else if(orFound) {//done parsing the "|"
292         orFound = false;
293         orList.add(newState);
294         newState = new FSMState(this);
295         Iterator<FSMState> orListIter = orList.iterator();
296 
297         while(orListIter.hasNext())
298           orListIter.next().put(null, newState);
299         orList.clear();
300       }
301 
302       if(token.equals("+")) {
303 
304         newState.put(null,currentState);
305         currentState = newState;
306         newState = new FSMState(this);
307         currentState.put(null,newState);
308         token = skipIgnoreTokens(st);
309 
310         if(null == tokenthrow
311           new InvalidRuleException("Tokeniser rule ended too soon!");
312       else if(token.equals("*")) {
313 
314         currentState.put(null,newState);
315         newState.put(null,currentState);
316         currentState = newState;
317         newState = new FSMState(this);
318         currentState.put(null,newState);
319         token = skipIgnoreTokens(st);
320 
321         if(null == tokenthrow
322           new InvalidRuleException("Tokeniser rule ended too soon!");
323       }
324       currentState = newState;
325     }
326     return currentState;
327   // parseLHS
328 
329   /** Parses from the given string tokeniser until it finds a specific
330    * delimiter.
331    * One use for this method is to read everything until the first quote.
332    *
333    @param st a {@link java.util.StringTokenizer StringTokenizer} that
334    *     provides the input
335    @param until a String representing the end delimiter.
336    */
337   String parseQuotedString(StringTokenizer st, String until)
338     throws TokeniserException {
339 
340     String token;
341 
342     if(st.hasMoreElements()) token = st.nextToken();
343     else return null;
344 
345     ///String type = "";
346     StringBuffer type = new StringBuffer(Gate.STRINGBUFFER_SIZE);
347 
348     while(!token.equals(until)){
349       //type += token;
350       type.append(token);
351       if(st.hasMoreElements())token = st.nextToken();
352       else throw new InvalidRuleException("Tokeniser rule ended too soon!");
353     }
354     return type.toString();
355   // parseQuotedString
356 
357   /** Skips the ignorable tokens from the input returning the first significant
358    * token.
359    * The ignorable tokens are defined by {@link #ignoreTokens a set}
360    */
361   protected static String skipIgnoreTokens(StringTokenizer st){
362     Iterator<String> ignorables;
363     boolean ignorableFound = false;
364     String currentToken;
365 
366     while(true){
367       if(st.hasMoreTokens()){
368         currentToken = st.nextToken();
369         ignorables = ignoreTokens.iterator();
370         ignorableFound = false;
371 
372         while(!ignorableFound && ignorables.hasNext()){
373           if(currentToken.equals(ignorables.next()))
374             ignorableFound = true;
375         }
376 
377         if(!ignorableFoundreturn currentToken;
378       else return null;
379     }
380   }//skipIgnoreTokens
381 
382   /* Computes the lambda-closure (aka epsilon closure) of the given set of
383    * states, that is the set of states that are accessible from any of the
384    * states in the given set using only unrestricted transitions.
385    * @return a set containing all the states accessible from this state via
386    * transitions that bear no restrictions.
387    */
388   /**
389    * Converts the finite state machine to a deterministic one.
390    *
391    @param s
392    */
393   private AbstractSet<FSMState> lambdaClosure(Set<FSMState> s){
394 
395     //the stack/queue used by the algorithm
396     LinkedList<FSMState> list = new LinkedList<FSMState>(s);
397 
398     //the set to be returned
399     AbstractSet<FSMState> lambdaClosure = new HashSet<FSMState>(s);
400 
401     FSMState top;
402     FSMState currentState;
403     Set<FSMState> nextStates;
404     Iterator<FSMState> statesIter;
405 
406     while(!list.isEmpty()) {
407       top = list.removeFirst();
408       nextStates = top.nextSet(null);
409 
410       if(null != nextStates){
411         statesIter = nextStates.iterator();
412 
413         while(statesIter.hasNext()) {
414           currentState = statesIter.next();
415           if(!lambdaClosure.contains(currentState)){
416             lambdaClosure.add(currentState);
417             list.addFirst(currentState);
418           }//if(!lambdaClosure.contains(currentState))
419         }//while(statesIter.hasNext())
420 
421       }//if(null != nextStates)
422     }
423     return lambdaClosure;
424   // lambdaClosure
425 
426   /** Converts the FSM from a non-deterministic to a deterministic one by
427    * eliminating all the unrestricted transitions.
428    */
429   void eliminateVoidTransitions() throws TokeniserException {
430 
431     //kalina:clear() faster than init() which is called with init()
432     newStates.clear();
433     Set<Set<FSMState>> sdStates = new HashSet<Set<FSMState>>();
434     LinkedList<Set<FSMState>> unmarkedDStates = new LinkedList<Set<FSMState>>();
435     DFSMState dCurrentState = new DFSMState(this);
436     Set<FSMState> sdCurrentState = new HashSet<FSMState>();
437 
438     sdCurrentState.add(initialState);
439     sdCurrentState = lambdaClosure(sdCurrentState);
440     newStates.put(sdCurrentState, dCurrentState);
441     sdStates.add(sdCurrentState);
442 
443     //find out if the new state is a final one
444     Iterator<FSMState> innerStatesIter = sdCurrentState.iterator();
445     String rhs;
446     FSMState currentInnerState;
447     Set<String> rhsClashSet = new HashSet<String>();
448     boolean newRhs = false;
449 
450     while(innerStatesIter.hasNext()){
451       currentInnerState = innerStatesIter.next();
452       if(currentInnerState.isFinal()){
453         rhs = currentInnerState.getRhs();
454         rhsClashSet.add(rhs);
455         dCurrentState.rhs = rhs;
456         newRhs = true;
457       }
458     }
459 
460     if(rhsClashSet.size() 1){
461       Err.println("Warning, rule clash: " +  rhsClashSet +
462                          "\nSelected last definition: " + dCurrentState.rhs);
463     }
464 
465     if(newRhs)dCurrentState.buildTokenDesc();
466     rhsClashSet.clear();
467     unmarkedDStates.addFirst(sdCurrentState);
468     dInitialState = dCurrentState;
469     Set<FSMState> nextSet;
470 
471     while(!unmarkedDStates.isEmpty()){
472       //Out.println("\n\n=====================" + unmarkedDStates.size());
473       sdCurrentState = unmarkedDStates.removeFirst();
474       for(int type = 0; type < maxTypeId; type++){
475       //Out.print(type);
476         nextSet = new HashSet<FSMState>();
477         innerStatesIter = sdCurrentState.iterator();
478 
479         while(innerStatesIter.hasNext()){
480           currentInnerState = innerStatesIter.next();
481           Set<FSMState> tempSet = currentInnerState.nextSet(type);
482           if(null != tempSetnextSet.addAll(tempSet);
483         }//while(innerStatesIter.hasNext())
484 
485         if(!nextSet.isEmpty()){
486           nextSet = lambdaClosure(nextSet);
487           dCurrentState = newStates.get(nextSet);
488 
489           if(dCurrentState == null){
490 
491             //we have a new DFSMState
492             dCurrentState = new DFSMState(this);
493             sdStates.add(nextSet);
494             unmarkedDStates.add(nextSet);
495 
496             //check to see whether the new state is a final one
497             innerStatesIter = nextSet.iterator();
498             newRhs =false;
499 
500             while(innerStatesIter.hasNext()){
501               currentInnerState = innerStatesIter.next();
502               if(currentInnerState.isFinal()){
503                 rhs = currentInnerState.getRhs();
504                 rhsClashSet.add(rhs);
505                 dCurrentState.rhs = rhs;
506                 newRhs = true;
507               }
508             }
509 
510             if(rhsClashSet.size() 1){
511               Err.println("Warning, rule clash: " +  rhsClashSet +
512                             "\nSelected last definition: " + dCurrentState.rhs);
513             }
514 
515             if(newRhs)dCurrentState.buildTokenDesc();
516             rhsClashSet.clear();
517             newStates.put(nextSet, dCurrentState);
518           }
519           newStates.get(sdCurrentState).put(type,dCurrentState);
520         // if(!nextSet.isEmpty())
521 
522       // for(byte type = 0; type < 256; type++)
523 
524     // while(!unmarkedDStates.isEmpty())
525 
526   // eliminateVoidTransitions
527 
528   /** Returns a string representation of the non-deterministic FSM graph using
529    * GML (Graph modelling language).
530    */
531   public String getFSMgml(){
532     String res = "graph[ \ndirected 1\n";
533     ///String nodes = "", edges = "";
534     StringBuffer nodes = new StringBuffer(Gate.STRINGBUFFER_SIZE),
535                  edges = new StringBuffer(Gate.STRINGBUFFER_SIZE);
536 
537     Iterator<FSMState> fsmStatesIter = fsmStates.iterator();
538     while (fsmStatesIter.hasNext()){
539       FSMState currentState = fsmStatesIter.next();
540       int stateIndex = currentState.getIndex();
541       /*nodes += "node[ id " + stateIndex +
542                " label \"" + stateIndex;
543         */
544         nodes.append("node[ id ");
545         nodes.append(stateIndex);
546         nodes.append(" label \"");
547         nodes.append(stateIndex);
548 
549              if(currentState.isFinal()){
550               ///nodes += ",F\\n" + currentState.getRhs();
551               nodes.append(",F\\n" + currentState.getRhs());
552              }
553              ///nodes +=  "\"  ]\n";
554              nodes.append("\"  ]\n");
555       ///edges += currentState.getEdgesGML();
556       edges.append(currentState.getEdgesGML());
557     }
558     res += nodes.toString() + edges.toString() "]\n";
559     return res;
560   // getFSMgml
561 
562   /** Returns a string representation of the deterministic FSM graph using
563    * GML.
564    */
565   public String getDFSMgml() {
566     String res = "graph[ \ndirected 1\n";
567     ///String nodes = "", edges = "";
568     StringBuffer nodes = new StringBuffer(Gate.STRINGBUFFER_SIZE),
569                  edges = new StringBuffer(Gate.STRINGBUFFER_SIZE);
570 
571     Iterator<DFSMState> dfsmStatesIter = dfsmStates.iterator();
572     while (dfsmStatesIter.hasNext()) {
573       DFSMState currentState = dfsmStatesIter.next();
574       int stateIndex = currentState.getIndex();
575 /*      nodes += "node[ id " + stateIndex +
576                " label \"" + stateIndex;
577 */
578         nodes.append("node[ id ");
579         nodes.append(stateIndex);
580         nodes.append(" label \"");
581         nodes.append(stateIndex);
582 
583              if(currentState.isFinal()){
584 ///              nodes += ",F\\n" + currentState.getRhs();
585               nodes.append(",F\\n" + currentState.getRhs());
586              }
587 ///             nodes +=  "\"  ]\n";
588              nodes.append("\"  ]\n");
589 ///      edges += currentState.getEdgesGML();
590         edges.append(currentState.getEdgesGML());
591     }
592     res += nodes.toString() + edges.toString() "]\n";
593     return res;
594   // getDFSMgml
595 
596   /**
597    * The method that does the actual tokenisation.
598    */
599   @Override
600   public void execute() throws ExecutionException {
601     interrupted = false;
602     AnnotationSet annotationSet;
603     //check the input
604     if(document == null) {
605       throw new ExecutionException(
606         "No document to tokenise!"
607       );
608     }
609 
610     if(annotationSetName == null ||
611        annotationSetName.equals("")) annotationSet = document.getAnnotations();
612     else annotationSet = document.getAnnotations(annotationSetName);
613 
614     fireStatusChanged(
615         "Tokenising " + document.getName() "...");
616 
617     String content = document.getContent().toString();
618     int length = content.length();
619     int currentChar;
620     int charsInCurrentCP = 1;
621 
622     DFSMState graphPosition = dInitialState;
623 
624     //the index of the first character of the token trying to be recognised
625     int tokenStart = 0;
626 
627     DFSMState lastMatchingState = null;
628     DFSMState nextState;
629     String tokenString;
630     int charIdx = 0;
631     int oldCharIdx = 0;
632     FeatureMap newTokenFm;
633 
634     while(charIdx < length){
635       currentChar = content.codePointAt(charIdx);
636       // number of chars we have to advance after processing this code point.
637       // 1 in the vast majority of cases, but 2 where the code point is a
638       // supplementary character represented as a surrogate pair.
639       charsInCurrentCP = Character.isSupplementaryCodePoint(currentChar1;
640       
641 //      Out.println(
642 //      currentChar + typesMnemonics[Character.getType(currentChar)+128]);
643       nextState = graphPosition.next(typeIds.get(
644                   new Integer(Character.getType(currentChar))).intValue());
645 
646       ifnull != nextState ) {
647         graphPosition = nextState;
648         if(graphPosition.isFinal()) {
649           lastMatchingState = graphPosition;
650         }
651         charIdx += charsInCurrentCP;
652       else {//we have a match!
653         newTokenFm = Factory.newFeatureMap();
654 
655         if (null == lastMatchingState) {
656           // no rule matches this character, so create a single-char
657           // DEFAULT_TOKEN annotation covering it and start again after it
658           charIdx  = tokenStart + charsInCurrentCP;
659           tokenString = content.substring(tokenStart, charIdx);
660           newTokenFm.put("type","UNKNOWN");
661           newTokenFm.put(TOKEN_STRING_FEATURE_NAME, tokenString);
662           newTokenFm.put(TOKEN_LENGTH_FEATURE_NAME,
663                          Integer.toString(tokenString.length()));
664 
665           try {
666             annotationSet.add(new Long(tokenStart),
667                               new Long(charIdx),
668                               "DEFAULT_TOKEN", newTokenFm);
669           catch (InvalidOffsetException ioe) {
670             //This REALLY shouldn't happen!
671             ioe.printStackTrace(Err.getPrintWriter());
672           }
673           // Out.println("Default token: " + tokenStart +
674           //             "->" + tokenStart + " :" + tokenString + ";");
675         else {
676           // we've reached the end of a string that the FSM recognised
677           tokenString = content.substring(tokenStart, charIdx);
678           newTokenFm.put(TOKEN_STRING_FEATURE_NAME, tokenString);
679           newTokenFm.put(TOKEN_LENGTH_FEATURE_NAME,
680                          Integer.toString(tokenString.length()));
681 
682           for(int i = 1; i < lastMatchingState.getTokenDesc().length; i++){
683             newTokenFm.put(lastMatchingState.getTokenDesc()[i][0],
684                            lastMatchingState.getTokenDesc()[i][1]);
685           //Out.println(lastMatchingState.getTokenDesc()[i][0] + "=" +
686           //                       lastMatchingState.getTokenDesc()[i][1]);
687           }
688 
689 
690           try {
691             annotationSet.add(new Long(tokenStart),
692                             new Long(charIdx),
693                             lastMatchingState.getTokenDesc()[0][0], newTokenFm);
694           catch(InvalidOffsetException ioe) {
695             //This REALLY shouldn't happen!
696             throw new GateRuntimeException(ioe.toString());
697           }
698 
699           // Out.println(lastMatchingState.getTokenDesc()[0][0] +
700           //              ": " + tokenStart + "->" + lastMatch +
701           //              " :" + tokenString + ";");
702           //charIdx = lastMatch + 1;
703         }
704 
705         // reset to initial state and start looking again from here
706         lastMatchingState = null;
707         graphPosition = dInitialState;
708         tokenStart = charIdx;
709       }
710 
711       if((charIdx - oldCharIdx > 256)){
712         fireProgressChanged((100 * charIdx )/ length );
713         oldCharIdx = charIdx;
714         if(isInterrupted()) throw new ExecutionInterruptedException();
715       }
716 
717     // while(charIdx < length)
718 
719     if (null != lastMatchingState) {
720       // we dropped off the end having found a match, annotate it
721       tokenString = content.substring(tokenStart, charIdx);
722       newTokenFm = Factory.newFeatureMap();
723       newTokenFm.put(TOKEN_STRING_FEATURE_NAME, tokenString);
724       newTokenFm.put(TOKEN_LENGTH_FEATURE_NAME,
725                      Integer.toString(tokenString.length()));
726 
727       for(int i = 1; i < lastMatchingState.getTokenDesc().length; i++){
728         newTokenFm.put(lastMatchingState.getTokenDesc()[i][0],
729                        lastMatchingState.getTokenDesc()[i][1]);
730       }
731 
732 
733       try {
734         annotationSet.add(new Long(tokenStart),
735                           new Long(charIdx),
736                           lastMatchingState.getTokenDesc()[0][0], newTokenFm);
737       catch(InvalidOffsetException ioe) {
738         //This REALLY shouldn't happen!
739         throw new GateRuntimeException(ioe.toString());
740       }
741 
742     }
743 
744     reset();
745     fireProcessFinished();
746     fireStatusChanged("Tokenisation complete!");
747   // run
748 
749   /**
750    * Sets the value of the <code>rulesURL</code> property which holds an URL
751    * to the file containing the rules for this tokeniser.
752    @param newRulesURL
753    */
754   @CreoleParameter(defaultValue="resources/tokeniser/DefaultTokeniser.rules", comment="The URL to the rules file", suffixes="rules")
755   public void setRulesURL(java.net.URL newRulesURL) {
756     rulesURL = newRulesURL;
757   }
758   /**
759    * Gets the value of the <code>rulesURL</code> property hich holds an
760    * URL to the file containing the rules for this tokeniser.
761    */
762   public java.net.URL getRulesURL() {
763     return rulesURL;
764   }
765     
766   @RunTime
767   @Optional
768   @CreoleParameter(comment="The annotation set to be used for the generated annotations")
769   public void setAnnotationSetName(String newAnnotationSetName) {
770     annotationSetName = newAnnotationSetName;
771   }
772   /**    */
773   public String getAnnotationSetName() {
774     return annotationSetName;
775   }
776   public void setRulesResourceName(String newRulesResourceName) {
777     rulesResourceName = newRulesResourceName;
778   }
779   public String getRulesResourceName() {
780     return rulesResourceName;
781   }
782   
783   @CreoleParameter(defaultValue="UTF-8", comment="The encoding used for reading the definitions")
784   public void setEncoding(String newEncoding) {
785     encoding = newEncoding;
786   }
787   public String getEncoding() {
788     return encoding;
789   }
790 
791   /** the annotations et where the new annotations will be adde
792    */
793   protected String annotationSetName;
794 
795   /** The initial state of the non deterministic machin
796    */
797   protected FSMState initialState;
798 
799   /** A set containng all the states of the non deterministic machine
800    */
801   protected Set<FSMState> fsmStates = new HashSet<FSMState>();
802 
803   /** The initial state of the deterministic machine
804    */
805   protected DFSMState dInitialState;
806 
807   /** A set containng all the states of the deterministic machine
808    */
809   protected Set<DFSMState> dfsmStates = new HashSet<DFSMState>();
810 
811   /** The separator from LHS to RH
812    */
813   static String LHStoRHS = ">";
814 
815   /** A set of string representing tokens to be ignored (e.g. blanks
816    */
817   static Set<String> ignoreTokens;
818 
819   /** maps from int (the static value on {@link java.lang.Character} to int
820    * the internal value used by the tokeniser. The ins values used by the
821    * tokeniser are consecutive values, starting from 0 and going as high as
822    * necessary.
823    * They map all the public static int members on{@link java.lang.Character}
824    */
825   public static final Map<Integer, Integer> typeIds;
826 
827   /** The maximum int value used internally as a type i
828    */
829   public static int maxTypeId;
830 
831   /** Maps the internal type ids to the type name
832    */
833   public static String[] typeMnemonics;
834 
835   /** Maps from type names to type internal id
836    */
837   public static final Map<String, Integer> stringTypeIds;
838 
839   /**
840    * This property holds an URL to the file containing the rules for this tokeniser
841    *
842    */
843 
844   /**    */
845   static protected String defaultResourceName =
846                             "creole/tokeniser/DefaultTokeniser.rules";
847 
848   private String rulesResourceName;
849   private java.net.URL rulesURL;
850   private String encoding;
851 
852   //kalina: added this as method to minimise too many init() calls
853   protected transient Map<Set<FSMState>, DFSMState> newStates = new HashMap<Set<FSMState>, DFSMState>();
854 
855 
856   /** The static initialiser will inspect the class {@link java.lang.Character}
857     * using reflection to find all the public static members and will map them
858     * to ids starting from 0.
859     * After that it will build all the static data: {@link #typeIds}{@link
860     * #maxTypeId}{@link #typeMnemonics}{@link #stringTypeIds}
861     */
862   static{
863     Field[] characterClassFields;
864 
865     try{
866       characterClassFields = Class.forName("java.lang.Character").getFields();
867     }catch(ClassNotFoundException cnfe){
868       throw new LuckyException("Could not find the java.lang.Character class!");
869     }
870 
871     Collection<Field> staticFields = new LinkedList<Field>();
872     // JDK 1.4 introduced directionality constants that have the same values as
873     //character types; we need to skip those as well
874     for(int i = 0; i< characterClassFields.length; i++)
875       if(Modifier.isStatic(characterClassFields[i].getModifiers()) &&
876          characterClassFields[i].getName().indexOf("DIRECTIONALITY"== -1)
877         staticFields.add(characterClassFields[i]);
878 
879     Map<Integer, Integer> tempTypeIds = new HashMap<Integer, Integer>();
880     maxTypeId = staticFields.size() -1;
881     typeMnemonics = new String[maxTypeId + 1];
882     Map<String, Integer> tempStringTypeIds = new HashMap<String, Integer>();
883 
884     
885     
886     Iterator<Field> staticFieldsIter = staticFields.iterator();
887     Field currentField;
888     int currentId = 0;
889     String fieldName;
890 
891     try {
892       while(staticFieldsIter.hasNext()){
893         currentField = staticFieldsIter.next();
894         if(currentField.getType().toString().equals("byte")){
895           fieldName = currentField.getName();
896           tempTypeIds.put(new Integer(currentField.getInt(null)),
897                                     new Integer(currentId));
898           typeMnemonics[currentId= fieldName;
899           tempStringTypeIds.put(fieldName, new Integer(currentId));
900           currentId++;
901         }
902       }
903     catch(Exception e) {
904       throw new LuckyException(e.toString());
905     }
906     
907     typeIds = Collections.unmodifiableMap(tempTypeIds);
908     stringTypeIds = Collections.unmodifiableMap(tempStringTypeIds);
909 
910     ignoreTokens = new HashSet<String>();
911     ignoreTokens.add(" ");
912     ignoreTokens.add("\t");
913     ignoreTokens.add("\f");
914   }
915 
916 // class DefaultTokeniser