/* * (c) 2000-2009 Carlos G�mez Rodr�guez, todos los derechos reservados / all rights reserved. * Licencia en license/bsd.txt / License in license/bsd.txt */ package eu.irreality.age; import java.util.*; import eu.irreality.age.bsh.ExtendedBSHInterpreter; import eu.irreality.age.debug.Debug; import eu.irreality.age.debug.ExceptionPrinter; import eu.irreality.age.scripting.ScriptException; import eu.irreality.age.scripting.bsh.BSHScriptException; import eu.irreality.age.util.VersionComparator; import bsh.*; public class ObjectCode { public static String getInterpreterVersion ( ) { return ("EVA 0.2, Beanshell 2.0 beta 2"); } public String toString() { return theCode; } public Object clone() { ObjectCode oc = new ObjectCode ( theCode , codeVersion , theWorld ); oc.permanent = this.permanent; oc.permanentInterpreter = null; oc.isConditionCode = this.isConditionCode; return oc; } public ObjectCode cloneIfNecessary () { if ( permanent ) return (ObjectCode)clone(); else return this; } private String codeVersion; private String theCode; private World theWorld; private boolean isConditionCode = false; //if it is a dynamic condition boolean permanent = true; ExtendedBSHInterpreter permanentInterpreter = null; //legacy behavior public static int INNER_END_RETURNS = 0; //new behavior public static int INNER_END_THROWS = 1; //what does an end() called from another BSH method do? private int endBehavior = INNER_END_THROWS; public void setEndBehavior ( int behavior ) { endBehavior = behavior; } public int getEndBehavior ( ) { return endBehavior; } public void configureEndBehavior ( World w ) { if ( new VersionComparator().compare(w.getParserVersion(),"1.2.3") < 0 ) setEndBehavior(INNER_END_RETURNS); else setEndBehavior(INNER_END_THROWS); } /** * Version may be [string literal]: * - "EVA" (Ensamblador Virtual de Aetheria) * - "BeanShell" * * This constructor is apparently only used by legacy code and by clone() */ public ObjectCode ( String code , String version , World world ) { theCode=code; codeVersion=version; theWorld=world; configureEndBehavior(world); } void resetPermanentInterpreter ( ) { permanentInterpreter = null; } //true si existe la rutina y llegamos al end //for EVA code execution only public boolean run ( String aroutine ) throws EVASemanticException { if ( !codeVersion.equalsIgnoreCase("EVA") ) return false; try { CodeRunner maquina = new CodeRunner ( theCode , theWorld ); return maquina.runCode ( theCode , aroutine ); } catch ( EVASyntaxException syn ) { theWorld.writeError("Error de sintaxis en el c�digo EVA.\n"); theWorld.writeError("En concreto: " + syn.getMessage() + "\n"); } return false; } /** This method is for EVA code execution only, needed data is placed in data segment. @return true: si hemos ejecutado el c�digo correctamente hasta un end. @return false: si no hemos ejecutado bien el c�digo o bien hemos llegado a un continue. @exception EVASemanticException: si el c�digo EVA tira una excepci�n. */ public boolean run ( String aroutine , String dataSegment ) throws EVASemanticException { if ( !codeVersion.equalsIgnoreCase("EVA") ) return false; try { CodeRunner maquina = new CodeRunner ( theCode , theWorld ); return maquina.runCode ( theCode , aroutine , dataSegment ); } catch ( EVASyntaxException syn ) { theWorld.writeError("Error de sintaxis en el c�digo EVA.\n"); theWorld.writeError("En concreto: " + syn.getMessage() + "\n"); } return false; } /** * Makes the given interpreter load the stdfunct.bsh file. * @param i * @throws EvalError */ private void sourceStandardLibrary ( ExtendedBSHInterpreter i ) throws EvalError { try { i.source(this.getClass().getClassLoader().getResource("stdfunct.bsh")); } catch ( java.io.FileNotFoundException fnfe ) { System.err.println("Warning: BeanShell standard function library stdfunct.bsh not found!"); } catch ( java.io.IOException fnfe ) { System.err.println("Warning: BeanShell standard function library stdfunct.bsh couldn't be read!"); } } /** * Sets a variable for each property in the given Entity. * @param i The interpreter in which to set the variables. * @param theCaller The entity whose properties we set the variables with. * @throws EvalError */ private void setPropertyVariables ( ExtendedBSHInterpreter i , Object theCaller ) throws EvalError { //set entity properties for convenience IF entity passed //further sets in the code will override these if present Entity context = null; if ( theCaller instanceof Entity ) context = (Entity)theCaller; if ( context != null ) { List pList = context.getProperties(); for ( int k = 0 ; k < pList.size() ; k++ ) { PropertyEntry pe = (PropertyEntry) pList.get(k); i.set ( pe.getName() , pe.getValueAsBoolean() ); } } } /** * Makes the interpreter set variables named arg0, arg1, ... , argk and returns the string "arg0, arg1, ..., argk" that is used to call the method via source() * @param i * @param theArguments * @return * @throws EvalError */ private String prepareArguments ( ExtendedBSHInterpreter i , Object[] theArguments ) throws EvalError { //set the arguments! String argString = " "; for ( int k = 0 ; k < theArguments.length ; k++ ) { i.set("arg" + k , theArguments[k]); argString += ( (k>0?", arg":"arg") + k ); } return argString; } /** * Processes an Object[][] which is an array of pairs (String,Objects); and sets in the interpreter * the variables named by the Strings to the values in the Objects. * @param i * @param initializations * @throws EvalError */ private void doInitializations ( ExtendedBSHInterpreter i , Object[][] initializations ) throws EvalError { for ( int w = 0 ; w < initializations.length ; w++ ) { Object[] curInit = initializations[w]; if ( curInit.length < 2 ) continue; i.set ( (String)curInit[0] , curInit[1] ); } } /** * Sets the standard variables obj, self and world in bsh code. * @param i Interpreter to set the variables in. * @param theCaller Entity that called the bsh code. * @throws EvalError */ private void setStandardVariables ( ExtendedBSHInterpreter i , Object theCaller ) throws EvalError { i.set("obj",theCaller); i.set("self",theCaller); i.set("world",theWorld); } /**This attribute is used to control the execution of initFakeInterpreter() so that it is executed only once.*/ private static boolean fakeInitializationDone = false; /** * This method is used to prevent a bounded memory leak caused by BeanShell. * The first time an Interpreter is initialized, BeanShell keeps a static reference to it (as a kind of fallback interpreter, I think). * This prevents the GC from collecting anything that is linked from variables in the scripted context of that interpreter. * Therefore, we use this method so that the first interpreter we create in a given session is "fake" and we don't really use it, * so that variables are not created on its context and thus the leak doesn't happen. */ private void initFakeInterpreter() { new ExtendedBSHInterpreter(); fakeInitializationDone = true; } /** * Initializes a beanshell interpreter for a given entity. * This includes loading the standard library, setting the standard variables (world, self) * and setting the property variables. * @param theCaller * @return * @throws EvalError */ private ExtendedBSHInterpreter initInterpreter ( Object theCaller ) throws EvalError { //the following line is to avoid a pitfall with beanshell that can cause a (bounded) memory leak. It's not needed to understand this code, since it does nothing that has an influence //on the working of this class: if ( !fakeInitializationDone ) initFakeInterpreter(); ExtendedBSHInterpreter i; i = new ExtendedBSHInterpreter(); permanentInterpreter = i; sourceStandardLibrary(i); setStandardVariables(i,theCaller); setPropertyVariables(i,theCaller); return i; } /** This method is for BeanShell code execution only. With aroutine != null, evaluates the code and executes the given routine with no parameters: *Throwing any non-end exception found as a TargetError, *Returning true if an end exception (BSHCodeExecutedOKException) is found, *Returning false if code is successfully executed with no end exception found (event not exclusive) or if code cannot be executed (nonexistent routine...) With aroutine == null, just evaluates the code. */ public boolean run ( String aroutine , Object theCaller , Object[] theArguments ) throws ScriptException { return run ( aroutine , theCaller , theArguments , null ); //unified with method below as of 2011-03-26, delete the following if it works /* if ( !codeVersion.equalsIgnoreCase("BeanShell") ) return false; try { ExtendedBSHInterpreter i; if ( permanent && permanentInterpreter != null ) { //Debug.println("Using permanent for " + aroutine + " at " + theCaller); i = permanentInterpreter; } else { //Debug.println("Using nonpermanent for " + aroutine + " at " + theCaller); i = initInterpreter(theCaller); i.eval(theCode); if ( aroutine == null ) { return false; //no se nos pide ejecutar una rutina. No se encontr� end. } setStandardVariables(i,theCaller); } //see if routine actually exists! if ( !existsMethod ( i , aroutine , theArguments ) ) return false; //set the arguments! String argString = prepareArguments(i,theArguments); debugInfo ( aroutine , theCaller , theArguments ); i.eval(aroutine + "(" + argString + ")"); //TODO: Why doesn't this throw EvalError properly in Zendyr's serverintro? } catch ( TargetError te ) //excepci�n tirada a prop�sito por el script { Throwable lastExcNode = te; while ( lastExcNode instanceof TargetError ) lastExcNode = ((TargetError)lastExcNode).getTarget(); if ( lastExcNode instanceof BSHCodeExecutedOKException ) return true; //lleg� al end else throw te; } catch ( EvalError pe ) { reportEvalError(pe,aroutine,theCaller,theArguments); } catch ( Exception e ) { theWorld.writeError("Catched the following exception: " + e); e.printStackTrace(); } return false; */ } /** * Evaluates some expression in the context of this code, not necessarily a function call. * @param code Code to evaluate. * @param theCaller Entity in which the code is evaluated. * @param retval Parameter that will hold the return value. * @return * @throws TargetError */ public boolean evaluate ( String code , Object theCaller , ReturnValue retval ) throws ScriptException { if ( !codeVersion.equalsIgnoreCase("BeanShell") ) return false; try { ExtendedBSHInterpreter i; if ( permanent && permanentInterpreter != null ) { i = permanentInterpreter; } else { //System.out.println("Using a nonpermanent for " + aroutine + " at " + theCaller); i = initInterpreter(theCaller); i.eval ( theCode ); setStandardVariables(i,theCaller); } Object returned = i.eval(code); retval.setRetVal(returned); return false; } catch ( TargetError te ) //excepci�n tirada a prop�sito por el script { Throwable lastExcNode = te; while ( lastExcNode instanceof TargetError ) lastExcNode = ((TargetError)lastExcNode).getTarget(); if ( lastExcNode instanceof BSHCodeExecutedOKException ) return true; //lleg� al end else throw new BSHScriptException(te); } catch ( EvalError pe ) { reportEvalError(pe,null,theCaller,null); } catch ( Exception e ) { theWorld.writeError("Catched the following exception: " + e); e.printStackTrace(); } return false; } /** This method is for BeanShell code execution only. With aroutine != null, evaluates the code and executes the given routine with no parameters: *Throwing any non-end exception found as a TargetError, *Returning true if an end exception (BSHCodeExecutedOKException) is found, *Returning false if code is successfully executed with no end exception found (event not exclusive) or if code cannot be executed (nonexistent routine...) With aroutine == null, just evaluates the code. ReturnValue holds the routine or code (usually predicate) return value, if any. */ public boolean run ( String aroutine , Object theCaller , Object[] theArguments , ReturnValue retval ) throws ScriptException { if ( !codeVersion.equalsIgnoreCase("BeanShell") ) return false; try { ExtendedBSHInterpreter i; if ( permanent && permanentInterpreter != null ) { i = permanentInterpreter; } else { //System.out.println("Using a nonpermanent for " + aroutine + " at " + theCaller); i = initInterpreter(theCaller); Object returned = i.eval ( theCode ); if ( aroutine == null ) { if (retval != null ) retval.setRetVal ( returned ); return false; //OK, no se nos pidi� ejecutar una rutina, se ejecut� el c�digo, se guard� el valor de retorno y no se encontr� end. } setStandardVariables(i,theCaller); } if ( !existsMethod ( i , aroutine , theArguments ) ) return false; //set the arguments! String argString = prepareArguments(i,theArguments); debugInfo ( aroutine , theCaller , theArguments ); Object returned = i.eval(aroutine + "(" + argString + ")"); if ( retval != null ) retval.setRetVal ( returned ); } catch ( TargetError te ) //excepci�n tirada a prop�sito por el script { Throwable lastExcNode = te; while ( lastExcNode instanceof TargetError ) lastExcNode = ((TargetError)lastExcNode).getTarget(); if ( lastExcNode instanceof BSHCodeExecutedOKException ) return true; //lleg� al end else throw new BSHScriptException(te); } catch ( EvalError pe ) { reportEvalError(pe,aroutine,theCaller,theArguments); } catch ( Exception e ) { theWorld.writeError("Catched the following exception: " + e); e.printStackTrace(); } return false; } /** This method is for BeanShell code execution only. With aroutine != null, evaluates the code and executes the given routine with no parameters: *Throwing any non-end exception found as a TargetError, *Returning true if an end exception (BSHCodeExecutedOKException) is found, *Returning false if code is successfully executed with no end exception found (event not exclusive) or if code cannot be executed (nonexistent routine...) With aroutine == null, just evaluates the code. ReturnValue holds the routine or code (usually predicate) return value, if any. initalizations is an Object[] consistent of {String,Object} pairs to set at init (Object[]'s too) IMPORTANT: This method always runs the full code again (in case it uses the initializations). This is a difference with the other methods which apply permanent interpreter optimization so as not to evaluate the full code. */ public boolean run ( String aroutine , Object theCaller , Object[] theArguments , ReturnValue retval , Object[][] initializations ) throws ScriptException { if ( !codeVersion.equalsIgnoreCase("BeanShell") ) return false; try { ExtendedBSHInterpreter i; if ( permanent && permanentInterpreter != null ) { i = permanentInterpreter; } else { //Debug.println("Using nonpermanent for " + aroutine + " at " + theCaller); i = initInterpreter(theCaller); } doInitializations ( i , initializations ); Object returned = i.eval(theCode); if ( aroutine == null ) { retval.setRetVal ( returned ); return false; //OK, no se nos pidi� ejecutar una rutina, se ejecut� el c�digo, se guard� el valor de retorno y no se encontr� end. } setStandardVariables(i,theCaller); if ( !existsMethod ( i , aroutine , theArguments ) ) return false; //set the arguments! String argString = prepareArguments(i,theArguments); debugInfo ( aroutine , theCaller , theArguments ); returned = i.eval(aroutine + "(" + argString + ")"); retval.setRetVal ( returned ); //Debug.println("Returnin':" + retval.getRetVal()); } catch ( TargetError te ) //excepci�n tirada a prop�sito por el script { Throwable lastExcNode = te; while ( lastExcNode instanceof TargetError ) lastExcNode = ((TargetError)lastExcNode).getTarget(); if ( lastExcNode instanceof BSHCodeExecutedOKException ) return true; //lleg� al end else throw new BSHScriptException(te); } catch ( EvalError pe ) { reportEvalError(pe,aroutine,theCaller,theArguments); } return false; } //todo: maybe check World getDebugMode() and then println private void debugInfo ( String aroutine , Object theCaller , Object[] theArguments ) { Debug.printlnCodeDebugging("Calling BSH method: " + aroutine); Debug.printlnCodeDebugging("On object: " + theCaller); Debug.printlnCodeDebugging("With arguments:"); if ( theArguments.length == 0 ) Debug.printlnCodeDebugging("(no arguments"); else { for ( int i = 0 ; i < theArguments.length ; i++ ) { Debug.printlnCodeDebugging("#" + (i+1) + ": " + prettyPrint(theArguments[i])); } } } private String prettyPrint(Object o) { if ( o == null ) return "null"; String theClass = o.getClass().getSimpleName(); return theClass + ": " + o; } void reportEvalError ( EvalError pe , String aroutine , Object theCaller , Object[] theArguments ) { String methodInfo = aroutine; if ( methodInfo == null && isConditionCode ) methodInfo = "(dynamic name or description condition code)"; ExceptionPrinter.reportEvalError(pe, theWorld, methodInfo, theCaller, theArguments); /* theWorld.writeError("Error de sintaxis en el c�digo BeanShell.\n"); //theWorld.writeError("En concreto: " + pe + "\n"); theWorld.writeError("Error: "+pe.getMessage()+"\n"); //theWorld.writeError("["+pe.getErrorSourceFile()+"]"); theWorld.writeError("En: c�digo del objeto " + theCaller + "\n"); //System.err.println(pe.getMessage()); //pe.printStackTrace(); theWorld.writeError("Cargado para llamar la rutina: " + aroutine + "\n"); //theWorld.writeError("Objeto del c�digo: " + theCaller + "\n"); theWorld.writeError("Con argumentos: "); for ( int i = 0 ; i < theArguments.length ; i++ ) theWorld.writeError(theArguments[i] + " "); theWorld.writeError("\n"); */ } public org.w3c.dom.Node getXMLRepresentation ( org.w3c.dom.Document doc ) { return getXMLRepresentation ( doc , "Code" ); } public org.w3c.dom.Node getXMLRepresentation ( org.w3c.dom.Document doc , String tagName ) { org.w3c.dom.Element suElemento = doc.createElement( tagName ); org.w3c.dom.Text t = doc.createTextNode( theCode ); suElemento.appendChild(t); suElemento.setAttribute ( "language" , String.valueOf( codeVersion ) ); return suElemento; } public ObjectCode ( World mundo , org.w3c.dom.Node n ) throws XMLtoWorldException { this ( mundo , n , false ); } public ObjectCode ( World mundo , org.w3c.dom.Node n , boolean isConditionCode ) throws XMLtoWorldException { this.isConditionCode = isConditionCode; if ( ! ( n instanceof org.w3c.dom.Element ) ) { throw ( new XMLtoWorldException ("Code node not Element") ); } org.w3c.dom.Element e = (org.w3c.dom.Element) n; if ( ! e.hasAttribute("language") ) { throw ( new XMLtoWorldException ("Code node lacks language attribute") ); } codeVersion = e.getAttribute("language"); org.w3c.dom.Node cdataNode = e.getFirstChild(); if ( cdataNode != null ) theCode = cdataNode.getNodeValue(); //should be a Text else theCode = ""; theWorld = mundo; configureEndBehavior(mundo); } /** * Checks for existence of a specified method in the beanshell code. * @param methodName Name of the required method. * @param theCaller Entity that owns the bsh method. * @param arguments Arguments to be passed to the method. * @return */ public boolean existsMethod ( String methodName , Object theCaller , Object[] arguments ) { ExtendedBSHInterpreter i; if ( permanent && permanentInterpreter != null ) { i = permanentInterpreter; } else { try { i = initInterpreter(theCaller); i.eval ( theCode ); setStandardVariables(i,theCaller); } catch ( EvalError ee ) { reportEvalError(ee,methodName,theCaller,arguments); return false; } } return existsMethod ( i , methodName , arguments ); } private boolean existsMethod ( Interpreter i , String methodName , Object[] arguments ) { BshMethod[] metodos = i.getNameSpace().getMethods(); int m; for ( m = 0 ; m < metodos.length ; m++ ) { BshMethod method = metodos[m]; if ( method.getName().equals(methodName) && method.getParameterTypes().length == arguments.length ) { boolean correct = true; for ( int k = 0 ; k < arguments.length ; k++ ) { Class argumentClass = null; if ( arguments[k] instanceof bsh.Primitive ) //if code is invoked from the bsh innards, we will get basic type values wrapped in bsh.Primitive { argumentClass = ((bsh.Primitive)arguments[k]).getType(); //this includes a primitive null if null is passed from bsh innards. In that case, we unbox arguments[k] //to simplify the null checks ahead. if ( argumentClass == null ) arguments[k] = null; } else //if code is invoked from AGE, we don't wrap basic type values, we use them directly as arguments { if ( arguments[k] != null ) argumentClass = arguments[k].getClass(); } if ( arguments[k] == null && Object.class.isAssignableFrom(method.getParameterTypes()[k]) ) ; // correct = true; else if ( arguments[k] == null && !Object.class.isAssignableFrom(method.getParameterTypes()[k]) ) correct = false; //null parameter, basic type else if ( ( argumentClass == int.class || argumentClass == Integer.class ) && ( method.getParameterTypes()[k] == int.class || method.getParameterTypes()[k] == Integer.class ) ) ; // correct = true; else if ( ( argumentClass == long.class || argumentClass == Long.class ) && ( method.getParameterTypes()[k] == long.class || method.getParameterTypes()[k] == Long.class ) ) ; // correct = true; else if ( ( argumentClass == boolean.class || argumentClass == Boolean.class ) && ( method.getParameterTypes()[k] == boolean.class || method.getParameterTypes()[k] == Boolean.class ) ) ; // correct = true; else if ( ( argumentClass == char.class || argumentClass == Character.class ) && ( method.getParameterTypes()[k] == char.class || method.getParameterTypes()[k] == Character.class ) ) ; // correct = true; else if ( ( argumentClass == float.class || argumentClass == Float.class ) && ( method.getParameterTypes()[k] == float.class || method.getParameterTypes()[k] == Float.class ) ) ; // correct = true; else if ( ( argumentClass == double.class || argumentClass == Double.class ) && ( method.getParameterTypes()[k] == double.class || method.getParameterTypes()[k] == Double.class ) ) ; // correct = true; else if ( arguments[k] != null && !method.getParameterTypes()[k].isAssignableFrom(argumentClass) ) correct = false; } if ( correct ) break; } } if ( m >= metodos.length ) return false; //no existe el m�todo. else return true; //hicimos break => existe. } }