Compiling and Scripting - Core Java for the Impatient (2013)

Core Java for the Impatient (2013)

Chapter 14. Compiling and Scripting

Topics in This Chapter

Image 14.1 The Compiler API

Image 14.2 The Scripting API

Image 14.3 The Nashorn Scripting Engine

Image 14.4 Shell Scripting with Nashorn

Image Exercises

In this chapter, you will learn how to use the compiler API to compile Java code from inside of your application. You will also see how to run programs written in other languages from your Java programs, using the scripting API. This is particularly useful if you want to give your users the ability to enhance your program with scripts.

The key points of this chapter are:

1. With the compiler API, you can generate Java code on the fly and compile it.

2. The scripting API lets Java program interoperate with a number of scripting languages.

3. The JDK includes Nashorn, a JavaScript interpreter with good performance and fidelity to the JavaScript standard.

4. Nashorn offers a convenient syntax for working with Java lists and maps, as well as JavaBeans properties.

5. Nashorn supports lambda expressions and a limited mechanism for extending Java classes and implementing Java interfaces.

6. Nashorn has support for writing shell scripts in JavaScript.

14.1 The Compiler API

There are quite a few tools that need to compile Java code. Obviously, development environments and programs that teach Java programming are among them, as well as testing and build automation tools. Another example is the processing of JavaServer Pages—web pages with embedded Java statements.

14.1.1 Invoking the Compiler

It is very easy to invoke the compiler. Here is a sample call:

Click here to view code image

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
OutputStream outStream = ...;
OutputStream errStream = ...;
int result = compiler.run(null, outStream, errStream,
"-sourcepath", "src", "Test.java");

A result value of 0 indicates successful compilation.

The compiler sends output and error messages to the provided streams. You can set these parameters to null, in which case System.out and System.err are used. The first parameter of the run method is an input stream. As the compiler takes no console input, you can always leave it as null. (The run method is inherited from a generic Tool interface, which allows for tools that read input.)

The remaining parameters of the run method are the arguments that you would pass to javac if you invoked it on the command line. These can be options or file names.

14.1.2 Launching a Compilation Task

You can have more control over the compilation process with a CompilationTask object. This can be useful if you want to supply source from string, capture class files in memory, or process the error and warning messages.

To obtain a CompilationTask, start with a compiler object as in the preceding section. Then call

Click here to view code image

JavaCompiler.CompilationTask task = compiler.getTask(
errorWriter, // Uses System.err if null
fileManager, // Uses the standard file manager if null
diagnostics, // Uses System.err if null
options, // null if no options
classes, // For annotation processing; null if none
sources);

The last three arguments are Iterable instances. For example, a sequence of options might be specified as

Click here to view code image

Iterable<String> options = Arrays.asList("-d", "bin");

The sources parameter is an Iterable of JavaFileObject instances. If you want to compile disk files, get a StandardJavaFileManager and call its getJavaFileObjects method:

Click here to view code image

StandardJavaFileManager fileManager =
compiler.getStandardFileManager(null, null, null);
Iterable<JavaFileObject> sources =
fileManager.getJavaFileObjectsFromFiles("File1.java", "File2.java");
JavaCompiler.CompilationTask task = compiler.getTask(
null, null, null, options, null, sources);


Image Note

The classes parameter is only used for annotation processing. In that case, you also need to call task.processors(annotationProcessors) with a list of Processor objects. See Chapter 11 for an example of annotation processing.


The getTask method returns the task object but does not yet start the compilation process. The CompilationTask class extends Callable<Boolean>. You can pass it to an ExecutorService for concurrent execution, or you can just make a synchronous call:

Click here to view code image

Boolean success = task.call();

14.1.3 Reading Source Files from Memory

If you generate source code on the fly, you can have it compiled from memory, without having to save files to disk. Use this class to hold the code:

Click here to view code image

public class StringSource extends SimpleJavaFileObject {
private String code;

StringSource(String name, String code) {
super(URI.create("string:///" + name.replace('.','/') + ".java"),
Kind.SOURCE);
this.code = code;
}

public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}

Then generate the code for your classes and give the compiler a list of StringSource objects.

Click here to view code image

String pointCode = ...;
String rectangleCode = ...;
List<StringSource> sources = Arrays.asList(
new StringSource("Point", pointCode),
new StringSource("Rectangle", rectangleCode));
task = compiler.getTask(null, null, null, null, null, sources);

14.1.4 Writing Byte Codes to Memory

If you compile classes on the fly, there is no need to save the class files to disk. You can save them to memory and load them right away.

First, here is a class for holding the bytes:

Click here to view code image

public class ByteArrayClass extends SimpleJavaFileObject {
private ByteArrayOutputStream out;

ByteArrayClass(String name) {
super(URI.create("bytes:///" + name.replace('.','/') + ".class"),
Kind.CLASS);
}

public byte[] getCode() {
return out.toByteArray();
}

public OutputStream openOutputStream() throws IOException {
out = new ByteArrayOutputStream();
return out;
}
}

Next, you need to rig the file manager to use these classes for output:

Click here to view code image

List<ByteArrayClass> classes = new ArrayList<>();
StandardJavaFileManager stdFileManager
= compiler.getStandardFileManager(null, null, null);
JavaFileManager fileManager
= new ForwardingJavaFileManager<JavaFileManager>(stdFileManager) {
public JavaFileObject getJavaFileForOutput(Location location,
String className, Kind kind, FileObject sibling)
throws IOException {
if (kind == Kind.CLASS) {
ByteArrayClass outfile = new ByteArrayClass(className);
classes.add(outfile);
return outfile;
} else
return super.getJavaFileForOutput(
location, className, kind, sibling);
}
};

To load the classes, you need a class loader (see Chapter 4):

Click here to view code image

public class ByteArrayClassLoader extends ClassLoader {
private Iterable<ByteArrayClass> classes;

public ByteArrayClassLoader(Iterable<ByteArrayClass> classes) {
this.classes = classes;
}

@Override public Class<?> findClass(String name) throws ClassNotFoundException {
for (ByteArrayClass cl : classes) {
if (cl.getName().equals("/" + name.replace('.','/') + ".class")) {
byte[] bytes = cl.getCode();
return defineClass(name, bytes, 0, bytes.length);
}
}
throw new ClassNotFoundException(name);
}
}

After compilation has finished, call the Class.forName method with that class loader:

Click here to view code image

ByteArrayClassLoader loader = new ByteArrayClassLoader(classes);
Class<?> cl = Class.forName("Rectangle", true, loader);

14.1.5 Capturing Diagnostics

To listen to error messages, install a DiagnosticListener. The listener receives a Diagnostic object whenever the compiler reports a warning or error message. The DiagnosticCollector class implements this interface. It simply collects all diagnostics so that you can iterate through them after the compilation is complete.

Click here to view code image

DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
compiler.getTask(null, fileManager,
collector, null, null, sources).call();
for (Diagnostic<? extends JavaFileObject> d : collector.getDiagnostics()) {
System.out.println(d);
}

A Diagnostic object contains information about the problem location (including file name, line number, and column number) as well as a human-readable description.

You can also install a DiagnosticListener to the standard file manager, in case you want to trap messages about missing files:

Click here to view code image

StandardJavaFileManager fileManager
= compiler.getStandardFileManager(diagnostics, null, null);

14.2 The Scripting API

A scripting language is a language that avoids the usual edit/compile/link/run cycle by interpreting the program text at runtime. This encourages experimentation. Also, scripting languages tend to be less complex, which makes them suitable as extension languages for expert users of your programs.

The scripting API lets you combine the advantages of scripting and traditional languages. It enables you to invoke scripts written in JavaScript, Groovy, Ruby, and even exotic languages such as Scheme and Haskell, from a Java program. In the following sections, you will see how to select an engine for a particular language, how to execute scripts, and how to take advantage of advanced features that some scripting engines offer.

14.2.1 Getting a Scripting Engine

A scripting engine is a library that can execute scripts in a particular language. When the virtual machine starts, it discovers the available scripting engines. To enumerate them, construct a ScriptEngineManager and invoke the getEngineFactories method.

Usually, you know which engine you need, and you can simply request it by name. For example:

Click here to view code image

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");

The Java Development Kit contains a JavaScript engine called “Nashorn” described in Section 14.3, “The Nashorn Scripting Engine,” on p. 428. You can add more languages by providing the necessary JAR files on the class path. There is no longer an official list of languages with Java scripting integration. Just use your favorite search engine to find “JSR 223 support” for your favorite language.

Once you have an engine, you can call a script simply by invoking

Click here to view code image

Object result = engine.eval(scriptString);

You can also read a script from a Reader:

Click here to view code image

Object result = engine.eval(Files.newBufferedReader(path, charset));

You can invoke multiple scripts on the same engine. If one script defines variables, functions, or classes, most scripting engines retain the definitions for later use. For example,

Click here to view code image

engine.eval("n = 1728");
Object result = engine.eval("n + 1");

will return 1729.


Image Note

To find out whether it is safe to concurrently execute scripts in multiple threads, call engine.getFactory().getParameter("THREADING"). The returned value is one of the following:

• null: Concurrent execution is not safe.

• "MULTITHREADED": Concurrent execution is safe. Effects from one thread might be visible from another thread.

• "THREAD-ISOLATED": In addition, different variable bindings are maintained for each thread.

• "STATELESS": In addition, scripts do not alter variable bindings.


14.2.2 Bindings

A binding consists of a name and an associated Java object. For example, consider these statements:

Click here to view code image

engine.put("k", 1728);
Object result = engine.eval("k + 1");

Conversely, you can retrieve variables that were bound by scripting statements:

Click here to view code image

engine.eval("n = 1728");
Object result = engine.get("n");

These bindings live in the engine scope. In addition, there is a global scope. Any bindings that you add to the ScriptEngineManager are visible to all engines.

Instead of adding bindings to the engine or global scope, you can collect them in an object of type Bindings and pass it to the eval method:

Click here to view code image

Bindings scope = engine.createBindings();
scope.put("k", 1728);
Object result = engine.eval("k + 1", scope);

This is useful if a set of bindings should not persist for future calls to the eval method.

14.2.3 Redirecting Input and Output

You can redirect the standard input and output of a script by calling the setReader and setWriter methods of the script context. For example,

Click here to view code image

StringWriter writer = new StringWriter();
engine.getContext().setWriter(writer);
engine.eval("print('Hello')");
String result = writer.toString();

Any output written with the JavaScript print function is sent to writer.

The setReader and setWriter methods only affect the scripting engine’s standard input and output sources. For example, if you execute the JavaScript code

Click here to view code image

print('Hello');
java.lang.System.out.println('World');

only the first output is redirected.


Image Note

The Nashorn engine does not have the notion of a standard input source. Calling setReader has no effect.



Image Note

In JavaScript, semicolons at the end of a line are optional. Many JavaScript programmers put them in anyway, but in this chapter, I omit them so you can more easily distinguish between Java and JavaScript code snippets.

For the same reason, I use '...', not "...", for JavaScript strings whenever possible.


14.2.4 Calling Scripting Functions and Methods

With some scripting engines, you can invoke a function in the scripting language without evaluating the code for the invocation as a script. This is useful if you allow users to implement a service in a scripting language of their choice, so that you can call it from Java.

The scripting engines that offer this functionality (among them Nashorn) implement the Invocable interface. To call a function, call the invokeFunction method with the function name, followed by the arguments:

Click here to view code image

// Define greet function in JavaScript
engine.eval("function greet(how, whom) { return how + ', ' + whom + '!' }");

// Call the function with arguments "Hello", "World"
result = ((Invocable) engine).invokeFunction(
"greet", "Hello", "World");

If the scripting language is object-oriented, call invokeMethod:

Click here to view code image

// Define Greeter class in JavaScript
engine.eval("function Greeter(how) { this.how = how }");
engine.eval("Greeter.prototype.welcome = "
+ " function(whom) { return this.how + ', ' + whom + '!' }");
// Construct an instance
Object yo = engine.eval("new Greeter('Yo')");

// Call the welcome method on the instance
result = ((Invocable) engine).invokeMethod(yo, "welcome", "World");


Image Note

For more information on how to define classes in JavaScript, see JavaScript—The Good Parts by Douglas Crockford (O’Reilly, 2008).



Image Note

If the script engine does not implement the Invocable interface, you might still be able to call a method in a language-independent way. The getMethodCallSyntax method of the ScriptEngineFactory class produces a string that you can pass to the eval method.


You can go a step further and ask the scripting engine to implement a Java interface. Then you can call scripting functions and methods with the Java method call syntax.

The details depend on the scripting engine, but typically you need to supply a function for each method of the interface. For example, consider a Java interface

Click here to view code image

public interface Greeter {
String welcome(String whom);
}

If you define a global function with the same name in Nashorn, you can call it through this interface.

Click here to view code image

// Define welcome function in JavaScript
engine.eval("function welcome(whom) { return 'Hello, ' + whom + '!' }");
// Get a Java object and call a Java method
Greeter g = ((Invocable) engine).getInterface(Greeter.class);
result = g.welcome("World");

In an object-oriented scripting language, you can access a script class through a matching Java interface. For example, here is how to call an object of the JavaScript Greeter class with Java syntax:

Click here to view code image

Greeter g = ((Invocable) engine).getInterface(yo, Greeter.class);
result = g.welcome("World");

See Exercise 2 for a more useful example.

14.2.5 Compiling a Script

Some scripting engines can compile scripting code into an intermediate form for efficient execution. Those engines implement the Compilable interface. The following example shows how to compile and evaluate code contained in a script file:

Click here to view code image

if (engine implements Compilable) {
Reader reader = Files.newBufferedReader(path, charset);
CompiledScript script = ((Compilable) engine).compile(reader);
script.eval();
}

Of course, it only makes sense to compile a script if it does a lot of work or if you need to execute it frequently.

14.3 The Nashorn Scripting Engine

The Java Development Kit ships with a JavaScript engine called Nashorn, which is very fast and highly compliant with version 5.1 of the ECMAScript standard for JavaScript. You can use Nashorn like any other script engine, but it also has special features for interoperating with Java.


Image Note

Nashorn is the German word for rhinoceros—literally, nose-horn, an allusion to a well-regarded JavaScript book that has a rhino on the cover. (You get extra karma for pronouncing it nas-horn, not na-shorn.)


14.3.1 Running Nashorn from the Command Line

Java 8 ships with a command-line tool called jjs. Simply launch it, and issue JavaScript commands.

$ jjs
jjs> 'Hello, World'
Hello, World

You get what’s called a “read-eval-print” loop, or REPL, in the world of Lisp, Scala, and so on. Whenever you enter an expression, its value is printed.

jjs> 'Hello, World!'.length
13

You can define functions and call them:

Click here to view code image

jjs> function factorial(n) { return n <= 1 ? 1 : n * factorial(n - 1) }
function factorial(n) { return n <= 1 ? 1 : n * factorial(n - 1) }
jjs> factorial(10)
3628800


Image Tip

When writing more complex functions, it is a good idea to put the JavaScript code into a file and load it into jjs with the load command:

load('functions.js')


You can call Java methods:

Click here to view code image

var url = new java.net.URL('http://horstmann.com')
var input = new java.util.Scanner(url.openStream())
input.useDelimiter('$')
var contents = input.next()

Now, when you type contents, you see the contents of the web page.

Look how refreshing this is. You didn’t have to worry about exceptions. You can make experiments dynamically. I wasn’t quite sure whether I could read the entire contents by setting the delimiter to $, but I tried it out and it worked. And I didn’t have to write public static void main. I didn’t have to compile a thing. I didn’t have to make a project in my IDE. The REPL is an easy way to explore an API. It is a bit odd that one drives Java from JavaScript, but it is also convenient. Note how I didn’t have to define the types for the input and contents variables.


Image Tip

The JavaScript REPL would be even more refreshing if it supported command-line editing. On Linux/Unix/Mac OS X, you can install rlwrap and run rlwrap jjs. Then you can press the ↑ key to get the previous commands, and you can edit them. Alternatively, you can run jjs inside Emacs. Don’t worry—this won’t hurt a bit. Start Emacs and hit M-x (that is, Alt+x or Esc x) shell Enter, then type jjs. Type expressions as usual. Use M-p and M-n to recall the previous or next line, and the left and right arrow keys to move within a line. Edit a command, then press Enter to see it executed.


14.3.2 Invoking Getters, Setters, and Overloaded Methods

When you have Java objects in a Nashorn program, you can invoke methods on them. For example, suppose you get an instance of the Java class NumberFormat:

Click here to view code image

var fmt = java.text.NumberFormat.getPercentInstance()

Of course, you can call a method on it:

Click here to view code image

fmt.setMinimumFractionDigits(2)

But in the case of a property getter or setter, you can do better than that, using the property access syntax:

fmt.minimumFractionDigits = 2

If the expression fmt.minimumFractionDigits occurs to the left of the = operator, it is translated to an invocation of the setMinimumFractionDigits method. Otherwise it turns into a call fmt.getMinimumFractionDigits().

You can even use the JavaScript bracket notation to access properties:

Click here to view code image

fmt['minimumFractionDigits'] = 2

Note that the argument of the [] operator is a string. In this context, it’s not useful, but you can call fmt[str] with a string variable and thereby access arbitrary properties.

JavaScript has no concept of method overloading. There can be only one method with a given name, and it can have any number of parameters of any type. Nashorn attempts to pick the correct Java method by looking at the number and types of the parameters.

In almost all cases, there is only one Java method that matches the supplied parameters. If there is not, you can manually pick the correct method with the following rather strange syntax:

list['remove(Object)'](1)

Here, we specify the remove(Object) method that removes the Integer object 1 from the list. (There is also a remove(int) method that removes the object at position 1.)

14.3.3 Constructing Java Objects

When you want to construct objects in JavaScript (as opposed to having them handed to you from the scripting engine), you need to know how to access Java packages. There are two mechanisms.

There are global objects java, javax, javafx, com, org, and edu that yield package and class objects via the dot notation. For example,

Click here to view code image

var javaNetPackage = java.net // A JavaPackage object
var URL = java.net.URL // A JavaClass object

If you need to access a package that does not start with one of the above identifiers, you can find it in the Package object, such as Package.ch.cern.

Alternatively, call the Java.type function:

Click here to view code image

var URL = Java.type('java.net.URL')

This is a bit faster than java.net.URL, and you get better error checking. (If you make a spelling error such as java.net.Url, Nashorn will think it is a package.) But if you want speed and good error handling, you probably shouldn’t be using a scripting language in the first place, so I will stick with the shorter form.


Image Note

The Nashorn documentation suggests that class objects should be defined at the top of a script file, just like you place imports at the top of a Java file:

Click here to view code image

var URL = Java.type('java.net.URL')
var JMath = Java.type('java.lang.Math')
// Avoids conflict with JavaScript Math object


Once you have a class object, you can call static methods:

JMath.floorMod(-3, 10)

To construct an object, pass the class object to the JavaScript new operator. Pass any constructor parameters in the usual way:

Click here to view code image

var URL = java.net.URL
var url = new URL('http://horstmann.com')

If you aren’t concerned about efficiency, you can also call

Click here to view code image

var url = new java.net.URL('http://horstmann.com')


Image Caution

If you use Java.type with new, you need an extra set of parentheses:

Click here to view code image

var url = new (Java.type('java.net.URL'))('http://horstmann.com')


If you need to specify an inner class, you can do so with the dot notation:

Click here to view code image

var entry = new java.util.AbstractMap.SimpleEntry('hello', 42)

Alternatively, if you use Java.type, add a $ like the JVM does:

Click here to view code image

var Entry = Java.type('java.util.AbstractMap$SimpleEntry')

14.3.4 Strings in JavaScript and Java

Strings in Nashorn are, of course, JavaScript objects. For example, consider the call

Click here to view code image

'Hello'.slice(-2) // Yields 'lo'

Here, we call the JavaScript method slice. There is no such method in Java.

But the call

'Hello'.compareTo('World')

also works, even though in JavaScript there is no compareTo method. (You just use the < operator.)

In this case, the JavaScript string is converted to a Java string. In general, a JavaScript string is converted to a Java string whenever it is passed to a Java method.

Also note that any JavaScript object is converted to a string when it is passed to a Java method with a String parameter. Consider

Click here to view code image

var path = java.nio.file.Paths.get(/home/)
// A JavaScript RegExp is converted to a Java String!

Here, /home/ is a regular expression. The Paths.get method wants a String, and it gets one, even though it makes no sense in this situation. One shouldn’t blame Nashorn for this. It follows the general JavaScript behavior to turn anything into a string when a string is expected. The same conversion happens for numbers and Boolean values. For example, 'Hello'.slice('-2') is perfectly valid. The string '-2' is silently converted to the number –2. It is features like this one that make programming in a dynamically typed language such an exciting adventure.

14.3.5 Numbers

JavaScript has no explicit support for integers. Its Number type is the same as the Java double type. When a number is passed to Java code that expects an int or long, any fractional part is silently removed. For example, 'Hello'.slice(-2.99) is the same as 'Hello'.slice(-2).

For efficiency, Nashorn keeps computations as integers when possible, but that difference is generally transparent. Here is one situation when it is not:

Click here to view code image

var price = 10
java.lang.String.format('Price: %.2f', price)
// Error: f format not valid for java.lang.Integer

The value of price happens to be an integer, and it is assigned to an Object since the format method has an Object... varargs parameter. Therefore, Nashorn produces a java.lang.Integer. That causes the format method to fail because the f format is intended for floating-point numbers. In this case, you can force conversion to java.lang.Double by calling the Number function:

Click here to view code image

java.lang.String.format('Unit price: %.2f', Number(price))

14.3.6 Working with Arrays

To construct a Java array, first make a class object:

Click here to view code image

var intArray = Java.type('int[]')
var StringArray = Java.type('java.lang.String[]')

Then call the new operator and supply the length of the array:

Click here to view code image

var numbers = new intArray(10) // A primitive int[] array
var names = new StringArray(10) // An array of String references

Then use the bracket notation in the usual way:

numbers[0] = 42
print(numbers[0])

You get the length of the array as numbers.length. To iterate through all values of the names array, use

for each (var elem in names)
Do something with elem

This is the equivalent of the enhanced for loop in Java. If you need the index values, use the following loop instead:

Click here to view code image

for (var i in names)
Do something with i and names[i]


Image Caution

Even though this loop looks just like the enhanced for loop in Java, it visits the index values. JavaScript arrays can be sparse. Suppose you initialize a JavaScript array as

var names = []
names[0] = 'Fred'
names[2] = 'Barney'

Then the loop for (var i in names) print(i) prints 0 and 2.


Java and JavaScript arrays are quite different. When you supply a JavaScript array where a Java array is expected, Nashorn will carry out the conversion. But sometimes, you need to help it along. Given a JavaScript array, use the Java.to method to obtain the equivalent Java array:

Click here to view code image

var javaNames = Java.to(names, StringArray) // An array of type String[]

Conversely, use Java.from to turn a Java array into a JavaScript array:

Click here to view code image

var jsNumbers = Java.from(numbers)
jsNumbers[-1] = 42

You need to use Java.to to resolve overload ambiguities. For example,

Click here to view code image

java.util.Arrays.toString([1, 2, 3])

is ambiguous since Nashorn can’t decide whether to convert to an int[] or Object[] array. In that situation, call

Click here to view code image

java.util.Arrays.toString(Java.to([1, 2, 3], Java.type('int[]')))

or simply

Click here to view code image

java.util.Arrays.toString(Java.to([1, 2, 3], 'int[]'))

14.3.7 Lists and Maps

Nashorn provides “syntactic sugar” for Java lists and maps. You can use the bracket operator with any Java List to invoke the get and set methods:

Click here to view code image

var names = java.util.Arrays.asList('Fred', 'Wilma', 'Barney')
var first = names[0]
names[0] = 'Duke'

The bracket operator also works for Java maps:

Click here to view code image

var scores = new java.util.HashMap
scores['Fred'] = 10 // Calls scores.put('Fred', 10)

To visit all elements in the map, you can use the JavaScript for each loop:

Click here to view code image

for (var key in scores) ...
for each (var value in scores) ...

If you want to process keys and values together, simply iterate over the entry set:

Click here to view code image

for each (var e in scores.entrySet())
Process e.key and e.value


Image Note

The for each loop works for any Java class that implements the Iterable interface.


14.3.8 Lambdas

JavaScript has anonymous functions, such as

Click here to view code image

var square = function(x) { return x * x }
// The right-hand side is an anonymous function
var result = square(2)
// The () operator invokes the function

Syntactically, such an anonymous function is very similar to a Java lambda expression. Instead of an arrow after the parameter list, you have the keyword function.

You can use an anonymous function as a functional interface argument of a Java method, just like you could use a lambda expression in Java. For example,

Click here to view code image

java.util.Arrays.sort(words,
function(a, b) { return java.lang.Integer.compare(a.length, b.length) })
// Sorts the array by increasing length

Nashorn supports shorthand for functions whose body is a single expression. For such functions, you can omit the braces and the return keyword:

Click here to view code image

java.util.Arrays.sort(words,
function(a, b) java.lang.Integer.compare(a.length, b.length))

Again, note the similarity with a Java lambda expression (a, b) -> Integer. compare(a.length, b.length).


Image Note

That shorthand notation (called an “expression closure”) is not part of the official JavaScript language standard (ECMAScript 5.1), but it is also supported by the Mozilla JavaScript implementation.


14.3.9 Extending Java Classes and Implementing Java Interfaces

To extend a Java class, or to implement a Java interface, use the Java.extend function. Supply the class object of the superclass or interface and a JavaScript object with the methods that you want to override or implement.

For example, here is an iterator that produces an infinite sequence of random numbers. We override two methods, next and hasNext. For each method, we provide an implementation as an anonymous JavaScript function:

Click here to view code image

var RandomIterator = Java.extend(java.util.Iterator, {
next: function() Math.random(),
hasNext: function() true
}) // RandomIterator is a class object
var iter = new RandomIterator() // Use it to construct an instance


Image Note

When calling Java.extend, you can specify any number of super-interfaces as well as a superclass. Place all class objects before the object with the implemented methods.


Another Nashorn syntax extension lets you define anonymous subclasses of interfaces or abstract classes. When new JavaClassObject is followed by a JavaScript object, an object of the extended class is returned. For example,

Click here to view code image

var iter = new java.util.Iterator {
next: function() Math.random(),
hasNext: function() true
}

If the supertype is abstract and has only one abstract method, you don’t even have to name the method. Instead, pass the function as if it were a constructor parameter:

Click here to view code image

var task = new java.lang.Runnable(function() { print('Hello') })
// task is an object of an anonymous class implementing Runnable


Image Caution

When extending a concrete class, you cannot use this constructor syntax. For example, new java.lang.Thread(function() { print('Hello') }) calls a Thread constructor, in this case the constructor Thread(Runnable). The call to new returns an object of class Thread, not of a subclass of Thread.


If you want instance variables in your subclass, add them to the JavaScript object. For example, here is an iterator that produces ten random numbers:

Click here to view code image

var iter = new java.util.Iterator {
count: 10,
next: function() { this.count--; return Math.random() },
hasNext: function() this.count > 0
}

Note that the JavaScript methods next and hasNext refer to the instance variable as this.count.

It is possible to invoke a superclass method when overriding a method, but it is quite finicky. The call Java.super(obj) yields an object on which you can invoke the superclass method of the class to which obj belongs, but you must have that object available. Here is a way to achieve that:

Click here to view code image

var arr = new (Java.extend(java.util.ArrayList)) {
add: function(x) {
print('Adding ' + x);
return Java.super(arr).add(x)
}
}

When you call arr.add('Fred'), a message is printed before the value is added to the array list. Note that the call Java.super(arr) requires the arr variable, which is being set to the value returned by new. Calling Java.super(this) does not work—that only gets the JavaScript object that defines the method, not the Java proxy. The Java.super mechanism is only useful for defining individual objects, not subclasses.


Image Note

Instead of calling Java.super(arr).add(x), you can also use the syntax arr.super$add(x).


14.3.10 Exceptions

When a Java method throws an exception, you can catch it in JavaScript in the usual way:

Click here to view code image

try {
var first = list.get(0)
...
} catch (e) {
if (e instanceof java.lang.IndexOutOfBoundsException)
print('list is empty')
}

Note that there is only one catch clause, unlike in Java where you can catch expressions by type. That, too, is in the spirit of dynamic languages where all type inquiry happens at runtime.

14.4 Shell Scripting with Nashorn

If you need to automate a repetitive task on your computer, chances are that you have put the commands in a shell script—a script that replays a set of OS-level commands. I have a directory ~/bin filled with dozens of shell scripts: to upload files to my web site, my blog, my photo storage, and to my publisher’s FTP site; to convert images to blog size; to bulk-email my students; to back up my computer at two o’clock in the morning.

For me, these are bash scripts, but in the olden days when I used Windows they were batch files. So what is wrong with that? The problem comes once you have a need for branches and loops. For some reason, most implementors of command shells are terrible at programming language design. The way variables, branches, loops, and functions are implemented in bash is simply awful, and the batch language in Windows is even worse. I have a few bash scripts that started out modest but have over time accreted so much cruft that they are unmanageable. This is a common problem.

Why not just write these scripts in Java? Java is quite verbose. If you call external commands via Runtime.exec, you need to manage standard input/output/error streams. The Nashorn designers want you to consider JavaScript as an alternative. The syntax is comparatively lightweight, and Nashorn offers some conveniences that are specifically geared towards shell programmers.

14.4.1 Executing Shell Commands

To use the scripting extensions in Nashorn, run

jjs -scripting

Now you can execute shell commands by including them in backquotes, for example

`ls -al`

The standard output and standard error streams of the last command are captured in $OUT and $ERR. The exit code of the command is in $EXIT. (By convention, an exit code of zero means success, and nonzero codes describe error conditions.)

You can also capture the standard output by assigning the result of the backquoted command to a variable:

var output = `ls -al`

If you want to supply standard input for a command, use

$EXEC(command, input)

For example, this command passes the output of ls -al to grep -v class:

Click here to view code image

$EXEC('grep -v class', `ls -al`)

It’s not quite as pretty as a pipe, but you can easily implement a pipe if you need it—see Exercise 10.

14.4.2 String Interpolation

In shell scripts, expressions inside ${...} are evaluated within doubly quoted and backquoted strings. This is called “string interpolation.” For example,

Click here to view code image

var cmd = "javac -classpath ${classpath} ${mainclass}.java"
$EXEC(cmd)

or simply

Click here to view code image

`javac -classpath ${classpath} ${mainclass}.java`

injects the contents of the variables classpath and mainclass into the command.

You can use arbitrary expressions inside the ${...}:

Click here to view code image

var message = "The current time is ${java.time.Instant.now()}"
// Sets message to a string such as The current time is 2013-10-12T21:48:58.545Z

As with the bash shell, string interpolation does not work inside singly quoted strings.

Click here to view code image

var message = 'The current time is ${java.time.Instant.now()}'
// Sets message to The current time is ${java.time.Instant.now()}

Strings are also interpolated in “here documents”—inline documents in a script. These inline documents are useful when a command reads multiple lines from standard input and the script author doesn’t want to put the input in a separate file. As an example, here is how you can feed commands to the GlassFish administration tool:

name='myapp'
dir='/opt/apps/myapp'
$EXEC("asadmin", <<END)
start-domain
start-database
deploy ${name} ${dir}
exit
END

The <<END construct means: “Insert the string that starts on the next line and is terminated by the line END.” (Instead of END, you can use any identifier that doesn’t appear inside the string.)

Note that the name and location of the application are interpolated.

String interpolation and here documents are only available in scripting mode.

14.4.3 Script Inputs

You can supply command-line arguments to a script. Since it is possible to include multiple script files on the jjs command line, you need to separate the script files and arguments with a --:

Click here to view code image

jjs script1.js script2.js -- arg1 arg2 arg3

In the script file, you receive the command-line arguments in the arguments array:

Click here to view code image

var deployCommand = "deploy ${arguments[0]} ${arguments[1]}"

You can use $ARG instead of arguments. If you use that variable with string interpolation, you need two dollar signs:

Click here to view code image

var deployCommand = "deploy ${$ARG[0]} ${$ARG[1]}"

In your script, you can obtain the shell’s environment variables through the $ENV object:

var javaHome = $ENV.JAVA_HOME

In scripting mode, you can prompt the user for input with the readLine function:

Click here to view code image

var username = readLine('Username: ')

Finally, to exit a script, use the exit function. You can supply an optional exit code.

Click here to view code image

if (username.length == 0) exit(1)

The first line of a script can be a “shebang,” the symbols #! followed by the location of the script interpreter. For example,

#!/opt/java/bin/jjs

On Linux/Unix/Mac OS X, you can make the script file executable, add the script directory to the PATH, and then simply run it as script.js.

When a script starts with a shebang, scripting mode is automatically activated.


Image Caution

When you use a shebang in a script with command-line arguments, script users need to supply dashes before the arguments:

script.js -- arg1 arg2 arg3


Exercises

1. In the JavaServer Pages technology, a web page is a mixture of HTML and Java, for example:

Click here to view code image

<ul>
<% for (int i = 10; i >= 0; i--) { %>
<li><%= i %></li>
<% } %>
<p>Liftoff!</p>

Everything outside <%...%> and <%=...%> is printed as is. Code inside is evaluated. If the starting delimiter is <%=, the result is added to the printout.

Implement a program that reads such a page, turns it into a Java method, executes it, and yields the resulting page.

2. From a Java program, call the JavaScript JSON.parse method to turn a JSON-formatted string into a JavaScript object, then turn it back into a string.

Do this (a) with eval, (b) with invokeMethod, (c) by a Java method call through the interface

Click here to view code image

public interface JSON {
Object parse(String str);
String stringify(Object obj);
}

3. Is compiling worthwhile with Nashorn? Write a JavaScript program that sorts an array the dumb way, trying all permutations until it is sorted. Compare the running time of the compiled and interpreted version. Here is a JavaScript function for computing the next permutation:

Click here to view code image

function nextPermutation(a) {
// Find the largest nonincreasing suffix starting at a[i]
var i = a.length - 1
while (i > 0 && a[i - 1] >= a[i]) i--
if (i > 0) {
// Swap a[i - 1] with the rightmost a[k] > a[i - 1]
// Note that a[i] > a[i - 1]
var k = a.length - 1
while (a[k] <= a[i - 1]) k--
swap(a, i - 1, k)
} // Otherwise, the suffix is the entire array

// Reverse the suffix
var j = a.length - 1
while (i < j) { swap(a, i, j); i++; j-- }
}

4. Find a Scheme implementation that is compatible with the Java Scripting API. Write a factorial function in Scheme and call it from Java.

5. Pick some part of the Java API that you want to explore—for example, the ZonedDateTime class. Run some experiments in jjs: construct objects, call methods, and observe the returned values. Did you find it easier than writing test programs in Java?

6. Run jjs and, using the stream library, interactively work out a solution for the following problem: Print all unique long words (> 12 characters) from a file in sorted order. First read the words, then filter the long words, and so on. How does this interactive approach compare to your usual workflow?

7. Run jjs. Call

Click here to view code image

var b = new java.math.BigInteger('1234567890987654321')

Then display b (simply by typing b and Enter). What do you get? What is the value of b.mod(java.math.BigInteger.TEN)? Why is b displayed so strangely? How can you display the actual value of b?

8. At the end of Section 14.3.9, “Extending Java Classes and Implementing Java Interfaces,” on p. 435, you saw how to extend ArrayList so that every call to add is logged. But that only worked for a single object. Write a JavaScript function that is a factory for such objects, so that you can generate any number of logging array lists.

9. Write a JavaScript function pipe that takes a sequence of shell commands and pipes the output of one to the input of the next, returning the final command’s output. For example, pipe('find .', 'grep -v class', 'sort'). Simply call $EXEC repeatedly.

10. The solution of the preceding exercise is not quite as good as a Unix pipe because the second command only starts when the first one has finished. Remedy that by using the ProcessBuilder class.

11. Write a script that prints the values of all environment variables.

12. Write a script nextYear.js that obtains the age of the user and then prints Next year, you will be ..., adding 1 to the input. The age can be specified on the command line or in the AGE environment variable. If neither are present, prompt the user.