Beginning with Rhino 1.5 Release 3 it is possible to serialize JavaScript
objects, including functions and scripts. However, serialization of
code in compilation mode has some significant limitations.. Serialization
provides a way to save the state of an object and write it out to a file
or send it across a network connection.
$ java org.mozilla.javascript.tools.shell.MainHere we see a simple case of a function being serialized to a file and then read into a new instance of Rhino and called.
js> function f() { return 3; }
js> serialize(f, "f.ser")
js> quit()
$ java org.mozilla.javascript.tools.shell.Main
js> f = deserialize("f.ser")
function f() {
return 3;
}
js> f()
3
js>
FileOutputStream fos = new FileOutputStream(filename);
ScriptableOutputStream out = new ScriptableOutputStream(fos, scope);
out.writeObject(obj);
out.close();
Here filename is the file to write to, obj is the object or function to write, and scope is the top-level scope containing obj.
Reading the serialized object back into memory is similarly simple:
FileInputStream fis = new FileInputStream(filename);
ObjectInputStream in = new ScriptableInputStream(fis, scope);
Object deserialized = in.readObject();
in.close();
Again, we need the scope to create our serialization stream class.
So why do we need these specialized stream classes instead of simply using ObjectOutputStream and ObjectInputStream? To understand the answer we must know what goes on behind the scenes when Rhino serializes objects.
out.addExcludedName("Foo");This code will prevent Foo and Foo.prototype from being serialized and will cause references to Foo or Foo.prototype to be resolved to the objects in the new scope upon deserialization. Exceptions will be thrown if Foo or Foo.prototype cannot be found the scopes used in either ScriptableOutputStream or ScriptableInputStream.
out.addExcludedName("Foo.prototype");
$ cat test.js
function f() { return 3; }
serialize(f, "f.ser");
g = deserialize("f.ser");
print(g());
$ java org.mozilla.javascript.tools.shell.Main -opt -1 test.js
3
$ java org.mozilla.javascript.tools.shell.Main test.js
js: uncaught JavaScript exception: java.lang.ClassNotFoundException: c1
The problem is that Java serialization has no built-in way to serialize Java classes themselves. (It might be possible to save the Java bytecodes in an array and then load the class upon deserialization, but at best that would eat up a lot of memory for just this feature.) One way around this is to compile the functions using the jsc tool:
$ cat f.js
function f() { return 3; }
$ java -classpath js.jar org.mozilla.javascript.tools.jsc.Main f.js
$ cat test2.js
loadClass("f");
serialize(f, "f.ser");
g = deserialize("f.ser");
print(g());
$ java -classpath 'js.jar;.' org.mozilla.javascript.tools.shell.Main test2.js
3
Now the function f is compiled to a Java class, but that class is then made available in the classpath so serialization works. This isn't that interesting an example since compiling a function to a class and then loading it accomplishes the same as serializing an interpreted function, but it becomes more relevant if you wish to serialize JavaScript objects that have references to compiled functions.