blob: 5b68db20b5f89ee2e78981bf94c1a9cf7eb61ab9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.libs.graalsdk;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.function.Function;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.netbeans.api.scripting.Scripting;
import org.netbeans.junit.NbTestCase;
public class ScriptingTutorial extends NbTestCase {
private ScriptEngine engine;
public ScriptingTutorial(String name) {
super(name);
}
@Override
protected void setUp() throws Exception {
engine = listAll();
assertNotNull(engine);
}
public ScriptEngine listAll() {
// @start region="listAll"
ScriptEngine found = null;
final ScriptEngineManager manager = Scripting.createManager();
for (ScriptEngineFactory factory : manager.getEngineFactories()) {
final String name = factory.getEngineName();
System.err.println("Found " + name);
if (factory.getMimeTypes().contains("text/javascript")) {
if (name.equals("GraalVM:js")) {
found = factory.getScriptEngine();
}
}
}
// @end region="listAll"
return found;
}
public void testHelloWorld() throws Exception {
// @start region="testHelloWorld"
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;
// @end region="testHelloWorld"
}
public void testCastPythonObj() throws Exception {
// @start region="testCastPythonObj"
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;
// @end region="testCastPythonObj"
}
public void testCastJsArray() throws Exception {
// @start region="testCastJsArray"
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");
// @end region="testCastJsArray"
}
public void testHelloWorldInPythonAndJavaScript() throws Exception {
// @start region="testHelloWorldInPythonAndJavaScript"
// creates a single shared manager for two languages
final ScriptEngineManager manager = Scripting.createManager();
// creates two engines connected to each other
ScriptEngine 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 argument
Object 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 value
Object worldFn = python.eval("\n"
+ "def world():\n"
+ " return 'World'\n"
+ "\n"
+ "world\n"
+ ""
);
// pass Python function as an argument to JavaScript function
String helloWorld = (String) sayHelloFn.apply(worldFn);
assert "Hello World!".equals(helloWorld) : helloWorld;
// @end region="testHelloWorldInPythonAndJavaScript"
}
public void testCallJavaScriptFunctionFromJava() throws Exception {
callJavaScriptFunctionFromJava();
}
// @start region="callJavaScriptFunctionFromJava"
@FunctionalInterface
interface Multiplier {
int multiply(int a, int b);
}
public void callJavaScriptFunctionFromJava() throws Exception {
Invocable invocable = (Invocable) engine;
String src = "(" +
"function (a, b) {\n" +
" return a * b;\n" +
"})";
// Evaluate JavaScript function definition
Object 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));
}
// @end region="callJavaScriptFunctionFromJava"
public void testPythonFunctionFromJava() throws Exception {
ScriptEngineManager manager = Scripting.createManager();
// @start region="testPythonFunctionFromJava"
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 definition
Object 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));
// @end region="testPythonFunctionFromJava"
}
public void testCallRFunctionFromJava() throws Exception {
ScriptEngine rEngine = Scripting.createManager().getEngineByMimeType("application/x-r");
if (rEngine != null) {
callRFunctionFromJava();
}
}
// @start region="callRFunctionFromJava"
@FunctionalInterface
interface BinomQuantile {
int qbinom(double q, int count, double prob);
}
public void callRFunctionFromJava() throws Exception {
// @start region="allowAllAccess
// FastR currently needs access to native libraries:
final ScriptEngineManager manager = Scripting.newBuilder().allowAllAccess(true).build();
ScriptEngine rEngine = manager.getEngineByMimeType("application/x-r");
// @end region="allowAllAccess"
final Object funcRaw = rEngine.eval("qbinom");
BinomQuantile func = ((Invocable) rEngine).getInterface(funcRaw, BinomQuantile.class);
assertEquals(4, func.qbinom(0.37, 10, 0.5));
}
// @end region="callRFunctionFromJava"
public void testCallRFunctionFromJavaTheOldWay() throws Exception {
ScriptEngine rEngine = Scripting.createManager().getEngineByMimeType("application/x-r");
// FastR currently needs access to native libraries:
rEngine.getContext().setAttribute("allowAllAccess", true, ScriptContext.GLOBAL_SCOPE);
final Object funcRaw = rEngine.eval("qbinom");
BinomQuantile func = ((Invocable) rEngine).getInterface(funcRaw, BinomQuantile.class);
assertEquals(4, func.qbinom(0.37, 10, 0.5));
}
public void testCallJavaScriptFunctionsWithSharedStateFromJava() throws Exception {
callJavaScriptFunctionsWithSharedStateFromJava();
}
// @start region="callJavaScriptFunctionsWithSharedStateFromJava"
interface Counter {
void addTime(int hours, int minutes, int seconds);
int timeInSeconds();
}
public void callJavaScriptFunctionsWithSharedStateFromJava() throws 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 definition
Object jsFunction = engine.eval(src);
// Execute the JavaScript function via its call method
Object 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());
}
// @end region="callJavaScriptFunctionsWithSharedStateFromJava"
public void testAccessFieldsOfJavaObject() throws Exception {
accessFieldsOfJavaObject();
}
public void testAccessFieldsOfJavaObjectWithConverter() throws Exception {
accessFieldsOfJavaObjectWithConverter();
}
// @start region="accessFieldsOfJavaObject"
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() throws 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 definition
Object jsFunction = engine.eval(src);
// Execute the JavaScript function, passing a Java object argument
Object 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);
}
// @end region="accessFieldsOfJavaObject"
// @start region="accessFieldsOfJavaObjectWithConverter"
@FunctionalInterface
interface MomentConverter {
int toSeconds(Moment moment);
}
public void accessFieldsOfJavaObjectWithConverter() throws 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 definition
final Object 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);
}
// @end region="accessFieldsOfJavaObjectWithConverter"
public void testCreateJavaScriptFactoryForJavaClass() throws Exception {
createJavaScriptFactoryForJavaClass();
}
// @start region="createJavaScriptFactoryForJavaClass"
interface MomentFactory {
Moment create(int h, int m, int s);
}
public void createJavaScriptFactoryForJavaClass() throws 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
final Object jsFunction = engine.eval(src);
// Create a JavaScript factory for the provided Java class
final Object 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);
}
// @end region="createJavaScriptFactoryForJavaClass"
public void testCallJavaScriptClassFactoryFromJava() throws Exception {
callJavaScriptClassFactoryFromJava();
}
// @start region="callJavaScriptClassFactoryFromJava"
interface Incrementor {
int inc();
int dec();
int value();
}
public void callJavaScriptClassFactoryFromJava() throws 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 definition
Object jsFunction = engine.eval(src);
final Invocable inv = (Invocable) engine;
// Execute the JavaScript function
Object 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());
}
// @end region="callJavaScriptClassFactoryFromJava"
public void testAccessJavaScriptArrayWithTypedElementsFromJava() throws Exception {
accessJavaScriptArrayWithTypedElementsFromJava();
}
// @start region="accessJavaScriptArrayWithTypedElementsFromJava"
interface Point {
int x();
int y();
}
@FunctionalInterface
interface PointProvider {
List<Point> createPoints();
}
public void accessJavaScriptArrayWithTypedElementsFromJava() throws Exception {
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 definition
Object 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 points
List<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());
}
// @end region="accessJavaScriptArrayWithTypedElementsFromJava"
public void tetsAccessJSONObjectProperties() throws Exception {
accessJavaScriptJSONObjectFromJava();
}
// Checkstyle: stop
// @start region="accessJavaScriptJSONObjectFromJava"
interface Repository {
int id();
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() throws Exception {
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 definition
Object 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());
final String 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());
}
// @end region="accessJavaScriptJSONObjectFromJava"
public void testHandleScriptException() throws Exception {
handleScriptExceptions();
}
public static class Callback {
public String next(List<String> l) {
return l.iterator().next();
}
public void io() throws IOException {
throw new IOException("");
}
}
// Checkstyle: stop
public void handleScriptExceptions() throws Exception {
// @start region="handleScriptExceptions"
// 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() instanceof IOException);
} 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, new LinkedList());
} catch (NoSuchElementException ex) {
// this is a checked java exception; it's thrown unchanged.
} catch (RuntimeException ex) {
// ... or wrapped in a Runtime:
assertTrue(ex.getCause() instanceof NoSuchElementException);
} catch (Exception ex) {
fail("NoSuchElement expected");
}
// @end region="handleScriptExceptions"
}
}