1   /*
2    *  RightHandSide.java - transducer class
3    *
4    *  Copyright (c) 1998-2001, The University of Sheffield.
5    *
6    *  This file is part of GATE (see http://gate.ac.uk/), and is free
7    *  software, licenced under the GNU Library General Public License,
8    *  Version 2, June 1991 (in the distribution as file licence.html,
9    *  and also available at http://gate.ac.uk/gate/licence.html).
10   *
11   *  Hamish Cunningham, 24/07/98
12   *
13   *  $Id: RightHandSide.java,v 1.19 2001/11/16 13:03:35 hamish Exp $
14   */
15  
16  
17  package gate.jape;
18  
19  import java.util.*;
20  import java.io.*;
21  import java.lang.reflect.Constructor;
22  import java.lang.reflect.Method;
23  
24  
25  import gate.annotation.*;
26  import gate.util.*;
27  import gate.*;
28  
29  
30  /**
31    * The RHS of a CPSL rule. The action part. Contains an inner class
32    * created from the code in the grammar RHS.
33    */
34  public class RightHandSide implements JapeConstants, java.io.Serializable
35  {
36    /** Debug flag */
37    private static final boolean DEBUG = false;
38  
39    /** The "action class" we create to implement the action. Has a static
40      * method that performs the action of the RHS.
41      */
42    transient private Class theActionClass;
43  
44    /** An instance of theActionClass. */
45    transient private Object theActionObject;
46  
47    /** The string we use to create the action class. */
48    private StringBuffer actionClassString;
49  
50    /** The bytes of the compiled action class. */
51    private byte[] actionClassBytes;
52  
53    /** The name of the action class. */
54    private String actionClassName;
55  
56    /** Package name for action classes. It's called a "dir name" because
57      * we used to dump the action classes to disk and compile them there.
58      */
59    static private String actionsDirName = "japeactionclasses";
60  
61    /** The qualified name of the action class. */
62    private String actionClassQualifiedName;
63  
64    /** Name of the .java file for the action class. */
65    private String actionClassJavaFileName;
66  
67    /** Name of the .class file for the action class. */
68    private String actionClassClassFileName;
69  
70    /** Cardinality of the action class set. Used for ensuring class name
71      * uniqueness.
72      */
73    private static int actionClassNumber = 0;
74  
75    /** Allow setting of the initial action class number. Used for ensuring
76      * class name uniqueness when running more than one transducer. The
77      * long-term solution is to have separate class loaders for each
78      * transducer.
79      */
80    public static void setActionClassNumber(int n) { actionClassNumber = n; }
81  
82    /** The set of block names.
83      * Used to ensure we only get their annotations once in the action class.
84      */
85    private HashSet blockNames;
86  
87    /** For debugging. */
88    public String getActionClassString() { return actionClassString.toString(); }
89  
90    /** The LHS of our rule, where we get bindings from. */
91    private LeftHandSide lhs;
92  
93    /** A list of the files and directories we create. */
94    static private ArrayList tempFiles = new ArrayList();
95  
96    /** Local fashion for newlines. */
97    private final String nl = Strings.getNl();
98  
99    /** Debug flag. */
100   static final boolean debug = false;
101   private String phaseName;
102   private String ruleName;
103 
104   /** Construction from the transducer name, rule name and the LHS. */
105   public RightHandSide(
106     String transducerName, String ruleName, LeftHandSide lhs
107   ) {
108     // debug = true;
109     this.lhs = lhs;
110     this.phaseName = transducerName;
111     this.ruleName = ruleName;
112     actionClassName = new String(
113       transducerName + ruleName + "ActionClass" + actionClassNumber++
114     );
115     blockNames = new HashSet();
116 
117     // initialise the class action string
118     actionClassString = new StringBuffer(
119       "// " + actionClassName + nl +
120       "package " + actionsDirName + "; " + nl +
121       "import java.io.*;" + nl +
122       "import java.util.*;" + nl +
123       "import gate.*;" + nl +
124       "import gate.jape.*;" + nl +
125       "import gate.annotation.*;" + nl +
126       "import gate.util.*;" + nl + nl +
127       "public class " + actionClassName + nl +
128       "implements java.io.Serializable, RhsAction { " + nl +
129       "  public void doit(Document doc, AnnotationSet annotations, " +
130       " java.util.Map bindings) { " + nl
131     );
132 
133     // initialise various names
134     actionClassJavaFileName =
135       actionsDirName +  File.separator +
136       actionClassName.replace('.', File.separatorChar) + ".java";
137     actionClassQualifiedName =
138       actionsDirName.
139       replace(File.separatorChar, '.').replace('/', '.').replace('\\', '.') +
140       "." + actionClassName;
141     actionClassClassFileName =
142       actionClassQualifiedName.replace('.', File.separatorChar) + ".class";
143   } // Construction from lhs
144 
145   /** Add an anonymous block to the action class */
146   public void addBlock(String anonymousBlock) {
147     actionClassString.append(anonymousBlock);
148     actionClassString.append(nl);
149   } // addBlock(anon)
150 
151   /** Add a named block to the action class */
152   public void addBlock(String name, String namedBlock) {
153     // is it really a named block?
154     // (dealing with null name cuts code in the parser...)
155     if(name == null) {
156       addBlock(namedBlock);
157       return;
158     }
159 
160     if(blockNames.add(name)) // it wasn't already a member
161       actionClassString.append(
162         "    AnnotationSet " + name + "Annots = (AnnotationSet)bindings.get(\""
163         + name + "\"); " + nl
164       );
165 
166     actionClassString.append(
167       "    if(" + name + "Annots != null && " + name +
168       "Annots.size() != 0) { " + nl + "      " + namedBlock +
169       nl + "    }" + nl
170     );
171   } // addBlock(name, block)
172 
173   /** Create the action class and an instance of it. */
174   public void createActionClass() throws JapeException {
175     // terminate the class string
176     actionClassString.append("  }" + nl + "}" + nl);
177 
178     // create class:
179     // contruct a file name from the class name
180     // write the action class string out into the file
181     // call javac on the class
182     // call the gate class loader to load the resultant class
183     // read in the bytes if the compiled file for later serialisation
184     // create an instance of the class
185     /* writeActionClass();
186     compileActionClass();
187     loadActionClass();
188     readActionClass();
189     instantiateActionClass();
190     */
191     //Out.println(actionClassString);
192     try {
193       actionClassBytes = new Jdk().compile(
194         actionClassString.toString(),
195         actionClassJavaFileName
196       );
197     } catch(GateException e) {
198       String nl = Strings.getNl();
199       String actionWithNumbers =
200         Strings.addLineNumbers(actionClassString.toString());
201       throw new JapeException(
202         "Couldn't create action class: " + nl + e + nl +
203         "offending code was: " + nl + actionWithNumbers + nl
204       );
205     }
206     defineActionClass();
207     instantiateActionClass();
208 
209     //Debug.pr(this, "RightHandSide: action class loaded ok");
210   } // createActionClass
211 
212   /** Write out the action class file. */
213   public void writeActionClass() throws JapeException {
214 
215     File actionClassJavaFile = new File(actionClassJavaFileName);
216     try {
217       FileWriter writer = new FileWriter(actionClassJavaFile);
218       writer.write(actionClassString.toString());
219       writer.close();
220     } catch(IOException e) {
221       throw new JapeException(
222         "problem writing to " + actionClassJavaFileName + ": " + e.getMessage()
223       );
224     }
225   } // writeActionClass
226 
227   /** Compile the action class. First tries to use the sun.tools.javac
228     * class directly via reflection. If that fails, tries to exec javac
229     * as an external process.
230     */
231   public void compileActionClass() throws JapeException {
232     if(debug) Out.println("RightHandSide: trying to compile action");
233 
234     // see if we can find the sun compiler class
235     Class sunCompilerClass = null;
236     try {
237       sunCompilerClass = Class.forName("sun.tools.javac.Main");
238     } catch(ClassNotFoundException e) {
239       sunCompilerClass = null;
240     }
241 
242     // if it's 1.2, we can't support the compiler class at present
243     String jversion = System.getProperty("java.version");
244     if(jversion == null || jversion.startsWith("1.2"))
245       sunCompilerClass = null;
246 
247     // if we have the sun compiler class, try to use it directly
248     if(sunCompilerClass != null) {
249       // none-reflection version:
250       // sun.tools.javac.Main compiler =
251       //   new sun.tools.javac.Main(System.err, "RhsCompiler");
252       // String toBeCompiled[] = new String[1];
253       // toBeCompiled[0] = actionClassJavaFileName;
254       // boolean compiledOk = compiler.compile(toBeCompiled);
255 
256       Boolean compiledOk = new Boolean(false);
257       try {
258         // get the compiler constructor
259         Class[] consTypes = new Class[2];
260         consTypes[0] = OutputStream.class;
261         consTypes[1] = String.class;
262         Constructor compilerCons = sunCompilerClass.getConstructor(consTypes);
263 
264         // get an instance of the compiler
265         Object[] consArgs = new Object[2];
266         consArgs[0] = System.err;
267         consArgs[1] = "RhsCompiler";
268         Object sunCompiler = compilerCons.newInstance(consArgs);
269 
270         // get the compile method
271         Class[] compilerTypes = new Class[1];
272         compilerTypes[0] = String[].class;
273         Method compileMethod = sunCompilerClass.getDeclaredMethod(
274           "compile", compilerTypes
275         );
276 
277         // call the compiler
278         String toBeCompiled[] = new String[1];
279         toBeCompiled[0] = actionClassJavaFileName;
280         Object[] compilerArgs = new Object[1];
281         compilerArgs[0] = toBeCompiled;
282         compiledOk = (Boolean) compileMethod.invoke(sunCompiler, compilerArgs);
283 
284       // any exceptions mean the reflection stuff failed, as the compile
285       // method doesn't throw any. so (apart from RuntimeExceptions) we just
286       // print a warning and go on to try execing javac
287       } catch(RuntimeException e) { // rethrow runtime exceptions as they are
288         throw e;
289       } catch(Exception e) { // print out other sorts, and try javac exec
290         compiledOk = new Boolean(false);
291         Err.println(
292           "Warning: RHS compiler error: " + e.toString()
293         );
294       }
295       if(debug) Out.println("RightHandSide: action compiled ok");
296       if(compiledOk.booleanValue())
297         return;
298     }
299 
300     // no sun compiler: try execing javac as an external process
301     Runtime runtime = Runtime.getRuntime();
302     try {
303       String actionCompileCommand = new String(
304         "javac -classpath " +
305         System.getProperty("java.class.path") +
306         " " + actionClassJavaFileName
307       );
308       if(debug) Out.println("doing " + actionCompileCommand);
309 
310       Process actionCompile = runtime.exec(actionCompileCommand);
311       //InputStream stdout = actionCompile.getInputStream();
312       //InputStream stderr = actionCompile.getErrorStream();
313       actionCompile.waitFor();
314 
315       //Out.flush();
316       //while(stdout.available() > 0)
317       //  Out.print((char) stdout.read());
318       //while(stderr.available() > 0)
319       //  Out.print((char) stderr.read());
320       //Out.flush();
321       if(debug) Out.println("process complete");
322 
323     } catch(Exception e) {
324       throw new JapeException(
325         "couldn't compile " + actionClassJavaFileName + ": " + e.toString()
326       );
327     }
328   } // compileActionClass
329 
330   /** Read action class bytes, for storing during serialisation. */
331   public void readActionClass() throws JapeException {
332     try {
333       File f = new File(actionClassClassFileName);
334       FileInputStream fis = new FileInputStream(actionClassClassFileName);
335       actionClassBytes = new byte[(int) f.length()];
336       fis.read(actionClassBytes, 0, (int) f.length());
337       fis.close();
338     } catch(IOException e) {
339       throw(new JapeException("couldn't read action class bytes: " + e));
340     }
341   } // readActionClass
342 
343   /** Load the action class. */
344   public void loadActionClass() throws JapeException {
345     //Debug.pr(this, "RightHandSide: trying to load the action class");
346     try {
347       theActionClass =
348         Gate.getClassLoader().loadClass(actionClassClassFileName, true);
349     } catch(Exception e) {
350       e.printStackTrace();
351       throw new JapeException(
352         "couldn't load " + actionClassClassFileName + ": " + e.getMessage()
353       );
354     }
355   } // loadActionClass
356 
357   /** Define the action class (after deserialisation). */
358   public void defineActionClass() throws JapeException {
359     //Debug.pr(this, "RightHandSide: trying to define the action class");
360     try {
361       theActionClass = Gate.getClassLoader().defineGateClass(
362         actionClassQualifiedName, actionClassBytes, 0, actionClassBytes.length
363       );
364     } catch(ClassFormatError e) {
365       e.printStackTrace();
366       throw new JapeException(
367         "couldn't define " + actionClassName + ": " + e
368       );
369 
370     }
371     Gate.getClassLoader().resolveGateClass(theActionClass);
372   } // defineActionClass
373 
374   /** Create an instance of the action class. */
375   public void instantiateActionClass() throws JapeException {
376 
377     try {
378       theActionObject = theActionClass.newInstance();
379     } catch(Exception e) {
380       throw new JapeException(
381         "couldn't create instance of action class " + actionClassName + ": "
382         + e.getMessage()
383       );
384     }
385   } // instantiateActionClass
386 
387   /** Remove class files created for actions. */
388   public static void cleanUp() {
389     if(tempFiles.size() == 0) return;
390 
391     // traverse the list in reverse order, coz any directories we
392     // created were done first
393     for(ListIterator i = tempFiles.listIterator(tempFiles.size()-1);
394         i.hasPrevious();
395        ) {
396       File tempFile = (File) i.previous();
397       tempFile.delete();
398     } // for each tempFile
399 
400     tempFiles.clear();
401   } // cleanUp
402 
403 
404   /** Makes changes to the document, using LHS bindings. */
405   public void transduce(Document doc, AnnotationSet annotations,
406                         java.util.Map bindings) throws JapeException {
407     if(theActionObject == null) {
408       defineActionClass();
409       instantiateActionClass();
410     }
411 
412     // run the action class
413     try {
414       ((RhsAction) theActionObject).doit(doc, annotations, bindings);
415 
416     // if the action class throws an exception, re-throw it with a
417     // full description of the problem, inc. stack trace and the RHS
418     // action class code
419     } catch (Exception e) {
420       StringWriter stackTraceWriter = new StringWriter();
421       e.printStackTrace(new PrintWriter(stackTraceWriter));
422       throw new JapeException(
423         "Couldn't run RHS action: " + Strings.getNl() +
424         stackTraceWriter.getBuffer().toString() +
425         Strings.addLineNumbers(actionClassString.toString())
426       );
427     }
428   } // transduce
429 
430   /** Create a string representation of the object. */
431   public String toString() { return toString(""); }
432 
433   /** Create a string representation of the object. */
434   public String toString(String pad) {
435     String nl = Strings.getNl();
436     StringBuffer buf = new StringBuffer(
437       pad + "RHS: actionClassName(" + actionClassName + "); "
438     );
439     //buf.append("actionClassString(" + nl + actionClassString + nl);
440     buf.append(
441       "actionClassClassFileName(" + nl + actionClassClassFileName + nl
442     );
443     buf.append("actionClassJavaFileName(" + nl + actionClassJavaFileName + nl);
444     buf.append(
445       "actionClassQualifiedName(" + nl + actionClassQualifiedName + nl
446     );
447 
448     buf.append("blockNames(" + blockNames.toString() + "); ");
449 
450     buf.append(nl + pad + ") RHS." + nl);
451 
452     return buf.toString();
453   } // toString
454 
455   /** Create a string representation of the object. */
456   public String shortDesc() {
457     String res = "" + actionClassName;
458     return res;
459   }
460   public void setPhaseName(String phaseName) {
461     this.phaseName = phaseName;
462   }
463   public String getPhaseName() {
464     return phaseName;
465   }
466   public void setRuleName(String ruleName) {
467     this.ruleName = ruleName;
468   }
469   public String getRuleName() {
470     return ruleName;
471   } // toString
472 
473 } // class RightHandSide
474 
475 
476 // $Log: RightHandSide.java,v $
477 // Revision 1.19  2001/11/16 13:03:35  hamish
478 // moved line numbers method to Strings
479 //
480 // Revision 1.18  2001/11/16 10:29:45  hamish
481 // JAPE RHS compiler errors now include the RHS code; test added
482 //
483 // Revision 1.17  2001/11/15 14:05:09  hamish
484 // better error messages from JAPE RHS problems
485 //
486 // Revision 1.16  2001/11/01 15:49:09  valyt
487 //
488 // DEBUG mode for Japes
489 //
490 // Revision 1.15  2001/09/13 12:09:50  kalina
491 // Removed completely the use of jgl.objectspace.Array and such.
492 // Instead all sources now use the new Collections, typically ArrayList.
493 // I ran the tests and I ran some documents and compared with keys.
494 // JAPE seems to work well (that's where it all was). If there are problems
495 // maybe look at those new structures first.
496 //
497 // Revision 1.14  2000/11/08 16:35:03  hamish
498 // formatting
499 //
500 // Revision 1.13  2000/10/26 10:45:30  oana
501 // Modified in the code style
502 //
503 // Revision 1.12  2000/10/16 16:44:34  oana
504 // Changed the comment of DEBUG variable
505 //
506 // Revision 1.11  2000/10/10 15:36:36  oana
507 // Changed System.out in Out and System.err in Err;
508 // Added the DEBUG variable seted on false;
509 // Added in the header the licence;
510 //
511 // Revision 1.10  2000/07/04 14:37:39  valyt
512 // Added some support for Jape-ing in a different annotations et than the default one;
513 // Changed the L&F for the JapeGUI to the System default
514 //
515 // Revision 1.9  2000/06/12 13:33:27  hamish
516 // removed japeactionclasse create code (static init block
517 //
518 // Revision 1.8  2000/05/16 10:38:25  hamish
519 // removed printout
520 //
521 // Revision 1.7  2000/05/16 10:30:33  hamish
522 // uses new gate.util.Jdk compiler
523 //
524 // Revision 1.6  2000/05/05 12:51:12  valyt
525 // Got rid of deprecation warnings
526 //
527 // Revision 1.5  2000/05/05 10:14:09  hamish
528 // added more to toString
529 //
530 // Revision 1.4  2000/05/02 16:54:47  hamish
531 // porting to new annotation API
532 //
533 // Revision 1.3  2000/04/20 13:26:42  valyt
534 // Added the graph_drawing library.
535 // Creating of the NFSM and DFSM now works.
536 //
537 // Revision 1.2  2000/02/24 17:28:48  hamish
538 // more porting to new API
539 //
540 // Revision 1.1  2000/02/23 13:46:11  hamish
541 // added
542 //
543 // Revision 1.1.1.1  1999/02/03 16:23:02  hamish
544 // added gate2
545 //
546 // Revision 1.21  1998/11/13 17:25:10  hamish
547 // stop it using sun.tools... when in 1.2
548 //
549 // Revision 1.20  1998/10/30 15:31:07  kalina
550 // Made small changes to make compile under 1.2 and 1.1.x
551 //
552 // Revision 1.19  1998/10/29 12:17:12  hamish
553 // use reflection when using sun compiler classes, so can compile without them
554 //
555 // Revision 1.18  1998/10/01 16:06:36  hamish
556 // new appelt transduction style, replacing buggy version
557 //
558 // Revision 1.17  1998/09/18 16:54:17  hamish
559 // save/restore works except for attribute seq
560 //
561 // Revision 1.16  1998/09/18 13:35:44  hamish
562 // refactored to split up createActionClass
563 //
564 // Revision 1.15  1998/09/18 12:15:40  hamish
565 // bugs fixed: anon block null ptr; no error for some non-existant labelled blocks
566 //
567 // Revision 1.14  1998/08/19 20:21:41  hamish
568 // new RHS assignment expression stuff added
569 //
570 // Revision 1.13  1998/08/17 10:43:29  hamish
571 // action classes have unique names so can be reloaded
572 //
573 // Revision 1.12  1998/08/12 15:39:42  hamish
574 // added padding toString methods
575 //
576 // Revision 1.11  1998/08/10 14:16:38  hamish
577 // fixed consumeblock bug and added batch.java
578 //
579 // Revision 1.10  1998/08/07 12:01:46  hamish
580 // parser works; adding link to backend
581 //
582 // Revision 1.9  1998/08/05 21:58:07  hamish
583 // backend works on simple test
584 //
585 // Revision 1.8  1998/08/04 12:42:56  hamish
586 // fixed annots null check bug
587 //
588 // Revision 1.7  1998/08/03 21:44:57  hamish
589 // moved parser classes to gate.jape.parser
590 //
591 // Revision 1.6  1998/08/03 19:51:26  hamish
592 // rollback added
593 //
594 // Revision 1.5  1998/07/31 16:50:18  hamish
595 // RHS compilation works; it runs - and falls over...
596 //
597 // Revision 1.4  1998/07/31 13:12:25  hamish
598 // done RHS stuff, not tested
599 //
600 // Revision 1.3  1998/07/30 11:05:24  hamish
601 // more jape
602 //
603 // Revision 1.2  1998/07/29 11:07:10  hamish
604 // first compiling version
605 //
606 // Revision 1.1.1.1  1998/07/28 16:37:46  hamish
607 // gate2 lives
608