finished support for reading in everything except method bodies
authoradam <adam@megacz.com>
Fri, 3 Jun 2005 03:21:30 +0000 (03:21 +0000)
committeradam <adam@megacz.com>
Fri, 3 Jun 2005 03:21:30 +0000 (03:21 +0000)
darcs-hash:20050603032130-5007d-dad53e6604dce57a27e22ec5cf7c4ef89aa14af9.gz

src/org/ibex/classgen/CGConst.java
src/org/ibex/classgen/CPGen.java
src/org/ibex/classgen/ClassGen.java
src/org/ibex/classgen/FieldGen.java
src/org/ibex/classgen/MethodGen.java
src/org/ibex/classgen/Type.java

index e67218c..59a7f74 100644 (file)
@@ -1,6 +1,7 @@
 package org.ibex.classgen;
 
 public interface CGConst {
+
     // Class only
     public static final int ACC_INTERFACE = (byte) 0x0200;
     public static final int ACC_SUPER     = (byte) 0x0020;
index 3a453d1..3682fe9 100644 (file)
@@ -24,7 +24,6 @@ class CPGen {
         CPGen owner; // don't need this yet, but we will to implement ref() and unref()
         int n; // this is the refcount if state == OPEN, index if >= STABLE
         int tag;
-        Ent e0;
         
         Ent(CPGen owner, int tag) { this.owner = owner; this.tag = tag; }
         
@@ -88,7 +87,7 @@ class CPGen {
         private String fixme() { throw new Error("fixme"); }
         Object key() throws ClassGen.ClassReadExn {
             switch(tag) {
-                case 7: return Type.fromDescriptor(((Utf8Ent)e0).s);
+                case 7: return Type.fromDescriptor(((Utf8Ent)e1).s);
                 case 8: return (((Utf8Ent)e1).s);
                 case 9: {
                     NameAndTypeKey nt = (NameAndTypeKey) e2.key();
@@ -96,12 +95,16 @@ class CPGen {
                     if(t == null) throw new ClassGen.ClassReadExn("invalid type descriptor");
                     return new FieldRef((Type.Object)e1.key(), nt.name, t);
                 }
-                case 10: {
+                case 10: case 11: {
                     NameAndTypeKey nt = (NameAndTypeKey) e2.key();
-                    return new MethodRef((Type.Object)e1.key(), fixme(), null, null);
+                    if (e1.key() == null) throw new Error(e1.tag + " => " + e1.key());
+                    return new MethodRef((Type.Object)e1.key(), "methodname", Type.VOID, new Type[0]); // FIXME FIXME
+                }
+                case 12: {
+                    return new NameAndTypeKey(((Utf8Ent)e1).s, ((Utf8Ent)e2).s); 
                 }
             }
-            throw new Error("FIXME");
+            throw new Error("FIXME " + tag);
         }
     }
         
@@ -110,7 +113,9 @@ class CPGen {
         Utf8Ent(CPGen owner) { super(owner, 1); }
         String debugToString() { return s; }
         void dump(DataOutput o) throws IOException { super.dump(o); o.writeUTF(s); }
-        Object key() { throw new Error("Brian is lame"); }
+        Object key() {
+            return s;
+        }
     }
     
     /*
@@ -146,6 +151,9 @@ class CPGen {
         if(e == null) throw new IllegalStateException("entry not found");
         return getIndex(e);
     }
+    public final String getUtf8ByIndex(int i) {
+        return ((Utf8Ent)getByIndex(i)).s;
+    }
     public final int getUtf8Index(String s) {
         Ent e = getUtf8(s);
         if(e == null) throw new IllegalStateException("entry not found");
@@ -156,7 +164,11 @@ class CPGen {
         return ent.n;
     }
 
-    public final Type.Object getType(int index) { return Type.fromDescriptor(((Utf8Ent)getByIndex(index)).s).asObject(); }
+    public final Type getType(int index) throws ClassGen.ClassReadExn {
+        Ent e = getByIndex(index);
+        if (e instanceof Utf8Ent) return Type.fromDescriptor(((Utf8Ent)e).s);
+        else return (Type)e.key();
+    }
 
     public final Ent getByIndex(int index) {
         if(state < STABLE) throw new IllegalStateException("constant pool is not stable");
@@ -311,8 +323,7 @@ class CPGen {
         usedSlots = in.readUnsignedShort();
         if(usedSlots==0) throw new ClassGen.ClassReadExn("invalid used slots");
         
-        // these are to remember the CPRefEnt e0 and e1s we have to fix up
-        int[] e0s = new int[usedSlots];
+        // these are to remember the CPRefEnt e1 and e2s we have to fix up
         int[] e1s = new int[usedSlots];
         int[] e2s = new int[usedSlots];
         
@@ -330,7 +341,7 @@ class CPGen {
                 case 12: // NameAndType
                 {
                     e = new CPRefEnt(this, tag);
-                    e1s[index] = in.readUnsignedShort();;
+                    e1s[index] = in.readUnsignedShort();
                     if(tag != 7 && tag != 8) e2s[index] = in.readUnsignedShort();
                     break;
                 }
@@ -348,7 +359,6 @@ class CPGen {
                     LongEnt le;
                     e = le = new LongEnt(this, tag);
                     le.l = in.readLong();
-                    index++;
                     break;
                 }
                 case 1: // Utf8
@@ -362,43 +372,50 @@ class CPGen {
                     throw new ClassGen.ClassReadExn("invalid cp ent tag");
             }
             entriesByIndex[index] = e;
+            if (e instanceof LongEnt) index++;
         }
         
         for(int index=1;index<usedSlots;index++) {
             int i = index;
             Ent e = entriesByIndex[index];
-            if(e == null) throw new Error("should never happen");
-            if(e instanceof CPRefEnt) {
-                CPRefEnt ce = (CPRefEnt) e;
-                if(e0s[i] == 0 || e0s[i] >= usedSlots) throw new ClassGen.ClassReadExn("invalid cp index");
-                ce.e0 = entriesByIndex[e0s[i]];
-                if(ce.e0 == null)  throw new ClassGen.ClassReadExn("invalid cp index");
-                if(ce.tag != 7 && ce.tag != 8) {
-                    if(e1s[i] == 0 || e1s[i] >= usedSlots) throw new ClassGen.ClassReadExn("invalid cp index");
-                    ce.e1 = entriesByIndex[e1s[i]];
-                    if(ce.e1 == null)  throw new ClassGen.ClassReadExn("invalid cp index");
-                }
-                switch(ce.tag) {
-                    case 7:
-                    case 8:
-                        if(!(ce.e0 instanceof Utf8Ent)) throw new ClassGen.ClassReadExn("expected a utf8 ent");
-                        break;
-                    case 9:
-                    case 10:
-                    case 11:
-                        if(!(ce.e1 instanceof CPRefEnt) || ((CPRefEnt)ce.e1).tag != 7)
-                            throw new ClassGen.ClassReadExn("expected a type ent");
-                        if(!(ce.e2 instanceof CPRefEnt) || ((CPRefEnt)ce.e2).tag != 12)
-                            throw new ClassGen.ClassReadExn("expected a name and type ent");
-                        break;
-                    case 12:
-                        if(!(ce.e1 instanceof Utf8Ent)) throw new ClassGen.ClassReadExn("expected a utf8 ent");
-                        if(!(ce.e2 instanceof Utf8Ent)) throw new ClassGen.ClassReadExn("expected a utf8 ent");
-                }
-            } else if(e instanceof LongEnt) {
+            if (e == null) throw new Error("should never happen: " + i + "/"+usedSlots);
+            if (e instanceof LongEnt) {
                 index++;
+                continue;
             }
+            if (!(e instanceof CPRefEnt)) continue;
+            CPRefEnt ce = (CPRefEnt) e;
+            if(e1s[i] == 0 || e1s[i] >= usedSlots) throw new ClassGen.ClassReadExn("invalid cp index");
+            ce.e1 = entriesByIndex[e1s[i]];
+            if(ce.e1 == null)  throw new ClassGen.ClassReadExn("invalid cp index");
+            if(ce.tag != 7 && ce.tag != 8) {
+                if(e2s[i] == 0 || e2s[i] >= usedSlots) throw new ClassGen.ClassReadExn("invalid cp index");
+                ce.e2 = entriesByIndex[e2s[i]];
+                if(ce.e2 == null)  throw new ClassGen.ClassReadExn("invalid cp index");
+            }
+            switch(ce.tag) {
+                case 7:
+                case 8:
+                    if(!(ce.e1 instanceof Utf8Ent)) throw new ClassGen.ClassReadExn("expected a utf8 ent");
+                    break;
+                case 9:
+                case 10:
+                case 11:
+                    if(!(ce.e1 instanceof CPRefEnt) || ((CPRefEnt)ce.e1).tag != 7)
+                        throw new ClassGen.ClassReadExn("expected a type ent");
+                    if(!(ce.e2 instanceof CPRefEnt) || ((CPRefEnt)ce.e2).tag != 12)
+                        throw new ClassGen.ClassReadExn("expected a name and type ent");
+                    break;
+                case 12:
+                    if(!(ce.e1 instanceof Utf8Ent)) throw new ClassGen.ClassReadExn("expected a utf8 ent");
+                    if(!(ce.e2 instanceof Utf8Ent)) throw new ClassGen.ClassReadExn("expected a utf8 ent");
+                    break;
+            }
+        }
+        for(int i=1; i<usedSlots; i++) {
+            Ent e = entriesByIndex[i];
             entries.put(e.key(), e);
+            if (e instanceof LongEnt) i++;
         }
         state = STABLE;
     }
index 47b67ea..f4c1a03 100644 (file)
@@ -8,6 +8,8 @@ public class ClassGen implements CGConst {
     private final Type.Object thisType;
     private final Type.Object superType;
     private final Type.Object[] interfaces;
+    private short minor;
+    private short major;
     final int flags;
     
     private String sourceFile; 
@@ -16,7 +18,48 @@ public class ClassGen implements CGConst {
     
     final CPGen cp;
     private final AttrGen attributes;
-    
+
+    public static String flagsToString(int flags) {
+        String ret = "";
+        if ((flags & ACC_PUBLIC) != 0)       ret += "public ";
+        if ((flags & ACC_PRIVATE) != 0)      ret += "private ";
+        if ((flags & ACC_PROTECTED) != 0)    ret += "protected ";
+        if ((flags & ACC_STATIC) != 0)       ret += "static ";
+        if ((flags & ACC_FINAL) != 0)        ret += "final ";
+        if ((flags & ACC_ABSTRACT) != 0)     ret += "abstract ";
+        if ((flags & ACC_SYNCHRONIZED) != 0) ret += "synchronized ";
+        if ((flags & ACC_NATIVE) != 0)       ret += "native ";
+        if ((flags & ACC_STRICT) != 0)       ret += "strictfp ";
+        if ((flags & ACC_VOLATILE) != 0)     ret += "volatile ";
+        if ((flags & ACC_TRANSIENT) != 0)    ret += "transient ";
+        return ret;
+    }
+  
+    public String toString() { StringBuffer sb = new StringBuffer(); toString(sb); return sb.toString(); }
+    public void   toString(StringBuffer sb) {
+        sb.append(flagsToString(flags));
+        sb.append((flags & ACC_INTERFACE) != 0 ? "interface " : "class ");
+        sb.append(thisType.humanReadable());
+        if (superType != null) sb.append(" extends " + superType.humanReadable());
+        if (interfaces != null && interfaces.length > 0) sb.append(" implements");
+        for(int i=0; i<interfaces.length; i++) sb.append((i==0?" ":", ")+interfaces[i].humanReadable());
+        sb.append(" {");
+        sb.append(" // [jcf v"+major+"."+minor+"]");
+        if (sourceFile != null) sb.append(" from " + sourceFile);
+        sb.append("\n");
+        for(int i=0; i<fields.size(); i++) {
+            sb.append("  ");
+            ((FieldGen)fields.elementAt(i)).toString(sb);
+            sb.append("\n");
+        }
+        for(int i=0; i<methods.size(); i++) {
+            sb.append("  ");
+            ((MethodGen)methods.elementAt(i)).toString(sb, thisType.getShortName());
+            sb.append("\n");
+        }
+        sb.append("}");
+    }
+
     /** @see #ClassGen(Type.Object, Type.Object, int) */
     public ClassGen(String name, String superName, int flags) {
         this(Type.fromDescriptor(name).asObject(), Type.fromDescriptor(superName).asObject(), flags);
@@ -39,6 +82,8 @@ public class ClassGen implements CGConst {
         this.superType = superType;
         this.interfaces = interfaces;
         this.flags = flags;
+        this.minor = 3;
+        this.major = 45;
         
         cp = new CPGen();
         attributes = new AttrGen(cp);
@@ -133,8 +178,8 @@ public class ClassGen implements CGConst {
         cp.seal();
         
         o.writeInt(0xcafebabe); // magic
-        o.writeShort(3); // minor_version
-        o.writeShort(45); // major_version
+        o.writeShort(minor); // minor_version
+        o.writeShort(major); // major_version
         
         cp.dump(o); // constant_pool
         
@@ -167,21 +212,24 @@ public class ClassGen implements CGConst {
     }
 
     ClassGen(DataInput i) throws ClassReadExn, IOException {
-        if(i.readInt() != 0xcadebabe) throw new ClassReadExn("invalid magic");
-        short minor = (short)i.readInt();
-        if(minor != 3) throw new ClassReadExn("invalid minor version: " + minor);
-        if(i.readInt() != 45) throw new ClassReadExn("invalid major version");
+        int magic = i.readInt();
+        if (magic != 0xcafebabe) throw new ClassReadExn("invalid magic: " + Long.toString(0xffffffffL & magic, 16));
+        minor = i.readShort();
+        //if (minor != 3) throw new ClassReadExn("invalid minor version: " + minor);
+        major = i.readShort();
+        //if (major != 45 && major != 46) throw new ClassReadExn("invalid major version");
         cp = new CPGen(i);
-        flags = (short)i.readShort();
-        thisType = cp.getType(i.readShort());
-        superType = cp.getType(i.readShort());
+        flags = i.readShort();
+        thisType = (Type.Object)cp.getType(i.readShort());
+        superType = (Type.Object)cp.getType(i.readShort());
         interfaces = new Type.Object[i.readShort()];
-        for(int j=0; j<interfaces.length; j++) interfaces[j] = cp.getType(i.readShort());
+        for(int j=0; j<interfaces.length; j++) interfaces[j] = (Type.Object)cp.getType(i.readShort());
         int numFields = i.readShort();
-        for(int j=0; j<numFields; j++) fields.add(new FieldGen(i));
+        for(int j=0; j<numFields; j++) fields.add(new FieldGen(cp, i));
         int numMethods = i.readShort();
-        for(int j=0; j<numMethods; j++) methods.add(new MethodGen(i));
+        for(int j=0; j<numMethods; j++) methods.add(new MethodGen(cp, i));
         attributes = new AttrGen(cp, i);
+        sourceFile = (String)attributes.get("SourceFile");
     }
     
     /** Thrown when class generation fails for a reason not under the control of the user
@@ -222,13 +270,15 @@ public class ClassGen implements CGConst {
         public AttrGen(CPGen cp) { this.cp = cp; }
         public AttrGen(CPGen cp, DataInput in) throws IOException {
             this(cp);
-            while(true) {
+            int size = in.readShort();
+            for(int i=0; i<size; i++) {
                 String name = null;
-                try {
-                    name = ((CPGen.Utf8Ent)cp.getByIndex(in.readShort())).s;
-                } catch (EOFException _) {
-                    return;
-                }
+                int idx = in.readShort();
+                CPGen.Ent e = cp.getByIndex(idx);
+                Object key = e.key();
+                if (key instanceof String) name = (String)key;
+                else name = ((Type)key).getDescriptor();
+
                 int length = in.readInt();
                 if (length==2) {   // FIXME might be wrong assumption
                     ht.put(name, cp.getByIndex(in.readShort()));
@@ -239,6 +289,12 @@ public class ClassGen implements CGConst {
                 }
             }
         }
+
+        public Object get(String s) {
+            Object ret = ht.get(s);
+            if (ret instanceof CPGen.Utf8Ent) return ((CPGen.Utf8Ent)ret).s;
+            return ret;
+        }
         
         public void add(String s, Object data) {
             cp.addUtf8(s);
@@ -268,31 +324,43 @@ public class ClassGen implements CGConst {
         }
     }
     
-    /*public static void main(String[] args) throws Exception {
-        Type.Object me = new Type.Object("Test");
-        ClassGen cg = new ClassGen("Test", "java.lang.Object", ACC_PUBLIC|ACC_SUPER|ACC_FINAL);
-        FieldGen fg = cg.addField("foo", Type.INT, ACC_PUBLIC|ACC_STATIC);
+    public static void main(String[] args) throws Exception {
+        if (args.length==1) {
+            if (args[0].endsWith(".class")) {
+                System.out.println(new ClassGen(new DataInputStream(new FileInputStream(args[0]))));
+            } else {
+                InputStream is = Class.forName(args[0]).getClassLoader().getResourceAsStream(args[0].replace('.', '/')+".class");
+                System.out.println(new ClassGen(new DataInputStream(is)));
+            }
+        } else {
+            /*
+            Type.Object me = new Type.Object("Test");
+            ClassGen cg = new ClassGen("Test", "java.lang.Object", ACC_PUBLIC|ACC_SUPER|ACC_FINAL);
+            FieldGen fg = cg.addField("foo", Type.INT, ACC_PUBLIC|ACC_STATIC);
         
-        MethodGen mg = cg.addMethod("main", Type.VOID, new Type[]{Type.arrayType(Type.STRING)}, ACC_STATIC|ACC_PUBLIC);
-        mg.setMaxLocals(1);
-        mg.addPushConst(0);
-        //mg.add(ISTORE_0);
-        mg.add(PUTSTATIC, fieldRef(me, "foo", Type.INT));
-        int top = mg.size();
-        mg.add(GETSTATIC, cg.fieldRef(new Type.Object("java.lang.System"), "out", new Type.Object("java.io.PrintStream")));
-        //mg.add(ILOAD_0);
-        mg.add(GETSTATIC, cg.fieldRef(me, "foo", Type.INT));
-        mg.add(INVOKEVIRTUAL, cg.methodRef(new Type.Object("java.io.PrintStream"), "println", Type.VOID, new Type[]{Type.INT}));
-        //mg.add(IINC, new int[]{0, 1});
-        //mg.add(ILOAD_0);
-        mg.add(GETSTATIC, cg.fieldRef(me, "foo", Type.INT));
-        mg.addPushConst(1);
-        mg.add(IADD);
-        mg.add(DUP);
-        mg.add(PUTSTATIC, cg.fieldRef(me, "foo", Type.INT));       
-        mg.addPushConst(10);
-        mg.add(IF_ICMPLT, top);
-        mg.add(RETURN);
-        cg.dump("Test.class");
-    }*/
+            MethodGen mg = cg.addMethod("main", Type.VOID, new Type[]{Type.arrayType(Type.STRING)}, ACC_STATIC|ACC_PUBLIC);
+            mg.setMaxLocals(1);
+            mg.addPushConst(0);
+            //mg.add(ISTORE_0);
+            mg.add(PUTSTATIC, fieldRef(me, "foo", Type.INT));
+            int top = mg.size();
+            mg.add(GETSTATIC, cg.fieldRef(new Type.Object("java.lang.System"), "out", new Type.Object("java.io.PrintStream")));
+            //mg.add(ILOAD_0);
+            mg.add(GETSTATIC, cg.fieldRef(me, "foo", Type.INT));
+            mg.add(INVOKEVIRTUAL, cg.methodRef(new Type.Object("java.io.PrintStream"), "println",
+                                               Type.VOID, new Type[]{Type.INT}));
+            //mg.add(IINC, new int[]{0, 1});
+            //mg.add(ILOAD_0);
+            mg.add(GETSTATIC, cg.fieldRef(me, "foo", Type.INT));
+            mg.addPushConst(1);
+            mg.add(IADD);
+            mg.add(DUP);
+            mg.add(PUTSTATIC, cg.fieldRef(me, "foo", Type.INT));       
+            mg.addPushConst(10);
+            mg.add(IF_ICMPLT, top);
+            mg.add(RETURN);
+            cg.dump("Test.class");
+            */
+        }
+    }
 }
index 330849b..aab5586 100644 (file)
@@ -12,8 +12,25 @@ public class FieldGen implements CGConst {
     private final ClassGen.AttrGen attrs;
     
     private Object constantValue;
+
+    public String toString() { StringBuffer sb = new StringBuffer(); toString(sb); return sb.toString(); }
+    public void   toString(StringBuffer sb) {
+        sb.append(ClassGen.flagsToString(flags));
+        sb.append(type.humanReadable());
+        sb.append(" ");
+        sb.append(name);
+        sb.append(";");
+        // FIXME: attrs
+    }
     
-    FieldGen(DataInput in) { throw new Error("Brian is lame"); }
+    FieldGen(CPGen cp, DataInput in) throws IOException {
+        this.cp = cp;
+        flags = in.readShort();
+        name = cp.getUtf8ByIndex(in.readShort());
+        type = cp.getType(in.readShort());
+        attrs = new ClassGen.AttrGen(cp, in);
+    }
+
     FieldGen(ClassGen owner, String name, Type type, int flags) {
         if((flags & ~(ACC_PUBLIC|ACC_PRIVATE|ACC_PROTECTED|ACC_VOLATILE|ACC_TRANSIENT|ACC_STATIC|ACC_FINAL)) != 0)
             throw new IllegalArgumentException("invalid flags");
index 88c35bb..4dd0547 100644 (file)
@@ -29,7 +29,39 @@ public class MethodGen implements CGConst {
     private byte[] op;
     private Object[] arg;
     
-    MethodGen(DataInput in) { throw new Error("Brian is lame"); }
+    public String toString() { StringBuffer sb = new StringBuffer(); toString(sb, "<init>"); return sb.toString(); }
+    public void   toString(StringBuffer sb, String constructorName) {
+        sb.append(ClassGen.flagsToString(flags));
+        sb.append(ret.humanReadable());
+        sb.append(" ");
+
+        if (name.equals("<clinit>")) sb.append("static ");
+        else {
+            if (name.equals("<init>")) sb.append(constructorName);
+            else sb.append(name);
+            sb.append("(");
+            for(int i=0; i<args.length; i++)
+                sb.append((i==0?"":", ")+args[i].humanReadable());
+            sb.append(") ");
+        }
+        sb.append("{");
+        sb.append("}");
+        // FIXME: attrs, body
+    }
+
+    MethodGen(CPGen cp, DataInput in) throws IOException {
+        this.cp = cp;
+        flags = in.readShort();
+        name = cp.getUtf8ByIndex(in.readShort());
+        String descriptor = cp.getUtf8ByIndex(in.readShort());
+        String ret = descriptor.substring(descriptor.indexOf(')')+1);
+        this.ret = Type.fromDescriptor(ret);
+        //String args = descriptor.substring(1, descriptor.indexOf(')'));
+        args = new Type[0]; // FIXME
+        codeAttrs = null;
+        attrs = new ClassGen.AttrGen(cp, in);
+    }
+
     MethodGen(ClassGen owner, String name, Type ret, Type[] args, int flags) {
         if((flags & ~(ACC_PUBLIC|ACC_PRIVATE|ACC_PROTECTED|ACC_STATIC|ACC_FINAL|ACC_SYNCHRONIZED|ACC_NATIVE|ACC_ABSTRACT|ACC_STRICT)) != 0)
             throw new IllegalArgumentException("invalid flags");
index 29301d3..1c9d97f 100644 (file)
@@ -24,7 +24,20 @@ public class Type {
     public static final Type[] NO_ARGS = new Type[0];
     
     final String descriptor;
-    
+
+    public String humanReadable() {
+        if(descriptor.equals("V")) return "void";
+        if(descriptor.equals("I")) return "int";
+        if(descriptor.equals("J")) return "long";
+        if(descriptor.equals("Z")) return "boolean";
+        if(descriptor.equals("D")) return "double";
+        if(descriptor.equals("F")) return "float";
+        if(descriptor.equals("B")) return "byte";
+        if(descriptor.equals("C")) return "char";
+        if(descriptor.equals("S")) return "short";
+        throw new Error("confounded by Type("+descriptor+")");
+    }
+
     protected Type(String descriptor) { this.descriptor = descriptor; }
     
     public static Type fromDescriptor(String descriptor) {
@@ -39,8 +52,7 @@ public class Type {
         if(descriptor.equals("S")) return SHORT;
         if(descriptor.endsWith("[")) return new Type.Array(fromDescriptor(descriptor.substring(0, descriptor.indexOf('['))),
                                                            descriptor.length() - descriptor.indexOf('['));
-        if(Type.Object.validDescriptorString(descriptor)) return new Type.Object(descriptor);
-        return null;
+        return new Type.Object(descriptor);
     }
         
     /** Returns the Java descriptor string for this object ("I", or "Ljava/lang/String", "[[J", etc */
@@ -60,6 +72,7 @@ public class Type {
     */
     public static Type arrayType(Type base, int dim) { return new Type.Array(base, dim); }
 
+    public String toString() { return getDescriptor(); }
     public Type.Object asObject() { throw new RuntimeException("attempted to use "+this+" as a Type.Object, which it is not"); }
     public Type.Array asArray() { throw new RuntimeException("attempted to use "+this+" as a Type.Array, which it is not"); }
     public boolean isObject() { return false; }
@@ -73,6 +86,11 @@ public class Type {
         protected Object(String s) { super(_initHelper(s)); }
         public Type.Object asObject() { return this; }
         public boolean isObject() { return true; }
+        public String humanReadable() { return internalForm().replace('/', '.'); }
+        public String getShortName() {
+            String hr = humanReadable();
+            return hr.substring(hr.lastIndexOf('.')+1);
+        }
 
         private static String _initHelper(String s) {
             if(!s.startsWith("L") || !s.endsWith(";")) s = "L" + s.replace('.', '/') + ";";
@@ -95,9 +113,15 @@ public class Type {
     }    
 
     public static class Array extends Object {
-        protected Array(Type t, int dim) {  super(_initHelper(t, dim)); }
+        private int dim;
+        protected Array(Type t, int dim) { super(_initHelper(t, dim)); this.dim = dim; }
         public Type.Array asArray() { return this; }
         public boolean isArray() { return true; }
+        public String humanReadable() { 
+            String ret = super.internalForm().replace('/', '.');
+            for(int i=0; i<dim; i++) ret += "[]";
+            return ret;
+        }
         String internalForm() { throw new Error("Type.Array does not have an internalForm()"); }
         String[] components() { throw new Error("Type.Array does not have components()"); }
         private static String _initHelper(Type t, int dim) {