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