gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] r18048 - in gnunet-java/src/org: . gnunet gnunet/construct


From: gnunet
Subject: [GNUnet-SVN] r18048 - in gnunet-java/src/org: . gnunet gnunet/construct gnunet/messages grothoff
Date: Mon, 7 Nov 2011 22:50:41 +0100

Author: grothoff
Date: 2011-11-07 22:50:41 +0100 (Mon, 07 Nov 2011)
New Revision: 18048

Added:
   gnunet-java/src/org/gnunet/construct/
   gnunet-java/src/org/gnunet/construct/Construct.java
   gnunet-java/src/org/gnunet/construct/ConstructTest.java
   gnunet-java/src/org/gnunet/construct/FixedSizeByteArray.java
   gnunet-java/src/org/gnunet/construct/TotalSize.java
   gnunet-java/src/org/gnunet/construct/VariableSizeByteArray.java
   gnunet-java/src/org/gnunet/construct/ZeroTerminatedUtf8String.java
   gnunet-java/src/org/gnunet/construct/uint16_t.java
   gnunet-java/src/org/gnunet/construct/uint32_t.java
   gnunet-java/src/org/gnunet/construct/uint8_t.java
   gnunet-java/src/org/gnunet/messages/
   gnunet-java/src/org/gnunet/messages/MessageHeader.java
   gnunet-java/src/org/grothoff/
   gnunet-java/src/org/grothoff/Runabout.java
Log:
message deserialization fun

Added: gnunet-java/src/org/gnunet/construct/Construct.java
===================================================================
--- gnunet-java/src/org/gnunet/construct/Construct.java                         
(rev 0)
+++ gnunet-java/src/org/gnunet/construct/Construct.java 2011-11-07 21:50:41 UTC 
(rev 18048)
@@ -0,0 +1,164 @@
+package org.gnunet.construct;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+
+import org.grothoff.Runabout;
+
+/**
+ * A version of Python's construct library for Java.
+ * 
+ * @author Christian Grothoff
+ * 
+ * @TODO serialization runabout, size computation runabout, some more basic
+ *       annotations (int16, int32, uint64, etc.)
+ * @TODO error handling (exceptions?), documentation
+ * @TODO performance evaluation
+ */
+public class Construct {
+
+       /**
+        * Given a byte array with a message, parse it into an object of type 
c. The
+        * fields of the class are expected to be annotated with annotations 
from
+        * the construct package.
+        * 
+        * @param data
+        *            serialized binary object data
+        * @param offset
+        *            where the message starts in data
+        * @param c
+        *            desired object type to return
+        * @return instance of the desired object type
+        * @throws RuntimeException
+        *             (ugh)
+        */
+       public static <T> T parse_as(byte[] data, int offset, Class<T> c) {
+               Deserializer<T> des = new Deserializer<T>(data, offset, c);
+               Field[] fs = c.getFields();
+               for (Field f : fs) {
+                       Annotation[] as = f.getAnnotations();
+                       des.setField(f);
+                       for (Annotation a : as)
+                               des.visitAppropriate(a);
+               }
+               return des.o;
+       }
+
+       /**
+        * Serialize a given message object to a binary byte array. The fields 
of
+        * the object are expected to be annotated with annotations from the
+        * construct package.
+        * 
+        * @param o
+        *            object to serialize
+        * @param data
+        *            where to write the binary object data
+        * @param offset
+        *            where to start writing data
+        * @return number of bytes written to data, -1 on error
+        */
+       public static int write_to(Object o, byte[] data, int offset) {
+               return -1;
+       }
+
+       /**
+        * Compute the size of a serialized message object.
+        * 
+        * @param o
+        *            object to serialize
+        * @return number of bytes required, -1 on error
+        */
+       public static int compute_binary_size(Object o) {
+               return -1;
+       }
+
+       public static class Deserializer<T> extends Runabout {
+
+               final byte[] data;
+
+               int offset;
+
+               int total_size = -1;
+
+               final T o;
+
+               Field f;
+
+               private Deserializer(byte[] data, int offset, Class<T> c) {
+                       this.data = data;
+                       this.offset = offset;
+                       try {
+                               this.o = c.newInstance();
+                       } catch (IllegalAccessException iae) {
+                               throw new RuntimeException(iae);
+                       } catch (InstantiationException ie) {
+                               throw new RuntimeException(ie);
+                       }
+               }
+
+               private void setField(Field f) {
+                       this.f = f;
+               }
+
+               public void visitDefault(Object o) {
+                       for (Annotation a : o.getClass().getAnnotations())
+                               visitAppropriate(a);
+               }
+
+               public void visit(TotalSize ts) {
+                       // FIXME: can we be sure that this annotation is 
processed last
+                       // (we can assume that it is added as the last 
annotation to the
+                       // respective field, but can the compiler/runtime 
reorder
+                       // annotations?)
+                       try {
+                               this.total_size = f.getInt(o);
+                       } catch (IllegalAccessException iae) {
+                               throw new RuntimeException(iae);
+                       }
+               }
+
+               public void visit(uint8_t ui) {
+                       try {
+                               f.setInt(o, data[offset]);
+                               // FIXME: this is wrong (not unsigned), just for
+                               // illustration...
+                               offset++;
+                       } catch (IllegalAccessException iae) {
+                               throw new RuntimeException(iae);
+                       }
+               }
+
+               public void visit(uint16_t ui) {
+                       try {
+                               f.setInt(o, data[offset] + data[offset] << 8);
+                               // FIXME: this is wrong (not proper 
deserialization),
+                               // just for illustration...
+                               offset += 2;
+                       } catch (IllegalAccessException iae) {
+                               throw new RuntimeException(iae);
+                       }
+               }
+
+               public void visit(FixedSizeByteArray fsba) {
+                       int length = fsba.length();
+                       Class ac = f.getClass();
+                       Class componentType = ac.getComponentType();
+                       Object array = Array.newInstance(componentType, length);
+                       for (int i = 0; i < length; i++) {
+                               Object element = parse_as(data, offset, 
componentType);
+                               offset += compute_binary_size(element);
+                               Array.set(array, i, element);
+                       }
+                       try {
+                               f.set(o, array);
+                       } catch (IllegalAccessException iae) {
+                               throw new RuntimeException(iae);
+                       }
+               }
+
+               // FIXME: many more 'visit' methods should be added here...
+
+       }
+
+}

Added: gnunet-java/src/org/gnunet/construct/ConstructTest.java
===================================================================
--- gnunet-java/src/org/gnunet/construct/ConstructTest.java                     
        (rev 0)
+++ gnunet-java/src/org/gnunet/construct/ConstructTest.java     2011-11-07 
21:50:41 UTC (rev 18048)
@@ -0,0 +1,16 @@
+package org.gnunet.construct;
+
+import org.gnunet.messages.MessageHeader;
+import org.junit.Test;
+
+public class ConstructTest {
+
+       @Test
+       public void testParse_as() {
+               byte[] data = { 3, 4, 5, 6 };
+               MessageHeader mh = Construct.parse_as(data, 0, 
MessageHeader.class);
+               assert mh.size == 1536;
+               assert mh.type == 2560;
+       }
+
+}

Added: gnunet-java/src/org/gnunet/construct/FixedSizeByteArray.java
===================================================================
--- gnunet-java/src/org/gnunet/construct/FixedSizeByteArray.java                
                (rev 0)
+++ gnunet-java/src/org/gnunet/construct/FixedSizeByteArray.java        
2011-11-07 21:50:41 UTC (rev 18048)
@@ -0,0 +1,12 @@
+package org.gnunet.construct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
address@hidden(RetentionPolicy.RUNTIME)
address@hidden(ElementType.FIELD)
+public @interface FixedSizeByteArray {
+       int length();
+}

Added: gnunet-java/src/org/gnunet/construct/TotalSize.java
===================================================================
--- gnunet-java/src/org/gnunet/construct/TotalSize.java                         
(rev 0)
+++ gnunet-java/src/org/gnunet/construct/TotalSize.java 2011-11-07 21:50:41 UTC 
(rev 18048)
@@ -0,0 +1,13 @@
+package org.gnunet.construct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
address@hidden(RetentionPolicy.RUNTIME)
address@hidden(ElementType.FIELD)
+public @interface TotalSize {
+
+}

Added: gnunet-java/src/org/gnunet/construct/VariableSizeByteArray.java
===================================================================
--- gnunet-java/src/org/gnunet/construct/VariableSizeByteArray.java             
                (rev 0)
+++ gnunet-java/src/org/gnunet/construct/VariableSizeByteArray.java     
2011-11-07 21:50:41 UTC (rev 18048)
@@ -0,0 +1,8 @@
+package org.gnunet.construct;
+
+public @interface VariableSizeByteArray {
+       // set to empty string to make the array extend to fill the available 
space
+       String lengthField();
+       
+       String memberType();
+}

Added: gnunet-java/src/org/gnunet/construct/ZeroTerminatedUtf8String.java
===================================================================
--- gnunet-java/src/org/gnunet/construct/ZeroTerminatedUtf8String.java          
                (rev 0)
+++ gnunet-java/src/org/gnunet/construct/ZeroTerminatedUtf8String.java  
2011-11-07 21:50:41 UTC (rev 18048)
@@ -0,0 +1,9 @@
+package org.gnunet.construct;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
address@hidden(RetentionPolicy.RUNTIME)
+public @interface ZeroTerminatedUtf8String {
+
+}

Added: gnunet-java/src/org/gnunet/construct/uint16_t.java
===================================================================
--- gnunet-java/src/org/gnunet/construct/uint16_t.java                          
(rev 0)
+++ gnunet-java/src/org/gnunet/construct/uint16_t.java  2011-11-07 21:50:41 UTC 
(rev 18048)
@@ -0,0 +1,18 @@
+package org.gnunet.construct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * 16-bit @UnsignedInteger in network byte order.
+ * 
+ * @author grothoff
+ */
address@hidden(RetentionPolicy.RUNTIME)
address@hidden(ElementType.FIELD)
+public @interface uint16_t {
+
+}

Added: gnunet-java/src/org/gnunet/construct/uint32_t.java
===================================================================
--- gnunet-java/src/org/gnunet/construct/uint32_t.java                          
(rev 0)
+++ gnunet-java/src/org/gnunet/construct/uint32_t.java  2011-11-07 21:50:41 UTC 
(rev 18048)
@@ -0,0 +1,17 @@
+package org.gnunet.construct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 16-bit @UnsignedInteger in network byte order.
+ * 
+ * @author grothoff
+ */
address@hidden(RetentionPolicy.RUNTIME)
address@hidden(ElementType.FIELD)
+public @interface uint32_t {
+
+}

Added: gnunet-java/src/org/gnunet/construct/uint8_t.java
===================================================================
--- gnunet-java/src/org/gnunet/construct/uint8_t.java                           
(rev 0)
+++ gnunet-java/src/org/gnunet/construct/uint8_t.java   2011-11-07 21:50:41 UTC 
(rev 18048)
@@ -0,0 +1,18 @@
+package org.gnunet.construct;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * 8-bit @UnsignedInteger in network byte order.
+ * 
+ * @author grothoff
+ */
address@hidden(RetentionPolicy.RUNTIME)
address@hidden(ElementType.FIELD)
+public @interface uint8_t {
+
+}

Added: gnunet-java/src/org/gnunet/messages/MessageHeader.java
===================================================================
--- gnunet-java/src/org/gnunet/messages/MessageHeader.java                      
        (rev 0)
+++ gnunet-java/src/org/gnunet/messages/MessageHeader.java      2011-11-07 
21:50:41 UTC (rev 18048)
@@ -0,0 +1,15 @@
+package org.gnunet.messages;
+
+import org.gnunet.construct.TotalSize;
+import org.gnunet.construct.uint16_t;
+
+public class MessageHeader {
+
+       @uint16_t
+       @TotalSize
+       public int size;
+       
+       @uint16_t
+       public int type;
+       
+}

Added: gnunet-java/src/org/grothoff/Runabout.java
===================================================================
--- gnunet-java/src/org/grothoff/Runabout.java                          (rev 0)
+++ gnunet-java/src/org/grothoff/Runabout.java  2011-11-07 21:50:41 UTC (rev 
18048)
@@ -0,0 +1,531 @@
+/**
+ * (C) 2002, 2003, 2004, 2005, 2006 Christian Grothoff
+ * <p>
+ * The Runabout is free software; you can redistribute it and/or modify it 
under
+ * the terms of the GNU General Public License as published by the Free 
Software
+ * Foundation; either version 2, or (at your option) any later version. The
+ * Runabout is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License along with
+ * the Runabout; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * 
+ * This software is also licensed under the Eclipse Public License v1.0 
+ * available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * @file org.grothoff.Runabout.java
+ */
+package org.grothoff;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+
+/**
+ * Runabout is a fast implementation of the Walkabout which is a variant of the
+ * Visitor Pattern that does not require an accept method and uses reflection
+ * instead.
+ * <p>
+ * An instance of Runabout is able to walk over an arbitrary object graph using
+ * visit methods which take arguments of the specific type of the object to
+ * visit. For each node in the object graph the Runabout invokes the most
+ * appropriate visit method.
+ * <p>
+ * Using the Runabout typically involves subclassimg Runabout and adding a
+ * couple of visit methods. The Runabout provides a 'visitAppropriate' method
+ * which will invoke the most appropriate visit method of the current Runabout
+ * instance. If no visit method is applicable, visitAppropriate calls
+ * visitDefault() which, if not overriden, throws an exception.
+ * <p>
+ * The elements of the object graph typically extend the Element class, which
+ * provides a generic way to quickly invoke the Runabout on all the fields of
+ * the Element.
+ * <p>
+ * Note that the Runabout uses dynamic code generation and dynamic loading in
+ * order to be quickly able to invoke the appropriate visit methods. To make 
the
+ * dynamic code generation fast, the code inlines parts of Java class-files in
+ * binary form (ugly!).<br>
+ * A per-thread Cache is used to speed-up the creation of the Runabout by
+ * caching reflection, code creation and dynamic loading operations.
+ * <p>
+ * <bf>Restrictions:</bf> Java semantics require:
+ * <ul>
+ * <li>all subclasses must be public, sadly this also means that <strong>you
+ * can not use an anonymous inner class</strong> (!)</li>
+ * <li>the types to all arguments of visit methods must be public</li>
+ * <li>all visit methods must be public (!)</li>
+ * </ul>
+ * Otherwise the visitor will die with an IllegalAccessError during execution.
+ * <p>
+ * 
+ * @version 5.0
+ * @author Christian Grothoff
+ */
+public class Runabout {
+
+    /**
+     * Singleton of the Runabout.Cache. We cache reflective information per VM;
+     * this avoids the need for repeated reflection, code generation and
+     * dispatching map updates.
+     */
+    private static final Runabout.Cache cache_ = new Runabout.Cache();
+
+    /**
+     * map_ contains a mapping from a class to the appropriate visit method.
+     * Note that at the beginning, map_ only contains the explicit mappings as
+     * given by the visit methods. Over time, map_ will be amended to also
+     * contain direct mappings for subclasses to the appropriate visit methods
+     * if they are used.
+     */
+    private final HashMap<Class, Runabout.Code> map_;
+
+    /**
+     * Code to invoke if no visitor is found (used to avoid scanning the
+     * hierarchy again and again).
+     */
+    private final Code noCode = new NoCode();
+
+    /**
+     * Create a Runabout.
+     */
+    public Runabout() {
+        map_ = cache_.get(this);
+    }
+
+    /**
+     * Call the appropriate visit method. Use this method if you are visiting a
+     * graph of objects (no primitives).
+     * 
+     * @param o the object to visit
+     */
+    public void visitAppropriate(Object o) {
+        getAppropriateCodeInternal(o.getClass()).visit(this, o);
+    }
+
+    /**
+     * Obtain the appropriate code to call for class c. The method either
+     * obtains the code quickly from the code map (fast path) or by calling the
+     * lookup method getAppropriateCode.
+     * 
+     * @return the code, never returns null
+     */
+    private Code getAppropriateCodeInternal(Class c) {
+        synchronized (cache_) {
+            Code co = map_.get(c);
+            if (co != null)
+                return co;
+            co = getAppropriateCode(c);
+            if (co == null)
+                co = noCode;
+            map_.put(c, co);
+            return co;
+        }
+    }
+
+    /**
+     * Find the appropriate Code to call in the map. If no code is found, 
return
+     * null. This lookup strategy first attempts to find a visit method defined
+     * for the parent classes of c. If no such method exists, it attempts to
+     * find an unambiguous visit method matching any interface transitively
+     * implemented by c. If that does not exist either, null is returned. If
+     * only an ambiguous visit method exists, an exception is raised.
+     * 
+     * @param c the class for which to find the code
+     * @return the code to run, or null if no code was found
+     * @throws RunaboutException if the lookup would be ambiguous
+     */
+    private Code getAppropriateCode(Class c) {
+        if (c.isArray()) {
+            // uh uh, array subtyping in action...
+            int dims = 1;
+            Class component = c.getComponentType();
+            while (component.isArray()) {
+                dims++;
+                component = component.getComponentType();
+            }
+            Class superComp = component.getSuperclass();
+            while (superComp != null) {
+                Code co = map_.get(Array.newInstance(superComp, new 
int[dims]).getClass());
+                if (co != null)
+                    return co;
+                superComp = superComp.getSuperclass();
+            }
+            // now try subtyping with multi-dimensional Object[]
+            // (see crazy runabout test).
+            Class objectClass = c.getSuperclass();
+            while (dims > 1) {
+                Code co = map_.get(Array.newInstance(objectClass, new 
int[dims]).getClass());
+                if (co != null)
+                    return co;
+                dims--;
+            }
+        }
+        Class cl = c.getSuperclass();
+        while (cl != null) {
+            Code co = map_.get(cl);
+            if (co != null)
+                return co;
+            cl = cl.getSuperclass();
+        }
+        return getAppropriateCode_ifc(c, c);
+    }
+
+    /**
+     * Find the appropriate Code to call in the map. If no code is found, 
return
+     * null.
+     * 
+     * @param c the class for which to find the code
+     * @param cl the class where to start looking from
+     * @return the code to run, or null if no code was found
+     */
+    private Code getAppropriateCode_ifc(Class c, Class cl) {
+        Code co = null;
+        while (cl != null) {
+            Class[] ifc = cl.getInterfaces();
+            for (int i = 0; i < ifc.length; i++) {
+                Code r = map_.get(ifc[i]);
+                if (r != null) {
+                    if ((co != null) && (r != co))
+                        throw new RunaboutException("Ambiguous resolution for 
visit call to "
+                                + c + " in " + this.getClass().getName());
+                    co = r;
+                }
+            }
+            for (int i = 0; i < ifc.length; i++) {
+                Code r = getAppropriateCode_ifc(c, ifc[i]);
+                if (r != null) {
+                    if ((co != null) && (r != co))
+                        throw new RunaboutException("Ambiguous resolution for 
visit call to "
+                                + c + " in " + this.getClass().getName());
+                    co = r;
+                }
+            }
+            cl = cl.getSuperclass();
+        }
+        return co;
+    }
+
+    /**
+     * Generate the initial version of the map that maps classes to Code to 
call
+     * the appropriate visit method.
+     */
+    final HashMap<Class, Runabout.Code> makeMap() {
+        // get number of methods
+        int size = 0;
+        Class me = this.getClass();
+        while (me != null) {
+            size += me.getDeclaredMethods().length;
+            me = me.getSuperclass();
+        }
+        // create map with slight over-estimate
+        HashMap<Class, Runabout.Code> result = new HashMap<Class, 
Runabout.Code>(size * 2);
+        // for all methods - create call code, put
+        me = this.getClass();
+        while (me != null) {
+            Method[] methods = me.getDeclaredMethods();
+            for (int i = 0; i < methods.length; i++) {
+                Method m = methods[i];
+                if ((m.getName().equals("visit"))
+                        && (!Modifier.isStatic(m.getModifiers()))) {
+                    Class[] args = m.getParameterTypes();
+                    if (args.length != 1)
+                        throw new RunaboutException("Invalid number of 
arguments for Runabout in method "
+                                + m);
+                    Class viC = args[0];
+                    if (result.get(viC) != null)
+                        continue;
+                    Code co = null;
+                    co = makeCode(viC);
+                    if (co == null)
+                        throw new RunaboutException("Could not create/load 
dynamic code!");
+                    result.put(viC, co);
+                }
+            }
+            me = me.getSuperclass();
+        }
+        return result;
+    }
+
+    /**
+     * Create the code to invoke a visit method.
+     * 
+     * @param cache the cache to use (for dynamic loading)
+     * @param c the type of the argument to the visit method
+     */
+    private Code makeCode(Class c) {
+        byte[] myName // Lovm/util/RunaboutExample; substitute
+        = canonicalName(getClass(), false);
+        final int myNameLen = myName.length;
+        final int myNameLenM2 = myNameLen - 2;
+        byte[] cName // Ljava/lang/String; substitute
+        = canonicalName(c, false);
+        byte[] cNameCast = canonicalName(c, true);
+        final int cNameLen = cName.length;
+        final int cNameLenCast = cNameCast.length - 2;
+        byte[] code = new byte[genCodeTemplate.length - 62 + myNameLenM2
+                + cNameLenCast + cNameLen];
+
+        // Build code by substituting a few strings in genCodeTemplate.
+        // 117-145: org/grothoff/RunaboutExample => myName
+        // 148-164: java/lang/String => cName
+        // 192-200: XXXXXXXX => number
+        // 250-271: (Ljava/lang/String;)V => "("+cName+")V"
+
+        System.arraycopy(genCodeTemplate, 0, code, 0, 115);
+        code[115] = (byte) ((myNameLenM2) >> 8);
+        code[116] = (byte) ((myNameLenM2) & 255);
+        System.arraycopy(myName, 1, code, 117, myNameLenM2);
+        code[117 + myNameLenM2] = 1; // tag for string
+        code[118 + myNameLenM2] = (byte) ((cNameLenCast) >> 8);
+        code[119 + myNameLenM2] = (byte) ((cNameLenCast) & 255);
+        System.arraycopy(cNameCast, 1, code, 120 + myNameLenM2, cNameLenCast);
+        System.arraycopy(genCodeTemplate, 164, code, 120 + myNameLenM2
+                + cNameLenCast, 248 - 164);
+        code[120 + myNameLenM2 + cNameLenCast + 248 - 164] = (byte) ((cNameLen 
+ 3) >> 8);
+        code[120 + myNameLenM2 + cNameLenCast + 249 - 164] = (byte) ((cNameLen 
+ 3) & 255);
+        code[120 + myNameLenM2 + cNameLenCast + 250 - 164] = (byte) '(';
+        System.arraycopy(cName, 0, code, 120 + myNameLenM2 + cNameLenCast + 251
+                - 164, cNameLen);
+        code[120 + myNameLenM2 + cNameLenCast + 250 - 164 + cNameLen + 1] = 
(byte) ')';
+        code[120 + myNameLenM2 + cNameLenCast + 250 - 164 + cNameLen + 2] = 
(byte) 'V';
+        System.arraycopy(genCodeTemplate,
+                         271,
+                         code,
+                         120 + myNameLenM2 + cNameLenCast + 250 - 164
+                                 + cNameLen + 3,
+                         genCodeTemplate.length - 271);
+        return cache_.loadCode(code, 120 + myNameLenM2 + cNameLenCast + 192
+                - 164);
+    }
+
+    /**
+     * Get the class name in canonical form.
+     * 
+     * @param cls the class, may not be primitive
+     * @return the ovm name, following the convention of
+     *         <code>java.util.Class.forName</code> according to the JavaDoc
+     *         specification (JDK 1.2.2/1.3/1.4) which differs from the actual
+     *         implementation in both SUN and IBM VMs.
+     */
+    private static byte[] canonicalName(Class cls, boolean forCast) {
+        String cname = cls.getName();
+        try {
+            byte[] utf = cname.getBytes("UTF-8");
+            int len = utf.length; // may be > cname.length()!
+            if ((cname.charAt(0) != '[') || (forCast)) {
+                byte[] ret = new byte[len + 2];
+                ret[0] = (byte) 'L';
+                System.arraycopy(utf, 0, ret, 1, len);
+                ret[len + 1] = (byte) ';';
+                for (int i = len; i > 0; i--)
+                    if (ret[i] == (byte) '.')
+                        ret[i] = (byte) '/';
+                return ret;
+            }
+            byte[] ret = utf;
+            for (int i = len - 1; i >= 0; i--)
+                if (ret[i] == (byte) '.')
+                    ret[i] = (byte) '/';
+            return ret;
+        } catch (UnsupportedEncodingException uee) {
+            throw new RunaboutException("UTF8 encoding not supported!?: " + 
uee);
+        }
+    }
+
+    /**
+     * The Runabout.Cache is essentially a per-class cache of the internal
+     * constant state of a Runabout instance. It contains the generated code to
+     * quicly invoke the appropriate visit methods.
+     * 
+     * @author Christian Grothoff
+     */
+    static final class Cache {
+
+        /**
+         * ClassLoader to use to load the code.
+         */
+        private final ClassLoader loader_;
+
+        /**
+         * Last name used by the class loader.
+         */
+        private final byte[] lastName_;
+
+        /**
+         * Mapping of classes to Maps.
+         */
+        private final HashMap<Class, HashMap<Class, Runabout.Code>> cachemap_;
+
+        /**
+         * Code that the loader should use.
+         */
+        byte[] code;
+
+        /**
+         * Create the Cache.
+         */
+        Cache() {
+            loader_ = new ClassLoader() {
+                public Class<?> loadClass(String name)
+                    throws ClassNotFoundException {
+                    if (name == "Code")
+                        return defineClass(null, code, 0, code.length);
+                    return super.loadClass(name);
+                }
+            };
+            cachemap_ = new HashMap<Class, HashMap<Class, Runabout.Code>>();
+            lastName_ = new byte[8];
+            for (int i = 0; i < 8; i++)
+                lastName_[i] = (byte) '0';
+        }
+
+        /**
+         * Create a class from the given bytecode. Since classes loaded by the
+         * same Loader must have a unique name, this method patches the 
bytecode
+         * at the given offset, changing the next 8 characters to a unique Java
+         * classname.
+         * 
+         * @param byteCode the bytecode of the class which must describe a 
class
+         *        of type 'Code'. The class must contain a sequence XXXXXXXX at
+         *        offset xIdx where the classname is to be patched
+         * @param xIdx the index of the XXXXXXXX sequence
+         * @return an instance of the loaded class, null on error
+         * @throws ArrayIndexOutOfBoundsException if more than 62<sup>8</sup>
+         *         classes are loaded :-)
+         * @throws RunaboutException if there are problems with dynamic loading
+         *         of the byteCode
+         */
+        Code loadCode(byte[] byteCode, int xIdx) {
+            boolean overflow = true;
+            int index = 7;
+            while (overflow) {
+                overflow = false;
+                lastName_[index]++;
+                if (lastName_[index] == (byte) ('9' + 1))
+                    lastName_[index] = (byte) 'A';
+                if (lastName_[index] == (byte) ('Z' + 1))
+                    lastName_[index] = (byte) 'a';
+                if (lastName_[index] == (byte) ('z' + 1)) {
+                    lastName_[index] = (byte) '0';
+                    overflow = true;
+                    index--;
+                }
+            }
+            System.arraycopy(lastName_, 0, byteCode, xIdx, 8);
+            code = byteCode;
+
+            Code co = null;
+            try {
+                co = (Code) loader_.loadClass("Code").newInstance();
+            } catch (InstantiationException ie) {
+                throw new RunaboutException(ie.toString());
+            } catch (ClassNotFoundException cnfe) {
+                throw new RunaboutException(cnfe.toString());
+            } catch (IllegalArgumentException iae) {
+                throw new RunaboutException(iae.toString());
+            } catch (ClassFormatError cfe) {
+                throw new RunaboutException(cfe.toString());
+            } catch (IllegalAccessException iae) {
+            }
+            code = null; // help GC
+            return co;
+        }
+
+        /**
+         * Obtain a map from the cache.
+         */
+        synchronized HashMap<Class, Runabout.Code> get(Runabout r) {
+            Class c = r.getClass();
+            HashMap<Class, Runabout.Code> map = cachemap_.get(c);
+            if (map == null) {
+                map = r.makeMap();
+                cachemap_.put(c, map);
+            }
+            return map;
+        }
+
+    } // end of Runabout.Cache
+
+    /**
+     * Code is the generic interface that all generated classes implement. It 
is
+     * used to quickly map a given class to the appropriate visit method.
+     * 
+     * @author Christian Grothoff
+     */
+    public static abstract class Code {
+        public Code() {
+        }
+
+        public abstract void visit(Runabout r, Object o);
+    } // end of Runabout.Code
+
+    /**
+     * Implementation of Code that is called if no visit method matches (calls
+     * visitDefault).
+     * 
+     * @author Christian Grothoff
+     */
+    static final class NoCode extends Code {
+        public final void visit(Runabout r, Object o) {
+            r.visitDefault(o);
+        }
+    } // end of Runabout.NoCode
+
+    /**
+     * Override this method to provide a default behavior when no other visit
+     * matches. The Runabout semantics are to search for a visit(X) and if 
there
+     * is no match, call visitDefault(). As usual with the Runabout, visit(X)
+     * looks at classes before interfaces. By default, visitDefault throws an
+     * exception.
+     */
+    protected void visitDefault(Object o) {
+        throw new RunaboutException("No visit method defined in "
+                + this.getClass() + " for " + o.getClass());
+    }
+
+    /**
+     * Generic Exception for problems in the Runabout.
+     * 
+     * @author Christian Grothoff
+     */
+    public static final class RunaboutException extends RuntimeException {
+        RunaboutException(String s) {
+            super(s);
+        }
+    }
+
+    /**
+     * Compile 'GenCodeXXXXXXXX.java' with the option '-g:none' to tell javac
+     * not to include any debugging information. This is the generated class
+     * file.
+     */
+    private final static byte genCodeTemplate[] = { -54, -2, -70, -66, 0, 0, 0,
+            49, 0, 22, 10, 0, 6, 0, 12, 7, 0, 13, 7, 0, 14, 10, 0, 2, 0, 15, 7,
+            0, 16, 7, 0, 18, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40,
+            41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 5, 118, 105, 115, 105,
+            116, 1, 0, 44, 40, 76, 111, 114, 103, 47, 103, 114, 111, 116, 104,
+            111, 102, 102, 47, 82, 117, 110, 97, 98, 111, 117, 116, 59, 76,
+            106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99,
+            116, 59, 41, 86, 12, 0, 7, 0, 8, 1, 0, 28, 111, 114, 103, 47, 103,
+            114, 111, 116, 104, 111, 102, 102, 47, 82, 117, 110, 97, 98, 111,
+            117, 116, 69, 120, 97, 109, 112, 108, 101, 1, 0, 16, 106, 97, 118,
+            97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 12, 0,
+            10, 0, 20, 1, 0, 28, 111, 114, 103, 47, 103, 114, 111, 116, 104,
+            111, 102, 102, 47, 71, 101, 110, 67, 111, 100, 101, 88, 88, 88, 88,
+            88, 88, 88, 88, 7, 0, 21, 1, 0, 26, 111, 114, 103, 47, 103, 114,
+            111, 116, 104, 111, 102, 102, 47, 82, 117, 110, 97, 98, 111, 117,
+            116, 36, 67, 111, 100, 101, 1, 0, 12, 73, 110, 110, 101, 114, 67,
+            108, 97, 115, 115, 101, 115, 1, 0, 21, 40, 76, 106, 97, 118, 97,
+            47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 86,
+            1, 0, 21, 111, 114, 103, 47, 103, 114, 111, 116, 104, 111, 102,
+            102, 47, 82, 117, 110, 97, 98, 111, 117, 116, 0, 33, 0, 5, 0, 6, 0,
+            0, 0, 0, 0, 2, 0, 1, 0, 7, 0, 8, 0, 1, 0, 9, 0, 0, 0, 17, 0, 1, 0,
+            1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 0, 0, 1, 0, 10, 0, 11,
+            0, 1, 0, 9, 0, 0, 0, 24, 0, 2, 0, 3, 0, 0, 0, 12, 43, -64, 0, 2,
+            44, -64, 0, 3, -74, 0, 4, -79, 0, 0, 0, 0, 0, 1, 0, 19, 0, 0, 0,
+            10, 0, 1, 0, 6, 0, 17, 0, 9, 4, 9 }; // GenCodeXXXXXXXX.class
+
+} // end of Runabout




reply via email to

[Prev in Thread] Current Thread [Next in Thread]