Advanced BeanShell Techniques

BeanShell has a few advanced features that we haven't mentioned yet. They will be discussed in this section.

BeanShell's Convenience Syntax

We noted earlier that BeanShell syntax does not require that variables be declared or defined with their type, and that variables that are not typed when first used can have values of differing types assigned to them. In addition to this loose syntax, BeanShell allows a convenience syntax for dealing with the properties of JavaBeans. They may be accessed or set as if they were data members. They may also be accessed using the name of the property enclosed in quotation marks and curly brackets. For example, the following statement are all equivalent, assuming btn is a JButton instance:

b.setText("Choose");
b.text = "Choose";
b{"text"} = "Choose";

The last form can also be used to access a key-value pair of a Hashtable object.

Special BeanShell Keywords

BeanShell uses special keywords to refer to variables or methods defined in the current or an enclosing block's scope:

  • The keyword this refers to the current scope.

  • The keyword super refers to the immediately enclosing scope.

  • The keyword global refers to the top-level scope of the macro script.

The following script illustrates the use of these keywords:

a = "top\n";
foo() {
    a = "middle\n";
    bar() {
        a = "bottom\n";
        textArea.setSelectedText(global.a);
        textArea.setSelectedText(super.a);
        // equivalent to textArea.setSelectedText(this.a):
        textArea.setSelectedText(a);
    }

    bar();
}
foo();

When the script is run, the following text is inserted in the current buffer:

top
middle
bottom

Implementing Classes and Interfaces

As discussed in the macro example in Chapter 14, A Dialog-Based Macro, scripted objects can implicitly implement Java interfaces such as ActionListener. For example:

myRunnable() {
    run() {
        System.out.println("Hello world!");
    }

    return this;
}

Runnable r = myRunnable();
new Thread(r).start();

Frequently it will not be necessary to implement all of the methods of a particular interface in order to specify the behavior of a scripted object. To prevent BeanShell from throwing exceptions for missing interface methods, implement the invoke() method, which is called when an undefined method is invoked on a scripted object. Typically, the implementation of this method will do nothing, as in the following example:

invoke(method, args) {}

In addition to the implicit interface definitions described above, BeanShell permits full-blown classes to be defined. Indeed, almost any Java class definition should work in BeanShell:

class Cons {
    // Long-live LISP!
    Object car;
    Object cdr;

    rplaca(Object car) {
        this.car = car;
    }

    rplacd(Object cdr) {
        this.cdr = cdr;
    }
}