Home

Back

Contents

Next

Scripting Interfaces

One of the most powerful features of BeanShell is the ability to script Java interfaces. This feature allows you to write scripts that serve as event handlers, listeners, and components of other Java APIs. It also makes calling scripted components from within your applications easier because they can be made to look just like any other Java object.

Anonymous Inner-Class Style

One way to get a scripted component to implement a Java interface is by using the standard Java anonymous inner class syntax to construct a scripted object implementing the interface type. For example:

buttonHandler = new ActionListener() {
    actionPerformed( event ) { 
        print(event);
    }
};

button = new JButton();
button.addActionListener( buttonHandler );
frame(button);

In the above example we have created an object that implements the ActionListener interface and assigned it to a variable called buttonHandler. The buttonHandler object contains the scripted method actionPerformed(), which will be called to handle invocations of that method on the interface.

Note that in the example we registered our scripted ActionListener with a JButton using its addActionListener() method. The JButton is, of course, a standard Swing component written in Java. It has no knowledge that when it invokes the buttonHandler's actionPerformed() method it will actually be causing the BeanShell interpreter to run a script to evaluate the outcome.

To generalize beyond this example a bit - Scripted interfaces work by looking for scripted methods to implement the methods of the interface. A Java method invocation on a script that implements an interface causes BeanShell to look for a corresponding scripted method with a matching signature (name and argument types). BeanShell then invokes the method, passing along the arguments and passing back any return value. When BeanShell runs in the same Java VM as the rest of the code, you can freely pass "live" Java objects as arguments and return values, working with them dynamically in your scripts; the integration can be seamless.

See also the dragText example.

'this' references as Interface Types

The anonymous inner class style syntax which we just discussed allows you to explicitly create an object of a specified interface type, just as you would in Java. But BeanShell is more flexible than that. In fact, within your BeanShell scripts, any 'this' type script reference can automatically implement any interface type, as needed. This means that you can simply use a 'this' reference to your script or a scripted object anywhere that you would use the interface type. BeanShell will automatically "cast" it to the correct type and perform the method delegation for you.

For example, we could script an event handler for our button even more simply using just a global method, like this:

actionPerformed( event ) {
    print( event );
}

button = new JButton("Foo!");
button.addActionListener( this );
frame( button ); 

Here, instead of making a scripted object to hold our actionPerformed() method we have simply placed the method in the current context (the global scope) and told BeanShell to look there for the method.

Just as before, when ActionEvents are fired by the button, your actionPerformed() method will be invoked. The BeanShell 'this' reference to our script implements the interface and directs method invocations to the appropriately named method, if it exists.

Note:
If you want to have some fun, try entering the previous example interactively in a shell or on the command line. You'll see that you can then redefine actionPerformed() as often as you like by simply entering the method again. Each button press will find the current version in your shell. In a sense, you are working inside a dynamic Java object that you are creating and modifying as you type. Neat, huh? Be the Bean!

Of course, you don't have to define all of your interface methods globally. You can create references in any scope, as we discussed in "Scripting Objects". For example, the following code creates a scripted message button object which displays a message when its pushed. The scripted object holds its own actionPerformed() method, along with a variable to hold the Frame used for the GUI:

messageButton( message ) {
    JButton button = new JButton("Press Me");
    button.addActionListener( this );
    JFrame frame = frame( button );
 
    actionPerformed( e ) {
        print( message );
        frame.setVisible(false);
    }
}

messageButton("Hey you!");
messageButton("Another message...");

The above example creates two buttons, with separate messages. Each button prints its message when pushed and then dismisses itself. The buttons are created by separate calls to the messageButton() method, so each will have its own method context, separate local variables, and a separate instance of the ActionListener interface handler. Each registers itself (its own method context) as the ActionListener for its button, using its own 'this' reference.

In this example all of the "action" is contained in messageButton() method context. It serves as a scripted object that implements the interface and also holds some state, the frame variable, which is used to dismiss the GUI. More generally however, as we saw in the "Scripting Objects" section, we could have returned the 'this' reference to the caller, allowing it to work with our messageButton object in other ways.

Interface Types and Casting

It is legal, but not usually necessary to perform an explicit cast of a BeanShell scripted object to an interface type. For example:

actionPerformed( event ) {
    print( event );
}

button.addActionListener( 
    (ActionListener)this ); // added cast

In the above, the cast to ActionListener would have been done automatically by BeanShell when it tried to match the 'this' type argument to the signature of the addActionListener() method.

Doing the cast explicitly has the same effect, but takes a different route internally. With the cast, BeanShell creates the necessary adapter that implements the ActionListener interface first, at the time of the cast, and then later finds that the method is a perfect match.

What's the difference? Well, there are times where performing an explicit cast to control when the type is created may be important. Specifically, when you are passing references out of your script, to Java classes that don't immediately use them as their intended type. In our earlier discussion we said that automatic casting happens "within your BeanShell scripts". And in our examples so far BeanShell has always had the opportunity to arrange for the scripted object to become the correct type, before passing it on. But it is possible for you to pass a 'this' reference to a method that, for example, takes the type 'Object', in which case BeanShell would have no way of knowning what it was destined for later. You might do this, for example, if you were placing your scripted objects into a collection (Map or List) of some kind. In that case, you can control the process by performing an explicit cast to the desired type before the reference leaves your script.

Another case where you may have to perform a cast is where you are using BeanShell in an embedded application and returning a scripted object as the result of an eval() or a get() variable from the Interpreter class. There again is a case where BeanShell has no way of knowing the intended type within the script. By performing an explicit cast you can create the type before the reference leaves your script.

We'll discuss embedded applications of BeanShell in the "Embedding BeanShell" section a bit later, along with the Interpreter getInterface() method, which is another way of accomplishing this type of cast from outside a script.

"Dummy" Adapters and Incomplete Interfaces

It is common in Java to see "dummy" adapters created for interfaces that have more than one method. The job of a dummy adapter is to implement all of the methods of the interface with stubs (empty bodies), allowing the developer to extend the adapter and override just the methods of interest.

We hinted in our earlier discussion that BeanShell could handle scripted interfaces that implement only the subset of methods that are actually used and that is indeed the case. You are free in BeanShell to script only the interface methods that you expect to be called. The penalty for leaving out a method that is actually invoked is a special run-time exception: java.lang.reflect.UndeclaredThrowableException, which the caller will receive.

The UndeclaredThrowableException is an artifact of Java Proxy API that makes dynamic interfaces possible. It says that an interface threw a checked exception type that was not prescribed by the method signature. This is a situation that cannot normally happen in compiled Java. So the Java reflection API handles it by wrapping the checked exception in this special unchecked (RuntimeException) type in order to throw it. You can get the underlying error using the exception's getCause() method, which will, in this case, reveal the BeanShell EvalError exception, reporting that the scripted method of the correct signature was not found.

The invoke() Meta-Method

BeanShell provides a very simple short-hand mechanism for scripting interfaces with large numbers of methods. You can implement the special method invoke( name, args ) in any scripted context. The invoke() method will be called to handle the invocation of any method of the interface that is not defined. For example:

mouseHandler = new MouseListener() {
    mousePressed( event ) { 
        print("mouse button pressed");  
    }

    invoke( method, args ) { 
        print("Undefined method of MouseListener interface invoked:"
            + name +", with args: "+args
        );
    }
};

In the above example we have neglected to implement four of the five methods of the MouseListener interface. They will be handled by the invoke() method, which will simply print the name of the method and its arguments. However since mousePressed() is defined it will be called for the interface.

Here is a slightly more realistic example of where this comes in handy. Let's use the invoke() method to print the names of methods called via the ContentHandler interface of the Java SAX API, while parsing an XML document.

import javax.xml.parsers.*;
import org.xml.sax.InputSource;

factory = SAXParserFactory.newInstance();
saxParser = factory.newSAXParser();
parser = saxParser.getXMLReader();
parser.setContentHandler( this );

invoke( name, args ) {
    print( name );
}

parser.parse( new InputSource(bsh.args[0]) );

By running this script with the XML file as an argument, we can see which of the dozen or so methods of the SAX API are being exercised by the structure of the document, without having to write a stub for each of them.

Tip:
You can use the invoke( name, args ) meta-method directly in your own scope or in the global scope as well, in which case you can handle arbitrary "unknown" method invocations yourself, perhaps to implement your own "virtual" commands. Try typing this on the command line:
  invoke(name,args) { print("Command: "+name+" invoked!"); }
  noSuchMethod(); // prints "Command: noSuchMethod() invoked!"

Threads - Scripting Runnable

BeanShell 'this' type references can implement the standard java.lang.Runnable interface. So you can declare a "run()" method in your bsh objects and make it the target of a Thread:

foo() {
    run() {
        // do work...
    }
    return this;
}

foo = foo();
// Start two threads on foo.run()
new Thread( foo ).start();
new Thread( foo ).start();

BeanShell is thread-safe internally, so as long as your scripts do not explicitly do anything ordinarily non-thread safe (e.g. access shared variables or objects) you can write multi-threaded scripts.

Note:
You can use the bg() "background" command to run an external script in a separate thread. See bg().

Limitations

When running under JDK 1.3 or greater BeanShell can script any kind of Java interface. However when running under JDK 1.2 (or JDK1.1 + Swing) only the core AWT and Swing interfaces are available. To support those legacy cases a special extension of the 'this' reference implementation (the bsh.This class) is loaded which implements these interfaces along with Runnable, statically.


Home

Back

Contents

Next