Dynamic Languages and Java/Interpreters in Java
From JVMLanguages
Introduction
A lot of people like to think that there are two types of software developers in the world. "Software Engineers" use real languages like C++, C, or even assembly language and work on complex, enterprise systems with millions of lines of code and 99.999% uptime. "Computer Programmers" use scripting languages like Perl, Python, or Visual Basic and work on small to medium-sized systems that work behind the scenes in either a batch processing mode or in some less critical environment.
Chances are that if you're reading this book, you fall somewhere in between these two categories or, at the very least, you bristled at several of those oversimplifications. One of the big things missing from this picture is the concept of "glue languages." A better way to divide the world is into producers and consumers of reusable software components. The producer of one component will often be the consumer of several others, so what you end up with is an acyclical graph where the core of the graph is composed of your most "hardcore" programmers writing the lowest layers in traditional, static languages and the periphery is composed of your more "business-focused" programmers writing in a glue language, to bring together all of the other components to achieve some purpose.
As described in Chapter 3, there are many different kinds of interpreters. Some take compiled code such as machine code or Java bytecode. Other interpreters take in source code, parse that code, and execute the abstract syntax tree. A few others interpret a file as they are parsing it; in effect, they act on the source code directly.
Interpreters have a number of practical applications. They can be used as a storage format for simple but powerful configuration files. They can also be used to write scripts that drive an application when it is run in batch mode. They can be used to implement macros, or even allow users to extend an application's functionality. Finally, they can be used to embed powerful debugging tools into an existing application, such as utilities to monitor server processes.
BeanShell
BeanShell (BSH) is a hosted interpreter which can parse and execute a language very similar to Java source code. As of BSH 2.0, the BeanShell language is a superset of Java, so any typical Java code can be executed by BSH. However, the language is a bit more relaxed in its syntax. For example, variables, parameters, and methods can be defined without explicit types.
Checked exceptions do not need to be caught. Even before Java 5, autoboxing of primitives -- automatically converting primitive types to Object-based wrapper types -- was supported by BeanShell. BeanShell also supports closures, which can be passed around as a first-class data type, and which can be used to implement Java interfaces.
BeanShell has been around for a while, and is used in quite a few high-profile projects. For example, the aspect-oriented programming framework dynaop uses BeanShell as its configuration language. BeanSheet is an open-source spreadsheet that uses BeanShell for its formula language.
BeanShell works by parsing its Java-based language into an Abstract Syntax Tree (AST) and then interpreting this AST. JavaCC is used to generate the BeanShell parser.
- Insert Figure 1. Using BeanShell 1.x
Invoking BeanShell
BeanShell can be used in many different ways. Run Scripts on the Command-line
$ java bsh.Interpreter yourscript.bsh 4
If you pass arguments on the command line after the name of a script, they will be used to populate the bsh.args variable (as a String array):
$ java bsh.Interpreter printArgs.bsh This "is a" test.
java.lang.String []: {
This,
is a,
test.,
}
Embedded in an Application
The most common use of BeanShell, though, is to embed it directly into your application. BSH can be invoked programmatically by instantiating the bsh.Interpreter class. From here, variables can be set or retrieved and strings or files can be evaluated. For example:
import bsh.Interpreter;
public void evaluate () {
Interpreter i = new Interpreter();
i.set("foo", 5);
// Eval a statement and get the result
i.eval("bar = foo*10");
System.out.println( i.get("bar") );
// Source an external script file
i.source("somefile.bsh");
}
Interactively
BeanShell can be run directly from the command line by invoking the bsh.Interpreter main class. If invoked without a file name, it reads from the standard input stream one statement at a time:
$ java bsh.Interpreter BeanShell 2.0b2 - by Pat Niemeyer (pat@pat.net) bsh % print(2+2); 4
GUI Desktop
BeanShell also provides a graphical "desktop", which can display multiple interpreter sessions and provides scrollback buffer and other terminal features. This desktop can be invoked with the bsh.Console main class, or simply by executing the bsh.jar:
$ java -jar bsh.jar
- Insert Figure 2. Screenshot of BSH Console
Remote Server
BeanShell supports two kinds of remote servers: a Java servlet-based HTTP server, and a simple text-based TCP server. These servers provide exactly the same functionality as the methods described above (the TCP server looks and acts very similar to BeanShell when it is invoked on the command line, and the Servlet method is the exact same code as the GUI desktop), but they make it trivial to embed an external BeanShell interface into an existing application. This can be used to debug a problem in a running process, to test an application, or to provide additional functionality or bugfixes at run-time.
The built-in server(portNumber) function can be used to start both of these servers. The HTTP server will be started on the specified port, and the TCP server on the next port (port+1). This may be useful if you already have some way to execute a BeanShell script inside of your application and you wish to debug it, or analyze the results.
However, the Java components that implement these servers can be invoked directly. The TCP interface is implemented by the bsh.util.Sessiond class. This class implements java.lang.Runnable and can be executed from your own Java code with just one line:
(new Thread(new Sessiond(null, 4224))).start();
Inserting this line of code into your application will open a server socket on port 4224, listen for connections, read BeanShell expression from every connected client, execute them, and redirect and I/O back to the client socket. Later in this Chapter we will use this as the basis for implementing a tool that can be used with any Java application, but if you want to use this technique in a specific server or application it is probably best to define logic in the application itself to start and stop this server, and to define utility functions and variables that will be particularly useful when debugging that application.
WARNING: Of course as with any "backdoor", care should be taken to prevent access from falling into the wrong hands. BeanShell has no security, and the port will be opened up all interfaces of the current machine. Although it can be very useful to use this technique in a production environment, you will probably want to implement a secure authentication mechanism, or at least implement some secure way to disable and enable it. In Chapter 12 we will discuss ways of implementing more secure backdoors.
Interacting with Java Objects from BeanShell
There are very few surprises here. BeanShell uses Java's wrapper objects directly, so there is no complex mapping between Java and BeanShell data types. BeanShell does auto-boxing of primitives automatically (as does Java 1.5).
One difference between Java and BeanShell is in how BeanShell searches for fields and methods on an object. To do this, it will use the actual type of the object and not the declared type of the variable which references it. This effectively means that you do not have to cast references to the appropriate subclass before calling methods that do not exist on a superclass. This is fairly common among interpreted languages, but differs from the way Java itself works.
bsh % Object o = new Random(); bsh % print(o.nextInt()); 1495610463
One interesting feature that BeanShell provides is the ability to control the access protection on constructors, methods, and fields. By calling setAccessibility(true) you can instruct the interpreter to override Java's access protection mechanism so that you can access private fields and call private methods.
This can be dangerous. It allows you to violate some of Java's most important rules -- for example, that String objects are always immutible:
bsh % str = "good"; bsh % setAccessibility(true); bsh % str.value[0] = 'f'; bsh % print(str); food
However, it can be a lifesaver when your script needs to access the internals of your application after it has been released to a production environment.
Interacting with BeanShell Objects from Java
BeanShell supports the instantiation of objects that implement a Java interface. These will be passed back to Java as java.lang.reflect.Proxy instances.
BeanShell also supports the instantiation of code blocks that can contain methods. When these objects are returned to Java they are represented by instances of the bsh.XThis class.
- Does BeanShell 2.x return proper classes as bsh.XThis or real classes?
Example: The BeanShell JEdit Plug-in
JEdit is a popular cross-platform text editor written in Java. The editor itself is relatively simple, but it supports a plug-in architecture and a sophisticated download and installation tool for its plug-ins. However, for many of the one-off tasks that may come up when editing, a full blown plug-in is unnecessary. For that reason, a BeanShell plug-in has been written that can execute scripts written in BeanShell that have access to all of the same features as a full-blown plug-in.
The BeanShell JEdit plug-in creates a BeanShell interpreter and passes the one object that it is given, a org.gjt.sp.jedit.View, down to that interpreter in the view variable.
eval() function
Like many interpreted languages, BeanShell supports an eval() function. From within a BSH script, eval() can be invoked to parse and execute a string as if it were a block of BeanShell code. This is a very powerful and intuitive way of doing metaprogramming, or writing code that operates on other code. For example, if you need to write a method that takes a map where from field names to values, and must set each of the specified fields on an object, in Java you would do this via reflection. It would look something like this:
public void setAllFields (Object target. Map values)
{
for (String fieldName e : values.keySet()) {
Object v = values.get(fieldName);
Field f = target.getClass().getDeclaredField(fieldName);
f.set(target, v);
}
}
In BeanShell, you could do this with:
void setAllFields (Object target, Map values)
{
for (String fieldName : values.keySet()) {
Object v = values.get(fieldName);
eval("target." + fieldName + " = v;");
}
}
- Doesn't work in bsh-2.0b2 -- need to declare Object v outside of the for loop. Bug in BSH's scoping logic? Investigate and file a bug report.
This code may not be dramatically simpler, but in a lot of ways it is more intuitive. Rather than having to use a completely different API (reflection) and code at a "meta" level just because you don't know the name of the field at compile time, you can use the same code you ordinarily would (target.someField = v;), but execute it with the eval() function. Unfortunately, this technique can become unwieldy when the code is more complex and includes metacharacters (e.g., strings).
The more obvious use for eval() is to create applications which must themselves evaluate user-supplied content. For example, a spreadsheet application written entirely in BeanShell would need to use eval() to evaluate the formula for each cell. There are dangers inherent to evaluating user-supplied content, however, and BeanShell provides no features to limit these risks. See Chapter 12 for an in-depth discussion of the features that other languages provide.
BeanShell as a Debugging Tool
BeanShell combines the potential for interactive development with syntax and semantics that almost exactly match Java's. This makes it an incredibly useful tool for prototyping and debugging Java applications. For example, have you ever wanted to get inside of any Java application and execute any arbitrary expressions at any time? Let's use the new Agent mechanism in Java 1.5 to do just this.
Agents in Java 1.5
Java 1.5 added a new mechanism to the Java VM -- "agents". Essentially, an agent is a class that is invoked before the program's main class, and has access to a few additional pieces of functionality. The class must define a method called premain(), which takes a string argument and an instance of the new java.lang.instrument.Instrumentation interface.
Agents should be packaged up into a jar whose manifest contains the following line:
Premain-Class: com.foo.package.YourClass
... where com.foo.package.YourClass is the class that contains your premain method. You can then perform various set-up tasks (e.g., spawning threads or setting static fields). You can also install ClassTransformer instances via the Instrumentation object, which can modify classes in various ways as they are loaded. You can also store the Instrumentation for later use. A bit later in this example we will use its redefineClasses() method to reload classes on-demand.
You can start up a Java application using agents by including one or more -javaagent:<jar-file>=<argument> arguments. The part after the equals sign will become the first argument to your premain() method.
A BeanShell Agent
For this example we will create a new agent, BeanShellAgent.java, and place it in a jar called bsh-agent.jar.
package com.oreilly.javalangint.bsh;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import bsh.util.Sessiond;
public class BeanShellAgent
{
public static void premain (String portNumber, Instrumentation instr)
{
try {
Interpreter interp = new Interpreter();
interp.set("instrumentation", instr); // Ignore for now, will use later.
(new Thread(new Sessiond(interp.getNameSpace(), Integer.valueOf(portNumber)))).start();
System.err.println("Started BeanShell server on port " + portNumber);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
We'll also put all of the BeanShell code that we need in bsh-agent.jar so we don't have to worry about manipulating the classpath. Now we can execute our agent inside of any other Java application without requiring any code changes. Take for example, the very cool BitTorrent client Azureus, which happens to be written in Java. On Linux, the azureus executable is actually a simple shell script which executes a Java command line:
$ java -cp /usr/share/junit/lib/junit.jar:/usr/share/log4j/lib/log4j.jar:/u\ sr/share/commons-cli-1/lib/commons-cli.jar:/usr/share/systray4j/lib/systray\ 4j.jar:/usr/share/swt-3/lib/swt.jar:/usr/lib/azureus/azureus.jar:/usr/lib/a\ zureus/seda.jar -Djava.library.path=/usr/lib \ org.gudy.azureus2.ui.swt.Main
If we want to run a BeanShell interpreter attached to Azureus, we can simply add our agent to this command line, as follows:
$ java -cp /usr/share/junit/lib/junit.jar:/usr/share/log4j/lib/log4j.jar:/u\ sr/share/commons-cli-1/lib/commons-cli.jar:/usr/share/systray4j/lib/systray\ 4j.jar:/usr/share/swt-3/lib/swt.jar:/usr/lib/azureus/azureus.jar:/usr/lib/a\ zureus/seda.jar -Djava.library.path=/usr/lib \ -javaagent:bsh-agent.jar=4000 \ org.gudy.azureus2.ui.swt.Main Started BeanShell server on port 4000 [...usual Azureus startup output...]
The Azureus GUI fires up, and you can interact with it exactly as you could before. There is no performance penalty as there may be in a debugging environment, there's simply one extra thread running in the background (the Sessiond runnable that our agent started up). However, we can now connect to port 4000 on our machine and we have a window into Azureus' Java VM.
$ telnet localhost 4000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
BeanShell 2.0b2 - by Pat Niemeyer (pat@pat.net)
bsh % for (x : Thread.getAllStackTraces().entrySet()) {
print(x.getKey().getName());
for (y : x.getValue())
print(" " + y);
print("");
}
The Azureus VM' will then respond with a stack trace for all running threads:
Peer Updater
java.lang.Thread.sleep(Native Method)
org.gudy.azureus2.core3.peer.impl.control.PEPeerControlImpl$PeerUpdater.runSupport(PEPeerControlImpl.java:304)
org.gudy.azureus2.core3.util.AEThread.run(AEThread.java:45)
Universal Plug and Play (UPnP)::SSDP:queryLoop
java.lang.Thread.sleep(Native Method)
com.aelitis.net.upnp.impl.ssdp.SSDPImpl.queryLoop(SSDPImpl.java:238)
com.aelitis.net.upnp.impl.ssdp.SSDPImpl$4.runSupport(SSDPImpl.java:203)
org.gudy.azureus2.core3.util.AERunnable.run(AERunnable.java:38)
org.gudy.azureus2.pluginsimpl.local.utils.UtilitiesImpl$1.runSupport(UtilitiesImpl.java:139)
org.gudy.azureus2.core3.util.AEThread.run(AEThread.java:45)
FMFileManager::closeQueueDispatcher
java.lang.Object.wait(Native Method)
java.lang.Object.wait(Object.java:474)
org.gudy.azureus2.core3.util.AESemaphore.reserveSupport(AESemaphore.java:115)
org.gudy.azureus2.core3.util.AESemaphore.reserve(AESemaphore.java:70)
org.gudy.azureus2.core3.util.AESemaphore.reserve(AESemaphore.java:63)
com.aelitis.azureus.core.diskmanager.file.impl.FMFileManagerImpl.closeQueueDispatch(FMFileManagerImpl.java:271)
com.aelitis.azureus.core.diskmanager.file.impl.FMFileManagerImpl$1.runSupport(FMFileManagerImpl.java:106)
org.gudy.azureus2.core3.util.AEThread.run(AEThread.java:45)
Tracker Scrape
java.lang.Thread.sleep(Native Method)
org.gudy.azureus2.core3.tracker.client.classic.TrackerChecker.runScrapes(TrackerChecker.java:220)
org.gudy.azureus2.core3.tracker.client.classic.TrackerChecker.access$000(TrackerChecker.java:18)
org.gudy.azureus2.core3.tracker.client.classic.TrackerChecker$1.runSupport(TrackerChecker.java:56)
org.gudy.azureus2.core3.util.AEThread.run(AEThread.java:45)
...
True, we probably could've gotten the same information from a kill -QUIT (assuming the the standard output stream hadn't been closed or redirected somewhere silly), but this is a lot more powerful. We can put it into a loop that prints out the stack trace of a specific thread every few seconds. We can even look at those class and method names and match them up to annotations, as I did in my "Peeking Inside the Box" articles.
As a debugging environment this isn't a great one. We can't step through the code a line at a time, or pause the execution of the system in any sensible way. However, unlike a traditional debugging environment we're not restricted to simply observing. We can execute arbitrary blocks of code, trigger the garbage collector, call methods, set variables, etc. We can create counters and install listeners to manipulate them, disconnect, and then come back in a few days to see how many times an event has occurred.
With a little bit more work, we could even take advantage of the Instrumentation object that was passed into our agent to allow us to reload Java classes at run-time. Ever start up a complex application and execute an intricate set of steps in order to reproduce a bug you think you just fixed, only to realize that you commented out the wrong line of code, or made some other trivial mistake? If you started your application up like this, simply fix your mistake, rebuild your code, and then telnet into your existing, set-up application and tell it to reload:
$ telnet localhost 4000 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. BeanShell 2.0b2 - by Pat Niemeyer (pat@pat.net) bsh %
Have done this but don't have the code with me. Will fill in later.
Or we could use the instrumentation API properly and weave additional tracing or profiling statements into our compiled classes. Write your own debugger based on BeanShell and Aspect-Oriented Programming. Or use dynaop, they've already done exactly this!
Thread-safety
Multiple threads and one interpreter should work. Multiple interpreters will definitely work.
Writing BeanShell Extensions
Although you can seamlessly call all public Java methods from BeanShell, there may come a time when you want to implement a function like eval() in Java that your BeanShell code can use. To do this, you need to write a new command.
Modifying the Classpath
If you're running the BeanShell console via java -jar bsh.jar, it won't be long before you want to import some code from another component. Instead of restarting the console with a different classpath, you can add additional entries to BSH's classpath at run-time.
This can also be very useful to pull debugging or analysis tools into the classpath of a production application, if you've embedded an HTTP or socket server in an existing program.
BSF Support
The engine for BSH is included along with the standard BSF distributions. Because BSH has no wrapper objects for primitives, the standard Java wrappers are returned from expressions.
XThis objects are returned from BeanShell when an object is instantiated from BeanShell. However, BSFEngine.call() knows how to invoke methods on an XThis instance, so you do not need to write BeanShell-dependent code to call methods defined in BeanShell.
Getting Help
If you're going to be using BSH interactively, you may find the javap() function useful. Like the javap executable that comes with the JDK, BeanShell's javap() function will print out the signatures of every field, constructor, and method available on the specified class or object.
bsh % javap(new Random());
Class class java.util.Random extends class java.lang.Object
public double java.util.Random.nextDouble()
public int java.util.Random.nextInt()
public int java.util.Random.nextInt(int)
public boolean java.util.Random.nextBoolean()
public void java.util.Random.nextBytes(byte[])
public float java.util.Random.nextFloat()
public synchronized double java.util.Random.nextGaussian()
public long java.util.Random.nextLong()
public synchronized void java.util.Random.setSeed(long)
BeanShell also supports a which() function that locates the specified class and reports the actual file in the classpath that contains it. This can be very useful for debugging jar conflicts.
bsh % which(java.util.Random) Jar: file:/usr/java/j2sdk1.4.0/jre/lib/rt.jar
- Uh, this doesn't actually seem to work in bsh-2.0b2. Test other versions and file a bug report.
Limitations
One of the main limitations of BeanShell 1.1 is that, although there was support for implementing Java interfaces in BSH, there is no support for extending Java classes. The reason for this is that interface support is built using Java's Dynamic Proxy API, which only supports interfaces. However, with BeanShell 2.0, BSH classes can now be compiled into Java bytecode, and thus can do anything that Java can do. Classes are generated using the open-source ASM library, and they appear as ordinary Java classes to the outside world.
- Insert Figure 3. Using BeanShell 2.x
For example, in BeanShell 2.0 you can create an anonymous class which extends another class:
bsh % d = new Date() { String toString() { return toGMTString(); } };
bsh % print(d);
1 Jun 2005 08:26:07 GMT
Since BSH 2.0 is a subset of Java 1.5, some users may want to use it as if it actually was a Java compiler. For these users, BSH added a function called setStrictJava(true), which tightens up the parsing and evaluation of BeanShell code so that only code supported by Java can be used. This means that all checked exceptions are verified, variable and field declarations must specify a type, closures are disabled, and various other restrictions are enforced. However, be aware that certain built-in BeanShell commands, such as print(), will not work in strict Java mode. It's also worth noting that strict Java mode is not quite as strict as it could be. For example, even in strict Java mode BeanShell will use the type of the object instead of the type of the reference when looking up field and methods. Our example above of calling nextInt() on an Object reference will still work in strict Java mode.
Embedding the BeanShell Console into an Existing Application
Earlier, I showed you how to create a plug-in for the JEdit application that executed BeanShell code. However, developing scripts for this environment can be a bit tricky. In Chapter 11 we'll explore how to use a real code debugger in this situation, but for now it might be nice just to run the script interactively -- one line at a time. Luckily, we can use the existing BeanShell console for this.
- Show how to do this...
JRuby
JRuby is an interpreter for the Ruby language written in Java. Although the semantics of Ruby and Java are very different, JRuby does a very good job of mapping between the two.
Invoking JRuby
JRuby includes a main class org.jruby.Main which can read a script off of the command-line arguments and invoke it. If no script is provided, JRuby will read the entire stream from standard input and then execute it. Unlike most other command-line interpreters, it does not evaluate it statement-by-statement.
$ java org.jruby.Main print "the answer is: " a = 2 + 2 print a.to_s, "\n" ^D the answer is: 4
If you provide a script, any arguments that follow the script will be appended to ARGV.
$ cat print_sum.rb
sum = 0
ARGV.each { |x| sum += x }
print sum.to_s, "\n"
$ java org.jruby.Main print_sum.rb 2 2
4
JRuby assumes the several Java system properties are set. This includes:
- jruby.base
- jruby.home
- jruby.lib
- jruby.shell
- jruby.script
You can also invoke Ruby programmatically by obtaining an instance of org.jruby.Ruby.
import org.jruby.*;
public long doMath ()
{
Ruby ruby = Ruby.getDefaultInstance();
ruby.defineReadonlyVariable("a", new RubyFixnum(ruby, 2));
RubyFixnum out = (RubyFixnum)ruby.evalScript("a + 2");
return out.getLongValue();
}
Using Existing Ruby Libraries
JRuby is only distributed with its own Ruby libraries, which are used for integration with Java. The core Ruby libraries such as XXXX are not included and must be downloaded separately from a typical Ruby installation.
Interacting with Java Objects from Ruby
Java objects are represented inside of Ruby as an instance of the JavaObject class, which has methods that can be used to locate its Java class (JavaClass) and look up methods and fields via a reflection-like API.
However, in most cases there is no need to interact with JavaObjects directly -- JRuby can also create proxies around these JavaObjects that make them look like ordinary Ruby objects. This means that you can call methods on them directly. Calling a method on a Ruby proxy that returns another Java object will also automatically wrap that object in a proxy.
public class MyJavaClass {
public MyOtherJavaCLass a () {
return new MyOtherJavaClass();
}
}
public class MyOtherJavaClass {
public int b () {
return 42;
}
}
class MyRubyClass
def myMethod (myJavaObject)
print "a.b = ", myJavaObject.a.b.to_s, "\n"
end
end
Ruby also supports proxies for Java classes, which expose static methods and constructors as if they were normal operations in Ruby. This is usually done through the include_package statement in Ruby, which creates proxies for all Java classes within that package.
The JavaBean standard dictates that nearly all Java objects have private fields and public accessor and mutator methods which named get* and set*, respectively. Ruby's coding style, on the other hand, generally leaves off the prefixes and instead uses the presence of a = suffix to indicate a mutator method. This convention is enforced by the language itself, which has syntactic sugar that makes a.b = 42; equal to a.b=(42);. Similarly, boolean accessor methods in Java are often prefixed with the word is, where boolean accessors in Ruby are conventionally suffixed with a question mark (?).
To ease the integration between Ruby and Java code, JRuby added support for mapping between these conventions and creating aliases on the Ruby proxies that follow the Ruby codingconventions. Thus the Java method Foo.getBar() can be called from Ruby either by foo.getBar() or by foo.bar. 2.4. Interacting with Ruby Objects from Java
Ruby has support for generating Java proxies from Ruby objects, however it is up to the caller to define the mapping between Java interfaces and Ruby classes.
BSF Support
The JRuby distribution contains a BSFEngine implementation called org.jruby.javasupport.bsf.JRubyEngine. However, the Languages.properties file included with BSF has this engine registered, so you will not need to register it explicitly. Simply use the language name ruby and the extension .rb.
Ruby's BSFEngine is responsible for converting all data types between Ruby's data types (RubyFixnum, RubyString, etc.) to Java's data types (Integer, Long, String, etc.). It does this conversion in both directions, for input parameters as well as return values.
Limitations
In the case of JRuby, we have a milestone against which we can measure its functionality. In fact, Ruby has a general conformance test called Rubicon that is designed to test all of the functionality that Ruby supports. According to the JRuby web site, 651 of the 933 (70%) Rubicon tests pass for 0.7.0. Is this still the most recent?
JudoScript
JudoScript is an interpreted scripting language that began in 2001 as a scripting language for JDBC and XML manipulation. It gradually took on more responsibilities, and now refers to itself as a "multi-domain" language. By this they mean that it has language extensions which essentially make it into a domain-specific language for each of several domains: file manipulation, relational databases, XML parsing, GUI building, graphics, web server interaction, and much more.
Invoking JudoScript
Interacting with Java objects from JudoScript
There are two ways to refer to Java classes in JudoScript. You can use the operators that are designed to be used exclusively with Java classes, as follows:
frame = javanew javax.swing.JFrame("Hi!");
Or you can use the import statement to include Java classes as if they were ordinary JudoScript classs, with the prefix java::.
import javax.swing.JFrame;
frame = new java::JFrame("Hi!");
After you obtain an instance of a Java class, interacting with it is the same as if it were a normal JudoScript class:
frame.setVisible(true);
Interacting with JudoScript objects from Java
Extending Java classes from JudoScript
class MyJudoClass extendsjava com.oreilly.MyJavaClass
{
...
}
BSF Support
NOTE: JudoScript's BSF implementation, unlike most dynamic languages, assumes that if you are invoking BSF's eval() function you have only a single expression, and that if you're invoking exec() you have one or more statments. This means that unlike BeanShell, TCL, Perl, and other languages, you cannot use a statement with eval. For example, the org.apache.bsf.Main class defaults to -mode eval, but to run any non-trivial file with it you will need to specify -mode exec. This also means that you cannot enter any complex JudoScript code into the BSF Calculator utility mentioned in the previous chapter.
Example: Interactive Web Cacher
When two of JudoScript's domains, HTTP data transfer and HTML scraping, are combined, it is possible to write some very intricate web spiders in a very small amount of code. To show off these two features we're going to write a program that downloads entire web-sites and makes them available for offline viewing. There are many tools that do this already, but very few of them can be used interactively, and those that can tend to be unintuitive or difficult to manage.
Instead, our tool is going to leverage another component that is not part of JudoScript, but is written in Java so that it can be seamlessly integrated with our script. Prefuse is a relatively new, but very promising, graphing library. Prefuse excels at interactive and highly-customizable presentations of tree or graph data. We'll use this to display web site content to users and allow them to choose which links should and should not be downloaded.
- Simplify this code a bit more. Use only the key points ("do ... as sgml"), creating Java objects, and implementing Java interfaces.
Example 3. vizcache.judo
import java.net.*;
import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.io.*;
import edu.berkeley.guir.prefuse.*;
import edu.berkeley.guir.prefuse.action.*;
import edu.berkeley.guir.prefuse.action.animate.*;
import edu.berkeley.guir.prefuse.action.assignment.*;
import edu.berkeley.guir.prefuse.action.filter.*;
import edu.berkeley.guir.prefuse.activity.*;
import edu.berkeley.guir.prefuse.event.*;
import edu.berkeley.guir.prefuse.graph.*;
import edu.berkeley.guir.prefuse.graph.io.*;
import edu.berkeley.guir.prefuse.render.*;
import edu.berkeley.guir.prefuse.util.*;
import edu.berkeley.guir.prefusex.controls.*;
import edu.berkeley.guir.prefusex.distortion.*;
import edu.berkeley.guir.prefusex.force.*;
import edu.berkeley.guir.prefusex.layout.*;
urlMap = {};
graph = new java::DefaultGraph(true);
function addNode url {
var node = new java::DefaultNode();
node.setAttribute("url", url);
var name = url;
var i = name.lastIndexOf("/", name.length() - 2);
if (i != -1)
name = name.substring(i + 1);
var j = name.lastIndexOf("?");
if (j != -1)
name = name.substring(0, j);
node.setAttribute("name", name);
urlMap.(url) = node;
graph.addNode(node);
return node;
}
function populateEdges node {
var url = node.getAttribute("url");
if (url) {
{
var parent = new java::URL(url);
var inTitle = 0;
do url as sgml {
<title>: inTitle = 1;
:TEXT: if (inTitle) node.setAttribute("name", $_);
</title>: inTitle = 0;
<a>:
if ($_.href) {
var subUrl = (new java::URL(parent, $_.href)).toString();
subUrl = subUrl.replace("#.*", "");
subUrl = subUrl.replace("intl/en/", "");
subUrl = subUrl.replace("index.html", "");
subUrl = subUrl.replace("/$", "");
if (!subUrl.matches("^.*(gif|jpg|jpeg|js|png|css)$")) {
println "*** ", subUrl;
links.add(subUrl);
var n = urlMap.get(subUrl);
if (!n) {
n = addNode(subUrl);
}
println(url + " -> " + subUrl);
graph.addEdge(new java::DefaultEdge(node, n, true));
} else {
println "... ", subUrl;
}
}
};
catch:
println "error with ", urlString, ": ", $_;
}
}
}
// create display and filter
registry = new java::ItemRegistry(graph);
display = new java::Display();
display.setSize(700,700);
nodeRenderer = new java::TextItemRenderer();
nodeRenderer.setRenderType(TextItemRenderer::RENDER_TYPE_FILL);
nodeRenderer.setRoundedCorner(8,8);
nodeRenderer.setTextAttributeName("name");
edgeRenderer = new java::DefaultEdgeRenderer();
registry.setRendererFactory(new java::DefaultRendererFactory(nodeRenderer, edgeRenderer, null));
fsim = new java::ForceSimulator();
fsim.addForce(new java::NBodyForce(-0.4f, -1f, 0.9f));
fsim.addForce(new java::SpringForce(2E-5f, 75f));
fsim.addForce(new java::DragForce(-0.01f));
displayAction = new java::ActionList(registry);
displayAction.add(new java::RepaintAction());
forces = new java::ActionList(registry,-1,20);
forces.add(new java::ForceDirectedLayout(fsim, false, false));
forces.add(displayAction);
update = new java::ActionList(registry);
update.add(new java::GraphFilter());
forces.add(displayAction);
display.setItemRegistry(registry);
display.addControlListener(new java::FocusControl());
display.addControlListener(new java::DragControl());
display.addControlListener(new java::PanControl());
display.addControlListener(new java::ZoomControl());
display.addControlListener(new java::NeighborHighlightControl(displayAction));
// set up initial focus and focus listener
class MyFocusListener extends java::FocusListener
{
ActionList update;
constructor (u) {
super();
this.update = u;
}
void focusChanged(FocusEvent e) {
if ( update.isScheduled() )
update.cancel();
node = e.getFirstAdded();
if ( node ) {
populateEdges(node);
update.runNow();
}
}
}
registry.getDefaultFocusSet().addFocusListener(new MyFocusListener(update));
forces.runNow();
frame = new java::JFrame();
// create and display application window
frame.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE);
frame.getContentPane().add(display, BorderLayout::CENTER);
frame.pack();
frame.show();
addNode "http://www.google.com/";
addNode "http://www.thinkgeek.com/";
addNode "http://www.slashdot.org/";
update.runNow();
Jacl
Jacl is a hosted TCL interpreter for the Java VM. Jacl's origin at Sun Microsystems in 1998 makes it one of the oldest Java-based scripting languages projects.
Invoking TCL Scripts
The main entrypoint for the Jacl interpreter is the tcl.lang.Interp class. Instances of Interp can be constructed by simply calling the default constructor.
Call interp.eval(String tclCode) and interp.evalFile(String fileName).
Call interp.setVar() and interp.unsetVar() to set variables.
Interacting with Java objects from TCL
TCL is typically seen as a procedural language, where the first word in an expression specifies the name of a procedure to call and the subsequent words are arguments to that statement.
puts "This line prints the value " $x "."
(very) brief history of OO TCL here?
However, JACL adopts the convention that when calling a method on an object, the first word will be the object that you're referencing, the second will be the name of the method, and subsequent words are the arguments.
$obj method1 arg1 arg2 argN
Sub-expressions can be specified with square brackets ([]), so two chained method calls could be written like this:
[[$obj method1] method2] method3]
Objects are instantiated with the java::new procedure, which takes a Java class name and one or more arguments and tries to find the appropriate constructor. For example:
set random [java::new java.util.Random $seed]
We can now call its methods as follows:
set v1 [$random nextInt]
When a method is overloaded and the method arguments present any ambiguity about which method should be called (e.g., if all arguments are null), you can use the following syntax to indicate to Jacl which method should be called:
$obj {method argType1 argType2} $arg1 $arg2
One thing to be aware of is that, unlike other dynamic languages such as BeanShell, Jacl's integration with Java uses static typing. There is no compile-time type-checking, and variables are not typed, but each reference to a Java object has an associated type. You will not be able to call methods or reference fields of the object that are not declared on the associated type. This means that if you call a method that returns a Pet, you cannot call its meow method unless you first use java::cast to cast the reference to the appropriate class.
set pet [$animalFactory create "Whiskers"]
$pet meow # WILL NOT WORK -- Reference is a 'Pet'.
set cat [java::cast $pet Cat]
$cat meow # Reference contains the same object,
# but a different type ('Cat').
There is no way to implement an interface or create generic Java proxies objects from TCL. However, the java::bind procedure can be used to create listeners for Java Bean events. This uses the BeanInfo class to look up events by name and generates custom Java bytecode to implement the listener objects.
java::bind $obj actionPerformed { your code }
The TCL catch {} construct can be used to trap Java exceptions. The TCL error procedure will instantiate a tcl.lang.TclException and throw it. If no TCL code catches it, it will propagate out of the original call to TCL and can then be caught in Java code. There is arbitrary Throwable objects. For example:
java::throw [java::new ClassNotFoundException "bad class foo"]
Interacting with TCL objects from Java
- TclBoolean
- TclInteger
- TclDouble
- TclString
- TclList
- TclObject
- ReflectObject
- TclRuntimeError
- TclEvent
- TclIndex
Creating Commands from Java
New TCL procedures can be implemented directly in Java code by implementing the tcl.lang.Command interface.
Writing Extensions
package require java
java::load -classpath . SimpleExtension
sayhello
BSF Support
A BSFEngine implementation for Jacl is included with BSF. You should not have to register Jacl explicitly -- simply include the two jars (jacl.jar and tcljava.jar) in your classpath and use the language named jacl. Jacl's BSFEngine manages the conversion of data types from the wrappers provided in tcljava.jar to standard Java data types.
Limitations
Jacl always respects the Java access checking mechanism, so method calls and field lookups are subject to normal private/protected semantics. Nothing will stop you from using the Java Reflection API through TCL directly, however.
- Mention support for tracing variables? Or in Chapter 11?
Other Interpreters
There are a number of other languages with interpreters available for Java.
- Rhino
- Bistro
- NetRexx
- Pnuts
- BeanBASIC
Continue on to Chapter 6: Compilers in Java.

