Home

Back

Contents

Next

Scripted Objects

Many people who use BeanShell use it to write scripts that work with existing Java classes and APIs, or perform other kinds of dynamic activities for their own applications at run-time without the aid of a compiler. Often this means writing relatively unstructured code - for example, a sequence of method invocations or loops, all contained in a single script file or eval() statement. In the previous section we saw that BeanShell is also capable of scripting methods, just like Java. Creating methods and new BeanShell commands (which are just methods in their own files) is the natural progression of organizing your scripts into re-usable and maintainable components.

Beyond methods and structured programming lie, of course, objects and the full breadth of object oriented programming. In Java objects are the products of classes. While BeanShell is compatible with standard Java syntax for statements, expressions, and methods, you can't yet script new Java classes within BeanShell. Instead, BeanShell allows you to script objects as "method closures", similar to the way it is done in Perl 5.x, JavaScript, and other object-capable scripting languages. This style of scripting objects (which we'll describe momentarily) is simple and flows very naturally from the style of scripting methods. The syntax, as you'll see, is a straightforward extension of the standard Java concept of referring to an object with a 'this' reference.

Note:
In standard Java, a method inside of an object (an instance method) may refer to the enclosing object using the special variable 'this'. For example:
    // MyClass.java
    MyClass {
        Object getObject() {
            return this; // return a reference to our object
        }
    }
In the example above, the getObject() method of MyClass returns a reference to its own object instance (an instance of the MyClass object) using 'this'.

The 'this' reference

As in most languages, an executing method in BeanShell has its own "local" scope that holds argument (parameter) variables and locally declared variables. For example, in the following code segment any variables that we might use within the foo() method will normally only be visible within the scope of foo() and for the lifetime of one particular foo() method invocation:

// Define the foo() method:
foo() {
    int bar = 42;
    print( bar );
}   

// Invoke the foo() method:
foo();  // prints 42

print( bar ); // Error, bar is undefined here 

In the above, the bar variable is local to foo() and therefore not available outside of the method invocation - it is thrown away when the method exits, just like a standard Java local variable.

Now comes the twist - In BeanShell you have the option to "hang on" to the scope of a method invocation after exiting the method by referring to the special 'this' reference. As in Java, 'this' refers to the current object context. The only difference is that in this case the context is associated with the method and not a class instance.

By saving the 'this' reference after the method returns, you can continue to refer to variables defined within the method, using the standard Java "." notation:

foo() {
    int bar = 42;
    return this;
}

fooObject = foo();
print( fooObject.bar ); // prints 42!

In the above, the value returned by the foo() method (the 'this' reference) can be thought of as an instance of a "foo" object. Each foo() method invocation effectively creates a new object; foo() is now not just a method, but a kind of object constructor.

In the above case our foo object is not so much an object, but really more of a structure. It contains variables (bar) but no "behavior". The next twist that we'll introduce is that BeanShell methods are also allowed to contain other methods:

foo() {
    bar() {
        ...
    }
}

Scripted methods may define any number of nested methods in this way, to an arbitrary depth. The methods are "local" to the method invocation.

Statements and expressions within the enclosing BeanShell method can call their "local" methods just like any other method. (Locally declared methods override outer-more methods like local variables hide instance variables in Java.) The enclosed methods are not directly visible outside of their enclosing method. However, as you might expect, we can invoke them as we would on a Java object, through an appropriate object reference:

foo() {
    int a = 42;
    bar() {
        print("The bar is open!");
    }
    
    bar();
    return this;
}

// Construct the foo object
fooObject = foo();     // prints "the bar is open!"
// Print a variable of the foo object
print ( fooObject.a );  // 42
// Invoke a method on the foo object
fooObject.bar();       // prints "the bar is open!"

Methods declared inside block structures within methods behave just as if they were declared directly in the method. i.e. there are no block-local methods. For example:

foo() {

	bar() { }

	if ( true ) {
		bar2() { }
	}

	return this;
}

In the above example the methods bar() and bar2() are both defined within foo().

In the next section we'll return to the topic of variable scoping and go into more depth about how to work with scripted methods and objects.


Home

Back

Contents

Next