diff src/com/go/trove/classfile/ClassFile.java @ 0:3dc0c5604566

Initial checkin of blitz 2.0 fcs - no installer yet.
author Dan Creswell <dan.creswell@gmail.com>
date Sat, 21 Mar 2009 11:00:06 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/go/trove/classfile/ClassFile.java	Sat Mar 21 11:00:06 2009 +0000
@@ -0,0 +1,973 @@
+/* ====================================================================
+ * Trove - Copyright (c) 1997-2000 Walt Disney Internet Group
+ * ====================================================================
+ * The Tea Software License, Version 1.1
+ *
+ * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        Walt Disney Internet Group (http://opensource.go.com/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must
+ *    not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written
+ *    permission, please contact opensource@dig.com.
+ *
+ * 5. Products derived from this software may not be called "Tea",
+ *    "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet",
+ *    "Kettle", "Trove" or "BeanDoc" appear in their name, without prior
+ *    written permission of the Walt Disney Internet Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * For more information about Tea, please see http://opensource.go.com/.
+ */
+
+package com.go.trove.classfile;
+
+import java.lang.reflect.*;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.HashMap;
+import java.io.*;
+
+/******************************************************************************
+ * A class used to create Java class files. Call the writeTo method
+ * to produce a class file.
+ *
+ * <p>See <i>The Java Virtual Machine Specification</i> (ISBN 0-201-63452-X)
+ * for information on how class files are structured. Section 4.1 describes
+ * the ClassFile structure.
+ * 
+ * @author Brian S O'Neill
+ * @version
+ * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 01/05/17 <!-- $-->
+ */
+public class ClassFile {
+    private static final int MAGIC = 0xCAFEBABE;
+    private static final int JDK1_1_MAJOR_VERSION = 45;
+    private static final int JDK1_1_MINOR_VERSION = 3;
+    
+    private int mMajorVersion = JDK1_1_MAJOR_VERSION;
+    private int mMinorVersion = JDK1_1_MINOR_VERSION;
+
+    private String mClassName;
+    private String mSuperClassName;
+    private String mInnerClassName;
+    private TypeDescriptor mType;
+
+    private ConstantPool mCp;
+    
+    private AccessFlags mAccessFlags;
+
+    private ConstantClassInfo mThisClass;
+    private ConstantClassInfo mSuperClass;
+    
+    // Holds ConstantInfo objects.
+    private List mInterfaces = new ArrayList(2);
+    private Set mInterfaceSet = new HashSet(7);
+    
+    // Holds objects.
+    private List mFields = new ArrayList();
+    private List mMethods = new ArrayList();
+    private List mAttributes = new ArrayList();
+    
+    private SourceFileAttr mSource;
+
+    private List mInnerClasses;
+    private int mAnonymousInnerClassCount = 0;
+    private InnerClassesAttr mInnerClassesAttr;
+
+    // Is non-null for inner classes.
+    private ClassFile mOuterClass;
+
+    /** 
+     * By default, the ClassFile defines public, non-final, concrete classes.
+     * This constructor creates a ClassFile for a class that extends
+     * java.lang.Object.
+     * <p>
+     * Use the {@link #getAccessFlags access flags} to change the default
+     * modifiers for this class or to turn it into an interface.
+     *
+     * @param className Full class name of the form ex: "java.lang.String".
+     */
+    public ClassFile(String className) {
+        this(className, (String)null);
+    }
+    
+    /** 
+     * By default, the ClassFile defines public, non-final, concrete classes.
+     * <p>
+     * Use the {@link #getAccessFlags access flags} to change the default
+     * modifiers for this class or to turn it into an interface.
+     *
+     * @param className Full class name of the form ex: "java.lang.String".
+     * @param superClass Super class.
+     */
+    public ClassFile(String className, Class superClass) {
+        this(className, superClass.getName());
+    }
+
+    /** 
+     * By default, the ClassFile defines public, non-final, concrete classes.
+     * <p>
+     * Use the {@link #getAccessFlags access flags} to change the default
+     * modifiers for this class or to turn it into an interface.
+     *
+     * @param className Full class name of the form ex: "java.lang.String".
+     * @param superClassName Full super class name.
+     */
+    public ClassFile(String className, String superClassName) {
+        if (superClassName == null) {
+            if (!className.equals(Object.class.getName())) {
+                superClassName = Object.class.getName();
+            }
+        }
+
+        mCp = new ConstantPool();
+
+        // public, non-final, concrete class
+        mAccessFlags = new AccessFlags(Modifier.PUBLIC);
+
+        mThisClass = ConstantClassInfo.make(mCp, className);
+        mSuperClass = ConstantClassInfo.make(mCp, superClassName);
+
+        mClassName = className;
+        mSuperClassName = superClassName;
+    }
+
+    /**
+     * Used to construct a ClassFile when read from a stream.
+     */
+    private ClassFile(ConstantPool cp, AccessFlags accessFlags,
+                      ConstantClassInfo thisClass,
+                      ConstantClassInfo superClass,
+                      ClassFile outerClass) {
+
+        mCp = cp;
+
+        mAccessFlags = accessFlags;
+
+        mThisClass = thisClass;
+        mSuperClass = superClass;
+
+        mClassName = thisClass.getClassName();
+        if (superClass != null) {
+            mSuperClassName = superClass.getClassName();
+        }
+
+        mOuterClass = outerClass;
+    }
+
+    public String getClassName() {
+        return mClassName;
+    }
+
+    public String getSuperClassName() {
+        return mSuperClassName;
+    }
+
+    /**
+     * Returns a TypeDescriptor for the type of this ClassFile.
+     */
+    public TypeDescriptor getType() {
+        if (mType == null) {
+            mType = new TypeDescriptor(mClassName);
+        }
+        return mType;
+    }
+
+    public AccessFlags getAccessFlags() {
+        return mAccessFlags;
+    }
+
+    /**
+     * Returns the names of all the interfaces that this class implements.
+     */
+    public String[] getInterfaces() {
+        int size = mInterfaces.size();
+        String[] names = new String[size];
+
+        for (int i=0; i<size; i++) {
+            names[i] = ((ConstantClassInfo)mInterfaces.get(i)).getClassName();
+        }
+
+        return names;
+    }
+
+    /**
+     * Returns all the fields defined in this class.
+     */
+    public FieldInfo[] getFields() {
+        FieldInfo[] fields = new FieldInfo[mFields.size()];
+        return (FieldInfo[])mFields.toArray(fields);
+    }
+
+    /**
+     * Returns all the methods defined in this class, not including
+     * constructors and static initializers.
+     */
+    public MethodInfo[] getMethods() {
+        int size = mMethods.size();
+        List methodsOnly = new ArrayList(size);
+
+        for (int i=0; i<size; i++) {
+            MethodInfo method = (MethodInfo)mMethods.get(i);
+            String name = method.getName();
+            if (!"<init>".equals(name) && !"<clinit>".equals(name)) {
+                methodsOnly.add(method);
+            }
+        }
+
+        MethodInfo[] methodsArray = new MethodInfo[methodsOnly.size()];
+        return (MethodInfo[])methodsOnly.toArray(methodsArray);
+    }
+
+    /**
+     * Returns all the constructors defined in this class.
+     */
+    public MethodInfo[] getConstructors() {
+        int size = mMethods.size();
+        List ctorsOnly = new ArrayList(size);
+
+        for (int i=0; i<size; i++) {
+            MethodInfo method = (MethodInfo)mMethods.get(i);
+            if ("<init>".equals(method.getName())) {
+                ctorsOnly.add(method);
+            }
+        }
+
+        MethodInfo[] ctorsArray = new MethodInfo[ctorsOnly.size()];
+        return (MethodInfo[])ctorsOnly.toArray(ctorsArray);
+    }
+
+    /**
+     * Returns the static initializer defined in this class or null if there
+     * isn't one.
+     */
+    public MethodInfo getInitializer() {
+        int size = mMethods.size();
+
+        for (int i=0; i<size; i++) {
+            MethodInfo method = (MethodInfo)mMethods.get(i);
+            if ("<clinit>".equals(method.getName())) {
+                return method;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns all the inner classes defined in this class. If no inner classes
+     * are defined, then an array of length zero is returned.
+     */
+    public ClassFile[] getInnerClasses() {
+        if (mInnerClasses == null) {
+            return new ClassFile[0];
+        }
+
+        ClassFile[] innerClasses = new ClassFile[mInnerClasses.size()];
+        return (ClassFile[])mInnerClasses.toArray(innerClasses);
+    }
+
+    /**
+     * Returns true if this ClassFile represents an inner class.
+     */
+    public boolean isInnerClass() {
+        return mOuterClass != null;
+    }
+
+    /**
+     * If this ClassFile represents a non-anonymous inner class, returns its
+     * short inner class name.
+     */
+    public String getInnerClassName() {
+        return mInnerClassName;
+    }
+
+    /**
+     * Returns null if this ClassFile does not represent an inner class.
+     *
+     * @see #isInnerClass()
+     */
+    public ClassFile getOuterClass() {
+        return mOuterClass;
+    }
+
+    /**
+     * Returns a value indicating how deeply nested an inner class is with
+     * respect to its outermost enclosing class. For top level classes, 0
+     * is returned. For first level inner classes, 1 is returned, etc.
+     */
+    public int getClassDepth() {
+        int depth = 0;
+
+        ClassFile outer = mOuterClass;
+        while (outer != null) {
+            depth++;
+            outer = outer.mOuterClass;
+        }
+
+        return depth;
+    }
+
+    /**
+     * Returns the source file of this class file or null if not set.
+     */
+    public String getSourceFile() {
+        if (mSource == null) {
+            return null;
+        }
+        else {
+            return mSource.getFileName();
+        }
+    }
+
+    public boolean isSynthetic() {
+        for (int i = mAttributes.size(); --i >= 0; ) {
+            Object obj = mAttributes.get(i);
+            if (obj instanceof SyntheticAttr) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isDeprecated() {
+        for (int i = mAttributes.size(); --i >= 0; ) {
+            Object obj = mAttributes.get(i);
+            if (obj instanceof DeprecatedAttr) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Provides access to the ClassFile's ContantPool.
+     *
+     * @return The constant pool for this class file.
+     */
+    public ConstantPool getConstantPool() {
+        return mCp;
+    }
+    
+    /**
+     * Add an interface that this class implements.
+     *
+     * @param interfaceName Full interface name.
+     */
+    public void addInterface(String interfaceName) {
+        if (!mInterfaceSet.contains(interfaceName)) {
+            mInterfaces.add(ConstantClassInfo.make(mCp, interfaceName));
+            mInterfaceSet.add(interfaceName);
+        }
+    }
+    
+    /**
+     * Add an interface that this class implements.
+     */
+    public void addInterface(Class i) {
+        addInterface(i.getName());
+    }
+    
+    /**
+     * Add a field to this class.
+     */
+    public FieldInfo addField(AccessFlags flags,
+                              String fieldName,
+                              TypeDescriptor type) {
+        FieldInfo fi = new FieldInfo(this, flags, fieldName, type);
+        mFields.add(fi);
+        return fi;
+    }
+    
+    /**
+     * Add a method to this class.
+     *
+     * @param ret Is null if method returns void.
+     * @param params May be null if method accepts no parameters.
+     */
+    public MethodInfo addMethod(AccessFlags flags,
+                                String methodName,
+                                TypeDescriptor ret,
+                                TypeDescriptor[] params) {
+        MethodDescriptor md = new MethodDescriptor(ret, params);
+        return addMethod(flags, methodName, md);
+    }
+
+    /**
+     * Add a method to this class.
+     */
+    public MethodInfo addMethod(AccessFlags flags,
+                                String methodName,
+                                MethodDescriptor md) {
+        MethodInfo mi = new MethodInfo(this, flags, methodName, md);
+        mMethods.add(mi);
+        return mi;
+    }
+
+    /**
+     * Add a method to this class. This method is handy for implementing
+     * methods defined by a pre-existing interface.
+     */
+    public MethodInfo addMethod(Method method) {
+        AccessFlags flags = new AccessFlags(method.getModifiers());
+        flags.setAbstract(false);
+
+        TypeDescriptor ret = new TypeDescriptor(method.getReturnType());
+
+        Class[] paramClasses = method.getParameterTypes();
+        TypeDescriptor[] params = new TypeDescriptor[paramClasses.length];
+        for (int i=0; i<params.length; i++) {
+            params[i] = new TypeDescriptor(paramClasses[i]);
+        }
+
+        MethodInfo mi = addMethod(flags, method.getName(), ret, params);
+        
+        // exception stuff...
+        Class[] exceptions = method.getExceptionTypes();
+        for (int i=0; i<exceptions.length; i++) {
+            mi.addException(exceptions[i].getName());
+        }
+
+        return mi;
+    }
+
+    /**
+     * Add a constructor to this class.
+     *
+     * @param params May be null if constructor accepts no parameters.
+     */
+    public MethodInfo addConstructor(AccessFlags flags,
+                                     TypeDescriptor[] params) {
+        MethodDescriptor md = new MethodDescriptor(null, params);
+        MethodInfo mi = new MethodInfo(this, flags, "<init>", md);
+        mMethods.add(mi);
+        return mi;
+    }
+
+    /**
+     * Add a static initializer to this class.
+     */
+    public MethodInfo addInitializer() {
+        MethodDescriptor md = new MethodDescriptor(null, null);
+        AccessFlags af = new AccessFlags();
+        af.setStatic(true);
+        MethodInfo mi = new MethodInfo(this, af, "<clinit>", md);
+        mMethods.add(mi);
+        return mi;
+    }
+
+    /**
+     * Add an inner class to this class. By default, inner classes are private
+     * static.
+     *
+     * @param innerClassName Optional short inner class name.
+     */
+    public ClassFile addInnerClass(String innerClassName) {
+        return addInnerClass(innerClassName, (String)null);
+    }
+
+    /**
+     * Add an inner class to this class. By default, inner classes are private
+     * static.
+     *
+     * @param innerClassName Optional short inner class name.
+     * @param superClass Super class.
+     */
+    public ClassFile addInnerClass(String innerClassName, Class superClass) {
+        return addInnerClass(innerClassName, superClass.getName());
+    }
+
+    /**
+     * Add an inner class to this class. By default, inner classes are private
+     * static.
+     *
+     * @param innerClassName Optional short inner class name.
+     * @param superClassName Full super class name.
+     * @param isStatic True specifies a static inner class.
+     */
+    public ClassFile addInnerClass(String innerClassName, 
+                                   String superClassName) {
+        String fullInnerClassName;
+        if (innerClassName == null) {
+            fullInnerClassName = 
+                mClassName + '$' + (++mAnonymousInnerClassCount);
+        }
+        else {
+            fullInnerClassName = mClassName + '$' + innerClassName;
+        }
+
+        ClassFile inner = new ClassFile(fullInnerClassName, superClassName);
+        AccessFlags access = inner.getAccessFlags();
+        access.setPrivate(true);
+        access.setStatic(true);
+        inner.mInnerClassName = innerClassName;
+        inner.mOuterClass = this;
+
+        if (mInnerClasses == null) {
+            mInnerClasses = new ArrayList();
+        }
+
+        mInnerClasses.add(inner);
+        
+        // Record the inner class in this, the outer class.
+        if (mInnerClassesAttr == null) {
+            addAttribute(new InnerClassesAttr(mCp));
+        }
+
+        mInnerClassesAttr.addInnerClass(fullInnerClassName, mClassName, 
+                                        innerClassName, access);
+
+        // Record the inner class in itself.
+        inner.addAttribute(new InnerClassesAttr(inner.getConstantPool()));
+        inner.mInnerClassesAttr.addInnerClass(fullInnerClassName, mClassName,
+                                              innerClassName, access);
+
+        return inner;
+    }
+
+    /**
+     * Set the source file of this class file by adding a source file
+     * attribute. The source doesn't actually have to be a file,
+     * but the virtual machine spec names the attribute "SourceFile_attribute".
+     */
+    public void setSourceFile(String fileName) {
+        addAttribute(new SourceFileAttr(mCp, fileName));
+    }
+
+    /**
+     * Mark this class as being synthetic by adding a special attribute.
+     */
+    public void markSynthetic() {
+        addAttribute(new SyntheticAttr(mCp));
+    }
+
+    /**
+     * Mark this class as being deprecated by adding a special attribute.
+     */
+    public void markDeprecated() {
+        addAttribute(new DeprecatedAttr(mCp));
+    }
+
+    /**
+     * Add an attribute to this class.
+     */
+    public void addAttribute(Attribute attr) {
+        if (attr instanceof SourceFileAttr) {
+            if (mSource != null) {
+                mAttributes.remove(mSource);
+            }
+            mSource = (SourceFileAttr)attr;
+        }
+        else if (attr instanceof InnerClassesAttr) {
+            if (mInnerClassesAttr != null) {
+                mAttributes.remove(mInnerClassesAttr);
+            }
+            mInnerClassesAttr = (InnerClassesAttr)attr;
+        }
+
+        mAttributes.add(attr);
+    }
+
+    public Attribute[] getAttributes() {
+        Attribute[] attrs = new Attribute[mAttributes.size()];
+        return (Attribute[])mAttributes.toArray(attrs);
+    }
+
+    /**
+     * Sets the version to use when writing the generated ClassFile. Currently,
+     * only version 45, 3 is supported, and is set by default.
+     *
+     * @exception IllegalArgumentException when the version isn't supported
+     */
+    public void setVersion(int major, int minor) 
+        throws IllegalArgumentException {
+
+        if (major != JDK1_1_MAJOR_VERSION ||
+            minor != JDK1_1_MINOR_VERSION) {
+
+            throw new IllegalArgumentException("Version " + major + ", " +
+                                               minor + " is not supported");
+        }
+
+        mMajorVersion = major;
+        mMinorVersion = minor;
+    }
+
+    /**
+     * Writes the ClassFile to the given OutputStream. When finished, the
+     * stream is flushed, but not closed.
+     */
+    public void writeTo(OutputStream out) throws IOException {
+        if (!(out instanceof DataOutput)) {
+            out = new DataOutputStream(out);
+        }
+
+        writeTo((DataOutput)out);
+        
+        out.flush();
+    }
+
+    /**
+     * Writes the ClassFile to the given DataOutput.
+     */
+    public void writeTo(DataOutput dout) throws IOException {
+        dout.writeInt(MAGIC);
+        dout.writeShort(mMinorVersion);
+        dout.writeShort(mMajorVersion);
+        
+        mCp.writeTo(dout);
+        
+        int modifier = mAccessFlags.getModifier();
+        dout.writeShort(modifier | Modifier.SYNCHRONIZED);
+
+        dout.writeShort(mThisClass.getIndex());
+        if (mSuperClass != null) {
+            dout.writeShort(mSuperClass.getIndex());
+        }
+        else {
+            dout.writeShort(0);
+        }
+        
+        int size = mInterfaces.size();
+        if (size > 65535) {
+            throw new RuntimeException
+                ("Interfaces count cannot exceed 65535: " + size);
+        }
+        dout.writeShort(size);
+        for (int i=0; i<size; i++) {
+            int index = ((ConstantInfo)mInterfaces.get(i)).getIndex();
+            dout.writeShort(index);
+        }
+        
+        size = mFields.size();
+        if (size > 65535) {
+            throw new RuntimeException
+                ("Field count cannot exceed 65535: " + size);
+        }
+        dout.writeShort(size);
+        for (int i=0; i<size; i++) {
+            FieldInfo field = (FieldInfo)mFields.get(i);
+            field.writeTo(dout);
+        }
+        
+        size = mMethods.size();
+        if (size > 65535) {
+            throw new RuntimeException
+                ("Method count cannot exceed 65535: " + size);
+        }
+        dout.writeShort(size);
+        for (int i=0; i<size; i++) {
+            MethodInfo method = (MethodInfo)mMethods.get(i);
+            method.writeTo(dout);
+        }
+        
+        size = mAttributes.size();
+        if (size > 65535) {
+            throw new RuntimeException
+                ("Attribute count cannot exceed 65535: " + size);
+        }
+        dout.writeShort(size);
+        for (int i=0; i<size; i++) {
+            Attribute attr = (Attribute)mAttributes.get(i);
+            attr.writeTo(dout);
+        }
+    }
+
+    /**
+     * Reads a ClassFile from the given InputStream. With this method, inner
+     * classes cannot be loaded, and custom attributes cannot be defined.
+     *
+     * @param in source of class file data
+     * @throws IOException for I/O error or if classfile is invalid.
+     * @throws ArrayIndexOutOfBoundsException if a constant pool index is out
+     * of range.
+     * @throws ClassCastException if a constant pool index references the
+     * wrong type.
+     */
+    public static ClassFile readFrom(InputStream in) throws IOException {
+        return readFrom(in, null, null);
+    }
+
+    /**
+     * Reads a ClassFile from the given DataInput. With this method, inner
+     * classes cannot be loaded, and custom attributes cannot be defined.
+     *
+     * @param din source of class file data
+     * @throws IOException for I/O error or if classfile is invalid.
+     * @throws ArrayIndexOutOfBoundsException if a constant pool index is out
+     * of range.
+     * @throws ClassCastException if a constant pool index references the
+     * wrong type.
+     */
+    public static ClassFile readFrom(DataInput din) throws IOException {
+        return readFrom(din, null, null);
+    }
+
+    /**
+     * Reads a ClassFile from the given InputStream. A
+     * {@link ClassFileDataLoader} may be provided, which allows inner class
+     * definitions to be loaded. Also, an {@link AttributeFactory} may be
+     * provided, which allows non-standard attributes to be read. All
+     * remaining unknown attribute types are captured, but are not decoded.
+     *
+     * @param in source of class file data
+     * @param loader optional loader for reading inner class definitions
+     * @param attrFactory optional factory for reading custom attributes
+     * @throws IOException for I/O error or if classfile is invalid.
+     * @throws ArrayIndexOutOfBoundsException if a constant pool index is out
+     * of range.
+     * @throws ClassCastException if a constant pool index references the
+     * wrong type.
+     */
+    public static ClassFile readFrom(InputStream in,
+                                     ClassFileDataLoader loader,
+                                     AttributeFactory attrFactory)
+        throws IOException
+    {
+        if (!(in instanceof DataInput)) {
+            in = new DataInputStream(in);
+        }
+        return readFrom((DataInput)in, loader, attrFactory);
+    }
+
+    /**
+     * Reads a ClassFile from the given DataInput. A
+     * {@link ClassFileDataLoader} may be provided, which allows inner class
+     * definitions to be loaded. Also, an {@link AttributeFactory} may be
+     * provided, which allows non-standard attributes to be read. All
+     * remaining unknown attribute types are captured, but are not decoded.
+     *
+     * @param din source of class file data
+     * @param loader optional loader for reading inner class definitions
+     * @param attrFactory optional factory for reading custom attributes
+     * @throws IOException for I/O error or if classfile is invalid.
+     * @throws ArrayIndexOutOfBoundsException if a constant pool index is out
+     * of range.
+     * @throws ClassCastException if a constant pool index references the
+     * wrong type.
+     */
+    public static ClassFile readFrom(DataInput din,
+                                     ClassFileDataLoader loader,
+                                     AttributeFactory attrFactory)
+        throws IOException
+    {
+        return readFrom(din, loader, attrFactory, new HashMap(11), null);
+    }
+
+    /**
+     * @param loadedClassFiles Maps name to ClassFiles for classes already
+     * loaded. This prevents infinite loop: inner loads outer loads inner...
+     */
+    private static ClassFile readFrom(DataInput din,
+                                      ClassFileDataLoader loader,
+                                      AttributeFactory attrFactory,
+                                      Map loadedClassFiles,
+                                      ClassFile outerClass)
+        throws IOException
+    {
+        int magic = din.readInt();
+        if (magic != MAGIC) {
+            throw new IOException("Incorrect magic number: 0x" + 
+                                  Integer.toHexString(magic));
+        }
+
+        int minor = din.readUnsignedShort();
+        /*
+        if (minor != JDK1_1_MINOR_VERSION) {
+            throw new IOException("Minor version " + minor + 
+                                  " not supported, version " + 
+                                  JDK1_1_MINOR_VERSION + " is.");
+        }
+        */
+
+        int major = din.readUnsignedShort();
+        /*
+        if (major != JDK1_1_MAJOR_VERSION) {
+            throw new IOException("Major version " + major + 
+                                  "not supported, version " + 
+                                  JDK1_1_MAJOR_VERSION + " is.");
+        }
+        */
+
+        ConstantPool cp = ConstantPool.readFrom(din);
+        AccessFlags accessFlags = new AccessFlags(din.readUnsignedShort());
+        accessFlags.setSynchronized(false);
+
+        int index = din.readUnsignedShort();
+        ConstantClassInfo thisClass = (ConstantClassInfo)cp.getConstant(index);
+
+        index = din.readUnsignedShort();
+        ConstantClassInfo superClass = null;
+        if (index > 0) {
+            superClass = (ConstantClassInfo)cp.getConstant(index);
+        }
+
+        ClassFile cf =
+            new ClassFile(cp, accessFlags, thisClass, superClass, outerClass);
+        loadedClassFiles.put(cf.getClassName(), cf);
+
+        // Read interfaces.
+        int size = din.readUnsignedShort();
+        for (int i=0; i<size; i++) {
+            index = din.readUnsignedShort();
+            ConstantClassInfo info = (ConstantClassInfo)cp.getConstant(index);
+            cf.addInterface(info.getClassName());
+        }
+        
+        // Read fields.
+        size = din.readUnsignedShort();
+        for (int i=0; i<size; i++) {
+            cf.mFields.add(FieldInfo.readFrom(cf, din, attrFactory));
+        }
+        
+        // Read methods.
+        size = din.readUnsignedShort();
+        for (int i=0; i<size; i++) {
+            cf.mMethods.add(MethodInfo.readFrom(cf, din, attrFactory));
+        }
+
+        // Read attributes.
+        size = din.readUnsignedShort();
+        for (int i=0; i<size; i++) {
+            Attribute attr = Attribute.readFrom(cp, din, attrFactory);
+            cf.addAttribute(attr);
+            if (attr instanceof InnerClassesAttr) {
+                cf.mInnerClassesAttr = (InnerClassesAttr)attr;
+            }
+        }
+
+        // Load inner and outer classes.
+        if (cf.mInnerClassesAttr != null && loader != null) {
+            InnerClassesAttr.Info[] infos =
+                cf.mInnerClassesAttr.getInnerClassesInfo();
+            for (int i=0; i<infos.length; i++) {
+                InnerClassesAttr.Info info = infos[i];
+
+                if (thisClass.equals(info.getInnerClass())) {
+                    // This class is an inner class.
+                    if (info.getInnerClassName() != null) {
+                        cf.mInnerClassName = info.getInnerClassName();
+                    }
+                    ConstantClassInfo outer = info.getOuterClass();
+                    if (cf.mOuterClass == null && outer != null) {
+                        cf.mOuterClass = readOuterClass
+                            (outer, loader, attrFactory, loadedClassFiles);
+                    }
+                    AccessFlags innerFlags = info.getAccessFlags();
+                    accessFlags.setStatic(innerFlags.isStatic());
+                    accessFlags.setPrivate(innerFlags.isPrivate());
+                    accessFlags.setProtected(innerFlags.isProtected());
+                    accessFlags.setPublic(innerFlags.isPublic());
+                }
+                else if (thisClass.equals(info.getOuterClass())) {
+                    // This class is an outer class.
+                    ConstantClassInfo inner = info.getInnerClass();
+                    if (inner != null) {
+                        ClassFile innerClass = readInnerClass
+                            (inner, loader, attrFactory, loadedClassFiles, cf);
+                        
+                        if (innerClass != null) {
+                            if (innerClass.getInnerClassName() == null) {
+                                innerClass.mInnerClassName =
+                                    info.getInnerClassName();
+                            }
+                            if (cf.mInnerClasses == null) {
+                                cf.mInnerClasses = new ArrayList();
+                            }
+                            cf.mInnerClasses.add(innerClass);
+                        }
+                    }
+                }
+            }
+        }
+
+        return cf;
+    }
+
+    private static ClassFile readOuterClass(ConstantClassInfo outer,
+                                            ClassFileDataLoader loader,
+                                            AttributeFactory attrFactory,
+                                            Map loadedClassFiles)
+        throws IOException
+    {
+        String name = outer.getClassName();
+
+        ClassFile outerClass = (ClassFile)loadedClassFiles.get(name);
+        if (outerClass != null) {
+            return outerClass;
+        }
+
+        InputStream in = loader.getClassData(name);
+        if (in == null) {
+            return null;
+        }
+
+        if (!(in instanceof DataInput)) {
+            in = new DataInputStream(in);
+        }
+
+        return readFrom
+            ((DataInput)in, loader, attrFactory, loadedClassFiles, null);
+    }
+
+    private static ClassFile readInnerClass(ConstantClassInfo inner,
+                                            ClassFileDataLoader loader,
+                                            AttributeFactory attrFactory,
+                                            Map loadedClassFiles,
+                                            ClassFile outerClass)
+        throws IOException
+    {
+        String name = inner.getClassName();
+
+        ClassFile innerClass = (ClassFile)loadedClassFiles.get(name);
+        if (innerClass != null) {
+            return innerClass;
+        }
+
+        InputStream in = loader.getClassData(name);
+        if (in == null) {
+            return null;
+        }
+
+        if (!(in instanceof DataInput)) {
+            in = new DataInputStream(in);
+        }
+
+        return readFrom
+            ((DataInput)in, loader, attrFactory, loadedClassFiles, outerClass);
+    }
+}