Class | Description |
---|---|
GraalSDK |
Integration of NetBeans Scripting
API and GraalVM; see the
tutorial for more details. |
Scripting
helper methods. This
environment lets Java interoperate with standard as well as
GraalVM based
guest languages via foreign objects and foreign functions.
For example Java code
can directly access guest language methods, objects, classes,
and some complex data structures
with Java-typed accessors. In the reverse direction, guest language code can access Java objects,
classes, and constructors.
This tutorial helps you get started, starting with setup instructions, followed by descriptions of different interoperation scenarios with (working) code examples.
NetBeans modules are uploaded to Maven central. You can use them from your pom.xml file as:
<dependency> <groupId>org.netbeans.api</groupId> <artifactId>scripting</artifactId> <version>11</version> <!-- or any later version --> </dependency>
ScriptEngineManager
via
Scripting
helper method.
You can then use the engine to
evaluate
guest language source code.
The following example evaluates a Python source, let it print a message, returns it, and then "casts" the result to a Java string.
ScriptEngine
python =Scripting
.createManager().getEngineByMimeType("text/x-python"); assert python != null : "Install Graal Python via `gu install python`";String
x = (String
) python.eval("\n" + "x = 'Hello World!'\n" + "print(x)\n" + "x\n" ); assert x.equals("Hello World!") : x;
ScriptEngine
found = null; finalScriptEngineManager
manager =Scripting
.createManager(); for (ScriptEngineFactory
factory : manager.getEngineFactories()) { finalString
name = factory.getEngineName();System
.err.println("Found " + name); if (factory.getMimeTypes().contains("text/javascript")) { if (name.equals("GraalVM:js")) { found = factory.getScriptEngine(); } } }
When the above code snippet is executed on GraalVM it may print:
Found Oracle Nashorn Found Graal.js Found GraalVM:js Found GraalVM:llvm Found GraalVM:pythone.g. a mixture of standard script engines in the JDK with additional ones provided as GraalVM languages located via dedicated implementation of
EngineProvider
interface
bin/gu
Graal Updater tool:
$ /graalvm/bin/gu available Downloading: Component catalog ComponentId Version Component name ---------------------------------------------------------------- python 1.0.0-rc9 Graal.Python R 1.0.0-rc9 FastR ruby 1.0.0-rc9 TruffleRuby $ /graalvm/bin/gu install python Downloading: Component catalog Processing component archive: Component pythonAfter invoking this command and downloading the bits, the JVM will be ready to execute Python scripts.
Scripting.createManager()
method
is your gateway to polyglot world! Just create a manager and it can serve
as a hub where various language engines connect together. Following example
shows JavaScript and Python interacting with each other:
// creates a single shared manager for two languages finalScriptEngineManager
manager =Scripting
.createManager(); // creates two engines connected to each otherScriptEngine
js = manager.getEngineByMimeType("text/javascript"); assert js != null : "Run on GraalVM!";ScriptEngine
python = manager.getEngineByMimeType("text/x-python"); assert python != null : "Install Graal Python via `gu install python`"; // JavaScript function that takes another function as argumentObject
sayHelloRaw = js.eval("(function(subject) {\n" + " return 'Hello ' + subject() + '!';\n" + "})\n"); @SuppressWarnings
("unchecked")Function
<Object
, ?> sayHelloFn = ((Invocable
)js).getInterface(sayHelloRaw,Function
.class); // Python function that returns a valueObject
worldFn = python.eval("\n" + "def world():\n" + " return 'World'\n" + "\n" + "world\n" + "" ); // pass Python function as an argument to JavaScript functionString
helloWorld = (String
) sayHelloFn.apply(worldFn); assert "Hello World!".equals(helloWorld) : helloWorld;
Languages provided by GraalVM use the
ScriptEngineManager
created by
org.netbeans.libs.graalsdk.Scripting#createManager()
factory
method as a connection point to talk to each other and mutually share
and use its objects, functions and other services.
org.netbeans.libs.graalsdk.Scripting
interoperation let's you
view each array-like object as List
. Just
"cast it"
like in following example:
ScriptEngine
js =Scripting
.createManager().getEngineByName("GraalVM:js"); assert js != null : "Run on GraalVM!";Object
raw = js.eval("['Hello', 'World']\n"); // convert the raw array into List:List
<?> list = ((Invocable
)js).getInterface(raw,List
.class); assert list.size() == 2; assert list.get(0).equals("Hello"); assert list.get(1).equals("World");
org.netbeans.libs.graalsdk.Scripting
interoperation lets Java call
foreign functions that guest language code exports
(details vary across languages).
This section presents a few examples.
Multiplier
in the following code.
@Notes:FunctionalInterface
interface Multiplier { int multiply(int a, int b); } public void callJavaScriptFunctionFromJava() throwsException
{Invocable
invocable = (Invocable
) engine;String
src = "(" + "function (a, b) {\n" + " return a * b;\n" + "})"; // Evaluate JavaScript function definitionObject
jsFunction = engine.eval(src); // Create Java access to JavaScript function Multiplier mul = invocable.getInterface(jsFunction, Multiplier.class); assertEquals(42, mul.multiply(6, 7)); assertEquals(144, mul.multiply(12, 12)); assertEquals(256, mul.multiply(32, 8)); }
Object
that can be
"cast"
to a foreign function with a Java type.
Notes:ScriptEngine
python = manager.getEngineByName("GraalVM:python");Invocable
invocable = (Invocable
) python;String
src = "" + "def mul(a, b):\n" + " return a * b\n" + "\n" + "mul\n"; // Evaluate Python function definitionObject
pythonFunction = python.eval(src); // Create Java access to Python function Multiplier mul = invocable.getInterface(pythonFunction, Multiplier.class); assertEquals(42, mul.multiply(6, 7)); assertEquals(144, mul.multiply(12, 12)); assertEquals(256, mul.multiply(32, 8));
mul
function and also
returns it as a value to the Java code that can
"cast"
it to a foreign function with a given Java type.qbinom
from the built-in stats package.
@Don't forget to install support for the R language into your GraalVM instance:FunctionalInterface
interface BinomQuantile { int qbinom(double q, int count, double prob); } public void callRFunctionFromJava() throwsException
{ // FastR currently needs access to native libraries: finalScriptEngineManager
manager =Scripting
.newBuilder().allowAllAccess(true).build();ScriptEngine
rEngine = manager.getEngineByMimeType("application/x-r"); assumeNotNull(rEngine); finalObject
funcRaw = rEngine.eval("qbinom"); BinomQuantile func = ((Invocable
) rEngine).getInterface(funcRaw, BinomQuantile.class); assertEquals(4, func.qbinom(0.37, 10, 0.5)); }
$ /graalvm/bin/gu install
Counter
in the following code.
interface Counter { void addTime(int hours, int minutes, int seconds); int timeInSeconds(); } public void callJavaScriptFunctionsWithSharedStateFromJava() throwsNotes:Exception
{String
src = "\n" + "(function() {\n" + " var seconds = 0;\n" + " function addTime(h, m, s) {\n" + " seconds += 3600 * h;\n" + " seconds += 60 * m;\n" + " seconds += s;\n" + " }\n" + " function time() {\n" + " return seconds;\n" + " }\n" + " return {\n" + " 'addTime': addTime,\n" + " 'timeInSeconds': time\n" + " }\n" + "})\n"; // Evaluate JavaScript function definitionObject
jsFunction = engine.eval(src); // Execute the JavaScript function via its call methodObject
jsObject = ((Invocable
)engine).invokeMethod(jsFunction, "call"); // Create Java access to the JavaScript object Counter counter = ((Invocable
)engine).getInterface(jsObject, Counter.class); counter.addTime(6, 30, 0); counter.addTime(9, 0, 0); counter.addTime(12, 5, 30); assertEquals(99330, counter.timeInSeconds()); }
jsFunction
) that can be
executed directly,
without giving it a Java type.jsFunction
returns a JS dynamic object (containing two methods
and a shared variable)
that can be "cast"
to a foreign object with a Java type.org.netbeans.libs.graalsdk.Scripting
interoperation allows Java to access fields and functions of a JavaScript class,
for example the foreign function factory and class given the Java type
Incrementor
in the following code.
interface Incrementor { int inc(); int dec(); int value(); } public void callJavaScriptClassFactoryFromJava() throwsNotes:Exception
{String
src = "\n" + "(function() {\n" + " class JSIncrementor {\n" + " constructor(init) {\n" + " this.value = init;\n" + " }\n" + " inc() {\n" + " return ++this.value;\n" + " }\n" + " dec() {\n" + " return --this.value;\n" + " }\n" + " }\n" + " return function(init) {\n" + " return new JSIncrementor(init);\n" + " }\n" + "})\n"; // Evaluate JavaScript function definitionObject
jsFunction = engine.eval(src); finalInvocable
inv = (Invocable
) engine; // Execute the JavaScript functionObject
jsFactory = inv.invokeMethod(jsFunction, "call"); // Execute the JavaScript factory to create Java objects Incrementor initFive = inv.getInterface( inv.invokeMethod(jsFactory, "call", null, 5), Incrementor.class ); Incrementor initTen = inv.getInterface( inv.invokeMethod(jsFactory, "call", null, 10), Incrementor.class ); initFive.inc(); assertEquals("Now at seven", 7, initFive.inc()); initTen.dec(); assertEquals("Now at eight", 8, initTen.dec()); initTen.dec(); assertEquals("Values are the same", initFive.value(), initTen.value()); }
jsFunction
that can be
executed directly,
without giving it a Java type.jsFunction
returns a JS factory method for class
JSIncrementor
that can also be executed directly.Incrementor
.invocable.getInterface(Class)
plays an essential role supporting interoperation between Java and guest language data
structures.
This section presents a few examples.
List
of objects with type given by interface Point
.
interface Point { int x(); int y(); } @Notes:FunctionalInterface
interface PointProvider {List
<Point> createPoints(); } public void accessJavaScriptArrayWithTypedElementsFromJava() throwsException
{String
src = "\n" + "(function() {\n" + " class Point {\n" + " constructor(x, y) {\n" + " this.x = x;\n" + " this.y = y;\n" + " }\n" + " }\n" + " return [ new Point(30, 15), new Point(5, 7) ];\n" + "})\n"; // Evaluate the JavaScript function definitionObject
jsFunction = engine.eval(src); // Create Java-typed access to the JavaScript function PointProvider pointProvider = ((Invocable
) engine).getInterface(jsFunction, PointProvider.class); // Invoke the JavaScript function to generate pointsList
<Point> points = pointProvider.createPoints(); assertEquals("Two points", 2, points.size()); Point first = points.get(0); assertEquals(30, first.x()); assertEquals(15, first.y()); Point second = points.get(1); assertEquals(5, second.x()); assertEquals(7, second.y()); }
jsFunction
) that can be
"cast"
to a foreign function with Java type PointProvider
.pointProvider
) creates
a JS array, which is returned as a foreign object
with Java type List<Point>
.Repository
and Owner
define the structure as Java types.
The following Java code is able to inspect a JavaScript JSON data structure generated by "mock parser" in a type-safe way.
interface Repository { int id();Notes:String
name(); Owner owner(); boolean has_wiki();List
<String
> urls(); } interface Owner { int id();String
login(); boolean site_admin(); } @FunctionalInterface
interface ParseJSON {List
<Repository> parse(); } public void accessJavaScriptJSONObjectFromJava() throwsException
{String
src = "(function () { \n" + " return function() {\n" + " return [\n" + " {\n" + " \"id\": 6109440,\n" + " \"name\": \"holssewebsocket\",\n" + " \"owner\": {\n" + " \"login\": \"jersey\",\n" + " \"id\": 399710,\n" + " \"site_admin\": false\n" + " },\n" + " \"urls\": [\n" + " \"https://api.github.com/repos/jersey/hol\",\n" + " \"https://api.github.com/repos/jersey/hol/forks\",\n" + " \"https://api.github.com/repos/jersey/hol/teams\",\n" + " ],\n" + " \"has_wiki\": true\n" + " }\n" + " ]\n" + " };\n" + "})\n"; // Evaluate the JavaScript function definitionObject
jsFunction = engine.eval(src); // Execute the JavaScript function to create the "mock parser"Object
jsMockParser = ((Invocable
) engine).invokeMethod(jsFunction, "call"); // Create Java-typed access to the "mock parser" ParseJSON mockParser = ((Invocable
) engine).getInterface(jsMockParser, ParseJSON.class);List
<Repository> repos = mockParser.parse(); assertEquals("One repo", 1, repos.size()); assertEquals("holssewebsocket", repos.get(0).name()); assertTrue("wiki", repos.get(0).has_wiki()); assertEquals("3 urls", 3, repos.get(0).urls().size()); finalString
url1 = repos.get(0).urls().get(0); assertEquals("1st", "https://api.github.com/repos/jersey/hol", url1); Owner owner = repos.get(0).owner(); assertNotNull("Owner exists", owner); assertEquals("login", "jersey", owner.login()); assertEquals("id", 399710, owner.id()); assertFalse(owner.site_admin()); }
jsFunction
that can be
executed directly,
without giving it a Java type.jsFunction
returns a JS mock JSON parser function
(assigned to jsMockParser
), that can be
"cast"
to a foreign function with Java type ParseJSON
.List<Repository>
.
Map
. Here is an example:
While not type-safe, it is a generic approach able to inspect objects of unknown structure.ScriptEngine
python =Scripting
.createManager().getEngineByName("GraalVM:python"); assert python != null : "Install Graal Python via `gu install python`";Object
raw = python.eval("\n" + "class Point:\n" + " x = 1\n" + " y = 2\n" + "Point()\n" );Map
<?,?> point = ((Invocable
)python).getInterface(raw,Map
.class); assert ((Number
)point.get("x")).intValue() == 1; assert ((Number
)point.get("y")).intValue() == 2;
Moment
in
the following example.
public static final class Moment { public final int hours; public final int minutes; public final int seconds; public Moment(int hours, int minutes, int seconds) { this.hours = hours; this.minutes = minutes; this.seconds = seconds; } } public void accessFieldsOfJavaObject() throwsNotes:Exception
{String
src = "\n" + "(function(t) {\n" + " return 3600 * t.hours + 60 * t.minutes + t.seconds;\n" + "})\n"; final Moment javaMoment = new Moment(6, 30, 10); // Evaluate the JavaScript function definitionObject
jsFunction = engine.eval(src); // Execute the JavaScript function, passing a Java object argumentObject
jsSeconds = ((Invocable
)engine).invokeMethod( jsFunction, "call", null, javaMoment ); // Convert foreign object result to desired Java type int seconds = ((Number
) jsSeconds).intValue(); assertEquals(3600 * 6 + 30 * 60 + 10, seconds); }
jsFunction
that can be executed directly with one argument.javaMoment
is seen by the JS function as a
foreign object whose public fields are visible.
jsFunction
returns a JS number
that can be
"cast"
to a Java Number
and then to a Java int
.
The multiple steps needed to convert the result in the above example
produces awkward code that can be simplified.
Instead of invoking the JS function directly, and "casting" the wrapped JS result,
we can instead
"cast"
the JS function to a Java foreign function (of type MomentConverter
) that
returns the desired Java type directly, as shown in the following variation.
@FunctionalInterface
interface MomentConverter { int toSeconds(Moment moment); } public void accessFieldsOfJavaObjectWithConverter() throwsException
{String
src = "\n" + "(function(t) {\n" + " return 3600 * t.hours + 60 * t.minutes + t.seconds;\n" + "})\n"; final Moment javaMoment = new Moment(6, 30, 10); // Evaluate the JavaScript function definition finalObject
jsFunction = engine.eval(src); // Convert the function to desired Java type MomentConverter converter = ((Invocable
) engine).getInterface( jsFunction, MomentConverter.class ); // Execute the JavaScript function as a Java foreign function int seconds = converter.toSeconds(javaMoment); assertEquals(3600 * 6 + 30 * 60 + 10, seconds); }
interface MomentFactory { Moment create(int h, int m, int s); } public void createJavaScriptFactoryForJavaClass() throwsNotes:Exception
{String
src = "\n" + "(function(Moment) {\n" + " return function(h, m, s) {\n" + " return new Moment(h, m, s);\n" + " };\n" + "})\n"; // Evaluate the JavaScript function definition finalObject
jsFunction = engine.eval(src); // Create a JavaScript factory for the provided Java class finalObject
jsFactory = ((Invocable
) engine).invokeMethod(jsFunction, "call", null, Moment.class); // Convert the JavaScript factory to a Java foreign function MomentFactory momentFactory = ((Invocable
) engine).getInterface(jsFactory, MomentFactory.class); final Moment javaMoment = momentFactory.create(6, 30, 10); assertEquals("Hours", 6, javaMoment.hours); assertEquals("Minutes", 30, javaMoment.minutes); assertEquals("Seconds", 10, javaMoment.seconds); }
jsFunction
that can be executed directly with one argument.Moment.class
is seen by the JS function as a
foreign class whose public constructor is visible.
jsFunction
with the Java class argument returns
a JS "factory" function (for the Java class) that can be
"cast"
to the desired Java function type (MomentFactory
).ScriptException
that provides the appropriate details.
RuntimeException
subclasses; the Throwable.getCause()
then indicates
the original cause for the exception.
// this is error in Javascript (null dereference), so ScriptException will be thrown, // with scripting engine's implementation exception inside. try { engine.eval( "var a = null;\n" + "a.call(null)"); } catch (ScriptException
ex) { } // The callback will throw a checked exception - something that happens // "outside" the script in the runtime: will throw RuntimeException subclass // with the real exception set as cause. Callback cb = new Callback(); try {Object
jsFunction = engine.eval( "(function(cb) {" + " cb.io();" + "})" ); ((Invocable
) engine).invokeMethod(jsFunction, "call", null, cb); } catch (RuntimeException
ex) { // this is a checked java exception; it's just wrapped into a // RuntimeException: assertTrue(ex.getCause() instanceofIOException
); } catch (Exception
ex) { fail("Runtime expected"); } // the last exception is a runtime exception originating from java. // it will be reported 'as is' or wrapped, depending on the engine try {Object
jsFunction = engine.eval( "(function(cb, l) {" + " cb.next(l);" + "})" ); ((Invocable
) engine).invokeMethod(jsFunction, "call", null, cb, newLinkedList
()); } catch (NoSuchElementException
ex) { // this is a checked java exception; it's thrown unchanged. } catch (RuntimeException
ex) { // ... or wrapped in a Runtime: assertTrue(ex.getCause() instanceofNoSuchElementException
); } catch (Exception
ex) { fail("NoSuchElement expected"); }