If you just want to start playing around you may be able to to launch the BeanShell desktop by simply double clicking on the bsh JAR file. More generally however you'll want to add the jar to your classpath so that you can work with your own classes and applications easily.
unix: export CLASSPATH=$CLASSPATH:bsh-xx.jar windows: set classpath %classpath%;bsh-xx.jar java bsh.Console // run the graphical desktop or java bsh.Interpreter // run as text-only on the command line |
Note: If you are running under JDK 1.1 and want to use the GUI you'll also have to add the swing JAR file to your classpath.
Once BeanShell is running you can also add to the classpath using the addClassPath() command.
The above examples show how to run bsh interactively. It's also possible to run bsh in a server mode and to embed bsh for non-interactive use in your applications. You can even make executable bsh scripts under Unix using the standard "#!" syntax. See the user's manual for more information on those topics.
Each console window effectively runs a separate bsh interpreter. Within the graphical console you have basic command history, line editing, and cut and paste. From any console window you can open a simple editor window for that console. In it you can write scripts and hit the 'eval' button to evaluate them in the attached workspace.
You can use these exactly as they would appear in Java, however in BeanShell you have the option of working with "loosely typed" variables. That is, you can simply be lazy and not declare the types of variables that you use (both primitives and objects). BeanShell will still give you an error if you attempt to misuse the actual type of the variable.
Here are some examples:
foo = "Foo"; four = (2 + 2)*2/2; print( foo + " = " + four ); // print() is a bsh command // Do a loop for (i=0; i<5; i++) print(i); // Pop up a frame with a button in it b = new JButton("My Button"); f = new JFrame("My Frame"); f.getContentPane().add(b, "Center"); f.pack(); f.show(); |
Here are a few other examples of BeanShell commands:
See the complete list of BeanShell commands for more information.
BeanShell commands are simply methods which are implemented by bsh scripts supplied in the bsh jar file. You can, of course, define your own methods in bsh and also add your own scripts to the classpath to extend the basic command set.
int addTwoNumbers( int a, int b ) { return a + b; } sum = addTwoNumbers( 5, 7 ); // 12 |
Bsh methods may also have dynamic (loose) argument and return types.
add( a, b ) { return a + b; } foo = add(1, 2); // 3 foo = add("Oh", " baby"); // "Oh baby" |
foo() { print("foo"); x=5; bar() { print("bar"); } return this; } myfoo = foo(); // prints "foo" print( myfoo.x ); // prints "5" myfoo.bar(); // prints "bar" |
If this seems strange to you please see the user's manual for more details.
You may also cast your scripted objects (e.g. myFoo above) to any Java interface type and supply the necessary methods in the script.
Note: implementing interface types requires BeanShell be running under a Java 1.3 environment or higher
You may also use the Java anonymous inner class style syntax to implement an interface type with a script.
ActionListener scriptedListener = new ActionListener() { actionPerformed( event ) { ... } } |
Normally, if you leave out methods required by the interface the calling code will throw an exception. However if you wish to override this behavior you can implement the special method invoke(name, args) in your scripted object to handle undefined method invocations:
ml = new MouseListener() { mousePressed( event ) { ... } // handle the rest invoke( name, args ) { print("Method: "+name+" invoked!"); } |
addClassPath | void addClassPath( string | URL ) |
bg | Thread bg( String script ) |
browseClass | void browseClass( String | Object | Class ) |
cat | void cat( String filename ) |
cd | void cd( String dirname ) |
classBrowser | void classBrowser() |
debug | void debug() |
desktop | void desktop() |
dir | void dir( String dirname ) |
error | void error( string ) |
eval | Object eval( String expression ) |
exec | exec( String process ) |
exit | void exit() |
extend | This extend( This object ) |
frame | Frame | JFrame | JInternalFrame frame( Component component ) |
getClass | Class getClass( String ) |
getClassPath | URL [] getClassPath() |
getResource | URL getResource( String path ) |
javap | void javap( String | Object | Class ) |
load | Object load( String filename ) |
object | This object() |
pathToFile | File pathToFile( String filename ) |
void print( arg ) | |
pwd | void pwd() |
reloadClasses | void reloadClasses( [ package name ] ) |
rm | void rm( String pathname ) |
run | This run( String script ) |
save | void save( Component component, String filename ) |
server | void server( int port ) |
setClassPath | void setClassPath( URL [] ) |
setFont | Font setFont( Component comp, int ptsize ) |
setNameCompletion | void setNameCompletion( boolean ) |
show | void show() |
source | void source( String filename ) |
super | This super( String scopename ) |
unset | void unset(String name) |
print void print( arg ) |
Print the string value of the argument, which may be of any type.
If beanshell is running interactively, the output will always go to the
command line, otherwise it will go to System.out.
Most often the printed value of an object will simply be the Java toString() of the object. However if the argument is an array the contents of the array will be (recursively) listed in a verbose way. Note that you are always free to use System.out.println() instead of print(). |
show void show() |
Toggle on or off displaying the results of expressions (off by default). When show mode is on bsh will print() the value returned by each expression you type on the command line. |
frame Frame | JFrame | JInternalFrame frame( Component component ) |
Display the component, centered and packed, in a Frame, JFrame, or JInternalFrame. Returns the frame. If the GUI desktop is running then a JInternaFrame will be used and automatically added to the desktop. Otherwise if Swing is available a top level JFrame will be created. Otherwise a plain AWT Frame will be created. |
load Object load( String filename ) |
Load a serialized Java object from filename. Returns the object. |
save void save( Component component, String filename ) |
Save a serializable Java object to filename. |
cat void cat( String filename ) |
Print the contents of filename (like Unix cat) |
exec exec( String process ) |
Get the Java Runtime and start the external process, display any output. Note: process handling will get more sophisticated in the future |
pwd void pwd() |
Print the bsh working directory. This is the cwd obeyed by all the unix like bsh comands. |
rm void rm( String pathname ) |
Remove the file (like Unix rm) |
unset void unset(String name) |
"undefine" the variable specifed by 'name' (So that it tests == void).
Note: there will be a better way to do this in the future. This is currently equivalent to doing namespace.setVariable(name, null); |
cd void cd( String dirname ) |
Change working directory for the dir() command (like Unix cd) |
dir void dir( String dirname ) |
Display the contets of directory dirname. The format is similar to the Unix ls -l command. dir void dir() Display the contents of the current working directory. The format is similar to the Unix ls -l |
pathToFile File pathToFile( String filename ) |
Create a File object corresponding to the specified file path name, taking into account the bsh current working directory (bsh.cwd) |
source void source( String filename ) |
Read filename into the interpreter and evaluate it in the current namespace. Like Bourne Shell "." command. |
eval Object eval( String expression ) |
Evaluate the string in the current interpreter (see source() above). Returns the result of the evaluation or null. |
server void server( int port ) |
Create a Server Mode server attached to the current interpreter, listening on the specified port. |
debug void debug() |
Toggle on and off debug mode... Note: debug output is verbose and gross. |
exit void exit() |
Call System.exit(0); |
getResource URL getResource( String path ) |
The equivalent of calling getResource() on the interpreter class in the bsh package. Use absolute paths to get stuff in the classpath. |
setFont Font setFont( Component comp, int ptsize ) |
Change the point size of the font on the specified component, to ptsize. |
run This run( String script ) |
run() is like source() except that it runs the command in a new, subordinate and prune()'d namespace. So it's like "running" a command instead of "sourcing" it. Returns the object context in which the command was run. |
bg Thread bg( String script ) |
This is like run() except that it runs the command in its own thread. Returns the thread object (for stop()ing, join()ing, etc.) |
object This object() |
object() Returns a new bsh object which you can use to hold data items.
e.g.
myStuff = object(); myStuff.foo = 42; myStuff.bar = "blah"; |
extend This extend( This object ) |
Note: this command will likely change along with a better inheritance mechanism
for bsh in the next release.
extend() is like the object() command, which creates a new bsh scripted object, except that the namespace of the new object is a child of the parent object.
For example: foo=object(); bar=extend(foo); is equivalent to: foo() { bar() { return this; } } foo=foo(); bar=foo.bar(); and also: foo=object(); bar=object(); bar.namespace.bind( foo.namespace ); The last example above is exactly what the extend() command does. In each case the bar object inherits variables from foo in the usual way. |
super This super( String scopename ) |
super( name ) Returns a bsh This reference to the enclosing scope
(method scope) of the specified name.
e.g.
foo() { x=1; bar() { x=2; gee() { x=3; print( x ); // 3 print( super.x ); // 2 print( super("foo").x ); // 1 } } } |
addClassPath void addClassPath( string | URL ) |
Add the specified directory or JAR file to the class path.
e.g.
addClassPath( "/home/pat/java/classes" ); addClassPath( "/home/pat/java/mystuff.jar" ); addClassPath( new URL("http://myserver/~pat/somebeans.jar") ); |
setClassPath void setClassPath( URL [] ) |
Change the classpath to the specified array of directories and/or archives. |
reloadClasses void reloadClasses( [ package name ] ) |
Reload the specified class, package name, or all classes if no name is given.
e.g.
reloadClasses(); reloadClasses("mypackage.*"); reloadClasses(".*") // reload unpackaged classes reloadClasses("mypackage.MyClass") |
browseClass void browseClass( String | Object | Class ) |
Open the class browser to view the specified class.
If the argument is a string it is considered to be a class name. If the argument is an object, the class of the object is used. If the arg is a class, the class is used.
|
classBrowser void classBrowser() |
Open the class browser. |
desktop void desktop() |
Start the BeanShell GUI desktop. |
error void error( string ) |
Print the item as an error. If in the GUI console the text will show up in red, else it will be printed to stderr |
getClassPath URL [] getClassPath() |
Get the current classpath including all user path, extended path, and the boostrap JAR file if possible. |
javap void javap( String | Object | Class ) |
print the public fields and methods of the specified class (output similar
to the JDK javap command).
If the argument is a string it is considered to be a class name. If the argument is an object, the class of the object is used. If the arg is a class, the class is used. |
setNameCompletion void setNameCompletion( boolean ) |
Turn name completion in the GUI console on or off. Name competion is on by default. Explicitly setting it to true however can be used to prompt bsh to read the classpath and provide immediate feedback. (Otherwise this may happen behind the scenes the first time name completion is attempted). Setting it to false will disable name completion. |
getClass Class getClass( String ) |
This is equivalent to the standard Class.forName() method for class loading, however it takes advantage of the BeanShell class manager so that added classpath will be taken into account. You can also use Class.forName(), however if you have modified the classpath or reloaded classes from within your script the modifications will only appear if you use the getClass() command. |
Standard Java Syntax
At the prompt you can type normal Java statements and expressions and
display the results.
Statements and expressions are the kinds of things you normally find
inside of a Java method - variable assignments, method calls, math
expressions, for-loops, etc., etc.
Here are some examples:
// Use a hashtable Hashtable h = new Hashtable(); Date d = new Date(); h.put("today", d); // Print the current clock value print( System.currentTimeMillis() ); // Loop for (int i=0; i<5; i++) print(i); // Pop up an AWT frame with a button in it JButton b = new JButton("My Button"); JFrame f = new JFrame("My Frame"); f.getContentPane()add(b, "Center"); f.pack(); f.show(); |
Loose Java Syntax
In the examples above, all of our variables have declared types - e.g.
'JButton b'. Beanshell will enforce these types, as you will see if you
later try to
assign something other than a JButton to the variable 'b' (you will get
an error message).
However bsh also supports dynamically typed variables - that is, you can
refer to
variables without declaring them first and without specifying any type.
In this case bsh will do type checking where appropriate at "runtime". So,
for example, we could have left off the types in the above example and
simply said something like:
foo = new JButton("Another Button"); |
We are then free to assign 'foo' to another type of Java object later. Untyped bsh variables can also freely hold Java primitive values like int and boolean. Don't worry, bsh always knows the real types and only lets you use the values where appropriate. For primitive types this includes doing the correct numeric promotion that the real Java language would do when you use them in an expression.
In bsh you may access JavaBean properties as if they were fields:
b = new java.awt.Button(); b.label = "my button"; // Equivalent to: b.setLabel("my button");Of course if there is a field of the same name (e.g. label in the above example) then it would take precedence.
If you wish to avoid ambiguity Bsh provides an additional, uniform syntax for accessing Java Bean properties and Hashtable entries. You may use the "{}" curly brace construct with a String identifier as a qualifier on any variable of the appropriate type:
b = new java.awt.Button(); b{"label"} = "my button"; // Equivalent to: b.setLabel("my button"); h = new Hashtable(); h{"foo"} = "bar"; // Equivalent to: h.put("foo", "bar"); |
Importing classes and packages
In BeanShell as in Java, you can either refer to classes by their fully
qualified names, or you can import one or more classes
from a Java package.
import mypackage.MyClass; or import mypackage.*; |
You may also automatically import the entire classpath using:
import *; |
See Class Path Management for details.
By default, several Java core and extension packages are imported for you. They are:
int addTwoNumbers( int a, int b ) { return a + b; } |
and use them immediately as you would any bsh command:
sum = addTwoNumbers( 5, 7 ); |
Just as bsh variables may be dynamically typed, bsh methods may have dynamic argument and return types. We could, for example, have declared our method like so:
add( a, b ) { return a + b; } |
In this case, bsh would dynamically determine the types when the method is called and "do the right thing":
foo = add(1, 2); print( foo ); // 3 foo = add("Oh", " baby"); print( foo ); // Oh baby |
In the first case Java performed arithmetic addition on the integers 1 and 2. By the way, if we had passed in numbers of other types bsh would have performed the appropriate numeric promotion and returned the right type. In the second case bsh performed the usual string concatenation for String types and returned a String object.
Methods with unspecified return types may return any type of object (as in the previous example). Alternatively they may also simply issue a "return;" without a value, in which case the effective type of the method is "void" (no type). In either case, the return statement is optional. If the method does not perform an explicit "return" statement, the value of the last statement or expression in the method body becomes the return value (and must adhere to any declared return typing).
Note:
The namespaces for methods and variables are separate, so it is ok to have a method named "foo()" and variable named "foo" within the same scope. |
Scope modifiers: this, super, global
Within a method, it is possible to explicitly qualify a variable or method reference with the values 'this', 'super', and 'global' to refer to, respectively, the current scope, the calling method's scope, or the global scope.
a = 42; foo() { a = 97; print( a ); print( this.a ); print( super.a ); } foo(); // prints 97, 97, 42 |
In the case above, 'global' and 'super' both refer to the top level scope.
We'll refer to instances of the special 'this', 'super', and 'global' references as "This" type references.
As in most languages, executing bsh methods have their own "local" scope that holds argument variables and locally declared variables. For example, any variables that we might have declared within our add() method above would normally only be visible within the scope of and for the lifetime of a particular method invocation:
foo() { bar = 42; print( bar ); } foo(); // prints 42 print( bar ); // Error, var is undefined here |
However, in BeanShell you can "hang on" to this scope after exiting the method by returning the special value: "this". As in Java, "this" refers to the current object context. By saving the "this" reference, you can continue to refer to variables defined within the method, using the standard Java "." notation:
foo() { bar = 42; return this; } obj = foo(); print( obj.bar ); // 42 |
In the above, the value returned by the foo() method is effectively an instance of a "foo" object. In this case foo is really just acting like a structure, but bsh methods are also allowed to contain their own methods:
foo() { bar() { ... } } |
Method may define any number of local methods in this way, to an arbitrary depth.
Statements and expressions within a Bsh method can call their own "local" methods just like any other method. (Locally declared methods shadow outer-more methods like local variables shadow outer variables.) And we can invoke local methods of objects through an appropriate 'this' reference for that object:
foo() { int a = 42; bar() { print("The bar is open!"); } bar(); return this; } obj = foo(); // prints "the bar is open!" print ( obj.a ) // 42 obj.bar(); // prints "the bar is open!" |
Within a nested method, locally declared variables at each level of scope shadow methods of the same name in outer-more scopes. Otherwise, variables are visible to an arbitrary depth of scope.
button = new java.awt.Button("foo!"); actionPerformed( event ) { print( event ); } button.addActionListener( this ); frame( button ); // show it |
Now when ActionEvents are fired by the button, your actionPerformed() method will be invoked. The bsh 'this' reference implements the interface and directs them to the appropriately named method, if it exists.
But you don't have to define all of your interface methods globally. You can get callbacks in any bsh object scope. For example, the following method creates a button that displays a message when pushed:
messageButton( message ) { b = new Button("Press Me"); b.addActionListener( this ); frame(b); actionPerformed( e ) { print( message ); } } messageButton("Hey you!"); messageButton("Another message..."); |
The above will create two buttons and each will display its own message when pushed. Each has a separate instance of the event handler object. Note too that we could return a 'this' reference from the handler method and use it in other contexts to register listeners...
Anonymous inner class style
More generally you can use the Java anonymous inner class style syntax
to construct a scripted object implementing an interface:
buttonHandler = new ActionListener() { actionPerformed( event ) { print(event); } }; |
See also the dragText example.
Details
What happens if I don't implement all of the methods of an interface?
You can implement the special method invoke() which will be called when a method of the interface is not found. e.g.
mouseHandler = new MouseListener() { mousePressed( event ) { print("mouse button pressed"); } invoke( method, args ) { print("Undefined method of MouseListener interface invoked:" + name +", with args: "+args ); } }; |
What kind of events can bsh handle in this way?
When running under JDK1.3 or greater BeanShell can handle any kind of event (serve as any kind of event listener) because bsh scripts are capable of implementing arbitrary Java interfaces. However when running under JDK1.2 (or JDK1.1 + Swing) only the core AWT and Swing interfaces are available. In the pre-JDK1.3 case the bsh.This object implements these interfaces (along with Runnable, etc.) directly.
foo() { run() { // do work... } return this; } foo = foo(); new Thread( foo ).start(); |
You can also use the bg() command to run an external script in a separate thread. example... FINISH
Special variables
You can test to see if a variable is "undefined" with the value "void", e.g.:
if ( foobar == void ) // undefined |
You can return a defined variable to the undefined state using the unset() command:
a == void; // true a=5; unset("a"); // note the quotes a == void; // true |
Special features of 'This' type references
'This' type references have four special "magic" members:
These are mainly for internal use by bsh commands. Note that there are certain special situations in which the ".interpreter" reference may not be available, such as in AWT event handlers (see the next section).
Add the specified directory or archive to the classpath. Archives may be located by URL, allowing them to be loaded over the network.
Examples:
addClassPath( "/home/pat/java/classes" ); addClassPath( "/home/pat/java/mystuff.jar" ); addClassPath( new URL("http://myserver/~pat/somebeans.jar") );Note that if you add class path that overlaps with the existing Java user classpath then the new path will effectively reload the classes in that area.
setClassPath( URL [] )
Change the entire classpath to the specified array of directories and/or archives.
This command has some important side effects. It effectively causes all classes to be reloaded (including any in the Java user class path at startup). Please see "Class Reloading" below for further details.
Note: setClassPath() cannot currently be used to make the classpath smaller than the Java user path at startup.
Auto-Importing from the Classpath
As an alternative to explicitly importing class names you may use the
following statement to trigger automatic importing:
import *;There may be a significant delay while the class path is mapped. This is why auto-importing is not turned on by default. When run interactively, Bsh will report the areas that it is mapping.
It is only necessary to issue the auto-import command once. Thereafter changes in the classpath via the addClassPath() and setClassPath() commands will remap as necessary.
Note: As of BeanShell 1.1alpha new class files added to the classpath
(from outside of BeanShell) after mapping will not be seen in imports.
Reloading Classes
BeanShell provides an easy to use mechanism for reloading classes from the
classpath. It is possible in BeanShell to reload arbitary subsets of classes
down to a single class file. However There are subtle issues to be understood
with respect to what it means to reload a class in the Java environment.
Please see the discussion of class loading detail below. But in a nutshell,
it is important that classes which work together be reloaded together at
the same time, unless you know what you are doing.
reloadClasses( [ package name ] )
The most course level of class reloading is accomplished by issuing the reloadClasses() command with no arguments.
reloadClasses();This will effectively reload all classes in the current classpath (including any changes you have made through addClassPath()).
Note: that reloading the full path is actually a light weight operation that simply replaces the class loader - normal style class loading is done as classes are subsequently referenced.
Be aware that any object instances which you have previously created may not function with new objects created by the new class loader. Please see the discussion of class loading details below.
You can also reload all of the classes in a specified package:
reloadClasses("mypackage.*");This will reload only the classes in the specified package. The classes will be reloaded even if they are located in different places in the claspath (e.g. if you have some of the package in one directory and some in another).
As a special case for reloading unpackaged classes the following commands are equivalent:
reloadClasses(".*") reloadClasses("")
You can also reload just an individual class file:
reloadClasses("mypackage.MyClass")Note: As of alpha1.1 classes contained in archives (jar files) cannot be reloaded. i.e. jar files cannot be swapped.
Class Loading in Java
A fundamental Java security proposition is that classes may only be loaded
through a class loader once and that classes loaded through different class
loaders live in different name spaces. By different name spaces I mean that
they are not considered to be of the same type, even if they came from the
very same class file.
You can think of this in the following way: When you load classes through a new class loader imagine that every class name is prefixed with the identifier "FromClassLoaderXXX" and that all internal references to other classes loaded through that class loader are similarly rewritten. Now if you attempt to pass a reference to a class instance loaded through another class loader to one of your newly loaded objects, it will not recognize it as the same type of class.
BeanShell works with objects dynamically through the reflection API, so
your scripts will not have a problem recognizing reloaded class objects.
However any objects which have you already created might not like them.
More, etc...
Class Loading in BeanShell
The following is a discussion of the BeanShell class loader architecture,
which allows both course class path extension and fine grained individual
class reloading...
Thriftiness - Abiding by the BeanShell thriftiness proposition: no class loading code is exercised unless directed by a command. BeanShell begins with no class loader and only adds class loading in layers as necessay to achieve desired effects.
The following diagram illustrates the two layer class loading scheme:
A "base" class loader is used to handle course changes to the classpath including added path. Unless directed by setClassPath() the base loader will only add path and will not cover existing Java user class path. This prevents unecessary class space changes for the existing classes.
Packages of classes and individual classes are mapped in sets by class loaders capable of handling discrete files. A mapping of reloaded classes is maintained. The discrete file class loaders will also use this mapping to resolve names outside there space, so when any individual class is reloaded it will see all previously reloaded classes as well.
The BshClassManager knows about all class loader changes and broadcasts notification of changes to registered listeners. BeanShell namespaces use this mechanism to dereference cached type information, however they do not remove existing object instances.
Type caching is extremely important to BeanShell performance. So changing the classloader, which necessitates clearing all type caches, should be considered an expensive operation.
The BeanShell Desktop is a simple GUI environment that provides multiple bsh shell windows (MDI), a simple text editor, and a simple class browser. The desktop is mostly implemented by bsh scripts.
The bsh console windows provide simple command line editing, history, cut & paste, and variable and class name completion.
import bsh.Interpreter; ... Interpreter i = new Interpreter(); i.set("foo", 5); i.eval("bar = foo*10"); System.out.println("bar = "+i.get("bar") ); |
Note: The trailing ";" semi-colon in the string containing the bsh statement to be evaluated is optional in this mode (bsh simply always adds one).
Embedding in Server Mode is the same, coming...
More coming...
Server mode lets you access a bsh session inside of a remote VM. You can literally telnet into the application and type commands at the bsh shell prompt. Or even better you can use your a web browser to bring up a remote console.
After starting the bsh server you can connect your web browser to the specified port. Bsh will act as an httpd and send over a remote console applet. You will get a bsh session that looks like the regular console applet, but it will be running remotely in the originating VM. You can open as many sessions into that VM as you like in this way...
Special Notes: In server mode bsh actually runs *two* services: a (minimal) httpd at the port you specify and a session server at your port + 1. You can actually connect to the session server with telnet, if you want to try it out...(you have to type ";"s manually at the end of every line).
More coming...
#!/usr/java/bin/java bsh.Interpreter print("foo"); |
#!/bin/sh #! The following hack allows java to reside anywhere in the PATH. //bin/sh -c "exec java bsh.Interpreter $0 $*"; exit print("foo"); |
Adding to the set of "prefab" commands supplied with bsh can be about as easy as writing any other bsh methods. You simply have to place your bsh scripts in a bsh/commands/ directory in the classpath (or inside the JAR file).
More info coming
I will try to start listing as many as possible here, but I will forget, so please feel free to remind me (don't be modest, everyone who contributes deserves to be mentioned).
Pat Niemeyer (pat@pat.net)
If you like BeanShell check out my book:
Exploring Java, O'Reilly & Associates, 2nd edition.
Third edition coming soon!