Making Scripting Languages JSR-223-Aware Blog

Version 2


    <  /div>

    The new Java Scripting API integrates scripting languages into the Java environment. JSR-223-aware applications can execute scripts and, if the scripting language supports this feature, exchange data objects with them. My article "Scripting for the Java Platform" shows you how to query available languages and how to communicate with them. But what does it take to make existing scripting languages JSR-223-aware? This article is based on my work on Java-AppleScript-Connector, a bridge between AppleScriptand Java. I will explain important classes and interfaces you need to provide and offer sample implementations as well as best practices.


    For a client application, the entry class to scripting isjavax.script.ScriptEngineManager. Its methodsgetEngineByExtension(),getEngineByMimeType(), andgetEngineByName() return instances ofjavax.script.ScriptEngine. A JSR-223-compliant scripting language must implement this interface, and its methods must be fully functional. Alternatively, programs can invokegetEngineFactories(), which returns instances ofScriptEngineFactory. A factory exposes metadata that describes the corresponding engine. You must implementScriptEngineFactory, too.ScriptEngineManager uses the service provider mechanism to obtain instances of all availableScriptEngineFactorys.

    The JAR file specification defines a service as a well-known set of interfaces and (usually) abstract classes. A service provider is a specific implementation of such a service. For scripting, the service consists of javax.script.ScriptEngineFactory. All classes that implement this interface are service providers. Service providers identify themselves by placing a so-called provider-configuration file in META-INF/services. Its filename corresponds to the fully qualified name of the service class, which is javax.script.ScriptEngineFactory. Each line of this file contains the fully qualified name of a service provider. For example, the factory class of Java-AppleScript-Connector is calledAppleScriptScriptEngineFactory. Its fully qualified class name isde.thomaskuenneth.jasconn.AppleScriptScriptEngineFactory. So the fileMETA-INF/services/javax.script.ScriptEngineFactorycontains one line with exactly this class name.

    To sum it up, a JSR-223-compliant scripting language must at least:

    • Implement javax.script.ScriptEngine.
    • Implement javax.script.ScriptEngineFactory.
    • Register itself as a service provider forjavax.script.ScriptEngineFactory.

    Though it is not a requirement, I suggest you give the classes implementing these interfaces names that start with the name of the language and end in ScriptEngine orScriptEngineFactory as appropriate, which makes it easy to identify what these classes do. This article is accompanied by a sample implementation called DummyLanguage. (Please refer to the Resources section for details.) The sample code consists of two base classes calledDummyLanguageScriptEngine andDummyLanguageScriptEngineFactory. We will now take a closer look at some of their methods.


    Currently, classes implementing ScriptEngine must provide 14 methods. They can be divided into several groups:

    • A factory method
    • Methods dealing with contexts
    • Methods dealing with bindings
    • Methods dealing with key-value pairs
    • Methods that run scripts

    Each script engine has a corresponding factory, which is returned by getFactory(). DummyLanguage always returns the same instance, which has been created upon initialization and is kept in a static variable.

    Execution of scripts takes place in a so-called script context, which exposes Readers and Writers that can be used by the script engine. For example,getErrorWriter() tells the engine where error messages should go and setWriter() sets the Writerfor scripts to use when displaying output. It is important to distinguish these from the Reader arguments of theeval() method, which specify the source of a script.

    The script context also maintains so-called bindings, which are bound name-value pairs. Globally scoped attributes are visible by all engines that have been created by the sameScriptEngineManager. Engine-scoped key-value pars, on the other hand, belong to individual instances of script engines and are visible only during their lifetime. Script contexts are maintained by classes implementing the ScriptContextinterface. Sun provides a basic implementation calledSimpleScriptContext.

    Two methods of ScriptEngine deal with script contexts. getContext() returns the default context that is used if no script context is specified; for example, as an argument to eval(). Appropriately,setContext() sets it. The initial default context of DummyLanguage is an instance ofSimpleScriptContext.

    Bindings are name-value pairs whose keys are alwaysStrings. Their behavior is defined through thejavax.script.Bindings interface. As forScriptContext, Sun provides a basic implementation called SimpleBindings. Although bindings belong to script contexts, ScriptEngine providescreateBindings(), which returns an uninitialized binding. DummyLanguage simply creates an instance ofSimpleBindings. Another method,getBindings(), exists to return the bindings of a certain scope. As you have already seen, there are at least two scopes, ScriptContext.GLOBAL_SCOPE andScriptContext.ENGINE_SCOPE. They represent key-value pairs that are either visible to all instances of a script engine that have been created by the sameScriptengineManager, or visible only during the lifetime of a certain script engine instance. Though this may seem quite difficult to implement, the documentation gives us a clue how to do it. It says:

    The Bindings instances that are returned must be identical to those returned by thegetBindings() method of ScriptContextcalled with corresponding arguments on the defaultScriptContext of theScriptEngine.

    So we just have to call getContext() to retrieve the current default context and then invoke itsgetBindings() method, passing the requested scope as its only parameter. The same applies to setBindings(), which sets the bindings of some scope. DummyLanguage just does this:

    getContext().setBindings(bindings, scope);

    ScriptEngine has two more methods that access key-value pairs. get() retrieves a value that has been set in the engine scope. So DummyLanguage just has to return the result ofgetBindings(ScriptContext.ENGINE_SCOPE).get(). Its counterpart, put(), sets a key-value pair in the engine scope, so you first need to callgetBindings(ScriptContext.ENGINE_SCOPE) and then invoke its put() method.

    There are currently seven predefined names that have a special meaning. ENGINE maps to the name of the script engine implementation. ENGINE_VERSION identifies its version.LANGUAGE holds the full name of the scripting language supported by this implementation, whereas NAMEcontains its short name. The version of the scripting language being implemented is specified through theLANGUAGE_VERSION key. The name of the current script is stored in FILENAME. Finally, ARGVcontains an array of positional arguments. A good place to set these key-value pairs is the constructor, where you can invokeput().

    There are a few more methods of ScriptEngine we need to look at. All six deal with script execution. Generally, there are three versions:

    • One takes just the script.
    • One takes the script and a script context.
    • One takes the script and a binding.

    The script can be passed as a string or through aReader. In order to minimize the implementation effort, you can convert Reader-based scripts into strings by utilizing StringWriter and then invoking the corresponding method that expects a String. TheDummyLanguageScriptEngine shows you how to do that. If no script context is specified, the default one is used. Consequently, your implementation might look like this:return eval(script, getContext()). If aBindings argument has been passed, it is used as theENGINE_SCOPE bindings of the script engine. Once again, the documentation gives us a clue what to do here.

    The Reader, Writer, and non-ENGINE_SCOPE Bindings of the defaultScriptContext are used [for script execution]. TheENGINE_SCOPE Bindings of theScriptEngine is not changed, and its mappings are unaltered by the script execution.

    The underlying idea is to temporarily change the engine scope bindings and re-set the original version after the script execution. Here is what DummyLanguage does.

    Bindings current = getContext().getBindings(ScriptContext.ENGINE_SCOPE); getContext().setBindings(bindings, ScriptContext.ENGINE_SCOPE); Object result = eval(reader); getContext().setBindings(current, ScriptContext.ENGINE_SCOPE);

    How to implement the actual script execution (i.e., what is done within eval()) of course depends on the scripting language in question. If it has been implemented using Java, you may be able to pass bindings; on the other hand, this will be quite difficult if you need to start external processes that know nothing about Java objects. Still, depending on the target language, basic script invocation may be quite easy. Here is how Java-AppleScript-Connector interacts with native resources.

    public Object eval(String string, ScriptContext scriptContext) throws ScriptException { NSAppleScript script = new NSAppleScript(string); NSMutableDictionary errors = new NSMutableDictionary(); NSAppleEventDescriptor results = script.execute(errors); if (errors.count() == 0) { return results; } System.err.println(errors.toString()); return null; }

    As you can see, invoking AppleScript scripts involves just three steps: first, we transform the String-based representation into an object. Then we instantiate a container object that will collect errors. Finally, we execute the script.

    The following section covers the second mandatory interface for JSR-223-compliant scripting languages,ScriptEngineFactory.


    A ScriptEngineFactory provides information about script engines and allows you to instantiate them. Once you have aScriptEngineManager object, you can invoke itsgetEngineFactories() to get ajava.util.List of ScriptEngineFactoryinstances.

    getScriptEngine() returns an instance of the script engine that is represented by this factory. Implementations may choose to pool, share, or reuse instances, but should generally create new ones. For the sake of brevity, DummyLanguage always returns the same object.

    getEngineName() and getEngineVersion()return the full name of the script engine and its version.getLanguageName() andgetLanguageVersion() provide similar information regarding the language the script engine is implementing.ScriptEngineFactory implementations could store such values as local copies. However, some of them are available through bindings, too. So it is easy to obtain them like this:

    return getScriptEngine().get(ScriptEngine.ENGINE).toString();

    The same applies to getParameter(), which can be implemented easily as follows:

    return getScriptEngine().get(key).toString();
    The documentation of this method describes a reserved key called THREADING, which seems to be described nowhere else. I suggest you set this value just like the other keys that already are defined as constants inScriptEngine.

    Other methods return immutable lists of names, MIME types, or extensions. DummyLanguage creates them usingArrays.asList(). The remaining methods deal with language-specific features. For example, getProgram()returns a valid scripting language program consisting of the passed statements. Please refer to the preliminary API documentation of ScriptEngineFactoryfor more information on these methods. When concatenatingStrings, please consider the use ofStringBuffer for efficiency reasons. This applies togetOutputStatement() andgetMethodCallSyntax(), as well.


    Establishing a basic link between Java and existing scripting languages using JSR 223 is quite easy. The number of interfaces you need to implement is fairly small. Additionally, Sun provides useful helper classes that you can take advantage of in your implementation. However, invoking existing scripts is just the beginning. True interaction takes place only if both Java and the scripting language can access each other's variables. My experience from my work on Java-AppleScript-Connector is that this is much harder to achieve.

    Also, please take into account that there are more interfaces that an advanced implementation of JSR 223 should offer. These arejavax.script.Compilable andjavax.script.Invocable.

    A final point I would like to make is that although there is now a proposed final draft of the JSR 223 specification, the new package javax.script still might be subject to change. So if you decide to jump on the new Java Scripting API please take a close look at the documentation once the specification is final.