# DmoAsmWorker.java

    /*
    ** Module   : DmoAsmWorker.java
    ** Abstract : Pattern worker which assembles bytecode for DMO interfaces and classes in memory
    **
    ** Copyright (c) 2015-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --------------------------------------Description----------------------------------------
    ** 001 ECF 20150407 Created initial version, implemented using ASM v5.0.3.
    ** 002 ECF 20150513 Fixed simple getter method assembly, which did not use the correct copy
    **                  constructor signature in some cases.
    ** 003 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
    ** 004 ECF 20160219 Extracted many constants to DmoAsmTypes interface.
    ** 005 ECF 20160320 Fixed assignField method to accommodate copy directly from field for scalar
    **                  fields.
    ** 006 ECF 20160619 Fixed initSimpleField to handle non-default scale for decimals with unknown
    **                  initial value. Replaced double constant in initSimpleField with string
    **                  constant, which is slower but always accurate.
    ** 007 ECF 20171209 Removed 'force' flag parameter from all BDT.assign method signatures/calls.
    **                  Error handling changes
    ** 008 SVL 20190614 Added CLOB and BLOB data types.
    ** 009 OM  20190704 Replaced TYPE_PERSISTABLE with TYPE_TEMPTABLERECORD for dynamic DMOs.
    ** 010 ECF 20190819 Fix for setter method for recid field (credit also to EVL).
    ** 011 OM  20200906 New ORM implementation.
    **     ECF 20221207 Enable the assembly of permanent table DMO interfaces.
    ** 012 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.schema;

    import java.util.*;
    import java.util.logging.*;

    import org.objectweb.asm.*;
    import com.goldencode.asm.*;
    import com.goldencode.ast.*;
    import com.goldencode.p2j.convert.*;
    import com.goldencode.p2j.uast.*;
    import com.goldencode.p2j.util.*;

    /**
     * A pattern worker which provides services to support the in-memory compilation of DMO interface
     * and implementation classes using ASM. This worker is only used at runtime, not during static
     * conversion. Class com.goldencode.p2j.persist.DynamicTablesHelper creates 4GL-compatible schema
     * ASTs and runs TRPL tasks to convert them to Java ASTs.
     * <p>
     * The rule-sets which generate those Java ASTs behave differently, depending on whether they are
     * executed at server runtime or in batch mode for static conversion. In runtime mode, some
     * adjustments are made to the ASTs to make them more amenable to assembing class files directly.
     * In batch mode, the ASTs are optimized for downstream anti-parsing into source code. For
     * example, in runtime mode, nodes are rearranged into a structure more suitable for bytecode
     * assembly, some different token types are used to give better assembly cues, class names are
     * fully qualified, and assembly hints are added.
     * <p>
     * This work was designed not to be a general-purpose assembler/compiler, but rather to provide a
     * pragmatic solution for DMO interface/class assembly, using TRPL to walk the Java ASTs and this
     * worker's service library to bridge to services in ASM which are not easily accessible from
     * TRPL. This specialization allows us to make some assumptions about the purpose of the bytecode
     * being assembled. As such, the service APIs in this worker are only as granular as required to
     * meet the needs of the AST structure, but are as coarse as possible. In the TRPL code, we do
     * not interpret every AST node, but rather use a combination of hints left behind during the AST
     * creation and simple patterns of token types to recognize a feature (e.g., initializing a DMO
     * field within a loop). We extract the information necessary for that feature from the AST, then
     * call into this worker with that information, and let this worker assemble the bytecode
     * instructions needed to implement that feature. This often assumes knowledge of the context of
     * that feature within the broader context of its method or class, such as the state of runtime
     * operand stack and local variable pool. This approach allows us to dispense with a much more
     * complicated state machine that would be necessary to implement a more general-purpose compiler,
     * enhancing the simplicity and performance of the service.
     * <p>
     * The solution replaces the use of an in-memory compiler implementation based on the use of
     * <code>javax.tools.JavaCompiler</code>. While effective and correct, that solution had several
     * drawbacks and needed to be replaced. It was considerably slower and created memory problems
     * through its internal use of an ever-growing character array to manage source code text;  and
     * with a dubious use of soft references, which required a workaround of disabling them entirely.
     * It also required the somewhat expensive, intermediate step of source code generation, which is
     * bypassed now by assembling directly from the Java ASTs.
     * <p>
     * This worker does not finally generate the class files or load them into the JVM. The byte
     * arrays representing the finished interface and implementation classes are stored by the TRPL
     * code into the current pattern engine. The <code>DynamicTablesHelper</code> retrieves them from
     * the pattern engine and then uses a {@link com.goldencode.asm.AsmClassLoader custom class
     * loader} to load them into the JVM.
     *
     * @author ECF
     */
    public final class DmoAsmWorker
    extends AbstractConversionWorker
    implements DmoAsmTypes,
               JavaTokenTypes,
               Opcodes
    {
       /** Logger. */
       private static final ConversionStatus LOG = ConversionStatus.get(DmoAsmWorker.class);

       /** Map of field types to the signatures of their associated <code>assign</code> methods */
       private static final Map<String, String> assignMethSigs = new HashMap<>();

       /** Map of field types to the types used in their associated setter method signatures */
       private static final Map<String, String> setterTypes = new HashMap<>();

       /** Map of field types to the descriptors used in their associated copy c'tor signatures */
       private static final Map<String, String> copyCtorTypes = new HashMap<>();

       static
       {
          assignMethSigs.put(TYPE_CHARACTER , "(L" + TYPE_TEXT       + ";)V");
          assignMethSigs.put(TYPE_DATE      , "(L" + TYPE_DATE       + ";)V");
          assignMethSigs.put(TYPE_DATETIME  , "(L" + TYPE_DATE       + ";)V");
          assignMethSigs.put(TYPE_DATETIMETZ, "(L" + TYPE_DATE       + ";)V");
          assignMethSigs.put(TYPE_DECIMAL   , "(L" + TYPE_NUMBERTYPE + ";)V");
          assignMethSigs.put(TYPE_HANDLE    , "(L" + TYPE_HANDLE     + ";)V");
          assignMethSigs.put(TYPE_INT64     , "(L" + TYPE_NUMBERTYPE + ";)V");
          assignMethSigs.put(TYPE_INTEGER   , "(L" + TYPE_NUMBERTYPE + ";)V");
          assignMethSigs.put(TYPE_LOGICAL   , "(L" + TYPE_LOGICAL    + ";)V");
          assignMethSigs.put(TYPE_LONGCHAR  , "(L" + TYPE_TEXT       + ";)V");
          assignMethSigs.put(TYPE_MEMPTR    , "(L" + TYPE_BINARYDATA + ";)V");
          assignMethSigs.put(TYPE_RAW       , "(L" + TYPE_BINARYDATA + ";)V");
          assignMethSigs.put(TYPE_RECID     , "(L" + TYPE_NUMBERTYPE + ";)V");
          assignMethSigs.put(TYPE_ROWID     , "(L" + TYPE_ROWID      + ";)V");
          assignMethSigs.put(TYPE_CLOB      , "(L" + TYPE_CLOB       + ";)V");
          assignMethSigs.put(TYPE_BLOB      , "(L" + TYPE_BLOB       + ";)V");

          setterTypes.put(TYPE_CHARACTER , TYPE_TEXT);
          setterTypes.put(TYPE_DATETIME  , TYPE_DATE);
          setterTypes.put(TYPE_DATETIMETZ, TYPE_DATE);
          setterTypes.put(TYPE_DECIMAL   , TYPE_NUMBERTYPE);
          setterTypes.put(TYPE_INT64     , TYPE_NUMBERTYPE);
          setterTypes.put(TYPE_INTEGER   , TYPE_NUMBERTYPE);
          setterTypes.put(TYPE_RECID     , TYPE_NUMBERTYPE);
          setterTypes.put(TYPE_LONGCHAR  , TYPE_TEXT);
          setterTypes.put(TYPE_MEMPTR    , TYPE_BINARYDATA);
          setterTypes.put(TYPE_RAW       , TYPE_BINARYDATA);
          setterTypes.put(TYPE_CLOB      , TYPE_CLOB);
          setterTypes.put(TYPE_BLOB      , TYPE_BLOB);

          copyCtorTypes.put(TYPE_DATETIME  , DESC_DATE);
          copyCtorTypes.put(TYPE_DATETIMETZ, DESC_DATE);
       }

       /**
        * Default constructor which defines the symbol library to be registered.
        */
       public DmoAsmWorker()
       {
          super();

          setLibrary(new Library());
       }

       /**
        * Create an ASM <code>ClassWriter</code> which computes frame and max stack depth information
        * automatically.
        *
        * @return  See above.
        */
       private static ClassWriter createClassWriter()
       {
          return new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
       }

       /**
        * Make a fully qualified, internal type descriptor from the given, short annotation name.
        *
        * @param   name
        *          Unqualified annotation name.
        *
        * @return  Fully qualified descriptor.
        */
       private static String makeAnnotationDescriptor(String name)
       {
          return ANNOTATION_DESC_PREFIX + name + ";";
       }

       /**
        * Given the internal type name of a DMO field, compose an indexed setter method signature of
        * the form <code>(IL&lt;type name&gt;;)V</code>.
        *
        * @param   fieldType
        *          Internal type name of a DMO field.
        *
        * @return  Indexed setter method signature.
        */
       private static String makeIndexedSetterSignature(String fieldType)
       {
          StringBuilder buf = new StringBuilder("(IL");
          String parmType = setterTypes.get(fieldType);
          if (parmType == null)
          {
             parmType = fieldType;
          }
          buf.append(parmType);
          buf.append(";)V");

          return buf.toString();
       }

       /**
        * Implement the loading of a composite element from its ArrayList and casting it to the
        * appropriate inner class type. Assumes the index to be retrieved is an int primitive
        * available in local variable 1 (i.e., the first parameter passed to the method being
        * processed).
        *
        * @param   mv
        *          ASM method visitor.
        * @param   classType
        *          Fully qualified internal type name of enclosing class.
        * @param   compFieldName
        *          Short name of field in enclosing class which contains the array list of
        *          composites we need to access.
        * @param   compClassName
        *          Short name of composite inner class.
        *
        * @return  The fully qualified type name of the composite inner class.
        */
       private static String loadComposite(MethodVisitor mv,
                                           String classType,
                                           String compFieldName,
                                           String compClassName)
       {
          // prepend enclosing class type to short name of composite class to create fully
          // qualified composite class type
          String compClassType = classType + '$' + compClassName;

          // load "this" from local variable 0
          mv.visitVarInsn(ALOAD, 0);

          // push the object reference within the specified field onto the stack
          mv.visitFieldInsn(GETFIELD, classType, compFieldName, DESC_LIST);

          // load first method parameter (the array index -- an int) into local variable 1
          mv.visitVarInsn(ILOAD, 1);

          // invoke ArrayList.get method
          mv.visitMethodInsn(INVOKEINTERFACE, TYPE_LIST, METH_GET, SIG_GET, true);

          // cast to appropriate composite class type
          mv.visitTypeInsn(CHECKCAST, compClassType);

          return compClassType;
       }

       /**
        * Implement the loading of a <code>NumberType</code> object and extracting its int value.
        *
        * @param   mv
        *          ASM method visitor.
        */
       private static void loadIndexParameter(MethodVisitor mv)
       {
          // load method parameter object reference (index as NumberType) into local variable 1
          mv.visitVarInsn(ALOAD, 1);

          // invoke NumberType.intValue to push index int value onto stack
          mv.visitMethodInsn(INVOKEVIRTUAL, TYPE_NUMBERTYPE, METH_INTVALUE, SIG_INTVALUE, false);
       }

       /**
        * An API of library functions available to TRPL to provide services to assemble DMO interface
        * and implementation classes using ASM.
        */
       public static class Library
       {
          /** Map of Java primitive data type common names to their internal type names */
          private static Map<String, String> primitives = new HashMap<>();

          static
          {
             primitives.put("boolean", "Z");
             primitives.put("byte"   , "B");
             primitives.put("char"   , "C");
             primitives.put("double" , "D");
             primitives.put("float"  , "F");
             primitives.put("int"    , "I");
             primitives.put("long"   , "J");
             primitives.put("short"  , "S");
             primitives.put("void"   , "V");
          }

          /**
           * Given a common type name for a primitive data type or Java object, convert it to the
           * internal type form name or descriptor needed for assembly. For object names, this
           * involves replacing all dot (<code>.</code>) characters with forward slash
           * (<code>/</code>) and involves additional transformation if <code>forSig</code> is
           * <code>true</code> (see below).
           *
           * @param   name
           *          Common name (e.g. <code>com.goldencode.p2j.util.integer</code> for the P2J
           *          <code>integer</code> class or <code>int</code> for the Java primitive integer)
           * @param   forSig
           *          If <code>true</code>, assume the result is being used for a method signature,
           *          and thus needs to be formatted as a descriptor. This prepends open square
           *          bracket (<code>[</code>) for arrays, and for object names, prepends capital
           *          L (<code>L</code>) and appends semi-colon(<code>;</code>).
           *
           * @return  Internal type name or descriptor as described above.
           */
          public String convertTypeName(String name, boolean forSig)
          {
             String type = primitives.get(name);
             if (type != null)
             {
                return type;
             }

             type = AsmUtils.commonToInternalTypeName(name);

             if (forSig)
             {
                StringBuilder buf = new StringBuilder("L");
                int pos = type.lastIndexOf("[]");
                if (pos < 0)
                {
                   buf.append(type);
                }
                else
                {
                   buf.insert(0, '[');
                   buf.append(type, 0, pos);
                }
                buf.append(';');
                type = buf.toString();
             }

             return type;
          }

          /**
           * Create and initialize a {@code ClassWriter} for a top-level, DMO interface of the given type. The
           * interface must extend {@code com.goldencode.p2j.persist.Temporary} (for a temp-table DMO interface)
           * or {@code com.goldencode.p2j.persist.DataModelObject} (for a permanent table DMO interface).
           *
           * @param   ifaceName
           *          Fully qualified, internal type name of the DMO interface.
           * @param   temporary
           *          {@code true} if the interface represents a temp-table DMO, {@code false} if it represents
           *          a permanent table.
           *
           * @return  Initialized {@code ClassWriter}.
           */
          public ClassWriter createTopIfaceClassWriter(String ifaceName, boolean temporary)
          {
             ClassWriter cw = createClassWriter();

             String innerIfaceName = ifaceName + BUF_SUFFIX;
             String superIface = temporary ? TYPE_TEMPORARY : TYPE_DATA_MODEL_OBJECT;
             cw.visit(V1_7,
                      ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE,
                      ifaceName,
                      null,
                      TYPE_OBJECT,
                      new String[] { superIface });

             // this is done both for the enclosing interface and when we visit the inner interface
             cw.visitInnerClass(innerIfaceName,
                                ifaceName,
                                BUF,
                                ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);

             return cw;
          }

          /**
           * Create and initialize a {@code ClassWriter} for a static, inner interface named
           * {@code Buf}, which extends {@code com.goldencode.p2j.persist.Buffer} (or
           * {@code TempTableBuffer} for temp tables) and the enclosing, top-level interface.
           *
           * @param   outerName
           *          Fully qualified, internal type name of enclosing interface.
           * @param   tempTable
           *          Flags the temp tables. The temp tables extend {@code TempTableBuffer}.
           *
           * @return  Initialized {@code ClassWriter}.
           */
          public ClassWriter createInnerIfaceClassWriter(String outerName, boolean tempTable)
          {
             ClassWriter cw = createClassWriter();

             String ifaceName = outerName + BUF_SUFFIX;
             String[] extNames = new String[]
             {
                outerName,
                tempTable ? TEMP_TABLE_BUFFER_IFACE : BUFFER_IFACE,
             };
             cw.visit(V1_7,
                      ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE,
                      ifaceName,
                      null,
                      TYPE_OBJECT,
                      extNames);
             cw.visitInnerClass(ifaceName,
                                outerName,
                                BUF,
                                ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);

             return cw;
          }

          /**
           * Create and initialize a {@code ClassWriter} for a top-level, DMO implementation
           * class of the given type, which implements the given DMO interface. In addition, the
           * class will implement:
           * <ul>
           * <li>{@code java.io.Serializable}</li>
           * <li>{@code com.goldencode.p2j.persist.Persistable}</li>
           * </ul>
           *
           * @param   implName
           *          Fully qualified, internal type name of the DMO implementation class.
           * @param   ifaceName
           *          Fully qualified, internal type name of the DMO interface which the class must
           *          implement.
           * @param   innerNames
           *          Optional list of fully qualified, internal type names which the DMO class will
           *          enclose as static inner classes. May be {@code null} or an empty list.
           *
           * @return  Initialized {@code ClassWriter}.
           */
          public ClassWriter createTopClassWriter(String implName,
                                                  String ifaceName,
                                                  List<String> innerNames)
          {
             ClassWriter cw = createClassWriter();

             // better use the KW_IMPLEMENTS list from AST instead of hard-coding this list here.
             // the difficulty is that the KW_IMPLEMENTS children are not fully qualified
             String[] ifaces = new String[]
             {
                TYPE_SERIALIZABLE,
                TYPE_TEMPTABLERECORD,
                ifaceName,
             };
             cw.visit(V1_7,
                      ACC_PUBLIC | ACC_SUPER,
                      implName,
                      null,
                      TYPE_OBJECT,
                      ifaces);

             if (innerNames != null && innerNames.size() > 0)
             {
                StringBuilder buf = new StringBuilder(implName);
                int len = implName.length();

                for (String next : innerNames)
                {
                   buf.setLength(len);
                   buf.append("$");
                   buf.append(next);
                   String innerName = buf.toString();

                   // this is done both for the enclosing class and when we visit the inner class
                   cw.visitInnerClass(innerName, implName, next, ACC_STATIC);
                }
             }

             return cw;
          }

          /**
           * Create and initialize a <code>ClassWriter</code> for an inner class.
           *
           * @param   outerName
           *          Fully qualified, internal type name of enclosing class.
           * @param   shortInnerName
           *          Short name of inner class.
           * @param   fullInnerName
           *          Fully qualified, internal type name of inner class.
           *
           * @return  Initialized <code>ClassWriter</code>.
           */
          public ClassWriter createInnerClassWriter(String outerName,
                                                    String shortInnerName,
                                                    String fullInnerName)
          {
             ClassWriter cw = createClassWriter();

             cw.visit(V1_7,
                      ACC_SUPER,
                      fullInnerName,
                      null,
                      TYPE_OBJECT,
                      new String[] { TYPE_SERIALIZABLE });

             cw.visitInnerClass(fullInnerName, outerName, shortInnerName, ACC_STATIC);

             return cw;
          }

          /**
           * Implement the declaration of an abstract, public method with the given name and
           * signature in a DMO interface.
           *
           * @param   cw
           *          Class writer for the method's interface.
           * @param   name
           *          Method name.
           * @param   sig
           *          Method signature, using internal type descriptors for parameters and return
           *          type.
           *
           * @return  The method visitor.
           */
          public MethodVisitor declareMethod(ClassWriter cw, String name, String sig)
          {
             MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, name, sig, null, null);
             mv.visitEnd();
             return mv;
          }

          /**
           * Create a {@code MethodVisitor} for the purpose of defining the implementation of a
           * public method. The caller will use this object to assemble the instructions of the
           * method; typically, it will be passed to additional methods in this library to complete
           * the implementation.
           *
           * @param   cw
           *          Class writer for the method's class.
           * @param   name
           *          Method name.
           * @param   sig
           *          Method signature, using internal type descriptors for parameters and return
           *          type.
           *
           * @return  The method visitor object.
           */
          public MethodVisitor defineMethod(ClassWriter cw, String name, String sig)
          {
             return cw.visitMethod(ACC_PUBLIC, name, sig, null, null);
          }

          /**
           * Create an initialized <code>FieldVisitor</code> for the purpose of declaring a DMO field
           * as an instance variable. The initialization of the field itself is implemented in the
           * DMO implementation class' (or inner class') default constructor. The field visitor is
           * used by the caller to implement annotations.
           *
           * @param   cw
           *          Class writer for the field's class.
           * @param   name
           *          Field name.
           * @param   type
           *          Field descriptor.
           * @param   isFinal
           *          <code>true</code> if the field is final, else <code>false</code>.
           *
           * @return  The field visitor object.
           */
          public FieldVisitor declareInstanceVariable(ClassWriter cw,
                                                      String name,
                                                      String type,
                                                      boolean isFinal)
          {
             int access = ACC_PRIVATE;
             if (isFinal)
             {
                access |= ACC_FINAL;
             }

             return cw.visitField(access, name, type, null, null);
          }

          /**
           * Create an {@code AnnotationVisitor} for the purpose of implementing a class-level Java
           * annotation.
           *
           * @param   cw
           *          Class writer for the enclosing class.
           * @param   name
           *          Unqualified annotation name.
           *
           * @return  The annotation visitor object.
           */
          public AnnotationVisitor createAnnotationVisitor(ClassWriter cw, String name)
          {
             String desc = makeAnnotationDescriptor(name);

             return cw.visitAnnotation(desc, true);
          }

          /**
           * Create an {@code AnnotationVisitor} for the purpose of implementing a nested Java
           * annotation (an element in an array of annotations).
           *
           * @param   av
           *          Annotation visitor for the enclosing annotation.
           * @param   name
           *          Unqualified annotation name.
           *
           * @return  The annotation visitor object.
           */
          public AnnotationVisitor createAnnotationVisitor(AnnotationVisitor av, String name)
          {
             String desc = makeAnnotationDescriptor(name);

             return av.visitAnnotation(null, desc);
          }

          /**
           * Create an {@code AnnotationVisitor} for the purpose of implementing a field-level Java
           * annotation.
           *
           * @param   fv
           *          Field visitor for the associated field.
           * @param   name
           *          Unqualified annotation name.
           *
           * @return  The annotation visitor object.
           */
          public AnnotationVisitor createAnnotationVisitor(FieldVisitor fv, String name)
          {
             String desc = makeAnnotationDescriptor(name);

             return fv.visitAnnotation(desc, true);
          }

          /**
           * Create an {@code AnnotationVisitor} for the purpose of implementing a method-level Java
           * annotation.
           *
           * @param   mv
           *          Field visitor for the associated field.
           * @param   name
           *          Unqualified annotation name.
           *
           * @return  The annotation visitor object.
           */
          public AnnotationVisitor createAnnotationVisitor(MethodVisitor mv, String name)
          {
             String desc = makeAnnotationDescriptor(name);

             return mv.visitAnnotation(desc, true);
          }

          /**
           * Assemble the bytecode instructions common to the beginning of a top-level DMO's default
           * constructor and an inner class' default constructor. These instructions invoke the
           * default constructor of the superclass, {@code java.lang.Object}.
           *
           * @param   mv
           *          The default constructor's method visitor.
           */
          public void defaultCtorCommon(MethodVisitor mv)
          {
             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // invoke the superclass' default constructor
             mv.visitMethodInsn(INVOKESPECIAL, TYPE_OBJECT, METH_INIT, SIG_DEF_INIT, false);
          }

          /**
           * Assemble the bytecode instructions to initialize a DMO field to {@code null} in a
           * default constructor. This is used for the DMO's primary key and multiplex ID fields.
           *
           * @param   mv
           *          The default constructor's method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   fieldName
           *          Field name.
           * @param   fieldDesc
           *          Field descriptor.
           */
          public void initDirectFieldNull(MethodVisitor mv,
                                          String classType,
                                          String fieldName,
                                          String fieldDesc)
          {
             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // push null onto stack
             mv.visitInsn(ACONST_NULL);

             // put null into field
             mv.visitFieldInsn(PUTFIELD, classType, fieldName, fieldDesc);
          }

          /**
           * Assemble the bytecode instructions to initialize a DMO field using a constructor
           * specific to the field's type. The instructions are added to a DMO's default constructor.
           * The field-level constructor may or may not have parameters passed to it.
           * <p>
           * If the initializer provided in <code>ctorAst</code> cannot be converted to a value of
           * the required type for the field, an error is logged and the field type's default
           * constructor is used, which generally will result in the field being initialized to
           * unknown value. Note that we do not support initializers for the following types at this
           * time, and these always will be initialized using their default constructors:
           * <ul>
           * <li>handle</li>
           * <li>memptr</li>
           * <li>raw</li>
           * </ul>
           *
           * @param   mv
           *          The default constructor's method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   fieldName
           *          Field name.
           * @param   fieldDesc
           *          Field descriptor.
           * @param   ctorAst
           *          Java AST node representing the field-level constructor to be invoked to
           *          initialize the field.
           */
          public void initSimpleField(MethodVisitor mv,
                                      String classType,
                                      String fieldName,
                                      String fieldDesc,
                                      Aast ctorAst)
          {
             String ctorOwner = AsmUtils.commonToInternalTypeName(ctorAst.getText());

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // instantiate the field object
             mv.visitTypeInsn(NEW, ctorOwner);

             // duplicate the object reference; the top reference is used to invoke the field-level
             // c'tor, the bottom reference is used to put the object into the DMO field
             mv.visitInsn(DUP);

             String fieldCtorParms = null;

             // handle field-level c'tor parameters, if any, by pushing them onto the stack with the
             // appropriate instruction(s)
             if (!ctorAst.isLeaf())
             {
                String initText = ctorAst.getFirstChild().getText();
                try
                {
                   switch (ctorOwner)
                   {
                      case TYPE_CHARACTER:
                      case TYPE_LONGCHAR:
                      case TYPE_ROWID:
                      {
                         fieldCtorParms = DESC_JAVA_STRING;
                         mv.visitLdcInsn(initText);
                         break;
                      }
                      case TYPE_DECIMAL:
                      {
                         Aast ast = (Aast) ctorAst.getFirstChild();
                         String numString = initText;
                         switch (ast.getType())
                         {
                            case NUM_LITERAL:
                            {
                               integer i = new integer(numString);
                               AsmUtils.pushInt(mv, i.intValue());
                               fieldCtorParms = "I";
                               break;
                            }
                            case DEC_LITERAL:
                            {
                               decimal d = new decimal(numString);
                               mv.visitLdcInsn(d.toStringExport());
                               fieldCtorParms = DESC_JAVA_STRING;
                               break;
                            }
                            case NULL_LITERAL:
                            {
                               // push null onto stack
                               mv.visitInsn(ACONST_NULL);
                               fieldCtorParms = DESC_JAVA_STRING;
                            }
                            default:
                               break;
                         }
                         ast = (Aast) ast.getNextSibling();
                         if (ast != null)
                         {
                            String scaleString = ast.getText();
                            integer scale = new integer(scaleString);
                            AsmUtils.pushInt(mv, scale.intValue());
                            fieldCtorParms += "I";
                         }
                         break;
                      }
                      case TYPE_INT64:
                      {
                         int64 i = new int64(initText);
                         mv.visitLdcInsn(i.longValue());
                         fieldCtorParms = "J";
                         break;
                      }
                      case TYPE_ARRAYLIST:
                      case TYPE_INTEGER:
                      case TYPE_RECID:
                      {
                         fieldCtorParms = "I";
                         integer i = new integer(initText);
                         AsmUtils.pushInt(mv, i.intValue());
                         break;
                      }
                      case TYPE_LOGICAL:
                      {
                         fieldCtorParms = "Z";
                         if (ctorAst.getFirstChild().getType() == JavaTokenTypes.BOOL_TRUE)
                         {
                            mv.visitInsn(ICONST_1);
                         }
                         else
                         {
                            mv.visitInsn(ICONST_0);
                         }
                         break;
                      }

                      // TODO: implement? How is initializer specified for these?
                      case TYPE_HANDLE:
                      case TYPE_MEMPTR:
                      case TYPE_RAW:

                      default:
                         // TODO: represents missing functionality; note this is caught immediately
                         // below
                         throw new ErrorConditionException(
                            "Unrecognized field initializer type: " + ctorOwner);
                   }
                }
                catch (ErrorConditionException exc)
                {
                   String msg =
                      "Unable to initialize field %s (%s) in dynamic DMO class %s to '%s'; " +
                         "field will be initialized to unknown value instead";
                   msg = String.format(msg, fieldName, fieldDesc, classType, initText);
                   LOG.log(Level.SEVERE, msg, exc);
                }
             }

             // compose the field-level c'tor's signature
             String fieldCtorSig;
             if (fieldCtorParms != null)
             {
                fieldCtorSig = "(" + fieldCtorParms + ")V";
             }
             else
             {
                fieldCtorSig = SIG_DEF_INIT;
             }

             // invoke the field's c'tor
             mv.visitMethodInsn(INVOKESPECIAL, ctorOwner, METH_INIT, fieldCtorSig, false);

             // put the initialized object into the DMO field
             mv.visitFieldInsn(PUTFIELD, classType, fieldName, fieldDesc);
          }

          /**
           * Assemble the bytecode instructions to initialize a DMO field using an static method
           * implementation of a Progress-compatible builtin function (e.g., <code>date.today</code>).
           * The instructions are added to a DMO's default constructor. Assumes that the static method
           * either accepts no arguments, or a single argument of type <code>java.lang.String</code>.
           *
           * @param   mv
           *          The default constructor's method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   fieldName
           *          Field name.
           * @param   fieldType
           *          Fully qualified, internal type name of field.
           * @param   fieldDesc
           *          Field descriptor.
           * @param   methName
           *          Static method name.
           * @param   initText
           *          String to be passed to the static method; <code>null</code> if the method takes
           *          no arguments.
           */
          public void initFieldBuiltin(MethodVisitor mv,
                                       String classType,
                                       String fieldName,
                                       String fieldType,
                                       String fieldDesc,
                                       String methName,
                                       String initText)
          {
             // create builtin's static method signature from initial text (if any) and field
             // description
             StringBuilder buf = new StringBuilder("(");
             if (initText != null)
             {
                buf.append(DESC_JAVA_STRING);
             }
             buf.append(")");
             buf.append(fieldDesc);
             String methSig = buf.toString();

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);
             if (initText != null)
             {
                // push method argument string onto stack
                mv.visitLdcInsn(initText);
             }

             // invoke the static method
             mv.visitMethodInsn(INVOKESTATIC, fieldType, methName, methSig, false);

             // put the initialized object into the DMO field
             mv.visitFieldInsn(PUTFIELD, classType, fieldName, fieldDesc);
          }

          /**
           * Assemble the bytecode instructions to initialize a list of composite, inner class
           * objects. The loop is executed <code>extent</code> times. In each pass, the default
           * constructor of the inner class is invoked, and the resulting object is added to the
           * list.
           *
           * @param   mv
           *          The default constructor's method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   fieldName
           *          Composite list field name.
           * @param   compType
           *          Fully qualified internal type name of composite inner class.
           * @param   extent
           *          List size.
           */
          public void initCompositeLoop(MethodVisitor mv,
                                        String classType,
                                        String fieldName,
                                        String compType,
                                        int extent)
          {
             // push 0 onto stack
             mv.visitInsn(ICONST_0);

             // store 0 into local variable 1, which is the for loop counter
             mv.visitVarInsn(ISTORE, 1);

             // label the top of the loop
             Label top = new Label();
             mv.visitLabel(top);

             // load the current loop counter value from local variable 1
             mv.visitVarInsn(ILOAD, 1);

             // push list size (representing loop count limit) onto stack
             AsmUtils.pushInt(mv, extent);

             // create a label for the bottom of the loop
             Label bottom = new Label();

             // compare the loop counter with the limit; if the former is >= the latter, jump to the
             // bottom of the loop
             mv.visitJumpInsn(IF_ICMPGE, bottom);

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // get the list reference from its instance variable
             mv.visitFieldInsn(GETFIELD, classType, fieldName, DESC_LIST);

             // instantiate a new composite (inner class) object
             mv.visitTypeInsn(NEW, compType);

             // duplicate the reference; top is used for the composite c'tor, bottom is used to add
             // it to the list of composite objects
             mv.visitInsn(DUP);

             // invoke the composite class' c'tor
             mv.visitMethodInsn(INVOKESPECIAL, compType, METH_INIT, SIG_DEF_INIT, false);

             // add the composite object to the list
             mv.visitMethodInsn(INVOKEINTERFACE, TYPE_LIST, METH_ADD, SIG_ADD, true);

             // ignore the return value from the list's add method
             mv.visitInsn(POP);

             // increment the loop counter in local variable 1 by 1
             mv.visitIincInsn(1, 1);

             // jump back to the top of the loop
             mv.visitJumpInsn(GOTO, top);

             // mark this location as the destination for IF_ICMPGE jump above
             mv.visitLabel(bottom);
          }

          /**
           * Assemble the bytecode instructions for the DMO constructor variant which accepts a
           * multiplex ID. The implementation invokes the default constructor and stores the
           * parameter into the <code>_multiplex</code> field.
           *
           * @param   mv
           *          The constructor's method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           */
          public void multiplexCtor(MethodVisitor mv, String classType)
          {
             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // invoke the DMO's default c'tor
             mv.visitMethodInsn(INVOKESPECIAL, classType, METH_INIT, SIG_DEF_INIT, false);

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // load the multiplex ID passed to this c'tor from local variable 1
             mv.visitVarInsn(ALOAD, 1);

             // put the multiplex ID into its DMO field
             mv.visitFieldInsn(PUTFIELD, classType, FLD_MULTIPLEX, DESC_JAVA_INTEGER);

             // void return
             mv.visitInsn(RETURN);
          }

          /**
           * Assemble the bytecode instructions for the DMO <code>deepCopy</code> method, which
           * instantiates a new DMO of this class' type, assigns the current DMO's data to it, and
           * returns it. The work of copying the data is delegated to the DMO's <code>assign</code>
           * method.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           */
          public void deepCopy(MethodVisitor mv, String classType)
          {
             // instantiate a new DMO
             mv.visitTypeInsn(NEW, classType);

             // duplicate the DMO object reference on the stack
             mv.visitInsn(DUP);

             // call the DMO's default constructor to initialize it
             mv.visitMethodInsn(INVOKESPECIAL, classType, METH_INIT, SIG_DEF_INIT, false);

             // store the DMO reference in local variable 1
             mv.visitVarInsn(ASTORE, 1);

             // load the DMO reference from local variable 1 to push it back onto the stack
             mv.visitVarInsn(ALOAD, 1);

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // call the DMO's assign method, passing in this object as the argument
             mv.visitMethodInsn(INVOKEINTERFACE, TYPE_UNDOABLE, METH_ASSIGN, SIG_DMO_ASSIGN, true);

             // load the new DMO back onto the stack
             mv.visitVarInsn(ALOAD, 1);

             // return the new DMO
             mv.visitInsn(ARETURN);
          }

          /**
           * Assemble the initial bytecode instructions for the DMO {@code assign} method.
           * These instructions load the {@code Undoable} object and cast it to the current
           * DMO's type. The remainder of the method's implementation varies according to the fields
           * in the DMO. The full implementation consists of the following flow:
           * <ul>
           *    <li>one call to {@code #assign(MethodVisitor, String)}</li>
           *    <li>one or more calls to {@link #assignField(MethodVisitor, String, String, String,
           *       String, boolean)}</li>
           *    <li>zero or more calls to {@link #assignLoopBegin(MethodVisitor, String, int)}
           *       <ul>
           *          <li>one or more calls to {@link #assignField(MethodVisitor, String, String,
           *             String, String, boolean)}</li>
           *       </ul>
           *    </li>
           *    <li>one call to {@link #voidReturn(MethodVisitor)}</li>
           * </ul>
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           */
          public void assign(MethodVisitor mv, String classType)
          {
             // load method parameter Undoable object reference into local variable 1
             mv.visitVarInsn(ALOAD, 1);

             // cast to enclosing class
             mv.visitTypeInsn(CHECKCAST, classType);

             // store object reference from top of stack into local variable 2
             mv.visitVarInsn(ASTORE, 2);
          }

          /**
           * Assemble the bytecode instructions to assign a single field or element of a composite
           * field from a DMO instance passed as an argument to the DMO <code>assign</code> method
           * (hereafter, "source" DMO) into the current DMO (hereafter, "target" DMO) object's
           * matching field.
           * <p>
           * For scalar fields, this is accomplished by invoking the target DMO's setter field and
           * passing in the source DMO's corresponding instance member.
           * <p>
           * For composite fields, this is accomplished by invoking the source DMO's getter method
           * for the desired field and storing the result in the target DMO by invoking its matching
           * setter method.
           * <p>
           * The setter instructions will differ slightly if the field is part of a composite inner
           * class, in that an index parameter must be handled for the method call.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   fieldType
           *          Fully qualified, internal type name of field.
           * @param   getterOrFieldName
           *          Name of getter method to be invoked on source DMO.
           * @param   setterMethName
           *          Name of setter method to be invoked on target DMO.
           * @param   inLoop
           *          <code>true</code> if the assignment takes place within the context of an assign
           *          loop (for a composite field), else <code>false</code>.
           *
           * @see     #assign(MethodVisitor, String)
           */
          public void assignField(MethodVisitor mv,
                                  String classType,
                                  String fieldType,
                                  String getterOrFieldName,
                                  String setterMethName,
                                  boolean inLoop)
          {
             StringBuilder buf = new StringBuilder();

             // create the setter method signature
             buf.append(inLoop ? "(IL" : "(L");
             String parmType = setterTypes.get(fieldType);
             if (parmType == null)
             {
                parmType = fieldType;
             }
             buf.append(parmType);
             buf.append(";)V");
             String setterSig = buf.toString();

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // load index of getter from local variable 3 (loop index) if in loop
             if (inLoop)
             {
                mv.visitVarInsn(ILOAD, 3);
             }

             // load DMO from which we are assigning data from local variable 2
             mv.visitVarInsn(ALOAD, 2);

             buf.setLength(0);

             if (inLoop)
             {
                // load index of getter from local variable 3 (loop index) if in loop
                mv.visitVarInsn(ILOAD, 3);

                // create the getter method signature
                buf.append("(I)L");
                buf.append(fieldType);
                buf.append(";");
                String getterSig = buf.toString();

                // call source DMO's getter method for target field, pushing the result onto stack
                mv.visitMethodInsn(INVOKEVIRTUAL, classType, getterOrFieldName, getterSig, false);
             }
             else
             {
                // create the field type descriptor
                buf.append("L");
                buf.append(fieldType);
                buf.append(";");
                String fieldDesc = buf.toString();

                // get the source field
                mv.visitFieldInsn(GETFIELD, classType, getterOrFieldName, fieldDesc);
             }

             // call target (this) DMO's setter method for target field, passing value from stack
             mv.visitMethodInsn(INVOKEVIRTUAL, classType, setterMethName, setterSig, false);
          }

          /**
           * Assemble the bytecode instructions for the top of a loop in the DMO <code>assign</code>
           * method, which is used to assign one or more composite fields. The actual assignment is
           * assembled in {@link #assignField(MethodVisitor, String, String, String, String, boolean)}
           * and the bottom of the loop is assembled in {@link #assignLoopEnd(MethodVisitor, Object)}.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   extent
           *          List size.
           *
           * @return  A size 2 array of ASM <code>Label</code> objects, where the first element marks
           *          the top of the loop and the second the bottom. This is returned as type
           *          <code>Object</code> to enable TRPL to handle it, since TRPL cannot deal with
           *          arrays. A <code>List</code> was considered, but an array was deemed to have
           *          lower overhead.
           */
          public Object assignLoopBegin(MethodVisitor mv, String classType, int extent)
          {
             // push 0 onto the stack
             mv.visitInsn(ICONST_0);

             // store 0 into local variable 3, which is the for loop counter
             mv.visitVarInsn(ISTORE, 3);

             // label the top of the loop
             Label top = new Label();
             mv.visitLabel(top);

             // load the current loop counter value from local variable 3
             mv.visitVarInsn(ILOAD, 3);

             // push list size (representing loop count limit) onto stack
             AsmUtils.pushInt(mv, extent);

             // create a label for the bottom of the loop
             Label bottom = new Label();

             // compare the loop counter with the limit; if the former is >= the latter, jump to the
             // bottom of the loop
             mv.visitJumpInsn(IF_ICMPGE, bottom);

             // store the labels into a small array to be returned to the caller, these will be
             // needed by assignLoopEnd to mark the loop bottom and implementing the GOTO to jump
             // to the top of the loop

             return new Label[] { top, bottom };
          }

          /**
           * Assemble the bytecode instructions for the bottom of a loop in the DMO
           * <code>assign</code> method, which is used to assign one or more composite fields.
           *
           * @param   mv
           *          The method visitor.
           * @param   labelArray
           *          A size 2 array of ASM <code>Label</code> objects, where the first element marks
           *          the top of the loop and the second the bottom.
           *
           * @see     #assign(MethodVisitor, String)
           * @see     #assignLoopBegin(MethodVisitor, String, int)
           */
          public void assignLoopEnd(MethodVisitor mv, Object labelArray)
          {
             Label[] labels = (Label[]) labelArray;
             Label top = labels[0];
             Label bottom = labels[1];

             // increment the loop counter in local variable 3 by 1
             mv.visitIincInsn(3, 1);

             // jump back to the top of the loop
             mv.visitJumpInsn(GOTO, top);

             // mark this location as the destination for IF_ICMPGE jump in assignLoopBegin
             mv.visitLabel(bottom);
          }

          /**
           * Assemble the bytecode instruction for a void return from a method.
           *
           * @param   mv
           *          The method visitor.
           */
          public void voidReturn(MethodVisitor mv)
          {
             // void return
             mv.visitInsn(RETURN);
          }

          /**
           * Assemble the bytecode instructions for a DMO method which retrieves and returns the
           * object in the specified field. This implementation is used for fields whose values are
           * immutable, such as the primary key and multiplex ID.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   fieldName
           *          Field name.
           * @param   fieldDesc
           *          Field descriptor.
           */
          public void directGetter(MethodVisitor mv,
                                   String classType,
                                   String fieldName,
                                   String fieldDesc)
          {
             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // push the object reference within the specified field onto the stack
             mv.visitFieldInsn(GETFIELD, classType, fieldName, fieldDesc);

             // return object reference
             mv.visitInsn(ARETURN);
          }

          /**
           * Assemble the bytecode instructions for a DMO method which sets the value of the
           * specified field to an object passed into the method. This implementation is used for
           * fields whose values are immutable, such as the primary key and multiplex ID.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   fieldName
           *          Field name.
           * @param   fieldDesc
           *          Field descriptor.
           */
          public void directSetter(MethodVisitor mv,
                                   String classType,
                                   String fieldName,
                                   String fieldDesc)
          {
             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // load method parameter object reference into local variable 1
             mv.visitVarInsn(ALOAD, 1);

             // put local variable 1 value into field
             mv.visitFieldInsn(PUTFIELD, classType, fieldName, fieldDesc);

             // void return
             mv.visitInsn(RETURN);
          }

          /**
           * Assemble the bytecode instructions for a DMO method which retrieves and returns the
           * value of the specified simple (i.e., non-indexed) field. This implementation is used for
           * fields whose values are mutable, which are all of the converted table fields. The
           * implementation invokes a copy constructor to make a copy of the field's data and returns
           * the copy, rather than the original object reference. This prevents outside code from
           * making any changes to the field's state, other than through the DMO's API.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   fieldName
           *          Field name.
           * @param   fieldType
           *          Fully qualified, internal type name of field.
           * @param   fieldDesc
           *          Field descriptor.
           */
          public void simpleGetter(MethodVisitor mv,
                                   String classType,
                                   String fieldName,
                                   String fieldType,
                                   String fieldDesc)
          {
             // instantiate a new object of the field's type
             mv.visitTypeInsn(NEW, fieldType);

             // duplicate the new object's reference on the stack; the top reference will be consumed
             // by the copy c'tor call below, the bottom will be returned from the getter method
             mv.visitInsn(DUP);

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // push the object reference within the specified field onto the stack
             mv.visitFieldInsn(GETFIELD, classType, fieldName, fieldDesc);

             // special handling for copy c'tors which accept a superclass type
             String adjFieldDesc = copyCtorTypes.get(fieldType);
             if (adjFieldDesc == null)
             {
                adjFieldDesc = fieldDesc;
             }

             // invoke copy c'tor which accepts a single parameter of the same type as the field
             String copyCtorSig = "(" + adjFieldDesc + ")V";
             mv.visitMethodInsn(INVOKESPECIAL, fieldType, METH_INIT, copyCtorSig, false);

             // return object reference
             mv.visitInsn(ARETURN);
          }

          /**
           * Assemble the bytecode instructions for a DMO method which sets the value of the specified
           * simple (i.e., non-indexed) field. This implementation is used for fields whose values are
           * mutable, which are all of the converted table fields. The implementation invokes the
           * implementation of the <code>assign</code> method which is appropriate for the field's
           * <code>BaseDataType</code> subclass. This prevents outside code from making any changes to
           * the field's state, other than through the DMO's API.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   fieldName
           *          Field name.
           * @param   fieldType
           *          Fully qualified, internal type name of field.
           * @param   fieldDesc
           *          Field descriptor.
           */
          public void simpleSetter(MethodVisitor mv,
                                   String classType,
                                   String fieldName,
                                   String fieldType,
                                   String fieldDesc)
          {
             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // push the object reference within the specified field onto the stack
             mv.visitFieldInsn(GETFIELD, classType, fieldName, fieldDesc);

             // load method parameter object reference into local variable 1
             mv.visitVarInsn(ALOAD, 1);

             // invoke BDT.assign method
             String assignSig = assignMethSigs.get(fieldType);
             mv.visitMethodInsn(INVOKEVIRTUAL, fieldType, METH_ASSIGN, assignSig, false);

             // void return
             mv.visitInsn(RETURN);
          }

          /**
           * Assemble the bytecode instructions for a composite getter method, which accesses a
           * composite object from an array list field, invokes a simple getter method on it, and
           * returns the result.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   compFieldName
           *          Short name of field in enclosing class which contains the array list of
           *          composites we need to access.
           * @param   compClassName
           *          Short name of composite inner class.
           * @param   compMethName
           *          Name of composite's simple getter method.
           * @param   returnDesc
           *          Type descriptor for the return type of the field's getter method.
           */
          public void compositeGetter(MethodVisitor mv,
                                      String classType,
                                      String compFieldName,
                                      String compClassName,
                                      String compMethName,
                                      String returnDesc)
          {
             // load composite element from its ArrayList and cast it to the appropriate type
             String compClassType = loadComposite(mv, classType, compFieldName, compClassName);

             // composite method takes no arguments and returns the same type as the method currently
             // being implemented
             String compMethSig = "()" + returnDesc;

             // invoke composite method to retrieve target object
             mv.visitMethodInsn(INVOKEVIRTUAL, compClassType, compMethName, compMethSig, false);

             // return object reference
             mv.visitInsn(ARETURN);
          }

          /**
           * Assemble the bytecode instructions for a composite setter method, which accesses a
           * composite object from an array list field, invokes a simple setter method on it.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   compFieldName
           *          Short name of field in enclosing class which contains the array list of
           *          composites we need to access.
           * @param   compClassName
           *          Short name of composite inner class.
           * @param   compMethName
           *          Name of composite's simple getter method.
           * @param   elementDesc
           *          Type descriptor for the <code>element</code> parameter passed to the field's
           *          setter method.
           */
          public void compositeSetter(MethodVisitor mv,
                                      String classType,
                                      String compFieldName,
                                      String compClassName,
                                      String compMethName,
                                      String elementDesc)
          {
             // load composite element from its ArrayList and cast it to the appropriate type
             String compClassType = loadComposite(mv, classType, compFieldName, compClassName);

             // composite method takes one argument (the element to be set) and returns void
             String compMethSig = "(" + elementDesc + ")V";

             // load second method parameter (the element to be set -- an object) int local variable 2
             mv.visitVarInsn(ALOAD, 2);

             // invoke composite method to set element
             mv.visitMethodInsn(INVOKEVIRTUAL, compClassType, compMethName, compMethSig, false);

             // void return
             mv.visitInsn(RETURN);
          }

          /**
           * Assemble the bytecode instructions for a getter method which returns all of the values
           * of a composite field as an array. The implementation creates an array of the specified
           * field type, populates it in a loop of calls to that field's indexed getter method, and
           * returns the array.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   fieldType
           *          Fully qualified, internal type name of field.
           * @param   extent
           *          List size.
           * @param   idxGetterMethName
           *          Indexed getter method name.
           */
          public void simpleGetterExtent(MethodVisitor mv,
                                         String classType,
                                         String fieldType,
                                         int extent,
                                         String idxGetterMethName)
          {
             // compose the signature for the indexed getter method
             String idxGetterSig = "(I)L" + fieldType + ";";

             // push the number of size of the return array onto the stack
             AsmUtils.pushInt(mv, extent);

             // instantiate a new array to hold the field values
             mv.visitTypeInsn(ANEWARRAY, fieldType);

             // store the array reference in local variable 1
             mv.visitVarInsn(ASTORE, 1);

             // push the initial value of the loop counter onto the stack
             mv.visitInsn(ICONST_0);

             // store the value of the loop counter in local variable 2
             mv.visitVarInsn(ISTORE, 2);

             // create a label for the top of the loop
             Label top = new Label();
             mv.visitLabel(top);

             // load the current value of the loop counter
             mv.visitVarInsn(ILOAD, 2);

             // push the loop limit onto the stack
             AsmUtils.pushInt(mv, extent);

             // create a label for the bottom of the loop
             Label bottom = new Label();

             // compare the loop counter with the limit; if the former is >= the latter, jump to the
             // bottom of the loop
             mv.visitJumpInsn(IF_ICMPGE, bottom);

             // load the array reference from local variable 1
             mv.visitVarInsn(ALOAD, 1);

             // load the loop counter (now used as the array index) from local variable 2
             mv.visitVarInsn(ILOAD, 2);

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // load the loop counter (now used as the indexed getter method index argument) from
             // local variable 2
             mv.visitVarInsn(ILOAD, 2);

             // invoke the indexed getter method
             mv.visitMethodInsn(INVOKEVIRTUAL, classType, idxGetterMethName, idxGetterSig, false);

             // store the returned value in the array at the current index
             mv.visitInsn(AASTORE);

             // increment the loop counter in local variable 2
             mv.visitIincInsn(2, 1);

             // jump to the top of the loop
             mv.visitJumpInsn(GOTO, top);

             // mark this location as the destination for IF_ICMPGE jump instruction above
             mv.visitLabel(bottom);

             // load the array reference from local variable 1
             mv.visitVarInsn(ALOAD, 1);

             // return array object reference
             mv.visitInsn(ARETURN);
          }

          /**
           * Assemble the bytecode instructions for an indexed getter method which accepts a
           * <code>NumberType</code> parameter for the index value. The implementation extracts the
           * primitive <code>int</code> value from the <code>NumberType</code> parameter and delegates
           * the main work to the associated getter method which accepts an <code>int</code> for the
           * index.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   delMethName
           *          Name of getter method to which to delegate.
           * @param   returnDesc
           *          Return type descriptor.
           */
          public void delegatingIndexedGetter(MethodVisitor mv,
                                              String classType,
                                              String delMethName,
                                              String returnDesc)
          {
             // composite getter method takes a primitive int argument and returns the same type as
             // the method currently being implemented
             String delMethSig = "(I)" + returnDesc;

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // load index parameter (index as NumberType) and extract int value from it
             loadIndexParameter(mv);

             // invoke delegate method (composite getter) to retrieve value
             mv.visitMethodInsn(INVOKEVIRTUAL, classType, delMethName, delMethSig, false);

             // return object reference
             mv.visitInsn(ARETURN);
          }

          /**
           * Assemble the bytecode instructions for an indexed setter method which accepts a
           * <code>NumberType</code> parameter for the index value. The implementation extracts the
           * primitive <code>int</code> value from the <code>NumberType</code> parameter and delegates
           * the main work to the associated getter method which accepts an <code>int</code> for the
           * index.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   delMethName
           *          Name of setter method to which to delegate.
           * @param   elementDesc
           *          Element type descriptor.
           */
          public void delegatingIndexedSetter(MethodVisitor mv,
                                              String classType,
                                              String delMethName,
                                              String elementDesc)
          {
             // composite setter method takes two arguments (the int index and the element to be set)
             // and returns void
             String delMethSig = "(I" + elementDesc + ")V";

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // load index parameter (index as NumberType) and extract int value from it
             loadIndexParameter(mv);

             // load method parameter object reference (element to be set) into local variable 2
             mv.visitVarInsn(ALOAD, 2);

             // invoke delegate method (composite setter) to set element value
             mv.visitMethodInsn(INVOKEVIRTUAL, classType, delMethName, delMethSig, false);

             // void return
             mv.visitInsn(RETURN);
          }

          /**
           * Assemble the bytecode instructions for a composite field setter method which accepts a
           * scalar value and assigns all elements of the composite field to that value. The
           * implementation invokes the appropriate indexed setter method in a loop which executes
           * a number of times equal to the size of the composite list.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   fieldType
           *          Fully qualified, internal type name of field.
           * @param   compName
           *          Field name which holds the list of composite elements.
           * @param   idxSetterMethName
           *          Indexed setter method name.
           */
          public void indexedSetterFromScalar(MethodVisitor mv,
                                              String classType,
                                              String fieldType,
                                              String compName,
                                              String idxSetterMethName)
          {
             String idxSetterSig = makeIndexedSetterSignature(fieldType);

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // load the composite array list from its field
             mv.visitFieldInsn(GETFIELD, classType, compName, DESC_ARRAYLIST);

             // get the list size
             mv.visitMethodInsn(INVOKEINTERFACE, TYPE_LIST, METH_SIZE, SIG_SIZE, true);

             // store the list size into local variable 2
             mv.visitVarInsn(ISTORE, 2);

             // push the initial value of the loop counter onto the stack
             mv.visitInsn(ICONST_0);

             // store the value of the loop counter in local variable 3
             mv.visitVarInsn(ISTORE, 3);

             // create a lable for the top of the loop
             Label top = new Label();
             mv.visitLabel(top);

             // load the current value of the loop counter
             mv.visitVarInsn(ILOAD, 3);

             // load the list size for use as the loop counter limit
             mv.visitVarInsn(ILOAD, 2);

             // create a label for the bottom of the loop
             Label bottom = new Label();

             // compare the loop counter with the limit; if the former is >= the latter, jump to the
             // bottom of the loop
             mv.visitJumpInsn(IF_ICMPGE, bottom);

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // load the current value of the loop counter (for use as the indexed setter method's
             // index argument)
             mv.visitVarInsn(ILOAD, 3);

             // load the scalar value passed into the method from local variable 1
             mv.visitVarInsn(ALOAD, 1);

             // invoke the indexed setter method
             mv.visitMethodInsn(INVOKEVIRTUAL, classType, idxSetterMethName, idxSetterSig, false);

             // increment the loop counter in local variable 3 by 1
             mv.visitIincInsn(3, 1);

             // jump to the top of the loop
             mv.visitJumpInsn(GOTO, top);

             // mark this location as the bottom of the loop
             mv.visitLabel(bottom);

             // void return
             mv.visitInsn(RETURN);
          }

          /**
           * Assemble the bytecode instructions for a composite field setter method which accepts an
           * array of values and assigns the elements of the composite field to the values in the
           * array on a best-efforts basis. The implementation invokes the appropriate indexed setter
           * method in a loop which executes a number of times equal to the smaller of the size of the
           * array and the size of the composite list.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   fieldType
           *          Fully qualified, internal type name of field.
           * @param   compName
           *          Field name which holds the list of composite elements.
           * @param   idxSetterMethName
           *          Indexed setter method name.
           */
          public void indexedSetterFromArray(MethodVisitor mv,
                                             String classType,
                                             String fieldType,
                                             String compName,
                                             String idxSetterMethName)
          {
             String idxSetterSig = makeIndexedSetterSignature(fieldType);

             // load the array passed in as a parameter from local variable 1
             mv.visitVarInsn(ALOAD, 1);

             // push the array's length onto the stack
             mv.visitInsn(ARRAYLENGTH);

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // load the composite array list from its field
             mv.visitFieldInsn(GETFIELD, classType, compName, DESC_LIST);

             // invoke List.size to push the list's size onto the stack
             mv.visitMethodInsn(INVOKEINTERFACE, TYPE_LIST, METH_SIZE, SIG_SIZE, true);

             // invoke Math.min against the two values, pushing the smaller onto the stack
             mv.visitMethodInsn(INVOKESTATIC, TYPE_MATH, METH_MIN, SIG_MIN, false);

             // store the result of Math.min in local variable 2, which will serve as the loop counter
             // limit
             mv.visitVarInsn(ISTORE, 2);

             // push the initial loop counter value onto the stack
             mv.visitInsn(ICONST_0);

             // store the loop counter in local variable 3
             mv.visitVarInsn(ISTORE, 3);

             // create a label for the top of the loop
             Label top = new Label();
             mv.visitLabel(top);

             // load the loop counter from local variable 3
             mv.visitVarInsn(ILOAD, 3);

             // load the loop limit from local variable 2
             mv.visitVarInsn(ILOAD, 2);

             // create a label for the bottom of the loop
             Label bottom = new Label();

             // compare the loop counter with the limit; if the former is >= the latter, jump to the
             // bottom of the loop
             mv.visitJumpInsn(IF_ICMPGE, bottom);

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // load the loop counter from local variable 3 (to serve as the index argument for the
             // indexed setter method)
             mv.visitVarInsn(ILOAD, 3);

             // load the array of values from local variable 1
             mv.visitVarInsn(ALOAD, 1);

             // load the loop counter from local variable 3 (to serve as the current array index)
             mv.visitVarInsn(ILOAD, 3);

             // load the value at the current index from the array
             mv.visitInsn(AALOAD);

             // invoke the indexed setter method
             mv.visitMethodInsn(INVOKEVIRTUAL, classType, idxSetterMethName, idxSetterSig, false);

             // increment the loop counter in local variable 3 by 1
             mv.visitIincInsn(3, 1);

             // jump to the top of the loop
             mv.visitJumpInsn(GOTO, top);

             // mark this location as the bottom of the loop
             mv.visitLabel(bottom);

             // void return
             mv.visitInsn(RETURN);
          }

          /**
           * Assemble the bytecode instructions for a method which returns the number of elements of
           * a composite field as a P2J <code>integer</code> object.
           *
           * @param   mv
           *          The method visitor.
           * @param   classType
           *          Fully qualified internal type name of enclosing class.
           * @param   compName
           *          Field name which holds the list of composite elements.
           */
          public void sizer(MethodVisitor mv, String classType, String compName)
          {
             mv.visitTypeInsn(NEW, TYPE_INTEGER);
             mv.visitInsn(DUP);

             // load "this" from local variable 0
             mv.visitVarInsn(ALOAD, 0);

             // push the object reference of composite array list field onto the stack
             mv.visitFieldInsn(GETFIELD, classType, compName, DESC_LIST);

             // invoke size method on composite array list, pushing result onto stack
             mv.visitMethodInsn(INVOKEINTERFACE, TYPE_LIST, METH_SIZE, SIG_SIZE, true);

             // invoke integer constructor using size of array list as its argument
             mv.visitMethodInsn(INVOKESPECIAL, TYPE_INTEGER, METH_INIT, SIG_INT_INIT, false);

             // return object reference
             mv.visitInsn(ARETURN);
          }
       }
    }