Nashorn and Scripting - Java 8 Recipes, 2th Edition (2014)

Java 8 Recipes, 2th Edition (2014)

CHAPTER 18. Nashorn and Scripting

In Java 6, the javax.script package was included. It enabled developers to embed code written in scripting languages into Java applications. This began a new generation of applications, as developers were able to construct Java solutions containing scripts written in languages such as JavaScript and Python. The JavaScript engine that was used in Java 6 was called Rhino. It is an implementation of the JavaScript engine, developed entirely in Java. While it contains a full JavaScript implementation, it is an older engine and is no longer compliant with current JavaScript Standards.

Java 8 introduced a new JavaScript engine called Nashorn. It is based on the ECMAScript-262 Edition 5.1 language specification and supports the javax.script API introduced in Java 6. Besides bringing a modern JavaScript engine to the Java platform, Nashorn also contains a few new features that make developing JavaScript and Java solutions easier and more robust. The new command-line tool called jjs provides scripting abilities above and beyond those that were available with jrunscript. Nashorn also has full access to the JavaFX 8 API, allowing developers to construct JavaFX applications completely in JavaScript.

This chapter touches on using the Nashorn engine to construct solutions that integrate the worlds of Java and JavaScript. It does not cover all of the features available with Nashorn, but you with provides enough to get up and running with this powerful new Java 8 feature.

18-1. Loading and Executing JavaScript from Java

Problem

You want to load and execute JavaScript code from within your Java application.

Solution

Execute the JavaScript using the Nashorn engine, the next-generation JavaScript engine that is part of Java 8 and is used to execute JavaScript code. The Nashorn engine can be called upon to process in-line JavaScript, or an external JavaScript file directly within Java code. Execute an external JavaScript file or in-line JavaScript code using the Java ScriptEngineManager. Once you’ve obtained a ScriptEngineManager(), you get an instance of the Nashorn engine to use for JavaScript code execution.

In the following example, a Nashorn ScriptEngine is used to invoke a JavaScript file that resides on the local file system.

public static void loadExternalJs(){
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine nashorn = sem.getEngineByName("nashorn");
try {
nashorn.eval("load('src/org/java8recipes/chapter18/js/helloNashorn.js')");
} catch (ScriptException ex) {
Logger.getLogger(NashornInvoker.class.getName()).log(Level.SEVERE, null, ex);
}
}

The code that resides in the helloNashorn.js file is as follows:

print("Hello Nashorn!");

Next, let’s take a look at some in-line JavaScript. In the following example, a Nashorn ScriptEngine is obtained, and then a JavaScript function is created for obtaining the gallons of water for an in-ground pool. The function is then executed to return a result.

public static void loadInlineJs(){
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine nashorn = sem.getEngineByName("nashorn");
try {
nashorn.eval("function gallons(width, length, avgDepth){var volume =
avgDepth * width * length;" +
"return volume * 7.48; }");
nashorn.eval("print('Gallons of water in pool: '+ gallons(16,32,5))");
} catch (ScriptException ex) {
Logger.getLogger(NashornInvoker.class.getName()).log(Level.SEVERE, null, ex);
}
}

}

Results:

run:
Hello Nashorn!
Gallons of water in pool: 19148.800000000003

How It Works

There are many different ways to use the Nashorn engine to execute JavaScript within a Java application. In this recipe, the example covers two such techniques for doing so, and each of them requires the use of the ScriptEngineManager, which has been part of the JDK since Java 6. To obtain a Nashorn engine from the ScriptEngineManager, first create a new instance of the ScriptEngineManager. Once obtained, you can obtain a particular engine by passing the string value that represents the desired engine to the getEngineByName() method. In this case, you pass the name nashorn to obtain the Nashorn engine for working with JavaScript. After obtaining the Nashorn engine, you are ready to invoke a JavaScript file or evaluate inline JavaScript code by calling on the engine’s eval() method.

The first code example in this recipe demonstrates how to pass a JavaScript file to the engine for invocation. The helloNashorn.js in this case contains a single line of JavaScript that prints a message without returning any results. Perhaps the most difficult part of executing a .js file is that you must ensure that the file is contained in the class path, or that you are passing the full path to the file to the eval() method.

The second code example demonstrates how to write and evaluate inline JavaScript. First, a function identified as gallons is defined and it accepts three parameters and returns the number of gallons based on the width, length, and average depth of a pool. In a subsequent eval() call, the function is invoked, passing parameters and returning a result. The important point to note in this example is that although the JavaScript spanned multiple eval() calls, the scope is maintained so that each eval() call within the engine can see objects created within previous calls.

Since Java 6, it has been possible to work with scripting languages from within Java code. The Nashorn engine is obtained in the same manner as others, by passing a string to indicate the engine by name. The difference between this JavaScript engine and the previous rendition Rhino is that the new JavaScript engine is much faster and provides better compliance with the EMCA-normalized JavaScript specification.

18-2. Executing JavaScript via the Command Line

Problem

You want to execute JavaScript via the command line for prototyping or execution purposes.

Solution 1

Invoke the jjs tool, which comes as part of Java 8. To execute a JavaScript file, invoke the jjs tool from the command line, and then pass the fully qualified name (path included if not in CLASSPATH) of a JavaScript file to execute. For example, to execute helloNashorn.js, use the following command:

jjs /src/org/java8recipes/chapter18/js/helloNashorn.js
Hello Nashorn!

To pass arguments to a JavaScript file for processing, call the script in the same manner, but include trailing dashes --, followed by the argument(s) you want to pass. For example, the following code resides within a file named helloParameter.js:

#! /usr/bin/env
var parameter = $ARG[0];
print(parameter ? "Hello ${parameter}!": "Hello Nashorn!");

Use the following command to invoke this JavaScript file, passing the parameter Oracle:

jjs /src/org/java8recipes/chapter18/js/helloParameter.js – Oracle

Here is the result:

Hello Oracle!

The jjs tool can also be utilized as an interactive interpreter by simply executing jjs without any options. The command interpreter allows you to work in a fully interactive JavaScript environment. In the following lines of code, the jjs tool is invoked to open a command shell, and a function is declared and executed. Finally, the command shell is exited.

jjs
jjs> function gallon(width, length, avgDepth){return (avgDepth * width * length) * 7.48;}
function gallon(width, length, avgDepth){return (avgDepth * width * length) * 7.48;}
jjs> gallon(16,32,5)
19148.800000000003
jjs> exit()

Solution 2

Make use of the JSR 223 jrunscript tool to execute JavaScript. To execute a JavaScript file, invoke the jrunscript tool from the command line and pass the fully qualified name (path included if not in CLASSPATH) of a JavaScript file to execute. For example, to executehelloNashorn.js, use the following command:

jrunscript /src/org/java8recipes/chapter18/js/helloNashorn.js
Hello Nashorn!

Perhaps you want to pass JavaScript code inline, rather than executing a JavaScript file. In this case, you would invoke jrunscript with the –e flag and pass the script in-line.

jrunscript -e "print('Hello Nashorn')"
Hello Nashorn

Image Note String interpolation is not available if you’re using the jrunscript utility. Therefore, you must use concatenation to achieve a similar effect. To learn more about String interpolation, refer to Recipe 18-3.

Similarly to jjs, the jrunscript tool also accepts arguments to pass to a JavaScript file for processing. To pass arguments using the jrunscript tool, simply append them to the command when invoking the script, with each argument separated by spaces. For instance, to call the file helloParameter.js and pass an argument, execute the following command:

jrunscript src/org/java8recipes/chapter18/js/helloParameter.js Oracle

Also similar to jjs, the jrunscript tool can execute an interactive interpreter, allowing you to develop and prototype on-the-fly.

jrunscript
nashorn> function gallon(width, length, avgDepth){return (avgDepth * width * length) * 7.48;}
function gallon(width, length, avgDepth){return (avgDepth * width * length) * 7.48;}
nashorn> gallon(16,32,5)
19148.800000000003

How It Works

Since the release of Java SE 6, it has been possible to work with scripting languages from Java. In this recipe, two solutions were demonstrated for executing JavaScript via the command line or terminal. In Solution 1, you looked at the jjs command-line tool, which is new in Java 8. This tool can be used to invoke one or more JavaScript files, or to start an interactive Nashorn interpreter. In the example, you took a look at how to invoke a JavaScript file with and without passing arguments. You also took a look at how to invoke jjs as an interactive interpreter. The tool contains several useful options. To see an entire list refer to the documentation online at http://docs.oracle.com/javase/8/docs/technotes/tools/windows/jjs.html. The jjs tool is the desired tool for use with Nashorn because it contains many more options than the jrunscript tool, which was demonstrated in Solution 2.

The jrunscript tool was introduced in Java 6 and it allows you to execute scripts from the command line or invoke an interactive interpreter, similar to jjs. The difference is that jrunscript also allows you to use other scripting languages by passing the –l flag, along with the scripting engine name.

jrunscript –l js myTest.js

The jrunscript tool also contains options, but it is limited in comparison to those available with jjs. To see all of the options available for jrunscript, refer to the online documentation athttp://docs.oracle.com/javase/8/docs/technotes/tools/windows/jrunscript.html.

18-3. Embedding Expressions in Strings

Problem

You want to refer to expressions or values within a string when invoking JavaScript via the jjs utility.

Solution

When using Nashorn as a shell scripting language via the jjs tool, it is possible to embed expressions or values in strings by enclosing them within dollar signs $ and curly brackets {} in a double quoted string of text. The following JavaScript resides in a file named recipe18_3.js, and it can be executed by the jjs tool as a shell script. The string interpolation works in this example because the script has been made executable by adding the shebang as the first line. Refer to Recipe 18-10 for more information on the shebang.

#! /usr/bin/env
function gallons(width, length, avgDepth){var volume = avgDepth * width * length;
return volume * 7.48; }
print("Gallons of water in pool: ${gallons(16,32,5)}");

Execute the JavaScript file via jjs as follows:

jjs src/org/java8recipes/chapter18/js/recipe18_3.js
Gallons of water in pool: 19148.800000000003

Image Note This example JavaScript file cannot be run from a ScriptEngineManager because it contains a shebang (it is an executable script).

How It Works

When you’re using Nashorn’s shell scripting features, you can embed expressions or values in double-quoted strings of text by enclosing them in dollar signs and curly braces ${...}. This concept is known a string interpolation in the Unix world, and Nashorn borrows the concept to make it easy to develop shell scripts for evaluating and displaying information. String interpolation makes it possible alter the contents of a string, replacing variables and expressions with values. Using this feature, it is easy to embed the contents of a variable in-line within a string without performing manual concatenation.

In the example for this recipe, a script that is stored within a .js file contains an embedded expression, and it calls on a JavaScript function to return the calculated number of liquid gallons. This is likely the most useful technique for real-world scenarios, but it is also possible to make use of embedded expressions when using the jjs tool as an interactive interpreter.

jjs -scripting
jjs> "The current date is ${Date()}"
The current date is Wed Apr 30 2014 23:44:41 GMT-0500 (CDT)

Image Note If you’re not using the scripting features of jjs, string interpolation will not be available. Also, double quotes must be placed around the string of text, as strings in single quotes are not interpolated. In the example, the shebang (#! usr/bin/env) is used to make the script executable, thereby invoking the scripting features of jjs.

18-4. Passing Java Parameters

Problem

You want to pass Java parameters to JavaScript for use.

Solution

Utilize a javax.script.SimpleBindings instance to provide a string-based name for any Java field, and then pass the SimpleBindings instance to the JavaScript engine invocation. In the following example, a Java string parameter is passed to the Nashorn engine, and then it’s printed via JavaScript.

String myJavaString = "This is a Java parameter!";
SimpleBindings simpleBindings = new SimpleBindings();
simpleBindings.put("myString", myJavaString);
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine nashorn = sem.getEngineByName("nashorn");
nashorn.eval("print (myString)", simpleBindings);

Here is the result:

This is a Java parameter!

More than one Java type value can be passed in a SimpleBindings instance. In the following example, three float values are passed in a single SimpleBindings instance, and then they’re passed to a JavaScript function.

float width = 16;
float length = 32;
float depth = 5;
SimpleBindings simpleBindings2 = new SimpleBindings();
simpleBindings2.put("globalWidth", width);
simpleBindings2.put("globalLength", length);
simpleBindings2.put("globalDepth", depth);
nashorn.eval("function gallons(width, length, avgDepth){var volume = avgDepth * width * length; "+
" return volume * 7.48; } " +
"print(gallons(globalWidth, globalLength, globalDepth));", simpleBindings2);

Result:

19148.800000000003

How It Works

To pass Java field values to JavaScript, use the javax.script.SimpleBindings construct, which is basically a HashMap that can be used for binding and passing values to the ScriptEngineManager. When values are passed to the Nashorn engine in this manner, they can be accessed as global variables within the JavaScript engine.

18-5. Passing Return Values from JavaScript to Java

Problem

You want to invoke a JavaScript function and return the result to the Java class that invoked it.

Solution

Create a ScriptEngine for use with Nashorn and then pass the JavaScript function to it for evaluation. Next, create an Invocable from the engine and then call its invokeFunction() method, passing the string-based name of the JavaScript function, along with an array of the arguments to be used. In the following example, a JavaScript function named gallons is passed to the ScriptEngine for evaluation, and it is later invoked using this technique. It then returns a double value.

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

// JavaScript code in a String
String gallonsFunction = "function gallons(width, length, avgDepth){var volume = avgDepth * width * length; "
+ " return volume * 7.48; } ";
try {
// evaluate script
engine.eval(gallonsFunction);
double width = 16.0;
double length = 32.0;
double depth = 5.0;
Invocable inv = (Invocable) engine;
double returnValue = (double) inv.invokeFunction("gallons",
new Double[]{width,length,depth});
System.out.println("The returned value:" + returnValue);

} catch (ScriptException | NoSuchMethodException ex) {
Logger.getLogger(Recipe18_5.class.getName()).log(Level.SEVERE, null, ex);
}

Here’s the result:

run:
The returned value:19148.800000000003

In the following example, a JavaScript file is invoked and returns a string value. The name of the JavaScript file is recipe18_5.js and its contents are as follows:

function returnName( name){
return "Hello " + name;
}

Next, use the ScriptEngine to create an Invocable and call on the JavaScript function within the external JavaScript file.

engine.eval("load('/path-to/src/org/java8recipes/chapter18/recipe18_05/js/recipe18_5.js')");
Invocable inv2 = (Invocable) engine;
String returnValue2 = (String) inv2.invokeFunction("returnName", new String[]{"Nashorn"});
System.out.println("The returned value:" + returnValue2);

How It Works

One of the most useful features of embedded scripting is the ability to integrate the code invoked via a script engine along with a Java application. In order to effectively integrate script engine code and Java code, the two must be able to pass values to each other. This recipe covers the concept of returning values from JavaScript back to Java. To do so, set up a ScriptEngine and then coerce it into a javax.script.Invocable object. The Invocable object can then be used to execute script functions and methods, returning values from those invocations.

An Invocable object enables you to execute a named JavaScript function or method and return values to the caller. Invocable can also return an interface that will provide a way to invoke the member functions of the scripting object. To provide this functionality, the Invocableobject contains several methods (see Table 18-1).

Table 18-1. Invocable methods

Method

Description

getInterface(Class<T>)

Returns an implementation of an interface using the functions compiled by the interpreter.

getInterface(Object, Class<T>)

Returns an implementation of an interface using member functions of a scripting object that has been compiled in the interpreter.

invokeFunction(String, Object)

Calls on top-level procedures and functions. Returns an object.

invokeFunction(Object, String, Object)

Calls a method on a script object that was compiled during a previous execution.

Before an Invocable can be generated, the JavaScript file or function must be evaluated by the ScriptEngine. The example demonstrates calling on the eval() method to evaluate an in-line JavaScript function (a string named gallonsFunction), and it shows how to evaluate an external JavaScript file. Once the eval() method has been called, the ScriptEngine can be coerced into an Invocable object, as follows:

Invocable inv = (Invocable) engine;

Invocable can then be called upon to execute functions or methods within the evaluated script code. Table 18-1 lists the methods of Invocable that can be used.

In this recipe’s examples, the invokeFunction method is used to call on the functions contained in the script. The first argument to invokeFunction is the string-based name of the function being called upon, and the second argument is a list of Objects that are being passed as arguments. The Invocable returns an Object from the JavaScript function call, which can be coerced into the appropriate Java type.

Sharing values between Java and ScriptEngine instances is very useful. In a real-life scenario, it may be very useful to call on an external JavaScript file, and have the ability to pass values back and forth between the Java code and the script. The underlying JavaScript file can be modified, if needed, without recompiling the application. This situation can be very useful when your application contains some business logic that needs to change from time to time. Imagine that you have a rules processor that can be used to evaluate strings, and the rules are constantly evolving. In this case, the rule engine can be written as an external JavaScript file, enabling dynamic changes to that file.

18-6. Using Java Classes and Libraries

Problem

You want to call upon Java classes and libraries within your Nashorn solution.

Solution

Create JavaScript objects based on Java classes or libraries using the Java.type() function. Pass the fully qualified string-based name of the Java class that you want to utilize to this function and assign it to a variable. The following code represents a Java object named Employee, which will be utilized via a JavaScript file in this application.

package org.java8recipes.chapter18.recipe18_06;

import java.util.Date;
public class Employee {
private int age;
private String first;
private String last;
private String position;
private Date hireDate;

public Employee(){

}

public Employee(String first,
String last,
Date hireDate){
this.first = first;
this.last = last;
this.hireDate = hireDate;
}

/**
* @return the first
*/
public String getFirst() {
return first;
}

/**
* @param first the first to set
*/
public void setFirst(String first) {
this.first = first;
}

/**
* @return the last
*/
public String getLast() {
return last;
}

/**
* @param last the last to set
*/
public void setLast(String last) {
this.last = last;
}

. . .
}

Next, let’s take a look the JavaScript file that makes use of the Employee class. This JavaScript code creates a couple of Employee instances and then prints them back out. It also uses the java.util.Date class to demonstrate using standard Java classes.

var oldDate = Java.type("java.util.Date");
var array = Java.type("java.util.ArrayList");
var emp = Java.type("org.java8recipes.chapter18.recipe18_06.Employee");

var empArray = new array();
var emp1 = new emp("Josh", "Juneau", new oldDate());
var emp2 = new emp("Joe", "Blow", new oldDate());
empArray.add(emp1);
empArray.add(emp2);
empArray.forEach(function(value, index, ar){
print("Employee: " + value);
print("Hire Date: " + value.hireDate);
});

Lastly, you execute the JavaScript file using a ScriptEngineManager:

ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine nashorn = sem.getEngineByName("nashorn");
try {
nashorn.eval("load('/path-to/employeeFactory.js');");
} catch (ScriptException ex) {
Logger.getLogger(NashornInvoker.class.getName()).log(Level.SEVERE, null, ex);
}

Here are the results:

Employee: Josh Juneau
Hire Date: Thu April 24 23:03:53 CDT 2014
Employee: Joe Blow
Hire Date: Fri April 25 12:00:00 CDT 2014

How It Works

It is very natural to use Java classes and libraries from within a Nashorn solution. The example in this recipe demonstrates how to use a Java class that has been generated specifically for use with a custom application, as well as how to use Java classes and libraries that are part of Java SE. In order to make such classes available to JavaScript, you must call the Java.type function from within the JavaScript and pass the string-based fully qualified name of the Java class to be used. The Java.type function returns a JavaScript reference to the Java type. In the following excerpt from the example, the java.util.Date, java.util.ArrayList, and Employee classes are made available to JavaScript using this technique.

var oldDate = Java.type("java.util.Date");
var array = Java.type("java.util.ArrayList");
var emp = Java.type("org.java8recipes.chapter18.recipe18_06.Employee");

Once the types have been made available to JavaScript, they can be invoked in a similar manner to their Java counterparts. For instance, new oldDate() is used to instantiate a new instance of java.util.Date in the example. An important difference is that you don’t use getters and setters to call upon Java properties. Rather, you omit the “get” or “set” portion of the method and begin with a lowercase letter for the field name. This makes property access from within JavaScript quite easy and much more productive and readable. An example of such access can be seen from within the forEach loop in the script. To access the employee hireDate property, simply call employee.hireDate rather than employee.getHireDate().

The ability to access Java seamlessly from within JavaScript makes it possible to create seamless Java and JavaScript integrations.

18-7. Accessing Java Arrays and Collections in Nashorn

Problem

You need to gain access to a Java array or collection from within your Nashorn solution.

solution

Use the Java.type function to coerce Java arrays to JavaScript. Once coerced, you instantiate the arrays by calling new and then accessing the members by specifying their index by number. In the following example, a Java int array type is created within JavaScript, and then it is instantiated and used for storage.

jjs> var intArray = Java.type("int[]");
jjs> var intArr = new intArray(5);
jjs> intArr[0] = 0;
0
jjs> intArr[1] = 1;
1
jjs> intArr[0]
0
jjs> intArr.length
5

Working with collections is quite similar. To access a Java Collection type, you call upon the Java.type function, passing the string-based name of the type you want to create. Once the type reference has been obtained, it can be instantiated and accessed from JavaScript.

jjs> var ArrayList = Java.type("java.util.ArrayList")
jjs> var array = new ArrayList();
jjs> array.add('hi');
true
jjs> array.add('bye');
true
jjs> array
[hi, bye]
jjs> var map = Java.type("java.util.HashMap")
jjs> var jsMap = new map();
jjs> jsMap.put(0, "first");
null
jjs> jsMap.put(1, "second");
null
jjs> jsMap.get(1);
second

How It Works

To make use of Java arrays and collections from within JavaScript, you invoke the Java.type() function and pass the name of the Java type that you want to access, assigning it to a JavaScript variable. The JavaScript variable can then be instantiated and utilized in the same manner as the Java type would be used from within Java code. The examples in this recipe demonstrate how to access Java arrays, ArrayLists, and HashMaps from within JavaScript.

When working with a Java array type from JavaScript, the type of array must be passed to the Java.type() function, including an empty set of brackets. Once the type has been obtained and assigned to a JavaScript variable, it can be instantiated by including the static size of the array within brackets, just as an array would be instantiated in the Java language. Similarly, the array can be accessed by specifying indices to assign and retrieve values from the array. To go backward and pass a JavaScript array to Java, use the Java.to() function, passing the JavaScript array to its Java-type counterpart. In the following code, a JavaScript string array is coerced into a Java type.

jjs> var strArr = ["one","two","three"]
jjs> var javaStrArr = Java.type("java.lang.String[]");
jjs> var javaArray = Java.to(strArr, javaStrArr);
jjs> javaArray[1];
two
jjs> javaArray.class
class [Ljava.lang.String;

Collections are very similar to arrays, in that the Java.type() function must be used to obtain the Java type and assign it to a JavaScript variable. The variable is then instantiated and the Collection type is then accessed in the same manner as it would be in the Java language.

18-8. Implementing Java Interfaces

Problem

You want to make use of a Java interface from your Nashorn solution.

Solution

Create a new instance of the interface, passing a JavaScript object consisting of properties. The JavaScript object properties will implement the methods defined in the interface. In the following example, an interface used for declaring employee position types is implemented within a JavaScript file. The example demonstrates custom method implementation, as well as use of a default method. The following code is the interface, PositionType, which will be implemented in JavaScript.

import java.math.BigDecimal;

public interface PositionType {

public double hourlyWage(BigDecimal hours, BigDecimal wage);

/**
* Hourly salary calculation
* @param wage
* @return
*/
public default BigDecimal yearlySalary(BigDecimal wage){
return (wage.multiply(new BigDecimal(40))).multiply(new BigDecimal(52));
}
}

Next, let’s take a look at the code within the JavaScript file that implements the PositionType interface.

var somePosition = new org.java8recipes.chapter18.recipe18_08.PositionType({
hourlyWage: function(hours, wage){
return hours * wage;
}
});

print(somePosition instanceof Java.type("org.java8recipes.chapter18.recipe18_08.PositionType"));
var bigDecimal = Java.type("java.math.BigDecimal");

print(somePosition.hourlyWage(new bigDecimal(40), new bigDecimal(12.75)));

How It Works

Using a Java interface in JavaScript can be beneficial for creating objects that adhere to the implementation criteria. However, using interfaces in JavaScript is a bit different than using them in a Java solution. For example, interfaces cannot be instantiated in Java. This is not the case when using them in JavaScript; you must actually instantiate an object of the interface type in order to use it.

The example demonstrates the implementation of an interface, PositionType, which is used for defining a number of methods within an employee position. The methods are used for calculating an employee’s hourly and yearly wage. To make use of the PositionType interface from JavaScript, the new keyword is used to instantiate an instance of that interface, assigning it to a JavaScript variable. When instantiating the interface, a JavaScript object is passed to the constructor. The object contains implementations for each of the non-default methods within the interface by identifying the name of the method, followed by the implementation. In the example, there is only one method implemented on instantiation—it is identified as hourlyWage(). If there had been more than one method implemented, the implementations would be separated by commas.

Although using Java interfaces is a bit different in JavaScript, they certainly provide a benefit. In reality, they are performing the same task within JavaScript as they are within Java. In Java, in order to implement an interface, you must create an object that implements it. You do the same thing within JavaScript, except that in order to create the implementing object, you must instantiate an instance of the interface.

18-9. Extending Java Classes

Problem

You want to extend a concrete Java class in your Nashorn JavaScript solution.

Solution

First obtain a reference to the Java class that is to be extended by calling the Java.type() function within your JavaScript file. Then create the subclass by calling on the Java.extend() function and passing the reference to the class that will be extended, along with a JavaScript object containing the implementations that will be altered.

The following code is that of the Employee class, which will later be extended from within a JavaScript file.

package org.java8recipes.chapter18.recipe18_09;

import java.math.BigDecimal;
import java.util.Date;

public class Employee {
private int age;
private String first;
private String last;
private String position;
private Date hireDate;

. . .

public BigDecimal grossPay(BigDecimal hours, BigDecimal rate){
return hours.multiply(rate);
}
}

Here’s the JavaScript code used to extend the class and use it:

var Employee = Java.type("org.java8recipes.chapter18.recipe18_09.Employee");
var bigDecimal = Java.type("java.math.BigDecimal");
var Developer = Java.extend(Employee, {
grossPay: function(hours, rate){
var bonus = 500;
return hours.multiply(rate).add(new bigDecimal(bonus));
}
});

var javaDev = new Developer();
javaDev.first = "Joe";
javaDev.last = "Dynamic";
print(javaDev + "'s gross pay for the week is: " + javaDev.grossPay(new bigDecimal(60),
new bigDecimal(80)));

Here’s the result:

Joe Dynamic's gross pay for the week is: 5300

How It Works

To extend a standard Java class from within JavaScript, you call on the Java.extend() function, passing the Java class that you’d like to extend, along with a JavaScript object containing any fields or functions that will be altered in the subclass. For the example in this recipe, a Java class entitled Employee is extended. However, the same technique can be used to extend any other Java interface, such as Runnable, Iterator, and so on.

In this example, to obtain the Employee class in JavaScript, the Java.type() function is called upon, passing the fully qualified class name. The object that is received from the call is stored in a JavaScript variable named Employee. Next, the class is extended by calling on theJava.extend() function and passing the Employee class, along with a JavaScript object. In the example, the JavaScript object that is sent to the Java.extend() function includes a different implementation of the Employee class grossPay() method. The object that is returned from the Java.extend() function is then instantiated and accessed via JavaScript.

Extending Java classes within JavaScript can be a very useful feature when you’re working with a Nashorn solution. The ability to share objects from Java makes it possible to access exiting Java solutions and build on them.

18-10. Creating Executable Scripts in Unix

Problem

You want to enable your JavaScript file to become executable.

Solution

Make a JavaScript file executable by adding a shebang (#!) as the first line of the script, followed by the path to the location of the jjs executable. In the following example, a very simple JavaScript file is made executable by the inclusion of a shebang, which points to the symbolic link of the jjs tool.

#! /usr/bin/env jjs
print('I am an executable');

To execute the script, it must be given the proper permissions. Apply the chmod a+x permissions (in Unix) to make the script executable.

chmod a+x src/org/java8recipes/chapter18/recipe18_10/jsExecutable.js

The script can now be invoked as an executable, as shown in the following command:

Juneau$ ./src/org/java8recipes/chapter18/recipe18_10/jsExecutable.js
I am an executable

How It Works

To make a script executable, you simply add a shebang to the first line. The shebang is used in Unix-based operating systems to tell the program loader that the script’s first line should be treated as an interpreter directive, and that the script should be passed to that interpreter for execution. In the solution to this recipe, the first line of the script tells the program loader that the script’s contents should be executed using the jjs tool:

#! /usr/bin/env jjs

By invoking the jjs tool in this manner, the scripting options are automatically enabled, allowing you to utilize scripting features within the script. The following list includes extra scripting features that can be used when executing via jjs with scripting options are enabled:

· String interpolation: (See Recipe 18-3)

var threeyr = 365 * 3;
print("The number of days in three years is ${threeyr}");

· Shell invocations: The ability to invoke external programs

· Special environment variables are available for use ($ARG and $ENV)

The ability to develop executable scripts in JavaScript can be very powerful. Not only is the world of JavaScript available at your fingertips, but the entire Java world is available, since you can import Java classes and libraries into your scripts.

18-11. Implementing JavaFX with Nashorn

Problem

You enjoy working in JavaScript code and want to implement a Java GUI using JavaScript.

Solution 1

Develop a JavaFX application using JavaScript and store it in a JavaScript file. Invoke the file using the jjs tool, along with the –fx option. The following code is a JavaFX application that is written in JavaScript. The JavaFX application can be used for collecting car data.

var ArrayList = Java.type("java.util.ArrayList");
var Scene = javafx.scene.Scene;
var Button = javafx.scene.control.Button;
var TextField = javafx.scene.control.TextField;
var GridPane = javafx.scene.layout.GridPane;
var Label = javafx.scene.control.Label;
var TextArea = javafx.scene.control.TextArea;

var carList = new ArrayList();
var carCount = "There are currently no cars";
var car = {
make:"",
model:"",
year:"",
description:""
};
print(carCount);
function start(primaryStage) {

primaryStage.title="Car Form JS Demo";

var grid = new GridPane();
grid.hgap = 10;
grid.vgap = 10;

var makeLabel = new Label("Make:");
grid.add(makeLabel, 0, 1);

var makeText = new TextField();
grid.add(makeText, 1, 1);

var modelLabel = new Label("Model:");
grid.add(modelLabel, 0, 2);

var modelText = new TextField();
grid.add(modelText, 1, 2);

var yearLabel = new Label("Year:");
grid.add(yearLabel, 0, 3);

var yearText = new TextField();
grid.add(yearText, 1, 3);

var descriptionLabel = new Label("Description:");
grid.add(descriptionLabel, 0, 4);

var descriptionText = new TextArea();
grid.add(descriptionText, 1, 4);

var button = new Button("Add Car");
button.onAction = function(){
print("Adding Car:" + makeText.text);
car.make=makeText.text;
car.model=modelText.text;
car.year=yearText.text;
car.description=descriptionText.text;
carList.add(car);
carCount = "The number of cars is: "+ carList.size();
print(carCount);
};
grid.add(button, 0,5);

primaryStage.scene = new Scene(grid, 800, 500);
primaryStage.show();
}

The resulting application looks like that shown in Figure 18-1.

9781430268277_Fig18-01.jpg

Figure 18-1. JavaFX application written in JavaScript

Solution 2

Write a JavaFX application using Java and embed the JavaScript application implementation using a ScriptEngine. The following Java class is called CarCollector.java and it implements javafx.application.Application. The Java class implements the start()method, which contains a ScriptEngine to embed the JavaScript code that implements the application.

package org.java8recipes.chapter18.recipe18_11;

import java.io.FileReader;
import javafx.application.Application;
import javafx.stage.Stage;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class CarCollector extends Application {

private final String SCRIPT = getClass().getResource("carCollector.js").getPath();

public static void main(String args[]) {
launch(args);
}

@Override
public void start(Stage stage) {
try {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.put("primaryStage", stage);
engine.eval(new FileReader(SCRIPT));
} catch (Exception e) {
e.printStackTrace();
}
}
}

Next, let’s take a look at the JavaScript file named carCollector.js, which implements the application. Note that the code does not contain a start() function, because the application start() method is already implemented in the Java code. The JavaScript file merely contains the implementation.

var ArrayList = Java.type("java.util.ArrayList");
var Scene = javafx.scene.Scene;
var Button = javafx.scene.control.Button;
var TextField = javafx.scene.control.TextField;
var GridPane = javafx.scene.layout.GridPane;
var Label = javafx.scene.control.Label;
var TextArea = javafx.scene.control.TextArea;

var carList = new ArrayList();
var carCount = "There are currently no cars";
var car = {
make: "",
model: "",
year: "",
description: ""
};
print(carCount);

primaryStage.title = "Car Form JS Demo";

var grid = new GridPane();
grid.hgap = 10;
grid.vgap = 10;

var makeLabel = new Label("Make:");
grid.add(makeLabel, 0, 1);

var makeText = new TextField();
grid.add(makeText, 1, 1);

var modelLabel = new Label("Model:");
grid.add(modelLabel, 0, 2);

var modelText = new TextField();
grid.add(modelText, 1, 2);

var yearLabel = new Label("Year:");
grid.add(yearLabel, 0, 3);

var yearText = new TextField();
grid.add(yearText, 1, 3);

var descriptionLabel = new Label("Description:");
grid.add(descriptionLabel, 0, 4);

var descriptionText = new TextArea();
grid.add(descriptionText, 1, 4);

var button = new Button("Add Car");
button.onAction = function() {
print("Adding Car:" + makeText.text);
car.make = makeText.text;
car.model = modelText.text;
car.year = yearText.text;
car.description = descriptionText.text;
carList.add(car);
carCount = "The number of cars is: " + carList.size();
print(carCount);
};
grid.add(button, 0, 5);

primaryStage.scene = new Scene(grid, 800, 500);
primaryStage.show();

How It Works

The Nashorn engine has full access to the JavaFX API. This means that it is possible to construct JavaFX applications that are written either entirely or partially in JavaScript. The two solutions to this recipe demonstrate each of these techniques. The first solution demonstrates how to develop a JavaFX application entirely of JavaScript. When you’re using the technique demonstrated in Solution 1, the JavaScript implementation can be executed by using the jjs tool and specifying the –fx option, as follows:

jjs –fx recipe18_11.js

Solution 2 demonstrates how to construct a JavaFX application from Java code, embedding the implementation code written in JavaScript. To use this technique, construct a standard JavaFX application class by extending the javafx.application.Application class and overriding the start() method. Within the start() method, create a Nashorn ScriptEngine object and use it to embed a JavaScript file that contains the application implementation. Prior to calling the engine’s eval() method and passing the JavaScript file, pass the JavaFX stage to the engine using the engine’s put() method.

engine.put("primaryStage", stage);

Digging into the JavaScript code a bit, any of the JavaFX API classes can be imported by using the Java.type() function and passing the fully-qualified class name. Assign the imported classes to JavaScript variables, which will later be used in the application construction. When written entirely in JavaScript, a start() function must be created to contain the JavaFX application stage construction. On the other hand, when you’re using Java code to launch the application, there is no need to create a start() function. In the example, a GridPane layout is used to construct a form for capturing car data. The form fields are each constructed with a Label and a TextField or TextArea. The car data is stored in a JavaScript object when a button is clicked.

There are a few things to note about the JavaScript code in both implementations. The syntax is a bit different than Java code because getters and setters are not used. Also, the implementation for the button action handler is a simple JavaScript function.

Constructing JavaFX applications using JavaScript can be a fun alternative to using Java code. The syntax has the feel of using the prior JavaFX Script language, and it is a bit less verbose than Java. It is also nice to be able to change the application without having to recompile if you’re using the full JavaScript implementation.

Summary

Nashorn enables developers to make use of modern JavaScript capabilities from within the Java ecosystem. The Nashorn engine has full access to all of the Java APIs, including JavaFX. The new jjs tool provides scripting capabilities, allowing developers to create executable scripts written entirely in JavaScript.