FieldReference.java

/*
** Module   : FieldReference.java
** Abstract : Buffer field reference which allows deferred read/write access
**
** Copyright (c) 2004-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20051112   @23388 Created initial version. Resolves a buffer
**                           field reference value at runtime. Used as a
**                           query substitution parameter for field
**                           references which can only be resolved as the
**                           query is executing and has partial results.
** 002 ECF 20051216   @23756 Added getValue method. Returns resolved value
**                           but reports a warning to the UI (via the
**                           error manager) if resolution fails.
** 003 ECF 20060104   @23822 Implement Resolvable interface. Allows this
**                           class to be used generically with util APIs.
** 004 ECF 20060202   @24223 Implement Accessor interface. Field access is
**                           now read/write instead of read-only.
** 005 ECF 20060307   @25015 Added new constructor and get method variant.
**                           These permit late binding of a DMO instance.
** 006 ECF 20060323   @25226 Added support for case-insensitive field
**                           references used as query substitution
**                           parameters. New constructor variants accept
**                           an uppercase flag.
** 007 GES 20060907   @29285 Setting an extent field was broken. The index
**                           had to be the first parameter instead of the
**                           second.
** 008 ECF 20060927   @29970 Added special exception handling in set() and
**                           getObject(). ConditionException wrapped in an
**                           InvocationTargetExcpetion is unwrapped and
**                           rethrown.
** 009 ECF 20061027   @30749 Replaced Object with DataModelObject for DMO
**                           arguments to methods/constructors. Required
**                           for compile-time type safety.
** 010 ECF 20070416   @33022 Integrated user interrupt handling.
** 011 ECF 20080528   @38480 Integrated generics.
** 012 ECF 20080606   @38645 Fixed FieldReference(Class<?>, String) c'tor
**                           variant. It was not properly setting the
**                           uppercase instance variable in the case of
**                           references to case-insensitive properties.
**                           Also added support for the reserved 'id'
**                           property in getGetter() method.
** 013 CA  20080815   @39458 Support API change in DBUtils.
** 014 ECF 20090701   @43217 Enabled lazy initialization of backing record
**                           buffer. Database instance variable is extracted
**                           from buffer lazily, rather than at construction.
** 015 SVL 20090723   @43342 Added getPropertyName() function.
** 016 ECF 20090819   @43695 Downgraded some log messages from ERROR to DEBUG.
** 017 GES 20090828   @43799 Don't unconditionally shift into warning mode as
**                           that would disable warning mode on exit from the
**                           getter even if it had already been enabled.
** 018 AIL 20121206          Update from character to TextOps.
** 019 VMN 20130207          Extended from FieldInterface and added support for attributes
**                           BUFFER-VALUE (KW_BUF_VAL)
**                           COLUMN-LABEL (KW_COL_LAB)
**                           EXTENT (KW_EXTENT)
**                           FORMAT (KW_FORMAT)
**                           STRING-VALUE (KW_STR_VAL)
** 020 VMN 20130213          Added support for attributes
**                           BUFFER-NAME (KW_BUF_NAME),
**                           LITERAL-QUESTION (KW_LIT_QSTN)
**                           VALIDATE-MESSAGE (KW_VAL_MSG)
**                           MANDATORY (KW_MAND)
**                           VALIDATE-EXPRESSION (KW_VAL_EXPR)
**                           CURRENT-ITERATION (KW_CUR_ITER)
** 021 CA  20130215          Added dynamic EXTENT and EXTENT parameter support.
** 022 VMN 20130217          Corrected for attribute
**                           CURRENT-ITERATION (KW_CUR_ITER)
**                           WIDTH-CHARS (KW_WIDTH_C)
** 023 VMN 20130219          Added support for attributes
**                           POSITION (KW_POS)
** 024 CA  20130224          Added support for UNIQUE-ID, AMD-DATA, Sizeable and CommonField
**                           attributes. Fixed CHANGE-VALUE. Added Deletable support.
** 025 OM  20130219          Changed return type of value (BUFFER-VALUE)
** 026 OM  20130304          Refactored isValid and isUnknown of WrappedResource to valid 
**                           and unknown.
** 027 OM  20130304          Added missing overloaded method for value(). 
** 028 CA  20130315          Added missing API for STRING-VALUE.
** 029 ECF 20130417          Moved initial() method from BufferImpl.
** 030 CA  20130603          resourceDelete must return a boolean value.
** 031 SVL 20130822          Implemented extent-related functions and value() function. Added
**                           getParentBuffer.
** 032 HC  20131215          Implemented ADM-DATA and UNIQUE-ID attribute.
** 033 VMN 20140123          Implemented extent(), initial(), stringValue(), getFormat(),
**                           setFormat(), columnLabel(), changeColumnLabel(), getColumnLabel(),
**                           setColumnLabel(), bufferName(), getDbName(), getTable(),
**                           position(), getDataType(), valid(), setValue() for non BaseDataType
**                           values.
** 034 OM  20140122          Fixed value() and value(int) to return unknown value when they are
**                           called for EXTENT, resp. non-EXTENT fields.
**                           Added Long support for changeValue(), minor aesthetic changes.
** 035 SVL 20140205          Fixed value(int).
** 036 EVL 20140217          Fixed javadoc comments to be able to compile in Windows 1252 CP.
** 037 ECF 20140318          Added logic to compensate for instance of unknown being passed to
**                           changeValue(). Replaced Apache commons logging with J2SE logging.
**                           Added error handling for BUFFER-VALUE() to validate index values
**                           passed to value(NumberType) and changeValue(NumberType) (0 is a valid
**                           index for scalar fields). Added widening conversion in changeValue
**                           for polymorphic BaseDataType arguments. Needs additional work to
**                           exactly mimic 4GL errors.
** 038 ECF 20140415          Set NAME attribute to its default value the first time name() is
**                           invoked.
** 039 HC  20140613          Fixed extent fields runtime support. 
** 040 HC  20141207          Implemented runtime support for widget coordinate attributes.
** 041 ECF 20150315          Major refactoring; moved all code related to BUFFER-FIELD support to
**                           BufferFieldImpl. This class is now only the worker which interacts
**                           with the DMO to get and set property values.
** 042 SVL 20150503          Added getIndex, getProperty.
** 043 ECF 20150715          Added APIs in support of compound query optimization.
** 044 EVL 20160223          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 045 ECF 20160224          Rework to better support denormalized extent fields. Removed
**                           redundant method and extent caches.
** 046 EVL 20160325          Javadoc fixes.
** 047 SVL 20160314          Added getTemp methods.
** 048 OM  20160613          Fixed character trimming.
** 049 SVL 20160811          Some methods are public now.
** 050 HC  20170830          Modified conversion of EXPORT to expand extent fields dynamically.
**                           This should prevent the Java source file size to get out of control
**                           for large extents.
** 051 ECF 20180201          Store proxy separately from DMO.
** 052 OM  20180301          Special handling for rowid/recid functions converted into surrogate
**                           [id] fields when encountered as substitution expressions.
**     ECF 20180305          Fix for regression with cases where property is id field, but dmo is
**                           a Persistable instance instead of a DMO proxy.
** 053 OM  20180614          Added support for bulk-accessors of extent fields.
** 054 SVL 20180528          Added getFullFieldName().
**     OM  20180627          Implemented equals() and hashCode(). Added index value for extent
**                           fields in string returned by getFullFieldName().
** 055 CA  20190829          Virtual properties (starting with '_') are looked in the 
**                           TempTableRecord interface.
**     OM  20190831          Added hidden fields and index specific to BEFORE TEMP-TABLES.
** 056 CA  20191009          In case of bound buffers, the DMO property must be translated.
** 057 RFB 20200117          We need a new constructor to allow us to pass in 'bulk'. This allows
**                           us the ability to use the multi assignment for un-subscripted field
**                           references in embedded assignment.
**     RFB 20200120          Update the constructor to not impede a non-negative index when passed
**                           in. Updated javadoc to be more accurate.
**     RFB 20200122          Had a type mismatch in the previous update.
** 058 ECF 20200906          New ORM implementation.
** 059 CA  20200924          Replaced Method.invoke with ReflectASM.
**     SVL 20201009          Index can be represented by an expression. Scalar index is stored as Integer
**                           rather as NumberType.
**     OM  20201012          Force use locally cached meta information instead of map lookup.
**     OM  20210328          Improve support for CODEPAGE of CLOB fields.
**     OM  20210404          CLOB fields are created and returned with their CP set. The getObject() method
**                           was too late to set their CP.
**     CA  20210609          Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly 
**                           initialized at the method's execution, and not at the caller's arguments.
**     CA  20210628          Indexed extent fields must resolve proper getter/setter - the problem was used 
**                           when IMPORT was targeting an extent field.
**     SVL 20210922          In getFullFieldName use legacy name instead of DMO alias as the latter is
**                           uniquely generated for dynamic buffers (see
**                           BufferManager.generateUniqueBufferName).
**     TJD 20220504          Upgrade do Java 11 minor changes.
**     OM  20221208          Small changes in method declarations.
**     EVL 20221222          Adding new method to get legacy field name.
** 060 OM  20230303          Proper identification of bulk mode in FieldReference constructor.
** 061 OM  20230307          The original extent and property name were lost when denormalizing extent fields.
** 062 OM  20230404          Improved toString() representation.
** 063 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
** 064 RAA 20230519          Made the computing of getter, setter and extent lazy. 
**                           Added buffer instance for getParentBuffer.
**     RAA 20230519          Added flags for getter, setter and extent to check which one has already been
**                           computed.
**     RAA 20230523          Done some code cleanup.
**     RAA 20230525          Creating a new BaseDataType instance is now done using BaseDataTypeFactory.
** 065 CA  20230330          Fixed NPE - 'buffer' can't be used standalone, use 'getParentBuffer()' getter.
** 066 EAB 20240429          Checked for unknownValue method if the getter is for a primary key. If it is,
**                           the function needs to return a value of type "rowid".
*/

/*
** 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.function.*;
import java.util.logging.*;
import com.goldencode.p2j.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.ErrorManager;
import com.goldencode.p2j.util.logging.*;

/**
 * A readable, writable reference to a buffer field, used in cases where the
 * field needs to be accessed at some arbitrary point in time after it is
 * referenced in application code.  For example, an instance of this class
 * would be used as a substitution parameter in a {@link CompoundQuery}, when
 * an inner loop where clause of a multi-table query refers to a field within
 * a buffer retrieved in an outer loop.
 * <p>
 * This class provides both read and write capability to the backing field,
 * insofar as the DMO has both an accessor (getter) and a mutator (setter)
 * method defined for the specified property.  Both indexed and non-indexed
 * versions of these methods are supported.  It is assumed that the methods
 * have bean-like signatures.  Specifically, the following, public signature
 * patterns are assumed:
 * <p>
 * <dl>
 * <dt>Simple Getter:</dt>
 * <dd>
 * <code>&lt;BaseDataType subclass&gt; get&lt;Property&gt;()</code>
 * </dd>
 * <dt>Indexed Getter:</dt>
 * <dd>
 * <code>&lt;BaseDataType subclass&gt; get&lt;Property&gt;(int)</code>
 * </dd>
 * <dt>Simple Setter:</dt>
 * <dd>
 * <code>void set&lt;Property&gt;(&lt;BaseDataType subclass&gt;)</code>
 * </dd>
 * <dt>Indexed Setter:</dt>
 * <dd>
 * <code>void set&lt;Property&gt;(&lt;BaseDataType subclass&gt;, int)</code>
 * </dd>
 * </dl>
 * <p>
 */
public final class FieldReference
implements Resolvable,
           Accessor
{
   /** Logger */
   private static final CentralLogger LOG = CentralLogger.get(FieldReference.class.getName());
   
   /**
    * The special {@code id} field. It is used for {@code rowid} / {@code recid} of the record and
    * does not exist as real field in P4GL, only in converted SQL database. For this reason it is
    * handled a bit different. Its value is implemented as objects extending {@code Record}
    * and not the DMO as other fields.
    */
   private static final String ID = DatabaseManager.PRIMARY_KEY;
   
   /** Property name */
   private final String property;
   
   /** Method used to resolve this field reference at query execution time */
   private Method getter;
   
   /** Method used to resolve this field reference at query execution time */
   private Method setter;
   
   /** This field's extent (if an array field), else <code>null</code> */
   private Integer extent;
   
   /** Optional index, used with a DMO indexed getter method */
   private final Integer index;

   /** Optional index expression, used with a DMO indexed getter method */
   private final Supplier<NumberType> indexExpr;
   
   /** Saved legacy field name. */
   private String legacyName = null;
   
   /** The associated database */
   private Database database = null;
   
   /** DMO or DMO proxy instance;  the referent of the referencing method */
   private Object dmo = null;
   
   /** DMO proxy, if available; may be {@code null} */
   private DataModelObject proxy = null;
   
   /** Flag to force resolved character result to uppercase/right-trim */
   private boolean uppercase = false;
   
   /** 
    * When the reference is an EXTENT field and {@code index} is {@code null} the bulk getter/setter
    * will be used.
    */
   private Boolean bulk = false;
   
   /** The record buffer which contains the referent DMO. */
   private RecordBuffer buffer = null;

   /** Interface of the target DMO. */
   private final Class<? extends DataModelObject> dmoIface;
   
   /**
    * Marks this as a reference to {@code id} special field.
    * 
    * @see #ID
    */
   private final boolean isIdAccessor;
   
   /** Flag that marks whether the extent field has already been computed or not. */
   private boolean isExtentComputed = false;
   
   /** Flag that marks whether the getter field has already been computed or not. */
   private boolean isGetterComputed = false;
   
   /** Flag that marks whether the setter field has already been computed or not. */
   private boolean isSetterComputed = false;
   
   /** Flag that marks whether the bulk field has already been computed or not. */
   private boolean isBulkComputed = false;
   
   /**
    * Create a new field reference given a DMO proxy and property name.  A
    * non-indexed getter method will be used to resolve the field.
    *
    * @param   dmo
    *          DMO proxy.
    * @param   property
    *          Target property name.
    */
   public FieldReference(DataModelObject dmo, String property)
   {
      this(dmo, property, false, null, null, true);
   }
   
   /**
    * Create a new field reference given a DMO proxy and property name.  A
    * non-indexed getter method will be used to resolve the field.
    *
    * @param   dmo
    *          DMO proxy.
    * @param   property
    *          Target property name.
    * @param   uppercase
    *          <code>true</code> to force a <code>character</code> value
    *          returned by {@link #getObject} to be uppercased/right-trimmed;
    *          <code>false</code> to leave the result unchanged.
    */
   public FieldReference(DataModelObject dmo, String property, boolean uppercase)
   {
      this(dmo, property, uppercase, null, null, true);
   }
   
   /**
    * Create a new field reference given a DMO proxy, a property name, and
    * an index for the property.  An indexed getter method will be used to
    * resolve the field.
    *
    * @param   dmo
    *          DMO proxy.
    * @param   property
    *          Target property name.
    * @param   index
    *          Index to pass to property's getter method at resolve time.
    */
   public FieldReference(DataModelObject dmo, String property, int index)
   {
      this(dmo, property, false, new integer(index), null, false);
   }

   /**
    * Create a new field reference given a DMO proxy, a property name, and
    * an index expression for the property.  An indexed getter method will be used to
    * resolve the field.
    *
    * @param   dmo
    *          DMO proxy.
    * @param   property
    *          Target property name.
    * @param   indexExpr
    *          Index expression which is evaluated to pass to property's getter method at resolve time.
    */
   public FieldReference(DataModelObject dmo, String property, Supplier<NumberType> indexExpr)
   {
      this(dmo, property, false, null, indexExpr, false);
   }
   
   /**
    * Create a new field reference given a DMO proxy, a property name, and
    * an index for the property.  An indexed getter method will be used to
    * resolve the field.
    *
    * @param   dmo
    *          DMO proxy.
    * @param   property
    *          Target property name.
    * @param   uppercase
    *          <code>true</code> to force a <code>character</code> value
    *          returned by {@link #getObject} to be uppercased/right-trimmed;
    *          <code>false</code> to leave the result unchanged.
    * @param   index
    *          Index to pass to property's getter method at resolve time.
    */
   public FieldReference(DataModelObject dmo, String property, boolean uppercase, int index)
   {
      this(dmo, property, uppercase, new integer(index), null, false);
   }

   /**
    * Create a new field reference given a DMO proxy, a property name, and
    * an index for the property.  An indexed getter method will be used to
    * resolve the field.
    *
    * @param   dmo
    *          DMO proxy.
    * @param   property
    *          Target property name.
    * @param   index
    *          Index to pass to property's getter method at resolve time.
    *
    * @throws  IllegalArgumentException
    *          if field reference is not recognized.
    */
   public FieldReference(DataModelObject dmo, String property, NumberType index)
   {
      this(dmo, property, false, index, null, false);
   }

   /**
    * Create a new field reference given a DMO proxy, a property name, and an index for the
    * property. An indexed getter method will be used to resolve the field.
    *
    * @param   dmo
    *          DMO proxy.
    * @param   property
    *          Target property name.
    * @param   uppercase
    *          {@code true} to force a {@code character} value returned by {@link #getObject} to 
    *          be uppercased/right-trimmed; {@code false} to leave the result unchanged.
    * @param   index
    *          Index to pass to property's getter method at resolve time.
    *
    * @throws  IllegalArgumentException
    *          if field reference is not recognized.
    */
   public FieldReference(DataModelObject dmo,
                         String property,
                         boolean uppercase,
                         NumberType index)
   {
      this(dmo, property, uppercase, index, null, false);
   }

   /**
    * Create a new field reference given a DMO proxy, a property name, and an index expression for the
    * property. An indexed getter method will be used to resolve the field.
    *
    * @param   dmo
    *          DMO proxy.
    * @param   property
    *          Target property name.
    * @param   uppercase
    *          {@code true} to force a {@code character} value returned by {@link #getObject} to
    *          be uppercased/right-trimmed; {@code false} to leave the result unchanged.
    * @param   indexExpr
    *          Index expression which is evaluated to pass to property's getter method at resolve time.
    *
    * @throws  IllegalArgumentException
    *          if field reference is not recognized.
    */
   public FieldReference(DataModelObject dmo,
                         String property,
                         boolean uppercase,
                         Supplier<NumberType> indexExpr)
   {
      this(dmo, property, uppercase, null, indexExpr, false);
   }
   
   /**
    * Create a new field reference given a DMO proxy, a property name, an index for the
    * property, and a flag indicating whether or not to use bulk. Allows for explicit use
    * of {@code bulk}. This constructor does not support forcing a {@code character} value 
    * returned by {@link #getObject} to be uppercased/right-trimmed; 
    *
    * @param   dmo
    *          DMO proxy.
    * @param   property
    *          Target property name.
    * @param   index
    *          index which, when negative, will be passed as null to constructor.
    * @param   bulk
    *          When true the bulk getter/setter will be used.
    *
    * @throws  IllegalArgumentException
    *          if field reference is not recognized.
    */
   public FieldReference(DataModelObject dmo,
                         String property,
                         int index,
                         boolean bulk)
   {
      this(dmo, property, false, index < 0 ? null : new integer(index), null, bulk);
   }

   /**
    * Create a new field reference given a DMO proxy, a property name, an index expression for the
    * property, and a flag indicating whether or not to use bulk. Allows for explicit use
    * of {@code bulk}. This constructor does not support forcing a {@code character} value
    * returned by {@link #getObject} to be uppercased/right-trimmed;
    *
    * @param   dmo
    *          DMO proxy.
    * @param   property
    *          Target property name.
    * @param   indexExpr
    *          Index expression which is evaluated to pass to property's getter method at resolve time.
    * @param   bulk
    *          When true the bulk getter/setter will be used.
    *
    * @throws  IllegalArgumentException
    *          if field reference is not recognized.
    */
   public FieldReference(DataModelObject dmo,
                         String property,
                         Supplier<NumberType> indexExpr,
                         boolean bulk)
   {
      this(dmo, property, false, null, indexExpr, bulk);
   }

   /**
    * Create a new field reference given a DMO proxy, a property name, an index for the
    * property, and a flag indicating whether or not to use bulk. Allows for explicit use
    * of {@code bulk}.
    *
    * @param   dmo
    *          DMO proxy.
    * @param   property
    *          Target property name.
    * @param   uppercase
    *          {@code true} to force a {@code character} value returned by {@link #getObject} to
    *          be uppercased/right-trimmed; {@code false} to leave the result unchanged.
    * @param   index
    *          index which, when negative, will be passed as null to constructor.
    * @param   bulk
    *          When true the bulk getter/setter will be used.
    *
    * @throws  IllegalArgumentException
    *          if field reference is not recognized.
    */
   public FieldReference(DataModelObject dmo,
                         String property,
                         boolean uppercase,
                         NumberType index,
                         boolean bulk)
   {
      this(dmo, property, uppercase, index, null, bulk);
   }

   /**
    * Create a new field reference given a DMO proxy, a property name, an index expression for the
    * property, and a flag indicating whether or not to use bulk. Allows for explicit use
    * of {@code bulk}.
    *
    * @param   dmo
    *          DMO proxy.
    * @param   property
    *          Target property name.
    * @param   uppercase
    *          {@code true} to force a {@code character} value returned by {@link #getObject} to
    *          be uppercased/right-trimmed; {@code false} to leave the result unchanged.
    * @param   indexExpr
    *          Index expression which is evaluated to pass to property's getter method at resolve time.
    * @param   bulk
    *          When true the bulk getter/setter will be used.
    *
    * @throws  IllegalArgumentException
    *          if field reference is not recognized.
    */
   public FieldReference(DataModelObject dmo,
                         String property,
                         boolean uppercase,
                         Supplier<NumberType> indexExpr,
                         boolean bulk)
   {
      this(dmo, property, uppercase, null, indexExpr, bulk);
   }
   
   /**
    * Create a new field reference given a DMO proxy, a property name, and an index / index expression for the
    * property. For extent fields, an indexed getter method will be used to resolve the field
    * unless {@code bulk} is {@code true} and {@code index} is {@code null}.
    *
    * @param   dmo
    *          DMO proxy.
    * @param   property
    *          Target property name.
    * @param   uppercase
    *          {@code true} to force a {@code character} value returned by {@link #getObject} to 
    *          be uppercased/right-trimmed; {@code false} to leave the result unchanged.
    * @param   index
    *          Index to pass to property's getter method at resolve time. Can be <code>null</code> if there is
    *          no index or <code>indexExpr</code> is specified.
    * @param   indexExpr
    *          Index expression which is evaluated to pass to property's getter method at resolve time. Not
    *          used is <code>index</code> is not <code>null</code>.
    * @param   bulk
    *          When the reference is an EXTENT field and {@code index} is {@code null} the bulk
    *          getter/setter will be used.
    *
    * @throws  IllegalArgumentException
    *          if field reference is not recognized.
    */
   public FieldReference(DataModelObject dmo,
                         String property,
                         boolean uppercase,
                         NumberType index,
                         Supplier<NumberType> indexExpr,
                         boolean bulk)
   {
      RecordBuffer definition = ((BufferReference) dmo).definition();
      RecordBuffer buffer = RecordBuffer.get(dmo);
      if (definition != buffer)
      {
         property = definition.getBoundPropertyMappings().get(property);
         dmo = buffer.getDMOProxy();
      }
      
      this.dmo = dmo;
      this.proxy = dmo;
      this.property = property;
      this.dmoIface = buffer.getDMOInterface();
      
      // get getter/setter either for an extent component or extent field
      this.extent = null;
      this.bulk = bulk;
      // We mark this as false because there are more conditions that need to be taken into consideration.
      this.isBulkComputed = false;
      this.getter = null;
      this.isIdAccessor = ID.equals(property);
      this.setter = null;
      this.index = index != null ? index.intValue() : null;
      this.indexExpr = index != null ? null : indexExpr;
      this.uppercase = uppercase;
   }
   
   /**
    * Create a new field reference given a DMO interface and property name.
    * A non-indexed getter method will be used to resolve the field.  A DMO
    * instance must be provided at resolution time, using the {@link
    * #get(Record)} variant of the <code>get</code> method.
    * <p>
    * Note that the created instance can be used to read the related field only.
    * 
    * @param   database
    *          The associated database.
    * @param   dmoIface
    *          Interface of the target DMO.
    * @param   property
    *          Target property name
    */
   FieldReference(Database database, Class<? extends DataModelObject> dmoIface, String property)
   {
      this.database = database;
      this.property = property;
      this.dmoIface = dmoIface;
      this.getter = null;
      this.isIdAccessor = ID.equals(property);
      this.extent = null;
      this.setter = null;
      this.index = null;
      this.indexExpr = null;
      this.isSetterComputed = true;
      this.isBulkComputed = true;
      this.isExtentComputed = true;
      
      Property propMeta = DmoMetadataManager.getDmoInfo(dmoIface).getFieldInfo(property);
      if (propMeta._isCharacter && !propMeta.caseSensitive)
      {
         this.uppercase = true;
      }
   }
   
   /**
    * Specialized copy constructor which copies a non-indexed field reference and applies an
    * index to the copy.
    * 
    * @param   fieldRef
    *          Original, non-indexed field reference. Although the field is non-indexed, this
    *          should be a reference to an extent field.
    * @param   index
    *          Zero-based index to be used as the subscript into the extent field represented by the copy.
    *          Use {@code null} for bulk access.
    */
   public FieldReference(FieldReference fieldRef, Integer index)
   {
      this(fieldRef, index, fieldRef.extent, fieldRef.property);
   }
   
   /**
    * Specialized copy constructor which copies a non-indexed field reference and applies an
    * index to the copy.
    * 
    * @param   fieldRef
    *          Original, non-indexed field reference. Although the field is non-indexed, this
    *          should be a reference to an extent field.
    * @param   index
    *          Zero-based index to be used as the subscript into the extent field represented by the copy.
    *          Use {@code null} for bulk access.
    * @param   origExtent
    *          The original value of the extent. In case of denormalized fields the {@code fieldRef} might
    *          lose this value.
    * @param   origPropName
    *          The original property name. In case of denormalized fields the {@code fieldRef} might
    *          lose this value.
    */
   public FieldReference(FieldReference fieldRef, Integer index, Integer origExtent, String origPropName)
   {
      RecordBuffer buffer = RecordBuffer.get((DataModelObject) fieldRef.dmo);
      this.bulk = (origExtent != null) && (index == null);
      this.isBulkComputed = true;
      
      this.dmoIface = buffer.getDMOInterface();
      this.database = fieldRef.database;
      this.property = (origPropName == null || origPropName.isEmpty()) ? fieldRef.property : origPropName;
      this.getter = null;
      this.isIdAccessor = ID.equals(property);
      this.setter = null;
      this.uppercase = fieldRef.uppercase;
      this.index = index;
      this.indexExpr = null;
      this.extent = origExtent;
      this.dmo = fieldRef.dmo;
      this.proxy = fieldRef.proxy;
      this.legacyName = fieldRef.legacyName;
   }
   
   /**
    * Create a new field reference given a property name and a DMO proxy. This method is for
    * internal package use. The referenced field can only be read.
    * 
    * @param   buffer
    *          Record buffer which backs this field reference's DMO proxy.
    * @param   property
    *          Target property name.
    */
   FieldReference(RecordBuffer buffer, String property)
   {
      this(buffer.getDatabase(), buffer.getDMOInterface(), property); // TODO: OM: caching Database obj?
      this.proxy = buffer.getDMOProxy();
   }
   
   /**
    * Convenience method to produce a <code>BaseDataType</code> instance
    * initialized to the unknown value.  An attempt is made to return an
    * object of the correct type (i.e., the getter method's return type).
    * If this fails for some reason, a new instance of <code>unknown</code>
    * is returned.
    *
    * @param   getter
    *          Getter method whose return type is used to determine the type
    *          of object to return.
    *
    * @return  An object whose type matches the return type of the getter
    *          method, or an instance of <code>unknown</code>.
    */
   static BaseDataType unknownValue(Method getter)
   {
      BaseDataType value;
      if (getter.getName().equals("primaryKey"))
      {
         return new rowid();
      }
      try
      {  
            value = (BaseDataType) BaseDataTypeFactory.instantiate(getter.getReturnType());
            value.setUnknown();
      }
      catch (InstantiationException exc)
      {
         value = new unknown();
      }
      catch (ReflectiveOperationException exc)
      {
         throw new RuntimeException(exc);
      }
      
      return value;
   }
   
   
   
   /**
    * Determine the data type that will be returned when this field reference
    * is resolved.
    *
    * @return  Class of the appropriate data type.
    */
   public Class<?> getType()
   {  
      return getGetter().getReturnType();
   }
   
   /**
    * Resolve the field reference's value, bracketed by calls to the error
    * manager to first enable, then disable, warning mode.  This is meant to
    * support the need to continue processing after resolution of a field
    * reference fails, due to an empty record buffer.
    *
    * @return  Current value of the target property in the referent, or the
    *          unknown value if this resolution fails.
    */
   public BaseDataType getValue()
   {
      boolean warning = ErrorManager.isWarningMode();
      
      if (!warning)
      {
         ErrorManager.warningModeEnable();
      }
      
      try
      {
         return get();
      }
      finally
      {
         if (!warning)
         {
            ErrorManager.warningModeDisable();
         }
      }
   }
   
   /**
    * Resolve the current value of the referent field by invoking the getter
    * method for the target property.  Note that this may return the unknown
    * value.
    *
    * @return  Current value of the target property in the referent.
    */
   public BaseDataType resolve()
   {
      return get();
   }
   
   /**
    * Resolve the current value of the referent field by invoking the getter
    * method for the target property.  Note that this may return the unknown
    * value.
    *
    * @return  Current value of the target property in the referent.
    *
    * @throws  ErrorConditionException
    *          if any persistence level error occurs getting the value from
    *          the DMO.
    * @throws  IllegalStateException
    *          if the backing property is not read accessible (i.e., no getter
    *          method was found).
    * @throws  RuntimeException
    *          if the backing, getter method cannot be accessed or if there
    *          is some other error at the invocation target.
    */
   public BaseDataType get()
   {
      return (BaseDataType) getObject();
   }
   
   /**
    * Resolve the current value of the referent field by invoking the getter method for the 
    * target property. This must be used only in case of extent fields.
    *
    * @return  Current value of the target property in the referent.
    *
    * @throws  ErrorConditionException
    *          if any persistence level error occurs getting the value from the DMO.
    * @throws  IllegalStateException
    *          if the backing property is not read accessible (i.e., no getter method was found).
    * @throws  RuntimeException
    *          if the backing, getter method cannot be accessed or if there is some other error at
    *          the invocation target.
    */
   public BaseDataType[] getExtentValues()
   {
      if (getExtent() == null)
      {
         throw new RuntimeException("Trying to retrieve extent data for a non-extent field.");
      }
      
      return (BaseDataType[]) getObject();
   }
   
   /**
    * Returns index used with the DMO indexed getter method.
    *
    * @return index used with the DMO indexed getter method or <code>-1</code> if the getter is
    *         not indexed.
    */
   public int getIndex()
   {
      if (index != null)
      {
         return index;
      }
      else if (indexExpr != null)
      {
         NumberType num = indexExpr.get();
         return num.isUnknown() ? -1 : num.intValue();
      }

      return -1;
   }
   
   /**
    * Get the DMO or proxy instance associated with this field reference.
    *
    * @return  DMO.
    */
   public Object getDMO()
   {
      return dmo;
   }
   
   /**
    * Get property name (converted field name).
    *
    * @return property name.
    */
   public String getProperty()
   {
      return property;
   }
   
   /**
    * Get legacy field name as it is defined in 4GL.
    *
    * @return field name.
    */
   public String getLegacyFieldName()
   {
      if (legacyName == null)
      {
         // lazy field init here
         RecordBuffer buffer = RecordBuffer.get((DataModelObject)dmo);
         if (buffer != null)
         {
            legacyName = buffer.getDmoInfo().getFieldInfo(property).legacy;
         }
      }

      return legacyName != null && legacyName.isEmpty() ? legacyName : property;
   }
   
   /**
    * Set the current value of the referent field by invoking the setter
    * method for the target property.
    *
    * @param   value
    *          New value to set into the field represented by this object.
    *
    * @throws  ErrorConditionException
    *          if this field reference represents an element in an array, but
    *          the index provided at construction is the unknown value;
    *          if any persistence-level error occurs setting the value into
    *          the DMO, including validation errors.
    * @throws  IllegalStateException
    *          if the backing property is not write accessible (i.e., no
    *          setter method was found).
    * @throws  RuntimeException
    *          if the backing, getter method cannot be accessed or if there
    *          is some other error at the invocation target.
    */
   public void set(BaseDataType value)
   {
      set(new BaseDataType[] { value });
   }
   
   /**
    * Set the current value of the referent field by invoking the setter method for the target 
    * property.
    * <p>
    * For non-extent fields, the value is placed on the first position in the array. For extent
    * fields, the value is the actual value which needs to be set for the array.
    * 
    * @param   value
    *          New value to set into the field represented by this object.
    *
    * @throws  ErrorConditionException
    *          if this field reference represents an element in an array, but the index provided 
    *          at construction is the unknown value; if any persistence-level error occurs setting
    *          the value into the DMO, including validation errors.
    * @throws  IllegalStateException
    *          if the backing property is not write accessible (i.e., no setter method was found).
    * @throws  RuntimeException
    *          if the backing, getter method cannot be accessed or if there is some other error at
    *          the invocation target.
    */
   public void set(BaseDataType[] value)
   {
      Method setter = getSetter();
      if (setter == null)
      {
         throw new IllegalStateException(
                  property + " property is not write-accessible [" +
                  dmo.getClass().getName() + "]");     
      }
      
      Object[] args;
      
      // if we are not extent or if we have a subscript, then the value is not an array
      if (getExtent() == null || hasIndex())
      {
         args = new Object[] { value[0] };
      }
      else
      {
         args = new Object[] { value };
      }
      
      // Handle extent fields.
      if (hasIndex())
      {
         int idx = getIndex();

         // An unknown value index constitutes an error.
         if (idx == -1)
         {
            ErrorManager.recordOrThrowError(
               26,
               "Array subscript 0 is out of range");
            
            return;
         }
         
         // Set up arguments, including index.
         args = new Object[] { idx, args[0] };
      }
      
      // Invoke the setter method on the proxy.
      try
      {
         Utils.invoke(setter, dmo, args);
      }
      catch (InvocationTargetException exc)
      {
         handleException(exc);
         
         Throwable cause = exc.getCause();
         
         if (LOG.isLoggable(Level.FINE))
         {
            LOG.log(Level.FINE, "Error setting value into field", cause);
         }
         
         if (cause instanceof NumberedException)
         {
            ErrorManager.recordOrThrowError((NumberedException) cause);
         }
         else if (cause instanceof ConditionException)
         {
            throw (ConditionException) cause;
         }
         else
         {
            // represents programming error
            throw new RuntimeException(cause);
         }
      }
      catch (IllegalAccessException | IllegalArgumentException exc)
      {
         // represents programming or JVM configuration error
         throw new RuntimeException(exc);
      }
   }
   
   /**
    * Return a string representation of this field reference, in the form:
    * <pre>
    * alias.property
    * </pre>
    * or, if indexed:
    * <pre>
    * alias.property[index]
    * </pre>
    * If the field reference is to a case-insensitive character field, the result will be
    * enclosed in the <code>upper()</code> function.
    * 
    * @return  String representation of this object. This may be the empty string if the field
    *          reference has not yet been realized (i.e., no DMO has been assigned).
    */
   public String toString()
   {
      if (dmo == null)
      {
         // field reference is unrealized
         return ((BufferImpl) proxy).doGetName() + "." + property;
      }
      
      String alias = getParentBuffer().getDMOAlias();
      StringBuilder buf = new StringBuilder();
      if (uppercase)
      {
         buf.append("upper(");
      }
      buf.append(alias);
      buf.append('.');
      buf.append(property);
      
      int i = getIndex();
      if (i >= 0)
      {
         buf.append('[');
         buf.append(i);
         buf.append(']');
      }
      
      if (uppercase)
      {
         buf.append(")");
      }
      
      return buf.toString();
   }
   
   /**
    * Get full converted name of the associated field in the format {@code alias_name.field_name}.
    * If this is a component of an extent field, the index is also added:
    * {@code alias_name.field_name[index]}.
    *
    * @return see above.
    */
   public String getFullFieldName()
   {
      StringBuilder sb = new StringBuilder();
      String legacyName = getParentBuffer().getLegacyName();
      sb.append(legacyName);
      sb.append('.');
      sb.append(property);
      
      if (getExtent() != null && hasIndex())
      {
         if (index != null)
         {
            int idx = getIndex();
            if (idx != -1)
            {
               sb.append('[').append(idx).append(']');
            }
         }
         else
         {
            // indexExpr != null
            sb.append("[expr]");
         }
      }
      
      return sb.toString();
   }
   
   /**
    * Get the interface of the DMO associated with this field reference.
    *
    * @return  DMO interface.
    */
   public Class<? extends DataModelObject> getDMOInterface()
   {
      return (Class<? extends DataModelObject>) getGetter().getDeclaringClass();
   }
   
   /**
    * Indicate whether resolved character value will be uppercased/right-trimmed.
    *
    * @return  Value of flag.
    */
   public boolean isUpperCase()
   {
      return uppercase;
   }
   
   /**
    * Returns the record buffer which contains the referent DMO.
    * <p>
    * <strong>Note:</strong> this method only should be invoked on instances of this class which
    * were constructed with a DMO proxy (as opposed to a DMO implementation class instance).
    *
    * @return  See above
    * 
    * @throws  NullPointerException
    *          if this field reference was not constructed with a DMO proxy instance.
    */
   public RecordBuffer getParentBuffer()
   {
      if (proxy == null)
      {
         throw new NullPointerException("DMO proxy not available");
      }
      
      if (buffer == null)
      {
         buffer = RecordBuffer.get(proxy);
      }
      
      return buffer;
   }
   
   /**
    * Returns <code>true</code> if index is specified (either as a scalar or as an expression).
    *
    * @return <code>true</code> if index is specified (either as a scalar or as an expression).
    */
   public boolean hasIndex()
   {
      return index != null || indexExpr != null;
   }
   
   /**
    * Return field's extent, if field reference is to an extent field, else <code>null</code>.
    * 
    * @return  Field's extent or <code>null</code>.
    */
   public Integer getExtent()
   {
      if (!isExtentComputed)
      {
         isExtentComputed = true;
         extent = getExtent(dmoIface, property);
      }
      
      return extent; 
   }
   
   /**
    * Test the equality with another object.
    * 
    * @param   o
    *          The object to test against.
    * 
    * @return {@code true} if both objects refer the same field. 
    */
   @Override
   public boolean equals(Object o)
   {
      if (this == o)
      {
         return true;
      }
      if (o == null || getClass() != o.getClass())
      {
         return false;
      }
      FieldReference that = (FieldReference) o;
      return uppercase == that.uppercase                   &&
             Objects.equals(property, that.property)       &&
             Objects.equals(getExtent(), that.getExtent()) &&
             Objects.equals(index, that.index)             &&
             Objects.equals(indexExpr, that.indexExpr)     &&
             Objects.equals(dmo, that.dmo);
   }
   
   /**
    * Compute the hash code for this object.
    * 
    * @return  the hash code for this object.
    */
   @Override
   public int hashCode()
   {
      return Objects.hash(property, getExtent(), index, indexExpr, dmo, uppercase);
   }
   
   /**
    * Resolve the current value of the referent field by invoking the getter
    * method for the target property, using the given DMO as the target.
    * Note that this may return <code>null</code> (or the unknown value for
    * <code>BaseDataTypes</code> return types).
    *
    * @param   dmo
    *          Target object which is used as the method's referent.
    *
    * @return  Current value of the target property in the referent or
    *          <code>null</code> if no value exists.
    *
    * @throws  ErrorConditionException
    *          if any persistence level error occurs getting the value from
    *          the DMO.
    * @throws  IllegalStateException
    *          if the backing property is not read accessible (i.e., no getter
    *          method was found).
    * @throws  RuntimeException
    *          if the backing, getter method cannot be accessed or if there
    *          is some other error at the invocation target.
    */
   Object get(DataModelObject dmo)
   {
      this.dmo = dmo;
      
      return getObject();
   }

   /**
    * Resolve the current value of the referent field by invoking the getter
    * method for the target property, using the given DMO as the target.
    * Note that this may return <code>null</code> (or the unknown value for
    * <code>BaseDataTypes</code> return types).
    *
    * @param   dmo
    *          Target object which is used as the method's referent.
    *
    * @return  Current value of the target property in the referent or
    *          <code>null</code> if no value exists.
    *
    * @throws  ErrorConditionException
    *          if any persistence level error occurs getting the value from
    *          the DMO.
    * @throws  IllegalStateException
    *          if the backing property is not read accessible (i.e., no getter
    *          method was found).
    * @throws  RuntimeException
    *          if the backing, getter method cannot be accessed or if there
    *          is some other error at the invocation target.
    */
   Object get(Record dmo)
   {
      this.dmo = dmo;
      
      return getObject();
   }
   
   /**
    * Resolve the current value of the referent field by invoking the getter
    * method for the target property, using the given DMO as the target.
    * Note that this may return <code>null</code> (or the unknown value for
    * <code>BaseDataTypes</code> return types).
    * <p>
    * Unlike the {@link #get(com.goldencode.p2j.persist.Record)} method, it
    * doesn't make this field reference use the passed DMO for future calls.
    *
    * @param   dmo
    *          Target object which is used as the method's referent.
    *
    * @return  Current value of the target property in the referent or
    *          <code>null</code> if no value exists.
    *
    * @throws  ErrorConditionException
    *          if any persistence level error occurs getting the value from
    *          the DMO.
    * @throws  IllegalStateException
    *          if the backing property is not read accessible (i.e., no getter
    *          method was found).
    * @throws  RuntimeException
    *          if the backing, getter method cannot be accessed or if there
    *          is some other error at the invocation target.
    */
   Object getTemp(Record dmo)
   {
      Object oldDmo = this.dmo;
      
      try
      {
         return get(dmo);
      }
      finally
      {
         this.dmo = oldDmo;
      }
   }
   
   /**
    * Resolve the current value of the referent field by invoking the getter
    * method for the target property, using the given DMO as the target.
    * Note that this may return <code>null</code> (or the unknown value for
    * <code>BaseDataTypes</code> return types).<p>
    * Unlike the {@link #get(com.goldencode.p2j.persist.DataModelObject)} method, it
    * doesn't make this field reference use the passed DMO for future calls.
    *
    * @param   dmo
    *          Target object which is used as the method's referent.
    *
    * @return  Current value of the target property in the referent or
    *          <code>null</code> if no value exists.
    *
    * @throws  ErrorConditionException
    *          if any persistence level error occurs getting the value from
    *          the DMO.
    * @throws  IllegalStateException
    *          if the backing property is not read accessible (i.e., no getter
    *          method was found).
    * @throws  RuntimeException
    *          if the backing, getter method cannot be accessed or if there
    *          is some other error at the invocation target.
    */
   Object getTemp(DataModelObject dmo)
   {
      Object oldDmo = this.dmo;

      try
      {
         return get(dmo);
      }
      finally
      {
         this.dmo = oldDmo;
      }
   }
   
   /**
    * Create an instance of unknown value of the correct type for this field reference.
    * 
    * @return  Unknown value.
    */
   BaseDataType unknownValue()
   {
      return FieldReference.unknownValue(getGetter());
   }
   
   /**
    * Look up the extent for the specified property and interface. Introspection is used 
    * the first time this method is invoked for a new DMO type;  thereafter, the extent is 
    * retrieved from a cache.
    *
    * @param   dmoIface
    *          Referent DMO interface.
    * @param   property
    *          Name of target property (i.e., field).
    *
    * @return  Extent, or <code>null</code> if <code>property</code> does not match any 
    *          property name in the DMO API or is not an extent property.
    */
   private Integer getExtent(Class<? extends DataModelObject> dmoIface, String property)
   {
      Map<String, Integer> propMap = PropertyHelper.extentsByProperty(dmoIface);
      
      return propMap.get(property);
   }
   
   /**
    * Look up the getter method for the specified property and interface. Introspection is used
    * the first time this method is invoked for a new DMO type;  thereafter, the getter is
    * retrieved from a cache.
    * <p>
    * For the reserved {@code id} property, {@link Record} interface is substituted for
    * {@code dmoIface}.
    * <p>
    * For extent properties the method is capable to return the indexed or the non-indexed getter.
    *
    * @return  Getter method, or {@code null} if {@code property} does not match any property name
    *          in the DMO API.
    */
   private Method getGetter()
   {
      if (isGetterComputed)
      {
         return getter;
      }
      
      isGetterComputed = true;
      if (!isBulkComputed)
      {
         isBulkComputed = true;
         bulk = bulk && (getExtent() != null) && (index == null);
      }
      
      Map<String, Method> propMap;
      if (bulk)
      {
         propMap = PropertyHelper.bulkLegacyGettersByProperty(dmoIface);
      }
      else
      {
         Class<?> iface = (ID.equals(property) ? Persistable.class : dmoIface);
         propMap = PropertyHelper.allGettersByProperty(iface);
      }
      
      Method method = propMap.get(property);
      if (method != null)
      {
         getter = method;
         return getter;
      }
      
      if (property.startsWith("_"))
      {
         for (Method m : TempRecord.class.getMethods())
         {
            if (m.getReturnType() != void.class &&
                m.getReturnType() != Void.class &&
                m.getName().equals(property))
            {
               if (LOG.isLoggable(Level.WARNING))
               {
                  LOG.log(Level.WARNING,
                          "Property '" + property + "' accessed through 'TempRecord' " +
                          "interface instead of converted legacy method.");
               }
               
               getter = m;
               return getter;
            }
         }
      }
      
      // sanity to make sure we have a getter (only getter for primary key, which is read-only)
      if (getter == null)
      {
         throw new IllegalArgumentException(
            "Invalid field reference [" + getParentBuffer().getDMOName() + "." +
            property + (index == null ? "" : ":" + index.toString()) +
            "]");
      }
      
      if (uppercase && !getter.getReturnType().equals(character.class))
      {
         throw new IllegalArgumentException(
            "Only a character-based FieldReference can be set to uppercase");
      }
      
      return null;
   }
   
   /**
    * Look up the setter method for the specified property and interface. Introspection is used
    * the first time this method is invoked for a new DMO type;  thereafter, the setter is
    * retrieved from a cache.
    * <p>
    * For extent properties the method is capable to return the indexed or the non-indexed setter.
    *
    * @return  Setter method, or {@code null} if {@code property} does not match any property name
    *          in the DMO API.
    */
   private Method getSetter()
   {
      if (isSetterComputed)
      {
         return setter;
      }
      
      isSetterComputed = true;
      if (!isBulkComputed)
      {
         isBulkComputed = true;
         bulk = bulk && (getExtent() != null) && (index == null);
      }
      
      Map<String, Method> propMap = bulk
            ? PropertyHelper.bulkLegacySettersByProperty(dmoIface)
            : PropertyHelper.allSettersByProperty(dmoIface);
      
      Method method = propMap.get(property);
      if (method != null)
      {
         setter = method;
         return setter;
      }
      
      if (property.startsWith("_"))
      {
         for (Method m : TempRecord.class.getMethods())
         {
            if ((m.getReturnType() == void.class || 
                 m.getReturnType() == Void.class) && 
                m.getName().equals(property))
            {
               if (LOG.isLoggable(Level.WARNING))
               {
                  LOG.log(Level.WARNING,
                          "Property '" + property + "' accessed through 'TempRecord' " +
                          "interface instead of converted legacy method.");
               }
               
               setter = m;
               return setter;
            }
         }
      }
      
      // sanity to make sure we have a setter
      if (setter == null && !isIdAccessor)
      {
         throw new IllegalArgumentException(
            "Invalid field reference [" + getParentBuffer().getDMOName() + "." +
            property + (index == null ? "" : ":" + index.toString()) +
            "]");
      }
      
      return null;
   }
   
   /**
    * Resolve the current value of the referent field by invoking the getter method for the target property.
    * <p>
    * Note that this may return {@code null} (or the unknown value for {@code BaseDataTypes} return types).
    *
    * @return  Current value of the target property in the referent or {@code null} if no value exists.
    *
    * @throws  ErrorConditionException
    *          if any persistence level error occurs getting the value from the DMO.
    * @throws  IllegalStateException
    *          if the backing property is not read accessible (i.e., no getter method was found).
    * @throws  RuntimeException
    *          if the backing, getter method cannot be accessed or if there is some other error at the
    *          invocation target.
    */
   private Object getObject()
   {   
      Method getter = getGetter();
      if (getter == null)
      {
         throw new IllegalStateException(property + " property is not read-accessible [" +
                                         dmo.getClass().getName() + "]");
      }
      
      Object[] args;
      
      // Handle extent fields.
      if (hasIndex())
      {
         int idx = getIndex();
         
         // An unknown value index propagates to an unknown value result.
         if (idx == -1)
         {
            return unknownValue(getter);
         }
         
         // Set index argument.
         args = new Object[] { idx };
      }
      else
      {
         args = null;
      }
      
      // invoke the getter method on the proxy and return the result
      try
      {
         // special case when the surrogate field 'id' is evaluated (and dmo represents the
         // proxy, not the underlying Record object)
         if (isIdAccessor && dmo == proxy)
         {
            return ((Buffer) dmo).rowID(); // use the wider rowid datatype
         }
         
         Object result = Utils.invoke(getter, dmo, args);
         
         if (!isIdAccessor && !(result instanceof BaseDataType))
         {
            Class<?> cls = result.getClass();
            if (cls == Long.class)
            {
               result = new int64((Long) result);
            }
            else if (cls == Integer.class)
            {
               result = new integer((Integer) result);
            }
            else if (cls == String.class)
            {
               result = new character((String) result);
            }
         }
         
         // if marked for uppercase, then we must uppercase and right trim the result.  This is a
         // special case where the result will be used in Progress-compatible comparisons.
         if (uppercase)
         {
            character c = (character) result;
            result = TextOps.toUpperCase(TextOps.rightTrim(c, " "));
         }
         
         if (result instanceof LargeObject)
         {
            ((LargeObject) result).setFieldReference(this);
         }
         
         return result;
      }
      catch (InvocationTargetException exc)
      {
         handleException(exc);
         
         Throwable cause = exc.getCause();
         
         if (LOG.isLoggable(Level.FINE))
         {
            LOG.log(Level.FINE, "Error getting value from field", cause);
         }
         
         if (cause instanceof NumberedException)
         {
            ErrorManager.recordOrThrowError((NumberedException) cause);
         }
         else if (cause instanceof ConditionException)
         {
            throw (ConditionException) cause;
         }
         else
         {
            // Represents programming error.
            throw new RuntimeException(cause);
         }
         
         return unknownValue(getter);
      }
      catch (IllegalAccessException exc)
      {
         // Represents programming or JVM configuration error.
         throw new RuntimeException(exc);
      }
   }
   
   /**
    * Handle a thread interruption or database connection error specially,
    * otherwise return normally.
    * 
    * @param   exc
    *          Exception to be inspected.
    * 
    * @see     DBUtils#handleException(Database, Exception)
    */
   private void handleException(Exception exc)
   {
      if (database == null && dmo instanceof DataModelObject)
      {
         RecordBuffer buffer = RecordBuffer.get((DataModelObject) dmo);
         database = buffer.getDatabase();
      }
      
      DBUtils.handleException(database, exc);
   }
}