This is the mail archive of the kawa@sourceware.org mailing list for the Kawa project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

How to serialize/externalize Scheme procedures


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;
  }
}

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]