Skip to main content

Java Sketchbook: Getting Started With Scripting

September 20, 2004

{cs.r.title}











Contents
Why Scripting?
Why JavaScript?
Getting Started
A Simple Example
A Useful Example
The Java Side
The
Scripting Engine
Writing
Some JavaScript
Playing
with the Numbers
Conclusion
Resources

What do you think of when I say "scripting?" If you are a
programmer, you might think of using Bash or Perl to automate command-line
programs. If you are a designer, you may think of DHTML and JavaScript for
building web pages. Either way, you probably don't think of graphical
desktop applications, and you certainly don't think of Java. In years past,
very few desktop applications had any support for internal scripting. Word
had VBScript and Emacs had Lisp macros, but that was pretty much it. Today
as Java developers, however, we have a wide variety of languages available
to be easily embedded into our applications. By the end of this article, you
will have a fully running Java program with an embedded JavaScript
interpreter, and know enough to add scripting to your own applications.

Why Scripting?

The key to scripting languages is that they are designed for ad hoc
programming: building small programs, or parts of programs, with quick
turnaround. Common scripting language traits like loose typing,
interpreting instead of compiling, and easy binding to other languages are
all in support of ad hoc programming.

Scripting languages are most commonly used in two scenarios. One is to
tie different components written in different languages together. These
components could be modules like in Perl and TCL, or entire programs (like
the many Unix command-line utilities) tied together with shell scripts. The
second use is to extend a single program in a controlled manner. This
second use is more commonly done by a person with lesser programming skills
or without access to the source of the underlying application, and it is this
scenario that we will discuss.

By creating a scripting layer to your application, you can allow
semi-skilled developers to extend your program in ways that you don't have
the time or expertise to do. Providing scripting support to your users
isn't the easiest thing in the world; after all, you are potentially
exposing the internals of your program to your users. But doing so can give
your users great flexibility and power. Scripting will let you provide a
better product for your customers and make them happy, and in the end,
happier customers can only be a good thing.

Why JavaScript?

If you are a regular reader of java.net, you may have noticed a recent poll asking readers what
scripting language they would like to see embedded in Java. The most
popular was JavaScript, closely followed by Python/Jython. I chose
JavaScript because it fits our task--to extend a single
program cleanly--very well. There are more people writing in JavaScript than probably
any other language (except maybe Excel functions). Though Perl is more
commonly used by professional programmers, there are many more
non-professional programmers doing simple but useful things with
JavaScript.

Don't believe me? Think of all of the web sites that use JavaScript. Not
just the professional sites that use prefab libraries, but also the one-off
pages done by complete novices. A little validation here, a bit of
animation there; it's all done in JavaScript. Now think of Konfabulator and Apple's upcoming
Dashboard.
These are applications designed from the ground up to allow novice
programmers to build simple but attractive systems. And their language of
choice is JavaScript. Simple snippets of JavaScript are used everywhere,
and by supporting JavaScript we can instantly take advantage of that shared
knowledge among our users. It may not be the choice of skilled programmers
(after all, I wouldn't use JavaScript to parse my web server logs or do
automated backups), but for our target audience, non-professional
programmers, it's the best thing out there.

Getting Started

There is pretty much only one JavaScript library for Java: Mozilla's
Rhino. Rhino was spun off
from a canceled project to rewrite Netscape
Navigator entirely in Java. As their web page says: "When Netscape
stopped work on 'Javagator', as it was called, somehow Rhino
escaped the axe (rumor had it that the executives 'forgot' it
existed).".

Though there is only one library, there are two ways to use it. You can
write directly to the Rhino API or you can use the Bean Scripting Framework, also known as BSF. BSF is a generic framework built by IBM's
AlphaWorks team to provide support for scripting Java using any language.
This means that if you code against BSF, then you can swap in any scripting
language you want with no code modifications. Currently BSF has built-in
support for JavaScript, Python, Tcl, NetRexx, and XSLT, in addition to
support for Java, Ruby, JudoScript, Groovy, and ObjectScript via third party libraries.
BSF was originally built as a research project at IBM, but was donated to
the Apache Jakarta project in 2002, where it resides today. We will be
using BSF for this project so that you can swap in another language
after the program is built.

To get started with scripting, you will first need to download the latest
versions of BSF and Rhino. BSF is available here. The
latest version of Rhino does not work with the latest release version of
BSF (v2.3.0rc1), so you should download Rhino 1.5R3 (or check out a newer version of BSF from CVS). Drop the .jars
from Rhino and BSF (bsf.jar and js.jar) into your
classpath, and you are good to go.

A Simple Example

Let's start with a simple example. Create a new class called
BSFJavascriptTest that looks like this:

import org.apache.bsf.*;
public class BSFJavascriptTest {
   public static void main(String[] args) throws Exception {
      BSFManager mgr = new BSFManager();
      mgr.registerScriptingEngine("javascript",
           "org.apache.bsf.engines.javascript.JavaScriptEngine",
           null);
      BSFEngine engine = mgr.loadScriptingEngine("javascript");
      Object return_value = engine.eval("asdf",10,10,"4+5");
      System.out.println("got: " + return_value);
   }
}

As you can see, it's ridiculously simple. The first line of main creates
a new BSFManager. The second line uses the
BSFManager to register a specific scripting language
implementation, much as you would specify a JDBC driver. In this case, we
are loading the JavaScript framework and naming it
"javascript". The next line creates a
BSFEngine, the object that actually interprets the scripts.
Finally, we evaluate our expression "4+5" and print
out the return value: '9'. The other three arguments to
eval: "asdf", '10', and
'10', are to specify the name of the code and the line and
column numbers in the source code, which are used for printing out errors.
Since we are loading code from a script file, we don't really care about
these values. Just put in whatever you want. If you run this program you
will get the value : 9.

A Useful Example

Let's try applying this to something more useful. When planning this
article, I wanted to build an application that was fun and would let a
novice programmer do something useful with small amounts of scripting.
Thinking back to my days with graphing calculators, I came up with the idea
of a particle simulator.

A particle simulator is a piece of software used to simulate a dynamic
system, like a volcano or birds in flight. Originally invented for
scientific use, they can also be amusing toys for playing with algorithms
and creating pretty pictures. Though they vary greatly, all particle
simulators work basically like this:

  1. Create a particle, or a set of
    particles, with certain properties (position on screen, color, velocity).
  2. Update each particle as time moves forward.
  3. Draw each particle.
  4. Loop.

Very simple rules (like particle.x = Sin(time)) can
create very complex and beautiful behavior, not to mention be entertaining
for hours.

Simulators are usually written in two parts. One is the infrastructure
that takes care of looping, drawing, and managing the objects. The other
part consists of the actual rules that control how the particles move. These rules
are usually simple, but require lots of tweaking, making them a perfect
place to use scripting. In our program, the Java code handles all of the
grunt work and leaves just the particle rules to JavaScript.

The Java Side

To combine the scripting and Java sides, I have created a program called
SimEditor, which is shown in Figure 1.

Figure 1: SimEditor running

Figure 1. SimEditor running

SimEditor displays the running simulation and includes a few
controls on the left. On the right are three text areas for writing scripts: one for creating new
particles, one for updating the particles, and one for drawing the
particles. A more sophisticated version of SimEditor would provide a real
code editor with syntax highlighting and code completion, but to keep this
simple, I've just used JTextAreas in scroll panes. The
underlying Java code manages the drawing canvas, starting and stopping the
threads, and loading code samples. To communicate with the scripting side,
there is a SimFrame interface with three methods for creating,
updating, an drawing particles. It is this interface that will be our
gateway to the scripting side.







The Scripting Engine

As with the simple example earlier, our code will create a BSFManager and a
scripting engine, and then execute the scripts at the right time. Here is
the basic outline:

package org.joshy.oreilly.scripting.sim;
import org.apache.bsf.util.*;
import java.awt.*;
import org.apache.bsf.*;

public class JavascriptFrame implements SimFrame {

    public SimEditor editor;
    public BSFEngine engine;
    public BSFManager mgr;

    public JavascriptFrame(SimEditor editor) {
        this.editor = editor;
        try {
            mgr = new BSFManager();
            mgr.registerScriptingEngine("javascript",
                "org.apache.bsf.engines.javascript.JavaScriptEngine",
                null);
            engine = mgr.loadScriptingEngine("javascript");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void initParticle(Particle part, double clock) {
        try {
            String text = this.editor.init.getText();
            engine.eval("Executing",1,1,text);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void updateParticle(Particle part, double clock) {
       // same as above, but from the update code editor   ...
    }

    public void drawParticle(Particle part, double clock, Graphics2D g) {
       // same as above, but from the draw code editor   ...
    }
}

The JavascriptFrame implements SimFrame and
allocates the scripting manager and engine in the constructor, just like
our simple example earlier. Whenever initParticle is called by
the simulator, the JavascriptFrame will execute the contents
of the init text area, which contains the code that the user typed
into the top editor of the application. It'll do the same for
updateParticle and drawParticle, except using the
code from the other two text areas.

What we have so far will work. It will execute the JavaScript and let
the user make calculations, but since the script has no access to Java
objects (or anything else), it's pretty useless. We need to move the
arguments of each function above (Particle,
clock, and Graphics2D) into the JavaScript
environment so that the scripts have something to work with. BSF enables
this kind of integration with the ObjectRegistry or directly
from the engine. The key is to add each object you want to be accessible using
the declareBean method. In the code below, you can see the
modified JavascriptFrame constructor.

public JavascriptFrame(SimEditor editor) {
    this.editor = editor;
    try {
        mgr = new BSFManager();
        mgr.registerScriptingEngine("javascript",
            "org.apache.bsf.engines.javascript.JavaScriptEngine",null);
        engine = mgr.loadScriptingEngine("javascript");
        ctx = new Context();
        mgr.declareBean("ctx",ctx, Context.class);
    } catch (Exception ex) {
        p(ex.toString());
        ex.printStackTrace();
    }
}

class Context {
    public double clock;
    public Particle particle;
    public Graphics2D graphics;
    public Color color;
    public Map map = new HashMap();
}

I have also created a class called Context, which has some
public variables for accessing the current clock, particle, and drawing
graphics. The map lets the script author add custom variables at
runtime. The JavascriptFrame constructor creates a new
Context variable and then adds it to the script environment
with the declareBean method. The first argument is the name
for the object within the environment (ctx), the second is the
object itself, and the third is the type the object should represent in the
scripting environment. Now, the script writer could write something like
this:

ctx.graphics.drawLine(0,0,50,50);

to draw a diagonal line.

Now we must update the three particle functions to set the members of
the context variable with their current values. This is the new
drawParticle method, which sets the current particle, clock,
and graphics object.

public void drawParticle(Particle part, double clock, Graphics2D g) {
    try {
        String text = this.editor.draw.getText();
        ctx.particle = part;
        ctx.clock = clock;
        ctx.graphics = g;
        engine.exec("Executing",1,1,text);
    } catch (Exception ex) {
        p(ex.toString());
        ex.printStackTrace();
    }
}

Writing Some JavaScript

Now that our framework is up and running, let's take it for a spin. If
you have Java Web Start installed on your computer, you can launch SimEditor
here. Select Simple Circle under the
Demos menu item and you will see the three editors fill with
code:

init()
    ctx.particle.startx = 100;
    ctx.particle.starty = 100;
    ctx.particle.startclock = ctx.clock;
update()
    // reduce spacing by 1/4
    // slow down time by 1/3
    var t = ctx.particle.startclock+ctx.clock;
    t = t/5;
    var rad = 50;
    // radius
    var cx = 100;
    // center x
    var cy = 100;
    // center y
    // parametric equation of a circle
    ctx.particle.currentx =Math.cos(t)*rad + cx;
    ctx.particle.currenty = Math.sin(t)*rad + cy;
draw();
    ctx.graphics.fillOval(
    ctx.particle.currentx,
    ctx.particle.currenty, 10,10);

This set draws a circle using a parametric equation. The
init method sets up each particle, including saving the
starting clock tick. The update method creates a slowed-down
time (to one-fifth), then calculates the current x, y location based on the
sine and cosine of the clock tick. Finally the draw method
draws the particle as a circle on screen. (Figure 2)

Figure 2. SimEditor running circles.xml

Figure 2. SimEditor running circles.xml

Playing with the Numbers

Hit the play button and watch the particles move. The really cool
thing about doing this project with a scripting language is that we can see
immediate results. In the update window, change the 50 in
the first line to 20. Since our code reloads based on the
current contents of the window, the simulation will immediately change on
each keystroke. When you type in the 20, the circle will
immediately become smaller. This is the power of interpreted code. Now
divide time by 2 for the x coordinate by changing the
line:

ctx.particle.currentx =Math.cos(t)*rad + cx;

to

ctx.particle.currentx =Math.cos(t/2)*rad + cx;

The circle quickly changes to a figure eight. Interpreting
the code each time through the loop allows you to continually tweak the
algorithm with immediate feedback. I have included three other
demonstration algorithms that show off complicated variations of the circle
equation, a gravity simulation that ejects colored circles like a volcano,
and a rainfall of spinning lines.

Conclusion

Integrating scripting languages with Java is very easy using the Bean Shell
Framework. I hope you have seen how combining a simple language with
immediate feedback provides a powerful and flexible user interface for a
variety of applications. I also hope you enjoy playing with the simulator
and post your own algorithms to the forum.

Resources

Josh Marinacci first tried Java in 1995 at the request of his favorite TA and has never looked back.
Related Topics >> GUI   |   Programming   |