The problem#
Java and JavaScript are insufficiently easily confused.
I had some code I wanted to run on the user's computer and not the
server, and it specifically had to be in Java to reference a library
written in Java. I found1 Doppio, a JVM written in
TypeScript, but it had bitrotted enough that I was having
trouble getting it to run, despite confirming it did what I wanted
using the official demo, which provides an in-browser
console-like interface.
The solution#
I was able to piece together a working simple page using Doppio, modified from the official example. It's in a repository on GitHub and should be straightforward to modify for your needs.
The details#
Official instructions#
Initially, I tried following Doppio's Getting
Started, but grunt release
showed
$ grunt release
Recompiling /home/anyoneeb/workspace/doppio/tasks/find_native_java.ts...
Recompiling /home/anyoneeb/workspace/doppio/tasks/ice-cream.ts...
[…]
Recompiling /home/anyoneeb/workspace/doppio/Grunttasks.ts...
Loading "Gruntfile.js" tasks...ERROR
>> Error: Compilation error: null
>> null
Warning: Task "release" not found. Use --force to continue.
I inspected Grunttasks.ts
and tried to see if the way grunt
specified tasks had changed, but I wasn't getting anywhere.
Modifying the demo#
I decided didn't have to figure out the build system is supposed to
work, since I had the working demo. Unfortunately, the logic of the demo
is in a file app.js
, which is minified. It
does have a source map, so I was able to get a somewhat readable version
of it, but the actual source is not in the repo. On top of that the demo
does a lot of work to make a nice console-like interface, which is not
relevant to just programmatically running some Java code. So, while I
did copy some code out, modifying the demo was a dead end.
Copying files from the demo#
But I still had the problem that I couldn't build my own copy of Doppio.
So I used the web developer tools to find the files the demo was using
and grabbed its copies of doppio.js
and browserfs.min.js
, which the
simple.html
example referenced. I also grabbed
doppio_home.zip
, which, among other things, contained the JRE files
from OpenJDK necessary to actually run a Java program.
Selecting the Doppio home path#
What is BrowserFS?#
Stepping back a bit, in order to make an environment that makes sense
from Java's point of view, Doppio has to emulate a lot of the context
of running as a desktop application that doesn't actually quite apply
in a web browser. Part of that is BrowserFS, a library
that presents a filesystem for the Java programs to use that supports
various backends so the actual files can be in various places that
make sense from a browser perspective like in IndexedDB or on Dropbox.
(Unfortunately, the version used by Droppio is too old to support the
File System API.) In addition to being used for the file system
operations of the Java programs run by Droppio, it's also used to store
the .java
/.class
/.jar
files Droppio acts on and the libraries they
reference including the core Java runtime.
The conclusion of all that is that in order to actually do anything with Droppio, we need a BrowserFS filesystem set up containing the Java runtime libraries and Droppio has to be given the correct path to them.
Setting up BrowserFS#
The Getting Started instructions do
explain how to create a basic filesystem, including setting up the
listings.json
file using make_xhrfs_index
. Since there's no HTTP
request for getting a directory listing, that just dumps the directory
listing to a JSON file, so BrowserFS can have it. Note that means you
need to rerun make_xhrfs_index
after adding or moving a file or
BrowserFS won't know it's there.
I unzipped the doppio_home.zip
file from the demo and pulled out the
vendor
directory, as that's what the instructions said was needed.
BrowserFS console commands#
As it can be confusing what is actually in the BrowserFS filesystem, it
can be useful to inspect it from the web developer tools console. In
order to get access to BrowserFS, load the fs
object (if the script
did not already put it in the global variables):
const fs = BrowserFS.BFSRequire("fs")
Then you can list a directory with fs.readdirSync()
:
fs.readdirSync('/')
(3) ['tmp', 'home', 'sys']
Or read a file with fs.readfileSync()
:
fs.readFileSync('/sys/listings.json').toString()
'{"doppio":{"app.js":null,"browserfs.min.js":null,…'
doppioHomePath
error messages#
If the doppioHomePath
is completely wrong, it will throw an exception
TypeError: Cannot read properties of null (reading 'throwNewException')
and there will be a variable with the Java type name "Ljava/lang/Thread;"
.
Since it fails throwing an exception, nothing will be output, so it
won't even be obvious anything happening other than it taking a long
time to load unless the web developer tools are open.
But following the Getting Started instructions
to point doppioHomePath
to a directory containing vendor/java_home
,
it prints the following message to the console:
Error constructing JVM: Failed to initialize Ljava/lang/reflect/Constructor;
Referencing doppio_home.zip
#
Since I knew it worked in the demo, in attempt to recreate the
conditions of the demo as closely as possible, I was able to fix that
error by referencing the entire doppio_home.zip
instead of taking just
the vendor/
directory.
BrowserFS has a backend for loading a ZIP file and making it look like a
directory, which is used by the official demo to load doppio_home.zip
,
so I figured it would be simplest to just copy that exactly. One minor
complication is that then the filesystem isn't initialized until after
the request to load that ZIP file has completed, but that's easy to deal
with.
The code to load a ZIP file looks like
const mfs = new BrowserFS.FileSystem.MountableFileSystem();
const buffer = BrowserFS.BFSRequire("buffer").Buffer;
const t = new XMLHttpRequest();
t.open("GET", "doppio_home.zip");
t.responseType = "arraybuffer";
t.addEventListener("load", function(e) {
mfs.mount("/doppio_home",
new BrowserFS.FileSystem.ZipFS(
new buffer(t.response)));
// … then actually run Doppio now that it's loaded.
});
t.send();
Then the correct doppioHomePath
is /doppio_home
.
Running a program#
doppio_home.zip
includes a Java compiler, which the example uses to
compile and run the simple program App.java
(it has a
string of that file and writes to it /tmp/App.java
). Here's the code
to actually compile and run the program:
Doppio.VM.CLI(
['-classpath', '/doppio_home',
'classes.util.Javac', '/tmp/App.java'],
{
doppioHomePath: '/doppio_home'
}, function(exitCode) {
if (exitCode === 0) {
Doppio.VM.CLI(
['-classpath', '/tmp', 'App'],
{
doppioHomePath: '/doppio_home'
});
}
});
Loading additional files#
I added to the example code that I copied out of the demo to load files from the user's computer into the BrowserFS filesystem, as most likely the reason to be running in the browser is to get access to some data on the user's computer that you don't want to leave their computer. You could modify that code to run a Doppio command after loading the files or add a separate button to do so.
-
Specifically, I remembered attending the talk at PLDI 2014. ↩
Comments
Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.
There are no comments yet.