KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > bsf > engines > java > JavaEngine


1 /*
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2002 The Apache Software Foundation. All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  * notice, this list of conditions and the following disclaimer in
16  * the documentation and/or other materials provided with the
17  * distribution.
18  *
19  * 3. The end-user documentation included with the redistribution, if
20  * any, must include the following acknowlegement:
21  * "This product includes software developed by the
22  * Apache Software Foundation (http://www.apache.org/)."
23  * Alternately, this acknowlegement may appear in the software itself,
24  * if and wherever such third-party acknowlegements normally appear.
25  *
26  * 4. The names "Apache BSF", "Apache", and "Apache Software Foundation"
27  * must not be used to endorse or promote products derived from
28  * this software without prior written permission. For written
29  * permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache"
32  * nor may "Apache" appear in their names without prior written
33  * permission of the Apache Group.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many individuals
50  * on behalf of the Apache Software Foundation and was originally created by
51  * Sanjiva Weerawarana and others at International Business Machines
52  * Corporation. For more information on the Apache Software Foundation,
53  * please see <http://www.apache.org/>.
54  */

55
56 package org.apache.bsf.engines.java;
57
58 import java.util.*;
59 import java.io.*;
60 import java.lang.*;
61 import java.lang.reflect.Method JavaDoc;
62
63 import org.apache.bsf.*;
64 import org.apache.bsf.util.BSFEngineImpl;
65 import org.apache.bsf.util.EngineUtils;
66 import org.apache.bsf.util.*;
67 import org.apache.bsf.debug.util.DebugLog;
68
69 /**
70  * This is the interface to Java from the
71  * Bean Scripting Framework.
72  * <p>
73  * The Java code must be written script-style -- that is, just the body of
74  * the function, without class or method headers or footers.
75  * The JavaEngine will generate those via a "boilerplate" wrapper:
76  * <pre>
77  * <code>
78  * import java.lang.*;
79  * import java.util.*;
80  * public class $$CLASSNAME$$ {
81  * static public Object BSFJavaEngineEntry(org.apache.bsf.BSFManager bsf) {
82  * // Your code will be placed here
83  * }
84  * }
85  * </code>
86  * </pre>
87  * $$CLASSNAME$$ will be replaced by a generated classname of the form
88  * BSFJava*, and the bsf parameter can be used to retrieve application
89  * objects registered with the Bean Scripting Framework.
90  * <p>
91  * If you use the placeholder string $$CLASSNAME$$ elsewhere
92  * in your script -- including within text strings -- BSFJavaEngine will
93  * replace it with the generated name of the class before the Java code
94  * is compiled.
95  * <p>
96  * <h2>Hazards:</h2>
97  * <p>
98  * NOTE that it is your responsibility to convert the code into an acceptable
99  * Java string. If you're invoking the JavaEngine directly (as in the
100  * JSPLikeInJava example) that means \"quoting\" characters that would
101  * otherwise cause trouble.
102  * <p>
103  * ALSO NOTE that it is your responsibility to return an object, or null in
104  * lieu thereof!
105  * <p>
106  * Since the code has to be compiled to a Java classfile, invoking it involves
107  * a fair amount of computation to load and execute the compiler. We are
108  * currently making an attempt to manage that by caching the class
109  * after it has been loaded, but the indexing is fairly primitive. It has
110  * been suggested that the Bean Scripting Framework may want to support
111  * preload-and-name-script and execute-preloaded-script-by-name options to
112  * provide better control over when and how much overhead occurs.
113  * <p>
114  * @author Joe Kesselman
115  */

116 public class JavaEngine extends BSFEngineImpl
117 {
118   Class JavaDoc javaclass=null;
119   private boolean bsfHandleCreated = false;
120   static Hashtable codeToClass=new Hashtable();
121   static String JavaDoc serializeCompilation="";
122   static String JavaDoc placeholder="$$CLASSNAME$$";
123   String JavaDoc minorPrefix;
124
125   /**
126    * Create a scratchfile, open it for writing, return its name.
127    * Relies on the filesystem to provide us with uniqueness testing.
128    * NOTE THAT uniqueFileOffset continues to count; we don't want to
129    * risk reusing a classname we have previously loaded in this session
130    * even if the classfile has been deleted.
131    */

132   private int uniqueFileOffset=-1;
133   private class GeneratedFile
134   {
135     File file=null;
136     FileOutputStream fos=null;
137     String JavaDoc className=null;
138     GeneratedFile(File file,FileOutputStream fos,String JavaDoc className)
139     {
140       this.file=file;
141       this.fos=fos;
142       this.className=className;
143     }
144   }
145   /**
146    * Constructor.
147    */

148   public JavaEngine ()
149   {
150     // Do compilation-possible check here??????????????
151
}
152   public Object JavaDoc call (Object JavaDoc object, String JavaDoc method, Object JavaDoc[] args)
153        throws BSFException
154   {
155     throw new BSFException (BSFException.REASON_UNSUPPORTED_FEATURE,
156                 "call() is not currently supported by JavaEngine");
157   }
158   public void compileScript (String JavaDoc source, int lineNo, int columnNo,
159           Object JavaDoc script, CodeBuffer cb) throws BSFException {
160     ObjInfo oldRet = cb.getFinalServiceMethodStatement ();
161
162     if (oldRet != null && oldRet.isExecutable ()) {
163       cb.addServiceMethodStatement (oldRet.objName + ";");
164     }
165
166     cb.addServiceMethodStatement (script.toString ());
167     cb.setFinalServiceMethodStatement (null);
168   }
169   /**
170    * This is used by an application to evaluate a string containing
171    * some expression. It should store the "bsf" handle where the
172    * script can get to it, for callback purposes.
173    * <p>
174    * Note that Java compilation imposes serious overhead,
175    * but in exchange you get full Java performance
176    * once the classes have been created (minus the cache lookup cost).
177    * <p>
178    * Nobody knows whether javac is threadsafe.
179    * I'm going to serialize access to protect it.
180    * <p>
181    * There is no published API for invoking javac as a class. There's a trick
182    * that seems to work for Java 1.1.x, but it stopped working in Java 1.2.
183    * We will attempt to use it, then if necessary fall back on invoking
184    * javac via the command line.
185    */

186   public Object JavaDoc eval (String JavaDoc source, int lineNo, int columnNo,
187               Object JavaDoc oscript) throws BSFException
188   {
189     if (debug)
190     {
191       debugStream.println("JavaEngine: tempDir=" + tempDir);
192     }
193
194     Object JavaDoc retval=null;
195     String JavaDoc classname=null;
196     GeneratedFile gf=null;
197
198     String JavaDoc basescript=oscript.toString();
199     String JavaDoc script=basescript; // May be altered by $$CLASSNAME$$ expansion
200

201     try {
202       // Do we already have a class exactly matching this code?
203
javaclass=(Class JavaDoc)codeToClass.get(basescript);
204       
205       if(javaclass!=null)
206     {
207       classname=javaclass.getName();
208     }
209       else
210     {
211       gf=openUniqueFile(tempDir, "BSFJava",".java");
212       if(gf==null)
213         throw new BSFException("couldn't create JavaEngine scratchfile");
214       
215       // Obtain classname
216
classname=gf.className;
217
218       // Write the kluge header to the file.
219
gf.fos.write(("import java.lang.*;"+
220             "import java.util.*;"+
221             "public class "+classname+" {\n" +
222                         " static public Object BSFJavaEngineEntry(org.apache.bsf.BSFManager bsf) {\n")
223                .getBytes());
224
225       // Edit the script to replace placeholder with the generated
226
// classname. Note that this occurs _after_ the cache was checked!
227
int startpoint,endpoint;
228       if((startpoint=script.indexOf(placeholder))>=0)
229         {
230           StringBuffer JavaDoc changed=new StringBuffer JavaDoc();
231           for(;
232           startpoint>=0;
233           startpoint=script.indexOf(placeholder,startpoint))
234         {
235           changed.setLength(0); // Reset for 2nd pass or later
236
if(startpoint>0)
237             changed.append(script.substring(0,startpoint));
238           changed.append(classname);
239           endpoint=startpoint+placeholder.length();
240           if(endpoint<script.length())
241             changed.append(script.substring(endpoint));
242           script=changed.toString();
243         }
244         }
245
246       // MJD - debug
247
// BSFDeclaredBean tempBean;
248
// String className;
249
//
250
// for (int i = 0; i < declaredBeans.size (); i++) {
251
// tempBean = (BSFDeclaredBean) declaredBeans.elementAt (i);
252
// className = StringUtils.getClassName (tempBean.bean.getClass ());
253
//
254
// gf.fos.write ((className + " " +
255
// tempBean.name + " = (" + className +
256
// ")bsf.lookupBean(\"" +
257
// tempBean.name + "\");").getBytes ());
258
// }
259
// MJD - debug
260

261     // Copy the input to the file.
262
// Assumes all available -- probably mistake, but same as other engines.
263
gf.fos.write(script.getBytes());
264       // Close the method and class
265
gf.fos.write(("\n }\n}\n").getBytes());
266       gf.fos.close();
267       
268       // Compile through Java to .class file
269
// May not be threadsafe. Serialize access on static object:
270
synchronized(serializeCompilation)
271         {
272           JavaUtils.JDKcompile(gf.file.getPath(), classPath);
273         }
274       
275       // Load class.
276
javaclass=EngineUtils.loadClass (mgr, classname);
277
278       // Stash class for reuse
279
codeToClass.put(basescript,javaclass);
280     }
281       
282       Object JavaDoc[] callArgs={mgr};
283       retval=internal_call(this,"BSFJavaEngineEntry",callArgs);
284     }
285     
286     
287     catch(Exception JavaDoc e)
288       {
289     e.printStackTrace ();
290     throw new BSFException (BSFException.REASON_IO_ERROR, e.getMessage ());
291       }
292     finally
293       {
294     // Cleanup: delete the .java and .class files
295

296 // if(gf!=null && gf.file!=null && gf.file.exists())
297
// gf.file.delete(); // .java file
298

299
300   if(classname!=null)
301     {
302       // Generated class
303
File file=new File(tempDir+File.separatorChar+classname+".class");
304 // if(file.exists())
305
// file.delete();
306

307       // Search for and clean up minor classes, classname$xxx.class
308
file=new File(tempDir); // ***** Is this required?
309
minorPrefix=classname+"$"; // Indirect arg to filter
310
String JavaDoc[] minor_classfiles=
311         file.list(new FilenameFilter()
312       {
313         // Starts with classname$ and ends with .class
314
public boolean accept(File dir,String JavaDoc name)
315           {
316             return
317         (0==name.indexOf(minorPrefix))
318         &&
319         (name.lastIndexOf(".class")==name.length()-6)
320         ;
321           }
322       });
323       for(int i=0;i<minor_classfiles.length;++i)
324         {
325     file=new File(minor_classfiles[i]);
326 // file.delete();
327
}
328     }
329       }
330
331     return retval;
332   }
333   public void initialize (BSFManager mgr, String JavaDoc lang,
334                           Vector declaredBeans) throws BSFException {
335     super.initialize (mgr, lang, declaredBeans);
336   }
337   /**
338    * Return an object from an extension.
339    * @param object Object on which to make the internal_call (ignored).
340    * @param method The name of the method to internal_call.
341    * @param args an array of arguments to be
342    * passed to the extension, which may be either
343    * Vectors of Nodes, or Strings.
344    */

345   Object JavaDoc internal_call (Object JavaDoc object, String JavaDoc method, Object JavaDoc[] args)
346                                                           throws BSFException
347   {
348     //***** ISSUE: Only static methods are currently supported
349
Object JavaDoc retval=null;
350     try
351       {
352     if(javaclass!=null)
353       {
354         //***** This should call the lookup used in BML, for typesafety
355
Class JavaDoc[] argtypes=new Class JavaDoc[args.length];
356         for(int i=0;i<args.length;++i)
357           argtypes[i]=args[i].getClass();
358         
359         Method JavaDoc m=MethodUtils.getMethod(javaclass,method,argtypes);
360         retval=m.invoke(null,args);
361       }
362       }
363     catch(Exception JavaDoc e)
364       {
365     throw new BSFException (BSFException.REASON_IO_ERROR, e.getMessage ());
366       }
367     return retval;
368   }
369   private GeneratedFile openUniqueFile(String JavaDoc directory,String JavaDoc prefix,String JavaDoc suffix)
370   {
371     File file=null;
372     FileOutputStream fos=null;
373     int max=1000; // Don't try forever
374
GeneratedFile gf=null;
375     int i;
376     String JavaDoc className = null;
377     for(i=max,++uniqueFileOffset;
378     fos==null && i>0;
379     --i,++uniqueFileOffset)
380       {
381     // Probably a timing hazard here... ***************
382
try
383       {
384         className = prefix+uniqueFileOffset;
385         file=new File(directory+File.separatorChar+className+suffix);
386         if(file!=null && !file.exists())
387           fos=new FileOutputStream(file);
388       }
389     catch(Exception JavaDoc e)
390       {
391         // File could not be opened for write, or Security Exception
392
// was thrown. If someone else created the file before we could
393
// open it, that's probably a threading conflict and we don't
394
// bother reporting it.
395
if(!file.exists())
396           {
397         DebugLog.stderrPrintln("openUniqueFile: unexpected "+e, DebugLog.BSF_LOG_L0);
398         e.printStackTrace();
399           }
400       }
401       }
402     if(fos==null)
403       DebugLog.stderrPrintln("openUniqueFile: Failed "+max+"attempts.", DebugLog.BSF_LOG_L0);
404     else
405       gf=new GeneratedFile(file,fos,className);
406     return gf;
407   }
408 }
409
Popular Tags