Script JavaBeans with the Bean Scripting Framework

Add scripts to your JavaBeans or JavaBeans to your scripts

The Bean Scripting Framework (BSF) is one of the more interesting Java offerings available free at IBM's alphaWorks site (see Resources). BSF lets Java programs run scripts written in other languages and also allows other scripting languages to use existing Java classes. If you have useful scripts written in Python or Perl, for example, or have some useful BasicScript program you don't want to reimplement in Java, BSF will let you call that script from your Java program. BSF can also do the reverse, letting you use Java facilities and existing classes from your scripting language. BSF currently supports interoperability between Java and the various languages that appear in Table 1. The documentation shipped with BSF indicates that support for more languages is in development. All of the languages listed are freely available, though the restrictions for commercial use vary. Downloading information for each language is available from the documentation shipped with BSF.

Some languages (as indicated in the table below), run only on Windows 95 or later, because the ActiveX scripting technology on which they rely is limited to Windows. The rest of the languages run portably on any system that has a Java Virtual Machine (JVM), since they are written in portable Java.

LanguageDescription
BMLIBM's Bean Markup Language
JScriptMicrosoft's implementation of JavaScript. Windows only.
Mozilla RhinoNetscape JavaScript, available from Mozilla. Written in Java.
NetRexxIBM's Java implementation of the Rexx editor language. Written in Java.
PerlScriptActiveState's Perl scripting environment. Windows only.
VBScriptVisualBasic scripting environment. Windows only.
JaclJava implementation of a large subset of Tcl.
JPythonJava implementation of Python.
LotusXSLIBM/Lotus implementation of the XSL language

Scripting languages supported by BSF

In this article, I'll explain why you may want to use BSF in your application, whether you're writing it in a scripting language or in Java. You'll see how to use an existing Java class from a script written in a scripting language. Then, you'll see a Java program that can evaluate scripts written in other languages and can use the results. Finally, I'll explain some of the ways IBM is using BSF in its own products.

Why scripting?

I've always found Java's lack of an eval statement to be a bit disappointing. Of course, I understand the reasons for leaving it out. The JVM is big enough without building a Java compiler into it, and eval would add another level of complexity to security control. Besides, a lot of the things I'd like to do with eval can be done more efficiently, elegantly, and type-safely with class loading. But still, sometimes I just want to be able to evaluate an expression. Is that too much to ask?

Since I was born and raised on Unix, my head is filled with arcana about various scripting languages I've learned. I've written a lot of useful scripts over the years, some of them quite complex. (I once wrote a compiler-compiler in Perl. Don't ask.) I'd love to be able to use my scripts from Java, but doing so by way of System.runtime() is what I call "3-I": inefficient, inelegant, and icky.

I'd also like to be able to make my applications extensible by adding scripting languages to them, so users can add the functionality they want. Extensibility is a key requirement for all of the servers with which I've worked. A venerable and still-popular Web server extension mechanism is CGI (Common Gateway Interface). CGI allows any program, written in almost any language, to masquerade as a Web page, providing dynamic content. But CGI can be very inefficient: common implementations of CGI start a new process for every request. Server developers began adding embedded scripting languages to their server offerings, allowing their customers to extend and customize servers without the overhead of individual processes. Modern servers, such as Apache, provide an API or other facilities to let users create extensions in virtually any scripting language.

All of the situations described above can now be addressed in Java, by way of BSF. As I mentioned earlier, BSF allows Java programs to evaluate and access results from scripts written in other languages. BSF also does the reverse. With BSF, scripts written in those other languages can create, manipulate, and access values from Java objects.

Let's start our tour of BSF by using it to call Java code and using Java classes from some common scripting languages.

Using Java code from a script

One of the ways languages, and particular implementations of languages, differ from one another is in their relative ease of support for various features. For example, writing screen resolution-independent GUI elements may be easier in languages such as Java and the Tool command language's (Tcl) Tk extension, with their customizable layout managers, than in languages such as BasicScript or JavaScript, which have little or no dynamic layout control. BSF provides the scripting languages it supports with access to all the features of Java classes and the existing Java code base.

In the examples below, I demonstrate how to use BSF to create a simple Java user interface from JavaScript and Jacl. Then I'll show you how to use an existing Java dialog box to get user data from a Jacl script.

Ave, mundus!

Listing 1 shows a simple example for using BSF to create Java objects in Jacl. The script creates a simple frame containing a single button labeled "Ave, mundus!" (The label means "Hello, world!" in Latin. This spring I'm visiting two Latin countries, France and Italy, so I'm brushing up.) Clicking the button causes the script to exit. Listing 1. The "Ave, mundus!" program in Jacl

001 package require Java
002 
003 set f [java::new java.awt.Frame "Jacl running in BSF"]
004 bsf addEventListener $f "window" "windowClosing" "exit"
005 set b1 [java::new java.awt.Button "Ave, mundus!"]
006 bsf addEventListener $b1 action {} { quit "Jacl says goodbye" }
007 
008 $f add $b1
009 $f pack
010 $f show
011 $f toFront
012 
013 proc quit { message } {
014     puts $message
015     exit 0
016 }

Line 2 in Listing 1 is a standard Tcl command that tells Jacl to load the java package. This statement creates a number of new commands in the Jacl interpreter, allowing Jacl statements to manipulate Java objects. BSF then allows Java objects and Jacl scripts to "talk" to one another via events.

Line 4 uses the standard Jacl java::new command to create a java.awt.Frame and associate it with the variable f.

Line 5 instructs BSF to add an event listener to the frame. (The following discussion assumes you understand Java 1.1+ event listener interfaces; if you don't, or if you need a refresher, see Resources below.) BSF creates a window event adapter object whose sole purpose is to listen for a windowClosing event to occur on the Frame object $f. When a windowClosing event occurs, the adapter object evaluates the Jacl expression "exit", causing the program to exit.

The general syntax that tells BSF to add an event listener to an event source is:

bsf addEventListener eventSource adapterType filter handlerScript

The event listener is an object, called an event adapter, that evaluates the handlerScript whenever an event of a particular type occurs on the eventSource. The event adapter classes for standard Java event types are defined by BSF and come with the distribution, but you also can define new adapters for custom event types (see the BSF documentation to find out how). The adapterType is the type of event, and the filter is the name of the Java event interface method handling that event.

Line 6 creates a Button with the label "Ave, mundus!" and line 7 instructs BSF to evaluate the given Jacl script { quit "Jacl says goodbye" } whenever an actionEvent occurs. Since there's only one method in the ActionEventListener interface, the filter argument is empty.

Lines 8 through 11 correspond to method calls on the Frame object $f. The ability to call Java methods directly from Jacl, shown here, is provided by Jacl and the java package imported in line 2. Each scripting language has a slightly different way of calling methods on JavaBeans created in that language; see the BSF documentation for the description for the language you're using.

Lines 13 through 16 in Listing 1 form the definition of the quit procedure called by the event listener defined in line 6. This is the script that's evaluated when the user pushes the button.

Figure 1 shows the window that results when running the sample file hello_world.jacl (you can download the sample code and scripts free; see Resources). To run BSF directly from the command line, be sure the Jacl and BSF jar files are in your classpath, then simply issue the following command to make sure the sample file is in your current directory:

java com.ibm.bsf.Main -in hello_world.jacl
Figure 1. The result of Listing 1

Reimplementing the same script in JavaScript is simple. The reimplementation appears in Listing 2 below.

Listing 2. "Ave, mundus!" in JavaScript

001 
002 f = new java.awt.Frame("JavaScript running in BSF");
003 bsf.addEventListener (f, "window", "windowClosing",
004                      "java.lang.System.exit (0);");
005 b1 = new java.awt.Button("Ave, mundus!");
006 bsf.addEventListener(b1, "action", null, "quit('JavaScript says goodbye')");
007 
008 f.add(b1);
009 f.pack();
010 f.show();
011 f.toFront();
012 
013 function quit( s ) {
014     java.lang.System.out.println( s );
015     java.lang.System.exit(0);
016 }

The program looks so much like Listing 1 that it scarcely needs explanation. The two scripts really differ only in the syntax of function calls and in how they access Java objects. Rhino JavaScript uses Netscape's LiveConnect to make Java objects' methods directly accessible in JavaScript. The resulting window, shown in Figure 2, is even more similar than the source code.

Figure 2. The result of Listing 2

You may have one lingering question: where did the bsf command (in the Jacl example) or object (in the JavaScript example) come from? The answer is simple: the BSF Main method, which you used to run the script, created a com.ibm.bsf.BSFManager object and "registered" it with the Jacl or JavaScript interpreter you were using. BSF lets you register JavaBeans with the interpreter, making that JavaBean instance available from within the scripting language. In both examples, Main() created an object with methods corresponding to BSF operations (like addEventListener()) and registered it under the name bsf with the interpreter in use. The script then used the resulting new bsf object to communicate with the BSF framework.

Note that bsf is a bit of a misnomer, because it's not limited to JavaBeans. In fact, any Java object, JavaBean or not, can be created by a script and/or created in Java and registered with a BSFManager for access by a script.

Now that you have the basic idea of how the framework can be used to create a Java object from a scripting language, let's create a slightly more complicated script.

A Java/Jacl dialog

Jacl is a pretty cool implementation of Tcl, but it has a major limitation. The standard extension for Tcl used for building GUIs, the tk library, isn't available in Jacl. So Jacl scripts are somewhat limited in their ability to provide user interfaces. That's OK, though, because with BSF, you can use Java's UI facilities from Jacl.

The user interface I happened to have lying around (after writing it as a sample for this article) is called AddressDialog. It has eight bound properties, all of type String:

  • firstName
  • lastName
  • address
  • city
  • stateOrRegion
  • zipOrPostalCode
  • homePhone
  • workPhone

The AddressDialog fires a PropertyChangeEvent each time one of these properties changes. External objects (for example, a Jacl script) can register as a listener for PropertyChangeEvents, and then react accordingly when a property changes.

A Jacl script that uses this dialog appears in Listing 3.

Listing 3. Using a Java dialog class from Jacl

001 
002 package require Java
003 
004 #
005 # Create an instance of the AddressDialog class and  activate it.
006 #
007 proc getUserData { varName } {
008     set propertiesToTrack  { firstName lastName address city stateOrRegion
009                              zipOrPostalCode homePhone workPhone }
010     set dialog [java::new com.javaworld.javabeans.mar2000.AddressDialog]
011     foreach property $propertiesToTrack {
012        bsf addEventListener $dialog propertyChange $property "
013           reportPropertyChange $dialog $property $varName
014        "
015     }
016     $dialog show
017     unset dialog
018  }
019 
020 #
021 # Update the property whose value has just changed
022 #
023 proc reportPropertyChange { dialog property arrayName } {
024     upvar #0 $arrayName arrayToUpdate
025     set arrayToUpdate($property) [java::prop $dialog $property]
026 }
027 
028 #
029 # main
030 #
031 
032 getUserData myData
033 
034 # Report input
035 foreach key [lsort [array names myData]] {
036      puts "$key = $myData($key)"
037 }
038 
039 exit

The main program in this script (lines 32 through 39) is very simple. Line 32 calls the Jacl proc getUserData with a single argument: the name of a global array variable in which to place results. The Jacl proc getUserData (lines 7 through 17) actually does most of the work in this script. Line 10 creates the dialog box by simply creating a new instance of the Java class com.javaworld.javabeans.mar2000.AddressDialog. Then, for each property of the dialog, the proc creates an event listener for that property, binding it to a call to the proc reportPropertyChange. Notice that line 13 defines the property type as propertyChange and defines the filter argument as $property. As a special case, the property change event adapter uses the name of the property as its filter, rather than the name of the java.beans.PropertyChangeListener method propertyChange.

Line 16 simply presents the dialog, and the user interacts with it. The dialog is modal, so the call to the show method will not return until the user clicks OK. The resulting dialog, already filled out by yours truly, appears in Figure 3. (You'll notice I'm still practicing my Latin.)

Figure 3. The AddressDialog called from a Jacl script

Each time the user changes a property in the dialog (by changing what is in the text box for that property and then changing focus), the dialog fires a propertyChangeEvent containing the name of the property and the previous and new values of that property. In response to that event, the event adapter (created in line 13) corresponding to that property name calls the Jacl reportPropertyChange handler method.

reportPropertyChange (lines 23 through 26) receives as arguments a reference to the dialog, the name of the property that was updated, and the name of the array where the result is to be stored. Line 24 is a rather odd Jacl statement, defining the local variable arrayToUpdate as a reference to the global variable named by the variable arrayName; in this program, it will always be myData. Line 25 simply sets the array entry whose name is the property name to the new value of the property indicated by the property parameter.

When the user clicks the OK button, the dialog closes and getUserData returns. Lines 35 through 37 then print the contents of the array to the standard output. A larger script probably would actually use the data collected to perform some task, such as adding the user to an eternal mailing list. The output of the Jacl script looks like this:

firstName = Julius
lastName = Caesar
address = 1 Domus Respublicus
city = Roma
stateOrRegion = RM
zipOrPostalCode = MCLIX
homePhone = DCLV-MDCLIXXX
workPhone = DCLV-MCCCXIV

Jacl has another problem you could easily overcome by combining BSF and Java. The current implementation of Jacl has no support for sockets, even though standard Tcl has excellent socket support. Yet networking (including sockets) is one of Java's strengths. So, a Jacl script could use BSF to implement sockets simply by passing all the work off to Java.

So far, I've demonstrated how to use BSF to handle Java from a scripting language. Now, I'll show you how to use scripting languages from inside Java: finally, I'll get my eval() statement!

Ad-libbing in Java with BSF

It's as easy to call other scripting languages from Java as it is to call Java from those languages. To demonstrate, I've created a class called ExpressionEvaluator. The source code is too long and tedious to reproduce here, especially since it was created in an IDE and is therefore a bit difficult to read. I'll just go over the major points, and if you're interested, you may read the source code directly in the download associated with this article (see Resources).

Figure 4 shows ExpressionEvaluator just after evaluating some JavaScript. A drop-down list at the top of the dialog contains the scripting languages this class understands: JavaScript, Jacl, and PerlScript.

Figure 4. The ExpressionEvaluator evaluating JavaScript

The language names are loaded into the drop-down list when the application initializes. The selection indicates how the dialog should interpret the text in the top of the two text areas below the control buttons. To add new languages, simply install the new language on your machine, set your classpath properly, install any necessary dynamic link libraries (DLLs, for Windows-only languages), then add the name of the language to the drop-down list (which currently you can do only in code.)

The top pane shows some JavaScript code that prints the numbers from 1 to 100 as a matrix. The Evaluate button evaluates the script and places the result in the bottom pane. The Clear Script and Clear Result buttons simply clear out the corresponding text area.

Figure 5 shows the ExpressionEvaluator evaluating an equivalent Jacl script.

Figure 5. A Jacl script equivalent to the JavaScript in Figure 4

The only really interesting method in this class is the evaluate() method, called by the Evaluate button. That method appears in Listing 4.

Listing 4. The ExpressionEvaluator's evaluate() method

127 public void evaluate() {
128 
129     // Get the name of the language we're evaluating this with
130     String language = getLanguageChoice().getSelectedItem();
131     String scriptText = getTextAreaScript().getText();
132 
133     System.out.println("Evaluating: {\n" + scriptText + "\n}");
134     System.out.println("Language: " + language);
135 
136     // Evaluate with _mgr
137     Object oResult = null;
138     try {
139         getTextAreaResults().setForeground(java.awt.Color.black);
140         oResult = _mgr.eval(language, "Evaluator", 0, 0, scriptText);
141     } catch (Exception ex) {
142         getTextAreaResults().setForeground(java.awt.Color.red);
143         oResult = "Exception: " + ex.getMessage();
144     }
145 
146     getTextAreaResults().setText(oResult.toString());
147 }
...
536 protected void initializeBSF() {
537     if (_mgr == null) {
538         System.out.println("Initializing");
539         _mgr = new BSFManager();
540         getLanguageChoice().add("JavaScript");
541         getLanguageChoice().add("Jacl");
542         getLanguageChoice().add("PerlScript");
543     }
544 }

The constructor for the ExpressionEvaluator calls initializeBSF() (lines 536 through 544), which initializes the private field _mgr to a new instance of BSFManager in line 539. The BSFManager is the BSF object that provides script-interpreting services: it loads the scripting engines, determines the language to interpret (based on the language chosen or the suffix of the input name), and evaluates scripts using the chosen language. It's really that easy to use: just create a BSFManager and start handing it scripts to evaluate. A user pushing the Evaluate button calls the evaluate() method, as shown in lines 127 through 147 of Listing 4. Line 130 gets the name of the selected language from the drop-down list, and line 131 gets the script text. Line 139 sets the result text area's foreground color to black (the default color for results), and calls the eval() routine of the BSFManager object _mgr. The arguments of eval() are:

  • The name of the scripting language
  • The name of the input source (typically a filename; here, it's always simply Evaluator)
  • The line number
  • The character position of the given script in the input source (these arguments are for error reporting)
  • The script to evaluate

The BSFManager loads the appropriate scripting engine, which actually interprets the given script and returns the result as an Object. If the evaluation fails, the BSFManager throws an exception, which is caught in the catch block (lines 141 through 144). The catch block turns the text color of the result pane to red and assigns the return value to the error message from the exception. Finally, line 146 converts the result of the eval() (or the error message) to a String, and places the results in the result text area.

All the rest of the code in this program is simply structural or user-interface code. The BSFManager literally does all of the work for you. PerlScript seems to work for simple arithmetic expressions but bombs on more complex scripts. Please email me sample scripts (or code patches) if you can get all of PerlScript to run.

Scriptable applications

Now you've seen how BSF lets you use Java from scripting languages, and lets you evaluate scripts from Java. But what good is it, really?

Making your applications scriptable can add enormous power and flexibility to your designs. The Tcl on which Jacl is based is a popular extensible scripting language. Tcl has a very small core of built-in operations, but it includes an API for creating new Tcl commands in Tcl or in C.

Factoring a design into primitive operations and then implementing those operations as Tcl commands lets Tcl programmers create flexible and extensible systems. Tcl is the glue that binds the operations together. Literally hundreds of Tcl extensions (as they are known) are available free, and thousands of applications have been written using these extensions. Tcl extensions are usually written in C; Jacl extensions are written in Java.

You can use BSF and scripting in your designs to extend any of the available scripting languages by writing JavaBeans that do the hard part in Java and then exposing those beans to the scripting language. Or, you can use existing script libraries you've written to do things in Java without having to rewrite the code in Java.

One excellent design technique is to combine these approaches to make your application user-scriptable. Define a set of operations on your data that you'd like to expose to your users. Implement an interface to those operations and register the resulting beans with BSF. Then, provide hooks in your code at strategic points, or from user-definable menu items, which evaluate small (or large) scriptlets that use those operations.

For example, imagine you were writing an image-editing program. You could create beans that perform primitive image operations ("rotate," "crop," "scale," and so forth) on the current image and then register those objects with the BSFManager. (See the BSF documentation on how to register beans with the scripting engines.) Give the user access to the menu bar of your application in the same way. Then, when the application starts, see whether the user has defined a startup script and, if so, evaluate it. The user's script could add to the application new menu items that implement custom image transformations, fitting the application to that user's needs.

Scripting is also excellent for automated testing. Expose any objects in your application that you'd like to test and write test suites that exercise your application objects and report unexpected results. I use this technique widely in my day job, and it really works!

IBM currently uses BSF in three products. The Bean Markup Language (BML), which is free for commercial use, uses BSF to manipulate Java beans in event handlers and other embedded scripts. The JavaServer Pages (JSPs) module of IBM's WebSphere Java Web server uses BSF to enable creating of JSP pages with scripts in any supported language. And finally, the scripting component of LotusXSL, IBM's implementation of the Extended Stylesheet Language, is written using BSF.

Running the sample programs

Running these packages requires a nontrivial amount of downloading and setup time. Download the BSF packages and the scripting languages from the Resources section below. If you're running Windows 95, you may need either to upgrade your operating system or download and install some packages from Microsoft; see the related discussion in Resources.

In the archive you download, two files can help you set up your environment: env.sh (for Unix users) and env.bat for Windows users. These scripts will get you most of the way toward a running BSF system. You'll have to adjust the paths to the jar files and scripting engines until the system works properly.

A really cool technology

You've just been introduced to the Bean Scripting Framework, a (currently) free software package from IBM alphaWorks that enables Java programs to execute scripts and lets scripts access and manipulate JavaBeans. I'm hoping these BSF examples start you thinking about how BSF could be useful in your next design.

Your next step is to download and experiment with the sample code from this article and write your own scripting systems. You can download everything you need freely on the Internet (see Resources below for a link).

The BSF distribution, as cool as it is, is definitely alpha quality. The documentation consists of fairly uninformative javadoc pages containing little more than parameter descriptions. There's a PDF file in the current distribution (2.0), containing an attractively formatted, but misleading, user guide to the current release. The 12-page guide explains BSF partially in terms of an obsolete API. Although it's not a tutorial introduction, it does provide some useful information, a general overview, and a handy syntax guide for each of the supported languages. The demo programs are sketchy and virtually without documentation, but just adequate for getting things going.

If you're creating BeanInfo objects for the classes you'll be scripting, be sure to define event set descriptors for any events your beans may fire, or BSF will refuse to add listeners for them. Also, the Windows scripting languages seem to work, but be sure to download and install the appropriate scripting engines, as well as put the two BSF DLLs (bsfactivescriptengine.dll and msvcp60.dll) from the distribution into your Windows directory (or any other directory on your DLLPATH).

Despite the disappointing and sometimes outdated documentation, BSF is seriously cool technology. Start experimenting with BSF and, before long, you'll find yourself creating applications that are more flexible than you ever thought possible.

Mark Johnson works in Ft. Collins, Colo., as a designer and developer for Velocity. by day, and as a JavaWorld columnist by night -- very late at night.

Learn more about this topic

  • Download the source code and class files for this month's article:
  • Note: These languages are applicable to Windows 95, Windows 98, and Windows NT 4.0 only. Also note that to use these scripting languages with Windows 95, you'll need Windows 95 OSR2, or Internet Explorer 4.0 or later; or you'll have to install DCOM. International versions of Windows 95 also require DCOM.

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more