package com.mind8.bsf.engines.netrexx; import java.io.File; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.Set; import java.util.Vector; import netrexx.lang.BadArgumentException; import org.apache.bsf.BSFDeclaredBean; import org.apache.bsf.BSFException; import org.apache.bsf.BSFManager; import org.apache.bsf.debug.util.DebugLog; import org.apache.bsf.util.BSFEngineImpl; import org.apache.bsf.util.BSFFunctions; import org.apache.bsf.util.EngineUtils; import org.apache.bsf.util.MethodUtils; import org.apache.bsf.util.StringUtils; import com.mind8.ui.skill.UISkillPlugin; /** * This is the interface to NetRexx from the Bean Scripting Framework. *

* The NetRexx code must be written script-style, without a "class" or * "properties" section preceeding the executable code. The NetRexxEngine will * generate a prefix for this code: * *

 * 
 * 
 *  class $$CLASSNAME$$;
 *  method BSFNetRexxEngineEntry(bsf=org.apache.bsf.BSFManager) public static;
 *  
 * 
 * 
* * $$CLASSNAME$$ will be replaced by a generated classname of the form * BSFNetRexx*, and the bsf parameter can be used to retrieve application * objects registered with the Bean Scripting Framework. *

* If you use the placeholder string $$CLASSNAME$$ elsewhere in your script -- * including within text strings -- BSFNetRexxEngine will replace it with the * generated name of the class before the NetRexx code is compiled. *

* If you need to use full NetRexx functionality, we recommend that your NetRexx * script define and invoke a "minor class", with or without the "dependent" * keyword as suits your needs. You'll have to use $$CLASSNAME$$ in naming the * minor class, since the name of the main class is synthesized; for example, to * create the minor class "bar" you'd write "class $$CLASSNAME$$.Bar". *

*

Hazards:

*

* Since NetRexx has to be _compiled_ to a Java classfile, invoking it involves * a fair amount of computation to load and execute the compiler. We are * currently making an attempt to manage that by caching the class after it has * been loaded, but the indexing is fairly primitive; we hash against the script * string to find the class for it. *

* Minor-class .class files are now being deleted after the major class loads. * This coould potentially cause problems. * * @author Joe Kesselman * @author Sanjiva Weerawarana */ public class NetRexxEngine extends BSFEngineImpl { static Hashtable codeToClass = new Hashtable(); static String serializeCompilation = ""; static String placeholder = "$$CLASSNAME$$"; /** * Create a scratchfile, open it for writing, return its name. Relies on the * filesystem to provide us with uniqueness testing. NOTE THAT * uniqueFileOffset continues to count; we don't want to risk reusing a * classname we have previously loaded in this session even if the classfile * has been deleted. I've made the offset static, due to concerns about * reuse/reentrancy of the NetRexx engine. */ private static int uniqueFileOffset = 0; BSFFunctions mgrfuncs; // private boolean bsfHandleCreated = false; String minorPrefix; private String m_originalClasspath; private class GeneratedFile { File file = null; FileOutputStream fos = null; String className = null; GeneratedFile(File file,FileOutputStream fos,String className) { this.file = file; this.fos = fos; this.className = className; } } // rexxclass used to be an instance variable, on the theory that // each NetRexxEngine was an instance of a specific script. // BSF is currently reusing Engines, so caching the class // no longer makes sense. // Class rexxclass; /** * Constructor. */ public NetRexxEngine() { /* * The following line is intended to cause the constructor to throw a * NoClassDefFoundError if the NetRexxC.zip dependency is not resolved. * If this line was not here, the problem would not surface until the * actual processing of a script. We want to know all is well at the * time the engine is instantiated, not when we attempt to process a * script. */ new BadArgumentException(); } /** * Return an object from an extension. * * @param Object * object from which to call our static method * @param method * The name of the method to call. * @param args * an array of arguments to be passed to the extension, which may be either * Vectors of Nodes, or Strings. */ public Object call(Object object,String method,Object[] args) throws BSFException { throw new BSFException(BSFException.REASON_UNSUPPORTED_FEATURE,"NetRexx doesn't currently support call()",null); } /** * Invoke a static method. * * @param rexxclass * Class to invoke the method against * @param method * The name of the method to call. * @param args * an array of arguments to be passed to the extension, which may be either * Vectors of Nodes, or Strings. */ Object callStatic(Class rexxclass,String method,Object[] args) throws BSFException { //***** ISSUE: Currently supports only static methods Object retval = null; try { if (rexxclass != null) { //***** This should call the lookup used in BML, for typesafety Class[] argtypes = new Class[args.length]; for (int i = 0 ; i < args.length ; ++i) argtypes[i] = args[i].getClass(); Method m = MethodUtils.getMethod(rexxclass,method,argtypes); retval = m.invoke(null,args); } else { DebugLog.stderrPrintln("NetRexxEngine: ERROR: rexxclass==null!",DebugLog.BSF_LOG_L3); } } catch(Exception e) { e.printStackTrace(); if (e instanceof InvocationTargetException) { Throwable t = ((InvocationTargetException) e).getTargetException(); t.printStackTrace(); } throw new BSFException(BSFException.REASON_IO_ERROR,e.getMessage(),e); } return retval; } public void declareBean(BSFDeclaredBean bean) throws BSFException { } /** * Override impl of execute. In NetRexx, methods which do not wish to return * a value should be invoked via exec, which will cause them to be generated * without the "returns" clause. Those which wish to return a value should * call eval instead. which will add "returns java.lang.Object" to the * header. Note: It would be nice to have the "real" return type avaialable, * so we could do something more type-safe than Object, and so we could * return primitive types without having to enclose them in their object * wrappers. BSF does not currently support that concept. */ public Object eval(String source,int lineNo,int columnNo,Object script) throws BSFException { return execEvalShared(source,lineNo,columnNo,script,true); } /** * Override impl of execute. In NetRexx, methods which do not wish to return * a value should be invoked via exec, which will cause them to be generated * without the "returns" clause. Those which wish to return a value should * call eval instead. which will add "returns java.lang.Object" to the * header. */ public void exec(String source,int lineNo,int columnNo,Object script) throws BSFException { execEvalShared(source,lineNo,columnNo,script,false); } /** * This is shared code for the exec() and eval() operations. It will * evaluate a string containing a NetRexx method body -- which may be as * simple as a single return statement. It should store the "bsf" handle * where the script can get to it, for callback purposes. *

* Note that NetRexx compilation imposes serious overhead -- 11 seconds for * the first compile, about 3 thereafter -- but in exchange you get * Java-like speeds once the classes have been created (minus the cache * lookup cost). *

* Nobody knows whether javac is threadsafe. I'm going to serialize access * to the compilers to protect it. */ public Object execEvalShared(String source,int lineNo,int columnNo,Object oscript,boolean returnsObject) throws BSFException { Object retval = null; String classname = null; GeneratedFile gf = null; // Moved into the exec process; see comment above. Class rexxclass = null; String basescript = oscript.toString(); String script = basescript; // May be altered by $$CLASSNAME$$ expansion try { // Do we already have a class exactly matching this code? rexxclass = (Class) codeToClass.get(basescript); appendSystemClassPath(); if (rexxclass != null) { DebugLog.debugPrintln("NetRexxEngine: Found pre-compiled class" + " for script '" + basescript + "'", DebugLog.BSF_LOG_L3); classname = rexxclass.getName(); } else { gf = openUniqueFile(tempDir,"BSFNetRexx",".nrx"); if (gf == null) throw new BSFException("couldn't create NetRexx scratchfile"); // Obtain classname classname = gf.className; // Decide whether to declare a return type String returnsDecl = ""; if (returnsObject) returnsDecl = "returns java.lang.Object"; // Write the kluge header to the file. // ***** By doing so we give up the ability to use Property // blocks. gf.fos.write(("class " + classname + ";\n").getBytes()); gf.fos.write( // ("method // BSFNetRexxEngineEntry(bsf=org.apache.bsf.util.BSFFunctions) // "+ ("method BSFNetRexxEngineEntry(bsf=org.apache.bsf.BSFManager) " + " public static " + returnsDecl + ";\n").getBytes()); // gf.fos.write(("method BSFNetRexxEngineEntry(bsf) " + // " public static " + returnsDecl + ";\n") // .getBytes()); // Edit the script to replace placeholder with the generated // classname. Note that this occurs _after_ the cache was // checked! int startpoint; // Edit the script to replace placeholder with the generated // classname. Note that this occurs _after_ the cache was // checked! int endpoint; if ((startpoint = script.indexOf(placeholder)) >= 0) { StringBuffer changed = new StringBuffer(); for ( ; startpoint >= 0 ; startpoint = script.indexOf(placeholder,startpoint)) { changed.setLength(0); //Reset for 2nd pass or later if (startpoint > 0) changed.append(script.substring(0,startpoint)); changed.append(classname); endpoint = startpoint + placeholder.length(); if (endpoint < script.length()) changed.append(script.substring(endpoint)); script = changed.toString(); } } BSFDeclaredBean tempBean; String className; for (int i = 0 ; i < declaredBeans.size() ; i++) { tempBean = (BSFDeclaredBean) declaredBeans.elementAt(i); className = StringUtils.getClassName(tempBean.type); gf.fos.write((tempBean.name + " =" + className + " bsf.lookupBean(\"" + tempBean.name + "\");\n").getBytes()); } // if (returnsObject) // gf.fos.write("return ".getBytes()); // Copy the input to the file. // Assumes all available -- probably mistake, but same as // other engines. gf.fos.write(script.getBytes()); gf.fos.close(); DebugLog.debugPrintln("NetRexxEngine: wrote temp file " + gf.file.getPath() + ", now compiling", DebugLog.BSF_LOG_L3); // Compile through Java to .class file String command = gf.file.getPath(); //classname; if (DebugLog.getLogLevel() >= 3) { command += " -verbose4"; } else { command += " -noverbose"; command += " -noconsole"; } netrexx.lang.Rexx cmdline = new netrexx.lang.Rexx(command); int retValue; // May not be threadsafe. Serialize access on static object: synchronized(serializeCompilation) { // compile to a .java file retValue = COM.ibm.netrexx.process.NetRexxC.main(cmdline,new PrintWriter(DebugLog.getDebugStream())); } // Check if there were errors while compiling the Rexx code. if (retValue == 2) { throw new BSFException(BSFException.REASON_EXECUTION_ERROR,"There were NetRexx errors."); } // Load class. DebugLog.debugPrintln("NetRexxEngine: loading class " + classname,DebugLog.BSF_LOG_L3); rexxclass = EngineUtils.loadClass(mgr,classname); // Stash class for reuse codeToClass.put(basescript,rexxclass); } // Object[] args = { mgrfuncs }; Object[] args = {UISkillPlugin.getBSFManager()}; retval = callStatic(rexxclass,"BSFNetRexxEngineEntry",args); } catch(BSFException e) { // Just forward the exception on. throw e; } catch(Exception e) { e.printStackTrace(); if (e instanceof InvocationTargetException) { Throwable t = ((InvocationTargetException) e).getTargetException(); t.printStackTrace(); } throw new BSFException(BSFException.REASON_IO_ERROR,e.getMessage(),e); } finally { // Cleanup: delete the .nrx and .class files // (if any) generated by NetRexx Trace requests. restoreSystemClassPath(); if ((gf != null) && (gf.file != null) && gf.file.exists()) gf.file.delete(); // .nrx file if (classname != null) { // Generated src File file = new File(tempDir + File.separatorChar + classname + ".java"); if (file.exists()) file.delete(); // Generated class file = new File(classname + ".class"); if (file.exists()) file.delete(); // Can this be done without disrupting trace? file = new File(tempDir + File.separatorChar + classname + ".crossref"); if (file.exists()) file.delete(); // Search for and clean up minor classes, classname$xxx.class file = new File(tempDir); minorPrefix = classname + "$"; // Indirect arg to filter String[] minor_classfiles = file.list( // ANONYMOUS CLASS for // filter: new FilenameFilter() { // Starts with classname$ // and ends with .class public boolean accept(File dir,String name) { return (0 == name.indexOf(minorPrefix)) && (name.lastIndexOf(".class") == (name.length() - 6)); } }); if (minor_classfiles != null) for (int i = minor_classfiles.length ; i > 0 ;) { file = new File(minor_classfiles[--i]); file.delete(); } } } return retval; } /** * Method restoreSystemClassPath. */ private void restoreSystemClassPath() { if (m_originalClasspath != null) System.setProperty("java.class.path",m_originalClasspath); } /** * Method appendSystemClassPath. */ private void appendSystemClassPath() { m_originalClasspath = System.getProperty("java.class.path"); Set cpaths = new HashSet(); // IPluginDescriptor[] descs = // Platform.getPluginRegistry().getPluginDescriptors(); // for (int i = 0; i < descs.length; i++) // { // URLClassLoader classLoader = (URLClassLoader) // descs[i].getPluginClassLoader(); // URL urls[] = classLoader.getURLs(); // for (int k = 0; k < urls.length; k++) // { // cpaths.add(new File(urls[k].getFile()).toString()); // } // } URL urls[] = UISkillPlugin.getDefault().getBundleURLs(); for (int j = 0 ; j < urls.length ; j++) { cpaths.add(new File(urls[j].getFile()).toString()); } String temporaryClasspath = m_originalClasspath; for (Iterator i = cpaths.iterator() ; i.hasNext() ;) { String cp = (String) i.next(); if (cp.length() > 0) temporaryClasspath += ";" + cp; } System.setProperty("java.class.path",temporaryClasspath); } // private void appendSystemClassPath() // { // m_originalClasspath = System.getProperty("java.class.path"); // //// URL url = // ScriptingPlugin.getDefault().getDescriptor().getInstallURL(); //// URL localUrl = null; //// try //// { //// localUrl = Platform.asLocalURL(url); //// } //// catch (Exception e) //// { //// } //// String pluginPath = localUrl.getPath(); //// File pluginFile = new File(pluginPath); //// File f = pluginFile.getParentFile(); // // String m_temporaryClasspath = m_originalClasspath; ////// File f = new File("c:\\eclipse\\workspace"); // File f = new File("c:\\lcl\\mwl\\Projects\\workspaceNEW"); // File[] allFiles = f.listFiles(); // for (int i = 0; i < allFiles.length; i++) // if (allFiles[i].isDirectory() && // !allFiles[i].getPath().endsWith(".metadata")) { // m_temporaryClasspath = m_temporaryClasspath + ";" + // allFiles[i].getAbsolutePath() + "\\bin"; //// System.out.println("bin: " + allFiles[i].getAbsolutePath() + "\\bin"); // String libpath = allFiles[i].getAbsolutePath() + "\\lib"; // File libDir = new File(libpath); // if (libDir.exists() && libDir.isDirectory()) { // File[] jars = libDir.listFiles(new FilenameFilter() { // public boolean accept(File dir, String name) { // return (name.lastIndexOf(".jar") == (name.length() - 4)); // } // }); // for (int j = 0; j < jars.length; j++) // { // m_temporaryClasspath = m_temporaryClasspath + ";" + // jars[j].getAbsolutePath(); //// System.out.println("jar: " + jars[j].getAbsolutePath()); // } // } // } //// m_temporaryClasspath = m_temporaryClasspath + ";" + // "c:\\lcl\\mwl\\Projects\\workspaceNEW\\org.apache.bsf\\bin"; //// m_temporaryClasspath = m_temporaryClasspath + ";" + // "c:\\lcl\\mwl\\Projects\\workspaceNEW\\org.foederal.mind\\bin"; // //// m_temporaryClasspath = m_temporaryClasspath + ";" + // "c:\\lcl\\mwl\\Projects\\workspaceNEW\\org.apache.bsf\\lib\\NetRexxC.jar"; //// m_temporaryClasspath = m_temporaryClasspath + ";" + // "c:\\lcl\\mwl\\Projects\\workspaceNEW\\org.apache.bsf\\lib\\tools.jar"; // ////// f = new File("d:\\eclipse\\plugins"); //// f = new File("c:\\lcl\\mwl\\eclipse2.1M3\\eclipse\\plugins"); //// allFiles = f.listFiles(); //// for (int i = 0; i < allFiles.length; i++) //// if (allFiles[i].isDirectory() && // !allFiles[i].getPath().endsWith(".metadata")) { //// File[] jars = allFiles[i].listFiles(new FilenameFilter() { //// public boolean accept(File dir, String name) { //// return (name.lastIndexOf(".jar") == (name.length() - 4)); //// } //// }); //// for (int j = 0; j < jars.length; j++) //// m_temporaryClasspath = m_temporaryClasspath + ";" + // jars[j].getAbsolutePath(); //// } // // System.setProperty("java.class.path", m_temporaryClasspath); // } public void initialize(BSFManager mgr,String lang,Vector declaredBeans) throws BSFException { super.initialize(mgr,lang,declaredBeans); mgrfuncs = new BSFFunctions(mgr,this); } private GeneratedFile openUniqueFile(String directory,String prefix,String suffix) { File file = null; File obj = null; FileOutputStream fos = null; int max = 1000; // Don't try forever GeneratedFile gf = null; int i; String className = null; for (i = max,++uniqueFileOffset ; (fos == null) && (i > 0) ; --i,++uniqueFileOffset) { // Probably a timing hazard here... *************** try { className = prefix + uniqueFileOffset; file = new File(directory + File.separatorChar + className + suffix); obj = new File(directory + File.separatorChar + className + ".class"); if ((file != null) && (!file.exists() & (obj != null) & !obj.exists())) fos = new FileOutputStream(file); } catch(Exception e) { // File could not be opened for write, or Security Exception // was thrown. If someone else created the file before we could // open it, that's probably a threading conflict and we don't // bother reporting it. if (!file.exists()) { DebugLog.stderrPrintln("openUniqueFile: unexpected " + e,DebugLog.BSF_LOG_L0); } } } if (fos == null) DebugLog.stderrPrintln("openUniqueFile: Failed " + max + "attempts.",DebugLog.BSF_LOG_L0); else gf = new GeneratedFile(file,fos,className); return gf; } public void undeclareBean(BSFDeclaredBean bean) throws BSFException { } }