-
Notifications
You must be signed in to change notification settings - Fork 176
Doppio Developer Guide
So you want to hack on doppio: maybe to integrate it into your project more closely, or to add support for JVM features that are currently busted, or whatever your heart desires. Start here to save yourself a lot of head-scratching.
Contents:
Native methods allow doppio to run Javascript code from Java classes. They're critical to the execution of the JVM, but they can also be used to provide web functionality to your Java programs. For example, you might write a native method to pop up an alert message, or manipulate DOM elements, or anything else you can do with Javascript.
First, identify the native method that you want to write:
is it in the Java Class Library somewhere, or perhaps in some Java code you wrote?
For this example, we'll assume we wrote the file classes/util/Test.java like so:
package classes.util;
class Test {
static native int addTwo(int x);
public static void main(String[] args) {
System.out.println(addTwo(5));
}
}
doppio ships with a utility called doppioh, which we liken to the official javah. If you are in the parent directory of classes, you can point it as your compiled class file classes/util/Test.class to generate a native method stub like so:
./doppioh -classpath . -d natives classes/util/Test
The above command will create the file natives/classes_util_Test.js with a stub for the native method addTwo. If you want to write your native methods in TypeScript, you can do that too, but you'll need to point it to a directory with doppio interface definitions for typing purposes:
./doppioh -classpath . -typescript build/dev-cli -d natives classes/util/Test
You can also generate native method stubs for an entire package, e.g. java.lang:
./doppioh -classpath vendor/classes -d natives java.lang
... which will define the file natives/java_lang.js.
ProTip: Make sure you perform a grunt release-cli so you have a doppioh shortcut in your doppio directory.
Next, you'll need to write your native method implementation. Here's the stub generated by doppioh:
registerNatives({
'classes/util/Test': {
'addTwo(I)I': function(thread, arg0) {
thread.throwNewException('Ljava/lang/UnsatisfiedLinkError;', 'Native method not implemented.');
}
}
});
Let's implement that! Our method will add 2 to the specified integer, which is fairly straightforward:
registerNatives({
'classes/util/Test': {
'addTwo(I)I': function(thread, arg0) {
// The |0 ensures that we return a 32-bit integer quantity. :)
return (arg0 + 2)|0;
}
}
});
Now that you have a new native defined, you'll need to make sure that doppio can find it at runtime. With the Node frontend, specify the directories with the -Xnative-classpath option, delimited by colons (:), much like the regular classpath. The browser frontend has a similar configuration option.
It's fairly straightforward to write a doppio native method that invokes asynchronous browser functionality:
- Put the doppio thread invoking your native method into the
ASYNC_WAITINGstate. - Perform the desired operation.
- When your operation completes, invoke the doppio thread's
asyncReturnfunction with the function's return value (if any), which will resume the thread with the specified return value.
Here's an example that 'sleeps' the thread for 50 milliseconds:
// Native methods can pull in doppio modules using 'require' and the path to the module.
// Even if you already have RequireJS or some other module system included on the page,
// this will work as we evaluate your native methods in an environment with our own
// `require` function.
var enums = require('./src/enums');
registerNatives({
'classes/util/Test': {
'sleep50()V': function(thread) {
// This informs the thread to ignore the return value from this JavaScript function.
thread.setStatus(enums.ThreadStatus.ASYNC_WAITING);
// Sleep for 50 milliseconds.
setTimeout(function() {
// Wake up the thread by returning from this method.
thread.asyncReturn();
}, 50);
}
}
});
While this doppio thread is waiting for your sleep operation to complete, other doppio threads may run.
If the above instructions aren't enough to get your native running, try looking at other native method implementations in src/natives/*.ts.
These contain examples of how to do all sorts of tricky things, like getting/setting fields on objects,
converting between JVM and Javascript strings, working with long integers, and more.
Our demo frontend certainly isn't perfect, and different applications might need different functionality. doppio was designed to be decoupled from its interface, but it currently needs a bit of care to integrate with a new client application.
Follow the instructions in README.md to clone and build a release version of doppio. You will need the following files from the build/release directory when you deploy doppio:
-
./doppio.js: The actual fully-built doppio library. -
./src/natives/*.js: Doppio's native methods. It dynamically loads these from the file system at runtime, much likeclassfiles. -
./vendor/classes: The Java Class Library. -
./vendor/java_home: Doppio's Java Home. Some of the Java Class Library classes rely on files in here (for e.g. locale information).
doppio requires the in-browser filesystem, BrowserFS. Include it on your webpage before doppio.js, and ensure that you have the files described above included somewhere in the file system (most likely, you will set up an XmlHttpRequest file system that pulls in files via downloads).
For example, you might do the following to set up temporary storage at /tmp, system files at /sys, and writable storage at /home:
<script type="text/javascript" src="browserfs.min.js"></script>
<script type="text/javascript">
// Wrap in a closure; don't pollute the global namespace.
(function() {
var mfs = new BrowserFS.FileSystem.MountableFileSystem(),
fs = BrowserFS.BFSRequire('fs');
BrowserFS.initialize(mfs);
// Temporary storage.
mfs.mount('/tmp', new BrowserFS.FileSystem.InMemory());
mfs.mount('/home', new BrowserFS.FileSystem.LocalStorage());
// The first argument is the relative URL to your listings file generated by the BrowserFS XHR
// listings tool. In this example, the URL is <thiswebpage>/browser/listings.json
mfs.mount('/sys', new BrowserFS.FileSystem.XmlHttpRequest('browser/listings.json', ''));
})();
</script>
First, make sure you include doppio.js on your webpage:
<script type="text/javascript" src="doppio.js"></script>
When you want to invoke the JVM, you can do so through a command-line style interface, or through a more traditional JavaScript interface. We'll describe the JavaScript interface first.
new doppio.JVM({
// Path to the Java Class Library (JCL), e.g. /sys/vendor/classes
jclPath: string,
// Non-JCL paths on the class path, e.g. [/path/to/my/classes]
classpath: string[],
// Path to JAVA_HOME, e.g. /sys/vendor/java_home
javaHomePath: string,
// Path where we can extract JAR files, e.g. /tmp
extractionPath: string,
// Paths where native methods are located, e.g. [/sys/src/natives, /path/to/my/natives]
nativeClasspath: string[]
}, function(err, jvmObject) {
// Called once initialization completes.
});
While it's weird to have a callback in a constructor, the above should be fairly straightforward once you have the BrowserFS file system set up properly.