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);
      }
   }
}