org.enerj.enhancer.ClassEnhancer.java Source code

Java tutorial

Introduction

Here is the source code for org.enerj.enhancer.ClassEnhancer.java

Source

/*******************************************************************************
 * Copyright 2000, 2006 Visual Systems Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License version 2
 * which accompanies this distribution in a file named "COPYING".
 * 
 * This program 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 of the License, or
 * (at your option) any later version.
 *      
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *******************************************************************************/
//Ener-J
//Copyright 2001-2005 Visual Systems Corporation
//$Header: /cvsroot/ener-j/ener-j/src/org/enerj/enhancer/ClassEnhancer.java,v 1.12 2006/06/06 22:41:27 dsyrstad Exp $

package org.enerj.enhancer;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.Modifier;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.enerj.annotations.SchemaAnnotation;
import org.enerj.core.ObjectSerializer;
import org.enerj.core.Persistable;
import org.enerj.core.PersistableHelper;
import org.enerj.core.PersistentAware;
import org.enerj.core.Persister;
import org.enerj.core.SystemCIDMap;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

/**
 * Class file enhancer/ASM vistor. An instance enhances a single class.
 * <p>
 * This can throw EnhancerException (runtime exception) from any of the ASM visitor methods.  
 *
 * @version $Id: ClassEnhancer.java,v 1.12 2006/06/06 22:41:27 dsyrstad Exp $
 * @author <a href="mailto:dsyrstad@ener-j.org">Dan Syrstad</a>
 */
class ClassEnhancer extends ClassAdapter implements Opcodes {
    private static final String FIELD_ACCESSOR_PREFIX = "enerj_Get_";
    private static final String FIELD_MUTATOR_PREFIX = "enerj_Set_";
    private static final String sPostLoadMethodName = "enerjPostLoad";
    private static final String sPreStoreMethodName = "enerjPreStore";
    private static final String sPostStoreMethodName = "enerjPostStore";
    private static final String sPreHollowMethodName = "enerjPreHollow";
    private static final String sClassIdFieldName = "enerj_sClassId";

    private static final String sPersisterDescr = Type.getDescriptor(Persister.class);
    private static final String sNoArgMethodSignature = "()V";
    private static final String sSchemaAnnotationDescr = Type.getDescriptor(SchemaAnnotation.class);
    private static final String sPersistableClassSlashed = Type.getInternalName(Persistable.class);
    private static final String sPersistableClassDescr = Type.getDescriptor(Persistable.class);
    private static final String sPersistableVoidSignature = '(' + sPersistableClassDescr + ")V";
    private static final String sPersistentAwareClassSlashed = Type.getInternalName(PersistentAware.class);
    private static final String sPersistableHelperClassSlashed = Type.getInternalName(PersistableHelper.class);
    private static final String sObjectSerializerClassNameSlashed = Type.getInternalName(ObjectSerializer.class);
    private static final String sObjectSerializerClassDescr = Type.getDescriptor(ObjectSerializer.class);
    private static final String sDataInputClassNameSlashed = Type.getInternalName(DataInput.class);
    private static final String sDataOutputClassNameSlashed = Type.getInternalName(DataOutput.class);
    private static final String sObjectInputStreamClassNameSlashed = Type.getInternalName(ObjectInputStream.class);
    private static final String sSerializableClassNameSlashed = Type.getInternalName(Serializable.class);

    private static final String sPersisterConstructorSignature = '(' + Type.getDescriptor(Persister.class) + ")V";
    private static final String sReadWriteObjectMethodSignature = '(' + sObjectSerializerClassDescr + ")V";
    private static final String sResolveObjectMethodSignature = '(' + sObjectSerializerClassDescr + "Z)V";

    private byte[] mOriginalClassBytes;
    private MetaData mMetaData;
    private String mClassName;
    private boolean mIsOnlyPersistentAware;
    private boolean mIsPersistable;
    private boolean mIsSerializable;
    private boolean mHasReadObjectMethod;
    private String mSuperClassName;
    private String mSuperClassNameSlashed;
    // If the super-class is not a true Persistable, this is the top-level persistable class.
    private boolean mIsTopLevelPersistable;
    private long mClassId;
    private String mThisClassDescr;
    private String mThisClassNameSlashed;
    // Collected method information.
    private boolean mHasNoArgConstructor = false;
    private boolean mHasPostLoad = false;
    private boolean mHasPreStore = false;
    private boolean mHasPostStore = false;
    private boolean mHasPreHollow = false;
    private boolean mEnhancedClone = false;
    private boolean mEnhancedClinit = false;
    private ArrayList<Field> mPersistentFields = new ArrayList<Field>();
    private ArrayList<Field> mTransientFields = new ArrayList<Field>();
    // Map from field name to Field.
    private Map<String, Field> mDeclaredFields = new HashMap<String, Field>();

    /**
     * Construct a ClassEnhancer. 
     *
     * @param aClassVisitor the ClassVisitor that is the delegate (usually a ClassWriter).
     * @param aClassName the fully qualified dotted class name.
     * @param anOriginalClassFile the original class file bytes before enhancement.
     * @param aMetaData the enhancer MetaData.
     */
    ClassEnhancer(ClassVisitor aClassVisitor, String aClassName, byte[] anOriginalClassFile, MetaData aMetaData) {
        super(aClassVisitor);

        mOriginalClassBytes = anOriginalClassFile;
        mMetaData = aMetaData;
        mClassName = aClassName;

        mThisClassNameSlashed = aClassName.replace('.', '/');
        mThisClassDescr = 'L' + mThisClassNameSlashed + ';';
    }

    /** 
     * {@inheritDoc}
     * Enhances at the class level. 
     * @see org.objectweb.asm.ClassAdapter#visit(int, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])
     */
    public void visit(int aVersion, int someAccessModifiers, String aName, String aSignature,
            String aSuperClassName, String[] someInterfaces) {
        try {
            // If this class already implements Persistable, don't enhance it.
            for (int i = 0; someInterfaces != null && i < someInterfaces.length; i++) {
                String interfaceName = someInterfaces[i];
                if (interfaceName.equals(sPersistableClassSlashed)
                        || interfaceName.equals(sPersistentAwareClassSlashed)) {
                    throw new AlreadyEnhancedException(
                            "Class " + mClassName + " has already been enhanced - skipping.", null);
                }

                if (interfaceName.equals(sSerializableClassNameSlashed)) {
                    mIsSerializable = true;
                }
            }

            // Don't enhance interfaces
            if ((someAccessModifiers & ACC_INTERFACE) == ACC_INTERFACE) {
                throw new SkipEnhancementException();
            }

            //System.out.println("Enhancing Class: " + mClassName);

            mIsOnlyPersistentAware = mMetaData.isClassOnlyPersistentAware(mClassName);
            mIsPersistable = !mIsOnlyPersistentAware;

            // If the super-class is not a true Persistable, this is the top-level persistable class.
            mSuperClassNameSlashed = aSuperClassName;
            mSuperClassName = mSuperClassNameSlashed.replace('/', '.');
            mIsTopLevelPersistable = mIsPersistable && !mMetaData.isClassAFCO(mSuperClassName);

            // Determine the class' CID. Special cases: Schema classes.
            mClassId = SystemCIDMap.getSystemCIDForClassName(mClassName);
            if (mClassId == ObjectSerializer.NULL_CID) {
                mClassId = generateClassId(mOriginalClassBytes);
            }

            // Add interfaces.
            String interfaceName = null;
            if (mIsPersistable) {
                interfaceName = sPersistableClassSlashed;
            } else if (mIsOnlyPersistentAware) {
                interfaceName = sPersistentAwareClassSlashed;
            }

            if (interfaceName != null) {
                String[] newInterfaces = new String[someInterfaces.length + 1];
                System.arraycopy(someInterfaces, 0, newInterfaces, 0, someInterfaces.length);
                newInterfaces[someInterfaces.length] = interfaceName;
                someInterfaces = newInterfaces;
            }

            cv.visit(aVersion, someAccessModifiers, aName, aSignature, aSuperClassName, someInterfaces);
        } catch (MetaDataException e) {
            throw new EnhancerException("Error processing class " + aName, e);
        }
    }

    /** 
     * {@inheritDoc}
     * Enhances methods.
     * @see org.objectweb.asm.ClassAdapter#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])
     */
    public MethodVisitor visitMethod(int someAccessModifiers, String aMethodName, String aDescriptor,
            String aSignature, String[] someExceptions) {
        MethodVisitor mv = cv.visitMethod(someAccessModifiers, aMethodName, aDescriptor, aSignature,
                someExceptions);

        // Check for lifecycle "hooks".
        if (aDescriptor.equals(sNoArgMethodSignature)) {
            if (aMethodName.equals(sPostLoadMethodName)) {
                mHasPostLoad = true;
            } else if (aMethodName.equals(sPreStoreMethodName)) {
                mHasPreStore = true;
            } else if (aMethodName.equals(sPostStoreMethodName)) {
                mHasPostStore = true;
            } else if (aMethodName.equals(sPreHollowMethodName)) {
                mHasPreHollow = true;
            }
        }

        boolean enhanceConstructor = false;
        boolean enhanceClone = false;
        boolean enhanceExternalOnly = false;
        boolean enhanceReadObject = false;

        // Is it a constructor?
        if (aMethodName.equals("<init>") && mIsPersistable) {
            enhanceExternalOnly = true;
            if (mIsTopLevelPersistable) {
                // For top-level persistable classes, do special enhancement to set 'new' persistable flag, etc.
                enhanceConstructor = true;
            }
            // else non-top-level Persistable. Do not change constructor.

            if (aDescriptor.equals(sNoArgMethodSignature)) {
                mHasNoArgConstructor = true;
            }
        } else if (aMethodName.equals("clone") && aDescriptor.equals("()Ljava/lang/Object;")) {
            enhanceExternalOnly = true;
            if (mIsTopLevelPersistable) {
                // Enhance clone() on top-level Persistables
                enhanceClone = true;
                mEnhancedClone = true;
            }
        } else if (mIsTopLevelPersistable && aMethodName.equals("readObject")
                && aDescriptor.equals("(Ljava/io/ObjectInputStream;)V")) {
            mHasReadObjectMethod = true;
            enhanceReadObject = true;
        }

        // Enhance method getfield/putfield/getstatic/putstatic access.
        // Yes, even <init> is enhanced because it could create and access fields of other Persistables.
        // Enhance everything but "enerj_*" methods, abstract, and native methods.
        if (!aMethodName.startsWith("enerj_") && !Modifier.isAbstract(someAccessModifiers)
                && !Modifier.isNative(someAccessModifiers)) {
            mv = new MethodEnhancer(mv, aMethodName, enhanceExternalOnly, enhanceConstructor, enhanceClone,
                    enhanceReadObject);
        }

        return mv;
    }

    /** 
     * {@inheritDoc}
     * @see org.objectweb.asm.ClassAdapter#visitField(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object)
     */
    public FieldVisitor visitField(int someAccessModifiers, String aName, String aDescriptor, String aSignature,
            Object aValue) {
        try {
            Field field = new Field(aName, aDescriptor, someAccessModifiers);
            mDeclaredFields.put(aName, field);

            if (mIsPersistable) {
                // Generate method to replace field access.
                // Mediation methods are generated for all fields, even transients
                // so that schema evolution proxies will operate on the delegate of
                // the proxy.
                if (mMetaData.isFieldPersistent(mClassName, aName, someAccessModifiers)) {
                    // Persistent field...
                    mPersistentFields.add(field);
                    emitPersistentFieldMediationMethods(field);
                } else {
                    // Transient field...
                    mTransientFields.add(field);
                    if ((someAccessModifiers & ACC_STATIC) == ACC_STATIC) {
                        emitStaticFieldMediationMethods(field);
                    } else {
                        emitTransientFieldMediationMethods(field);
                    }
                }
            }

            return cv.visitField(someAccessModifiers, aName, aDescriptor, aSignature, aValue);
        } catch (MetaDataException e) {
            throw new EnhancerException("Error processing class " + mClassName, e);
        }
    }

    /** 
     * {@inheritDoc}
     * @see org.objectweb.asm.ClassAdapter#visitEnd()
     */
    public void visitEnd() {
        // If this is the top-level Persistable class, add the enerj_ fields, and enerj_ core methods to it.
        if (mIsTopLevelPersistable) {
            enhanceTopLevelClass();
        }

        if (mIsPersistable) {
            // Make sure field overrides actually exist in the class
            try {
                mMetaData.validateFieldOverrides(mClassName, mDeclaredFields);
            } catch (MetaDataException e) {
                throw new EnhancerException("Error processing class " + mClassName, e);
            }
        }

        // If we didn't find a clone method, generate one, even if the class doesn't implement Cloneable.
        if (mIsTopLevelPersistable && !mEnhancedClone) {
            emitClone();
        }

        // If this is a Top-Level Persistable and the class implements Serializable and 
        // does not implement readObject(ObjectInputStream)
        // emit a readObject method so that initPersistable is called.
        if (mIsTopLevelPersistable && mIsSerializable && !mHasReadObjectMethod) {
            emitReadObject();
        }

        if (mIsPersistable) {
            // Create the schema info and add it as an annotation to the enhanced class file.
            AnnotationVisitor av = cv.visitAnnotation(sSchemaAnnotationDescr, true);
            av.visit("originalByteCodes", mOriginalClassBytes);
            av.visit("classID", Long.valueOf(mClassId));

            AnnotationVisitor av1 = av.visitArray("persistentFieldNames");
            List<Field> somePersistentFields = getPersistentFields();
            for (int i = 0; i < somePersistentFields.size(); i++) {
                Field field = (Field) somePersistentFields.get(i);
                av1.visit(null, field.getName());
            }

            av1.visitEnd();

            av1 = av.visitArray("transientFieldNames");
            List<Field> someTransientFields = getTransientFields();
            for (int i = 0; i < someTransientFields.size(); i++) {
                Field field = (Field) someTransientFields.get(i);
                av1.visit(null, field.getName());
            }

            av1.visitEnd();
            av.visitEnd();

            // Emit constructor used to instantiate objects from the databse.
            emitSpecialConstructor();

            // Generate enerj_ReadObject, enerj_WriteObject, and enerj_ResolveObject methods.
            emitReadObject(mPersistentFields);
            emitWriteObject(mPersistentFields);
            emitResolveObject(mPersistentFields);

            emitHollow(mPersistentFields);

            // Emit the class id static variable and getter on every Persistable.
            emitClassId();

            if (!mEnhancedClinit) {
                // Add <clinit> to initialize the class Id.
                emitClassInit();
            }
        }

        cv.visitEnd();
    }

    /**
     * Generate class Id.<p>
     *
     * @param someBytesCodes the unenhanced bytecodes of the class.
     *
     * @return a class Id that does not conflict with the system class Id range
     *  of [ObjectServer.NULL_CID..ObjectServer.LAST_SYSTEM_CID).
     *
     * @throws EnhancerException if an error occurs (e.g., java.security.NoSuchAlgorithmException).
     */
    private static long generateClassId(byte[] someByteCodes) throws EnhancerException {
        try {
            java.security.MessageDigest sha1Digest = java.security.MessageDigest.getInstance("SHA-1");
            byte[] sha1 = sha1Digest.digest(someByteCodes);
            long cid = (long) (sha1[0] & 0xff) | ((long) (sha1[1] & 0xff) << 8) | ((long) (sha1[2] & 0xff) << 16)
                    | ((long) (sha1[3] & 0xff) << 24) | ((long) (sha1[4] & 0xff) << 32)
                    | ((long) (sha1[5] & 0xff) << 40) | ((long) (sha1[6] & 0xff) << 48)
                    | ((long) (sha1[7] & 0xff) << 56);

            if (cid >= ObjectSerializer.NULL_CID && cid <= ObjectSerializer.LAST_SYSTEM_CID) {
                // Shift it up to make it a valid user CID.
                cid += ObjectSerializer.LAST_SYSTEM_CID;
            }

            return cid;
        } catch (NoSuchAlgorithmException e) {
            throw new EnhancerException("Cannot create class ID", e);
        }
    }

    /**
     * Modify a top-level Persistable class to add the core "enerj_" fields and methods.
     * This generally implements the Persistable interface.
     */
    private void enhanceTopLevelClass() {
        // enerj_ Fields
        String boolDescr = Type.BOOLEAN_TYPE.getDescriptor();
        String longDescr = Type.LONG_TYPE.getDescriptor();
        String intDescr = Type.INT_TYPE.getDescriptor();

        cv.visitField(ACC_PROTECTED | ACC_TRANSIENT, "enerj_mModified", boolDescr, null, null);
        cv.visitField(ACC_PROTECTED | ACC_TRANSIENT, "enerj_mNew", boolDescr, null, null);
        cv.visitField(ACC_PROTECTED | ACC_TRANSIENT, "enerj_mLoaded", boolDescr, null, null);
        cv.visitField(ACC_PROTECTED | ACC_TRANSIENT, "enerj_mAllowNonTransactionalReads", boolDescr, null, null);
        cv.visitField(ACC_PROTECTED | ACC_TRANSIENT, "enerj_mAllowNonTransactionalWrites", boolDescr, null, null);

        cv.visitField(ACC_PRIVATE | ACC_TRANSIENT, "enerj_mVersion", longDescr, null, null);
        cv.visitField(ACC_PRIVATE | ACC_TRANSIENT, "enerj_mOID", longDescr, null, null);
        cv.visitField(ACC_PRIVATE | ACC_TRANSIENT, "enerj_mPersister", sPersisterDescr, null, null);
        cv.visitField(ACC_PRIVATE | ACC_TRANSIENT, "enerj_mLockLevel", intDescr, null, null);

        // Methods
        //   Simple Accessors
        emitAccessorMethod(ACC_PUBLIC | ACC_FINAL, boolDescr, "enerj_IsModified", "enerj_mModified");
        emitAccessorMethod(ACC_PUBLIC | ACC_FINAL, boolDescr, "enerj_IsNew", "enerj_mNew");
        emitAccessorMethod(ACC_PUBLIC | ACC_FINAL, boolDescr, "enerj_IsLoaded", "enerj_mLoaded");
        emitAccessorMethod(ACC_PUBLIC | ACC_FINAL, boolDescr, "enerj_AllowsNonTransactionalRead",
                "enerj_mAllowNonTransactionalReads");
        emitAccessorMethod(ACC_PUBLIC | ACC_FINAL, boolDescr, "enerj_AllowsNonTransactionalWrite",
                "enerj_mAllowNonTransactionalWrites");
        emitAccessorMethod(ACC_PUBLIC | ACC_FINAL, longDescr, "enerj_GetVersion", "enerj_mVersion");
        emitAccessorMethod(ACC_PUBLIC | ACC_FINAL, longDescr, "enerj_GetPrivateOID", "enerj_mOID");
        emitAccessorMethod(ACC_PUBLIC | ACC_FINAL, sPersisterDescr, "enerj_GetPersister", "enerj_mPersister");
        emitAccessorMethod(ACC_PUBLIC | ACC_FINAL, intDescr, "enerj_GetLockLevel", "enerj_mLockLevel");

        //   Simple Mutators
        emitMutatorMethod(ACC_PUBLIC | ACC_FINAL, boolDescr, "enerj_SetModified", "enerj_mModified");
        emitMutatorMethod(ACC_PUBLIC | ACC_FINAL, boolDescr, "enerj_SetNew", "enerj_mNew");
        emitMutatorMethod(ACC_PUBLIC | ACC_FINAL, boolDescr, "enerj_SetLoaded", "enerj_mLoaded");
        emitMutatorMethod(ACC_PUBLIC | ACC_FINAL, boolDescr, "enerj_SetAllowNonTransactionalRead",
                "enerj_mAllowNonTransactionalReads");
        emitMutatorMethod(ACC_PUBLIC | ACC_FINAL, boolDescr, "enerj_SetAllowNonTransactionalWrite",
                "enerj_mAllowNonTransactionalWrites");
        emitMutatorMethod(ACC_PUBLIC | ACC_FINAL, longDescr, "enerj_SetVersion", "enerj_mVersion");
        emitMutatorMethod(ACC_PUBLIC | ACC_FINAL, longDescr, "enerj_SetPrivateOID", "enerj_mOID");
        emitMutatorMethod(ACC_PUBLIC | ACC_FINAL, sPersisterDescr, "enerj_SetPersister", "enerj_mPersister");
        emitMutatorMethod(ACC_PUBLIC | ACC_FINAL, intDescr, "enerj_SetLockLevel", "enerj_mLockLevel");
    }

    /**
     * Emits a readObject() method on a top-level persistable that implements
     * Serializable.
     */
    private void emitReadObject() {
        MethodVisitor mv = cv.visitMethod(ACC_PRIVATE, "readObject", "(Ljava/io/ObjectInputStream;)V", null,
                new String[] { "java/io/IOException", "java/lang/ClassNotFoundException" });
        mv.visitCode();
        // Call PersistableHelper.initPersistable(this)
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESTATIC, sPersistableHelperClassSlashed, "initPersistable",
                sPersistableVoidSignature);
        // Call defaultLoadObject to complete the load.
        mv.visitVarInsn(ALOAD, 1);
        mv.visitMethodInsn(INVOKEVIRTUAL, sObjectInputStreamClassNameSlashed, "defaultReadObject", "()V");
        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Emit a simple, generic accessor method.
     */
    private void emitAccessorMethod(int someAccessFlags, String aDescriptor, String aMethodName,
            String aFieldName) {
        MethodVisitor mv = cv.visitMethod(someAccessFlags, aMethodName, "()" + aDescriptor, null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, aFieldName, aDescriptor);
        mv.visitInsn(getReturnOpcodeForDescriptor(aDescriptor));
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Emit a simple, generic accessor method.
     */
    private void emitMutatorMethod(int someAccessFlags, String aDescriptor, String aMethodName, String aFieldName) {
        MethodVisitor mv = cv.visitMethod(someAccessFlags, aMethodName, '(' + aDescriptor + ")V", null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(getLoadOpcodeForDescriptor(aDescriptor), 1);
        mv.visitFieldInsn(PUTFIELD, mThisClassNameSlashed, aFieldName, aDescriptor);
        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Gets the opcode offset for the specified descriptor.
     *
     * @param aDescriptor a descriptor.
     * 
     * @return the opcode as defined in 
     */
    private static int getOpcodeOffsetForDescriptor(String aDescriptor) {
        char first = aDescriptor.charAt(0);
        switch (first) {
        case 'Z':
        case 'C':
        case 'B':
        case 'S':
        case 'I':
            return 0;

        case 'J':
            return 1;

        case 'F':
            return 2;

        case 'D':
            return 3;

        case '[':
        case 'L':
            return 4;

        //case 'V': not loaded.
        default:
            throw new IllegalArgumentException("aDescriptor not recognized: " + aDescriptor);
        }
    }

    /**
     * Gets the xLOAD opcode for the specified descriptor.
     *
     * @param aDescriptor a descriptor.
     * 
     * @return the opcode as defined in 
     */
    private static int getLoadOpcodeForDescriptor(String aDescriptor) {
        return ILOAD + getOpcodeOffsetForDescriptor(aDescriptor);
    }

    /**
     * Gets the xRETURN opcode for the specified descriptor.
     *
     * @param aDescriptor a descriptor.
     * 
     * @return the opcode as defined in 
     */
    private static int getReturnOpcodeForDescriptor(String aDescriptor) {
        if (aDescriptor.charAt(0) == 'V') {
            return RETURN;
        }

        return IRETURN + getOpcodeOffsetForDescriptor(aDescriptor);
    }

    /**
     * Create the method name suffix used for a field getter or setter.
     *
     * @param aClassName the field's fully-qualified dotted class name.
     * @param aFieldName the field's name.
     *
     * @return the method name suffix.
     */
    private String getFieldMethodNameSuffix(String aClassName, String aFieldName) {
        // Change '.'s in class name to '_'
        return aClassName.replace('.', '_') + '_' + aFieldName;
    }

    /**
     * Generate the getfield/putfield replacement methods (enerj_Get_* and enerj_Set_*)
     * for persistent fields.
     * Parameters to the generated methods conveniently match the stack frame of
     * getfield and putfield.
     *
     * @param aField a Field for which the methods will be generated.
     */
    private void emitPersistentFieldMediationMethods(Field aField) {
        int fieldScope = aField.getAccessModifiers() & (ACC_PRIVATE | ACC_PUBLIC | ACC_PROTECTED);
        String fieldName = aField.getName();
        String methodNameSuffix = getFieldMethodNameSuffix(mClassName, fieldName);
        String fieldType = aField.getDescriptor();
        int opcodeOffset = getOpcodeOffsetForDescriptor(fieldType);
        // TODO do we need to worry about fields with parameterized types? 
        //String genericSignature = Type.getDescriptor( aField.getGenericType() );

        MethodVisitor mv = cv.visitMethod(fieldScope | ACC_STATIC, FIELD_ACCESSOR_PREFIX + methodNameSuffix,
                '(' + mThisClassDescr + ')' + fieldType, null, null);
        Label label0 = new Label();
        mv.visitLabel(label0);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, "enerj_mLoaded", "Z");
        Label label1 = new Label();
        mv.visitJumpInsn(IFNE, label1);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, "enerj_mNew", "Z");
        mv.visitJumpInsn(IFNE, label1);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitInsn(ICONST_0);
        mv.visitMethodInsn(INVOKESTATIC, sPersistableHelperClassSlashed, "checkLoaded",
                "(Lorg/enerj/core/Persistable;Z)V");
        mv.visitLabel(label1);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, fieldName, fieldType);
        mv.visitInsn(getReturnOpcodeForDescriptor(fieldType));
        Label label3 = new Label();
        mv.visitLabel(label3);
        mv.visitLocalVariable("anInstance", mThisClassDescr, null, label0, label3, 0);
        mv.visitMaxs(0, 0);

        ///--------------
        // Mutator (Setter). Because of the == comparison, the method is a little different
        // for primitives, Strings (final class that implements equals() properly), and regular objects.

        mv = cv.visitMethod(fieldScope | ACC_STATIC, FIELD_MUTATOR_PREFIX + methodNameSuffix,
                '(' + mThisClassDescr + fieldType + ")V", null, null);
        mv.visitCode();

        // Check if checkLoaded needs to be called. Call if (!enerj_mLoaded && !enerj_mNew)
        Label mutatorStartLabel = new Label();
        mv.visitLabel(mutatorStartLabel);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, "enerj_mLoaded", "Z");
        Label mutatorLabel1 = new Label();
        mv.visitJumpInsn(IFNE, mutatorLabel1);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, "enerj_mNew", "Z");
        mv.visitJumpInsn(IFNE, mutatorLabel1);

        // Call checkLoaded
        mv.visitVarInsn(ALOAD, 0);
        mv.visitInsn(ICONST_1);
        mv.visitMethodInsn(INVOKESTATIC, sPersistableHelperClassSlashed, "checkLoaded",
                "(Lorg/enerj/core/Persistable;Z)V");
        mv.visitLabel(mutatorLabel1);

        // Check if the field's value is actually changing. For primitives, we use ==;
        // for special classes declared final (e.g., String, Integer), we use equals(); 
        // and for other objects we use == (identity).

        Label notModifiedLabel = new Label();

        if (MetaData.isPrimitive(fieldType)) {
            // Push parameter 1 - the new value
            mv.visitVarInsn(ILOAD + opcodeOffset, 1);
            // This
            mv.visitVarInsn(ALOAD, 0);
            // Current value
            mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, fieldName, fieldType);

            char type = fieldType.charAt(0);
            switch (type) {
            case 'B':
            case 'Z':
            case 'C':
            case 'S':
            case 'I':
                mv.visitJumpInsn(IF_ICMPEQ, notModifiedLabel);
                break;

            case 'F':
                mv.visitInsn(FCMPL);
                mv.visitJumpInsn(IFEQ, notModifiedLabel);
                break;

            case 'J':
                mv.visitInsn(LCMP);
                mv.visitJumpInsn(IFEQ, notModifiedLabel);
                break;

            case 'D':
                mv.visitInsn(DCMPL);
                mv.visitJumpInsn(IFEQ, notModifiedLabel);
                break;

            default:
                throw new RuntimeException("Unknown primitive type: " + type);
            }
        } else if (fieldType.equals("Ljava/lang/String;") || fieldType.equals("Ljava/lang/Integer;")
                || fieldType.equals("Ljava/lang/Long;") || fieldType.equals("Ljava/lang/Byte;")
                || fieldType.equals("Ljava/lang/Boolean;") || fieldType.equals("Ljava/lang/Character;")
                || fieldType.equals("Ljava/lang/Short;") || fieldType.equals("Ljava/lang/Float;")
                || fieldType.equals("Ljava/lang/Double;")) {

            // One of the core final immutable types. Use equals() to compare values, like this:
            // "if ((aValue == null{1} && anInstance.mString != null{2}) || (aValue != null{3} && !aValue.equals(anInstance.mString){4})" ...

            // {1}: aValue == null
            mv.visitVarInsn(ALOAD, 1);
            Label imEqualsLabel = new Label();
            mv.visitJumpInsn(IFNONNULL, imEqualsLabel);

            // {2}: anInstance.mString != null
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, fieldName, fieldType);
            Label imStoreLabel = new Label();
            mv.visitJumpInsn(IFNONNULL, imStoreLabel);

            // {3}: aValue != null
            mv.visitLabel(imEqualsLabel);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitJumpInsn(IFNULL, notModifiedLabel);

            // {4}: equals()...
            // Push parameter 1 - the new value
            mv.visitVarInsn(ALOAD, 1);
            // Current Value
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, fieldName, fieldType);
            // Compare !equals()
            mv.visitMethodInsn(INVOKEVIRTUAL, aField.getInternalName(), "equals", "(Ljava/lang/Object;)Z");
            mv.visitJumpInsn(IFNE, notModifiedLabel);

            mv.visitLabel(imStoreLabel);
        } else {
            // Some other Object -- use identity ==
            // Push parameter 1 - the new value
            mv.visitVarInsn(ALOAD, 1);
            // This
            mv.visitVarInsn(ALOAD, 0);
            // Current value
            mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, fieldName, fieldType);
            mv.visitJumpInsn(IF_ACMPEQ, notModifiedLabel);
        }

        // Store the value
        // Mark owner object as modified - short circuit if already marked modified
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, "enerj_mModified", "Z");
        Label mutatorLabel2 = new Label();
        mv.visitJumpInsn(IFNE, mutatorLabel2);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESTATIC, sPersistableHelperClassSlashed, "addModified", sPersistableVoidSignature);
        mv.visitLabel(mutatorLabel2);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ILOAD + opcodeOffset, 1);
        mv.visitFieldInsn(PUTFIELD, mThisClassNameSlashed, fieldName, fieldType);
        mv.visitLabel(notModifiedLabel);
        mv.visitInsn(RETURN);

        Label mutatorEndlabel = new Label();
        mv.visitLabel(mutatorEndlabel);
        mv.visitLocalVariable("anInstance", mThisClassDescr, null, mutatorStartLabel, mutatorEndlabel, 0);
        mv.visitLocalVariable("aValue", fieldType, null, mutatorStartLabel, mutatorEndlabel, 1);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Generate the getfield/putfield replacement methods (enerj_Get_* and enerj_Set_*)
     * for non-static transient fields.
     * Parameters to the generated methods conveniently match the stack frame of
     * getfield and putfield.
     *
     * @param aField a Field for which the methods will be generated.
     */
    private void emitTransientFieldMediationMethods(Field aField) {
        int fieldScope = aField.getAccessModifiers() & (ACC_PRIVATE | ACC_PUBLIC | ACC_PROTECTED);
        String fieldName = aField.getName();
        String methodNameSuffix = getFieldMethodNameSuffix(mClassName, fieldName);
        String fieldType = aField.getDescriptor();

        // Accessor (Getter).
        MethodVisitor mv = cv.visitMethod(fieldScope | ACC_STATIC, FIELD_ACCESSOR_PREFIX + methodNameSuffix,
                '(' + mThisClassDescr + ')' + fieldType, null, null);
        mv.visitCode();
        Label startLabel = new Label();
        mv.visitLabel(startLabel);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, fieldName, fieldType);
        mv.visitInsn(getReturnOpcodeForDescriptor(fieldType));
        Label endLabel = new Label();
        mv.visitLabel(endLabel);
        mv.visitLocalVariable("anInstance", mThisClassDescr, null, startLabel, endLabel, 0);
        mv.visitMaxs(0, 0);
        mv.visitEnd();

        // Mutator (Setter).
        mv = cv.visitMethod(fieldScope | ACC_STATIC, FIELD_MUTATOR_PREFIX + methodNameSuffix,
                '(' + mThisClassDescr + fieldType + ")V", null, null);
        mv.visitCode();
        startLabel = new Label();
        mv.visitLabel(startLabel);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(getLoadOpcodeForDescriptor(fieldType), 1);
        mv.visitFieldInsn(PUTFIELD, mThisClassNameSlashed, fieldName, fieldType);
        mv.visitInsn(RETURN);
        endLabel = new Label();
        mv.visitLabel(endLabel);
        mv.visitLocalVariable("anInstance", mThisClassDescr, null, startLabel, endLabel, 0);
        mv.visitLocalVariable("aValue", fieldType, null, startLabel, endLabel, 1);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Generate the getstatic/putstatic replacement methods (enerj_Get_* and enerj_Set_*)
     * for static transient fields. 
     * Parameters to the generated methods conveniently match the stack frame of
     * getstatic and putstatic.
     *
     * @param aField a Field for which the methods will be generated.
     */
    private void emitStaticFieldMediationMethods(Field aField) {
        int fieldScope = aField.getAccessModifiers() & (ACC_PRIVATE | ACC_PUBLIC | ACC_PROTECTED);
        String fieldName = aField.getName();
        String methodNameSuffix = getFieldMethodNameSuffix(mClassName, fieldName);
        String fieldType = aField.getDescriptor();

        // Accessor (Getter).
        MethodVisitor mv = cv.visitMethod(fieldScope | ACC_STATIC, FIELD_ACCESSOR_PREFIX + methodNameSuffix,
                "()" + fieldType, null, null);
        mv.visitCode();
        mv.visitFieldInsn(GETSTATIC, mThisClassNameSlashed, fieldName, fieldType);
        mv.visitInsn(getReturnOpcodeForDescriptor(fieldType));
        mv.visitMaxs(0, 0);
        mv.visitEnd();

        // Mutator (Setter).
        mv = cv.visitMethod(fieldScope | ACC_STATIC, FIELD_MUTATOR_PREFIX + methodNameSuffix,
                '(' + fieldType + ")V", null, null);
        mv.visitCode();
        Label startLabel = new Label();
        mv.visitLabel(startLabel);
        mv.visitVarInsn(getLoadOpcodeForDescriptor(fieldType), 0);
        mv.visitFieldInsn(PUTSTATIC, mThisClassNameSlashed, fieldName, fieldType);
        mv.visitInsn(RETURN);
        Label endLabel = new Label();
        mv.visitLabel(endLabel);
        mv.visitLocalVariable("aValue", fieldType, null, startLabel, endLabel, 0);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Emit the special clone() method on a top-level persistable. This is
     * generated when a top-level Persistable doesn't have a clone method.
     * It ensures initPersistableClone is called if a sub-class implements clone().
     */
    private void emitClone() {
        // TODO Only add this exception if superclass throws it. Object does, while java.util.Date does not.
        // TODO Either that or never throw it and wrap the super.clone() in a try/catch.
        // TODO catch part should never happen. If it does, return null.
        String[] exceptions = new String[] { "java/lang/CloneNotSupportedException" };
        MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "clone", "()Ljava/lang/Object;", null, exceptions);

        mv.visitCode();
        Label startLabel = new Label();
        mv.visitLabel(startLabel);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, mSuperClassNameSlashed, "clone", "()Ljava/lang/Object;");
        mv.visitInsn(DUP);
        mv.visitTypeInsn(CHECKCAST, sPersistableClassSlashed);
        mv.visitMethodInsn(INVOKESTATIC, sPersistableHelperClassSlashed, "initPersistableClone",
                sPersistableVoidSignature);
        mv.visitInsn(ARETURN);
        Label endLabel = new Label();
        mv.visitLabel(endLabel);

        mv.visitLocalVariable("this", mThisClassDescr, null, startLabel, endLabel, 0);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Emits the enerj_ReadObject method.
     *
     * @param someFields a list of the persistent fields for this class (not including
     *  super-class fields).
     */
    private void emitReadObject(ArrayList<Field> someFields) {
        MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "enerj_ReadObject", sReadWriteObjectMethodSignature, null,
                new String[] { "java/io/IOException" });

        mv.visitCode();
        Label startLabel = new Label();
        mv.visitLabel(startLabel);

        mv.visitVarInsn(ALOAD, 1);
        mv.visitMethodInsn(INVOKEVIRTUAL, sObjectSerializerClassNameSlashed, "getDataInput",
                "()Ljava/io/DataInput;");
        mv.visitVarInsn(ASTORE, 2);

        if (!mIsTopLevelPersistable) {
            // Call super read if not a top-level persistable
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitMethodInsn(INVOKESPECIAL, mSuperClassNameSlashed, "enerj_ReadObject",
                    sReadWriteObjectMethodSignature);
        }

        for (Field field : someFields) {
            String fieldName = field.getName();
            String fieldType = field.getDescriptor();

            // This will return null if the type is not a primitive.
            String dataInOutSuffix = MetaData.getPrimitiveDataInOutSuffix(fieldType);
            if (dataInOutSuffix != null) {
                // Read instructions for primitive...
                // this - For putfield
                mv.visitVarInsn(ALOAD, 0);
                // DataInput "stream"
                mv.visitVarInsn(ALOAD, 2);
                // Invoke read method on DataInput
                mv.visitMethodInsn(INVOKEINTERFACE, sDataInputClassNameSlashed, "read" + dataInOutSuffix,
                        "()" + fieldType);
                // Store the value
                mv.visitFieldInsn(PUTFIELD, mThisClassNameSlashed, fieldName, fieldType);
            } else {
                // Read instructions for a SCO or FCO...
                // this - For putfield
                mv.visitVarInsn(ALOAD, 0);
                // ReadContext - method param 1
                mv.visitVarInsn(ALOAD, 1);
                // this - method param 2
                mv.visitVarInsn(ALOAD, 0);
                // Invoke readObject - value to stack for putfield
                mv.visitMethodInsn(INVOKEVIRTUAL, sObjectSerializerClassNameSlashed, "readObject",
                        "(" + sPersistableClassDescr + ")Ljava/lang/Object;");
                // Cast Object result to proper type
                mv.visitTypeInsn(CHECKCAST, field.getInternalName());
                // Store the value
                mv.visitFieldInsn(PUTFIELD, mThisClassNameSlashed, fieldName, fieldType);
            }
        }

        // Invoke enerjPostLoad if it exists
        if (mHasPostLoad) {
            // this
            mv.visitVarInsn(ALOAD, 0);
            // See comments on call to enerjPreStore for reason why INVOKESPECIAL is used.
            mv.visitMethodInsn(INVOKESPECIAL, mThisClassNameSlashed, sPostLoadMethodName, sNoArgMethodSignature);
        }

        mv.visitInsn(RETURN);

        Label endLabel = new Label();
        mv.visitLabel(endLabel);

        mv.visitLocalVariable("this", mThisClassDescr, null, startLabel, endLabel, 0);
        mv.visitLocalVariable("aContext", "Lorg/enerj/core/ObjectSerializer;", null, startLabel, endLabel, 1);
        mv.visitLocalVariable("stream", "Ljava/io/DataInput;", null, startLabel, endLabel, 2);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Emits enerj_WriteObject.
     *
     * @param someFields a list of the persistent fields for this class (not including
     *  super-class fields).
     */
    private void emitWriteObject(ArrayList<Field> someFields) {
        MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "enerj_WriteObject", sReadWriteObjectMethodSignature, null,
                new String[] { "java/io/IOException" });

        mv.visitCode();
        Label startLabel = new Label();
        mv.visitLabel(startLabel);

        mv.visitVarInsn(ALOAD, 1);
        mv.visitMethodInsn(INVOKEVIRTUAL, sObjectSerializerClassNameSlashed, "getDataOutput",
                "()Ljava/io/DataOutput;");
        mv.visitVarInsn(ASTORE, 2);

        // Invoke enerjPreStore if it exists
        if (mHasPreStore) {
            // Use INVOKESPECIAL here rather than INVOKEVIRTUAL for two reasons:
            // 1) We want to call the method on this object ONLY, NOT an overridden
            // method in a sub-class; 2) the method may be private, in which case
            // INVOKESPECIAL should be used according to the VM Spec.
            // this
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, mThisClassNameSlashed, sPreStoreMethodName, sNoArgMethodSignature);
        }

        if (!mIsTopLevelPersistable) {
            // Call super write if not a top-level persistable
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitMethodInsn(INVOKESPECIAL, mSuperClassNameSlashed, "enerj_WriteObject",
                    sReadWriteObjectMethodSignature);
        }

        for (Field field : someFields) {
            String fieldName = field.getName();
            String fieldType = field.getDescriptor();

            // This will return null if the type is not a primitive.
            String dataInOutSuffix = MetaData.getPrimitiveDataInOutSuffix(fieldType);
            if (dataInOutSuffix != null) {
                // Write instructions for primitive...
                // DataOutput "stream" - for write method
                mv.visitVarInsn(ALOAD, 2);
                // this - for getfield
                mv.visitVarInsn(ALOAD, 0);
                mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, fieldName, fieldType);
                // Invoke write method on DataOutput
                char sigChar = fieldType.charAt(0);
                // Special case - byte, short, and char are given as an int parameter.
                if (sigChar == 'B' || sigChar == 'S' || sigChar == 'C') {
                    sigChar = 'I';
                }

                mv.visitMethodInsn(INVOKEINTERFACE, sDataOutputClassNameSlashed, "write" + dataInOutSuffix,
                        "(" + sigChar + ")V");
            } else {
                // Write instructions for a SCO or FCO...
                // WriteContext - write method param 1
                mv.visitVarInsn(ALOAD, 1);
                // this - for getfield
                mv.visitVarInsn(ALOAD, 0);
                // getfield value - write method param 2
                mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, fieldName, fieldType);
                // this - write method param 3
                mv.visitVarInsn(ALOAD, 0);
                // Invoke writeObject method.
                mv.visitMethodInsn(INVOKEVIRTUAL, sObjectSerializerClassNameSlashed, "writeObject",
                        "(Ljava/lang/Object;Lorg/enerj/core/Persistable;)V");
            }
        }

        // Invoke enerjPostStore if it exists
        if (mHasPostStore) {
            // See comments on call to enerjPreStore for reason why INVOKESPECIAL is used.
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, mThisClassNameSlashed, sPostStoreMethodName, sNoArgMethodSignature);
        }

        mv.visitInsn(RETURN);

        Label endLabel = new Label();
        mv.visitLabel(endLabel);
        mv.visitLocalVariable("this", mThisClassDescr, null, startLabel, endLabel, 0);
        mv.visitLocalVariable("aContext", sObjectSerializerClassDescr, null, startLabel, endLabel, 1);
        mv.visitLocalVariable("stream", "Ljava/io/DataOutput;", null, startLabel, endLabel, 2);
        mv.visitMaxs(3, 3);
        mv.visitEnd();
    }

    /**
     * Emits enerj_ResolveObject.
     *
     * @param someFields a list of the persistent fields for this class (not including
     *  super-class fields).
     */
    private void emitResolveObject(ArrayList<Field> someFields) {
        MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "enerj_ResolveObject", sResolveObjectMethodSignature, null,
                new String[] { "java/io/IOException" });

        mv.visitCode();
        Label startLabel = new Label();
        mv.visitLabel(startLabel);

        if (!mIsTopLevelPersistable) {
            // Call super write if not a top-level persistable
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitVarInsn(ILOAD, 2);
            mv.visitMethodInsn(INVOKESPECIAL, mSuperClassNameSlashed, "enerj_ResolveObject",
                    sResolveObjectMethodSignature);
        }

        for (Field field : someFields) {
            String fieldName = field.getName();
            String fieldType = field.getDescriptor();

            // This will return null if the type is not a primitive.
            String dataInOutSuffix = MetaData.getPrimitiveDataInOutSuffix(fieldType);
            // Only need to resolve objects, not primitive types.
            if (dataInOutSuffix == null) {
                // Write instructions for a SCO or FCO...
                // WriteContext - write method param 1
                mv.visitVarInsn(ALOAD, 1);
                // this - for getfield
                mv.visitVarInsn(ALOAD, 0);
                // getfield value - write method param 2
                mv.visitFieldInsn(GETFIELD, mThisClassNameSlashed, fieldName, fieldType);
                // this - write method param 2
                mv.visitVarInsn(ILOAD, 2);
                mv.visitMethodInsn(INVOKEVIRTUAL, sObjectSerializerClassNameSlashed, "resolveObject",
                        "(Ljava/lang/Object;Z)V");
            }
        }

        mv.visitInsn(RETURN);

        Label endLabel = new Label();
        mv.visitLabel(endLabel);
        mv.visitLocalVariable("this", mThisClassDescr, null, startLabel, endLabel, 0);
        mv.visitLocalVariable("aContext", sObjectSerializerClassDescr, null, startLabel, endLabel, 1);
        mv.visitLocalVariable("shouldDisassociate", "Z", null, startLabel, endLabel, 2);
        mv.visitMaxs(3, 3);
        mv.visitEnd();
    }

    /**
     * Generate the enerj_Hollow method.
     *
     * @param someFields a list of the persistent fields for this class (not including
     *  super-class fields).
     *
     * @throws EnhancerException in the event of an error.
     */
    private void emitHollow(ArrayList<Field> someFields) throws EnhancerException {
        MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "enerj_Hollow", sNoArgMethodSignature, null, null);

        mv.visitCode();
        Label startLabel = new Label();
        mv.visitLabel(startLabel);

        // Invoke enerjPreHollow if it exists
        if (mHasPreHollow) {
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, mThisClassNameSlashed, "enerjPreHollow", sNoArgMethodSignature);
        }

        if (!mIsTopLevelPersistable) {
            // Call super's hollow if not a top-level persistable.
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, mSuperClassNameSlashed, "enerj_Hollow", sNoArgMethodSignature);
        }

        for (Field field : someFields) {
            String fieldType = field.getDescriptor();
            // Only null the field if not a primitive type.
            if (!MetaData.isPrimitive(fieldType)) {
                // this
                mv.visitVarInsn(ALOAD, 0);
                // null
                mv.visitInsn(ACONST_NULL);
                // Store the value
                mv.visitFieldInsn(PUTFIELD, mThisClassNameSlashed, field.getName(), fieldType);
            }
        }

        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESTATIC, sPersistableHelperClassSlashed, "completeHollow",
                sPersistableVoidSignature);
        mv.visitInsn(RETURN);

        Label endLabel = new Label();
        mv.visitLabel(endLabel);

        mv.visitLocalVariable("this", mThisClassDescr, null, startLabel, endLabel, 0);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Emit the special constructor. "&lt;init>(Persister)".
     */
    private void emitSpecialConstructor() {
        MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", sPersisterConstructorSignature, null, null);

        mv.visitCode();
        Label startLabel = new Label();
        mv.visitLabel(startLabel);
        mv.visitVarInsn(ALOAD, 0);

        String constructorClass = mSuperClassNameSlashed;
        String signature;
        if (mIsTopLevelPersistable) {
            // Super-class is not Persistable, call default constructor
            signature = sNoArgMethodSignature;

            if (mHasNoArgConstructor) {
                // Use this class' no-arg constructor rather than the super-class'.
                // The super-class might not have an exposed no-arg constructor.
                // However, this also causes PersistableHelper.initPersistable to be called since
                // the default constructor was enhanced to do so. This has side-effect,
                // of just marking the object as "new". However, the Persister.getObjectByOID()
                // method clears the "new" flag.
                constructorClass = mThisClassNameSlashed;
            }
        } else {
            mv.visitVarInsn(ALOAD, 1); // aDatabase
            signature = sPersisterConstructorSignature;
        }

        mv.visitMethodInsn(INVOKESPECIAL, constructorClass, "<init>", signature);

        mv.visitInsn(RETURN);
        Label endLabel = new Label();
        mv.visitLabel(endLabel);

        mv.visitLocalVariable("this", mThisClassDescr, null, startLabel, endLabel, 0);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Emits the class Id static variable and a static and non-static getters for it.
     */
    private void emitClassId() {
        // Emit static field
        FieldVisitor fv = cv.visitField(ACC_PRIVATE + ACC_STATIC + ACC_TRANSIENT, sClassIdFieldName, "J", null,
                null);
        fv.visitEnd();

        // Emit static getter. We'll use this when we only have a class and not an object.
        MethodVisitor mv = cv.visitMethod(ACC_PUBLIC + ACC_STATIC, "enerj_GetClassIdStatic", "()J", null, null);
        mv.visitCode();
        mv.visitFieldInsn(GETSTATIC, mThisClassNameSlashed, sClassIdFieldName, "J");
        mv.visitInsn(LRETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();

        // Emit non-static Persistable interface getter. 
        // We'll use for non-reflective access when we have a Persistable.
        mv = cv.visitMethod(ACC_PUBLIC, "enerj_GetClassId", "()J", null, null);
        mv.visitCode();
        Label startLabel = new Label();
        mv.visitLabel(startLabel);
        mv.visitFieldInsn(GETSTATIC, mThisClassNameSlashed, sClassIdFieldName, "J");
        mv.visitInsn(LRETURN);

        Label endLabel = new Label();
        mv.visitLabel(endLabel);
        mv.visitLocalVariable("this", mThisClassDescr, null, startLabel, endLabel, 0);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Emits the &lt;clinit> method when one does not already exist.
     * Initializes the value of the class Id.
     */
    private void emitClassInit() {
        MethodVisitor mv = cv.visitMethod(ACC_STATIC, "<clinit>", sNoArgMethodSignature, null, null);
        mv.visitCode();
        mv.visitLdcInsn((Long) mClassId);
        mv.visitFieldInsn(PUTSTATIC, mThisClassNameSlashed, sClassIdFieldName, "J");
        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    /**
     * Answers if this class is a Persistable.
     *
     * @return true if it is a Persistable.
     */
    boolean isPersistable() {
        return mIsPersistable;
    }

    /**
     * Gets the Persistent Fields.
     *
     * @return a ArrayList<Field>.
     */
    List<Field> getPersistentFields() {
        return mPersistentFields;
    }

    /**
     * Gets the Transient Fields.
     *
     * @return a ArrayList<Field>.
     */
    List<Field> getTransientFields() {
        return mTransientFields;
    }

    /**
     * Gets the ClassId.
     *
     * @return a class Id.
     */
    long getClassId() {
        return mClassId;
    }

    /**
     * Handles enhancement of existing methods.
     */
    private class MethodEnhancer extends MethodAdapter {
        private String mMethodName;
        /** 
         * If the method is <init> or clone on a Persistable, only enhance
         * access to Persistables external to this one. This eliminates unnecessary
         * modification marking and race conditions on initialization.
         */
        private boolean mShouldEnhanceExternalOnly;
        private boolean mShouldEnhanceConstructor;
        private boolean mShouldEnhanceClone;
        private boolean mShouldEnhanceReadObject;
        private boolean mIsClinit;

        /**
         * Constructs a MethodEnhancer.
         * 
         * @param aMethodVisitor the delegate method visitor.
         * @param enhanceExternalOnly true if only field access external to this class should be replaced.  
         * @param isClone true if this method is a clone()Ljava/lang/Object; method.
         * @param shouldEnhanceConstructor true if the method being visited is a constructor and 
         *  constructor enhancement should be performed.
         * @param shouldEnhanceClone true if the method being visited is clone() and clone enhancement should be performed.
         * @param shouldEnhanceReadObject true if the method being visited is readObject(ObjectInputStream) and it should
         *  be enhanced.  
         */
        MethodEnhancer(MethodVisitor aMethodVisitor, String aMethodName, boolean shouldEnhanceExternalOnly,
                boolean shouldEnhanceConstructor, boolean shouldEnhanceClone, boolean shouldEnhanceReadObject) {
            super(aMethodVisitor);
            mShouldEnhanceExternalOnly = shouldEnhanceExternalOnly;
            mShouldEnhanceConstructor = shouldEnhanceConstructor;
            mShouldEnhanceClone = shouldEnhanceClone;
            mShouldEnhanceReadObject = shouldEnhanceReadObject;
            mIsClinit = aMethodName.equals("<clinit>");
        }

        /** 
         * {@inheritDoc}
         * Do special first instruction insertion, if necessary.
         * 
         * @see org.objectweb.asm.MethodVisitor#visitCode()
         */
        public void visitCode() {
            mv.visitCode();
            // Insert call to PersistableHelper.initPersistable(this) as first statement of readObject().
            if (mShouldEnhanceReadObject) {
                mv.visitVarInsn(ALOAD, 0);
                mv.visitMethodInsn(INVOKESTATIC, sPersistableHelperClassSlashed, "initPersistable",
                        sPersistableVoidSignature);
            }
        }

        /** 
         * {@inheritDoc}
         * Replace any getfield/getstatic/putfield/putstatic instructions in the 
         * specified method which reference fields of persistable classes.
         * 
         * @see org.objectweb.asm.MethodAdapter#visitFieldInsn(int, java.lang.String, java.lang.String, java.lang.String)
         */
        public void visitFieldInsn(int anOpcode, String anOwnerClass, String aFieldName, String aFieldDescriptor) {
            try {
                // anOpcode == GETFIELD || anOpcode == PUTFIELD || anOpcode == GETSTATIC || anOpcode == PUTSTATIC
                // The referenced field's class name must be an FCO to enhance access to it.
                // Also, if mShouldEnhanceExternalOnly is true, the field class name should not be the
                // the same as this class name. 
                String ownerClassDotted = anOwnerClass.replace('/', '.');
                if (!mIsClinit && mMetaData.isClassAFCO(ownerClassDotted)
                        && !(mShouldEnhanceExternalOnly && anOwnerClass.equals(mThisClassNameSlashed))) {

                    String ownerClassDescriptor = 'L' + anOwnerClass + ';';

                    switch (anOpcode) {
                    case GETFIELD:
                        mv.visitMethodInsn(INVOKESTATIC, anOwnerClass,
                                FIELD_ACCESSOR_PREFIX + getFieldMethodNameSuffix(ownerClassDotted, aFieldName),
                                '(' + ownerClassDescriptor + ')' + aFieldDescriptor);
                        break;

                    case PUTFIELD:
                        mv.visitMethodInsn(INVOKESTATIC, anOwnerClass,
                                FIELD_MUTATOR_PREFIX + getFieldMethodNameSuffix(ownerClassDotted, aFieldName),
                                '(' + ownerClassDescriptor + aFieldDescriptor + ")V");
                        break;

                    case GETSTATIC:
                        mv.visitMethodInsn(INVOKESTATIC, anOwnerClass,
                                FIELD_ACCESSOR_PREFIX + getFieldMethodNameSuffix(ownerClassDotted, aFieldName),
                                "()" + aFieldDescriptor);
                        break;

                    case PUTSTATIC:
                        mv.visitMethodInsn(INVOKESTATIC, anOwnerClass,
                                FIELD_MUTATOR_PREFIX + getFieldMethodNameSuffix(ownerClassDotted, aFieldName),
                                '(' + aFieldDescriptor + ")V");
                        break;

                    default:
                        throw new EnhancerException("Unexpected field opcode: " + anOpcode, null);
                    }
                } else {
                    mv.visitFieldInsn(anOpcode, anOwnerClass, aFieldName, aFieldDescriptor);
                }
            } catch (MetaDataException e) {
                throw new EnhancerException("Error processing class " + mClassName, e);
            }
        }

        /** 
         * {@inheritDoc}
         * Special enhancement on top-level persistable constructors.
         * @see org.objectweb.asm.MethodAdapter#visitMethodInsn(int, java.lang.String, java.lang.String, java.lang.String)
         */
        public void visitMethodInsn(int anOpcode, String anOwnerClass, String aMethodName,
                String aMethodDescriptor) {
            mv.visitMethodInsn(anOpcode, anOwnerClass, aMethodName, aMethodDescriptor);

            // We look for an invokespecial on an <init> method of the superclass.
            // This eliminates enhancement on constructors that call "this(...)".
            if (mShouldEnhanceConstructor && anOpcode == INVOKESPECIAL
                    && anOwnerClass.equals(mSuperClassNameSlashed)) {
                // Insert PersistableHelper.initPersistable(this) right after the super() constructor call. Only done
                // on top-level persistables.
                mv.visitVarInsn(ALOAD, 0);
                mv.visitMethodInsn(INVOKESTATIC, sPersistableHelperClassSlashed, "initPersistable",
                        sPersistableVoidSignature);
            }
        }

        /** 
         * {@inheritDoc}
         * Special enhancement on clone() methods.
         * @see org.objectweb.asm.MethodAdapter#visitInsn(int)
         */
        public void visitInsn(int anOpcode) {
            // Search for areturn instruction - there may be multiple returns
            if (mShouldEnhanceClone && anOpcode == ARETURN) {
                // Duplicate the clone object that will be returned
                mv.visitInsn(DUP);
                // Insert PersistableHelper.initPersistableClone(this) right before the return.
                mv.visitMethodInsn(INVOKESTATIC, sPersistableHelperClassSlashed, "initPersistableClone",
                        sPersistableVoidSignature);
            } else if (mIsPersistable && mIsClinit && anOpcode == RETURN) {
                // Existing <clinit>, insert instructions to initialize class ID. Then flag that we did it.
                mEnhancedClinit = true;
                mv.visitLdcInsn((Long) mClassId);
                mv.visitFieldInsn(PUTSTATIC, mThisClassNameSlashed, sClassIdFieldName, "J");
            }

            mv.visitInsn(anOpcode);
        }
    }
}