![]() Home | ![]() Back | ![]() Contents | ![]() Next |
There are a number of reasons you might use BeanShell in this way. Here are a few:
Java with loose variables is a very simple and appealing language; especially because there is already so much Java out there. Even a non-programmer will be familiar with the name "Java" and more inclined to want to work with it than an arbitrary new language.
BeanShell can also be used to perform dynamic evaluation of complex expressions such as mathematics or computations entered by the user. Why write an arithmetic expression parser when you can let your user enter equations using intermediate variables, operators, and other constructs. If strict control is desired, you can generate the script yourself using your own rules, and still leave the evaluation to BeanShell.
BeanShell solves the problem of complex configuration files by allowing users to work not only with simple properties style values (loose variable assignment) but also to have the full power of Java to construct objects, arrays, perform loops and conditionals, etc. And as we'll see, BeanShell scripts can work seamlessly with objects from the application, without the need to turn them into strings to cross the script boundary.
More and more people are using BeanShell for embedded applications in small devices. We have reports of BeanShell running everywhere from palm-tops to autonomous buoys in the Pacific ocean!
In "QuickStart" we showed a few examples:
import bsh.Interpreter; Interpreter i = new Interpreter(); // Construct an interpreter i.set("foo", 5); // Set variables i.set("date", new Date() ); Date date = (Date)i.get("date"); // retrieve a variable // Eval a statement and get the result i.eval("bar = foo*10"); System.out.println( i.get("bar") ); // Source an external script file i.source("somefile.bsh"); |
The default constructor for the Interpreter assumes that it is going to be used for simple evaluation. Other constructors allow you to set up the Interpreter to work with interactive sources including streams and the GUI console.
Note: It is not necessary to add a trailing ";" semi-colon at the end of the evaluated string. BeanShell always adds one at the end of the string. |
The result of the evaluation of the last statement or expression in the evaluated string is returned as the value of the eval(). Primitive types (e.g int, char, boolean) are returned wrapped in their primitive wrappers (e.g. Integer, Character, Boolean). If an evaluation of a statement or expression yields a "void" value; such as would be the case for something like a for-loop or a void type method invocation, eval() returns null.
Object result = i.eval( "long time = 42; new Date( time )" ); // Date Object result = i.eval("2*2"); // Integer |
You can also evaluate text from a java.io.Reader stream using eval():
reader = new FileReader("myscript.bsh"); i.eval( reader ); |
try { i.eval( script ); } catch ( EvalError e ) { // Error evaluating script } |
You can get the error message, line number and source file of the error from the EvalError with the following methods:
String getErrorText() { |
int getErrorLineNumber() { |
String getErrorSourceFile() { |
The TargetError contains the "cause" exception. You can retrieve it with the getTarget() method.
try { i.eval( script ); } catch ( TargetError e ) { // The script threw an exception Throwable t = e.getTarget(); print( "Script threw exception: " + t ); } catch ( ParseException e ) { // Parsing error } catch ( EvalError e ) { // General Error evaluating script } |
i.source("myfile.bsh"); |
The Interpreter source() method may throw FileNotFoundException and IOException in addition to EvalError. Aside from that source() is simply and eval() from a file.
It should be noted that get() and set() are capable of evaluation of arbitrarily complex or compound variable and field expression. For example:
import bsh.Interpreter; i=new Interpreter(); i.eval("myobject=object()" ); i.set("myobject.bar", 5); i.eval("ar=new int[5]"); i.set("ar[0]", 5); i.get("ar[0]"); |
The get() and set() methods have all of the evaluation capabilities of eval() except that they will resolve only one variable target or value and they will expect the expression to be of the appropriate resulting type.
The deprecated setVariable() and getVariable() methods are no longer used because the did not allow for complex evaluation of variable names
You can use the unset() method to return a variable to the undefined state.
The following example scripts a global actionPerformed() method and returns a reference to itself as an ActionListener type:
// script the method globally i.eval( "actionPerformed( e ) { print( e ); }"); // Get a reference to the script object (implementing the interface) ActionListener scriptedHandler = (ActionListener)i.eval("return (ActionListener)this"); // Use the scripted event handler normally... new JButton.addActionListener( scriptedHandler ); |
Here we have performed the explicit cast in the script as we returned the reference. (And of course we could have used the standard Java anonymous inner class style syntax as well.)
An alternative would have been to have used the Interpreter getInterface() method, which asks explicitly for the global scope to be cast to a specific type and returned. The following example fetches a reference to the interpreter global namespace and cast it to the specified type of interface type.
Interpreter interpreter = new Interpreter(); // define a method called run() interpreter.eval("run() { ... }"); // Fetch a reference to the interpreter as a Runnable Runnable runnable = (Runnable)interpreter.getInterface( Runnable.class );
The interface generated is an adapter (as are all interpreted interfaces). It does not interfere with other uses of the global scope or other references to it. We should note also that the interpreter does *not* require that any or all of the methods of the interface be defined at the time the interface is generated. However if you attempt to invoke one that is not defined you will get a runtime exception.
The Interpreter class is, in general, thread safe and allows you to work with threads, within the normal bounds of the Java language. BeanShell does not change the normal application level threading issues of multiple threads from accessing the same variables: you still have to synchronize access using some mechanism if necessary. However it is legal to perform multiple simultaneous evaluations. You can also write multi-threaded scripts within the language, as we discussed briefly in "Scripting Interfaces".
Since working with multiple threads introduces issues of synchronization and application structure, you may wish to simply create multiple Interpreter instances. BeanShell Interpreter instances were designed to be very light weight. Construction time is usually negligible and in simple tests, we have found that it is possible to maintain hundreds (or even thousands) of instances.
There are other options in-between options as well. It is possible to retrieve BeanShell scripted objects from the interpreter and "re-bind" them again to the interpreter. We'll talk about that in the next section. You can also get and set the root level bsh.NameSpace object for the entire Interpreter. The NameSpace is roughly equivalent to a BeanShell method context. Each method context has an associated NameSpace object.
Tip: You can clear all variables, methods, and imports from a scope using the clear() command. |
Note: at the time of this writing the synchronized language keyword is not implemented. This will be corrected in an upcoming release.
See also "The BeanShell Parser" for more about performance issues.
Note: There is serious Java bug that affects BeanShell serializability in Java versions prior to 1.3. When using these versions of Java the primitive type class identifiers cannot be de-serialized. See the FAQ for a workaround. |
It is also possible to serialize individual BeanShell scripted objects ('this' type references and interfaces to scripts). The same rules apply. One thing to note is that by default serializing a scripted object context will also serialize all of that object's parent contexts up to the global scope - effectively serializing the whole interpreter.
To detach a scripted object from its parent namespace you can use the namespace prune() method:
// From BeanShell object.namespace.prune(); // From Java object.getNameSpace().prune(); |
To bind a BeanShell scripted object back into a particular method scope you can use the bind() command:
// From BeanShell bind( object, this.namespace ); // From Java bsh.This.bind( object, namespace, interpreter ); |
The bind() operation requires not only the namespace (method scope) into which to bind the object, but an interpreter reference as well. The interpreter reference is the "declaring interpreter" of the object and is used for cases where there is no active interpreter - e.g. where an external method call from compiled Java enters the object.
The BeanShell save() command which serializes objects recognize when you are trying to save a BeanShell scripted object (a bsh.This reference) type and automatically prune()s it from the parent namespace, so that saving the object doesn't drag along the whole interpreter along for the ride. Similarly, load() binds the object to the current scope.
![]() Home | ![]() Back | ![]() Contents | ![]() Next |