See: Description
Annotation Type | Description |
---|---|
JavaScriptBody |
Put this annotation on a method to provide its special implementation
in JavaScript.
|
JavaScriptResource |
Include JavaScript libraries into your application easily.
|
The above defines method meaning which sums two JavaScript objects together (being invoked inside of a JavaScript interpreter). The meaning method now becomes a properly typed Java surface to your JavaScript code which can be directly called from the rest of your Java code:@
JavaScriptBody
(args = {"x", "y"}, body = "return x + y;") private static native int meaning(int x, int y);
public static void main(String... args) { assert 42 == meaning(40, 2) : "Meaning of World should be 42!"; }Real code tip: real classes using this technique are available online: JsMethods and Bodies. Editing hint: one can see the list of arguments of the meaning is now duplicated - it is once specified in Java, and once inside of the
JavaScriptBody
array of args
. This is necessary to keep the names of
arguments accessible during runtime. However don't despair - there
is a code completion for the value of args
attribute!
Just type the Java signature first and then press Ctrl+Space and the
right parameter names will be inserted for you.
small code snippets
-
that is also possible thanks to JavaScriptResource
annotation. Imagine file mul.js
with following content:
function mul(x, y) { return x * y; }Place the file next to your class and reference it with
the annotation
:
All the Java methods annotated@
JavaScriptResource
("mul.js") class Mul {@
JavaScriptBody
(args = { "x", "y" }, body = "return mul(x, y);") public static native int multiply(int x, int y); public static void main(String... args) { assert 42 == multiply(6, 7) : "Meaning of World should be 42!"; } }
JavaScriptBody
can now reference everything that is in the mul.js
file -
e.g. the body of the multiply
method can reference the
function mul
and use it.
Real code tip:
this
is the way
the knockout.js library
is included in its ko4j library.
As can be seen, there is a special syntax (starting with @) to properly identify the right Java method to call on a Java object passed into the JavaScript interpreter. The syntax starts with a fully qualified name of the class, followed by :: and name of the method including signature of its parameters. In case of runnable, this is just () as the method has no parameters, but the signature can be more complicated. For example in case of following method@
JavaScriptBody
(args = {"id", "r"},javacall
= true, body = "\n" + " document.getElementById(id).onclick = function() {\n" + " r.@
java.lang.Runnable::run()();\n" + " };\n" + " ") public static native void onClick(String id, Runnable r);
static int compare(int i1, String s1, int i2, String s2)it would be (ILjava/lang/String;ILjava/lang/String;) (btw. the return type is not included in the signature). The actual parameters then follows. The JavaScript call to such compare method would then look like:
@
the.pkg.Clazz::compare(ILjava/lang/String;ILjava/lang/String;)(1, 'One', 2, 'Two');
This syntax gives enough flexibility, helps to properly select one
of overloaded methods and follows the tradition of previous attempts to
provide JavaScript to Java calling conventions.
Please note that to turn the special Java callback syntax on, one
needs to set the JavaScriptBody.javacall()
attribute to true. The callback syntax consists of
following parts:
[instance.]@classname::methodname(signature)(arguments)
Here is the JNI type signatures table one can use to convert Java parameters to JVM's internal letter based representation:
Type Signature | Java Type |
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
L fully-qualified-class ; | fully-qualified-class |
[ type | type[] |
package x.y.z; class Handler { int pulse() { return 1; } int pulse(int howMuch) { return howMuch; } int pulse(long evenMore) { return (int) (5 + evenMore); } }you then need to choose in
JavaScriptBody
the appropriate method to call:
@
JavaScriptBody
(args = { "h" }, javacall = true, // you want to process the @ syntax body = "return h.@x.y.z.Handler::pulse()() +" + // the call to no argument method "h.@x.y.z.Handler::pulse(I)(10) +" + // the call to method with integer argument "h.@x.y.z.Handler::pulse(J)(10);" // the call to method with long argument ) static native void threePulsesFromJavaScript(Handler h); static { assert 26 == threePulsesFromJavaScript(new Handler()); }
To avoid ambiguity, the specification of the correct signature is required on every call. However, to simplify the development, there is an annotation processor to verify the signature really refers to an existing method.
JavaScriptBody
requires
the arrays to be always transfered by a copy. As such following code:
will still print Hello World! in spite the JavaScript code sets the 0-th array element to@
JavaScriptBody
(args = {"arr"}, body = "arr[0] = null;") private static native void uselessModify(String[] arr); public static void main(String... args) { String[] hello = { "Hello", "World!" }; uselessModify(arr); System.out.println(arr[0] + " " + arr[1]); }
null
. Because the array
is passed as a copy, such assignment has no effect on the Java array.
In case one needs to modify an array in a JavaScript and use its
values in Java, one has to return the array back as a return value:
now the program prints Ahoy World! as the modified array is returned back and converted (by a copy) into a Java@
JavaScriptBody
(args = {"arr"}, body = "arr[0] = 'Ahoy'; return arr;") private static native Object[] usefulModify(String[] arr); public static void main(String... args) { String[] hello = { "Hello", "World!" }; Object[] ret = usefulModify(arr); System.out.println(ret[0] + " " + ret[1]); }
Object[]
(but of course the ret != hello
). Usually the copy based
passing of arrays works OK. It is however good to keep it in mind to
avoid unwanted surprises.
class WrapperAroundJsObj { private final Object js; WrapperAroundJsObj() { js = initValue(); } public void set(int v) { setValue(js, v); }The type of the Java reference is@JavaScriptBody
(args = {}, body = "return { value : 0 };") private static native Object initValue();@JavaScriptBody
( args = { "js", "v" }, body = "js.value = v;", wait4js = false ) private static native void setValue(Object js, int v); }
Object
.
From a Java perspective it has no additional methods or fields, however
its properties can be manipulated from JavaScript. Send the object back
to JavaScript by passing it as a parameter of some method
(like the setValue
one) and perform necessary JavaScript
calls or changes on it.
null
and
undefined
. Java has just null
.
For purposes of simplicity and easier inter-operability, undefined
values returned from @JavaScriptBody
annotated methods are converted to null
. In the following
example both methods return null
:
This is the behavior since version 1.4.@JavaScriptBody
( args = {}, body = "var empty = {}; return empty.x;" ) private static native Object returnUndefined();@JavaScriptBody
( args = {}, body = "var empty = {}; empty.x = null; return empty.x;" ) private static native Object returnNull(); }
JavaScriptBody
annotated methods need to
be post processed before they can be used - e.g. their native
body needs to be generated to call into JavaScript (btw. the code is performed
via Fn
). There are three ways
such post processing can happen.
Compile time processing - this is the preferred method that
most of the Html Java APIs are using.
Just include following plugin configuration into your pom.xml
and your classes will be ready for execution as soon as process-classes
Maven phase is over:
<plugin> <groupId>org.netbeans.html</groupId> <artifactId>html4j-maven-plugin</artifactId> <version>${net.java.html.version}</version> <executions> <execution> <id>js-classes</id> <goals> <goal>process-js-annotations</goal> </goals> </execution> </executions> </plugin>This plugin works in orchestration with annotation processor associated with
JavaScriptBody
and JavaScriptResource
- the processor creates
list of files that need post-processing. The
Maven
plugin reads these files, processes classes mentioned in them and
modifies (and deletes at the end) the files to not include classes
already processed.
Instrumentation Agent - one can do processing in runtime
using JDK's instrumentation
abilities. The JAR artifact of org.netbeans.html:net.java.html.boot
contains an Agent-Class
and Premain-Class
definitions in its manifest. As such one can launch the Java virtual
machine with
$ java -javaagent:jarpath=net.java.html.boot-x.y.jarand the runtime will take care of processing bytecode of classes not yet processed in compile time before they are loaded into the virtual machine. Special classloading - when booting your application with
BrowserBuilder
there is a 3rd option of
processing the classes. If there are some classes not yet processed
(remember the files listing them generated by the
annotation
processor), the launching method
will create a special classloader to that does the processing before
loading the bytecode into the virtual machine.
The options are rich, however to avoid any troubles (as the runtime
processing needs to also include asm-5.0.jar
on application
classpath), it is recommended
to perform the compile time processing.
$ mvn archetype:generate \ -DarchetypeGroupId=org.apidesign.html \ -DarchetypeArtifactId=knockout4j-archetype \ -DarchetypeVersion=x.yAnswer few questions (for example choose myfirstbrwsrpage as artifactId) and then you can:
$ cd myfirstbrwsrpage $ mvn process-classes exec:javaIn a few seconds (or minutes if Maven decides to download the whole Internet of dependencies) you should see a sample Hello World application. It is basically composed from one Java and one HTML file:
$ ls src/main/java/**/DataModel.java $ ls src/main/webapp/pages/index.htmlPlay with them, modify them and enjoy Html for Java!
The following video shows how easy it is to use NetBeans 8.0, JDK8 to debug an application that intermixes Java and JavaScript calls. One can put breakpoints into Java part, as well as JavaScript source code, inspect Java as well as JavaScript variables and switch between these two languages without any restrictions.
Copyright © 2021 The Apache Software Foundation. All rights reserved.