team,

 i have developed a test automation tool that essentially allows one BeanShell interpreter to call another remote instance and request that it run a script. i presently subclass the interpreter and use Object Serialization to send the request. on the other side i unpack the request, spawn the interpreter, buffer the response, and send it back. works nicely. i recently extended it to allow it to execute BeanShell commands on the other side, rather than scripts (after all commands are really just scripts anyway). i developed a hokey mechanism for doing this:

1) send the command name over along with a hashmap of the named arguments

2) set this variable names equal to the values in the remote interpreter

3) dynamically build the method call signature and execute it

i.e.

set x = 1;
set y=2;

eval("foo(x, y););

it works, but there must be a more eleant way to accomplish this. can anyone chime in if something more intellegent comes to mind? i am an engineer in pursuit of excellence who is sometimes poorly equipt :)

best regards all,

mp



here is my class. it has some unrelated stuff in it, so please ignore that:




/*
 * Copyright (C) 2002 by Michael Pitoniak (pitoniakm@msn.com).
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to:
 *
 *   The Free Software Foundation, Inc.,
 *   59 Temple Place - Suite 330
 *   Boston, MA 02111-1307
 *   USA
 */


package harness.parsers.beanShell;

import harness.common.CommonConstants;
import harness.jComponents.jPanels.interfaces.JPanelConstants;
import harness.jComponents.jPanels.interfaces.TestPanelInterface;
import harness.logging.LogManager;
import harness.parsers.beanShell.interfaces.BeanShellParserConstants;
import harness.parsers.interfaces.ParserInterface;
import harness.parsers.interfaces.RemoteParserInterface;
import harness.parsers.interfaces.ScriptManagerInterface;
import harness.utils.classServices.ClassServices;
import harness.utils.exceptionServices.ExceptionServices;
import harness.utils.threadServices.BooleanLock;
import java.awt.Component;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;


/**************************************************
 *                                
 *                                 TEST FOR THIS IN:
 *                                 ExecRemoteScriptRequest.execte()
 *                                 ScriptJPanel.runScript()
 *
 * *************************************************/


public class BeanShellCMDParser extends java.lang.Thread implements ParserInterface, ScriptManagerInterface, BeanShellParserConstants, JPanelConstants, CommonConstants{
    protected RemoteParserInterface                 m_RemoteParserInterface = null;
    protected BooleanLock                           m_BooleanLock = null;
    protected String                                m_BeanShellCMD = null;
    protected HashMap                                m_ArgsHashMap = null;
    protected String                                m_PrefsFilePath = null;
    protected long                                                         m_TimeOutMS;
    private BeanShellInterptererThread2          m_BeanShellInterptererThread2 = null;
    private HashMap                                 m_ResponseHashMap = null;
    protected String                                  m_InetAddrName = null;
    private boolean                                 m_ParserStopped = false;
    protected boolean                                                 m_ParserTimeOut = false;
    protected LogManager                         m_LogManager = null;
   
    //TODO is this constructor needed anymore
    //it looks like we use it just to initialize all common code to all constructors
    //note it is called by other constructors in this class
    //following statement is not true...verify
    /** Default constructor needed by SEFBeanShellScriptParser */
    public BeanShellCMDParser() throws Exception{
        m_ResponseHashMap = new HashMap();
        m_LogManager = LogManager.Instance();
    }
   
    /** Creates a new instance of BeanShellParser */
    public BeanShellCMDParser(RemoteParserInterface remoteParserInterface, String bshCMD, HashMap args, String prefsFilePath, long timeOutMS) throws Exception{
        this();
        m_RemoteParserInterface = remoteParserInterface;
        m_BeanShellCMD                         = bshCMD;
        m_ArgsHashMap                         = args;
        m_PrefsFilePath                 = prefsFilePath;
        m_TimeOutMS                         = timeOutMS;  
    }
   
    public void run(){
        scriptStateMachine();
    }
   
    protected void scriptStateMachine(){
        int state = INITIALIZE;
       
        try{
            while(state != SCRIPT_COMPLETE) {
               
                switch(state) {

                    case INITIALIZE:
                       
                        initialize();
                        state = EVAL_CMD;
                        break;
                       
                    case EVAL_CMD:
                       
                        evalCMD(m_BeanShellCMD);
                        state = SCRIPT_COMPLETE;
                        break;
   
                    default:
                       
                        writeError(ClassServices.getCurrentMethodName() + "Illegal State Machine State");
                        state = SCRIPT_COMPLETE;
                       
                }
            }
        }catch(Error error){
            /*If any ThreadDeath Errors slip past we get them here before EventQue
            An instance of ThreadDeath is thrown in the victim thread when the stop
            method with zero arguments in class Thread is called. An application
            should catch instances of this class only if it must clean up after being
            terminated asynchronously. If ThreadDeath is caught by a method, it is
            important that it be rethrown so that the thread actually dies.
            The top-level error handler does not print out a message if ThreadDeath
            is never caught. The class ThreadDeath is specifically a subclass of
            Error rather than Exception, even though it is a "normal occurrence",
            because many applications catch all occurrences of Exception and then
            discard the exception.*/
            writeError(ClassServices.getCurrentMethodName() + error);
            writeError(error.getStackTrace());
        }catch(Exception e){
            writeError(ClassServices.getCurrentMethodName(), e);
        }finally{
            //ScriptManager tracks timeout if we never get here
            if(m_BooleanLock != null){
                m_BooleanLock.setValue(true);
            }
            //set System.out and System,.err back before
            //exiting because Redicted Streams are persistent
            cleanUp();
           
        }
    }
   
    //If this fails we throw an Exception and stop state machine
    protected void initialize() throws Exception{
        //reset output buffers on each script execution
        try{
            m_BooleanLock = new BooleanLock();
            m_InetAddrName = InetAddress.getLocalHost().getHostName();
            if(m_InetAddrName == null){
                m_InetAddrName = "REMOTE";
            }
            //Set name for ThreadViewer Local/Remote Identification
            if(m_RemoteParserInterface instanceof TestPanelInterface){
                this.setName(ClassServices.getClassNameNoPackage(this.getClass().getName()));
            }else{
                this.setName("REMOTE_" + ClassServices.getClassNameNoPackage(this.getClass().getName()));
            }
        }catch(Exception e){
            writeError(ClassServices.getCurrentMethodName(), e);
            throw e;
        }
    }

   
    /* Note: Additional local namespace variables are initialized by
     * BeanShellInterptererThread.initializeBshInterpreterNameSpace()
     * Any additional variables that need to be loaded by a script
     * can be achieved by just sourcing a file with varables
     * defined in it. That file should follow java syntax.
     *
     * If this fails it throws an Exception and stops State Machine
     */
    public void loadScriptArgs(Object args) throws Exception{
        m_BeanShellInterptererThread2.loadScriptArgs(args);
    }
   
    /*
     * NOTE: There are multiple instances of bsh classes in the jar file mounts. Test Harness
     * BeanShell support will not work unless the latest beanshell jar is found first.
     * The order in which directories and archives appear in the Filesystems tabbed pane
     * can affect the running and debugging of sources in the IDE. For example, if you have
     * Java files with identical class and package names in different mounted filesystems,
     * the file in the first mounted filesystem is always loaded during execution and debugging.
     * This happens even if you have select the second class before calling the command.
     * To change the order of mounted filesystems:
     * In the Explorer, right-click the root Filesystems ( ) node and choose Customize.
     * Right-click the Filesystems Settings node. Choose Change Order from the contextual menu.
     * In the Change Order dialog box, select the filesystem you want to move.
     * Click the Move Up button or the Move Down button to change the position
     * of the filesystem relative to the other filesystems.
     */
    //TODO this must be public because intrinsics call it....fix that
    public void evalCMD(String bshCmd){
        try{
                     //build the bsh cmd to evaluate
            boolean bFirst = true;
            StringBuffer bshCmdBuffer = new StringBuffer();
            bshCmdBuffer.append(bshCmd + "(");

            if(m_ArgsHashMap != null){
                Set mappings = m_ArgsHashMap.entrySet();
                for (Iterator i = mappings.iterator(); i.hasNext();) {
                    Map.Entry me = (Map.Entry)i.next();
                    Object key = me.getKey();
                    if(!bFirst){
                        bshCmdBuffer.append(", ");
                    }
                    bshCmdBuffer.append((String)key);
                    bFirst = false;
                }
            }
            bshCmdBuffer.append(");");
            writeInfo(ClassServices.getCurrentMethodName() + "Evaluating: " + bshCmdBuffer.toString());
            //TODO design change, BeanShellInterpreter should really take ParserInterface
            m_BeanShellInterptererThread2 = new BeanShellInterptererThread2(this, m_BooleanLock, bshCmdBuffer.toString(), m_ArgsHashMap, m_PrefsFilePath);

            //the BeanShellInterpreter will source the script relative the
            //the "BeanShell" CWD which was set in this.initialize()
            //note: this call is synchronous, and returns when script is complete
            m_BeanShellInterptererThread2.start();
            m_BooleanLock.waitUntilTrue(m_TimeOutMS);
            if(m_BooleanLock.isFalse()){
                m_ParserTimeOut = true;
                m_RemoteParserInterface.writeError(ClassServices.getCurrentMethodName() + m_InetAddrName + "->" + "Script TimeOut.......Exceeded: " + m_TimeOutMS + "Ms");
            }else{
                m_RemoteParserInterface.writeInfo(ClassServices.getCurrentMethodName() + "BooleanLock Released......Script Complete");
            }
            writeInfo(ClassServices.getCurrentMethodName() + "Done Evaluating: " + bshCmd);
        }catch(Exception e){
            writeError(ClassServices.getCurrentMethodName(), e);
        }catch(Error error){
            writeError(ClassServices.getCurrentMethodName() + ExceptionServices.getStackTrace(error));
            throw error;
        }finally{
            if(m_RemoteParserInterface instanceof TestPanelInterface){
                ((TestPanelInterface)m_RemoteParserInterface).setRunButtonEnabled(true);
                m_LogManager.closeScriptLogFiles();
                //insure ElapsedTimerThread in BeanShellScriptParser dies
                m_RemoteParserInterface = null;
            }
        }
    }
   
    public Object getEvalReturnObj(){
        return m_BeanShellInterptererThread2.getEvalReturnObj();
    }
   
    //callesd by ScriptJPanel.stopScript() to perform a user abort.
    public void stopParser(){
        try{
            //TODO find appropriate way to stop interpreter
            //interrupt didn't work here, and stop is depricated
           
            /* Thread.stop()
             * The thread represented by this thread is forced to stop whatever
             * it is doing abnormally and to throw a newly created ThreadDeath
             * object as an exception.
             * It is permitted to stop a thread that has not yet been started.
             * If the thread is eventually started, it immediately terminates.
             */
            setParserStopped();
            if(m_BeanShellInterptererThread2.isAlive()){
                writeInfo(ClassServices.getCurrentMethodName() + "STOPPING INTERPRETER");
                m_BeanShellInterptererThread2.stop();
            }
        }catch(Exception e){
           
        }catch(Error err){
           
        }
    }

    /*
     * By setting Interpreter's parser and interpreter variables to null
     * when the Parser completes, or times out, we insure that any script
     * spawned threads that try to access the parser's methods after the
     * script completes, or is stopped; will throw an exception and exit.
     * This prevents unwanted Script spawned Threads executing after the
     * RemoteClient session exits.
     *
     * Called at end of  ScriptManager.scriptStateMachine()
     *
     * */
    public void disableParser(){
        try{
            m_BeanShellInterptererThread2.setInterpreterVar("parser", null);
            m_BeanShellInterptererThread2.setInterpreterVar("interpreter", null);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
   
    /* This method is Overridden in derived subclasses to perform any parser
     * specific cleanup required by that class. This is very usefull as "any"
     * syntax error in a script will cause the beanshell interpreter to exit
     * without executing any catch or finally in the script. This method ends
     * up being the only way to implement finally() for the interpreter.
     */
    protected void cleanUp(){
        //override for specific cleanup needs
    }
   
    /* This method is used to initialize  BeanShellInterpreter local varaibles "
     * before" scriptexecution.
     **/
    public void setVariableValue(String var, Object val){
        try{
            m_BeanShellInterptererThread2.setInterpreterVar(var, val);
        }catch(Exception e){
            writeError(ClassServices.getCurrentMethodName(), e);
        }
    }

    //TODO can we consolidate setVariableValue and this??
    public void addResponseVar(String var, Object value){
        m_ResponseHashMap.put(var, value);
    }
   
    //TODO deprecate?
    public void addResponseHashMap(HashMap hashMap){
        m_ResponseHashMap = hashMap;
    }
   
    /*Scripts pass varaibles back to RemoteClient Matser via addResponseVar()
     * ExecRemoteScriptRequest constructs an ExecRemoteScriptResponse, and calls
     * getResponseHashMap() to get the HashMap to pass back
    */
    public HashMap getResponseHashMap(){
        return m_ResponseHashMap;
    }
 
    //getParserStopped() is used by scripts to determine in parser has been halted
    public boolean getParserStopped() {
        return m_ParserStopped;
    }
   
    public void setParserStopped() {
        m_ParserStopped = true;
    }

   
    /*
     *
     *                 RemoteParserInterface Methods
     *
     * */
    public boolean getScriptSuccess(){
        return m_RemoteParserInterface.getScriptSuccess();
    }
   
    public void setScriptSuccess(boolean bVal){
        m_RemoteParserInterface.setScriptSuccess(bVal);
    }
   
    public long incrFailCnt(){
        return m_RemoteParserInterface.incrFailCnt();
    }
   
    public long incrSuccessCnt(){
        return m_RemoteParserInterface.incrSuccessCnt();
    }

    public long incrScriptIterationCnt(){
        return m_RemoteParserInterface.incrScriptIterationCnt();
    }

    public void writeOutput(Object obj){
        m_RemoteParserInterface.writeOutput(obj);  
    }

    public void writeOutput(String[] strArray){
        m_RemoteParserInterface.writeOutput(strArray);
    }
   
    public void writeOutput(int[] intArray) {
        m_RemoteParserInterface.writeOutput(intArray);
    }
   
    public void writeInfo(Object obj){
        m_RemoteParserInterface.writeInfo(obj);  
    }
   
    public void writeError(String methodName, Exception e){
        m_RemoteParserInterface.writeError(methodName, e);
    }
   
    public void writeError(Object obj){
        m_RemoteParserInterface.writeError(obj);
    }
   
    public void writeError(String[] strArray){
        m_RemoteParserInterface.writeError(strArray);
    }
   
    public void writeError(int[] intArray){
        m_RemoteParserInterface.writeError(intArray);
    }
   
    public void writeError(StackTraceElement[] stackTraceElementArray){
        StringBuffer sb = new StringBuffer();
        for(int i=0; i< stackTraceElementArray.length; i++){
                 sb.append(stackTraceElementArray[i] + NEWLINE_CHAR);
            }
        m_RemoteParserInterface.writeError(sb.toString());
    }
   
    public void writeWarning(Object obj) {
        m_RemoteParserInterface.writeWarning(obj);
    }

    public void writeDebug(Object obj){
        m_RemoteParserInterface.writeDebug(obj);  
    }
   
    public void writeDebug(String[] strArray){
        m_RemoteParserInterface.writeDebug(strArray);
    }
   
    public void writeLogger(Object obj){
        m_RemoteParserInterface.writeLogger(obj);
    }
   
    public void writeLogger(String[] strArray){
        m_RemoteParserInterface.writeLogger(strArray);
    }
   
    public void writeReporting(Object obj){
        m_RemoteParserInterface.writeReporting(obj);
    }
   
    public void writeStackTrace(Exception e) {
        m_RemoteParserInterface.writeStackTrace(e);  
    }
   
    public void closeLogFiles(){
        //TODO what about console logs
        m_LogManager.closeScriptLogFiles();
    }
   
   
    /*
     *
     *                 ParserInterface Methods
     *
     * */
    public void addJTabbedPaneComponent(String tabTitle, Component component) {
        try{
            if(m_RemoteParserInterface instanceof ParserInterface){
                ((ParserInterface)m_RemoteParserInterface).addJTabbedPaneComponent(tabTitle, component);
            }else{
                m_RemoteParserInterface.writeError(ClassServices.getCurrentMethodName() + "addJTabbedPaneComponent() not supported on RemoteExecution");  
            }
        }catch(Exception e){
            writeError(ClassServices.getCurrentMethodName(), e);
        }
    }
   
    public void enableTab(int index, boolean bval){
        try{
            if(m_RemoteParserInterface instanceof ParserInterface){
                ((ParserInterface)m_RemoteParserInterface).enableTab(index, bval);
            }else{
                m_RemoteParserInterface.writeError(ClassServices.getCurrentMethodName() + "enableTab() not supported on RemoteExecution");  
            }
        }catch(Exception e){
            writeError(ClassServices.getCurrentMethodName(), e);
        }
    }

    public void removeTab(int index){
        try{
            if(m_RemoteParserInterface instanceof ParserInterface){
                ((ParserInterface)m_RemoteParserInterface).removeTab(index);
            }else{
                m_RemoteParserInterface.writeError(ClassServices.getCurrentMethodName() + "removeTab() not supported on RemoteExecution");  
            }
        }catch(Exception e){
            writeError(ClassServices.getCurrentMethodName(), e);
        }
    }
   
    public void removeTab(String tabTitle){
        try{
            if(m_RemoteParserInterface instanceof ParserInterface){
                ((ParserInterface)m_RemoteParserInterface).removeTab(tabTitle);
            }else{
                m_RemoteParserInterface.writeError(ClassServices.getCurrentMethodName() + "removeTab() not supported on RemoteExecution");  
            }
        }catch(Exception e){
            writeError(ClassServices.getCurrentMethodName(), e);
        }
    }
   
    public void setActiveTab(String tabTitle){
        try{
            if(m_RemoteParserInterface instanceof ParserInterface){
                ((ParserInterface)m_RemoteParserInterface).setActiveTab(tabTitle);
            }else{
                m_RemoteParserInterface.writeError(ClassServices.getCurrentMethodName() + "setActiveTab() not supported on RemoteExecution");  
            }
        }catch(Exception e){
            writeError(ClassServices.getCurrentMethodName(), e);
        }
    }  
   
    //used in ExecRemoteScriptRequest/ExecRemoteScriptResponse to track Remote Script time-out
    //TODO we presently dont use this info in executeRemoteScript() intrinsic
    //since we propogate timeout value, the RemoteClient connection will expire before
    //the remote script times out....analyze design here
    public boolean getParserTimedOut(){
        return m_ParserTimeOut;
    }

    /*this method does not need to be synchronized because it is
    called before BeanShellScriptParser is started, and therefor
    is only called by this Thread*/
   public void zeroCounters() {
       //TODO finish
   }

   public static void main(String args[]) {
        int i=0;
       
        try{
            while(true){
                BeanShellCMDParser beanShellCMDParser = new BeanShellCMDParser();
                System.out.println(i++);
            }
        }catch(Exception e){}
    }
   
}