DmoClass.java

/*
** Module   : DmoClass.java
** Abstract : Assembles and loads DMO implementation classes on the fly
**
** Copyright (c) 2019-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- -------------------------------------Description---------------------------------------
** 001 ECF 20191210 Created initial version. Assembles DMO implementation classes on the fly and
**                  loads them into JVM using AsmClassLoader.
** 002 OM  20200120 Added full support for extent fields.
**     CA  20200622 Small performance improvement - use IdentityHashMap if the key is java.lang.Class.
** 003 ECF 20200916 Use DmoMeta to retrieve schema name instead of DatabaseManager.getSchemaByInterface.
**         20200919 Use DmoMeta to map permanent DMO.
**         20200920 Moved mapping of permanent DMO to DmoMetadataManager.registerDmo, after DmoMeta is
**                  fully initialized.
**     OM  20220713 The implementation classes override the toPOJO() and populateFromPOJO() methods used
**                  for conversion to and from POJO objects.
** 004 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 005 TJD 20240123 Java 17 compatibility updates
** 006 AD  20240313 Added method that assembles implementation and returns the new DmoClass object.
**     AD  20240403 Added callback method when assembling dmo implementation.
*/

/*
** 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.persist.orm;

import java.lang.reflect.*;
import java.util.*;
import java.util.function.Consumer;

//import java.util.function.*; // enable for debug
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.persist.annotation.*;
import org.objectweb.asm.*;
import org.objectweb.asm.Type;
import com.goldencode.asm.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.Record;
import com.goldencode.p2j.schema.*;
import com.goldencode.p2j.util.*;
import com.goldencode.util.*;

/**
 * A class which assembles data model object (DMO) implementation classes on the fly and loads
 * them into the JVM using the {@link AsmClassLoader}. A DMO implementation class extends either
 * {@link Record} (for converted, persistent tables) or {@link TempRecord} (for converted
 * temp-tables). It implements a DMO interface which specifies getter and setter methods, and
 * which contains annotations describing the legacy table which the DMO represents.
 * <p>
 * Once assembled and loaded, a DMO implementation class is cached (mapped to its DMO interface),
 * so it can be fetched again later. A class is loaded/fetched using the static {@link
 * #assembleImplementation(DmoMeta)} method, which accepts as its only parameter the DMO interface which
 * the implementation class implements.
 */
public final class DmoClass
implements DmoAsmTypes,
           Opcodes
{
   /** Name of the {@code RecordMeta} static field of the DMO implementation class */
   public static final String FLD_METADATA = "_metadata";
   
   /** Name of the {@code _multiplex} instance variable used for temp-table DMOs */
   private static final String FLD_MULTIPLEX = "_multiplex";
   
   /** Internal type name of the {@code orm} package */
   private static final String PKG_ORM = PKG_PERSIST + "orm/";
   
   /** Internal name of {@link Record} class */
   private static final String RECORD = PKG_PERSIST + "Record";
   
   /** Internal name of {@link TempRecord} class */
   private static final String TEMPRECORD = PKG_PERSIST + "TempRecord";
   
   /** Internal name of {@link Persistable} interface */
   private static final String PERSISTABLE = PKG_PERSIST + "Persistable";
   
   /** Internal name of {@link TempTableRecord} interface */
   private static final String TEMPTABLERECORD = PKG_PERSIST + "TempTableRecord";
   
   /** Internal name of {@link RecordMeta} class */
   private static final String RECORDMETA = PKG_ORM + "RecordMeta";
   
   /** Internal type name of {@link RecordMeta} class */
   private static final String DESC_RECORDMETA = "L" + RECORDMETA + ";";
   
   /** Suffix added to DMO interface name to create DMO implementation class name */
   private static final String IMPL_SUFFIX = DBUtils.IMPL_SUFFIX;
   
   /** Internal name of the {@link Record#_recordMeta()} method */
   private static final String METH_RECORDMETA = "_recordMeta";
   
   /** Method signature taking no arguments and returning void */
   private static final String SIG_NOARG_VOID = "()V";
   
   /** Method signature taking taking one {@code Integer} argument and returning void */
   private static final String SIG_INTEGER_VOID = "(Ljava/lang/Integer;)V";
   
   /** Method signature taking one class argument and returning void */
   private static final String SIG_CLASS_VOID = "(Ljava/lang/Class;)V";
   
   private static final String SIG_NOARG_RECORDMETA = "()" + DESC_RECORDMETA;
   
   /** Information used when assembling getter and setter methods */
   private static final Map<Class<? extends BaseDataType>, BaseDataAccess> baseDataAccess =
      new IdentityHashMap<>();
   
   /** Mapping of BDT classes to their String representation of POJO types. */
   private static final Map<Class<? extends BaseDataType>, Dmo2Pojo> converters = new IdentityHashMap<>();
   
   static
   {
      // TODO: complete this list for all supported types
      // all supported BDT go here:
      // BDT (narrow) type, package name, BDT (wide) type name (used for setter methods)
      Object[] bdtInfo = new Object[]
      {
         blob.class      , PKG_UTIL, "blob",
         character.class , PKG_UTIL, "Text",
         clob.class      , PKG_UTIL, "clob",
         comhandle.class , PKG_UTIL, "comhandle",
         datetimetz.class, PKG_UTIL, "date",
         datetime.class  , PKG_UTIL, "date",
         date.class      , PKG_UTIL, "date",
         decimal.class   , PKG_UTIL, "NumberType",
         handle.class    , PKG_UTIL, "handle",
         int64.class     , PKG_UTIL, "NumberType",
         integer.class   , PKG_UTIL, "NumberType",
         logical.class   , PKG_UTIL, "logical",
         object.class    , PKG_UTIL, "object",
         raw.class       , PKG_UTIL, "BinaryData",
         recid.class     , PKG_UTIL, "NumberType",
         rowid.class     , PKG_UTIL, "rowid",
      };
      
      int len = bdtInfo.length;
      for (int i = 0; i < len; i += 3)
      {
         Class<? extends BaseDataType> type = (Class<? extends BaseDataType>) bdtInfo[i];
         String pkg = (String) bdtInfo[i + 1];
         String simpleNarrow = type.getSimpleName();
         String wide = (String) bdtInfo[i + 2];
         
         BaseDataAccess bda = new BaseDataAccess();
         bda.logical = (type == logical.class);
         
         String upperNarrow = StringHelper.changeCase(simpleNarrow, true);
         bda.getter = "_" + (bda.logical ? "is" : "get") + upperNarrow;
         bda.setter = "_" + "set" + upperNarrow;
         bda.narrowClass = pkg + simpleNarrow;
         bda.narrowType = "L" + pkg + simpleNarrow + ";";
         bda.wideType = "L" + pkg + wide + ";";
         bda.getterSignature = "(I)" + bda.narrowType;
         bda.setterSignature = "(I" + bda.wideType + ")V";
         
         baseDataAccess.put(type, bda);
      }
      
      converters.put(blob.class,       new Dmo2Pojo("[B",               null, null, null));
      converters.put(character.class,  new Dmo2Pojo("java/lang/String", null, null, null));
      converters.put(clob.class,       new Dmo2Pojo("java/lang/String", null, null, null));
      converters.put(comhandle.class,  new Dmo2Pojo("java/lang/String", null, null, null));
      converters.put(datetimetz.class,
                     new Dmo2Pojo("java/time/OffsetDateTime", "java/util/GregorianCalendar",
                                  "toDmoDatetimetz",         "toPojo"));
      converters.put(datetime.class,
                     new Dmo2Pojo("java/sql/Timestamp",      "java/util/GregorianCalendar",
                                  "toDmoDatetime",           "toPojo"));
      converters.put(date.class,
                     new Dmo2Pojo("java/sql/Date",           "java/util/GregorianCalendar",
                                  "toDmoDate",               "toPojo"));
      converters.put(decimal.class,    new Dmo2Pojo("java/math/BigDecimal",   null, null, null));
      converters.put(handle.class,     new Dmo2Pojo("java/lang/Long",         null, null, null));
      converters.put(int64.class,      new Dmo2Pojo("java/lang/Long",         null, null, null));
      converters.put(integer.class,    new Dmo2Pojo("java/lang/Integer",      null, null, null));
      converters.put(logical.class,    new Dmo2Pojo("java/lang/Boolean",      null, null, null));
      converters.put(object.class,     new Dmo2Pojo("java/lang/Long",         null, null, null));
      converters.put(raw.class,        new Dmo2Pojo("[B",                     null, null, null));
      converters.put(recid.class,      new Dmo2Pojo("java/lang/Integer",      null, null, null));
      converters.put(rowid.class,      new Dmo2Pojo("java/lang/Long",         null, null, null));
   }
   
   private static class Dmo2Pojo
   {
      private final String dmoType;
      private final String pojoType;
      private final String pojo2dmo;
      private final String dmo2pojo;
      
      public Dmo2Pojo(String dmoType, String pojoType, String pojo2dmo, String dmo2pojo)
      {
         this.dmoType = dmoType;
         this.pojoType = pojoType == null ? dmoType : pojoType;
         this.pojo2dmo = pojo2dmo;
         this.dmo2pojo = dmo2pojo;
      }
   }
   
   /** ASM class writer */
   private final ClassWriter cw = new ClassWriter(0);
   
   /** DMO implementation class name, as a JVM binary type name (dot separators) */
   private final String binaryName;
   
   /** DMO implementation class name, as a JVM internal type name (slash separators) */
   private final String internalName;
   
   /**
    * Constructor which assembles the DMO implementation class bytecode, but which does not load
    * it into the JVM.
    * 
    * @param   dmoIface
    *          DMO interface from which to extract information for the implementation class.
    * @param   propMeta
    *          The set of meta property of the generated class. 
    */
   private DmoClass(Class<? extends DataModelObject> dmoIface, PropertyMeta[] propMeta)
   {
      boolean isTemp = Temporary.class.isAssignableFrom(dmoIface);
      String ifaceName = AsmUtils.commonToInternalTypeName(dmoIface);
      String ifaceDesc = "L" + ifaceName + ";";
      String superClass = isTemp ? TEMPRECORD : RECORD;
      String implemented = isTemp ? TEMPTABLERECORD : PERSISTABLE;
      
      this.binaryName = dmoIface.getName() + IMPL_SUFFIX;
      this.internalName = ifaceName + IMPL_SUFFIX;
      
      // create the class writer and create the class, specifying the DMO implementation class
      // name, superclass name, and name of the DMO interface to implement
      cw.visit(V1_8,
               ACC_PUBLIC | ACC_SUPER | ACC_FINAL,
               internalName,
               null,
               superClass,
               new String[] { implemented, ifaceName });
      
      // create the RecordMeta class variable
      FieldVisitor fv = cw.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC,
                                      FLD_METADATA,
                                      DESC_RECORDMETA,
                                      null,
                                      null);
      fv.visitEnd();
      
      // create static initializer (instantiates and stores the RecordMeta object
      createStaticInitializer(ifaceDesc);
      
      createRecordMetaMethod();
      
      // create the default constructor
      createConstructor(superClass);
      
      // for TEMP-TABLES, create the _multiplex field and associated  constructor:
      if (isTemp)
      {
         addMultiplexSupport();
      }
      
      // implement Undoable interface:
      // createDeepCopyMethod();
      // createAssignMethod();
      
      // create the getter and setter methods
      Set<String> origDenormNames = new HashSet<>();
      int len = propMeta.length;
      for (int i = 0; i < len; i++)
      {
         PropertyMeta next = propMeta[i];
         
         // when we hit the first in a series of denormalized extent fields, ensure we generate special
         // legacy extent field methods below
         String origName = next.getOriginalName();
         boolean denorm = origName.length() > 0 && origDenormNames.add(origName);
         
         BaseDataAccess bda = baseDataAccess.get(next.getType());
         boolean extent = next.getExtent() > 0;
         createGetter(next, bda, i, extent, false); // BDT  getFieldK()    or BDT  getFieldK(int)
         createSetter(next, bda, i, extent, false); // void setFieldK(BDT) or void setFieldK(int, BDT)
         
         if (extent || denorm)
         {
            // create all signature necessary for extent field handling:
            createGetter(next, bda, i, true, true); // BDT   getFieldK(NumberType)
            createSetter(next, bda, i, true, true); // void  setFieldK(NumberType, BDT)
            createBulkGetter(next, bda, i);         // BDT[] getFieldK()
            createBulkSetter(next, bda, i);         // void  setFieldK(BDT[])
            createMassSetter(next, bda, i);         // void  setFieldK(BDT)
            
            if (denorm)
            {
               // create the primitive int-indexed, legacy extent field getter and setter method for a
               // denormalized extent field
               createGetter(next, bda, i, true, false); // BDT   getFieldK(int)
               createSetter(next, bda, i, true, false); // void  setFieldK(int, BDT)
            }
            else
            {
               // skip objects after the first in a series, if the metadata represents an extent field,
               // except if we've gotten here from a scalar, denormalized extent field property
               i += next.getExtent() - 1;
            }
         }
      }
      
      Table annotation = dmoIface.getAnnotation(Table.class);
      if (annotation != null)
      {
         Class<?> pojoClass = annotation.pojoClass();
         if (pojoClass != Object.class)
         {
            create_toPOJO_method(pojoClass, propMeta);
            create_populateFromPOJO_method(pojoClass, propMeta);
         }
      }
   }
   
   /**
    * This method is used at conversion time to pre-generate an implementation class for a DMO interface.
    * Loading the class is not necessary, but retrieving the DmoClass object is a simple way
    * of getting the bytecode needed to save it to disk.
    * 
    * @param   dmoMeta
    *          The meta information of the DMO interface.
    * 
    * @return  DMO implementation class.
    */
   public static DmoClass assembleAndRetrieveImplementation(DmoMeta dmoMeta)
   {
      RecordMeta recordMeta = new RecordMeta(dmoMeta.dmoImplInterface, dmoMeta);
      PropertyMeta[] propMeta = recordMeta.getPropertyMeta(false);
      return new DmoClass(dmoMeta.dmoImplInterface, propMeta);
   }
   
   /**
    * This method is the main external entry point into this class, used to assemble and load a
    * DMO implementation class for the given DMO interface.
    * 
    * @param   dmoMeta
    *          The meta information of the DMO interface.    
    * @param   assembleCallback
    *          Callback method, in case extra information about the new class is needed.
    * 
    * @return  DMO implementation class.
    * 
    * @throws  ClassNotFoundException
    *          if the class is not found. This should not be possible, since we are providing the
    *          byte code to the class loader and the search should not fail.
    * @throws  ConfigurationException
    *          If the {@link AsmClassLoader} was not added in Java command line parameters so the dynamically
    *          assembled class implementation cannot be loaded.
    */
   public static Class<? extends Record> assembleImplementation(DmoMeta dmoMeta, Consumer<DmoClass> assembleCallback)
   throws ClassNotFoundException,
          NoSuchFieldException,
          IllegalAccessException,
          ConfigurationException
   {
      if (dmoMeta.implClass != null)
      {
         return dmoMeta.implClass;
      }
      
      Class<? extends DataModelObject> dmoIface = dmoMeta.dmoImplInterface;
      RecordMeta recordMeta = new RecordMeta(dmoIface, dmoMeta);
      String implClassName = null;
      try
      {
         implClassName = dmoIface.getName() + DBUtils.IMPL_SUFFIX;
         ClassLoader cl = ClassLoader.getSystemClassLoader();
         dmoMeta.implClass = (Class<? extends Record>) cl.loadClass(implClassName);
         if (dmoMeta.implClass != null)
         {
            return dmoMeta.implClass;
         }
      }
      catch (ClassNotFoundException e)
      {
         // implementation does exist on disk, or we failed to load it
      }
      
      /* DEBUG
      Function<Class<?>, Set<String>> methPrinter = (c) ->
      {
         Set<String> set = new LinkedHashSet<>();
         
         LOG.finer("CLASS:  " + c.getCanonicalName());
         LOG.finer("");
         
         boolean isIface = c.isInterface();
         
         for (Method m : c.getMethods())
         {
            if (!isIface && Modifier.isAbstract(m.getModifiers()))
            {
               continue;
            }
            
            String rt = m.getReturnType().getSimpleName();
            String name = m.getName();
            Class<?>[] params = m.getParameterTypes();
            
            StringBuilder buf = new StringBuilder();
            buf.append(rt);
            buf.append(" ");
            buf.append(name);
            buf.append("(");
            for (int i = 0; i < params.length; i++)
            {
               Class<?> p = params[i];
               if (i > 0)
               {
                  buf.append(", ");
               }
               buf.append(p.getSimpleName());
            }
            buf.append(")");
            
            String s = buf.toString();
            
            set.add(s);

            LOG.finer("   " + s);
         }

         LOG.finer("");
         
         return set;
      };
      
      Set<String> ifaceMethods = methPrinter.apply(dmoIface);
      */
      
      PropertyMeta[] propMeta = recordMeta.getPropertyMeta(false);
      DmoClass assembler = new DmoClass(dmoIface, propMeta);
      if (assembleCallback != null)
      {
         assembleCallback.accept(assembler);         
      }
      Class<? extends Record> loadedClass = assembler.load();
      
      /* DEBUG
      Set<String> implMethods = methPrinter.apply(loadedClass);
      ifaceMethods.removeAll(implMethods);
      
      if (!ifaceMethods.isEmpty())
      {
         LOG.finer("UNIMPLEMENTED (" + dmoIface.getSimpleName() + "):");
         for (String s : ifaceMethods)
         {
            LOG.finer("   " + s);
         }
         LOG.finer();
      }
      */
      
      Field metaField = loadedClass.getDeclaredField("_metadata");
      metaField.setAccessible(true);
      if (metaField.get(null) != recordMeta)
      {
         // actually, because of the late initialization, the [_metadata] static field from the
         // new class is not set in the load() method. Instead it is set only at first access
         // which we force when the get(null) method above is called.
         // We force it now intentionally to make the new class call the RecordMeta.get(),
         // otherwise the [_metadata] will be incorrectly initialized.
         throw new RuntimeException("Failed to set _metadata on new Record");
      }
      if (RecordMeta.get(dmoIface) != null)
      {
         // if the RecordMeta.get() was not called or was called with invalid arguments from
         // the static block of the newly created class, the RecordMeta.local was not reset, so
         // we would get it now
         throw new RuntimeException("_metadata not configured on new Record");
      }
      
      return loadedClass;
   }
   
   /**
    * Getter for the classes internal name.
    * 
    * @return  DMO implementation class internal name (with slash separators).
    */
   public String getInternalName()
   {
      return internalName;
   }
   
   /**
    * Getter for the new classes bytecode.
    * 
    * @return  DMO implementation class bytecode.
    */
   public byte[] getBytecode()
   {
      return cw.toByteArray();
   }
   
   /**
    * Create static initializer, which instantiates RecordMeta object and stores it in
    * {@code _metadata} class variable.
    * 
    * @param   ifaceDesc
    *          Type descriptor of DMO interface.
    */
   private void createStaticInitializer2(String ifaceDesc)
   {
      // create <clinit> method (static initializer)
      MethodVisitor mv = cw.visitMethod(ACC_STATIC, METH_CLINIT, SIG_NOARG_VOID, null, null);
      mv.visitCode();
      
      // invoke RecordMeta c'tor, which takes a Class<?> argument (the DMO interface)
      mv.visitTypeInsn(NEW, RECORDMETA);
      mv.visitInsn(DUP);
      mv.visitLdcInsn(Type.getType(ifaceDesc));
      mv.visitMethodInsn(INVOKESPECIAL, RECORDMETA, METH_INIT, SIG_CLASS_VOID, false);
      
      // store the result of the RecordMeta c'tor in the _metadata class variable
      mv.visitFieldInsn(PUTSTATIC, internalName, FLD_METADATA, DESC_RECORDMETA);
      
      // implicit void return
      mv.visitInsn(RETURN);
      
      // set max stack depths and finish definition
      mv.visitMaxs(3, 0);
      mv.visitEnd();
   }
   
   /**
    * Create static initializer, which instantiates RecordMeta object and stores it in
    * {@code _metadata} class variable.
    * 
    * @param   ifaceDesc
    *          Type descriptor of DMO interface.
    */
   private void createStaticInitializer(String ifaceDesc)
   {
      // create <clinit> method (static initializer)
      MethodVisitor mv = cw.visitMethod(ACC_STATIC, METH_CLINIT, SIG_NOARG_VOID, null, null);
      mv.visitCode();
      
      // push the interface class name on stack as argument for next call
      mv.visitLdcInsn(Type.getType(ifaceDesc));
      
      // invoke RecordMeta static "get" method, which takes a Class<?> argument (the DMO interface
      // which is already pushed onto stack)
      mv.visitMethodInsn(INVOKESTATIC,
                         RECORDMETA,
                         "get",
                         "(Ljava/lang/Class;)L" + RECORDMETA + ";",
                         false);
      
      // store the result of the RecordMeta c'tor in the _metadata class variable
      mv.visitFieldInsn(PUTSTATIC, internalName, FLD_METADATA, DESC_RECORDMETA);
      
      // implicit void return
      mv.visitInsn(RETURN);
      
      // set max stack depths and finish definition
      mv.visitMaxs(1, 0);
      mv.visitEnd();
   }
   
   /**
    * Implement the {@code _recordMeta} method. (declared abstract in {@link Record}), which
    * returns the {@link RecordMeta} instance associated with this DMO implementation class.
    */
   private void createRecordMetaMethod()
   {
      // create the _recordMeta method
      MethodVisitor mv = cw.visitMethod(ACC_PROTECTED | ACC_FINAL,
                                        METH_RECORDMETA,
                                        SIG_NOARG_RECORDMETA,
                                        null,
                                        null);
      mv.visitCode();
      
      // get the value stored in the static _metadata field
      mv.visitFieldInsn(GETSTATIC, internalName, FLD_METADATA, DESC_RECORDMETA);
      
      // return it
      mv.visitInsn(ARETURN);
      
      // set max stack depths and finish definition
      mv.visitMaxs(1, 1);
      mv.visitEnd();
   }
   
   /**
    * Add a constructor to the temp-table flavor of the DMO implementation class, which accepts
    * an {@code Integer} for the multiplex ID.
    */
   private void addMultiplexSupport()
   {
      // the _multiplex (Integer) constructor
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, METH_INIT, SIG_INTEGER_VOID, null, null);
      mv.visitCode();
      
      // invoke my default c'tor
      mv.visitInsn(ALOAD_0);
      mv.visitMethodInsn(INVOKESPECIAL, internalName, METH_INIT, SIG_NOARG_VOID, false);
      
      // copy the parameter into _multiplex instance variable
      mv.visitInsn(ALOAD_0); // load the DMO's "this" reference
      mv.visitInsn(ALOAD_1);
      mv.visitFieldInsn(PUTFIELD, TEMPRECORD, FLD_MULTIPLEX, DESC_JAVA_INTEGER);
      
      // implicit void return
      mv.visitInsn(RETURN);
      
      // set max stack depths and finish definition
      mv.visitMaxs(2, 2);
      mv.visitEnd();
   }
   
   /**
    * Creates the default constructor, which invokes the superclass' default constructor.
    * 
    * @param   superClass
    *          The name of the super class whose default c'tor will be called.
    */
   private void createConstructor(String superClass)
   {
      // create <init> method (default c'tor)
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, METH_INIT, SIG_NOARG_VOID, null, null);
      mv.visitCode();
      
      // invoke superclass c'tor
      mv.visitInsn(ALOAD_0);
      mv.visitMethodInsn(INVOKESPECIAL, superClass, METH_INIT, SIG_NOARG_VOID, false);
      
      // implicit void return
      mv.visitInsn(RETURN);
      
      // set max stack depths and finish definition
      mv.visitMaxs(1, 1);
      mv.visitEnd();
   }
   
   /**
    * Create a standard getter method for one of the DMO's data properties. The method returns
    * an instance of a {@link BaseDataType} subclass.
    *
    * @param   meta
    *          Metadata describing the DMO property to be accessed.
    * @param   bda
    *          Helper object which holds type-specific data for bytecode instructions.
    * @param   offset
    *          Zero-based offset of the beginning of the data associated with the target property
    *          in the underlying {@link BaseRecord} data array.
    * @param   extent
    *          {@code true} if it sets an element of an array property (i.e., converted extent
    *          field); {@code false} if the method sets a scalar property. Array setter methods
    *          take two parameters; the first is an index within the array, the second is the
    *          BDT object to be set. Scalar methods take only the BDT parameter.
    * @param   bdtIndex
    *          Generate the getter using BDT index instead of native java. Only for extents.
    */
   private void createGetter(PropertyMeta meta,
                             BaseDataAccess bda,
                             int offset,
                             boolean extent,
                             boolean bdtIndex)
   {
      // signature of method we are defining
      StringBuilder sb = new StringBuilder("(");
      if (extent)
      {
         if (bdtIndex)
         {
            sb.append("L").append(TYPE_NUMBERTYPE).append(";");
         }
         else
         {
            sb.append("I");
         }
      }
      sb.append(")").append(bda.narrowType);
      String methSig = sb.toString();
      
      // method name must be constructed from original property name for denormalized extent field case;
      // otherwise, we use the scalar getter name
      String oriName;
      String methName = extent && (oriName = meta.getOriginalName()).length() > 0
                        ? (bda.logical ? "is" : "get") + StringHelper.changeCase(oriName, true)
                        : meta.getterName;
      
      // define the method
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methName, methSig, null, null);
      mv.visitCode();
      
      // load the DMO's "this" reference
      mv.visitInsn(ALOAD_0);
      
      // push the data offset onto the stack
      AsmUtils.pushInt(mv, offset);
      
      // for an array field, add the offset of the element within the array, which is the only
      // argument to the method
      if (extent)
      {
         if (bdtIndex)
         {
            // load method parameter object reference (index as NumberType) into local variable 1
            mv.visitInsn(ALOAD_1);
            
            // invoke NumberType.intValue to push index int value onto stack
            mv.visitMethodInsn(INVOKEVIRTUAL, TYPE_NUMBERTYPE, METH_INTVALUE, SIG_INTVALUE, false);
         }
         else
         {
            mv.visitInsn(ILOAD_1);
         }
         mv.visitInsn(IADD);
      }
      
      // invoke index-aware, BDT-aware getter method
      mv.visitMethodInsn(INVOKEVIRTUAL, RECORD, bda.getter, bda.getterSignature, false);
      
      // return the object retrieved from the DMO's data array
      mv.visitInsn(ARETURN);
      
      // set max stack depths and finish method definition
      if (extent)
      {
         mv.visitMaxs(3, 2);
      }
      else
      {
         mv.visitMaxs(2, 1);
      }
      mv.visitEnd();
   }
   
   /**
    * Create a standard setter method for one of the DMO's data properties. The method sets an
    * instance of a {@link BaseDataType} subclass and has no return value.
    * 
    * @param   meta
    *          Metadata describing the DMO property to be accessed.
    * @param   bda
    *          Helper object which holds type-specific data for bytecode instructions.
    * @param   offset
    *          Zero-based offset of the beginning of the data associated with the target property
    *          in the underlying {@link BaseRecord} data array.
    * @param   extent
    *          {@code true} if it sets an element of an array property (i.e., converted extent
    *          field); {@code false} if the method sets a scalar property. Array setter methods
    *          take two parameters; the first is an index within the array, the second is the
    *          BDT object to be set. Scalar methods take only the BDT parameter.
    * @param   bdtIndex
    *          Generate the getter using BDT index instead of native java. Only for extents.
    */
   private void createSetter(PropertyMeta meta,
                             BaseDataAccess bda,
                             int offset,
                             boolean extent,
                             boolean bdtIndex)
   {
      // signature of method we are defining
      StringBuilder sb = new StringBuilder("(");
      if (extent)
      {
         if (bdtIndex)
         {
            sb.append("L").append(TYPE_NUMBERTYPE).append(";");
         }
         else
         {
            sb.append("I");
         }
      }
      sb.append(bda.wideType).append(")V");
      String methSig = sb.toString();
      
      // method name must be constructed from original property name for denormalized extent field case;
      // otherwise, we use the scalar setter name
      String oriName;
      String methName = extent && (oriName = meta.getOriginalName()).length() > 0
                        ? "set" + StringHelper.changeCase(oriName, true)
                        : meta.setterName;
      
      // define the method
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methName, methSig, null, null);
      mv.visitCode();
      
      // load the DMO's "this" reference
      mv.visitInsn(ALOAD_0);
      
      // push the data offset onto the stack
      AsmUtils.pushInt(mv, offset);
      
      // for an array field, add the offset of the element within the array, which is the only
      // argument to the method
      if (extent)
      {
         if (bdtIndex)
         {
            // load method parameter object reference (index as NumberType) into local variable 1
            mv.visitInsn(ALOAD_1);
            
            // invoke NumberType.intValue to push index int value onto stack
            mv.visitMethodInsn(INVOKEVIRTUAL, TYPE_NUMBERTYPE, METH_INTVALUE, SIG_INTVALUE, false);
         }
         else
         {
            mv.visitInsn(ILOAD_1);
         }
         mv.visitInsn(IADD);
      }
      
      // load the BDT parameter onto the stack; for the scalar form, the BDT object with be the
      // first parameter to the DMO method, else it will be the second parameter
      mv.visitInsn(extent ? ALOAD_2 : ALOAD_1);
      
      // invoke index-aware, BDT-aware setter method
      mv.visitMethodInsn(INVOKEVIRTUAL, RECORD, bda.setter, bda.setterSignature, false);
      
      // void return
      mv.visitInsn(RETURN);
      
      // set max stack depths and finish method definition
      mv.visitMaxs(3, extent ? 3 : 2);
      mv.visitEnd();
   }
   
   /**
    * Create a bulk getter for to obtain the full set of value of the extent field. The full
    * signature of the method is {@code BDT[] getFieldK()}.
    *
    * @param   meta
    *          Metadata describing the DMO property to be accessed.
    * @param   bda
    *          Helper object which holds type-specific data for bytecode instructions.
    * @param   offset
    *          Zero-based offset of the beginning of the data associated with the target property
    *          in the underlying {@link BaseRecord} data array.
    */
   private void createInlineBulkGetter(PropertyMeta meta, BaseDataAccess bda, int offset)
   {
      // signature of method we are defining
      String methSig = "()[" + bda.wideType;
      
      // define the method
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, meta.getterName, methSig, null, null);
      mv.visitCode();
      
      // LocalVariableTable:
      //    Slot  Name   Signature                             Comment
      //    0     this   L<DMO>__Impl__;                       Type of the Impl object
      //    1     ret    [Lcom/goldencode/p2j/util/logical;    Array of [bda.narrowClass], in this particular case, [logical]
      //    2     k      I                                     An iterator variable
      
      // prepare two labels, used for local jumping of execution pointer:
      Label lbl8 = new Label();
      Label lbl31 = new Label();
      
      // logical[] ret = new logical[10];
      {
         // push the extent onto the stack. 
         // Note: The extent value can be any number between 1 and 28000, inclusive.
         //       If size is limited to less than 127 use a byte, otherwise use a short
         /// 0: bipush        10          // extent size
         int extent = meta.getOriginalExtent();
         mv.visitIntInsn((extent <= 127) ? BIPUSH : SIPUSH, extent);
         
         // create a new array of references of length count already pushed to stack and component
         // type identified by the specified class reference
         /// 2: anewarray     #2          // class com/goldencode/p2j/util/logical
         mv.visitTypeInsn(ANEWARRAY, bda.narrowClass);
         
         // ... the result is popped from stack and stored in local variable ret (slot_1)
         /// 5: astore_1
         mv.visitInsn(ASTORE_1);
      }
      
      // iterate all the [extent] items from of array, copying data from our [data] field
      /// for (int k = 0; k < 10; k++)
      {
         // the 'for' initialization: prepare 0 constant on stack
         /// 6: iconst_0
         mv.visitInsn(ICONST_0);
         
         // ... and store it in local variable  k (slot_2)
         /// 7: istore_2
         mv.visitInsn(ISTORE_2);
         
         // put a label at this location, as the loop point  
         mv.visitLabel(lbl8);
         
         // prepare testing the end of the 'for' loop (compare k and extent). First push k (slot_2) 
         /// 8: iload_2
         mv.visitInsn(ILOAD_2);
         
         // push the [extent] onto the stack as the second operand. Take care of the byes needed:
         // byte or short 
         /// 9: bipush        10
         mv.visitIntInsn((meta.getExtent() <= 127) ? BIPUSH : SIPUSH, meta.getExtent());
         
         // compare k (slot_2) to [extent] and jump to end of loop, if the case
         /// 11: if_icmpge     31
         mv.visitJumpInsn(IF_ICMPGE, lbl31);
      }
      
      // retrieve the data as BDT and store it in the appropriate offset in the array to be returned
      ///    ret[k] = _isLogical(7 + k);
      {
         // prepare the destination array by pushing it to stack 
         /// 14: aload_1
         mv.visitInsn(ALOAD_1);
         
         // we need to save at a specified location (k, slot_2) so push this too, to stack
         /// 15: iload_2
         mv.visitInsn(ILOAD_2);
         
         // prepare the call to getter (_isLogical, in this case), pushing the target object (this, slot_0)
         /// 16: aload_0
         mv.visitInsn(ALOAD_0);
         
         // we need to compute the offset of out data by adding the offset
         /// 17: bipush        7          // first field offset
         mv.visitIntInsn((offset <= 127) ? BIPUSH : SIPUSH, offset);
         
         // ... with the local iterator variable (k, slot_2), so we push then to stack 
         // 19: iload_2
         mv.visitInsn(ILOAD_2);
         
         // do actual offset calculation. the result remains on top of the stack
         /// 20: iadd
         mv.visitInsn(IADD);
         
         // At this moment, on stack we have:
         //    * 'this' (slot_0), loaded at line 16
         //    * the relative offset, computed and left on stack at line 20
         // We can invoke the getter (_isLogical in our sample). The result remains on top of the stack:
         // 21: invokevirtual #3         // Method _isLogical:(I)Lcom/goldencode/p2j/util/logical;
         mv.visitMethodInsn(INVOKEVIRTUAL, RECORD, bda.getter, bda.getterSignature, false);
         
         // At this moment, on stack we have:
         //    * our destination, local array 'ret' (slot_1), loaded at line 14
         //    * its relative offset, pushed on stack at line 15
         //    * the result from getter, from line 21
         // We can pop the result from stack and store it in the [ret] array.
         /// 24: aastore
         mv.visitInsn(AASTORE);
      }
      
      // -- complete the 'for' loop
      {
         // increment out iterator variable k (slot_2)
         /// 25: iinc          2, 1
         mv.visitIincInsn(2, 1);
         
         // and jump back to test termination of 'for' statement
         /// 28: goto          8
         mv.visitJumpInsn(GOTO, lbl8);   // jump to test(k<extent) to start new iteration
      }
      
      // returning [ret] array
      {
         // mark this as the point of exit from the 'for' loop
         mv.visitLabel(lbl31);
         
         // push the array to be returned (ret, slot_1) to stack
         /// 31: aload_1
         mv.visitInsn(ALOAD_1);
         
         // return normally, returning the array object at the top of the stack
         // 32: areturn
         mv.visitInsn(ARETURN);
      }
      
      // set max stack depths and finish method definition: stack=5, locals=3, args_size=1
      mv.visitMaxs(5, 3);
      
      // end of bytecode for this method
      mv.visitEnd();
   }
   
   /**
    * Create a bulk setter for setting the full extent field. The full signature of the method is
    * {@code void setFieldK(BDT[])}.
    *
    * @param   meta
    *          Metadata describing the DMO property to be accessed.
    * @param   bda
    *          Helper object which holds type-specific data for bytecode instructions.
    * @param   offset
    *          Zero-based offset of the beginning of the data associated with the target property
    *          in the underlying {@link BaseRecord} data array.
    */
   private void createInlineBulkSetter(PropertyMeta meta, BaseDataAccess bda, int offset)
   {
      // signature of method we are defining
      String methSig = "([" + bda.wideType + ")V";
      
      // define the method
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, meta.setterName, methSig, null, null);
      mv.visitCode();
      
      // LocalVariableTable:
      //    Slot  Name   Signature                             Comment
      //    0     this   L<DMO>__Impl__;                       Type of the Impl object
      //    1     val    [Lcom/goldencode/p2j/util/logical;    The parameter array
      //    2     len    I                                     The number of processed elements from array
      //    3     k      I                                     An iterator variable
      
      // prepare two labels, used for local jumping of execution pointer:
      Label lbl10 = new Label();
      Label lbl32 = new Label();
      
      // detect the number of items to be set as the minimum between the val's size and our extent
      //// int len = Math.min(val.length, extent);
      {
         // load the array passed in as a parameter from local variable 1 (slot_1: val)
         /// 0: aload_1
         mv.visitInsn(ALOAD_1);
         
         // push the array's length onto the stack
         //// 1: arraylength
         mv.visitInsn(ARRAYLENGTH);
         
         // push the extent onto the stack. 
         // Note: The extent value can be any number between 1 and 28000, inclusive.
         //       If size is limited to less than 127 use a byte, otherwise use a short
         //// 2: bipush        10 (the extent)
         int extent = meta.getOriginalExtent();
         mv.visitIntInsn((extent <= 127) ? BIPUSH : SIPUSH, extent);
         
         // invoke Math.min() against the two values, the result remains on the stack
         //// 4: invokestatic  #4                  // Method java/lang/Math.min:(II)I
         mv.visitMethodInsn(INVOKESTATIC, TYPE_MATH, METH_MIN, SIG_MIN, false);
         
         // pop the result of Math.min() from stack and store it in local variable 2 (slot_2: len).
         // This  will serve as the loop counter limit.
         //// 7: istore_2
         mv.visitInsn(ISTORE_2);
      }
      
      // iterate the first [len] items from the passed array
      /// for (int k = 0; k < len; k++)
      {
         // the 'for' initialization: prepare 0 constant on stack
         /// 8: iconst_0
         mv.visitInsn(ICONST_0);
         
         // ... and store it in local variable  k (slot_3)
         /// 9: istore_3
         mv.visitInsn(ISTORE_3);
         
         // put a label at this location, as the loop point  
         mv.visitLabel(lbl10);
         
         // prepare testing the end of the 'for' loop (compare k and len). First push k (slot_3)
         /// 10: iload_3
         mv.visitInsn(ILOAD_3);
         
         // the second operand, local variable len (slot_2) goes to stack
         /// 11: iload_2
         mv.visitInsn(ILOAD_2);
         
         // compare k (slot_3) to len (slot_2) and jump to end of loop, if the case
         /// 12: if_icmpge     32
         mv.visitJumpInsn(IF_ICMPGE, lbl32);
      }
      
      // ... and set the value in our extent field
      ///   _setLogical(7 + k, val[k]);
      {
         // prepare to call a method ('_setLogical', in this case) on [this], by pushing it (slot_0) on stack
         /// 15: aload_0
         mv.visitInsn(ALOAD_0);
         
         // we need to compute the offset of the item in [data]. Prepare the base field offset.
         /// 16: bipush        7
         mv.visitIntInsn(offset <= 127 ? BIPUSH : SIPUSH, offset);
         
         // push local variable k (slot_3) on stack
         /// 18: iload_3
         mv.visitInsn(ILOAD_3);
         
         // now we can compute the relative offset by adding the two previously pushed values
         // (offset + k). The result remains in the top of the stack.
         /// 19: iadd
         mv.visitInsn(IADD);
         
         // lets's compute val[k]: push the parameter val (slot_1) on stack
         /// 20: aload_1
         mv.visitInsn(ALOAD_1);
         
         // push the integer local variable k (slot_3) on stack 
         /// 21: iload_3
         mv.visitInsn(ILOAD_3);
         
         // load onto the stack the reference from an array, that is val[k]
         /// 22: aaload
         mv.visitInsn(AALOAD);
         
         // At this moment, on stack we have:
         //    * 'this' (slot_0), loaded at line 15
         //    * the relative offset, computed and left on stack at line 19
         //    * the reference to val[k], computed left on stack at line 22
         // We can invoke the setter (_setLogical in our sample):
         /// 23: invokevirtual #5      // Method _setLogical:(ILcom/goldencode/p2j/util/logical;)V
         mv.visitMethodInsn(INVOKEVIRTUAL, RECORD, bda.setter, bda.setterSignature, false);
      }
      
      // -- complete the 'for' loop
      {
         // increment out iterator variable k (slot_3)
         /// 26: iinc          3, 1
         mv.visitIincInsn(3, 1);
         
         // and jump back to test termination of 'for' statement 
         // 29: goto          10
         mv.visitJumpInsn(GOTO, lbl10);   // jump to test(k < len) to start new iteration
      }
      
      // leave the method
      {
         // put a label at this location. It will be used when the loop ends 
         mv.visitLabel(lbl32);
         
         // normal exit, return void from method
         // 25: return
         mv.visitInsn(RETURN);
      }
      
      // set max stack depths and finish method definition: stack=4, locals=4, args_size=2
      mv.visitMaxs(4, 4);
      
      // end of bytecode for this method
      mv.visitEnd();
   }
   
   /**
    * Create a mass getter for filling the full set of value of the extent field with a given 
    * scalar BDT value. The full signature of the method is {@code void setFieldK(BDT)}.
    *
    * @param   meta
    *          Metadata describing the DMO property to be accessed.
    * @param   bda
    *          Helper object which holds type-specific data for bytecode instructions.
    * @param   offset
    *          Zero-based offset of the beginning of the data associated with the target property
    *          in the underlying {@link BaseRecord} data array.
    */
   private void createInlineMassSetter(PropertyMeta meta, BaseDataAccess bda, int offset)
   {
      // signature of method we are defining
      String methSig = "(" + bda.wideType + ")V";
      
      // define the method
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, meta.setterName, methSig, null, null);
      mv.visitCode();
      
      // LocalVariableTable:
      //    Slot  Name   Signature                             Comment
      //    0     this   L<DMO>__Impl__;                       Type of the Impl object
      //    1     val    Lcom/goldencode/p2j/util/logical;     The parameter variable
      //    2     k      I                                     An iterator variable
      
      // prepare two labels, used for local jumping of execution pointer:
      Label lbl2 = new Label();
      Label lbl23 = new Label();
      
      // iterate all (extent) elements of the property/field using 'k' int iterator
      /// for (int k = 0; k < 10; k++)
      {
         // the 'for' initialization: prepare o constant on stack
         /// 0: iconst_0
         mv.visitInsn(ICONST_0);
         
         // ... and store it in local variable  k (slot_2)
         /// 1: istore_2
         mv.visitInsn(ISTORE_2);
         
         // put a label at this location, as the loop point  
         mv.visitLabel(lbl2);
         
         // prepare testing the end of the 'for' loop (compare k and extent). First push k (slot_2) 
         /// 2: iload_2
         mv.visitInsn(ILOAD_2);
         
         // push the extent onto the stack. 
         // Note: The extent value can be any number between 1 and 28000, inclusive.
         //       If size is limited to less than 127 use a byte, otherwise use a short
         /// 3: bipush 10          // extent size
         int extent = meta.getOriginalExtent();
         mv.visitIntInsn((extent <= 127) ? BIPUSH : SIPUSH, extent);
         
         // compare k (slot2) to extent size (10) from the stack and jump to return stmt if needed
         /// 5: if_icmpge 23
         mv.visitJumpInsn(IF_ICMPGE, lbl23);
      }
      
      // ... and set the value in [data] structure
      ///    _setLogical(7 + k, val);
      {
         // prepare to invoke the setter on current object by pushing this (slot_0) onto stack
         /// 8: aload_0
         mv.visitInsn(ALOAD_0);
         
         // prepare the field's relative offset by pushing it to stack
         // Note: There are at most 5000 fields in a record, but offset may be greater because of
         //       the previous extent fields
         //       If the offset is limited to less than 127 use a byte, otherwise use a short
         /// 9: bipush 7
         mv.visitIntInsn((offset <= 127) ? BIPUSH : SIPUSH, offset);
         
         // also prepare the iterator (k, slot_2) by pushing it to stack
         /// 11: iload_2
         mv.visitInsn(ILOAD_2);
         
         // compute absolute offset of the extent item by adding last two int values from stack
         // (k + offset). The result remains at the top of it. 
         /// 12: iadd
         mv.visitInsn(IADD);
         
         // put the parameter value (val, slot_1) on stack
         /// 13: aload_1
         mv.visitInsn(ALOAD_1);
         
         // At this moment, on stack we have:
         //    * 'this' (slot_0), loaded at line 8
         //    * the absolute offset, computed and left on stack at line 12
         //    * the reference to val, pushed on stack at line 13
         // We can invoke the setter (_setLogical in our sample):
         /// 14: invokevirtual #4         // Method _setLogical:(ILcom/goldencode/p2j/util/logical;)V
         mv.visitMethodInsn(INVOKEVIRTUAL, RECORD, bda.setter, bda.setterSignature, false);
      }
      
      // -- complete the 'for' loop 
      {
         // increment out iterator variable k (slot_2)
         /// 17: iinc 2, 1
         mv.visitIincInsn(2, 1);
         
         // and jump back to test termination of 'for' statement 
         /// 20: goto 2
         mv.visitJumpInsn(GOTO, lbl2);   // jump to test(k<extent) to start new iteration
      }
      
      // leave the method 
      {
         // put a label at this location. It will be used when the loop ends
         mv.visitLabel(lbl23);
         
         // normal exit, return void from method
         // 23: return
         mv.visitInsn(RETURN);
      }
      
      // set max stack depths and finish method definition: stack=3, locals=3, args_size=2
      mv.visitMaxs(3, 3);
      
      // end of bytecode for this method
      mv.visitEnd();
   }
   
   /**
    * Create a bulk getter for to obtain the full set of value of the extent field. The full
    * signature of the method is {@code BDT[] getFieldK()}.
    *
    * @param   meta
    *          Metadata describing the DMO property to be accessed.
    * @param   bda
    *          Helper object which holds type-specific data for bytecode instructions.
    * @param   offset
    *          Zero-based offset of the beginning of the data associated with the target property
    *          in the underlying {@link BaseRecord} data array.
    */
   private void createBulkGetter(PropertyMeta meta, BaseDataAccess bda, int offset)
   {
      // signature of method we are defining
      String methSig = "()[" + bda.narrowType;
      
      // method name must be constructed from original property name for denormalized extent field case;
      // otherwise, we use the stored getter name
      String oriName = meta.getOriginalName();
      String methName = oriName.length() > 0
                        ? (bda.logical ? "is" : "get") + StringHelper.changeCase(oriName, true)
                        : meta.getterName;
      
      // define the method
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methName, methSig, null, null);
      mv.visitCode();
      
      // prepare the call to bulk getter from super class, Record (_isLogical, in this case),
      // pushing the target object (this, slot_0)
      mv.visitInsn(ALOAD_0);
      
      // push the extent onto the stack. 
      // Note: The extent value can be any number between 1 and 28000, inclusive.
      //       If size is limited to less than 127 use a byte, otherwise use a short
      int extent = meta.getOriginalExtent();
      mv.visitIntInsn((extent <= 127) ? BIPUSH : SIPUSH, extent);
      
      // we need to compute the offset of out data by adding the offset
      // Note: There are at most 5000 fields in a record, but offset may be greater because of
      //       the previous extent fields
      //       If the offset is limited to less than 127 use a byte, otherwise use a short
      mv.visitIntInsn((offset <= 127) ? BIPUSH : SIPUSH, offset);
      
      // At this moment, on stack we have:
      //    * 'this' (slot_0)
      //    * the field extent
      //    * the offset in [data] field of the first element of extent property 
      // We can invoke the getter (_isLogical(int, int) in our sample).
      mv.visitMethodInsn(INVOKEVIRTUAL, RECORD, bda.getter, "(II)[" + bda.narrowType, false);
      
      // return normally, returning the array object at the top of the stack, left behind by the
      // previous invocation
      mv.visitInsn(ARETURN);
      
      // set max stack depths and finish method definition: stack=3, locals=1, args_size=1
      mv.visitMaxs(3, 1);
      
      // end of bytecode for this method
      mv.visitEnd();
   }
   
   /**
    * Create a bulk setter for setting the full extent field. The full signature of the method is
    * {@code void setFieldK(BDT[])}.
    *
    * @param   meta
    *          Metadata describing the DMO property to be accessed.
    * @param   bda
    *          Helper object which holds type-specific data for bytecode instructions.
    * @param   offset
    *          Zero-based offset of the beginning of the data associated with the target property
    *          in the underlying {@link BaseRecord} data array.
    */
   private void createBulkSetter(PropertyMeta meta, BaseDataAccess bda, int offset)
   {
      // signature of method we are defining
      String methSig = "([" + bda.wideType + ")V";
      
      // method name must be constructed from original property name for denormalized extent field case;
      // otherwise, we use the stored setter name
      String oriName = meta.getOriginalName();
      String methName = oriName.length() > 0
                        ? "set" + StringHelper.changeCase(oriName, true)
                        : meta.setterName;
      
      // define the method
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methName, methSig, null, null);
      mv.visitCode();
      
      // LocalVariableTable:
      //    Slot  Name   Signature                             Comment
      //    0     this   L<DMO>__Impl__;                       Type of the Impl object
      //    1     val    [Lcom/goldencode/p2j/util/logical;    The parameter array
      
      // prepare the call to bulk getter from super class, Record (_isLogical, in this case),
      // pushing the target object (this, slot_0)
      mv.visitInsn(ALOAD_0);
      
      // load the array passed in as a parameter from local variable 1 (slot_1: val)
      mv.visitInsn(ALOAD_1);
      
      // push the extent onto the stack. 
      // Note: The extent value can be any number between 1 and 28000, inclusive.
      //       If size is limited to less than 127 use a byte, otherwise use a short
      mv.visitIntInsn((meta.getExtent() <= 127) ? BIPUSH : SIPUSH, meta.getExtent());
      
      // we need to compute the offset of out data by adding the offset
      // Note: There are at most 5000 fields in a record, but offset may be greater because of
      //       the previous extent fields
      //       If the offset is limited to less than 127 use a byte, otherwise use a short
      mv.visitIntInsn((offset <= 127) ? BIPUSH : SIPUSH, offset);
      
      // At this moment, on stack we have:
      //    * 'this' (slot_0)
      //    * the 'val' parameter (slot_1) 
      //    * the field extent
      //    * the offset in [data] field of the first element of extent property 
      // We can invoke the getter (_isLogical(int, int) in our sample).
      mv.visitMethodInsn(INVOKEVIRTUAL, RECORD, bda.setter, "([" + bda.wideType + "II)V", false);
         
      // normal exit, return void from method
      mv.visitInsn(RETURN);
      
      // set max stack depths and finish method definition: stack=4, locals=2, args_size=2
      mv.visitMaxs(4, 2);
      
      // end of bytecode for this method
      mv.visitEnd();
   }
   
   /**
    * Create a mass getter for filling the full set of value of the extent field with a given 
    * scalar BDT value. The full signature of the method is {@code void setFieldK(BDT)}.
    *
    * @param   meta
    *          Metadata describing the DMO property to be accessed.
    * @param   bda
    *          Helper object which holds type-specific data for bytecode instructions.
    * @param   offset
    *          Zero-based offset of the beginning of the data associated with the target property
    *          in the underlying {@link BaseRecord} data array.
    */
   private void createMassSetter(PropertyMeta meta, BaseDataAccess bda, int offset)
   {
      // signature of method we are defining
      String methSig = "(" + bda.wideType + ")V";
      
      // method name must be constructed from original property name for denormalized extent field case;
      // otherwise, we use the stored setter name
      String oriName = meta.getOriginalName();
      String methName = oriName.length() > 0
                        ? "set" + StringHelper.changeCase(meta.getOriginalName(), true)
                        : meta.setterName;
      
      // define the method
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, methName, methSig, null, null);
      mv.visitCode();
      
      // LocalVariableTable:
      //    Slot  Name   Signature                             Comment
      //    0     this   L<DMO>__Impl__;                       Type of the Impl object
      //    1     val    Lcom/goldencode/p2j/util/logical;    The parameter array
      
      // prepare the call to bulk getter from super class, Record (_isLogical, in this case),
      // pushing the target object (this, slot_0)
      mv.visitInsn(ALOAD_0);
      
      // load the array passed in as a parameter from local variable 1 (slot_1: val)
      mv.visitInsn(ALOAD_1);
      
      // push the extent onto the stack. 
      // Note: The extent value can be any number between 1 and 28000, inclusive.
      //       If size is limited to less than 127 use a byte, otherwise use a short
      mv.visitIntInsn((meta.getExtent() <= 127) ? BIPUSH : SIPUSH, meta.getExtent());
      
      // we need to compute the offset of out data by adding the offset
      // Note: There are at most 5000 fields in a record, but offset may be greater because of
      //       the previous extent fields
      //       If the offset is limited to less than 127 use a byte, otherwise use a short
      mv.visitIntInsn((offset <= 127) ? BIPUSH : SIPUSH, offset);
      
      // At this moment, on stack we have:
      //    * 'this' (slot_0)
      //    * the 'val' parameter (slot_1) 
      //    * the field extent
      //    * the offset in [data] field of the first element of extent property 
      // We can invoke the getter (_isLogical(int, int) in our sample).
      mv.visitMethodInsn(INVOKEVIRTUAL, RECORD, bda.setter, "(" + bda.wideType + "II)V", false);
      
      // normal exit, return void from method
      mv.visitInsn(RETURN);
      
      // set max stack depths and finish method definition: stack=4, locals=2, args_size=2
      mv.visitMaxs(4, 2);
      
      // end of bytecode for this method
      mv.visitEnd();
   }
   
   /**
    * Dynamically generate the {@code toPOJO()} method.
    * 
    * @param   pojoClass
    *          The destination class.
    * @param   propMeta
    *          The list of properties used in data transfer.
    */
   private void create_toPOJO_method(Class<?> pojoClass, PropertyMeta[] propMeta)
   {
      String pojoClassName = pojoClass.getName().replace('.', '/');
      
      // signature of method we are defining
      String methSig = "()Ljava/lang/Object;";
      
      // define the method
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "toPOJO", methSig, null, null);
      mv.visitCode();
      
      // Example: com.goldencode.testcases.pojo.fwd.Foo foo = new com.goldencode.testcases.pojo.fwd.Foo();
      mv.visitTypeInsn(NEW, pojoClassName);
      mv.visitInsn(DUP);
      mv.visitMethodInsn(INVOKESPECIAL, pojoClassName, "<init>", "()V", false);
      mv.visitInsn(ASTORE_1);
      
      for (int i = 0; i < propMeta.length; i++)
      {
         PropertyMeta pm = propMeta[i];
         int extent = pm.getExtent();
         Dmo2Pojo cvt = converters.get(pm.getType());
         
         if (extent > 0)
         {
            // array / extent
            if (cvt.dmo2pojo == null)
            {
               // POJO and DMO are using same datatype. No conversion necessary:
               //    Example: System.arraycopy(data, i, foo.getPrices(), 0, extent);
               mv.visitInsn(ALOAD_0);
               mv.visitFieldInsn(GETFIELD,
                                 "com/goldencode/p2j/persist/orm/BaseRecord",
                                 "data",
                                 "[Ljava/lang/Object;");          // access BaseRecord.data[] field
               mv.visitIntInsn((i <= 127) ? BIPUSH : SIPUSH, i);  // at position [i]
               mv.visitInsn(ALOAD_1);
               mv.visitMethodInsn(INVOKEVIRTUAL,
                                  pojoClassName,
                                  pm.getterName,
                                  "()[L" + cvt.pojoType + ";",
                                  false);
               mv.visitInsn(ICONST_0);
               mv.visitIntInsn((extent <= 127) ? BIPUSH : SIPUSH, extent);
               mv.visitMethodInsn(INVOKESTATIC,
                                  "java/lang/System",
                                  "arraycopy",
                                  "(Ljava/lang/Object;ILjava/lang/Object;II)V",
                                  false);
            }
            else
            {
               // POJO and DMO are using DIFFERENT datatype. Use N(j) times Record.<cvt.dmo2pojo>(p) static method:
               //    Example: foo.setMsrp(j, Record.toPojo((BigDecimal) data[i + j]));
               for (int j = 0; j < extent; j++)
               {
                  mv.visitInsn(ALOAD_1);
                  mv.visitIntInsn((j <= 127) ? BIPUSH : SIPUSH, j);           // to position [j]
                  mv.visitInsn(ALOAD_0);
                  mv.visitFieldInsn(GETFIELD,
                                    "com/goldencode/p2j/persist/orm/BaseRecord",
                                    "data",
                                    "[Ljava/lang/Object;");                   // access BaseRecord.data[] field
                  mv.visitIntInsn((i + j <= 127) ? BIPUSH : SIPUSH, i + j);   // from position [i + j]
                  mv.visitInsn(AALOAD);
                  mv.visitTypeInsn(CHECKCAST, cvt.dmoType);
                  mv.visitMethodInsn(INVOKESTATIC,
                                     "com/goldencode/p2j/persist/Record",
                                     cvt.dmo2pojo,
                                     "(L" + cvt.dmoType + ";)L" + cvt.pojoType + ";",
                                     false);
                  mv.visitMethodInsn(INVOKEVIRTUAL,
                                     pojoClassName,
                                     pm.setterName,
                                     "(IL" + cvt.pojoType + ";)V",
                                     false);
               }
            }
            
            i += extent - 1;
         }
         else
         {
            // scalar / denormalized extent field:
            if (cvt.dmo2pojo == null)
            {
               // POJO and DMO are using same datatype. No conversion necessary:
               //    Example: foo.setMsrp((BigDecimal) data[i]);
               mv.visitInsn(ALOAD_1);
               mv.visitInsn(ALOAD_0);
               mv.visitFieldInsn(GETFIELD,
                                 "com/goldencode/p2j/persist/orm/BaseRecord",
                                 "data",
                                 "[Ljava/lang/Object;");          // access BaseRecord.data[] field
               mv.visitIntInsn((i <= 127) ? BIPUSH : SIPUSH, i);  // at position [i]
               mv.visitInsn(AALOAD);
               mv.visitTypeInsn(CHECKCAST, cvt.dmoType);
               mv.visitMethodInsn(INVOKEVIRTUAL,
                                  pojoClassName,
                                  pm.setterName,
                                  cvt.pojoType.indexOf('[') != -1
                                        ? "(" + cvt.pojoType + ")V"
                                        : "(L" + cvt.pojoType + ";)V",
                                  false);
            }
            else
            {
               // POJO and DMO are using DIFFERENT datatype. Use Record.<cvt.dmo2pojo>(p) static method:
               //    Example: foo.setMsrp(Record.toPojo((BigDecimal) data[i]));
               mv.visitInsn(ALOAD_1);
               mv.visitInsn(ALOAD_0);
               mv.visitFieldInsn(GETFIELD,
                                 "com/goldencode/p2j/persist/orm/BaseRecord",
                                 "data",
                                 "[Ljava/lang/Object;");          // access BaseRecord.data[] field
               mv.visitIntInsn((i <= 127) ? BIPUSH : SIPUSH, i);  // at position [i]
               mv.visitInsn(AALOAD);
               mv.visitTypeInsn(CHECKCAST, cvt.dmoType);
               mv.visitMethodInsn(INVOKESTATIC,
                                  "com/goldencode/p2j/persist/Record",
                                  cvt.dmo2pojo,
                                  "(L" + cvt.dmoType + ";)L" + cvt.pojoType + ";",
                                  false);
               mv.visitMethodInsn(INVOKEVIRTUAL,
                                  pojoClassName,
                                  pm.setterName,
                                  cvt.pojoType.indexOf('[') != -1
                                        ? "(" + cvt.pojoType + ")V"
                                        : "(L" + cvt.pojoType + ";)V",
                                  false);
            }
         }
      }
      
      // return foo; normal exit, load and return top of stack object from method
      mv.visitInsn(ALOAD_1);
      mv.visitInsn(ARETURN);
      
      // set max stack depths and finish method definition: stack=5, locals=2, args_size=1
      mv.visitMaxs(5, 2);
      
      // end of bytecode for this method
      mv.visitEnd();
   }
   
   /**
    * Dynamically generate the {@code populateFromPOJO()} method.
    *
    * @param   pojoClass
    *          The destination class.
    * @param   propMeta
    *          The list of properties used in data transfer.
    */
   private void create_populateFromPOJO_method(Class<?> pojoClass, PropertyMeta[] propMeta)
   {
      String pojoClassName = pojoClass.getName().replace('.', '/');
      
      // signature of method we are defining
      String methSig = "(Ljava/lang/Object;)V";
      
      // define the method
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "populateFromPOJO", methSig, null, null);
      mv.visitCode();
      
      // Example: com.goldencode.testcases.pojo.fwd.Foo pojo = (com.goldencode.testcases.pojo.fwd.Foo) src;
      mv.visitInsn(ALOAD_1);
      mv.visitTypeInsn(CHECKCAST, pojoClassName);
      mv.visitInsn(ASTORE_2);
      
      for (int i = 0; i < propMeta.length; i++)
      {
         PropertyMeta pm = propMeta[i];
         int extent = pm.getExtent();
         Dmo2Pojo cvt = converters.get(pm.getType());
         
         if (extent > 0)
         {
            // array / extent
            if (cvt.pojo2dmo == null)
            {
               // POJO and DMO are using same datatype. No conversion necessary:
               // Example: System.arraycopy(data, i, foo.getPrices(), 0, extent);
               mv.visitInsn(ALOAD_2);
               mv.visitMethodInsn(INVOKEVIRTUAL,
                                  pojoClassName,
                                  pm.getterName,
                                  "()[L" + cvt.pojoType + ";",
                                  false);
               mv.visitInsn(ICONST_0);
               mv.visitInsn(ALOAD_0);
               mv.visitFieldInsn(GETFIELD,
                                 "com/goldencode/p2j/persist/orm/BaseRecord",
                                 "data",
                                 "[Ljava/lang/Object;");          // access BaseRecord.data[] field
               mv.visitIntInsn((i <= 127) ? BIPUSH : SIPUSH, i);  // at position [i]
               mv.visitIntInsn((extent <= 127) ? BIPUSH : SIPUSH, extent);
               mv.visitMethodInsn(INVOKESTATIC,
                                  "java/lang/System",
                                  "arraycopy",
                                  "(Ljava/lang/Object;ILjava/lang/Object;II)V",
                                  false);
            }
            else
            {
               // POJO and DMO are using DIFFERENT datatype. Use N(j) times Record.<cvt.pojo2dmo>(p) static method:
               //    Example: data[i + j] = Record.toDmoDate(pojo.getD003(j);
               for (int j = 0; j < extent; j++)
               {
                  mv.visitInsn(ALOAD_0);
                  mv.visitFieldInsn(GETFIELD,
                                    "com/goldencode/p2j/persist/orm/BaseRecord",
                                    "data",
                                    "[Ljava/lang/Object;");                   // access BaseRecord.data[] field
                  mv.visitIntInsn((i + j <= 127) ? BIPUSH : SIPUSH, i + j);   // at position [i + j]
                  mv.visitInsn(ALOAD_2);
                  mv.visitIntInsn((j <= 127) ? BIPUSH : SIPUSH, j);           // from position [j]
                  mv.visitMethodInsn(INVOKEVIRTUAL,
                                     pojoClassName,
                                     pm.getterName,
                                     cvt.pojoType.indexOf('[') != -1
                                           ? "(I)" + cvt.pojoType
                                           : "(I)L" + cvt.pojoType + ";",
                                     false);
                  mv.visitMethodInsn(INVOKESTATIC,
                                     "com/goldencode/p2j/persist/Record",
                                     cvt.pojo2dmo,
                                     "(L" + cvt.pojoType + ";)L" + cvt.dmoType + ";",
                                     false);
                  mv.visitInsn(AASTORE);
               }
            }
            
            i += extent - 1;
         }
         else
         {
            // scalar / denormalized extent field:
            if (cvt.pojo2dmo == null)
            {
               // POJO and DMO are using same datatype. No conversion necessary:
               // Example: data[i] = pojo.getMsrp();
               mv.visitInsn(ALOAD_0);
               mv.visitFieldInsn(GETFIELD,
                                 "com/goldencode/p2j/persist/orm/BaseRecord",
                                 "data",
                                 "[Ljava/lang/Object;");          // access BaseRecord.data[] field
               mv.visitIntInsn((i <= 127) ? BIPUSH : SIPUSH, i);  // at position [i]
               mv.visitInsn(ALOAD_2);
               mv.visitMethodInsn(INVOKEVIRTUAL,
                                  pojoClassName,
                                  pm.getterName,
                                  cvt.pojoType.indexOf('[') != -1
                                        ? "()" + cvt.pojoType
                                        : "()L" + cvt.pojoType + ";",
                                  false);
               mv.visitInsn(AASTORE);
            }
            else
            {
               // POJO and DMO are using DIFFERENT datatype. Use Record.<cvt.pojo2dmo>(p) static method:
               // Example: data[i] = Record.toDmoDate(pojo.getD003();
               mv.visitInsn(ALOAD_0);
               mv.visitFieldInsn(GETFIELD,
                                 "com/goldencode/p2j/persist/orm/BaseRecord",
                                 "data",
                                 "[Ljava/lang/Object;");          // access BaseRecord.data[] field
               mv.visitIntInsn((i <= 127) ? BIPUSH : SIPUSH, i);  // at position [i]
               mv.visitInsn(ALOAD_2);
               mv.visitMethodInsn(INVOKEVIRTUAL,
                                  pojoClassName,
                                  pm.getterName,
                                  cvt.pojoType.indexOf('[') != -1
                                        ? "()" + cvt.pojoType
                                        : "()L" + cvt.pojoType + ";",
                                  false);
               mv.visitMethodInsn(INVOKESTATIC,
                                  "com/goldencode/p2j/persist/Record",
                                  cvt.pojo2dmo,
                                  "(L" + cvt.pojoType + ";)L" + cvt.dmoType + ";",
                                  false);
               mv.visitInsn(AASTORE);
            }
         }
      }
      
      // return: normal exit
      mv.visitInsn(RETURN);
      
      // set max stack depths and finish method definition: stack=5, locals=3, args_size=2
      mv.visitMaxs(5, 3);
      
      // end of bytecode for this method
      mv.visitEnd();
   }
   
   /**
    * Generate the implementation of {@link Undoable#deepCopy()} method. The method delegates
    * the call to {@link Record#snapshot()} method.
    * <p>
    * This method became deprecated and is not being called any more as the {@link Persistable}
    * does not extends {@code Undoable} interface.
    */
   @Deprecated
   private void createDeepCopyMethod()
   {
      // signature of method we are defining
      String methSig = "()Lcom/goldencode/p2j/util/Undoable;";
      
      // define the method
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "deepCopy", methSig, null, null);
      mv.visitCode();
      
      // LocalVariableTable:
      //    Slot  Name   Signature                             Comment
      //    0     this   L<DMO>__Impl__;                       Type of the Impl object
      
      // prepare the call to bulk getter from super class, snapshot(), pushing the target object
      // (this, slot_0)
      mv.visitInsn(ALOAD_0);
      
      // at this moment, on stack we have:
      //    * 'this' (slot_0)
      // We can invoke the Record.snapshot() 
      mv.visitMethodInsn(INVOKEVIRTUAL,
                         RECORD,
                         "snapshot",
                         "()Lcom/goldencode/p2j/persist/Record;",
                         false);
      
      // before returning check the type of the returned value
      mv.visitTypeInsn(CHECKCAST, "com/goldencode/p2j/util/Undoable");
         
      // normal exit, return top of stack object from method
      mv.visitInsn(ARETURN);
      
      // set max stack depths and finish method definition: stack=1, locals=1, args_size=1
      mv.visitMaxs(1, 1);
      
      // end of bytecode for this method
      mv.visitEnd();
   }
   
   /**
    * Generate the implementation of {@link Undoable#assign(Undoable)} method. The method
    * delegates the call to {@link Record#copy(BaseRecord)} method and return its result.
    * <p>
    * This method became deprecated and is not being called any more as the {@link Persistable}
    * does not extends {@code Undoable} interface.
    */
   @Deprecated
   private void createAssignMethod()
   {
      // signature of method we are defining
      String methSig = "(Lcom/goldencode/p2j/util/Undoable;)V";
      
      // define the method
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "assign", methSig, null, null);
      mv.visitCode();
      
      // LocalVariableTable:
      //    Slot  Name   Signature                             Comment
      //    0     this   L<DMO>__Impl__;                       Type of the Impl object
      //    1     old    Lcom/goldencode/p2j/util/Undoable;    The Undoable parameter
      
      // prepare the call to bulk getter from super class, copy(BaseRecord) pushing the target
      // object (this, slot_0)
      mv.visitInsn(ALOAD_0);
      
      // push on stack the argument of this method as the only parameter of Record.copy. That is,
      // (old, slot_1)
      mv.visitInsn(ALOAD_1);
      
      // before actually calling the copy method, make sure the parameter is of the right type:
      mv.visitTypeInsn(CHECKCAST, "com/goldencode/p2j/persist/orm/BaseRecord");
      
      // at this moment, on stack we have:
      //    * 'this' (slot_0)
      //    * 'old' (slot_1), already type-checked 
      // We can invoke the Record.snapshot() 
      mv.visitMethodInsn(INVOKEVIRTUAL,
                         RECORD,
                         "copy",
                         "(Lcom/goldencode/p2j/persist/orm/BaseRecord;)V",
                         false);
         
      // normal exit, return void from method
      mv.visitInsn(RETURN);
      
      // set max stack depths and finish method definition: stack=2, locals=2, args_size=2
      mv.visitMaxs(2, 2);
      
      // end of bytecode for this method
      mv.visitEnd();
   }
   
   /**
    * Load the DMO implementation class into the JVM, using the {@link AsmClassLoader}.
    * 
    * @return  The loaded class.
    * 
    * @throws  ClassNotFoundException
    *          if the class is not found. This should not be possible, since we are providing the
    *          byte code to the class loader and the search should not fail.
    * @throws  ConfigurationException
    *          If the {@link AsmClassLoader} was not added in Java command line parameters so the dynamically
    *          assembled class implementation cannot be loaded.
    */
   private Class<? extends Record> load()
   throws ClassNotFoundException,
          ConfigurationException
   {
      byte[] code = cw.toByteArray();
      
      // enable the following lines for debugging
      //try
      //{
      //   new FileOutputStream(binaryName + ".class").write(code);
      //}
      //catch (IOException e)
      //{
      //   System.out.println("Failed to write class binary for [" + binaryName +"]");
      //}
      
      AsmClassLoader instance = AsmClassLoader.getInstance();
      if (instance == null)
      {
         throw new ConfigurationException(
               "AsmClassLoader not instantiated. Please set it as system class loader using " +
               "'-Djava.system.class.loader' property in command line.");
      }
      
      return (Class<? extends Record>) instance.loadClass(binaryName, code);
   }
   
   /**
    * Helper class which holds information needed to assemble bytecode instructions to invoke
    * the low level, BDT-aware getter and setter methods in the {@code Record} class.
    */
   private static class BaseDataAccess
   {
      /** Getter method name */
      String getter;
      
      /** Setter method name */
      String setter;
      
      /** Most specific BDT subclass which is returned by a getter method */
      String narrowType;
      
      /** Least specific BDT subclass which is accepted by a setter method */
      String wideType;
      
      /** Getter method signature as needed by bytecode */
      String getterSignature;
      
      /** Setter method signature as needed by bytecode */
      String setterSignature;
      
      /** The narrow class name. Used to create arrays of this type. */
      String narrowClass;
      
      /** Is the property type a logical (boolean) type? */
      boolean logical;
   }
}