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