Java tutorial
/* * Copyright 2004,2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.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.util.Hashtable; import java.util.Vector; import org.apache.bsf.BSFDeclaredBean; import org.apache.bsf.BSFException; import org.apache.bsf.BSFManager; 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 org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * This is the interface to NetRexx from the * Bean Scripting Framework. * <p> * 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: * <pre> * <code> * class $$CLASSNAME$$; * method BSFNetRexxEngineEntry(bsf=org.apache.bsf.BSFManager) public static; * </code> * </pre> * $$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. * <p> * 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. * <p> * 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". * <p> * <h2>Hazards:</h2> * <p> * 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. * <p> * 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 { BSFFunctions mgrfuncs; static Hashtable codeToClass = new Hashtable(); static String serializeCompilation = ""; static String placeholder = "$$CLASSNAME$$"; String minorPrefix; private Log logger = LogFactory.getLog(this.getClass().getName()); /** * 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; 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 netrexx.lang.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 { logger.error("NetRexxEngine: ERROR: rexxclass==null!"); } } 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. * <p> * 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). * <p> * 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); if (rexxclass != null) { logger.debug("NetRexxEngine: Found pre-compiled class" + " for script '" + basescript + "'"); 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) " + " 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, 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 + "\");") .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(); logger.debug("NetRexxEngine: wrote temp file " + gf.file.getPath() + ", now compiling"); // Compile through Java to .class file String command = gf.file.getPath(); //classname; if (logger.isDebugEnabled()) { 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(System.err)); } // 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. logger.debug("NetRexxEngine: loading class " + classname); rexxclass = EngineUtils.loadClass(mgr, classname); // Stash class for reuse codeToClass.put(basescript, rexxclass); } Object[] args = { mgrfuncs }; 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. 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; } 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, 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()) { logger.error("openUniqueFile: unexpected " + e); } } } if (fos == null) logger.error("openUniqueFile: Failed " + max + "attempts."); else gf = new GeneratedFile(file, fos, className); return gf; } public void undeclareBean(BSFDeclaredBean bean) throws BSFException { } }