/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */


package com.sun.jdo.api.persistence.enhancer.impl;

import java.util.HashMap;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Enumeration;

import com.sun.jdo.api.persistence.enhancer.classfile.*;

import com.sun.jdo.api.persistence.enhancer.util.Support;
import com.sun.jdo.api.persistence.enhancer.util.InternalError;
import com.sun.jdo.api.persistence.enhancer.util.ClassFileSource;

//@olsen: added import
import com.sun.jdo.api.persistence.enhancer.meta.JDOMetaData;


//@olsen: cosmetics
//@olsen: moved: this class -> package impl
//@olsen: subst: (object)state -> flags
//@olsen: subst: JDOflags -> jdoFlags
//@olsen: subst: makeJDO[gs]etFlags -> makeJDO[GS]etFlags
//@olsen: subst: JDO[gs]etFlags -> jdo[GS]etFlags
//@olsen: subst: [Nn]eedsJDORefMethods -> [Nn]eedsJDOStateManagerMethods
//@olsen: subst: JDOref -> jdoStateManager
//@olsen: subst: makeJDO[gs]etRef -> makeJDO[GS]etStateManager
//@olsen: subst: JDO[gs]etRef -> jdo[GS]etStateManager
//@olsen: subst: [iI]Persistent -> [pP]ersistenceCapable
//@olsen: subst: PersistentAux -> StateManager
//@olsen: subst: jdo/ -> com/sun/forte4j/persistence/internal/
//@olsen: subst: jdo. -> com.sun.forte4j.persistence.internal.
//@olsen: subst: /* ... */ -> // ...
//@olsen: added: final modifiers on local variables
//@olsen: subst: FilterEnv -> Environment
//@olsen: made MethodBuilder's methods non-static
//@olsen: dropped parameter 'Environment env', use association instead
//@olsen: subst: Enumeration,... -> Iterator, hasNext(), next()
//@olsen: subst: myMethodName -> methodName
//@olsen: added: support for I18N
//@olsen: subst: FilterError -> UserException, affirm()
//@olsen: removed: proprietary support for ClassInfo
//@olsen: removed: proprietary support for FieldNote
//@olsen: removed: old, disabled ODI code


/**
 * MethodBuilder is a collection of methods which
 * create the persistence methods of a persistent class
 */
class MethodBuilder
    extends Support
    implements VMConstants {

    //@olsen: fix for bug 4467428:
    // Debugging under jdk 1.3.1 shows the problem that any breakpoints
    // in PC classes are ignored if the added jdo methods do NOT have a
    // non-empty line number table attribute, no matter whether the
    // 'Synthetic' attribute is given or not.  However, this doesn't
    // seem to comply with the JVM Spec (2nd edition), which states
    // that the synthetic attribute _must_ be specified if no source
    // code information is available for the member:
    //
    //     4.7.6 The Synthetic Attribute
    //     ... A class member that does not appear in the source code must
    //     be marked using a Synthetic attribute. ...
    //
    //     4.7.8 The LineNumberTable Attribute
    //     The LineNumberTable attribute is an optional variable-length
    //     attribute in the attributes table of a Code (see 4.7.3)
    //     attribute. It may be used by debuggers to determine which
    //     part of the Java virtual machine code array corresponds to a
    //     given line number in the original source file. ... Furthermore,
    //     multiple LineNumberTable attributes may together represent a
    //     given line of a source file; that is, LineNumberTable attributes
    //     need not be one-to-one with source lines.
    //
    // Unfortunately, if we do both, adding the synthetic attribute and
    // a (dummy) line number table on generated methods, jdk's 1.3.1 javap
    // fails to disassemble the classfile with an exception:
    //
    //     sun.tools.java.CompilerError: checkOverride() synthetic
    //
    // So, to workaround these problems and to allow for both, debugging
    // and disassembling with the jdk (1.3.1) tools, we pretend that the
    // generated jdo methods have source code equivalents by
    // - not adding the synthetic code attribute
    // - providing a dummy line number table code attribute
    static private final boolean addSyntheticAttr = false;
    static private final boolean addLineNumberTableAttr = true;

    /* Central repository for the options and classes */
    //@olsen: added association
    //@olsen: made final
    private final Environment env;

    /**
     * Constructor.
     */
    //@olsen: added constructor
    //@olsen: added parameter 'env' for association
    public MethodBuilder(Environment env) {
        this.env = env;
    }

    /**
     * Build a null method named methodName for the class.
     *
     * public void methodName() {
     * }
     */
    //@olsen: moved to beginning of this class
    ClassMethod makeNullMethod(final ClassAction ca,
                               final String methodName) {
        //@olsen: added variable
        final String methodSig = "()V";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass thisClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();
        final ClassMethod nullMethod
            = new ClassMethod(ACCPublic,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // end of method body
        insn = insn.append(Insn.create(opc_return));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              0, // maxStack
                              1, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return nullMethod;
    }

    /**
     * Build the initializeContents method for the class
     */
//@olsen: dropped method
/*
    ClassMethod makeInitializeContents(ClassAction ca) {
        final ConstantPool pool = ca.classFile().pool();
        final ConstClass thisClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();
        ClassMethod initializeContentsMethod =
            new ClassMethod(ACCPublic,
                            pool.addUtf8("initializeContents"),
                            pool.addUtf8("(Lcom/sun/forte4j/persistence/internal/ObjectContents;)V"),
                            methodAttrs);

        // begin of method body
        Insn begin = new InsnTarget();
        Insn insn = begin;

        // Allocated reg 2 as cached ClassInfo*
        insn = insn.append(
            Insn.create(opc_getstatic,
                        pool.addFieldRef(thisClass.asString(),
                                         ca.getClassInfoMember(),
                                         "Lcom/sun/forte4j/persistence/internal/ClassInfo;")));
        insn = insn.append(Insn.create(opc_astore_2));

        for (Iterator e = ca.fieldActions(); e.hasNext();) {
            FieldAction act = (FieldAction)e.next();
            if (act.isPersistent()) {
                int idx = act.index();
                // get this
                insn = insn.append(Insn.create(opc_aload_0));
                // get GenericObject
                insn = insn.append(Insn.create(opc_aload_1));
                // get field index
                insn = insn.append(InsnUtils.integerConstant(idx, pool));
                // get ClassInfo
                insn = insn.append(Insn.create(opc_aload_2));
                // call the get method
                insn = insn.append(Insn.create(opc_invokevirtual,
                                               pool.addMethodRef("com/sun/forte4j/persistence/internal/ObjectContents",
                                                                 act.getMethod(), act.getMethodSig())));

                switch(act.getMethodReturn()) {
                case T_DOUBLE:
                case T_LONG:
                case T_BOOLEAN:
                case T_CHAR:
                case T_FLOAT:
                case T_BYTE:
                case T_SHORT:
                case T_INT:
                case TC_STRING:
                    // The above types are assumed to be accurate, with no
                    // conversions required
                    break;

                case TC_OBJECT:
                case TC_INTERFACE:
                    if (!act.typeDescriptor().equals("Ljava/lang/Object;")) {
                        ConstClass fieldType = pool.addClass(act.typeName());
                        // Add a cast to the appropriate type
                        insn = insn.append(Insn.create(opc_checkcast, fieldType));
                    }
                    break;
                default:
                    throw new InternalError("Unexpected return type");
                }

                // finally, store the result
                insn = insn.append(Insn.create(opc_putfield,
                                               pool.addFieldRef(thisClass.asString(), act.fieldName(),
                                                                act.typeDescriptor())));
            }
        }

        ConstClass superClass = ca.classFile().superName();
        if (!ca.getImplementsPersistence()) {
            // Need to invoke initializeContents on super
            // get this
            insn = insn.append(Insn.create(opc_aload_0));
            // get generic object
            insn = insn.append(Insn.create(opc_aload_1));
            // do the invoke
            insn = insn.append(Insn.create(opc_invokespecial,
                                           pool.addMethodRef(superClass.asString(),
                                                             "initializeContents", "(Lcom/sun/forte4j/persistence/internal/ObjectContents;)V")));
        }

        // end of method body
        insn = insn.append(Insn.create(opc_return));

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              4, // maxStack
                              3, // maxLocals
                              begin,
                              new ExceptionTable(),
                              new AttributeVector()));
        return initializeContentsMethod;
    }
*/

    /**
     * Build the flushContents method for the class
     */
//@olsen: dropped method
/*
    ClassMethod makeFlushContents(ClassAction ca) {
        final ConstantPool pool = ca.classFile().pool();
        final ConstClass thisClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();
        ClassMethod flushContentsMethod =
            new ClassMethod(ACCPublic,
                            pool.addUtf8("flushContents"),
                            pool.addUtf8("(Lcom/sun/forte4j/persistence/internal/ObjectContents;)V"),
                            methodAttrs);

        // begin of method body
        Insn begin = new InsnTarget();
        Insn insn = begin;

        // Allocated reg 2 as cached ClassInfo*
        insn = insn.append(
            Insn.create(opc_getstatic,
                        pool.addFieldRef(thisClass.asString(),
                                         ca.getClassInfoMember(),
                                         "Lcom/sun/forte4j/persistence/internal/ClassInfo;")));
        insn = insn.append(Insn.create(opc_astore_2));

        for (Iterator e = ca.fieldActions(); e.hasNext();) {
            FieldAction act = (FieldAction)e.next();
            if (act.isPersistent()) {
                int idx = act.index();
                // get GenericObject
                insn = insn.append(Insn.create(opc_aload_1));
                // get field index
                insn = insn.append(InsnUtils.integerConstant(idx, pool));
                // get this
                insn = insn.append(Insn.create(opc_aload_0));
                // get the field value
                insn = insn = insn.append(Insn.create(opc_getfield,
                                                      pool.addFieldRef(thisClass.asString(), act.fieldName(),
                                                                       act.typeDescriptor())));
                // get ClassInfo
                insn = insn.append(Insn.create(opc_aload_2));

                // call the set method
                insn = insn.append(Insn.create(opc_invokevirtual,
                                               pool.addMethodRef("com/sun/forte4j/persistence/internal/ObjectContents",
                                                                 act.setMethod(), act.setMethodSig())));
            }
        }

        ConstClass superClass = ca.classFile().superName();
        if (!ca.getImplementsPersistence()) {
            // Need to invoke flushContents on super
            // get this
            insn = insn.append(Insn.create(opc_aload_0));
            // get generic object
            insn = insn.append(Insn.create(opc_aload_1));
            // do the invoke
            insn = insn.append(Insn.create(opc_invokespecial,
                                           pool.addMethodRef(superClass.asString(),
                                                             "flushContents", "(Lcom/sun/forte4j/persistence/internal/ObjectContents;)V")));
        }

        // end of method body
        insn = insn.append(Insn.create(opc_return));

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              6, // maxStack (might actually be 5, but ok)
                              3, // maxLocals
                              begin,
                              new ExceptionTable(),
                              new AttributeVector()));
        return flushContentsMethod;
    }
*/

    /**
     * Build the clearContents method for the class
     */
//@olsen: dropped method
/*
    ClassMethod makeClearContents(ClassAction ca) {
        final ConstantPool pool = ca.classFile().pool();
        final ConstClass thisClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();
        ClassMethod clearContentsMethod =
            new ClassMethod(ACCPublic,
                            pool.addUtf8("clearContents"),
                            pool.addUtf8("()V"),
                            methodAttrs);

        // begin of method body
        Insn begin = new InsnTarget();
        Insn insn = begin;

        for (Iterator e = ca.fieldActions(); e.hasNext();) {
            FieldAction act = (FieldAction)e.next();
            if (act.isPersistent()) {
                int idx = act.index();
                // get this
                insn = insn.append(Insn.create(opc_aload_0));

                // Use the getMethodReturn type to decide how to initialize
                switch(act.getMethodReturn()) {
                case T_DOUBLE:
                    insn = insn.append(Insn.create(opc_dconst_0));
                    break;
                case T_LONG:
                    insn = insn.append(Insn.create(opc_lconst_0));
                    break;
                case T_FLOAT:
                    insn = insn.append(Insn.create(opc_fconst_0));
                    break;
                case T_BOOLEAN:
                case T_CHAR:
                case T_BYTE:
                case T_SHORT:
                case T_INT:
                    insn = insn.append(Insn.create(opc_iconst_0));
                    break;
                case TC_STRING:
                case TC_OBJECT:
                case TC_INTERFACE:
                    insn = insn.append(Insn.create(opc_aconst_null));
                    break;
                default:
                    throw new InternalError("Unexpected return type");
                }

                // finally, store the result
                insn = insn.append(Insn.create(opc_putfield,
                                               pool.addFieldRef(thisClass.asString(), act.fieldName(),
                                                                act.typeDescriptor())));
            }
        }

        ConstClass superClass = ca.classFile().superName();
        if (!ca.getImplementsPersistence()) {
            // Need to invoke clearContents on super
            // get this
            insn = insn.append(Insn.create(opc_aload_0));
            // do the invoke
            insn = insn.append(Insn.create(opc_invokespecial,
                                           pool.addMethodRef(superClass.asString(),
                                                             "clearContents", "()V")));
        }

        // end of method body
        insn = insn.append(Insn.create(opc_return));

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              3, // maxStack (maybe only 2 - ok)
                              1, // maxLocals
                              begin,
                              new ExceptionTable(),
                              new AttributeVector()));
        return clearContentsMethod;
    }
*/

    /**
     * Build an empty class initializer method for this class
     */
//@olsen: dropped method
/*
    ClassMethod makeClassInit(ClassAction ca) {
        final ConstantPool pool = ca.classFile().pool();
        final ConstClass thisClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();
        ClassMethod classInitMethod =
            new ClassMethod(ACCStatic,
                            pool.addUtf8("<clinit>"),
                            pool.addUtf8("()V"),
                            methodAttrs);

        // begin of method body
        Insn begin = new InsnTarget();
        Insn insn = begin;

        // return from the initializer
        // end of method body
        insn = insn.append(Insn.create(opc_return));

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              0,
                              0, // maxLocals
                              begin,
                              new ExceptionTable(),
                              new AttributeVector()));
        return classInitMethod;
    }
*/

    /**
     * Build the jdoGetStateManager method for the class.
     *
     * public StateManager jdoGetStateManager() {
     *     return this.jdoStateManager;
     * }
     */
    //@olsen: cosmetics
    ClassMethod makeJDOGetStateManager(final ClassAction ca,
                                       final String methodName) {
        //@olsen: added variable
        final String methodSig = "()" + JDOMetaData.JDOStateManagerSig;//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();

        //@olsen: made method final
        final ClassMethod jdoGetStateManagerMethod
            = new ClassMethod(ACCPublic | ACCFinal,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // get jdoStateManager field
        insn = insn.append(Insn.create(opc_aload_0));
        insn = insn.append(
            Insn.create(opc_getfield,
                        pool.addFieldRef(theClass.asString(),
                                         JDOMetaData.JDOStateManagerFieldName,
                                         JDOMetaData.JDOStateManagerFieldSig)));

        // end of method body
        insn = insn.append(Insn.create(opc_areturn));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              1, // maxStack
                              1, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return jdoGetStateManagerMethod;
    }

    /**
     * Build the jdoSetStateManager method for the class.
     *
     * public void jdoSetStateManager(StateManager sm) {
     *     this.jdoStateManager = sm;
     * }
     */
    //@olsen: cosmetics
    ClassMethod makeJDOSetStateManager(final ClassAction ca,
                                       final String methodName) {
        //@olsen: added variable
        final String methodSig = "(" + JDOMetaData.JDOStateManagerSig + ")V";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();

        //@olsen: made method final
        final ClassMethod jdoSetStateManagerMethod
            = new ClassMethod(ACCPublic | ACCFinal,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // put argument value to jdoStateManager field
        insn = insn.append(Insn.create(opc_aload_0));
        insn = insn.append(Insn.create(opc_aload_1));
        insn = insn.append(
            Insn.create(opc_putfield,
                        pool.addFieldRef(theClass.asString(),
                                         JDOMetaData.JDOStateManagerFieldName,
                                         JDOMetaData.JDOStateManagerFieldSig)));

        // end of method body
        insn = insn.append(Insn.create(opc_return));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              2, // maxStack
                              2, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return jdoSetStateManagerMethod;
    }

    /**
     * Build the jdoGetFlags method for the class.
     *
     * public byte jdoGetFlags() {
     *     return this.jdoFlags;
     * }
     */
    //@olsen: cosmetics
    ClassMethod makeJDOGetFlags(final ClassAction ca,
                                final String methodName) {
        //@olsen: added variable
        final String methodSig = "()B";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();

        //@olsen: made method final
        final ClassMethod jdoGetFlagsMethod
            = new ClassMethod(ACCPublic | ACCFinal,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // get jdoFlags field
        insn = insn.append(Insn.create(opc_aload_0));
        insn = insn.append(
            Insn.create(opc_getfield,
                        pool.addFieldRef(theClass.asString(),
                                         JDOMetaData.JDOFlagsFieldName,
                                         JDOMetaData.JDOFlagsFieldSig)));

        // end of method body
        insn = insn.append(Insn.create(opc_ireturn));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              1, // maxStack
                              1, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return jdoGetFlagsMethod;
    }

    /**
     * Build the jdoSetFlags method for the class.
     *
     * public void jdoSetFlags(byte flags) {
     *     this.jdoFlags = flags;
     * }
     */
    //@olsen: cosmetics
    ClassMethod makeJDOSetFlags(final ClassAction ca,
                                final String methodName) {
        //@olsen: added variable
        final String methodSig = "(B)V";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();

        //@olsen: made method final
        final ClassMethod jdoSetFlagsMethod
            = new ClassMethod(ACCPublic | ACCFinal,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // put argument value to jdoFlags field
        insn = insn.append(Insn.create(opc_aload_0));
        insn = insn.append(Insn.create(opc_iload_1));
        insn = insn.append(
            Insn.create(opc_putfield,
                        pool.addFieldRef(theClass.asString(),
                                         JDOMetaData.JDOFlagsFieldName,
                                         JDOMetaData.JDOFlagsFieldSig)));

        // end of method body
        insn = insn.append(Insn.create(opc_return));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              2, // maxStack
                              2, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return jdoSetFlagsMethod;
    }

    /**
     * Build the jdoMakeDirty method for the class.
     *
     * public void makeDirty() {
     *     final StateManager sm = this.jdoStateManager;
     *     if (sm != null)
     *         sm.makeDirty(fieldName);
     * }
     */
    //@olsen: added method for generating the jdoMakeDirty method
    ClassMethod makeJDOMakeDirtyMethod(final ClassAction ca,
                                       final String methodName) {
        //@olsen: added variable
        final String methodSig = "(Ljava/lang/String;)V";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();

        //@olsen: made method final
        final ClassMethod jdoMakeDirtyMethod
            = new ClassMethod(ACCPublic | ACCFinal,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // fetch the jdoStateManager field into local var
        insn = insn.append(Insn.create(opc_aload_0));
        insn = insn.append(
            Insn.create(
                opc_getfield,
                pool.addFieldRef(theClass.asString(),
                                 JDOMetaData.JDOStateManagerFieldName,
                                 JDOMetaData.JDOStateManagerFieldSig)));
        insn = insn.append(Insn.create(opc_astore_2));

        // test the jdoStateManager field and goto end if null
        InsnTarget end = new InsnTarget();
        insn = insn.append(Insn.create(opc_aload_2));
        insn = insn.append(Insn.create(opc_ifnull, end));

        // call the jdoStateManager's makeDirty method with argument
        insn = insn.append(Insn.create(opc_aload_2));
        insn = insn.append(Insn.create(opc_aload_1));
        insn = insn.append(
            new InsnInterfaceInvoke(
                pool.addInterfaceMethodRef(
                    JDOMetaData.JDOStateManagerPath,
                    "makeDirty",//NOI18N
                    "(Ljava/lang/String;)V"),//NOI18N
                2));

        // end of method body
        insn = insn.append(end);
        insn = insn.append(Insn.create(opc_return));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              2, // maxStack
                              3, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return jdoMakeDirtyMethod;
    }

    /**
     * Build an interrogative method named methodName for the class.
     *
     * public boolean isXXX() {
     *     final StateManager sm = this.jdoStateManager;
     *     if (sm == null)
     *         return false;
     *     return sm.isXXXX();
     * }
     */
    //@olsen: added method for generating an interrogative JDO method
    ClassMethod makeJDOInterrogativeMethod(final ClassAction ca,
                                           final String methodName) {
        //@olsen: added variable
        final String methodSig = "()Z";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();

        //@olsen: made method final
        final ClassMethod interrogativeMethod
            = new ClassMethod(ACCPublic | ACCFinal,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // fetch the jdoStateManager field into local var
        insn = insn.append(Insn.create(opc_aload_0));
        insn = insn.append(
            Insn.create(
                opc_getfield,
                pool.addFieldRef(theClass.asString(),
                                 JDOMetaData.JDOStateManagerFieldName,
                                 JDOMetaData.JDOStateManagerFieldSig)));
        insn = insn.append(Insn.create(opc_astore_1));

        // test the jdoStateManager field and return false if null
        InsnTarget call = new InsnTarget();
        insn = insn.append(Insn.create(opc_aload_1));
        insn = insn.append(Insn.create(opc_ifnonnull, call));
        insn = insn.append(Insn.create(opc_iconst_0));
        insn = insn.append(Insn.create(opc_ireturn));

        // call the jdoStateManager's interrogative method (jdoIs... -> is...)
        final String callName = "i" + methodName.substring(4);//NOI18N
        insn = insn.append(call);
        insn = insn.append(Insn.create(opc_aload_1));
        insn = insn.append(
            new InsnInterfaceInvoke(
                pool.addInterfaceMethodRef(
                    JDOMetaData.JDOStateManagerPath,
                    callName,
                    "()Z"),//NOI18N
                1));

        // end of method body
        insn = insn.append(Insn.create(opc_ireturn));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              1, // maxStack
                              2, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return interrogativeMethod;
    }

    /**
     * Build the jdoGetPersistenceManager method for the class.
     *
     * public PersistenceManager jdoGetPersistenceManager() {
     *     final StateManager sm = this.jdoStateManager;
     *     if (sm == null)
     *         return null;
     *     return sm.getPersistenceManager();
     * }
     */
    //@olsen: added method for generating the jdoGetPersistenceManager method
    ClassMethod makeJDOGetPersistenceManagerMethod(final ClassAction ca,
                                                   final String methodName) {
        //@olsen: added variable
        final String methodSig = "()" + JDOMetaData.JDOPersistenceManagerSig;//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();

        //@olsen: made method final
        final ClassMethod jdoGetPersistenceManagerMethod
            = new ClassMethod(ACCPublic | ACCFinal,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // fetch the jdoStateManager field into local var
        insn = insn.append(Insn.create(opc_aload_0));
        insn = insn.append(
            Insn.create(
                opc_getfield,
                pool.addFieldRef(theClass.asString(),
                                 JDOMetaData.JDOStateManagerFieldName,
                                 JDOMetaData.JDOStateManagerFieldSig)));
        insn = insn.append(Insn.create(opc_astore_1));

        // test the jdoStateManager field and return null if null
        InsnTarget call = new InsnTarget();
        insn = insn.append(Insn.create(opc_aload_1));
        insn = insn.append(Insn.create(opc_ifnonnull, call));
        insn = insn.append(Insn.create(opc_aconst_null));
        insn = insn.append(Insn.create(opc_areturn));

        // call the jdoStateManager's getPersistenceManager method
        insn = insn.append(call);
        insn = insn.append(Insn.create(opc_aload_1));
        insn = insn.append(
            new InsnInterfaceInvoke(
                pool.addInterfaceMethodRef(
                    JDOMetaData.JDOStateManagerPath,
                    "getPersistenceManager",//NOI18N
                    "()" + JDOMetaData.JDOPersistenceManagerSig),//NOI18N
                1));

        // end of method body
        insn = insn.append(Insn.create(opc_areturn));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              1, // maxStack
                              2, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return jdoGetPersistenceManagerMethod;
    }

    /**
     * Build the jdoGetObjectId method for the class.
     *
     * public Object jdoGetObjectId() {
     *     final StateManager sm = this.jdoStateManager;
     *     if (sm == null)
     *         return null;
     *     return sm.getObjectId();
     * }
     */
    //@olsen: added method for generating the jdoGetObjectId method
    ClassMethod makeJDOGetObjectIdMethod(final ClassAction ca,
                                         final String methodName) {
        //@olsen: added variable
        final String methodSig = "()Ljava/lang/Object;";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();

        //@olsen: made method final
        final ClassMethod jdoGetObjectIdMethod
            = new ClassMethod(ACCPublic | ACCFinal,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // fetch the jdoStateManager field into local var
        insn = insn.append(Insn.create(opc_aload_0));
        insn = insn.append(
            Insn.create(
                opc_getfield,
                pool.addFieldRef(theClass.asString(),
                                 JDOMetaData.JDOStateManagerFieldName,
                                 JDOMetaData.JDOStateManagerFieldSig)));
        insn = insn.append(Insn.create(opc_astore_1));

        // test the jdoStateManager field and return null if null
        InsnTarget call = new InsnTarget();
        insn = insn.append(Insn.create(opc_aload_1));
        insn = insn.append(Insn.create(opc_ifnonnull, call));
        insn = insn.append(Insn.create(opc_aconst_null));
        insn = insn.append(Insn.create(opc_areturn));

        // call the jdoStateManager's getObjectId method
        insn = insn.append(call);
        insn = insn.append(Insn.create(opc_aload_1));
        insn = insn.append(
            new InsnInterfaceInvoke(
                pool.addInterfaceMethodRef(
                    JDOMetaData.JDOStateManagerPath,
                    "getObjectId",//NOI18N
                    "()Ljava/lang/Object;"),//NOI18N
                1));

        // end of method body
        insn = insn.append(Insn.create(opc_areturn));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              1, // maxStack
                              2, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return jdoGetObjectIdMethod;
    }

    /**
     * Build the jdoGetJDOConstructor method for the class.
     *
     * public <init>(StateManager sm) {
     *     this.jdoFlags = 1;
     *     this.jdoStateManager = sm;
     * }
     */
    //@olsen: added method for generating the JDO constructor
    ClassMethod makeJDOConstructor(final ClassAction ca,
                                   final String methodName) {
        //@olsen: added variable
        final String methodSig = "(" + JDOMetaData.JDOStateManagerSig + ")V";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();
        final ClassMethod jdoGetJDOConstructor
            = new ClassMethod(ACCPublic,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // call the super-class' constructor
        final ConstClass superClass = ca.classFile().superName();
        insn = insn.append(Insn.create(opc_aload_0));
        insn = insn.append(
            Insn.create(opc_invokespecial,
                        pool.addMethodRef(superClass.asString(),
                                          "<init>",//NOI18N
                                          "()V")));//NOI18N

        // put argument value to jdoFlags field
        insn = insn.append(Insn.create(opc_aload_0));
        insn = insn.append(Insn.create(opc_iconst_1));
        insn = insn.append(
            Insn.create(opc_putfield,
                        pool.addFieldRef(theClass.asString(),
                                         JDOMetaData.JDOFlagsFieldName,
                                         JDOMetaData.JDOFlagsFieldSig)));

        // put argument value to jdoStateManager field
        insn = insn.append(Insn.create(opc_aload_0));
        insn = insn.append(Insn.create(opc_aload_1));
        insn = insn.append(
            Insn.create(
                opc_putfield,
                pool.addFieldRef(theClass.asString(),
                                 JDOMetaData.JDOStateManagerFieldName,
                                 JDOMetaData.JDOStateManagerFieldSig)));

        // end of method body
        insn = insn.append(Insn.create(opc_return));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              2, // maxStack
                              2, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return jdoGetJDOConstructor;
    }

    /**
     * Build the jdoNewInstance method for the class.
     *
     * public Object jdoNewInstance(StateManager sm) {
     *     return new <init>(sm);
     * }
     */
    //@olsen: added method for generating the getObjectId method
    ClassMethod makeJDONewInstanceMethod(final ClassAction ca,
                                         final String methodName) {
        //@olsen: added variable
        final String methodSig
            = "(" + JDOMetaData.JDOStateManagerSig + ")Ljava/lang/Object;";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();
        final ClassMethod jdoNewInstanceMethod
            = new ClassMethod(ACCPublic,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // create an instance of the class by JDO constructor
        insn = insn.append(Insn.create(opc_new, theClass));
        insn = insn.append(Insn.create(opc_dup));
        insn = insn.append(Insn.create(opc_aload_1));
        insn = insn.append(
            Insn.create(
                opc_invokespecial,
                pool.addMethodRef(
                    theClass.asString(),
                    "<init>",//NOI18N
                    "(" + JDOMetaData.JDOStateManagerSig + ")V")));//NOI18N

        // end of method body
        insn = insn.append(Insn.create(opc_areturn));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              3, // maxStack
                              2, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return jdoNewInstanceMethod;
    }

    /**
     * Build the jdoClear method for the class.
     *
     * public void jdoClear() {
     *     ...
     * }
     */
    //@olsen: added method for generating the jdoClear method
    ClassMethod makeJDOClearMethod(final ClassAction ca,
                                   final String methodName) {
        //@olsen: added variable
        final String methodSig = "()V";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();
        final ClassMethod jdoClearMethod
            = new ClassMethod(ACCPublic,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        //@olsen: disabled code
        if (false) {
            // reset jdoFlags = LOAD_REQUIRED
            insn = insn.append(Insn.create(opc_aload_0));
            insn = insn.append(Insn.create(opc_iconst_1));
            insn = insn.append(
                Insn.create(opc_putfield,
                            pool.addFieldRef(theClass.asString(),
                                             JDOMetaData.JDOFlagsFieldName,
                                             JDOMetaData.JDOFlagsFieldSig)));
        }

        // iterate over all declared fields of the class
        for (Iterator e = ca.fieldActions(); e.hasNext();) {
            final FieldAction act = (FieldAction)e.next();
            //printFieldAction(act);
            //System.out.println();

            // ignore non-persistent fields
            if (!act.isPersistent())
                continue;

            // ignore primary key fields
            if (act.isPrimaryKey())
                continue;

            //@olsen: disconnect mutable SCOs before clear
            if (act.isMutableSCO()) {
                // fetch field
                insn = insn.append(Insn.create(opc_aload_0));
                insn = insn.append(
                    Insn.create(opc_getfield,
                                pool.addFieldRef(
                                    theClass.asString(),
                                    act.fieldName(),
                                    act.typeDescriptor())));

                // test whether instanceof SCO base type
                // skip disconnecting if == 0
                final ConstClass cc
                    = pool.addClass(JDOMetaData.JDOSecondClassObjectBasePath);
                InsnTarget disconnect = new InsnTarget();
                InsnTarget afterDisconnect = new InsnTarget();
                insn = insn.append(
                    Insn.create(opc_dup));
                insn = insn.append(
                    Insn.create(opc_instanceof,
                                cc));
                insn = insn.append(
                    Insn.create(opc_ifne,
                                disconnect));

                // pop field and skip disconnecting
                insn = insn.append(
                    Insn.create(opc_pop));
                insn = insn.append(
                    Insn.create(opc_goto, afterDisconnect));

                // disconnect SCO field's object
                insn = insn.append(disconnect);

                // cast to SCO base type
                insn = insn.append(
                    Insn.create(opc_checkcast,
                                cc));

                // call method: void unsetOwner();
                final int requiredStack = 1;
                insn = insn.append(
                    new InsnInterfaceInvoke(
                        pool.addInterfaceMethodRef(
                            JDOMetaData.JDOSecondClassObjectBasePath,
                            "unsetOwner",//NOI18N
                            "()V"),//NOI18N
                        requiredStack));

                insn = insn.append(afterDisconnect);
            }

            // get this
            insn = insn.append(Insn.create(opc_aload_0));

            // use the getMethodReturn type to decide how to clear field
            switch(act.getMethodReturn()) {
            case T_DOUBLE:
                insn = insn.append(Insn.create(opc_dconst_0));
                break;
            case T_LONG:
                insn = insn.append(Insn.create(opc_lconst_0));
                break;
            case T_FLOAT:
                insn = insn.append(Insn.create(opc_fconst_0));
                break;
            case T_BOOLEAN:
            case T_CHAR:
            case T_BYTE:
            case T_SHORT:
            case T_INT:
                insn = insn.append(Insn.create(opc_iconst_0));
                break;
            case TC_STRING:
            case TC_OBJECT:
            case TC_INTERFACE:
                insn = insn.append(Insn.create(opc_aconst_null));
                break;
            default:
                throw new InternalError("Unexpected return type");//NOI18N
            }

            // put default value to field
            insn = insn.append(
                Insn.create(opc_putfield,
                            pool.addFieldRef(theClass.asString(),
                                             act.fieldName(),
                                             act.typeDescriptor())));
        }

        // end of method body
        insn = insn.append(Insn.create(opc_return));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              3, // maxStack
                              1, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return jdoClearMethod;
    }

    /**
     * Build the jdoCopy method for the class.
     *
     * public void jdoCopy(Object o, boolean cloneSCOs) {
     *     ...
     * }
     */
//@lars, @olsen: disabled jdoCopy becuase of two problems with the
// current code (this method hasn't been used by the runtime anyway):
// 4388418: Generated jdoCopy() must ignore static fields
// 4388367: Generated jdoCopy() can throw NPE if argument = 'true'
/*
    //@olsen: added method for generating the jdoCopy method
    ClassMethod makeJDOCopyMethod(final ClassAction ca,
                                  final String methodName) {

        //@olsen: added variable
        final String methodSig = "(Ljava/lang/Object;Z)V";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();
        final ClassMethod jdoCopyMethod
            = new ClassMethod(ACCPublic,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // get class object of this and of argument object
        insn = insn.append(Insn.create(opc_aload_1));
        insn = insn.append(
            Insn.create(opc_invokevirtual,
                        pool.addMethodRef(
                            "java/lang/Object",//NOI18N
                            "getClass",//NOI18N
                            "()Ljava/lang/Class;")));//NOI18N
        insn = insn.append(Insn.create(opc_aload_1));
        insn = insn.append(
            Insn.create(opc_invokevirtual,
                        pool.addMethodRef(
                            "java/lang/Object",//NOI18N
                            "getClass",//NOI18N
                            "()Ljava/lang/Class;")));//NOI18N

        // test class objects for equality and throw exception if false
        insn = insn.append(
            Insn.create(opc_invokevirtual,
                        pool.addMethodRef(
                            "java/lang/Object",//NOI18N
                            "equals",//NOI18N
                            "(Ljava/lang/Object;)Z")));//NOI18N
        InsnTarget cast = new InsnTarget();
        insn = insn.append(Insn.create(opc_ifne, cast));
        final String exceptionClassName
            = "com/sun/forte4j/persistence/JDOFatalException";//NOI18N
        insn = insn.append(
            Insn.create(opc_new,
                        pool.addClass(exceptionClassName)));
        insn = insn.append(Insn.create(opc_dup));
        insn = insn.append(
            Insn.create(opc_invokespecial,
                        pool.addMethodRef(
                            exceptionClassName,
                            "<init>",//NOI18N
                            "()V")));//NOI18N
        insn = insn.append(Insn.create(opc_athrow));

        // cast argument object to this class' type and store into local var
        insn = insn.append(cast);
        insn = insn.append(Insn.create(opc_aload_1));
        insn = insn.append(Insn.create(opc_checkcast, theClass));
        insn = insn.append(Insn.create(opc_astore_3));

        // iterate over all declared fields of the class
        final ArrayList mscoFields = new ArrayList();
        final JDOMetaData jdoMetaData = env.getJDOMetaData();
        for (Iterator e = ca.fieldActions(); e.hasNext();) {
            final FieldAction act = (FieldAction)e.next();
            //printFieldAction(act);
            //System.out.println();

            // remember fields of MSCO type for later processing
            if (act.isMutableSCO()) {
                mscoFields.add(act);
                continue;
            }

            // get this object
            insn = insn.append(Insn.create(opc_aload_0));

            // fetch arguments object's field
            insn = insn.append(Insn.create(opc_aload_3));
            insn = insn.append(
                Insn.create(opc_getfield,
                            pool.addFieldRef(
                                theClass.asString(),
                                act.fieldName(),
                                act.typeDescriptor())));

            // store into this object's field
            insn = insn.append(
                Insn.create(opc_putfield,
                            pool.addFieldRef(
                                theClass.asString(),
                                act.fieldName(),
                                act.typeDescriptor())));
        }

        if (mscoFields.size() > 0) {
            // test boolean argument value and clone msco fields if true
            insn = insn.append(Insn.create(opc_iload_2));
            InsnTarget shallow = new InsnTarget();
            insn = insn.append(Insn.create(opc_ifeq, shallow));

            // clone fields
            for (Iterator i = mscoFields.iterator(); i.hasNext();) {
                final FieldAction act = (FieldAction)i.next();

                // get this object
                insn = insn.append(Insn.create(opc_aload_0));

                // fetch arguments object's field
                insn = insn.append(Insn.create(opc_aload_3));
                insn = insn.append(
                    Insn.create(opc_getfield,
                                pool.addFieldRef(
                                    theClass.asString(),
                                    act.fieldName(),
                                    act.typeDescriptor())));

                // clone field value and cast to field's type
                insn = insn.append(
                    Insn.create(opc_invokevirtual,
                                pool.addMethodRef(
                                    act.typeName(),
                                    "clone",//NOI18N
                                    "()Ljava/lang/Object;")));//NOI18N
                insn = insn.append(
                    Insn.create(opc_checkcast,
                                pool.addClass(act.typeName())));

                // store into this object's field
                insn = insn.append(
                    Insn.create(opc_putfield,
                                pool.addFieldRef(
                                    theClass.asString(),
                                    act.fieldName(),
                                    act.typeDescriptor())));
            }
            InsnTarget done = new InsnTarget();
            insn = insn.append(Insn.create(opc_goto, done));

            // copy field values
            insn = insn.append(shallow);
            for (Iterator i = mscoFields.iterator(); i.hasNext();) {
                final FieldAction act = (FieldAction)i.next();

                // get this object
                insn = insn.append(Insn.create(opc_aload_0));

                // fetch arguments object's field
                insn = insn.append(Insn.create(opc_aload_3));
                insn = insn.append(
                    Insn.create(opc_getfield,
                                pool.addFieldRef(
                                    theClass.asString(),
                                    act.fieldName(),
                                    act.typeDescriptor())));

                // store into this object's field
                insn = insn.append(
                    Insn.create(opc_putfield,
                                pool.addFieldRef(
                                    theClass.asString(),
                                    act.fieldName(),
                                    act.typeDescriptor())));
            }

            insn = insn.append(done);
        }

        // end of method body
        insn = insn.append(Insn.create(opc_return));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              4, // maxStack
                              4, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return jdoCopyMethod;
    }
*/

    /**
     * Build the jdoGetField method for the class.
     *
     * public Object jdoGetField(int fieldNumber) {
     *     return ...
     * }
     */
    //@olsen: added method for generating the jdoGetField method
    ClassMethod makeJDOGetFieldMethod(final ClassAction ca,
                                      final String methodName) {
        //@olsen: added variable
        final String methodSig = "(I)Ljava/lang/Object;";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();
        final ClassMethod jdoGetFieldMethod
            = new ClassMethod(ACCPublic,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // get the declared, persistent fields from the JDOMetaData
        final String className = ca.className();
        //@lars: changed getPersistentFields() into getManagedFields()
        final String[] fieldNames
            = env.getJDOMetaData().getManagedFields(className);
        final int nofFields = fieldNames.length;
        //@olsen: added println() for debugging
        if (false) {
            System.out.print("MethodBuilder.makeJDOGetFieldMethod(): "//NOI18N
                             + " declared, persistent fields of class '"//NOI18N
                             + className + "' = {");//NOI18N
            for (int i = 0; i < nofFields; i++)
                System.out.print(" " + fieldNames[i]);//NOI18N
            System.out.println(" }");//NOI18N
        }

        // generate the switch-statement only if more than zero fields
        final InsnTarget defaultOp = new InsnTarget();
        if (nofFields > 0) {
            // get the declared, persistent fields from the class
            final HashMap fieldsByName = new HashMap();
            for (Iterator e = ca.fieldActions(); e.hasNext();) {
                final FieldAction act = (FieldAction)e.next();
                fieldsByName.put(act.fieldName(), act);
            }

            // do the tableswitch on argument
            insn = insn.append(Insn.create(opc_iload_1));
            final int lowOp = 0;
            final InsnTarget[] targetsOp = new InsnTarget[nofFields];
            for (int i = 0; i < nofFields; i++)
                targetsOp[i] = new InsnTarget();
            insn = insn.append(
                new InsnTableSwitch(lowOp, defaultOp, targetsOp));

            // do the case-targets for field accesses
            for (int i = 0; i < nofFields; i++) {
                // target for accessing field [i]
                insn = insn.append(targetsOp[i]);

                final FieldAction act
                    = (FieldAction)fieldsByName.get(fieldNames[i]);
                affirm(act,
                       ("Field '" + fieldNames[i]//NOI18N
                        + "' returned by JDOMetaData is not known by class '"//NOI18N
                        + className + "'."));//NOI18N

                // use the getMethodReturn type to create the wrapper object
                final String wrapperClassName;
                final String wrapperSignature;
                switch(act.getMethodReturn()) {
                case T_DOUBLE:
                    wrapperClassName = "java/lang/Double";//NOI18N
                    wrapperSignature = "(D)V";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_new, pool.addClass(wrapperClassName)));
                    insn = insn.append(Insn.create(opc_dup));
                    break;
                case T_LONG:
                    wrapperClassName = "java/lang/Long";//NOI18N
                    wrapperSignature = "(J)V";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_new, pool.addClass(wrapperClassName)));
                    insn = insn.append(Insn.create(opc_dup));
                    break;
                case T_FLOAT:
                    wrapperClassName = "java/lang/Float";//NOI18N
                    wrapperSignature = "(F)V";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_new, pool.addClass(wrapperClassName)));
                    insn = insn.append(Insn.create(opc_dup));
                    break;
                case T_BOOLEAN:
                    wrapperClassName = "java/lang/Boolean";//NOI18N
                    wrapperSignature = "(Z)V";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_new, pool.addClass(wrapperClassName)));
                    insn = insn.append(Insn.create(opc_dup));
                    break;
                case T_CHAR:
                    wrapperClassName = "java/lang/Character";//NOI18N
                    wrapperSignature = "(C)V";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_new, pool.addClass(wrapperClassName)));
                    insn = insn.append(Insn.create(opc_dup));
                    break;
                case T_BYTE:
                    wrapperClassName = "java/lang/Byte";//NOI18N
                    wrapperSignature = "(B)V";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_new, pool.addClass(wrapperClassName)));
                    insn = insn.append(Insn.create(opc_dup));
                    break;
                case T_SHORT:
                    wrapperClassName = "java/lang/Short";//NOI18N
                    wrapperSignature = "(S)V";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_new, pool.addClass(wrapperClassName)));
                    insn = insn.append(Insn.create(opc_dup));
                    break;
                case T_INT:
                    wrapperClassName = "java/lang/Integer";//NOI18N
                    wrapperSignature = "(I)V";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_new, pool.addClass(wrapperClassName)));
                    insn = insn.append(Insn.create(opc_dup));
                    break;
                case TC_STRING:
                case TC_OBJECT:
                case TC_INTERFACE:
                    wrapperClassName = null;
                    wrapperSignature = null;
                    break;
                default:
                    throw new InternalError("Unexpected return type");//NOI18N
                }

                // fetch this object's field
                insn = insn.append(Insn.create(opc_aload_0));
                insn = insn.append(
                    Insn.create(
                        opc_getfield,
                        pool.addFieldRef(
                            theClass.asString(),
                            act.fieldName(),
                            act.typeDescriptor())));

                // wrap the field value if primitive
                switch(act.getMethodReturn()) {
                case T_DOUBLE:
                case T_LONG:
                case T_FLOAT:
                case T_BOOLEAN:
                case T_CHAR:
                case T_BYTE:
                case T_SHORT:
                case T_INT:
                    insn = insn.append(
                        Insn.create(
                            opc_invokespecial,
                            pool.addMethodRef(
                                wrapperClassName,
                                "<init>",//NOI18N
                                wrapperSignature)));
                    break;
                case TC_STRING:
                case TC_OBJECT:
                case TC_INTERFACE:
                    break;
                default:
                    throw new InternalError("Unexpected return type");//NOI18N
                }

                // return the object (break)
                insn = insn.append(Insn.create(opc_areturn));
            }
        }

        // do the default branch target creating a fatal exception
        insn = insn.append(defaultOp);
        final String exceptionClassName
            = "com/sun/jdo/api/persistence/support/JDOFatalException";//NOI18N
        insn = insn.append(
            Insn.create(
                opc_new,
                pool.addClass(exceptionClassName)));
        insn = insn.append(Insn.create(opc_dup));
        insn = insn.append(
            Insn.create(
                opc_invokespecial,
                pool.addMethodRef(
                    exceptionClassName,
                    "<init>",//NOI18N
                    "()V")));//NOI18N

        // end of method body
        insn = insn.append(Insn.create(opc_athrow));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              4, // maxStack
                              2, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return jdoGetFieldMethod;
    }

    /**
     * Build the jdoSetField method for the class.
     *
     * public jdoSetField(int fieldNumber, Object value) {
     *     ...
     * }
     */
    //@olsen: added method for generating the jdoSetField method
    ClassMethod makeJDOSetFieldMethod(final ClassAction ca,
                                      final String methodName) {
        //@olsen: added variable
        final String methodSig = "(ILjava/lang/Object;)V";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ConstantPool pool = ca.classFile().pool();
        final ConstClass theClass = ca.classFile().className();

        final AttributeVector methodAttrs = new AttributeVector();
        final ClassMethod jdoSetFieldMethod
            = new ClassMethod(ACCPublic,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

        // get the declared, persistent fields from the JDOMetaData
        final String className = ca.className();
        //@lars: changed getPersistentFields() into getManagedFields()
        final String[] fieldNames
            = env.getJDOMetaData().getManagedFields(className);
        final int nofFields = fieldNames.length;
        //@olsen: added println() for debugging
        if (false) {
            System.out.print("MethodBuilder.makeJDOSetFieldMethod(): "//NOI18N
                             + " declared, persistent fields of class '"//NOI18N
                             + className + "' = {");//NOI18N
            for (int i = 0; i < nofFields; i++)
                System.out.print(" " + fieldNames[i]);//NOI18N
            System.out.println(" }");//NOI18N
        }

        // generate the switch-statement only if more than zero fields
        final InsnTarget defaultOp = new InsnTarget();
        if (nofFields > 0) {
            // get the declared, persistent fields from the class
            final HashMap fieldsByName = new HashMap();
            for (Iterator e = ca.fieldActions(); e.hasNext();) {
                final FieldAction act = (FieldAction)e.next();
                fieldsByName.put(act.fieldName(), act);
            }

            // do the tableswitch on argument
            insn = insn.append(Insn.create(opc_iload_1));
            final int lowOp = 0;
            final InsnTarget[] targetsOp = new InsnTarget[nofFields];
            for (int i = 0; i < nofFields; i++)
                targetsOp[i] = new InsnTarget();
            insn = insn.append(
                new InsnTableSwitch(lowOp, defaultOp, targetsOp));

            // do the case-targets for field accesses
            for (int i = 0; i < nofFields; i++) {
                // target for accessing field [i]
                insn = insn.append(targetsOp[i]);

                final FieldAction act
                    = (FieldAction)fieldsByName.get(fieldNames[i]);
                affirm(act,
                       ("Field '"//NOI18N
                        + fieldNames[i]
                        + "' returned by JDOMetaData is not known by class '"//NOI18N
                        + className + "'."));//NOI18N

                // get object and value argument
                insn = insn.append(Insn.create(opc_aload_0));
                insn = insn.append(Insn.create(opc_aload_2));

                // use the getMethodReturn type to downcast the Object argument
                final String wrapperClassName;
                final String unwrapperSignature;
                final String unwrapperName;
                switch(act.getMethodReturn()) {
                case T_DOUBLE:
                    wrapperClassName = "java/lang/Double";//NOI18N
                    unwrapperSignature = "()D";//NOI18N
                    unwrapperName = "doubleValue";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_checkcast,
                                    pool.addClass(wrapperClassName)));
                    break;
                case T_LONG:
                    wrapperClassName = "java/lang/Long";//NOI18N
                    unwrapperSignature = "()J";//NOI18N
                    unwrapperName = "longValue";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_checkcast,
                                    pool.addClass(wrapperClassName)));
                    break;
                case T_FLOAT:
                    wrapperClassName = "java/lang/Float";//NOI18N
                    unwrapperSignature = "()F";//NOI18N
                    unwrapperName = "floatValue";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_checkcast,
                                    pool.addClass(wrapperClassName)));
                    break;
                case T_BOOLEAN:
                    wrapperClassName = "java/lang/Boolean";//NOI18N
                    unwrapperSignature = "()Z";//NOI18N
                    unwrapperName = "booleanValue";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_checkcast,
                                    pool.addClass(wrapperClassName)));
                    break;
                case T_CHAR:
                    wrapperClassName = "java/lang/Character";//NOI18N
                    unwrapperSignature = "()C";//NOI18N
                    unwrapperName = "charValue";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_checkcast,
                                    pool.addClass(wrapperClassName)));
                    break;
                case T_BYTE:
                    wrapperClassName = "java/lang/Byte";//NOI18N
                    unwrapperSignature = "()B";//NOI18N
                    unwrapperName = "byteValue";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_checkcast,
                                    pool.addClass(wrapperClassName)));
                    break;
                case T_SHORT:
                    wrapperClassName = "java/lang/Short";//NOI18N
                    unwrapperSignature = "()S";//NOI18N
                    unwrapperName = "shortValue";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_checkcast,
                                    pool.addClass(wrapperClassName)));
                    break;
                case T_INT:
                    wrapperClassName = "java/lang/Integer";//NOI18N
                    unwrapperSignature = "()I";//NOI18N
                    unwrapperName = "intValue";//NOI18N
                    insn = insn.append(
                        Insn.create(opc_checkcast,
                                    pool.addClass(wrapperClassName)));
                    break;
                case TC_STRING:
                case TC_OBJECT:
                case TC_INTERFACE:
                    wrapperClassName = null;
                    unwrapperSignature = null;
                    unwrapperName = null;
                    insn = insn.append(
                        Insn.create(opc_checkcast,
                                    pool.addClass(act.typeName())));
                    break;
                default:
                    throw new InternalError("Unexpected return type");//NOI18N
                }

                // unwrap the object if primitive wrapper
                switch(act.getMethodReturn()) {
                case T_DOUBLE:
                case T_LONG:
                case T_FLOAT:
                case T_BOOLEAN:
                case T_CHAR:
                case T_BYTE:
                case T_SHORT:
                case T_INT:
                    insn = insn.append(
                        Insn.create(
                            opc_invokevirtual,
                            pool.addMethodRef(
                                wrapperClassName,
                                unwrapperName,
                                unwrapperSignature)));
                    break;
                case TC_STRING:
                case TC_OBJECT:
                case TC_INTERFACE:
                    break;
                default:
                    throw new InternalError("Unexpected return type");//NOI18N
                }

                // store argument value in field
                insn = insn.append(
                    Insn.create(
                        opc_putfield,
                        pool.addFieldRef(
                            theClass.asString(),
                            act.fieldName(),
                            act.typeDescriptor())));

                // return (break)
                insn = insn.append(Insn.create(opc_return));
            }
        }

        // do the default branch target creating a fatal exception
        insn = insn.append(defaultOp);
        final String exceptionClassName
            = "com/sun/jdo/api/persistence/support/JDOFatalException";//NOI18N
        insn = insn.append(
            Insn.create(
                opc_new,
                pool.addClass(exceptionClassName)));
        insn = insn.append(Insn.create(opc_dup));
        insn = insn.append(
            Insn.create(
                opc_invokespecial,
                pool.addMethodRef(
                    exceptionClassName,
                    "<init>",//NOI18N
                    "()V")));//NOI18N

        // end of method body
        insn = insn.append(Insn.create(opc_athrow));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              3, // maxStack
                              3, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        return jdoSetFieldMethod;
    }

    /**
     * Build the clone method for the class.
     */
    //@olsen: subst: makeClone -> makeJDOClone
    ClassMethod makeJDOClone(final ClassAction ca,
                             final String methodName) {
        //@olsen: added variable
        final String methodSig = "()Ljava/lang/Object;";//NOI18N
        env.message("adding "//NOI18N
                    + ca.classControl().userClassName() +
                    "." + methodName//NOI18N
                    + Descriptor.userMethodArgs(methodSig));

        final ClassFile cFile = ca.classFile();
        final ConstantPool pool = cFile.pool();
        final ConstClass theClass = cFile.className();
        final ConstClass superClass = cFile.superName();

        final AttributeVector methodAttrs = new AttributeVector();
        //@olsen: fixed bug: changed ACCProtected to ACCPublic to allow for
        //        an inherited method clone() to be public!
        //@olsen: removed ACCSynchronized flag
        final ClassMethod cloneMethod
            = new ClassMethod(ACCPublic, //|ACCSynchronized,
                              pool.addUtf8(methodName),
                              pool.addUtf8(methodSig),
                              methodAttrs);

        // begin of method body
        //@olsen: fix 4467428, made 'begin' final InsnTarget
        final InsnTarget begin = new InsnTarget();
        Insn insn = begin;

//@olsen: disabled feature
/*
        //   if (jdoFlags < 0)
        //     Implementation.fetch(this);

        InsnTarget cloneStart = new InsnTarget();

        insn = insn.append(Insn.create(opc_aload_0));
        insn = insn.append(
            Insn.create(opc_getfield,
                        pool.addFieldRef(theClass.asString(),
                                         JDOMetaData.JDOFlagsFieldName,
                                         JDOMetaData.JDOFlagsFieldSig)));
        insn = insn.append(Insn.create(opc_ifge, cloneStart));

        insn = insn.append(Insn.create(opc_aload_0));
        insn = insn.append(
            Insn.create(opc_invokestatic,
                        pool.addMethodRef("com/sun/forte4j/persistence/internal/Implementation", "fetch",
                                          "(" + JDOMetaData.JDOPersistenceCapableSig + ")V")));

        insn = insn.append(cloneStart);
*/

        // THISCLASS newObject = (THISCLASS) super.clone();
        {
            insn = insn.append(Insn.create(opc_aload_0));
            insn = insn.append(
                Insn.create(opc_invokespecial,
                            pool.addMethodRef(superClass.asString(),
//                            pool.addMethodRef("java/lang/Object",
                                              methodName,
                                              methodSig)));

            // add cast to the appropriate type
            insn = insn.append(Insn.create(opc_checkcast, theClass));
        }

//@olsen: disabled feature
/*
        if (ca.getNeedsJDOStateManagerMethods()) {
*/
        // newObject.jdoStateManager = null;
        if (false)
        {
            insn = insn.append(Insn.create(opc_dup));
            insn = insn.append(Insn.create(opc_aconst_null));
            insn = insn.append(
                Insn.create(
                    opc_putfield,
                    pool.addFieldRef(theClass.asString(),
                                     JDOMetaData.JDOStateManagerFieldName,
                                     JDOMetaData.JDOStateManagerFieldSig)));
        }

//@olsen: disabled feature
/*
        if (ca.getNeedsJDOFlagsMethods()) {
*/
        // newObject.jdoFlags = 0;
        if (false)
        {
            insn = insn.append(Insn.create(opc_dup));
            insn = insn.append(Insn.create(opc_iconst_0));
            insn = insn.append(
                Insn.create(opc_putfield,
                            pool.addFieldRef(theClass.asString(),
                                             JDOMetaData.JDOFlagsFieldName,
                                             JDOMetaData.JDOFlagsFieldSig)));
        }

        // return newObject;

        // end of method body
        insn = insn.append(Insn.create(opc_areturn));

        final AttributeVector codeSpecificAttrs = new AttributeVector();

        //@olsen: fix 4467428, added dummy, non-empty line number table
        if (addLineNumberTableAttr) {
            codeSpecificAttrs.addElement(
                new LineNumberTableAttribute(
                    pool.addUtf8(LineNumberTableAttribute.expectedAttrName),
                    new short[]{ 0 }, new InsnTarget[]{ begin }));
        }

        methodAttrs.addElement(
            new CodeAttribute(pool.addUtf8(CodeAttribute.expectedAttrName),
                              //@olsen: updated maxLocals
                              1, //3, //3, // maxStack
                              1, //2, // maxLocals
                              begin,
                              new ExceptionTable(),
                              codeSpecificAttrs));

        //@olsen: fix 4467428, added synthetic attribute for generated method
        if (addSyntheticAttr) {
            methodAttrs.addElement(
                new SyntheticAttribute(
                    pool.addUtf8(SyntheticAttribute.expectedAttrName)));
        }

        methodAttrs.addElement(
            new ExceptionsAttribute(
                pool.addUtf8(ExceptionsAttribute.expectedAttrName),
                pool.addClass("java/lang/CloneNotSupportedException")));//NOI18N

        return cloneMethod;
    }

    // for debugging
    static private void printFieldAction(FieldAction act) {
        System.out.println("fieldName() = " + act.fieldName());//NOI18N
        System.out.println("userFieldName() = " + act.userFieldName());//NOI18N
        System.out.println("typeDescriptor() = " + act.typeDescriptor());//NOI18N
        System.out.println("typeName() = " + act.typeName());//NOI18N
        System.out.println("fieldClassName() = " + act.fieldClassName());//NOI18N
        System.out.println("isPersistent() = " + act.isPersistent());//NOI18N
        System.out.println("isPrimaryKey() = " + act.isPrimaryKey());//NOI18N
        System.out.println("isMutableSCO() = " + act.isMutableSCO());//NOI18N
        System.out.println("isSynthetic() = " + act.isSynthetic());//NOI18N
        //System.out.println("index() = " + act.index());
        System.out.println("nDims() = " + act.nDims());//NOI18N
        System.out.println("createMethod() = " + act.createMethod());//NOI18N
        System.out.println("createMethodSig() = " + act.createMethodSig());//NOI18N
        System.out.println("setMethod() = " + act.setMethod());//NOI18N
        System.out.println("setMethodSig() = " + act.setMethodSig());//NOI18N
        System.out.println("setMethodArg() = "//NOI18N
                           + typeToString(act.setMethodArg()));
        System.out.println("getMethod() = " + act.getMethod());//NOI18N
        System.out.println("getMethodSig() = " + act.getMethodSig());//NOI18N
        System.out.println("getMethodReturn() = "//NOI18N
                           + typeToString(act.getMethodReturn()));
    }

    // for debugging
    static private String typeToString(int val) {
        switch(val) {
        case T_DOUBLE:
            return "T_DOUBLE";//NOI18N
        case T_LONG:
            return "T_LONG";//NOI18N
        case T_BOOLEAN:
            return "T_BOOLEAN";//NOI18N
        case T_CHAR:
            return "T_CHAR";//NOI18N
        case T_FLOAT:
            return "T_FLOAT";//NOI18N
        case T_BYTE:
            return "T_BYTE";//NOI18N
        case T_SHORT:
            return "T_SHORT";//NOI18N
        case T_INT:
            return "T_INT";//NOI18N
        case TC_STRING:
            return "TC_STRING";//NOI18N
        case TC_OBJECT:
            return "TC_OBJECT";//NOI18N
        case TC_INTERFACE:
            return "TC_INTERFACE";//NOI18N
        default:
            throw new InternalError("Unexpected return type");//NOI18N
        }
    }
}
