koush / quack

Apache License 2.0
62 stars 10 forks source link

Quack

Quack provides Java (Android and desktop) bindings to JavaScript engines.

Runtimes

Quack supports both the Duktape and QuickJS runtimes.

Features

Examples

Evaluating JavaScript

JavaScript

'hello'

Java:

Calling QuackContext.evaluate will return a Java Object with the evaluation result.

QuackContext quack = QuackContext.create();
Object result = quack.evaluate(javascriptString);
System.out.println(result);
// prints "hello"

Evaluating and Calling JavaScript Functions

JavaScript

(function () {
  return 'hello';
})

Java

QuackContext quack = QuackContext.create();
JavaScriptObject result = quack.evaluateForJavaScriptObject(javascriptString);
System.out.println(result.call());
// prints "hello"

Passing Data to JavaScript

JavaScript

(function (str) {
  return str + ' world';
})

Java

QuackContext quack = QuackContext.create();
JavaScriptObject result = quack.evaluateForJavaScriptObject(javascriptString);
System.out.println(result.call("hello"));
// prints "hello world"

Setting and using Global Properties

JavaScript

System.out.println('hello world');

Java

QuackContext quack = QuackContext.create();
quack.getGlobalObject().set("System", System.class);
quack.evaluate(javascriptString);
// prints "hello world"

Passing Objects to JavaScript

JavaScript

(function(obj) {
  obj.hello();
})

Java

class Foo {
  public void hello() {
    System.out.println("hello");
  }
}

QuackContext quack = QuackContext.create();
JavaScriptObject result = quack.evaluateForJavaScriptObject(javascriptString);
quack.call(new Foo());
// prints "hello world"

Passing Interfaces to JavaScript

JavaScript

(function(func) {
  // notice that it is not func.run()
  // single method interfaces (lambdas) are automatically coerced into functions!
  func();
})

Java

Runnable runnable = () -> {
  System.out.println("hello world");
}

QuackContext quack = QuackContext.create();
JavaScriptObject result = quack.evaluateForJavaScriptObject(javascriptString);
result.call(runnable);
// prints "hello world"

Passing Interfaces back to Java

JavaScript

return {
  hello: function(printer) {
    printer('hello world');
  }
}

Java

interface Foo {
  void hello(Printer printer);
}

interface Printer {
  print(String str);
}

QuackContext quack = QuackContext.create();
Foo result = quack.evaluate(javascriptString, Foo.class);  
result.hello(str -> System.out.println(str));
// prints "hello world"

Creating Java Objects in JavaScript

JavaScript

var foo = new Foo();
foo.hello('hello world');

Java

class Foo {
  public void hello(String str) {
    System.out.println(str);
  }
}

QuackContext quack = QuackContext.create();
quack.getGlobalObject().set("Foo", Foo.class);
quack.evaluate(javascriptString);
// prints "hello world"

Creating Java Objects in JavaScript (simplified)

JavaScript

var Foo = JavaClass.forName("com.whatever.Foo");
var foo = new Foo();
foo.hello('hello world');

Java

class Foo {
  public void hello(String str) {
    System.out.println(str);
  }
}

QuackContext quack = QuackContext.create();
quack.getGlobalObject().set("JavaClass", Class.class);
quack.evaluate(javascriptString);
// prints "hello world"

Marshalling

Types need to be marshalled when passing between the runtimes. The class specifier and parameter types are used to determine the behavior when being marshalled. The following builtin types are marshalled as follows:

JavaScript (Input) Java (Output)
number Number (Integer or Double)
Uint8Array ByteBuffer (direct, deep copy)
undefined null
Java (Input) JavaScript (Output)
long string (otherwise precision is lost)
ByteBuffer (direct or byte array backed) Uint8Array (deep copy)
byte, short, int, float, double number
null null

Coercions

Types and methods can be coerced between runtimes.

Java to JavaScript Type Coercion

JavaScript

(function(data) {
  return data;
})

Java

class Foo {}

QuackContext quack = QuackContext.create();
// all instances of Foo sent to JavaScript get coerced into the String "hello world"
quack.putJavaToJavaScriptCoercion(Foo.class, (clazz, o) -> "hello world");
System.out.println(quack.evaluateForJavaScriptObject.call(new Foo()));
// prints "hello world"

Concurrency

JavaScript runtimes are single threaded. All execution in the JavaScript runtime is gauranteed thread safe, by way of Java synchronization.

Garbage Collection

When a Java object is passed to the JavaScript runtime, a hard reference is held by the JavaScript proxy counterpart. This reference is removed when the JavaScriptObject is finalized. And same for when a Java object is passed to the JavaScript runtime. JavaScriptObjects sent to the Java runtime will be deduped, so the same proxy instance is always used. JavaObjects sent to JavaScript will marshall a new Proxy object every time.

Debugging

Install the appropriate QuickJS Debugger or Duktape Debugger for VS Code. QuickJS is the default runtime used by Quack.

JavaScript

System.out.println('set a breakpoint here!');

Java

QuackContext quack = QuackContext.create();
quack.getGlobalObject().set("System", System.class);
quack.waitForDebugger("0.0.0.0:9091")
// attach using VS Code
quack.evaluate(javascriptString);

Square Duktape-Android

Quack was initially forked from Square's Duktape Android library. But it has been totally rewritten to suit different needs.