DmoProxyPlugin.java
/*
** Module : DmoProxyPlugin.java
** Abstract : A ProxyAssemblerPlugin implementation for DMOs.
**
** Copyright (c) 2016-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 ECF 20160219 Created initial version. Simplifies and moves a significant amount of logic
** from the RecordBuffer invocation handler into a much more performant
** implementation in a DMO proxy.
** 002 EVL 20160325 Javadoc fixes.
** 003 ECF 20171025 Javadoc correction.
** 004 ECF 20171231 Performance improvement.
** 005 OM 20180614 Saved arrayBulk(S/G)etterMap s to PropertyHelper.
** 006 CA 20190812 Made analyze() package-private - is required to allow TempTableBuilder to
** initialize PropertyHelper, if a certain legacy table has not been loaded in
** FWD yet, but is referenced by its name.
** 007 OM 20191115 New ORM implementation.
** 008 OM 20201001 Improved DMO manipulation performance by caching slow Property annotation access.
** 009 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 010 AD 20240321 Modified class to permit creation and use of this plugin at conversion/compilation step.
** 011 ICP 20240715 Modified analyze() to also search methods implemented in the hierarchy.
*/
/*
** 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;
import java.lang.reflect.*;
import java.util.*;
import java.util.logging.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.asm.AsmUtils;
import com.goldencode.p2j.schema.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.proxy.*;
import org.objectweb.asm.*;
import org.apache.commons.lang3.tuple.Pair;
/**
* A proxy assembler plugin which manages the assembly of indexed DMO getter/setter methods,
* for normalized and denormalized implementations. The proxy assembler is allowed to perform
* its default assembly of all simple, scalar getter and setter methods. This plugin handles the
* full assembly of the following method types (where "foo" is an example property method):
* <pre>
* T<? extends BaseDataType> getFoo(NumberType index)
* T<? extends BaseDataType>[] getFoo()
* void setFoo(NumberType index, T<? extendsBaseDataType> foo)
* void setFoo(T<? extendsBaseDataType> foo)
* void setFoo(T<? extendsBaseDataType>[] foo)
* </pre>
* <p>
* Note that the supported return and parameter types are concrete subclasses of
* <code>BaseDataType</code>, not generics; the generics above are meant only to express that the
* data types supported are limited to <code>BaseDataType</code> sub-types.
* <p>
* In addition, methods with the following signatures are handled specially by this plugin:
* <pre>
* T<? extends BaseDataType> getFoo(int index)
* void setFoo(int index, T<? extendsBaseDataType> foo)
* </pre>
* <p>
* For extent fields converted to a normalized form, the above two method types are overridden
* by this plugin. For the most part, the implementation is provided by the main proxy assembler.
* However, it is augmented with a range check, which is applied before the normal delegation to
* an invocation handler.
* <p>
* For the denormalized form of these two method types, the plugin implementation applies the
* range check, then performs the bytecode equivalent of a switch statement to delegate an indexed
* invocation to the appropriate, matching scalar getter or setter method. The actual scalar
* method invoked is determined by the subscript value passed into the indexed method. The scalar
* method then delegates to the invocation handler as usual.
* <p>
* As a result of these specialized method implementations, the only DMO getter and setter method
* signatures that ever are passed to the associated invocation handler for scalar fields and
* extent fields converted to a denormalized form are simple, scalar variants (simple bean
* methods). For extent fields converted to a normalized form, the additional two, indexed methods
* above are passed in as well. This allows the invocation handler to remain simpler and avoid
* expensive runtime lookups to distinguish the less commonly invoked forms of the legacy-support
* methods (i.e., the first five variants listed above). It also ensures that any subscripts have
* been checked for unknown value (for {@code NumberType} index values) and for range
* validity before the invocation handler is invoked. This avoids the runtime cost of determining
* extents for subscript validity checks in the invocation handler, since these checks are now
* compiled into the proxies themselves.
*
* @author ecf
*/
public final class DmoProxyPlugin
implements ProxyAssemblerPlugin,
DmoAsmTypes,
Opcodes
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(DmoProxyPlugin.class.getName());
// /** Prefix for name of composite inner class containing normalized extent field properties */
// private static final String COMPOSITE = "Composite";
/** The DMO interface. */
private final Class<? extends DataModelObject> dmoIface;
/** DMO interface which declares POJO bean, <code>Buffer</code> and legacy support methods */
private final Class<? extends DataModelObject /*& Buffer*/> targetInterface;
/** Methods for which this plugin implements proxies (instead of default proxy assembler) */
private Set<Method> methods = null;
/** Indexed getter/setter methods to proxy method assemblers */
private Map<Method, ProxyAssembler.ProxyMethod> methodAssemblerMap = null;
/** Indexed property names to extent sizes (does not include denormalized properties) */
private Map<String, Integer> extentMap = new HashMap<>();
/** Set of property names representing only legacy fields (not denormalized) */
private Set<String> legacyProps = new HashSet<>();
/** Property names to legacy getter methods (primitive int index for extent fields) */
private Map<String, Method> legacyGetterMap = new LinkedHashMap<>();
/** Property names to legacy setter methods (primitive int index for extent fields) */
private Map<String, Method> legacySetterMap = new LinkedHashMap<>();
/** Indexed property names to arrays of denormalized, custom extent property names */
private Map<String, String[]> denormPropMap = new LinkedHashMap<>();
/** Denormalized properties mapped to original properties and their index (1-base). */
private Map<String, Pair<String, Integer>> reverseDenormPropMap = new HashMap<>();
/** Indexed property names to corresponding extents */
private Map<String, Integer> normPropExtentMap = new LinkedHashMap<>();
/** Property names to POJO getter methods */
private Map<String, Method> pojoGetterMap = null;
/** Property names to all setter methods */
private Map<String, Method> pojoSetterMap = null;
/** Denormalized property names to indexed getter methods with a primitive integer index. */
private Map<String, Method> primGetterMap = new LinkedHashMap<>();
/** Denormalized property names to indexed setter methods with a primitive integer index. */
private Map<String, Method> primSetterMap = new LinkedHashMap<>();
/** Denormalized property names to indexed getter methods with a NumberType wrapper index */
private Map<String, Method> bdtGetterMap = new LinkedHashMap<>();
/** Denormalized property names to indexed setter methods with a NumberType wrapper index */
private Map<String, Method> bdtSetterMap = new LinkedHashMap<>();
/** Denormalized property names to getter methods which return an array of wrapper objects */
private Map<String, Method> arrayBulkGetterMap = new LinkedHashMap<>();
/** Denormalized property names to bulk setter methods which accept an array parameter */
private Map<String, Method> arrayBulkSetterMap = new LinkedHashMap<>();
/** Denormalized property names to bulk setter methods which accept a scalar parameter */
private Map<String, Method> scalarBulkSetterMap = new LinkedHashMap<>();
/** Indicates whether exceptions from {@link DmoMetadataManager} should be caught. */
private boolean catchDmoMetaException = false;
/**
* Create an instance of this plugin. An instance is created each time a DMO is proxied by
* the main proxy assembler. This instance should be short-lived and discarded once its work
* is done.
*
* @param dmoBufInterface
* DMO buffer interface.
* @param dmoInterface
* DMO interface.
*/
public DmoProxyPlugin(Class<?> dmoBufInterface,
Class<? extends DataModelObject> dmoInterface)
{
this.targetInterface = findRootProxyInterface(dmoBufInterface);
this.dmoIface = dmoInterface;
}
/**
* Create an instance of this plugin. An instance is created each time a DMO is proxied by
* the main proxy assembler. This instance should be short-lived and discarded once its work
* is done.
* It might encounter exceptions from the {@link DmoMetadataManager} when analyzing the fields, but be meant to
* catch them and continue execution.
*
* @param dmoBufInterface
* DMO buffer interface.
* @param dmoInterface
* DMO interface.
* @param catchDmoMetaException
* True if exception from the {@link DmoMetadataManager} should be caught and handled
* when analyzing the fields.
*/
public DmoProxyPlugin(Class<?> dmoBufInterface,
Class<? extends DataModelObject> dmoInterface,
boolean catchDmoMetaException)
{
this.catchDmoMetaException = catchDmoMetaException;
this.targetInterface = findRootProxyInterface(dmoBufInterface);
this.dmoIface = dmoInterface;
}
/**
* Find the ancestral root of inner interfaces which extend {@code Buffer}. This will be
* the one which declares any special methods declared for legacy business logic, and whose
* enclosing interface defines the POJO methods of the DMO.
* <p>
* Converted temp-table DMO interfaces may define such nested interfaces arbitrarily deeply,
* so we have to find the root in order to inspect the methods we need to proxy.
*
* @param iface
* DMO buffer interface to test.
*
* @return Ancestral, inner, DMO buffer interface, or {@code iface} if it represents the
* root.
*/
private static Class<? extends DataModelObject> findRootProxyInterface(Class<?> iface)
{
Class<?> next = iface;
for (Class<?> parent : next.getInterfaces())
{
if (!Buffer.class.equals(parent) && Buffer.class.isAssignableFrom(parent))
{
if (parent.getEnclosingClass() != null)
{
next = findRootProxyInterface(parent);
}
else
{
break;
}
}
}
if (next == null || !DataModelObject.class.isAssignableFrom(next))
{
return null; // maybe throw some exceptions (?)
}
return (Class<? extends DataModelObject>) next;
}
/**
* Assemble bytecode instructions to return an instance of a <code>BaseDataType</code>
* subclass of the given type, initialized to unknown value. While the call to
* <code>setUnknownValue()</code> may be redundant for most BDT sub-types (since a default
* constructor initializes to unknown value in most cases), it was decided to leave this
* invocation in place to make the operation explicit and to make the assembled code more
* resilient to possible future changes.
*
* @param mv
* Method visitor.
* @param typeName
* Internal form name of <code>BaseDataType</code> subclass which should be
* instantiated.
* @param localVar
* Zero-based index of the local variable slot which should be used for the unknown
* value object before the object is returned.
*/
private static void assembleReturnUnknown(MethodVisitor mv, String typeName, int localVar)
{
// instantiate an object of the method's return type (a BDT subclass)
mv.visitTypeInsn(NEW, typeName);
// duplicate the reference
mv.visitInsn(DUP);
// invoke the default c'tor
mv.visitMethodInsn(INVOKESPECIAL, typeName, METH_INIT, SIG_DEF_INIT, false);
// store the reference in a local variable
mv.visitVarInsn(ASTORE, localVar);
// load it back from the variable
mv.visitVarInsn(ALOAD, localVar);
// call setUnknown on the BDT instance
mv.visitMethodInsn(INVOKEVIRTUAL, typeName, METH_SETUNK, SIG_SETUNK, false);
// load it back from the variable
mv.visitVarInsn(ALOAD, localVar);
// return it
mv.visitInsn(ARETURN);
}
/**
* Report the set of methods which this plugin will implement. This set will not be
* implemented by the main proxy assembler. This method is invoked before the main proxy
* assembler begins assembling the proxy class.
* <p>
* Note that any method in the returned set will NOT be included in the array of methods the
* invocation handler will have access to at runtime. Therefore, the returned set should NOT
* include any method handled specifically by {@link #getProxyMethodAssembler(Method)}.
* {@link #getProxyMethodAssembler(Method)} will not be invoked for any of the methods in the
* returned set.
* <p>
* See the {@link DmoProxyPlugin class javadoc} for additional information on which DMO
* methods this plugin will implement.
*
* @return Set of methods which this plugin will implement.
*/
@Override
public Set<Method> getImplementedMethods()
{
if (methods == null)
{
analyze();
}
return methods;
}
/**
* Return an object which will assemble the given proxy method, or <code>null</code> if this
* plugin will allow the default proxy method implementation. If a non-<code>null</code>
* value is returned, the corresponding method should NOT be included in the set of methods
* reported by {@link #getImplementedMethods()}.
* <p>
* This method is invoked for each proxy method implemented by the main proxy assembler.
* <p>
* This plugin implementation will return a non-<code>null</code> value only for indexed
* getter and setter fields which accept a primitive <code>int</code> for the first argument
* (the subscript value), and only in the case the associated extent field was converted in
* normalized form. This is done because we need the default proxy implementation for this
* category of method (it is backed by an implementation in the DMO implementation class), but
* we also need to implement a range check on the subscript value ahead of that default
* behavior.
* <p>
* For all other methods, <code>null</code> is returned.
*
* @param method
* Method for which a proxy implementation is needed.
*
* @return A subclass of <code>ProxyAssembler.ProxyMethod</code> or <code>null</code> if the
* default proxy method implementation is suitable for the given method.
*
* @see #getImplementedMethods()
*/
@Override
public ProxyAssembler.ProxyMethod getProxyMethodAssembler(Method method)
{
return methodAssemblerMap == null ? null : methodAssemblerMap.get(method);
}
/**
* Entry point into this plugin's arbitrary method assembly process. This will be called after
* {@link #getImplementedMethods()} and after {@link #getProxyMethodAssembler(Method)} (i.e.,
* after the main proxy assembler has implemented its methods), but before
* <code>ClassWriter.visitEnd</code> is invoked to terminate the assembly process.
*
* @param classWriter
* ASM class writer which the plugin will use to do its work.
*/
@Override
public void assemble(ClassWriter classWriter)
{
if (methods == null)
{
analyze();
}
String targetIfaceType = AsmUtils.commonToInternalTypeName(targetInterface);
for (Map.Entry<String, Method> entry : primGetterMap.entrySet())
{
String property = entry.getKey();
Method method = entry.getValue();
assembleDenormPrimGetter(classWriter, targetIfaceType, property, method);
}
for (Map.Entry<String, Method> entry : primSetterMap.entrySet())
{
String property = entry.getKey();
Method method = entry.getValue();
assembleDenormPrimSetter(classWriter, targetIfaceType, property, method);
}
for (Map.Entry<String, Method> entry : bdtGetterMap.entrySet())
{
String property = entry.getKey();
Method method = entry.getValue();
assembleDenormBdtGetter(classWriter, targetIfaceType, property, method);
}
for (Map.Entry<String, Method> entry : bdtSetterMap.entrySet())
{
String property = entry.getKey();
Method method = entry.getValue();
assembleDenormBdtSetter(classWriter, targetIfaceType, property, method);
}
for (Map.Entry<String, Method> entry : arrayBulkGetterMap.entrySet())
{
String property = entry.getKey();
Method method = entry.getValue();
assembleArrayBulkGetter(classWriter, targetIfaceType, property, method);
}
for (Map.Entry<String, Method> entry : arrayBulkSetterMap.entrySet())
{
String property = entry.getKey();
Method method = entry.getValue();
assembleArrayBulkSetter(classWriter, targetIfaceType, property, method);
}
for (Map.Entry<String, Method> entry : scalarBulkSetterMap.entrySet())
{
String property = entry.getKey();
Method method = entry.getValue();
assembleScalarBulkSetter(classWriter, targetIfaceType, property, method);
}
}
/**
* Analyze the instance fields in the DMO implementation class associated with the DMO
* interface, and cross-reference this information with the interface's methods, for the
* purpose of assembling proxy implementations of these methods.
* <p>
* Update the {@link PropertyHelper}'s legacy getter and setter method caches for the DMO
* interface as an important side effect.
*/
void analyze()
{
methods = new HashSet<>();
analyzeFields(dmoIface);
// sanity
for (Map.Entry<String, String[]> entry : denormPropMap.entrySet())
{
for (String s : entry.getValue())
{
if (s == null)
{
String msg = "DMO class %s is missing a denormalized field for property %s";
throw new NullPointerException(
String.format(msg, targetInterface.getName(), entry.getKey()));
}
}
}
// analyze all methods declared specifically in the DMO interface and in the hierachy
ArrayList<Method> allMethods = new ArrayList<>(Arrays.asList(dmoIface.getDeclaredMethods()));
for (Class<?> parent : dmoIface.getInterfaces())
{
allMethods.addAll(Arrays.asList(parent.getDeclaredMethods()));
}
for (Method method : allMethods)
{
String property = getPropertyName(method);
if (property == null)
{
System.out.println("Unknown property for method " + method); // TODO: LOG it?
continue;
}
if (normPropExtentMap.containsKey(property) || denormPropMap.containsKey(property))
{
analyzeExtentPropertyMethod(method, property);
}
else if (legacyProps.contains(property) || reverseDenormPropMap.containsKey(property))
{
analyzeSimplePropertyMethod(method, property);
}
else
{
System.out.println("Unknown type of property " + property + " of method " + method); // TODO: LOG it?
}
}
// store all method PropertyHelper property mappings related to this buffer interface's
// enclosing class; we use the enclosing interface of the actual DMO buffer interface
// passed to the constructor, rather than the target buffer interface's enclosing interface,
// because they might be different for temp-table DMOs, and we have to ensure that any
// subsequent lookups for the buffer associated with this proxy don't fail
PropertyHelper.putExtentsByProperty(dmoIface, extentMap);
PropertyHelper.putLegacyGettersByProperty(dmoIface, legacyGetterMap);
PropertyHelper.putLegacySettersByProperty(dmoIface, legacySetterMap);
PropertyHelper.putLegacyBulkGettersByProperty(dmoIface, arrayBulkGetterMap);
PropertyHelper.putLegacyBulkSettersByProperty(dmoIface, arrayBulkSetterMap);
// scalarBulkSetterMap ??
// get maps of all the POJO getters and setters for the enclosing interface
pojoGetterMap = PropertyHelper.pojoGettersByProperty(dmoIface);
pojoSetterMap = PropertyHelper.pojoSettersByProperty(dmoIface);
// there is an additional classification of methods which will not be declared in the inner
// interface, but rather in the enclosing DMO interface: normalized, indexed getters and
// setters; we need to proxy these as well, in order to implement index range checking code
if (!normPropExtentMap.isEmpty())
{
for (Map.Entry<String, Integer> entry : normPropExtentMap.entrySet())
{
String property = entry.getKey();
Integer extent = entry.getValue();
if (methodAssemblerMap == null)
{
methodAssemblerMap = new HashMap<>();
}
Method method = legacyGetterMap.get(property);
if (method == null)
{
String msg = "No getter method found in DMO interface %s for property %s";
throw new NullPointerException(String.format(msg, dmoIface.getName(), property));
}
methodAssemblerMap.put(method, new IndexedGetterAssembler(extent));
method = legacySetterMap.get(property);
if (method == null)
{
String msg = "No setter method found in DMO interface %s for property %s";
throw new NullPointerException(String.format(msg, dmoIface.getName(), property));
}
methodAssemblerMap.put(method, new IndexedSetterAssembler(extent));
}
}
}
/**
* Analyze the instance fields of a DMO interface, or inner composite class, for
* the purpose of categorizing the fields, extracting annotation metadata, and later
* cross-referencing this information with DMO interface methods which require proxy
* implementations.
*
* @param cls
* DMO implementation class or inner composite class which contains instance fields.
*/
private void analyzeFields(Class<? extends DataModelObject> cls)
{
DmoMeta dmoInfo;
Iterator<Property> it;
try
{
dmoInfo = DmoMetadataManager.getDmoInfo(cls);
it = dmoInfo.getFields(true);
}
catch (IllegalArgumentException e)
{
if (catchDmoMetaException)
{
it = DmoMeta.getPropertiesList(cls, Temporary.class.isAssignableFrom(cls)).iterator();
}
else
{
throw e;
}
}
it.forEachRemaining(property ->
{
String propertyName = property.name;
int extent = property.extent;
if (extent == 0)
{
// legacy scalar field
legacyProps.add(propertyName);
}
else
{
int index = property.index;
if (index == 0)
{
// if index is 0, we are in an inner, composite class, visiting the (java)
// instance field which represents a normalized (4GL) extent field
normPropExtentMap.put(propertyName, extent);
extentMap.put(propertyName, extent);
// legacy extent field
legacyProps.add(propertyName);
}
else
{
// otherwise, we are visiting the (java) instance field in the top-level DMO
// class which represents a denormalized (4GL) extent field
// property name which would be have been used for normalized field
String baseProperty = property.original;
extentMap.put(baseProperty, extent);
// try to look up denormalized property info by base property name, create and map
// the array if it doesn't exist
String[] denormProps = denormPropMap.computeIfAbsent(baseProperty, k -> new String[extent]);
// set denormalized property at appropriate index in extent data list
denormProps[index - 1] = propertyName;
// update the reversed lookup map
reverseDenormPropMap.put(propertyName, Pair.of(baseProperty, index));
}
}
});
}
/**
* Analyze a method declared by the root DMO buffer interface, and categorize this method for
* further processing/assembly.
* <p>
* The <code>method</code> argument must conform to one of the following signatures, where
* "foo" is an example property name (note: access modifiers intentionally are omitted, and
* the generics here are intended only to express valid types; however, the actual method
* signature must not include generics):
* <pre>
* T<? extends BaseDataType> getFoo(int index)
* T<? extends BaseDataType> getFoo(NumberType index)
* T<? extends BaseDataType>[] getFoo()
* void setFoo(int index, T<? extendsBaseDataType> foo)
* void setFoo(NumberType index, T<? extendsBaseDataType> foo)
* void setFoo(T<? extendsBaseDataType> foo)
* void setFoo(T<? extendsBaseDataType>[] foo)
* </pre>
* <p>
* If any other signature is encountered, a warning is logged and this method returns
* immediately.
*
* @param method
* Method requiring analysis.
* @param property
* The property accessed by the method. Must not be {@code null}.
*/
private void analyzeExtentPropertyMethod(Method method, String property)
{
method.setAccessible(true);
Class<?> returnType = method.getReturnType();
Class<?>[] parmTypes = method.getParameterTypes();
int parms = parmTypes.length;
boolean isGetter = !returnType.equals(Void.TYPE);
if (isGetter) // try to match indexed getter method
{
if (parms == 1 && BaseDataType.class.isAssignableFrom(returnType))
{
Class<?> parmType = parmTypes[0];
if (Integer.TYPE.equals(parmType))
{
if (!denormPropMap.containsKey(property))
{
primGetterMap.put(property, method);
}
legacyGetterMap.put(property, method);
return;
}
else if (NumberType.class.equals(parmType))
{
bdtGetterMap.put(property, method);
methods.add(method);
return;
}
}
else if (returnType.isArray() && parms == 0) // try to match array getter method
{
Class<?> compType = returnType.getComponentType();
if (BaseDataType.class.isAssignableFrom(compType))
{
arrayBulkGetterMap.put(property, method);
methods.add(method);
return;
}
}
}
else
{
// try to match indexed setter method
if (parms == 2 && BaseDataType.class.isAssignableFrom(parmTypes[1]))
{
Class<?> indexType = parmTypes[0];
if (Integer.TYPE.equals(indexType))
{
if (!denormPropMap.containsKey(property))
{
primSetterMap.put(property, method);
}
legacySetterMap.put(property, method);
return;
}
else if (NumberType.class.equals(indexType))
{
bdtSetterMap.put(property, method);
methods.add(method);
return;
}
}
else if (parms == 1) // try to match a bulk setter method
{
if (parmTypes[0].isArray()) // try to match array bulk setter method
{
Class<?> compType = parmTypes[0].getComponentType();
if (BaseDataType.class.isAssignableFrom(compType))
{
arrayBulkSetterMap.put(property, method);
methods.add(method);
return;
}
}
else if (BaseDataType.class.isAssignableFrom(parmTypes[0])) // try to match scalar bulk setter method
{
scalarBulkSetterMap.put(property, method);
methods.add(method);
return;
}
}
}
// all expected methods have been matched by now
logUnexpectedMethod(method);
}
/**
* Analyze a method declared by the root DMO buffer interface's enclosing interface, and
* categorize this method for further processing/assembly.
* <p>
* The <code>method</code> argument must conform to one of the following signatures, where
* "foo" is an example property name (note: access modifiers intentionally are omitted, and
* the generics here are intended only to express valid types; however, the actual method
* signature must not include generics):
* <pre>
* T<? extends BaseDataType> getFoo()
* T<? extends BaseDataType> getFoo(int index)
* void setFoo(T<? extendsBaseDataType> foo)
* void setFoo(int index, T<? extendsBaseDataType> foo)
* </pre>
* <p>
* If any other signature is encountered, a warning is logged and this method returns
* immediately.
*
* @param method
* Method requiring analysis.
* @param property
* The property accessed by the method. Must not be {@code null}.
*/
private void analyzeSimplePropertyMethod(Method method, String property)
{
Class<?> returnType = method.getReturnType();
boolean isGetter = !returnType.equals(Void.TYPE);
Class<?>[] parmTypes = method.getParameterTypes();
int parms = parmTypes.length;
if (normPropExtentMap.containsKey(property))
{
if (isGetter)
{
if (parms == 1 && Integer.TYPE.equals(parmTypes[0]))
{
legacyGetterMap.put(property, method);
return;
}
}
else
{
if (parms == 2 && Integer.TYPE.equals(parmTypes[0]))
{
legacySetterMap.put(property, method);
return;
}
}
}
else if (isGetter)
{
if (parms == 0 && BaseDataType.class.isAssignableFrom(returnType))
{
legacyGetterMap.put(property, method);
return;
}
}
else if (parms == 1 && BaseDataType.class.isAssignableFrom(parmTypes[0]))
{
legacySetterMap.put(property, method);
return;
}
logUnexpectedMethod(method);
}
/**
* Extract the DMO property name from the given method, which is assumed to be a getter or
* setter method.
* <p>
* If the method does not follow the naming convention of a getter or setter method, log a
* warning and return <code>null</code>.
*
* @param method
* DMO getter/setter method.
*
* @return Property name, or <code>null</code> if method is not a getter or setter.
*/
private String getPropertyName(Method method)
{
try
{
return PropertyHelper.getPropertyName(method).intern();
}
catch (IllegalArgumentException exc)
{
// not a getter or setter
logUnexpectedMethod(method);
}
return null;
}
/**
* Assemble an indexed getter method which accepts a primitive <code>int</code> index and
* delegates to the appropriate, denormalized, simple getter method.
* <p>
* The source equivalent for an <code>integer</code> property <code>foo</code> with extent
* <code>N</code> and default denormalization naming would be:
* <pre>
* public integer getFoo(int index)
* {
* switch (index)
* {
* case 0:
* return getFoo1();
* case 1:
* return getFoo2();
* ...
* case N:
* return getFooN();
* }
*
* outOfBoundsError(index);
* integer unk = new integer();
* unk.setUnknown();
*
* return unk;
* }
* </pre>
*
* @param classWriter
* ASM class writer
* @param dmoBufIfaceType
* Internal type name of the interface containing the method being implemented and
* the methods it calls.
* @param baseProperty
* Base property name associated with the indexed setter.
* @param method
* Indexed setter method.
*/
private void assembleDenormPrimGetter(ClassWriter classWriter,
String dmoBufIfaceType,
String baseProperty,
Method method)
{
// look up the denormalized property names associated with this base property
String[] denormProps = denormPropMap.get(baseProperty);
int len = denormProps == null ? 0 : denormProps.length;
if (len == 0)
{
return;
}
// create method visitor
String baseDesc = ProxyUtils.makeMethodDescriptor(method);
String methName = method.getName();
MethodVisitor mv =
classWriter.visitMethod(ACC_PUBLIC,
methName,
baseDesc,
null,
ProxyUtils.getCaughtExceptionTypeNames(method));
// compose array of possible case values
int[] cases = new int[len];
for (int i = 0; i < len; i++)
{
cases[i] = i;
}
// compose array labels for lookupswitch instruction
Label[] switchLabels = new Label[len];
for (int i = 0; i < len; i++)
{
switchLabels[i] = new Label();
}
// label for lookupswitch instruction's default jump
Label defLabel = new Label();
// start bytecode
mv.visitCode();
// load index parameter
mv.visitVarInsn(ILOAD, 1);
// switch statement
mv.visitLookupSwitchInsn(defLabel, cases, switchLabels);
// switch cases
for (int i = 0; i < len; i++)
{
// get denormalized method to invoke
String property = denormProps[i];
Method denormMeth = pojoGetterMap.get(property);
String denormName = denormMeth.getName();
String denormDesc = ProxyUtils.makeMethodDescriptor(denormMeth);
// mark this as the jump location for case i
mv.visitLabel(switchLabels[i]);
// load this object reference
mv.visitVarInsn(ALOAD, 0);
// invoke the denormalized, simple, getter method
mv.visitMethodInsn(INVOKEINTERFACE, dmoBufIfaceType, denormName, denormDesc, true);
// return object reference from stack
mv.visitInsn(ARETURN);
}
// default case
mv.visitLabel(defLabel);
// load this object reference
mv.visitVarInsn(ALOAD, 0);
// load index parameter
mv.visitVarInsn(ILOAD, 1);
// invoke BufferImpl.outOfBoundsError method
mv.visitMethodInsn(INVOKEVIRTUAL, TYPE_BUFFERIMPL, METH_OOBE, SIG_OOBE, false);
// instantiate and return a wrapper of the appropriate type, set to unknown value
String typeName = AsmUtils.commonToInternalTypeName(method.getReturnType());
assembleReturnUnknown(mv, typeName, 2);
// parameters ignored
mv.visitMaxs(0, 0);
// end bytecode for this method
mv.visitEnd();
}
/**
* Assemble an indexed setter method which accepts a primitive <code>int</code> index and
* delegates to the appropriate, denormalized, simple setter method.
* <p>
* The source equivalent for an <code>integer</code> property <code>foo</code> with extent
* <code>N</code> and default denormalization naming would be:
* <pre>
* public void setFoo(int index, integer foo)
* {
* switch (index)
* {
* case 0:
* setFoo1(foo);
* return;
* case 1:
* setFoo2(foo);
* return;
* ...
* case N:
* setFooN();
* return;
* }
*
* outOfBoundsError(index);
* }
* </pre>
*
* @param classWriter
* ASM class writer.
* @param dmoBufIfaceType
* Internal type name of the interface containing the method being implemented and
* the methods it calls.
* @param baseProperty
* Base property name associated with the indexed setter.
* @param method
* Indexed setter method.
*/
private void assembleDenormPrimSetter(ClassWriter classWriter,
String dmoBufIfaceType,
String baseProperty,
Method method)
{
// look up the denormalized property names associated with this base property
String[] denormProps = denormPropMap.get(baseProperty);
int len = denormProps == null ? 0 : denormProps.length;
if (len == 0)
{
return;
}
// create method visitor
String baseDesc = ProxyUtils.makeMethodDescriptor(method);
String methName = method.getName();
MethodVisitor mv =
classWriter.visitMethod(ACC_PUBLIC,
methName,
baseDesc,
null,
ProxyUtils.getCaughtExceptionTypeNames(method));
// compose array of possible case values
int[] cases = new int[len];
for (int i = 0; i < len; i++)
{
cases[i] = i;
}
// compose array labels for lookupswitch instruction
Label[] switchLabels = new Label[len];
for (int i = 0; i < len; i++)
{
switchLabels[i] = new Label();
}
// label for lookupswitch instruction's default jump
Label defLabel = new Label();
// start bytecode
mv.visitCode();
// load index (int) parameter
mv.visitVarInsn(ILOAD, 1);
// switch statement
mv.visitLookupSwitchInsn(defLabel, cases, switchLabels);
// switch cases
for (int i = 0; i < len; i++)
{
// get denormalized method to invoke
String property = denormProps[i];
Method denormMeth = pojoSetterMap.get(property);
String denormName = denormMeth.getName();
String denormDesc = ProxyUtils.makeMethodDescriptor(denormMeth);
// mark this as the jump location for case i
mv.visitLabel(switchLabels[i]);
// load this object reference
mv.visitVarInsn(ALOAD, 0);
// load the second method parameter (BDT subclass)
mv.visitVarInsn(ALOAD, 2);
// invoke the denormalized, simple, getter method
mv.visitMethodInsn(INVOKEINTERFACE, dmoBufIfaceType, denormName, denormDesc, true);
// return void
mv.visitInsn(RETURN);
}
// default case
mv.visitLabel(defLabel);
// load this object reference
mv.visitVarInsn(ALOAD, 0);
// load index parameter
mv.visitVarInsn(ILOAD, 1);
// invoke BufferImpl.outOfBoundsError method
mv.visitMethodInsn(INVOKEVIRTUAL, TYPE_BUFFERIMPL, METH_OOBE, SIG_OOBE, false);
// return void
mv.visitInsn(RETURN);
// parameters ignored
mv.visitMaxs(0, 0);
// end bytecode for this method
mv.visitEnd();
}
/**
* Assemble an indexed getter method which accepts a <code>NumberType</code> index, does an
* unknown value check, and delegates to the corresponding, indexed getter method.
* <p>
* The source equivalent for an <code>integer</code> property <code>foo</code> with default
* denormalization naming would be:
* <pre>
* public integer getFoo(NumberType index)
* {
* if (index.isUnknown())
* {
* integer unk = new integer();
* unk.setUnknown();
*
* return unk;
* }
*
* return getFoo(index.intValue());
* }
* </pre>
*
* @param classWriter
* ASM class writer
* @param dmoBufIfaceType
* Internal type name of the interface containing the method being implemented and
* the methods it calls.
* @param baseProperty
* Base property name associated with the indexed getter.
* @param method
* Indexed getter method.
*/
private void assembleDenormBdtGetter(ClassWriter classWriter,
String dmoBufIfaceType,
String baseProperty,
Method method)
{
// create method visitor
String methDesc = ProxyUtils.makeMethodDescriptor(method);
String methName = method.getName();
MethodVisitor mv =
classWriter.visitMethod(ACC_PUBLIC,
methName,
methDesc,
null,
ProxyUtils.getCaughtExceptionTypeNames(method));
// start bytecode
mv.visitCode();
// check index parameter for unknown value and jump past conditional block if test fails
Label jumpLabel = assembleUnknownValueCheck(mv);
// instantiate and return a wrapper of the appropriate type, set to unknown value
String typeName = AsmUtils.commonToInternalTypeName(method.getReturnType());
assembleReturnUnknown(mv, typeName, 2);
// mark the jump location after the unknown check block
mv.visitLabel(jumpLabel);
// load this object reference
mv.visitVarInsn(ALOAD, 0);
// load index parameter
mv.visitVarInsn(ALOAD, 1);
// extract its primitive int value
mv.visitMethodInsn(INVOKEVIRTUAL, TYPE_NUMBERTYPE, METH_INTVALUE, SIG_INTVALUE, false);
// delegate to the indexed getter which takes a primitive int index
Method primGetter = legacyGetterMap.get(baseProperty);
String primMethDesc = ProxyUtils.makeMethodDescriptor(primGetter);
mv.visitMethodInsn(INVOKEINTERFACE, dmoBufIfaceType, methName, primMethDesc, true);
// return the retrieved value
mv.visitInsn(ARETURN);
// parameters ignored
mv.visitMaxs(0, 0);
// end bytecode for this method
mv.visitEnd();
}
/**
* Assemble an indexed setter method which accepts a <code>NumberType</code> index, does an
* unknown value check, and delegates to the corresponding, indexed getter method.
* <p>
* The source equivalent for an <code>integer</code> property <code>foo</code> with default
* denormalization naming would be:
* <pre>
* public void setFoo(NumberType index, integer foo)
* {
* if (index.isUnknown())
* {
* return;
* }
*
* setFoo(index.intValue(), foo);
* }
* </pre>
*
* @param classWriter
* ASM class writer
* @param dmoBufIfaceType
* Internal type name of the interface containing the method being implemented and
* the methods it calls.
* @param baseProperty
* Base property name associated with the indexed setter.
* @param method
* Indexed setter method.
*/
private void assembleDenormBdtSetter(ClassWriter classWriter,
String dmoBufIfaceType,
String baseProperty,
Method method)
{
// create method visitor
String methDesc = ProxyUtils.makeMethodDescriptor(method);
String methName = method.getName();
MethodVisitor mv =
classWriter.visitMethod(ACC_PUBLIC,
methName,
methDesc,
null,
ProxyUtils.getCaughtExceptionTypeNames(method));
// start bytecode
mv.visitCode();
// check index parameter for unknown value and jump past conditional block if test fails
Label jumpLabel = assembleUnknownValueCheck(mv);
// return immediately if index was unknown value
mv.visitInsn(RETURN);
// mark the jump location after the unknown check block
mv.visitLabel(jumpLabel);
// load this object reference
mv.visitVarInsn(ALOAD, 0);
// load index parameter
mv.visitVarInsn(ALOAD, 1);
// extract its primitive int value
mv.visitMethodInsn(INVOKEVIRTUAL, TYPE_NUMBERTYPE, METH_INTVALUE, SIG_INTVALUE, false);
// load the second method parameter (BDT subclass)
mv.visitVarInsn(ALOAD, 2);
// delegate to the indexed setter which takes a primitive int index
Method primSetter = legacySetterMap.get(baseProperty);
String primMethDesc = ProxyUtils.makeMethodDescriptor(primSetter);
mv.visitMethodInsn(INVOKEINTERFACE, dmoBufIfaceType, methName, primMethDesc, true);
// return void
mv.visitInsn(RETURN);
// parameters ignored
mv.visitMaxs(0, 0);
// end bytecode for this method
mv.visitEnd();
}
/**
* Assemble a getter method which returns an array containing all the values in an array
* property by delegating to the corresponding, primitive, indexed getter method in a loop.
* <p>
* The source equivalent for an <code>integer</code> property <code>foo</code> with extent
* <code>N</code> and default denormalization naming would be:
* <pre>
* public integer[] getFoo()
* {
* integer[] fooArray = new integer[N];
* for (int i = 0; i < N; i++)
* {
* fooArray[i] = getFoo(i);
* }
* return fooArray;
* }
* </pre>
*
* @param classWriter
* ASM class writer
* @param dmoBufIfaceType
* Internal type name of the interface containing the method being implemented and
* the methods it calls.
* @param baseProperty
* Base property name associated with the getter.
* @param method
* Array bulk getter method.
*/
private void assembleArrayBulkGetter(ClassWriter classWriter,
String dmoBufIfaceType,
String baseProperty,
Method method)
{
// create method visitor
String methDesc = ProxyUtils.makeMethodDescriptor(method);
String methName = method.getName();
MethodVisitor mv =
classWriter.visitMethod(ACC_PUBLIC,
methName,
methDesc,
null,
ProxyUtils.getCaughtExceptionTypeNames(method));
// start bytecode
mv.visitCode();
// push the extent size onto the stack
int extent = extentMap.get(baseProperty);
AsmUtils.pushInt(mv, extent);
// instantiate an array in which to store results
Class<?> type = method.getReturnType().getComponentType();
String typeName = AsmUtils.commonToInternalTypeName(type);
mv.visitTypeInsn(ANEWARRAY, typeName);
// store the array in a local variable
mv.visitVarInsn(ASTORE, 1);
// push starting loop counter value onto stack
mv.visitInsn(ICONST_0);
// store current value of loop counter in a local variable
mv.visitVarInsn(ISTORE, 2);
// mark top of loop
Label topOfLoop = new Label();
mv.visitLabel(topOfLoop);
// load loop counter from local variable
mv.visitVarInsn(ILOAD, 2);
// push loop limit value onto stack for comparison with counter
AsmUtils.pushInt(mv, extent);
// jump past loop body if counter >= limit
Label afterLoop = new Label();
mv.visitJumpInsn(IF_ICMPGE, afterLoop);
// load the array
mv.visitVarInsn(ALOAD, 1);
// load the loop counter to use as the index into the array
mv.visitVarInsn(ILOAD, 2);
// load this object reference
mv.visitVarInsn(ALOAD, 0);
// load the loop counter to use as parameter for indexed getter method
mv.visitVarInsn(ILOAD, 2);
// invoke the indexed getter method
Method primGetter = legacyGetterMap.get(baseProperty);
String primMethDesc = ProxyUtils.makeMethodDescriptor(primGetter);
mv.visitMethodInsn(INVOKEINTERFACE, dmoBufIfaceType, methName, primMethDesc, true);
// store the returned value in the array
mv.visitInsn(AASTORE);
// increment loop counter (local variable 2) by 1
mv.visitIincInsn(2, 1);
// jump back to top of loop
mv.visitJumpInsn(GOTO, topOfLoop);
// mark afterLoop jump location
mv.visitLabel(afterLoop);
// load the array
mv.visitVarInsn(ALOAD, 1);
// return the array
mv.visitInsn(ARETURN);
// parameters ignored
mv.visitMaxs(0, 0);
// end of bytecode for this method
mv.visitEnd();
}
/**
* Assemble a setter method which accepts an array containing zero or more values to be stored
* in an array property, at the corresponding element positions, by delegating to the
* corresponding, primitive, indexed setter method in a loop.
* <p>
* The source equivalent for an <code>integer</code> property <code>foo</code> with extent
* <code>N</code> and default denormalization naming would be:
* <pre>
* public void setFoo(integer[] foo)
* {
* int len = Math.min(foo.length, N);
* for (int i = 0; i < len; i++)
* {
* setFoo(i, foo[i]);
* }
* }
* </pre>
*
* @param classWriter
* ASM class writer
* @param dmoBufIfaceType
* Internal type name of the interface containing the method being implemented and
* the methods it calls.
* @param baseProperty
* Base property name associated with the setter.
* @param method
* Array bulk setter method.
*/
private void assembleArrayBulkSetter(ClassWriter classWriter,
String dmoBufIfaceType,
String baseProperty,
Method method)
{
// create method visitor
String methDesc = ProxyUtils.makeMethodDescriptor(method);
String methName = method.getName();
MethodVisitor mv =
classWriter.visitMethod(ACC_PUBLIC,
methName,
methDesc,
null,
ProxyUtils.getCaughtExceptionTypeNames(method));
// start bytecode
mv.visitCode();
// load array parameter
mv.visitVarInsn(ALOAD, 1);
// push array length onto stack
mv.visitInsn(ARRAYLENGTH);
// push the extent size onto the stack
int extent = extentMap.get(baseProperty);
AsmUtils.pushInt(mv, extent);
// get the minimum of the array length and the extent size
mv.visitMethodInsn(INVOKESTATIC, TYPE_MATH, METH_MIN, SIG_MIN, false);
// store resulting min value (our loop limit) in a local variable
mv.visitVarInsn(ISTORE, 2);
// push starting loop counter value onto stack
mv.visitInsn(ICONST_0);
// store current value of loop counter in a local variable
mv.visitVarInsn(ISTORE, 3);
// mark top of loop
Label topOfLoop = new Label();
mv.visitLabel(topOfLoop);
// load loop counter
mv.visitVarInsn(ILOAD, 3);
// load loop limit
mv.visitVarInsn(ILOAD, 2);
// jump past loop body in counter >= limit
Label afterLoop = new Label();
mv.visitJumpInsn(IF_ICMPGE, afterLoop);
// load this object reference
mv.visitVarInsn(ALOAD, 0);
// load loop counter to use as parameter for indexed setter method
mv.visitVarInsn(ILOAD, 3);
// load array reference
mv.visitVarInsn(ALOAD, 1);
// load the loop counter to use as the index into the array
mv.visitVarInsn(ILOAD, 3);
// load array value at that index
mv.visitInsn(AALOAD);
// invoke the indexed setter method
Method primSetter = legacySetterMap.get(baseProperty);
String primMethDesc = ProxyUtils.makeMethodDescriptor(primSetter);
mv.visitMethodInsn(INVOKEINTERFACE, dmoBufIfaceType, methName, primMethDesc, true);
// increment loop counter (local variable 3) by 1
mv.visitIincInsn(3, 1);
// jump back to top of loop
mv.visitJumpInsn(GOTO, topOfLoop);
// mark afterLoop jump location
mv.visitLabel(afterLoop);
// return void
mv.visitInsn(RETURN);
// parameters ignored
mv.visitMaxs(0, 0);
// end of bytecode for this method
mv.visitEnd();
}
/**
* Assemble a setter method which accepts a scalar value and store that value into each
* element in an array property, by delegating to the corresponding, primitive, indexed setter
* method in a loop.
* <p>
* The source equivalent for an <code>integer</code> property <code>foo</code> with extent
* <code>N</code> and default denormalization naming would be:
* <pre>
* public void setFoo(integer foo)
* {
* for (int i = 0; i < N; i++)
* {
* setFoo(i, foo);
* }
* }
* </pre>
*
* @param classWriter
* ASM class writer
* @param dmoBufIfaceType
* Internal type name of the interface containing the method being implemented and
* the methods it calls.
* @param baseProperty
* Base property name associated with the setter.
* @param method
* Scalar bulk setter method.
*/
private void assembleScalarBulkSetter(ClassWriter classWriter,
String dmoBufIfaceType,
String baseProperty,
Method method)
{
// create method visitor
String methDesc = ProxyUtils.makeMethodDescriptor(method);
String methName = method.getName();
MethodVisitor mv =
classWriter.visitMethod(ACC_PUBLIC,
methName,
methDesc,
null,
ProxyUtils.getCaughtExceptionTypeNames(method));
// start bytecode
mv.visitCode();
// push the extent size onto the stack
int extent = extentMap.get(baseProperty);
AsmUtils.pushInt(mv, extent);
// store extent size (our loop limit) in a local variable
mv.visitVarInsn(ISTORE, 2);
// push starting loop counter value onto stack
mv.visitInsn(ICONST_0);
// store current value of loop counter in a local variable
mv.visitVarInsn(ISTORE, 3);
// mark top of loop
Label topOfLoop = new Label();
mv.visitLabel(topOfLoop);
// load loop counter
mv.visitVarInsn(ILOAD, 3);
// load loop limit
mv.visitVarInsn(ILOAD, 2);
// jump past loop body in counter >= limit
Label afterLoop = new Label();
mv.visitJumpInsn(IF_ICMPGE, afterLoop);
// load this object reference
mv.visitVarInsn(ALOAD, 0);
// load loop counter to use as parameter for indexed setter method
mv.visitVarInsn(ILOAD, 3);
// load the method parameter (BDT subclass)
mv.visitVarInsn(ALOAD, 1);
// invoke the indexed setter method
Method primSetter = legacySetterMap.get(baseProperty);
String primMethDesc = ProxyUtils.makeMethodDescriptor(primSetter);
mv.visitMethodInsn(INVOKEINTERFACE, dmoBufIfaceType, methName, primMethDesc, true);
// increment loop counter (local variable 3) by 1
mv.visitIincInsn(3, 1);
// jump back to top of loop
mv.visitJumpInsn(GOTO, topOfLoop);
// mark afterLoop jump location
mv.visitLabel(afterLoop);
// return void
mv.visitInsn(RETURN);
// parameters ignored
mv.visitMaxs(0, 0);
// end of bytecode for this method
mv.visitEnd();
}
/**
* Assemble the instructions to test whether the <code>NumberType</code> object in local
* variable slot 1 (i.e., the subscript parameter to an indexed getter/setter method)
* represents the unknown value, and to jump to the instruction represented by the returned
* label if so. It is up to the caller to assign the label to a jump destination.
*
* @param mv
* Method visitor.
*
* @return Jump label, to allow the caller to determine the jump destination, in the event of
* a failed check (i.e., unknown == true).
*/
private Label assembleUnknownValueCheck(MethodVisitor mv)
{
// load index (NumberType) parameter
mv.visitVarInsn(ALOAD, 1);
// check whether index is unknown
mv.visitMethodInsn(INVOKEVIRTUAL, TYPE_NUMBERTYPE, METH_ISUNK, SIG_ISUNK, false);
// jump past block if index was not unknown
Label jumpLabel = new Label();
mv.visitJumpInsn(IFEQ, jumpLabel);
return jumpLabel;
}
/**
* Log a warning that an unexpected method was encountered while introspecting a DMO
* interface or DMO buffer interface.
*
* @param method
* Unexpected method.
*/
private void logUnexpectedMethod(Method method)
{
if (LOG.isLoggable(Level.WARNING))
{
String msg = "Unexpected method in DMO buffer target interface %s: %s";
LOG.warning(String.format(msg, targetInterface.getName(), method.toString()));
}
}
/**
* A base class for concrete implementations of indexed proxy method assemblers.
*/
protected abstract static class IndexedProxyMethod
extends ProxyAssembler.ProxyMethod
{
/** Extent of the associated property */
private final int extent;
/**
* Default constructor.
*
* @param extent
* Extent of the associated property.
*/
IndexedProxyMethod(int extent)
{
super();
this.extent = extent;
}
/**
* Assemble the bytecode instructions to perform a range check on a primitive integer value
* in local variable 1 (the subscript parameter of an indexed getter/setter method), and to
* jump to the instruction represented by the returned label if the check passes. If the
* check fails, the instruction next visited by the caller is executed. It is up to the
* caller to assign the label to a jump destination.
* <p>
* The assembled method will delegate to the {@link BufferImpl#checkExtentRange(int, int)
* parent class' range check method} for the range check itself.
*
* @param mv
* Method visitor
*
* @return Jump label, to allow the caller to determine the jump destination, in the event
* of a failed check (i.e., subscript is out of range).
*/
protected Label assembleRangeCheck(MethodVisitor mv)
{
// load this object reference
mv.visitVarInsn(ALOAD, 0);
// load the first method parameter (index)
mv.visitVarInsn(ILOAD, 1);
// push the extent size onto the stack
AsmUtils.pushInt(mv, extent);
// invoke BufferImpl.checkExtentRange
mv.visitMethodInsn(INVOKEVIRTUAL, TYPE_BUFFERIMPL, METH_CHKXRNG, SIG_CHKXRNG, false);
// jump past block if index was valid
Label jumpLabel = new Label();
mv.visitJumpInsn(IFNE, jumpLabel);
return jumpLabel;
}
}
/**
* This class assembles an indexed getter method which performs a range check on the index
* before calling the proxy's invocation handler.
*/
private static class IndexedGetterAssembler
extends IndexedProxyMethod
{
/**
* Default constructor.
*
* @param extent
* Extent of the associated property.
*/
IndexedGetterAssembler(int extent)
{
super(extent);
}
/**
* Assemble the bytecode which comprises the proxy method. We simply add the range check at
* the top of the method and delegate to the superclass to implement the remainder of the
* method.
*
* @param classWriter
* Class writer for proxy class.
* @param mv
* Method visitor for this method.
* @param method
* Method for which this proxy is being implemented.
* @param methodIndex
* Index of this method within the array of methods used by the invocation handler.
* @param parentIsHandler
* <code>true</code> if the parent class implements the
* <code>InvocationHandler</code> interface, else <code>false</code>. If
* <code>true</code>, no separate instance field will be assembled to store the
* invocation handler reference, as the instance of this class itself will act as
* the handler.
* @param proxyName
* Internal form of the proxy class' name.
*/
public void assemble(ClassWriter classWriter,
MethodVisitor mv,
Method method,
int methodIndex,
boolean parentIsHandler,
String proxyName)
{
// perform the range check
Label jumpLabel = assembleRangeCheck(mv);
// instantiate and return a wrapper of the appropriate type, set to unknown value
String typeName = AsmUtils.commonToInternalTypeName(method.getReturnType());
assembleReturnUnknown(mv, typeName, 2);
// mark the jump location after the range check block
mv.visitLabel(jumpLabel);
// let the superclass assemble the remainder of the method
super.assemble(classWriter, mv, method, methodIndex, parentIsHandler, proxyName);
}
}
/**
* This class assembles an indexed setter method which performs a range check on the index
* before calling the proxy's invocation handler.
*/
private static class IndexedSetterAssembler
extends IndexedProxyMethod
{
/**
* Default constructor.
*
* @param extent
* Extent of the associated property.
*/
IndexedSetterAssembler(int extent)
{
super(extent);
}
/**
* Assemble the bytecode which comprises the proxy method.
*
* @param classWriter
* Class writer for proxy class.
* @param mv
* Method visitor for this method.
* @param method
* Method for which this proxy is being implemented.
* @param methodIndex
* Index of this method within the array of methods used by the invocation handler.
* @param parentIsHandler
* <code>true</code> if the parent class implements the
* <code>InvocationHandler</code> interface, else <code>false</code>. If
* <code>true</code>, no separate instance field will be assembled to store the
* invocation handler reference, as the instance of this class itself will act as
* the handler.
* @param proxyName
* Internal form of the proxy class' name.
*/
public void assemble(ClassWriter classWriter,
MethodVisitor mv,
Method method,
int methodIndex,
boolean parentIsHandler,
String proxyName)
{
// perform the range check
Label jumpLabel = assembleRangeCheck(mv);
// return immediately
mv.visitInsn(RETURN);
// mark the jump location after the range check block
mv.visitLabel(jumpLabel);
// let the superclass assemble the remainder of the method
super.assemble(classWriter, mv, method, methodIndex, parentIsHandler, proxyName);
}
}
}