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