Package org.apache.bsf.engines.netrexx

Source Code of org.apache.bsf.engines.netrexx.NetRexxEngine$GeneratedFile

/*
* 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 {}
}
TOP

Related Classes of org.apache.bsf.engines.netrexx.NetRexxEngine$GeneratedFile

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.