This is the mail archive of the
kawa@sourceware.org
mailing list for the Kawa project.
How to serialize/externalize Scheme procedures
- From: oliver dot flasch at cs dot uni-dortmund dot de
- To: kawa at sources dot redhat dot com
- Date: Sun, 25 Mar 2007 00:31:14 +0100 (MET)
- Subject: How to serialize/externalize Scheme procedures
- Full-name:
Hi,
I need to serialize procedures (and later closures) via XStream
(http://xstream.codehaus.org/) at the Kawa toplevel to save them to disk
and to transfer them over a network.
In order to get this to work, I modified gnu.bytecode.ArrayClassLoader
(from kawa-1.9.1.tar.gz, source code attached) to keep the compiled
bytecodes, which seems to work fine.
I then serialized the module class of a simple procedure "foo" to XML
(at the toplevel):
# (define (foo a) (+ a 42)) ; a simple procedure
# (define module-class (invoke foo:module 'getClass)) ; the procedure's
module class ("atEvalLevel")
# (define module-classloader (invoke module-class 'getClassLoader))
# (module-class:foo 3) ; => 45, works OK
# (export-to-xml (module-classloader:getBytecodeMap)
"/home/oflasch/bcm.xml") ; serialize the "bytecode map" of my modified
ArrayClassLoader
...and finally tried to de-serialize the module class again, which
failed quite miserably:
# (define acl
(make <gnu.bytecode.ArrayClassLoader>
((<java.lang.Thread>:currentThread):getContextClassLoader)
(import-from-xml "/home/oflasch/bcm.xml"))) ; make a new
ArrayClassLoader to load the de-serialized bytecodes
# (define imported-class (acl:loadClass (make <java.lang.String>
"atEvalLevel")))
# imported-class:foo ; => java.lang.Object
atEvalLevel.foo(java.lang.Object), OK, the "foo" method seems to be
there
# (make imported-class) ; throws an
java.lang.ExceptionInInitializerError
# (imported-class:foo 1) ; always throws an
java.lang.NoClassDefFoundError
The ExceptionInInitializerError gave me the following stack trace:
<StackTrace>
java.lang.ExceptionInInitializerError
(message:) java.lang.ExceptionInInitializerError
(caused by...)
java.lang.ExceptionInInitializerError
(message:) null
(caused by...)
java.lang.NullPointerException
(message:) null
gnu.expr.Compilation.findForImmediateLiterals(Compilation.java:2826)
gnu.expr.Compilation.setupLiterals(Compilation.java:2781)
atEvalLevel.<clinit>(<string>)
sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
java.lang.reflect.Constructor.newInstance(Constructor.java:494)
gnu.expr.PrimProcedure.apply(PrimProcedure.java:238)
gnu.mapping.CallContext.runUntilDone(CallContext.java:251)
gnu.mapping.CallContext.runUntilValue(CallContext.java:315)
gnu.kawa.reflect.Invoke.applyN(Invoke.java:199)
gnu.kawa.reflect.Invoke.apply(Invoke.java:98)
gnu.mapping.CallContext.runUntilDone(CallContext.java:251)
gnu.expr.ModuleExp.evalModule(ModuleExp.java:296)
kawa.lang.Eval.evalBody(Eval.java:99)
kawa.lang.Eval.evalBody(Eval.java:42)
kawa.standard.Scheme.eval(Scheme.java:847)
kawa.standard.Scheme.eval(Scheme.java:830)
</StackTrace>
I must confess that I'm still not very comfortable with Kawa's
internals, although the Website and especially Per Bothner's "Kawa
internals" papers have been a great help. I would of cause be happy to
contribute my code, if I get the procedure serialization to work. :)package gnu.bytecode;
import java.util.Hashtable;
import java.net.URL;
/** Load classes from a set of byte arrays.
* @author Per Bothner
*/
public class ArrayClassLoader extends ClassLoader
{
Hashtable bytecodeMap = new Hashtable(100);
transient Hashtable classMap = new Hashtable(100);
/** If non-null, context to use for finding resources. */
URL context;
public ArrayClassLoader ()
{
}
public ArrayClassLoader (ClassLoader parent)
{
super(parent);
}
public ArrayClassLoader (ClassLoader parent, Hashtable bytecodeMap)
{
super(parent);
this.bytecodeMap = bytecodeMap;
}
/** Get base URL to use for finding resources, or null if none is set. */
public URL getResourceContext () { return context; }
/** Set base URL to use for finding resources. */
public void setResourceContext (URL context) { this.context = context; }
/** Load classes from the given byte arrays.
By convention, the classes we manage are named "lambda"+<INTEGER>. */
public ArrayClassLoader (byte[][] classBytes)
{
for (int i = classBytes.length; --i >= 0; )
addClass("lambda" + i, classBytes[i]);
}
public ArrayClassLoader (String[] classNames, byte[][] classBytes)
{
for (int i = classBytes.length; --i >= 0; )
addClass(classNames[i], classBytes[i]);
}
public Hashtable getBytecodeMap()
{
return bytecodeMap;
}
public void addClass(Class clas)
{
classMap.put(clas.getName(), clas);
}
public void addClass(String name, byte[] bytes)
{
bytecodeMap.put(name, bytes);
}
public void addClass (ClassType ctype)
throws java.io.IOException
{
if ((ctype.flags & ClassType.EXISTING_CLASS) != 0)
addClass(ctype. getReflectClass());
else
addClass(ctype.getName(), ctype.writeToArray());
}
protected URL findResource(String name)
{
if (context != null)
{
try
{
URL url = new URL(context, name);
url.openConnection().connect();
return url;
}
catch (Throwable ex)
{
// Fall through ...
}
}
return super.findResource(name);
}
/* #ifdef JAVA2 */
public Class findClass (String name)
/* #else */
// public Class loadClass (String name, boolean resolve)
/* #endif */
throws ClassNotFoundException
{
final Class alreadyLoadedClass = (Class) classMap.get(name);
if (alreadyLoadedClass != null)
return alreadyLoadedClass;
final byte[] bytecodesToLoad = (byte[]) bytecodeMap.get(name);
if (bytecodesToLoad == null)
{
/* #ifdef JAVA2 */
throw new ClassNotFoundException(name);
/* #else */
// clas = Class.forName(name);
/* #endif */
}
// double-locking? is this safe? or needed? FIXME.
final Class loadedClass;
synchronized (this)
{
loadedClass = defineClass (name, bytecodesToLoad, 0, bytecodesToLoad.length);
classMap.put(name, loadedClass);
System.out.println(toString() + ": defined class " + name + " of size " + bytecodesToLoad.length); // TODO
}
return loadedClass;
}
}