Interpret.java
001 package gate.creole.morph;
002 
003 import gate.creole.ResourceInstantiationException;
004 
005 import java.lang.reflect.Method;
006 import java.net.MalformedURLException;
007 import java.net.URL;
008 import java.util.ArrayList;
009 import java.util.Comparator;
010 import java.util.HashSet;
011 import java.util.Iterator;
012 import java.util.List;
013 import java.util.Set;
014 import java.util.SortedSet;
015 import java.util.TreeSet;
016 import java.util.regex.Pattern;
017 
018 /**
019  <p>
020  * Title: Interpret.java
021  </p>
022  <p>
023  * Description: This is the main class which which should be invoked to load the
024  * rule file in the system and then to execute the program to find the root word
025  * and the affix to it.
026  </p>
027  */
028 public class Interpret {
029 
030   /**
031    * instance of the ReadFile class which reads the file and stores each line
032    * of the given program in the arraylist which can be read using different
033    * methods of the ReadFile class
034    */
035   private ReadFile file;
036 
037   /** Boolean variables to keep track on which section is being read */
038   private boolean isDefineVarSession, isDefineRulesSession;
039 
040   /** Instance of Storage class, which is used store all the variables details */
041   private Storage variables;
042 
043   /** This variables keeps the record of available methods for the morphing */
044   private Method[] methods;
045 
046   /** This variables holds the affix */
047   private String affix;
048 
049   private Pattern vPat = Pattern.compile("((VB)[DGNPZ]?)|(MD)");
050 
051   private Pattern nPat = Pattern.compile("(NN)(S)*");
052 
053   MorphFunctions morphInst;
054 
055   List<Pattern> patterns = new ArrayList<Pattern>();
056   List<List<CharClass>> fsms = new ArrayList<List<CharClass>>();
057   
058   /**
059    * The initial state of the FSM that backs this morpher
060    */
061   protected FSMState initialState;
062 
063   //protected Set lastStates;
064 
065   /**
066    * It starts the actual program
067    */
068   public void init(URL ruleFileURLthrows ResourceInstantiationException {
069     variables = new Storage();
070     prepareListOfMorphMethods();
071     file = new ReadFile(ruleFileURL);
072     affix = null;
073     isDefineRulesSession = false;
074     isDefineVarSession = false;
075     morphInst = new MorphFunctions();
076 
077     readProgram();
078     initialState = new FSMState(-1);
079     
080     //lastStates = new HashSet();
081     interpretProgram();
082 
083     variables = null;
084     file = null;
085     //lastStates = null;
086   }
087   
088   /**
089    * Initialize this Interpret by copying pointers to the sharable state
090    * of an existing Interpret instance.
091    */
092   public void init(Interpret existingInterpret) {
093     affix = null;
094     isDefineRulesSession = false;
095     isDefineVarSession = false;
096     morphInst = new MorphFunctions();
097     
098     // copy shared state
099     fsms = existingInterpret.fsms;
100     patterns = existingInterpret.patterns;
101     initialState = existingInterpret.initialState;
102   }
103 
104   class CharClass {
105     char ch;
106     FSMState st;
107   }
108   
109   public void addState(char ch, FSMState fsm, int index) {
110     if(index == fsms.size()) {
111       fsms.add(new ArrayList<CharClass>());
112     }
113     
114     List<CharClass> fs = fsms.get(index);
115     for(int i=0;i<fs.size();i++) {
116       CharClass cc = fs.get(i);
117       if(cc.ch == ch)
118         return;
119     }
120     
121     CharClass cc = new CharClass();
122     cc.ch = ch;
123     cc.st = fsm;
124     fs.add(cc);
125   }
126 
127   
128   public FSMState getState(char ch, int index) {
129     if(index >= fsms.size()) return null;
130     List<CharClass> fs = fsms.get(index);
131     for(int i=0;i<fs.size();i++) {
132       CharClass cc = fs.get(i);
133       if(cc.ch == ch)
134         return cc.st;
135     }
136     return null;
137   }
138   
139   private Set<FSMState> getStates(char ch, Set<FSMState> states) {
140     Set<FSMState> newStates = new HashSet<FSMState>();
141     Iterator<FSMState> iter = states.iterator();
142     while (iter.hasNext()) {
143       FSMState st = iter.next();
144       FSMState chState = st.next(ch, FSMState.CHILD_STATE);
145       if (chState != null) {
146         newStates.add(chState);
147       }
148 
149       FSMState adState = st.next(ch, FSMState.ADJ_STATE);
150       if (adState != null) {
151         newStates.add(adState);
152       }
153     }
154     return newStates;
155   }
156 
157   private boolean validCategory(String category) {
158     if (category.equals("*")) {
159       return true;
160     else if (vPat.matcher(category).matches()) {
161       return true;
162     else if (nPat.matcher(category).matches()) {
163       return true;
164     }
165     return false;
166   }
167   
168   /**
169    @return set of the Lookups associated with the parameter
170    */
171   public String runMorpher(String word, String category) {
172     affix = null;
173     if(!validCategory(category)) {
174       return word;
175     }
176     
177     foundRule = false;
178     Set<FSMState> states = new HashSet<FSMState>();
179     states.add(initialState);
180     for (int i = 0; i < word.length(); i++) {
181       char ch = word.charAt(i);
182       states = getStates(ch, states);
183       if (states.isEmpty()) {
184         return word;
185       }
186 
187     }
188 
189     // we have all states here
190     // we obtain all RHSes
191     SortedSet<RHS> rhses = new TreeSet<RHS>(new Comparator<RHS>() {
192       @Override
193       public int compare(RHS r1, RHS r2) {
194         return r1.getPatternIndex() - r2.getPatternIndex();
195       }
196     });
197     
198     Iterator<FSMState> iter = states.iterator();
199     while (iter.hasNext()) {
200       FSMState st = iter.next();
201       rhses.addAll(st.getRHSes());
202     }
203 
204     if (rhses.isEmpty()) {
205       return word;
206     }
207 
208     return executeRHSes(rhses, word, category);
209   }
210 
211   protected int patternIndex = -1;
212   public int getPatternIndex() {
213     return patternIndex;
214   }
215   
216   protected String executeRHSes(SortedSet<RHS> rhses, String word, String category) {
217     foundRule = false;
218     // rhses are in sorted order
219     // we need to check if the word is compatible with pattern
220     Iterator<RHS> rhsiter = rhses.iterator();
221     while (rhsiter.hasNext()){
222       RHS r1 = rhsiter.next();
223       String answer = executeRHS(word, category, r1);
224       
225       if (foundRule) {
226         patternIndex = r1.getPatternIndex();
227         return answer;
228       }
229     }
230     return word;
231   }
232   
233   protected boolean foundRule = false;
234 
235   protected String executeRHS(String word, String category, RHS rhs) {
236     if (category.equals("*")) {
237       return executeRule(word, rhs);
238     else if (rhs.isVerb() && vPat.matcher(category).matches()) {
239       return executeRule(word, rhs);
240     else if (rhs.isNoun() && nPat.matcher(category).matches()) {
241       return executeRule(word, rhs);
242     }
243     return word;
244   }
245 
246   private String executeRule(String word, RHS rhs) {
247     Pattern p = patterns.get(rhs.getPatternIndex());
248 
249     short methodIndex = rhs.getMethodIndex();
250     if (!p.matcher(word).matches()) {
251       foundRule = false;
252       return word;
253     }
254 
255     // call the appropriate function
256     String[] parameters = rhs.getParameters();
257 
258     // set the given word in that morph program
259     morphInst.setInput(word);
260     String answer = null;
261     switch (methodIndex) {
262     case ParsingFunctions.IRREG_STEM:
263       answer = morphInst.irreg_stem(parameters[0], parameters[1]);
264       break;
265     case ParsingFunctions.NULL_STEM:
266       answer = morphInst.null_stem();
267       break;
268     case ParsingFunctions.SEMIREG_STEM:
269       answer = morphInst.semi_reg_stem(Integer.parseInt(parameters[0]),
270           parameters[1]);
271       break;
272     case ParsingFunctions.STEM:
273       answer = morphInst.stem(Integer.parseInt(parameters[0]),
274           parameters[1], parameters[2]);
275       break;
276     default:
277       answer = null;
278       break;
279     }
280     
281     if(answer != null) {
282       this.affix = morphInst.getAffix();
283       foundRule = true;
284       return answer;
285     else {
286       foundRule = false;
287       return word;
288     }
289   }
290 
291   /**
292    * This method prepares the list of available methods in the MorphFunctions
293    * class
294    */
295   private void prepareListOfMorphMethods()
296       throws ResourceInstantiationException {
297     methods = MorphFunctions.class.getDeclaredMethods();
298   }
299 
300   /**
301    * read the program file
302    */
303   private void readProgram() throws ResourceInstantiationException {
304     // read the program file
305     boolean readStatus = file.read();
306 
307     // check if read was success
308     if (!readStatus) {
309       // not it wasn't so simply display the message and ask user to check
310       // it
311       generateError("Some errors reading program file.. please check the"
312           "program and try again");
313     }
314   }
315 
316   /**
317    * This method reads each line of the program and interpret them
318    */
319   private void interpretProgram() throws ResourceInstantiationException {
320     // read each line and parse it
321     while (file.hasNext()) {
322       String currentLine = file.getNext();
323 
324       if (currentLine == null || currentLine.trim().length() == 0) {
325         continue;
326       }
327 
328       // remove all the leading spaces
329       currentLine = currentLine.trim();
330 
331       /*
332        * if commandType is 0 ==> defineVars command if commandType is 1
333        * ==> defineRules command if commandType is 2 ==> variable
334        * declaration if commandType is 3 ==> rule declaration otherwise //
335        * unknown generate error
336        */
337       int commandType = findCommandType(currentLine);
338       switch (commandType) {
339       case -1:
340         // comment command
341         continue;
342       case 0:
343         // defineVars command
344         defineVarsCommand();
345         break;
346       case 1:
347         // defineRules command
348         defineRulesCommand();
349         break;
350       case 2:
351         // variable declaration
352         variableDeclarationCommand(currentLine);
353         break;
354       case 3:
355         // rule declaration
356         ruleDeclarationCommand(currentLine);
357         break;
358       default:
359         generateError("Syntax Error at line " + file.getPointer()
360             " : " + currentLine);
361         break;
362       }
363     // end while
364   }
365 
366   /**
367    * This method interprets the line and finds out the type of command and
368    * returns the integer indicating the type of the command
369    
370    @param line
371    *            The program command to be interpreted
372    @return and <tt>int</tt> value
373    */
374   private int findCommandType(String line) {
375 
376     // check for the comment command
377     if (line.substring(02).equals("//"|| line.charAt(0== '#') {
378       return -1;
379     else if (line.equals("defineVars")) {
380       return 0;
381     else if (line.equals("defineRules")) {
382       return 1;
383     else if (isDefineVarSession && line.split("==>").length == 2) {
384       return 2;
385     else if (isDefineRulesSession &&
386     /*
387      * (line.charAt(0) == '{' || line.charAt(0) == '[' || line.charAt(0) ==
388      * '(' || line.charAt(0) == '\"')
389      */(line.charAt(0== '<'&& line.split("==>").length == 2) {
390       return 3;
391     else {
392       return Codes.ERROR_CODE;
393     }
394   }
395 
396   /**
397    * This method processes the command to define the variable section
398    */
399   private void defineVarsCommand() throws ResourceInstantiationException {
400 
401     // variable section can only be defined once
402     if (isDefineVarSession) {
403       generateError("Variable Section already defined - " "see line "
404           + file.getPointer());
405     else if (isDefineRulesSession) {
406       generateError("Variable section must be declared before the Rule "
407           "Section - see line " + file.getPointer());
408     else {
409       isDefineVarSession = true;
410     }
411   }
412 
413   /**
414    * This method processes the command to define the rule section
415    */
416   private void defineRulesCommand() throws ResourceInstantiationException {
417     if (isDefineRulesSession) {
418       generateError("Rule Section already defined - see " "line "
419           + file.getPointer());
420     else {
421       isDefineVarSession = false;
422       isDefineRulesSession = true;
423     }
424   }
425 
426   /**
427    * This method processes the command to declare the variable
428    
429    @param line
430    */
431   private void variableDeclarationCommand(String line)
432       throws ResourceInstantiationException {
433     // ok so first find the variable name and the value for it
434     String varName = (line.split("==>"))[0].trim();
435     String varValue = (line.split("==>"))[1].trim();
436 
437     // find the type of variable it is
438     int valueType = ParsingFunctions.findVariableType(varValue
439         .trim());
440     if (valueType == Codes.ERROR_CODE) {
441       generateError(varName + " - Variable Syntax Error - see " "line"
442           + file.getPointer() " : " + line);
443     }
444 
445     // based on the variable type create the instance
446     Variable varInst = null;
447     switch (valueType) {
448     case Codes.CHARACTER_RANGE_CODE:
449       varInst = new CharacterRange();
450       break;
451     case Codes.CHARACTER_SET_CODE:
452       varInst = new CharacterSet();
453       break;
454     case Codes.STRING_SET_CODE:
455       varInst = new StringSet();
456       break;
457     }
458 
459     // set the values in the variable
460     if (!varInst.set(varName, varValue)) {
461       generateError(varName
462           " - Syntax Error while assigning value to the "
463           "variable - see line" + file.getPointer() " : " + line);
464     }
465 
466     // and finally add the variable in
467     if (!variables.add(varName, varInst.getPattern())) {
468       generateError(varName.trim() " - Variable already defined - see "
469           "line " + file.getPointer() " : " + line);
470     }
471 
472     varInst.resetPointer();
473   }
474 
475   /**
476    * This method processes the command to declare the rule
477    
478    @param line
479    */
480   private void ruleDeclarationCommand(String line)
481       throws ResourceInstantiationException {
482     // lets divide the rule into two parts
483     // LHS and RHS.
484     // LHS is a part which requires to be parsed and
485     // RHS should be checked for the legal function name and valid arguments
486     // we process RHS first and then the LHS
487     String[] ruleParts = line.split("==>");
488     if (ruleParts.length != 2) {
489       generateError("Error in declaring rule at line : "
490           + file.getPointer() " : " + line);
491     }
492 
493     // now check if the method which has been called in this rule actually
494     // available in the MorphFunction Class
495     String methodCalled = ruleParts[1].trim();
496     if (!isMethodAvailable(methodCalled)) {
497 
498       // no method is not available so print the syntax error
499       generateError("Syntax error - method does not exists - see "
500           "line " + file.getPointer() " : " + line);
501     }
502 
503     // so RHS part is Ok
504     // now we need to check if LHS is written properly
505     // and convert it to the pattern that is recognized by the java
506     String category = "";
507     // we need to find out the category
508     int i = 1;
509     for (; i < ruleParts[0].length(); i++) {
510       if (ruleParts[0].charAt(i== '>')
511         break;
512       category = category + ruleParts[0].charAt(i);
513     }
514 
515     if (i >= ruleParts[0].length()) {
516       generateError("Syntax error - pattern not written properly - see "
517           "line " + file.getPointer() " : " + line);
518     }
519 
520     RHS rhs = new RHS(ruleParts[1], category, (short)patterns.size());
521     ruleParts[0= ruleParts[0].substring(i + 1, ruleParts[0].length())
522         .trim();
523     String regExp = ParsingFunctions.convertToRegExp(
524         ruleParts[0], variables);
525     patterns.add(Pattern.compile(regExp));
526     String[] rules = ParsingFunctions.normlizePattern(regExp);
527     for (int m = 0; m < rules.length; m++) {
528       Set<Set<FSMState>> lss = new HashSet<Set<FSMState>>();
529       lss.clear();
530       Set<FSMState> newSet = new HashSet<FSMState>();
531       newSet.add(initialState);
532       lss.add(newSet);
533       PatternPart parts[] = ParsingFunctions
534           .getPatternParts(rules[m].trim());
535       for (int j = 0; j < parts.length; j++) {
536         lss = ParsingFunctions.createFSMs(parts[j].getPartString(), parts[j].getType(), lss, this);
537       }
538       Iterator<Set<FSMState>> iter = lss.iterator();
539       while (iter.hasNext()) {
540         Set<FSMState> set = iter.next();
541         Iterator<FSMState> subIter = set.iterator();
542         while (subIter.hasNext()) {
543           FSMState st = subIter.next();
544           st.addRHS(rhs);
545         }
546       }
547     }
548     //drawFSM();
549   }
550 
551   @SuppressWarnings("unused")
552   private Set<FSMState> intersect(Set<FSMState> a, Set<FSMState> b) {
553     Set<FSMState> result = new HashSet<FSMState>();
554     Iterator<FSMState> iter = a.iterator();
555     while (iter.hasNext()) {
556       FSMState st = iter.next();
557       if (b.contains(st)) {
558         result.add(st);
559       }
560     }
561     return result;
562   }
563 
564   @SuppressWarnings("unused")
565   private void drawFSM() {
566     // we start with initialState
567     System.out.println("Initial:");
568     String space = "";
569     drawFSM(initialState, space);
570   }
571 
572   private void drawFSM(FSMState st, String space) {
573     CharMap map = st.getTransitionFunction();
574     char[] keys = map.getItemsKeys();
575     if (keys != null) {
576       System.out.println(space + "Child:");
577       for (int i = 0; i < keys.length; i++) {
578         System.out.println(space + "'" + keys[i"':");
579         drawFSM(map.get(keys[i], FSMState.CHILD_STATE), space + "  ");
580       }
581     }
582     keys = map.getAdjitemsKeys();
583     if (keys != null) {
584       System.out.println("ADJ:");
585       for (int i = 0; i < keys.length; i++) {
586         System.out.println(space + "'" + keys[i"' :");
587         // drawFSM(map.get(keys[i], FSMState.ADJ_STATE), space+" ");
588       }
589     }
590   }
591 
592   /**
593    * This method takes a method signature and searches if the method
594    
595    @param method
596    @return <tt>boolean</tt> value.
597    */
598   private boolean isMethodAvailable(String method) {
599     // now first find the name of the method
600     // their parameters and their types
601     int index = method.indexOf("(");
602     if (index == -|| index == 0
603         || method.charAt(method.length() 1!= ')') {
604       return false;
605     }
606 
607     String methodName = method.substring(0, index);
608     // now get the parameters
609 
610     String[] parameters;
611     int[] userMethodParams;
612 
613     String arguments = method.substring(index + 1, method.length() 1);
614     if (arguments == null || arguments.trim().length() == 0) {
615       parameters = null;
616       userMethodParams = null;
617     else {
618       parameters = method.substring(index + 1, method.length() 1)
619           .split(",");
620       userMethodParams = new int[parameters.length];
621     }
622 
623     // find the parameter types
624     // here we define only three types of arguments
625     // String, boolean and int
626     if (parameters != null) {
627       for (int i = 0; i < parameters.length; i++) {
628         if (parameters[i].startsWith("\"")
629             && parameters[i].endsWith("\"")) {
630           userMethodParams[i7;
631           parameters[i"java.lang.String";
632           continue;
633         else if (ParsingFunctions.isBoolean(parameters[i])) {
634           userMethodParams[i6;
635           parameters[i"boolean";
636         else if (ParsingFunctions.isInteger(parameters[i])) {
637           userMethodParams[i2;
638           parameters[i"int";
639         else {
640           // type cannot be recognized so generate error
641           return false;
642         }
643       }
644     }
645 
646     // now parameters have been found, so check them with the available
647     // methods
648     // in the morph function
649     for (int i = 0; i < methods.length; i++) {
650       if (methods[i].getName().equals(methodName)) {
651         // yes method has found now check for the parameters
652         // compatibility
653         Class<?>[] methodParams = methods[i].getParameterTypes();
654         // first check for the number of parameters
655         if (methods[i].getName().equals("null_stem")) {
656           return true;
657         }
658         if (methodParams.length == parameters.length) {
659           // yes arity has matched
660           // now set the precedence
661           int[] paramPrecedence = new int[methodParams.length];
662 
663           // assign precedence
664           for (int j = 0; j < methodParams.length; j++) {
665             if (methodParams[j].getName()
666                 .equals("java.lang.String"))
667               paramPrecedence[j7;
668             else if (methodParams[j].getName().equals("boolean"))
669               paramPrecedence[j6;
670             else if (methodParams[j].getName().equals("int"))
671               paramPrecedence[j2;
672             else
673               return false;
674           }
675 
676           // if we are here that means all the type matched
677           // so valid method declaration
678           return true;
679         }
680       }
681     }
682     // if we are here that means method doesnot found
683     return false;
684   }
685 
686   /**
687    * Generates the error and stop the execution
688    
689    @param mess -
690    *            message to be displayed as an error on the standard output
691    */
692   private void generateError(String mess)
693       throws ResourceInstantiationException {
694     System.out.println("\n\n" + mess);
695     System.out.println("Program terminated...");
696     throw new ResourceInstantiationException("\n\n" + mess);
697   }
698 
699   /**
700    * Main method
701    
702    @param args
703    */
704   public static void main(String[] args)
705       throws ResourceInstantiationException {
706     if (args == null || args.length < 3) {
707       System.out
708           .println("Usage : Interpret <Rules fileName> <word> <POS>");
709       System.exit(-1);
710     }
711     Interpret interpret = new Interpret();
712     try {
713       interpret.init(new URL(args[0]));
714     catch (MalformedURLException mue) {
715       throw new RuntimeException(mue);
716     }
717     String rootWord = interpret.runMorpher(args[1], args[2]);
718     String affix = interpret.getAffix();
719     System.out.println("Root : " + rootWord);
720     System.out.println("affix : " + affix);
721   }
722 
723   /**
724    * This method tells what was the affix to the provided word
725    
726    @return affix
727    */
728   public String getAffix() {
729     return this.affix;
730   }
731 
732   public FSMState getInitialState() {
733     return initialState;
734   }
735 }