DmoAsmWorker.java
/*
** Module : DmoAsmWorker.java
** Abstract : Pattern worker which assembles bytecode for DMO interfaces and classes in memory
**
** Copyright (c) 2015-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------------Description----------------------------------------
** 001 ECF 20150407 Created initial version, implemented using ASM v5.0.3.
** 002 ECF 20150513 Fixed simple getter method assembly, which did not use the correct copy
** constructor signature in some cases.
** 003 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 004 ECF 20160219 Extracted many constants to DmoAsmTypes interface.
** 005 ECF 20160320 Fixed assignField method to accommodate copy directly from field for scalar
** fields.
** 006 ECF 20160619 Fixed initSimpleField to handle non-default scale for decimals with unknown
** initial value. Replaced double constant in initSimpleField with string
** constant, which is slower but always accurate.
** 007 ECF 20171209 Removed 'force' flag parameter from all BDT.assign method signatures/calls.
** Error handling changes
** 008 SVL 20190614 Added CLOB and BLOB data types.
** 009 OM 20190704 Replaced TYPE_PERSISTABLE with TYPE_TEMPTABLERECORD for dynamic DMOs.
** 010 ECF 20190819 Fix for setter method for recid field (credit also to EVL).
** 011 OM 20200906 New ORM implementation.
** ECF 20221207 Enable the assembly of permanent table DMO interfaces.
** 012 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.schema;
import java.util.*;
import java.util.logging.*;
import org.objectweb.asm.*;
import com.goldencode.asm.*;
import com.goldencode.ast.*;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.uast.*;
import com.goldencode.p2j.util.*;
/**
* A pattern worker which provides services to support the in-memory compilation of DMO interface
* and implementation classes using ASM. This worker is only used at runtime, not during static
* conversion. Class com.goldencode.p2j.persist.DynamicTablesHelper creates 4GL-compatible schema
* ASTs and runs TRPL tasks to convert them to Java ASTs.
* <p>
* The rule-sets which generate those Java ASTs behave differently, depending on whether they are
* executed at server runtime or in batch mode for static conversion. In runtime mode, some
* adjustments are made to the ASTs to make them more amenable to assembing class files directly.
* In batch mode, the ASTs are optimized for downstream anti-parsing into source code. For
* example, in runtime mode, nodes are rearranged into a structure more suitable for bytecode
* assembly, some different token types are used to give better assembly cues, class names are
* fully qualified, and assembly hints are added.
* <p>
* This work was designed not to be a general-purpose assembler/compiler, but rather to provide a
* pragmatic solution for DMO interface/class assembly, using TRPL to walk the Java ASTs and this
* worker's service library to bridge to services in ASM which are not easily accessible from
* TRPL. This specialization allows us to make some assumptions about the purpose of the bytecode
* being assembled. As such, the service APIs in this worker are only as granular as required to
* meet the needs of the AST structure, but are as coarse as possible. In the TRPL code, we do
* not interpret every AST node, but rather use a combination of hints left behind during the AST
* creation and simple patterns of token types to recognize a feature (e.g., initializing a DMO
* field within a loop). We extract the information necessary for that feature from the AST, then
* call into this worker with that information, and let this worker assemble the bytecode
* instructions needed to implement that feature. This often assumes knowledge of the context of
* that feature within the broader context of its method or class, such as the state of runtime
* operand stack and local variable pool. This approach allows us to dispense with a much more
* complicated state machine that would be necessary to implement a more general-purpose compiler,
* enhancing the simplicity and performance of the service.
* <p>
* The solution replaces the use of an in-memory compiler implementation based on the use of
* <code>javax.tools.JavaCompiler</code>. While effective and correct, that solution had several
* drawbacks and needed to be replaced. It was considerably slower and created memory problems
* through its internal use of an ever-growing character array to manage source code text; and
* with a dubious use of soft references, which required a workaround of disabling them entirely.
* It also required the somewhat expensive, intermediate step of source code generation, which is
* bypassed now by assembling directly from the Java ASTs.
* <p>
* This worker does not finally generate the class files or load them into the JVM. The byte
* arrays representing the finished interface and implementation classes are stored by the TRPL
* code into the current pattern engine. The <code>DynamicTablesHelper</code> retrieves them from
* the pattern engine and then uses a {@link com.goldencode.asm.AsmClassLoader custom class
* loader} to load them into the JVM.
*
* @author ECF
*/
public final class DmoAsmWorker
extends AbstractConversionWorker
implements DmoAsmTypes,
JavaTokenTypes,
Opcodes
{
/** Logger. */
private static final ConversionStatus LOG = ConversionStatus.get(DmoAsmWorker.class);
/** Map of field types to the signatures of their associated <code>assign</code> methods */
private static final Map<String, String> assignMethSigs = new HashMap<>();
/** Map of field types to the types used in their associated setter method signatures */
private static final Map<String, String> setterTypes = new HashMap<>();
/** Map of field types to the descriptors used in their associated copy c'tor signatures */
private static final Map<String, String> copyCtorTypes = new HashMap<>();
static
{
assignMethSigs.put(TYPE_CHARACTER , "(L" + TYPE_TEXT + ";)V");
assignMethSigs.put(TYPE_DATE , "(L" + TYPE_DATE + ";)V");
assignMethSigs.put(TYPE_DATETIME , "(L" + TYPE_DATE + ";)V");
assignMethSigs.put(TYPE_DATETIMETZ, "(L" + TYPE_DATE + ";)V");
assignMethSigs.put(TYPE_DECIMAL , "(L" + TYPE_NUMBERTYPE + ";)V");
assignMethSigs.put(TYPE_HANDLE , "(L" + TYPE_HANDLE + ";)V");
assignMethSigs.put(TYPE_INT64 , "(L" + TYPE_NUMBERTYPE + ";)V");
assignMethSigs.put(TYPE_INTEGER , "(L" + TYPE_NUMBERTYPE + ";)V");
assignMethSigs.put(TYPE_LOGICAL , "(L" + TYPE_LOGICAL + ";)V");
assignMethSigs.put(TYPE_LONGCHAR , "(L" + TYPE_TEXT + ";)V");
assignMethSigs.put(TYPE_MEMPTR , "(L" + TYPE_BINARYDATA + ";)V");
assignMethSigs.put(TYPE_RAW , "(L" + TYPE_BINARYDATA + ";)V");
assignMethSigs.put(TYPE_RECID , "(L" + TYPE_NUMBERTYPE + ";)V");
assignMethSigs.put(TYPE_ROWID , "(L" + TYPE_ROWID + ";)V");
assignMethSigs.put(TYPE_CLOB , "(L" + TYPE_CLOB + ";)V");
assignMethSigs.put(TYPE_BLOB , "(L" + TYPE_BLOB + ";)V");
setterTypes.put(TYPE_CHARACTER , TYPE_TEXT);
setterTypes.put(TYPE_DATETIME , TYPE_DATE);
setterTypes.put(TYPE_DATETIMETZ, TYPE_DATE);
setterTypes.put(TYPE_DECIMAL , TYPE_NUMBERTYPE);
setterTypes.put(TYPE_INT64 , TYPE_NUMBERTYPE);
setterTypes.put(TYPE_INTEGER , TYPE_NUMBERTYPE);
setterTypes.put(TYPE_RECID , TYPE_NUMBERTYPE);
setterTypes.put(TYPE_LONGCHAR , TYPE_TEXT);
setterTypes.put(TYPE_MEMPTR , TYPE_BINARYDATA);
setterTypes.put(TYPE_RAW , TYPE_BINARYDATA);
setterTypes.put(TYPE_CLOB , TYPE_CLOB);
setterTypes.put(TYPE_BLOB , TYPE_BLOB);
copyCtorTypes.put(TYPE_DATETIME , DESC_DATE);
copyCtorTypes.put(TYPE_DATETIMETZ, DESC_DATE);
}
/**
* Default constructor which defines the symbol library to be registered.
*/
public DmoAsmWorker()
{
super();
setLibrary(new Library());
}
/**
* Create an ASM <code>ClassWriter</code> which computes frame and max stack depth information
* automatically.
*
* @return See above.
*/
private static ClassWriter createClassWriter()
{
return new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
}
/**
* Make a fully qualified, internal type descriptor from the given, short annotation name.
*
* @param name
* Unqualified annotation name.
*
* @return Fully qualified descriptor.
*/
private static String makeAnnotationDescriptor(String name)
{
return ANNOTATION_DESC_PREFIX + name + ";";
}
/**
* Given the internal type name of a DMO field, compose an indexed setter method signature of
* the form <code>(IL<type name>;)V</code>.
*
* @param fieldType
* Internal type name of a DMO field.
*
* @return Indexed setter method signature.
*/
private static String makeIndexedSetterSignature(String fieldType)
{
StringBuilder buf = new StringBuilder("(IL");
String parmType = setterTypes.get(fieldType);
if (parmType == null)
{
parmType = fieldType;
}
buf.append(parmType);
buf.append(";)V");
return buf.toString();
}
/**
* Implement the loading of a composite element from its ArrayList and casting it to the
* appropriate inner class type. Assumes the index to be retrieved is an int primitive
* available in local variable 1 (i.e., the first parameter passed to the method being
* processed).
*
* @param mv
* ASM method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param compFieldName
* Short name of field in enclosing class which contains the array list of
* composites we need to access.
* @param compClassName
* Short name of composite inner class.
*
* @return The fully qualified type name of the composite inner class.
*/
private static String loadComposite(MethodVisitor mv,
String classType,
String compFieldName,
String compClassName)
{
// prepend enclosing class type to short name of composite class to create fully
// qualified composite class type
String compClassType = classType + '$' + compClassName;
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// push the object reference within the specified field onto the stack
mv.visitFieldInsn(GETFIELD, classType, compFieldName, DESC_LIST);
// load first method parameter (the array index -- an int) into local variable 1
mv.visitVarInsn(ILOAD, 1);
// invoke ArrayList.get method
mv.visitMethodInsn(INVOKEINTERFACE, TYPE_LIST, METH_GET, SIG_GET, true);
// cast to appropriate composite class type
mv.visitTypeInsn(CHECKCAST, compClassType);
return compClassType;
}
/**
* Implement the loading of a <code>NumberType</code> object and extracting its int value.
*
* @param mv
* ASM method visitor.
*/
private static void loadIndexParameter(MethodVisitor mv)
{
// load method parameter object reference (index as NumberType) into local variable 1
mv.visitVarInsn(ALOAD, 1);
// invoke NumberType.intValue to push index int value onto stack
mv.visitMethodInsn(INVOKEVIRTUAL, TYPE_NUMBERTYPE, METH_INTVALUE, SIG_INTVALUE, false);
}
/**
* An API of library functions available to TRPL to provide services to assemble DMO interface
* and implementation classes using ASM.
*/
public static class Library
{
/** Map of Java primitive data type common names to their internal type names */
private static Map<String, String> primitives = new HashMap<>();
static
{
primitives.put("boolean", "Z");
primitives.put("byte" , "B");
primitives.put("char" , "C");
primitives.put("double" , "D");
primitives.put("float" , "F");
primitives.put("int" , "I");
primitives.put("long" , "J");
primitives.put("short" , "S");
primitives.put("void" , "V");
}
/**
* Given a common type name for a primitive data type or Java object, convert it to the
* internal type form name or descriptor needed for assembly. For object names, this
* involves replacing all dot (<code>.</code>) characters with forward slash
* (<code>/</code>) and involves additional transformation if <code>forSig</code> is
* <code>true</code> (see below).
*
* @param name
* Common name (e.g. <code>com.goldencode.p2j.util.integer</code> for the P2J
* <code>integer</code> class or <code>int</code> for the Java primitive integer)
* @param forSig
* If <code>true</code>, assume the result is being used for a method signature,
* and thus needs to be formatted as a descriptor. This prepends open square
* bracket (<code>[</code>) for arrays, and for object names, prepends capital
* L (<code>L</code>) and appends semi-colon(<code>;</code>).
*
* @return Internal type name or descriptor as described above.
*/
public String convertTypeName(String name, boolean forSig)
{
String type = primitives.get(name);
if (type != null)
{
return type;
}
type = AsmUtils.commonToInternalTypeName(name);
if (forSig)
{
StringBuilder buf = new StringBuilder("L");
int pos = type.lastIndexOf("[]");
if (pos < 0)
{
buf.append(type);
}
else
{
buf.insert(0, '[');
buf.append(type, 0, pos);
}
buf.append(';');
type = buf.toString();
}
return type;
}
/**
* Create and initialize a {@code ClassWriter} for a top-level, DMO interface of the given type. The
* interface must extend {@code com.goldencode.p2j.persist.Temporary} (for a temp-table DMO interface)
* or {@code com.goldencode.p2j.persist.DataModelObject} (for a permanent table DMO interface).
*
* @param ifaceName
* Fully qualified, internal type name of the DMO interface.
* @param temporary
* {@code true} if the interface represents a temp-table DMO, {@code false} if it represents
* a permanent table.
*
* @return Initialized {@code ClassWriter}.
*/
public ClassWriter createTopIfaceClassWriter(String ifaceName, boolean temporary)
{
ClassWriter cw = createClassWriter();
String innerIfaceName = ifaceName + BUF_SUFFIX;
String superIface = temporary ? TYPE_TEMPORARY : TYPE_DATA_MODEL_OBJECT;
cw.visit(V1_7,
ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE,
ifaceName,
null,
TYPE_OBJECT,
new String[] { superIface });
// this is done both for the enclosing interface and when we visit the inner interface
cw.visitInnerClass(innerIfaceName,
ifaceName,
BUF,
ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
return cw;
}
/**
* Create and initialize a {@code ClassWriter} for a static, inner interface named
* {@code Buf}, which extends {@code com.goldencode.p2j.persist.Buffer} (or
* {@code TempTableBuffer} for temp tables) and the enclosing, top-level interface.
*
* @param outerName
* Fully qualified, internal type name of enclosing interface.
* @param tempTable
* Flags the temp tables. The temp tables extend {@code TempTableBuffer}.
*
* @return Initialized {@code ClassWriter}.
*/
public ClassWriter createInnerIfaceClassWriter(String outerName, boolean tempTable)
{
ClassWriter cw = createClassWriter();
String ifaceName = outerName + BUF_SUFFIX;
String[] extNames = new String[]
{
outerName,
tempTable ? TEMP_TABLE_BUFFER_IFACE : BUFFER_IFACE,
};
cw.visit(V1_7,
ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE,
ifaceName,
null,
TYPE_OBJECT,
extNames);
cw.visitInnerClass(ifaceName,
outerName,
BUF,
ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
return cw;
}
/**
* Create and initialize a {@code ClassWriter} for a top-level, DMO implementation
* class of the given type, which implements the given DMO interface. In addition, the
* class will implement:
* <ul>
* <li>{@code java.io.Serializable}</li>
* <li>{@code com.goldencode.p2j.persist.Persistable}</li>
* </ul>
*
* @param implName
* Fully qualified, internal type name of the DMO implementation class.
* @param ifaceName
* Fully qualified, internal type name of the DMO interface which the class must
* implement.
* @param innerNames
* Optional list of fully qualified, internal type names which the DMO class will
* enclose as static inner classes. May be {@code null} or an empty list.
*
* @return Initialized {@code ClassWriter}.
*/
public ClassWriter createTopClassWriter(String implName,
String ifaceName,
List<String> innerNames)
{
ClassWriter cw = createClassWriter();
// better use the KW_IMPLEMENTS list from AST instead of hard-coding this list here.
// the difficulty is that the KW_IMPLEMENTS children are not fully qualified
String[] ifaces = new String[]
{
TYPE_SERIALIZABLE,
TYPE_TEMPTABLERECORD,
ifaceName,
};
cw.visit(V1_7,
ACC_PUBLIC | ACC_SUPER,
implName,
null,
TYPE_OBJECT,
ifaces);
if (innerNames != null && innerNames.size() > 0)
{
StringBuilder buf = new StringBuilder(implName);
int len = implName.length();
for (String next : innerNames)
{
buf.setLength(len);
buf.append("$");
buf.append(next);
String innerName = buf.toString();
// this is done both for the enclosing class and when we visit the inner class
cw.visitInnerClass(innerName, implName, next, ACC_STATIC);
}
}
return cw;
}
/**
* Create and initialize a <code>ClassWriter</code> for an inner class.
*
* @param outerName
* Fully qualified, internal type name of enclosing class.
* @param shortInnerName
* Short name of inner class.
* @param fullInnerName
* Fully qualified, internal type name of inner class.
*
* @return Initialized <code>ClassWriter</code>.
*/
public ClassWriter createInnerClassWriter(String outerName,
String shortInnerName,
String fullInnerName)
{
ClassWriter cw = createClassWriter();
cw.visit(V1_7,
ACC_SUPER,
fullInnerName,
null,
TYPE_OBJECT,
new String[] { TYPE_SERIALIZABLE });
cw.visitInnerClass(fullInnerName, outerName, shortInnerName, ACC_STATIC);
return cw;
}
/**
* Implement the declaration of an abstract, public method with the given name and
* signature in a DMO interface.
*
* @param cw
* Class writer for the method's interface.
* @param name
* Method name.
* @param sig
* Method signature, using internal type descriptors for parameters and return
* type.
*
* @return The method visitor.
*/
public MethodVisitor declareMethod(ClassWriter cw, String name, String sig)
{
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, name, sig, null, null);
mv.visitEnd();
return mv;
}
/**
* Create a {@code MethodVisitor} for the purpose of defining the implementation of a
* public method. The caller will use this object to assemble the instructions of the
* method; typically, it will be passed to additional methods in this library to complete
* the implementation.
*
* @param cw
* Class writer for the method's class.
* @param name
* Method name.
* @param sig
* Method signature, using internal type descriptors for parameters and return
* type.
*
* @return The method visitor object.
*/
public MethodVisitor defineMethod(ClassWriter cw, String name, String sig)
{
return cw.visitMethod(ACC_PUBLIC, name, sig, null, null);
}
/**
* Create an initialized <code>FieldVisitor</code> for the purpose of declaring a DMO field
* as an instance variable. The initialization of the field itself is implemented in the
* DMO implementation class' (or inner class') default constructor. The field visitor is
* used by the caller to implement annotations.
*
* @param cw
* Class writer for the field's class.
* @param name
* Field name.
* @param type
* Field descriptor.
* @param isFinal
* <code>true</code> if the field is final, else <code>false</code>.
*
* @return The field visitor object.
*/
public FieldVisitor declareInstanceVariable(ClassWriter cw,
String name,
String type,
boolean isFinal)
{
int access = ACC_PRIVATE;
if (isFinal)
{
access |= ACC_FINAL;
}
return cw.visitField(access, name, type, null, null);
}
/**
* Create an {@code AnnotationVisitor} for the purpose of implementing a class-level Java
* annotation.
*
* @param cw
* Class writer for the enclosing class.
* @param name
* Unqualified annotation name.
*
* @return The annotation visitor object.
*/
public AnnotationVisitor createAnnotationVisitor(ClassWriter cw, String name)
{
String desc = makeAnnotationDescriptor(name);
return cw.visitAnnotation(desc, true);
}
/**
* Create an {@code AnnotationVisitor} for the purpose of implementing a nested Java
* annotation (an element in an array of annotations).
*
* @param av
* Annotation visitor for the enclosing annotation.
* @param name
* Unqualified annotation name.
*
* @return The annotation visitor object.
*/
public AnnotationVisitor createAnnotationVisitor(AnnotationVisitor av, String name)
{
String desc = makeAnnotationDescriptor(name);
return av.visitAnnotation(null, desc);
}
/**
* Create an {@code AnnotationVisitor} for the purpose of implementing a field-level Java
* annotation.
*
* @param fv
* Field visitor for the associated field.
* @param name
* Unqualified annotation name.
*
* @return The annotation visitor object.
*/
public AnnotationVisitor createAnnotationVisitor(FieldVisitor fv, String name)
{
String desc = makeAnnotationDescriptor(name);
return fv.visitAnnotation(desc, true);
}
/**
* Create an {@code AnnotationVisitor} for the purpose of implementing a method-level Java
* annotation.
*
* @param mv
* Field visitor for the associated field.
* @param name
* Unqualified annotation name.
*
* @return The annotation visitor object.
*/
public AnnotationVisitor createAnnotationVisitor(MethodVisitor mv, String name)
{
String desc = makeAnnotationDescriptor(name);
return mv.visitAnnotation(desc, true);
}
/**
* Assemble the bytecode instructions common to the beginning of a top-level DMO's default
* constructor and an inner class' default constructor. These instructions invoke the
* default constructor of the superclass, {@code java.lang.Object}.
*
* @param mv
* The default constructor's method visitor.
*/
public void defaultCtorCommon(MethodVisitor mv)
{
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// invoke the superclass' default constructor
mv.visitMethodInsn(INVOKESPECIAL, TYPE_OBJECT, METH_INIT, SIG_DEF_INIT, false);
}
/**
* Assemble the bytecode instructions to initialize a DMO field to {@code null} in a
* default constructor. This is used for the DMO's primary key and multiplex ID fields.
*
* @param mv
* The default constructor's method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param fieldName
* Field name.
* @param fieldDesc
* Field descriptor.
*/
public void initDirectFieldNull(MethodVisitor mv,
String classType,
String fieldName,
String fieldDesc)
{
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// push null onto stack
mv.visitInsn(ACONST_NULL);
// put null into field
mv.visitFieldInsn(PUTFIELD, classType, fieldName, fieldDesc);
}
/**
* Assemble the bytecode instructions to initialize a DMO field using a constructor
* specific to the field's type. The instructions are added to a DMO's default constructor.
* The field-level constructor may or may not have parameters passed to it.
* <p>
* If the initializer provided in <code>ctorAst</code> cannot be converted to a value of
* the required type for the field, an error is logged and the field type's default
* constructor is used, which generally will result in the field being initialized to
* unknown value. Note that we do not support initializers for the following types at this
* time, and these always will be initialized using their default constructors:
* <ul>
* <li>handle</li>
* <li>memptr</li>
* <li>raw</li>
* </ul>
*
* @param mv
* The default constructor's method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param fieldName
* Field name.
* @param fieldDesc
* Field descriptor.
* @param ctorAst
* Java AST node representing the field-level constructor to be invoked to
* initialize the field.
*/
public void initSimpleField(MethodVisitor mv,
String classType,
String fieldName,
String fieldDesc,
Aast ctorAst)
{
String ctorOwner = AsmUtils.commonToInternalTypeName(ctorAst.getText());
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// instantiate the field object
mv.visitTypeInsn(NEW, ctorOwner);
// duplicate the object reference; the top reference is used to invoke the field-level
// c'tor, the bottom reference is used to put the object into the DMO field
mv.visitInsn(DUP);
String fieldCtorParms = null;
// handle field-level c'tor parameters, if any, by pushing them onto the stack with the
// appropriate instruction(s)
if (!ctorAst.isLeaf())
{
String initText = ctorAst.getFirstChild().getText();
try
{
switch (ctorOwner)
{
case TYPE_CHARACTER:
case TYPE_LONGCHAR:
case TYPE_ROWID:
{
fieldCtorParms = DESC_JAVA_STRING;
mv.visitLdcInsn(initText);
break;
}
case TYPE_DECIMAL:
{
Aast ast = (Aast) ctorAst.getFirstChild();
String numString = initText;
switch (ast.getType())
{
case NUM_LITERAL:
{
integer i = new integer(numString);
AsmUtils.pushInt(mv, i.intValue());
fieldCtorParms = "I";
break;
}
case DEC_LITERAL:
{
decimal d = new decimal(numString);
mv.visitLdcInsn(d.toStringExport());
fieldCtorParms = DESC_JAVA_STRING;
break;
}
case NULL_LITERAL:
{
// push null onto stack
mv.visitInsn(ACONST_NULL);
fieldCtorParms = DESC_JAVA_STRING;
}
default:
break;
}
ast = (Aast) ast.getNextSibling();
if (ast != null)
{
String scaleString = ast.getText();
integer scale = new integer(scaleString);
AsmUtils.pushInt(mv, scale.intValue());
fieldCtorParms += "I";
}
break;
}
case TYPE_INT64:
{
int64 i = new int64(initText);
mv.visitLdcInsn(i.longValue());
fieldCtorParms = "J";
break;
}
case TYPE_ARRAYLIST:
case TYPE_INTEGER:
case TYPE_RECID:
{
fieldCtorParms = "I";
integer i = new integer(initText);
AsmUtils.pushInt(mv, i.intValue());
break;
}
case TYPE_LOGICAL:
{
fieldCtorParms = "Z";
if (ctorAst.getFirstChild().getType() == JavaTokenTypes.BOOL_TRUE)
{
mv.visitInsn(ICONST_1);
}
else
{
mv.visitInsn(ICONST_0);
}
break;
}
// TODO: implement? How is initializer specified for these?
case TYPE_HANDLE:
case TYPE_MEMPTR:
case TYPE_RAW:
default:
// TODO: represents missing functionality; note this is caught immediately
// below
throw new ErrorConditionException(
"Unrecognized field initializer type: " + ctorOwner);
}
}
catch (ErrorConditionException exc)
{
String msg =
"Unable to initialize field %s (%s) in dynamic DMO class %s to '%s'; " +
"field will be initialized to unknown value instead";
msg = String.format(msg, fieldName, fieldDesc, classType, initText);
LOG.log(Level.SEVERE, msg, exc);
}
}
// compose the field-level c'tor's signature
String fieldCtorSig;
if (fieldCtorParms != null)
{
fieldCtorSig = "(" + fieldCtorParms + ")V";
}
else
{
fieldCtorSig = SIG_DEF_INIT;
}
// invoke the field's c'tor
mv.visitMethodInsn(INVOKESPECIAL, ctorOwner, METH_INIT, fieldCtorSig, false);
// put the initialized object into the DMO field
mv.visitFieldInsn(PUTFIELD, classType, fieldName, fieldDesc);
}
/**
* Assemble the bytecode instructions to initialize a DMO field using an static method
* implementation of a Progress-compatible builtin function (e.g., <code>date.today</code>).
* The instructions are added to a DMO's default constructor. Assumes that the static method
* either accepts no arguments, or a single argument of type <code>java.lang.String</code>.
*
* @param mv
* The default constructor's method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param fieldName
* Field name.
* @param fieldType
* Fully qualified, internal type name of field.
* @param fieldDesc
* Field descriptor.
* @param methName
* Static method name.
* @param initText
* String to be passed to the static method; <code>null</code> if the method takes
* no arguments.
*/
public void initFieldBuiltin(MethodVisitor mv,
String classType,
String fieldName,
String fieldType,
String fieldDesc,
String methName,
String initText)
{
// create builtin's static method signature from initial text (if any) and field
// description
StringBuilder buf = new StringBuilder("(");
if (initText != null)
{
buf.append(DESC_JAVA_STRING);
}
buf.append(")");
buf.append(fieldDesc);
String methSig = buf.toString();
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
if (initText != null)
{
// push method argument string onto stack
mv.visitLdcInsn(initText);
}
// invoke the static method
mv.visitMethodInsn(INVOKESTATIC, fieldType, methName, methSig, false);
// put the initialized object into the DMO field
mv.visitFieldInsn(PUTFIELD, classType, fieldName, fieldDesc);
}
/**
* Assemble the bytecode instructions to initialize a list of composite, inner class
* objects. The loop is executed <code>extent</code> times. In each pass, the default
* constructor of the inner class is invoked, and the resulting object is added to the
* list.
*
* @param mv
* The default constructor's method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param fieldName
* Composite list field name.
* @param compType
* Fully qualified internal type name of composite inner class.
* @param extent
* List size.
*/
public void initCompositeLoop(MethodVisitor mv,
String classType,
String fieldName,
String compType,
int extent)
{
// push 0 onto stack
mv.visitInsn(ICONST_0);
// store 0 into local variable 1, which is the for loop counter
mv.visitVarInsn(ISTORE, 1);
// label the top of the loop
Label top = new Label();
mv.visitLabel(top);
// load the current loop counter value from local variable 1
mv.visitVarInsn(ILOAD, 1);
// push list size (representing loop count limit) onto stack
AsmUtils.pushInt(mv, extent);
// create a label for the bottom of the loop
Label bottom = new Label();
// compare the loop counter with the limit; if the former is >= the latter, jump to the
// bottom of the loop
mv.visitJumpInsn(IF_ICMPGE, bottom);
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// get the list reference from its instance variable
mv.visitFieldInsn(GETFIELD, classType, fieldName, DESC_LIST);
// instantiate a new composite (inner class) object
mv.visitTypeInsn(NEW, compType);
// duplicate the reference; top is used for the composite c'tor, bottom is used to add
// it to the list of composite objects
mv.visitInsn(DUP);
// invoke the composite class' c'tor
mv.visitMethodInsn(INVOKESPECIAL, compType, METH_INIT, SIG_DEF_INIT, false);
// add the composite object to the list
mv.visitMethodInsn(INVOKEINTERFACE, TYPE_LIST, METH_ADD, SIG_ADD, true);
// ignore the return value from the list's add method
mv.visitInsn(POP);
// increment the loop counter in local variable 1 by 1
mv.visitIincInsn(1, 1);
// jump back to the top of the loop
mv.visitJumpInsn(GOTO, top);
// mark this location as the destination for IF_ICMPGE jump above
mv.visitLabel(bottom);
}
/**
* Assemble the bytecode instructions for the DMO constructor variant which accepts a
* multiplex ID. The implementation invokes the default constructor and stores the
* parameter into the <code>_multiplex</code> field.
*
* @param mv
* The constructor's method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
*/
public void multiplexCtor(MethodVisitor mv, String classType)
{
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// invoke the DMO's default c'tor
mv.visitMethodInsn(INVOKESPECIAL, classType, METH_INIT, SIG_DEF_INIT, false);
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// load the multiplex ID passed to this c'tor from local variable 1
mv.visitVarInsn(ALOAD, 1);
// put the multiplex ID into its DMO field
mv.visitFieldInsn(PUTFIELD, classType, FLD_MULTIPLEX, DESC_JAVA_INTEGER);
// void return
mv.visitInsn(RETURN);
}
/**
* Assemble the bytecode instructions for the DMO <code>deepCopy</code> method, which
* instantiates a new DMO of this class' type, assigns the current DMO's data to it, and
* returns it. The work of copying the data is delegated to the DMO's <code>assign</code>
* method.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
*/
public void deepCopy(MethodVisitor mv, String classType)
{
// instantiate a new DMO
mv.visitTypeInsn(NEW, classType);
// duplicate the DMO object reference on the stack
mv.visitInsn(DUP);
// call the DMO's default constructor to initialize it
mv.visitMethodInsn(INVOKESPECIAL, classType, METH_INIT, SIG_DEF_INIT, false);
// store the DMO reference in local variable 1
mv.visitVarInsn(ASTORE, 1);
// load the DMO reference from local variable 1 to push it back onto the stack
mv.visitVarInsn(ALOAD, 1);
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// call the DMO's assign method, passing in this object as the argument
mv.visitMethodInsn(INVOKEINTERFACE, TYPE_UNDOABLE, METH_ASSIGN, SIG_DMO_ASSIGN, true);
// load the new DMO back onto the stack
mv.visitVarInsn(ALOAD, 1);
// return the new DMO
mv.visitInsn(ARETURN);
}
/**
* Assemble the initial bytecode instructions for the DMO {@code assign} method.
* These instructions load the {@code Undoable} object and cast it to the current
* DMO's type. The remainder of the method's implementation varies according to the fields
* in the DMO. The full implementation consists of the following flow:
* <ul>
* <li>one call to {@code #assign(MethodVisitor, String)}</li>
* <li>one or more calls to {@link #assignField(MethodVisitor, String, String, String,
* String, boolean)}</li>
* <li>zero or more calls to {@link #assignLoopBegin(MethodVisitor, String, int)}
* <ul>
* <li>one or more calls to {@link #assignField(MethodVisitor, String, String,
* String, String, boolean)}</li>
* </ul>
* </li>
* <li>one call to {@link #voidReturn(MethodVisitor)}</li>
* </ul>
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
*/
public void assign(MethodVisitor mv, String classType)
{
// load method parameter Undoable object reference into local variable 1
mv.visitVarInsn(ALOAD, 1);
// cast to enclosing class
mv.visitTypeInsn(CHECKCAST, classType);
// store object reference from top of stack into local variable 2
mv.visitVarInsn(ASTORE, 2);
}
/**
* Assemble the bytecode instructions to assign a single field or element of a composite
* field from a DMO instance passed as an argument to the DMO <code>assign</code> method
* (hereafter, "source" DMO) into the current DMO (hereafter, "target" DMO) object's
* matching field.
* <p>
* For scalar fields, this is accomplished by invoking the target DMO's setter field and
* passing in the source DMO's corresponding instance member.
* <p>
* For composite fields, this is accomplished by invoking the source DMO's getter method
* for the desired field and storing the result in the target DMO by invoking its matching
* setter method.
* <p>
* The setter instructions will differ slightly if the field is part of a composite inner
* class, in that an index parameter must be handled for the method call.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param fieldType
* Fully qualified, internal type name of field.
* @param getterOrFieldName
* Name of getter method to be invoked on source DMO.
* @param setterMethName
* Name of setter method to be invoked on target DMO.
* @param inLoop
* <code>true</code> if the assignment takes place within the context of an assign
* loop (for a composite field), else <code>false</code>.
*
* @see #assign(MethodVisitor, String)
*/
public void assignField(MethodVisitor mv,
String classType,
String fieldType,
String getterOrFieldName,
String setterMethName,
boolean inLoop)
{
StringBuilder buf = new StringBuilder();
// create the setter method signature
buf.append(inLoop ? "(IL" : "(L");
String parmType = setterTypes.get(fieldType);
if (parmType == null)
{
parmType = fieldType;
}
buf.append(parmType);
buf.append(";)V");
String setterSig = buf.toString();
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// load index of getter from local variable 3 (loop index) if in loop
if (inLoop)
{
mv.visitVarInsn(ILOAD, 3);
}
// load DMO from which we are assigning data from local variable 2
mv.visitVarInsn(ALOAD, 2);
buf.setLength(0);
if (inLoop)
{
// load index of getter from local variable 3 (loop index) if in loop
mv.visitVarInsn(ILOAD, 3);
// create the getter method signature
buf.append("(I)L");
buf.append(fieldType);
buf.append(";");
String getterSig = buf.toString();
// call source DMO's getter method for target field, pushing the result onto stack
mv.visitMethodInsn(INVOKEVIRTUAL, classType, getterOrFieldName, getterSig, false);
}
else
{
// create the field type descriptor
buf.append("L");
buf.append(fieldType);
buf.append(";");
String fieldDesc = buf.toString();
// get the source field
mv.visitFieldInsn(GETFIELD, classType, getterOrFieldName, fieldDesc);
}
// call target (this) DMO's setter method for target field, passing value from stack
mv.visitMethodInsn(INVOKEVIRTUAL, classType, setterMethName, setterSig, false);
}
/**
* Assemble the bytecode instructions for the top of a loop in the DMO <code>assign</code>
* method, which is used to assign one or more composite fields. The actual assignment is
* assembled in {@link #assignField(MethodVisitor, String, String, String, String, boolean)}
* and the bottom of the loop is assembled in {@link #assignLoopEnd(MethodVisitor, Object)}.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param extent
* List size.
*
* @return A size 2 array of ASM <code>Label</code> objects, where the first element marks
* the top of the loop and the second the bottom. This is returned as type
* <code>Object</code> to enable TRPL to handle it, since TRPL cannot deal with
* arrays. A <code>List</code> was considered, but an array was deemed to have
* lower overhead.
*/
public Object assignLoopBegin(MethodVisitor mv, String classType, int extent)
{
// push 0 onto the stack
mv.visitInsn(ICONST_0);
// store 0 into local variable 3, which is the for loop counter
mv.visitVarInsn(ISTORE, 3);
// label the top of the loop
Label top = new Label();
mv.visitLabel(top);
// load the current loop counter value from local variable 3
mv.visitVarInsn(ILOAD, 3);
// push list size (representing loop count limit) onto stack
AsmUtils.pushInt(mv, extent);
// create a label for the bottom of the loop
Label bottom = new Label();
// compare the loop counter with the limit; if the former is >= the latter, jump to the
// bottom of the loop
mv.visitJumpInsn(IF_ICMPGE, bottom);
// store the labels into a small array to be returned to the caller, these will be
// needed by assignLoopEnd to mark the loop bottom and implementing the GOTO to jump
// to the top of the loop
return new Label[] { top, bottom };
}
/**
* Assemble the bytecode instructions for the bottom of a loop in the DMO
* <code>assign</code> method, which is used to assign one or more composite fields.
*
* @param mv
* The method visitor.
* @param labelArray
* A size 2 array of ASM <code>Label</code> objects, where the first element marks
* the top of the loop and the second the bottom.
*
* @see #assign(MethodVisitor, String)
* @see #assignLoopBegin(MethodVisitor, String, int)
*/
public void assignLoopEnd(MethodVisitor mv, Object labelArray)
{
Label[] labels = (Label[]) labelArray;
Label top = labels[0];
Label bottom = labels[1];
// increment the loop counter in local variable 3 by 1
mv.visitIincInsn(3, 1);
// jump back to the top of the loop
mv.visitJumpInsn(GOTO, top);
// mark this location as the destination for IF_ICMPGE jump in assignLoopBegin
mv.visitLabel(bottom);
}
/**
* Assemble the bytecode instruction for a void return from a method.
*
* @param mv
* The method visitor.
*/
public void voidReturn(MethodVisitor mv)
{
// void return
mv.visitInsn(RETURN);
}
/**
* Assemble the bytecode instructions for a DMO method which retrieves and returns the
* object in the specified field. This implementation is used for fields whose values are
* immutable, such as the primary key and multiplex ID.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param fieldName
* Field name.
* @param fieldDesc
* Field descriptor.
*/
public void directGetter(MethodVisitor mv,
String classType,
String fieldName,
String fieldDesc)
{
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// push the object reference within the specified field onto the stack
mv.visitFieldInsn(GETFIELD, classType, fieldName, fieldDesc);
// return object reference
mv.visitInsn(ARETURN);
}
/**
* Assemble the bytecode instructions for a DMO method which sets the value of the
* specified field to an object passed into the method. This implementation is used for
* fields whose values are immutable, such as the primary key and multiplex ID.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param fieldName
* Field name.
* @param fieldDesc
* Field descriptor.
*/
public void directSetter(MethodVisitor mv,
String classType,
String fieldName,
String fieldDesc)
{
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// load method parameter object reference into local variable 1
mv.visitVarInsn(ALOAD, 1);
// put local variable 1 value into field
mv.visitFieldInsn(PUTFIELD, classType, fieldName, fieldDesc);
// void return
mv.visitInsn(RETURN);
}
/**
* Assemble the bytecode instructions for a DMO method which retrieves and returns the
* value of the specified simple (i.e., non-indexed) field. This implementation is used for
* fields whose values are mutable, which are all of the converted table fields. The
* implementation invokes a copy constructor to make a copy of the field's data and returns
* the copy, rather than the original object reference. This prevents outside code from
* making any changes to the field's state, other than through the DMO's API.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param fieldName
* Field name.
* @param fieldType
* Fully qualified, internal type name of field.
* @param fieldDesc
* Field descriptor.
*/
public void simpleGetter(MethodVisitor mv,
String classType,
String fieldName,
String fieldType,
String fieldDesc)
{
// instantiate a new object of the field's type
mv.visitTypeInsn(NEW, fieldType);
// duplicate the new object's reference on the stack; the top reference will be consumed
// by the copy c'tor call below, the bottom will be returned from the getter method
mv.visitInsn(DUP);
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// push the object reference within the specified field onto the stack
mv.visitFieldInsn(GETFIELD, classType, fieldName, fieldDesc);
// special handling for copy c'tors which accept a superclass type
String adjFieldDesc = copyCtorTypes.get(fieldType);
if (adjFieldDesc == null)
{
adjFieldDesc = fieldDesc;
}
// invoke copy c'tor which accepts a single parameter of the same type as the field
String copyCtorSig = "(" + adjFieldDesc + ")V";
mv.visitMethodInsn(INVOKESPECIAL, fieldType, METH_INIT, copyCtorSig, false);
// return object reference
mv.visitInsn(ARETURN);
}
/**
* Assemble the bytecode instructions for a DMO method which sets the value of the specified
* simple (i.e., non-indexed) field. This implementation is used for fields whose values are
* mutable, which are all of the converted table fields. The implementation invokes the
* implementation of the <code>assign</code> method which is appropriate for the field's
* <code>BaseDataType</code> subclass. This prevents outside code from making any changes to
* the field's state, other than through the DMO's API.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param fieldName
* Field name.
* @param fieldType
* Fully qualified, internal type name of field.
* @param fieldDesc
* Field descriptor.
*/
public void simpleSetter(MethodVisitor mv,
String classType,
String fieldName,
String fieldType,
String fieldDesc)
{
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// push the object reference within the specified field onto the stack
mv.visitFieldInsn(GETFIELD, classType, fieldName, fieldDesc);
// load method parameter object reference into local variable 1
mv.visitVarInsn(ALOAD, 1);
// invoke BDT.assign method
String assignSig = assignMethSigs.get(fieldType);
mv.visitMethodInsn(INVOKEVIRTUAL, fieldType, METH_ASSIGN, assignSig, false);
// void return
mv.visitInsn(RETURN);
}
/**
* Assemble the bytecode instructions for a composite getter method, which accesses a
* composite object from an array list field, invokes a simple getter method on it, and
* returns the result.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param compFieldName
* Short name of field in enclosing class which contains the array list of
* composites we need to access.
* @param compClassName
* Short name of composite inner class.
* @param compMethName
* Name of composite's simple getter method.
* @param returnDesc
* Type descriptor for the return type of the field's getter method.
*/
public void compositeGetter(MethodVisitor mv,
String classType,
String compFieldName,
String compClassName,
String compMethName,
String returnDesc)
{
// load composite element from its ArrayList and cast it to the appropriate type
String compClassType = loadComposite(mv, classType, compFieldName, compClassName);
// composite method takes no arguments and returns the same type as the method currently
// being implemented
String compMethSig = "()" + returnDesc;
// invoke composite method to retrieve target object
mv.visitMethodInsn(INVOKEVIRTUAL, compClassType, compMethName, compMethSig, false);
// return object reference
mv.visitInsn(ARETURN);
}
/**
* Assemble the bytecode instructions for a composite setter method, which accesses a
* composite object from an array list field, invokes a simple setter method on it.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param compFieldName
* Short name of field in enclosing class which contains the array list of
* composites we need to access.
* @param compClassName
* Short name of composite inner class.
* @param compMethName
* Name of composite's simple getter method.
* @param elementDesc
* Type descriptor for the <code>element</code> parameter passed to the field's
* setter method.
*/
public void compositeSetter(MethodVisitor mv,
String classType,
String compFieldName,
String compClassName,
String compMethName,
String elementDesc)
{
// load composite element from its ArrayList and cast it to the appropriate type
String compClassType = loadComposite(mv, classType, compFieldName, compClassName);
// composite method takes one argument (the element to be set) and returns void
String compMethSig = "(" + elementDesc + ")V";
// load second method parameter (the element to be set -- an object) int local variable 2
mv.visitVarInsn(ALOAD, 2);
// invoke composite method to set element
mv.visitMethodInsn(INVOKEVIRTUAL, compClassType, compMethName, compMethSig, false);
// void return
mv.visitInsn(RETURN);
}
/**
* Assemble the bytecode instructions for a getter method which returns all of the values
* of a composite field as an array. The implementation creates an array of the specified
* field type, populates it in a loop of calls to that field's indexed getter method, and
* returns the array.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param fieldType
* Fully qualified, internal type name of field.
* @param extent
* List size.
* @param idxGetterMethName
* Indexed getter method name.
*/
public void simpleGetterExtent(MethodVisitor mv,
String classType,
String fieldType,
int extent,
String idxGetterMethName)
{
// compose the signature for the indexed getter method
String idxGetterSig = "(I)L" + fieldType + ";";
// push the number of size of the return array onto the stack
AsmUtils.pushInt(mv, extent);
// instantiate a new array to hold the field values
mv.visitTypeInsn(ANEWARRAY, fieldType);
// store the array reference in local variable 1
mv.visitVarInsn(ASTORE, 1);
// push the initial value of the loop counter onto the stack
mv.visitInsn(ICONST_0);
// store the value of the loop counter in local variable 2
mv.visitVarInsn(ISTORE, 2);
// create a label for the top of the loop
Label top = new Label();
mv.visitLabel(top);
// load the current value of the loop counter
mv.visitVarInsn(ILOAD, 2);
// push the loop limit onto the stack
AsmUtils.pushInt(mv, extent);
// create a label for the bottom of the loop
Label bottom = new Label();
// compare the loop counter with the limit; if the former is >= the latter, jump to the
// bottom of the loop
mv.visitJumpInsn(IF_ICMPGE, bottom);
// load the array reference from local variable 1
mv.visitVarInsn(ALOAD, 1);
// load the loop counter (now used as the array index) from local variable 2
mv.visitVarInsn(ILOAD, 2);
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// load the loop counter (now used as the indexed getter method index argument) from
// local variable 2
mv.visitVarInsn(ILOAD, 2);
// invoke the indexed getter method
mv.visitMethodInsn(INVOKEVIRTUAL, classType, idxGetterMethName, idxGetterSig, false);
// store the returned value in the array at the current index
mv.visitInsn(AASTORE);
// increment the loop counter in local variable 2
mv.visitIincInsn(2, 1);
// jump to the top of the loop
mv.visitJumpInsn(GOTO, top);
// mark this location as the destination for IF_ICMPGE jump instruction above
mv.visitLabel(bottom);
// load the array reference from local variable 1
mv.visitVarInsn(ALOAD, 1);
// return array object reference
mv.visitInsn(ARETURN);
}
/**
* Assemble the bytecode instructions for an indexed getter method which accepts a
* <code>NumberType</code> parameter for the index value. The implementation extracts the
* primitive <code>int</code> value from the <code>NumberType</code> parameter and delegates
* the main work to the associated getter method which accepts an <code>int</code> for the
* index.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param delMethName
* Name of getter method to which to delegate.
* @param returnDesc
* Return type descriptor.
*/
public void delegatingIndexedGetter(MethodVisitor mv,
String classType,
String delMethName,
String returnDesc)
{
// composite getter method takes a primitive int argument and returns the same type as
// the method currently being implemented
String delMethSig = "(I)" + returnDesc;
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// load index parameter (index as NumberType) and extract int value from it
loadIndexParameter(mv);
// invoke delegate method (composite getter) to retrieve value
mv.visitMethodInsn(INVOKEVIRTUAL, classType, delMethName, delMethSig, false);
// return object reference
mv.visitInsn(ARETURN);
}
/**
* Assemble the bytecode instructions for an indexed setter method which accepts a
* <code>NumberType</code> parameter for the index value. The implementation extracts the
* primitive <code>int</code> value from the <code>NumberType</code> parameter and delegates
* the main work to the associated getter method which accepts an <code>int</code> for the
* index.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param delMethName
* Name of setter method to which to delegate.
* @param elementDesc
* Element type descriptor.
*/
public void delegatingIndexedSetter(MethodVisitor mv,
String classType,
String delMethName,
String elementDesc)
{
// composite setter method takes two arguments (the int index and the element to be set)
// and returns void
String delMethSig = "(I" + elementDesc + ")V";
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// load index parameter (index as NumberType) and extract int value from it
loadIndexParameter(mv);
// load method parameter object reference (element to be set) into local variable 2
mv.visitVarInsn(ALOAD, 2);
// invoke delegate method (composite setter) to set element value
mv.visitMethodInsn(INVOKEVIRTUAL, classType, delMethName, delMethSig, false);
// void return
mv.visitInsn(RETURN);
}
/**
* Assemble the bytecode instructions for a composite field setter method which accepts a
* scalar value and assigns all elements of the composite field to that value. The
* implementation invokes the appropriate indexed setter method in a loop which executes
* a number of times equal to the size of the composite list.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param fieldType
* Fully qualified, internal type name of field.
* @param compName
* Field name which holds the list of composite elements.
* @param idxSetterMethName
* Indexed setter method name.
*/
public void indexedSetterFromScalar(MethodVisitor mv,
String classType,
String fieldType,
String compName,
String idxSetterMethName)
{
String idxSetterSig = makeIndexedSetterSignature(fieldType);
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// load the composite array list from its field
mv.visitFieldInsn(GETFIELD, classType, compName, DESC_ARRAYLIST);
// get the list size
mv.visitMethodInsn(INVOKEINTERFACE, TYPE_LIST, METH_SIZE, SIG_SIZE, true);
// store the list size into local variable 2
mv.visitVarInsn(ISTORE, 2);
// push the initial value of the loop counter onto the stack
mv.visitInsn(ICONST_0);
// store the value of the loop counter in local variable 3
mv.visitVarInsn(ISTORE, 3);
// create a lable for the top of the loop
Label top = new Label();
mv.visitLabel(top);
// load the current value of the loop counter
mv.visitVarInsn(ILOAD, 3);
// load the list size for use as the loop counter limit
mv.visitVarInsn(ILOAD, 2);
// create a label for the bottom of the loop
Label bottom = new Label();
// compare the loop counter with the limit; if the former is >= the latter, jump to the
// bottom of the loop
mv.visitJumpInsn(IF_ICMPGE, bottom);
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// load the current value of the loop counter (for use as the indexed setter method's
// index argument)
mv.visitVarInsn(ILOAD, 3);
// load the scalar value passed into the method from local variable 1
mv.visitVarInsn(ALOAD, 1);
// invoke the indexed setter method
mv.visitMethodInsn(INVOKEVIRTUAL, classType, idxSetterMethName, idxSetterSig, false);
// increment the loop counter in local variable 3 by 1
mv.visitIincInsn(3, 1);
// jump to the top of the loop
mv.visitJumpInsn(GOTO, top);
// mark this location as the bottom of the loop
mv.visitLabel(bottom);
// void return
mv.visitInsn(RETURN);
}
/**
* Assemble the bytecode instructions for a composite field setter method which accepts an
* array of values and assigns the elements of the composite field to the values in the
* array on a best-efforts basis. The implementation invokes the appropriate indexed setter
* method in a loop which executes a number of times equal to the smaller of the size of the
* array and the size of the composite list.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param fieldType
* Fully qualified, internal type name of field.
* @param compName
* Field name which holds the list of composite elements.
* @param idxSetterMethName
* Indexed setter method name.
*/
public void indexedSetterFromArray(MethodVisitor mv,
String classType,
String fieldType,
String compName,
String idxSetterMethName)
{
String idxSetterSig = makeIndexedSetterSignature(fieldType);
// load the array passed in as a parameter from local variable 1
mv.visitVarInsn(ALOAD, 1);
// push the array's length onto the stack
mv.visitInsn(ARRAYLENGTH);
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// load the composite array list from its field
mv.visitFieldInsn(GETFIELD, classType, compName, DESC_LIST);
// invoke List.size to push the list's size onto the stack
mv.visitMethodInsn(INVOKEINTERFACE, TYPE_LIST, METH_SIZE, SIG_SIZE, true);
// invoke Math.min against the two values, pushing the smaller onto the stack
mv.visitMethodInsn(INVOKESTATIC, TYPE_MATH, METH_MIN, SIG_MIN, false);
// store the result of Math.min in local variable 2, which will serve as the loop counter
// limit
mv.visitVarInsn(ISTORE, 2);
// push the initial loop counter value onto the stack
mv.visitInsn(ICONST_0);
// store the loop counter in local variable 3
mv.visitVarInsn(ISTORE, 3);
// create a label for the top of the loop
Label top = new Label();
mv.visitLabel(top);
// load the loop counter from local variable 3
mv.visitVarInsn(ILOAD, 3);
// load the loop limit from local variable 2
mv.visitVarInsn(ILOAD, 2);
// create a label for the bottom of the loop
Label bottom = new Label();
// compare the loop counter with the limit; if the former is >= the latter, jump to the
// bottom of the loop
mv.visitJumpInsn(IF_ICMPGE, bottom);
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// load the loop counter from local variable 3 (to serve as the index argument for the
// indexed setter method)
mv.visitVarInsn(ILOAD, 3);
// load the array of values from local variable 1
mv.visitVarInsn(ALOAD, 1);
// load the loop counter from local variable 3 (to serve as the current array index)
mv.visitVarInsn(ILOAD, 3);
// load the value at the current index from the array
mv.visitInsn(AALOAD);
// invoke the indexed setter method
mv.visitMethodInsn(INVOKEVIRTUAL, classType, idxSetterMethName, idxSetterSig, false);
// increment the loop counter in local variable 3 by 1
mv.visitIincInsn(3, 1);
// jump to the top of the loop
mv.visitJumpInsn(GOTO, top);
// mark this location as the bottom of the loop
mv.visitLabel(bottom);
// void return
mv.visitInsn(RETURN);
}
/**
* Assemble the bytecode instructions for a method which returns the number of elements of
* a composite field as a P2J <code>integer</code> object.
*
* @param mv
* The method visitor.
* @param classType
* Fully qualified internal type name of enclosing class.
* @param compName
* Field name which holds the list of composite elements.
*/
public void sizer(MethodVisitor mv, String classType, String compName)
{
mv.visitTypeInsn(NEW, TYPE_INTEGER);
mv.visitInsn(DUP);
// load "this" from local variable 0
mv.visitVarInsn(ALOAD, 0);
// push the object reference of composite array list field onto the stack
mv.visitFieldInsn(GETFIELD, classType, compName, DESC_LIST);
// invoke size method on composite array list, pushing result onto stack
mv.visitMethodInsn(INVOKEINTERFACE, TYPE_LIST, METH_SIZE, SIG_SIZE, true);
// invoke integer constructor using size of array list as its argument
mv.visitMethodInsn(INVOKESPECIAL, TYPE_INTEGER, METH_INIT, SIG_INT_INIT, false);
// return object reference
mv.visitInsn(ARETURN);
}
}
}