# BufferFieldImpl.java

    /*
    ** Module   : BufferFieldImpl.java
    ** Abstract : Implementation of the BufferField interface
    **
    ** Copyright (c) 2013-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 ECF 20150315 Created initial version by refactoring all support specific to BUFFER-FIELD
    **                  4GL compatibility from the FieldReference class. See that class for a full
    **                  history. This refactoring was done to eliminate the memory footprint and
    **                  overhead of the BUFFER-FIELD implementation from the many simpler use cases
    **                  for the FieldReference class.
    ** 002 ECF 20150320 Javadoc fix.
    ** 003 OM  20150415 Added LITERAL-QUESTION attribute implementation.
    ** 004 SVL 20150503 Added bufferHandle.
    ** 005 ECF 20151208 Fixed getDataType, which was reporting the data type of extent fields with
    **                  trailing brackets.
    ** 006 ECF 20160106 Fixed extent method, which would fail when the buffer was empty.
    ** 007 ECF 20160227 Reworked extent field implementation to support denormalized extent fields.
    ** 008 OM  20160613 Fixed LITERAL-QUESTION attribute implementation based on new researches.
    ** 009 IAS 20160714 Use legacy name instead of DMO alias.
    ** 010 EVL 20161003 Fix for initial() character return value.  If the backing variable is unknown
    **                  the returned character value should be unknown as well.
    ** 011 SVL 20170126 Added caseSensitive.
    ** 012 SVL 20171025 Field attributes are now buffer-specific.
    ** 013 GES 20180418 POSITION is now backed by the original schema value (if available).
    ** 014 OM  20181114 Added NamedSerializable interface implementation.
    ** 015 OM  20190405 Added stubs for XML persistence.
    ** 016 CA  20190514 Added DEFAULT-STRING stub.
    ** 017 SVL 20190614 Changed access modifiers of getProperty and getDMOProxy.
    ** 018 OM  20190611 Improved support for before and before temp-tables.
    ** 019 SBI 20190728 Implemented getHelp.
    **     SBI 20190814 WIDTH-CHARS should return a value even if the back end record is not available.
    ** 020 IAS 20200221 Added AVAILABLE attr. support, fixed BUFFER-VALUE() attr. support.
    ** 021 IAS 20200402 Added DEFAULT-STRING & DECIMALS attrs. support, fixed INITIAL value parsing for date
    **     CA  20200427 Allow bulk assign support for BUFFER-VALUE and ::, in case of extent fields.
    ** 022 GES 20200628 Added DEFAULT-VALUE.  Cleaned up code formatting and added comments.
    **     SVL 20200916 getProperty became public. Fixed position() for temp tables.
    **     CA  20201210 A small fix for WIDTH-CHARS attribute, when the field is extent.
    **     OM  20201030 Invalid attribute API support for getters/setters. Added DECIMALS attribute.
    **                  Implemented several missing options.
    **     OM  20210309 Do not use DmoMeta as key in TableMapper because temp-tables share the same DmoMeta.
    **     OM  20210323 Using delegation to local TableMapper.LFI instead of multiple map lookup in
    **                  RecordBuffer.
    **     OM  20210312 Saved reference to parent buffer in case of LOBs obtained by dereference.
    **     VVT 20210428 setFormat(String) fixed: now checks the format before setting it.
    **                  Missing @Override annotations added.
    **     VVT 20210428 setFormat(String) changed to be more uniform: the LogicalFormatParser
    **                  is now used to check logical formats.
    **     CA  20210628 Fixed DATETIME and DATETIME-TZ toStringExport() usage.
    **     VVT 20211018 New implementaton methods added: getWidth(), setWidth(), getHeight() and setHeight() to match
    **                  the changes in the Sizeable interface. See: #5594-45.
    **     CA  20211204 Fixed BUFFER-FIELD behavior, they need to behave like real resources, with their lifetime
    **                  linked with the parent BUFFER resource.
    **     CA  20220402 Fixed changeValue, to consider the case when the value is a BDT proxy returned by a
    **                  DYNAMIC-FUNCTION call.
    **     IAS 20220609 Consider character field format on assignment.
    **     OM  20220713 Fixed validateBufferFieldIndex() for denormalized fields. value(int, boolean) will locate
    **                  and invoke the getter method of the correct denormalied property.
    **     OM  20220727 Improve debugging.
    **     CA  20221006 Refactored TableMapper for temp-tables to keep a cache of LegacyTableInfo and the mutable
    **                  state in MutableFieldInfo, for the actual TempTable instance. Refs #6825
    **                  UNIQUE-ID and ADM-DATA are kept as Java types instead of BDT.  Refs #6827
    **                  BUFFER-FIELD's instantiating procedure must be the BUFFER's, and not when it gets lazily
    **                  created.
    **     CA  20221006 Fixed a NPE related to instantiating-procedure in 'changeValue' (surrogate instances being
    **                  created).
    **     TJD 20220504 Upgrade do Java 11 minor changes
    **     OM  20221207 Added createFieldReference() method.
    **     RFB 20221223 Minor javadoc repair
    **     CA  20220531 If the BUFFER is in 'unknown mode' (due to an outer join not finding a record), do not
    **                  raise 'no record is available' ERROR condition.
    **     OM  20220604 Improved dereferenciation support.
    **     OM  20220706 Implemented or fix runtime for XML-NODE-TYPE, DBNAME, TABLE, KEY, READ-ONLY and VIEW-AS
    **                  attributes. Added conversion methodto P2JField.
    **     OM  20220914 Added MAX-WIDTH support for CHARACTER fields.
    **     CA  20221006 Fixed a NPE related to instantiating-procedure in 'changeValue' (surrogate instances being
    **                  created).
    **     CA  20230110 Cache the mutable info at the temp-table and not in a separate map at TableMapper.
    **     CA  20230110 UNIQUE-ID is generated only when is read by the application.
    **     SVL 20230110 P2JIndex.components() provides direct access to the components array in order to improve
    **                  performance.
    **     HC  20230116 Replaced some handle usages with the actual wrapped resources for
    **                  performance.
    **     CA  20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
    ** 023 OM  20230303 Fixed behaviour of value() for 0-index extent fields.
    ** 024 CA  20230215 Improved other cases to reduce BDT usage from within FWD runtime.
    ** 025 OM  20230307 The original extent and property name were lost when denormalizing extent fields.
    ** 026 OM  20230320 Added getOriginalPropertyName() method. asP2JField() returns the original extent.
    ** 027 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 028 IAS 20230519 Fixed 'getXmlDataType' method.
    ** 029 AL2 20230608 Minor refactor to compute type and class only once in changeValue.
    ** 030 RAA 20230615 Refactored calls of newInstance with a value to use BaseDataTypeFactory.instantiate.
    ** 031 CA  20230928 Remove the type information for an unknown object field - this is required for dynamic
    **                  invocation, where 4GL allows a type match if the type is not set.
    ** 032 CA  20231031 BUFFER-FIELD handles associated with permanent database table fields can set their own,
    **                  private, value for certain attributes (like FORMAT, LABEL, XML-DATA-TYPE, etc)
    ** 033 CA  20231129 Directly set the INSTANTIATING-PROCEDURE referent, from the parent BUFFER's, instead of
    **                  passing it as a handle.
    ** 034 AI  20240312 Changed empty string to unknown when setting value of field if type is not of text type.
    ** 035 AL2 20240418 Refactored some legacy calls to use lfi.getExtent() instead of fieldRef.getExtent().
    ** 036 CA  20240423 Resolve the field's data-type only once.
    ** 037 OM  20240524 The initial attribute use the session format (for date related datatypes).
    ** 038 DDF 20240527 Changed recordOrShowError() to recordOrShowDatabaseError() in value() method.
    ** 039 CA  20241030 Cache the DATA-TYPE attribute.
    ** 040 ICP 20250123 Used integer and logical constants to leverage caches instances.
    */

    /*
    ** 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.ref.*;
    import java.lang.reflect.*;
    import java.util.*;
    import java.util.logging.*;

    import com.goldencode.p2j.convert.*;
    import com.goldencode.p2j.persist.orm.*;
    import com.goldencode.p2j.persist.serial.*;
    import com.goldencode.p2j.ui.*;
    import com.goldencode.p2j.ui.client.format.*;
    import com.goldencode.p2j.schema.*;
    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.ErrorManager;
    import com.goldencode.p2j.util.UniqueIdGenerator.*;
    import com.goldencode.p2j.util.logging.*;

    /**
     * Implementation of Progress 4GL BUFFER-FIELD methods and attributes, using a {@link FieldReference} as a
     * delegate worker.
     */
    public final class BufferFieldImpl
    extends HandleChain
    implements BufferField,
               Deletable
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(BufferField.class.getName());

       /** {@link FieldReference} delegate object */
       private final FieldReference fieldRef;

       /** Corresponds to ADM-DATA attribute. */
       private String admData = null;

       /** Corresponds to UNIQUE-ID attribute. */
       private Long uniqueID = null;

       /** The legacy info collected from table creation or modified afterwards. */
       private final TableMapper.LegacyFieldInfo lfi;

       /** Flag for mutable options. This happens when the field belongs to a temp-table. */
       private boolean mutableOptions = false;

       /** Flag indicating if the delete must be forced. */
       private boolean forceDelete = false;

       /** The key flag. Lazily initialized / computed. */
       private Boolean key = null;

       /** The current value for VIEW-AS attribute. */
       private String viewAs = BufferField.FILL_IN;

       /** The current value for READ-ONLY attribute. */
       private boolean readOnly = false;

       /** Explicit value of <code>LITERAL-QUESTION</code> attribute for database table fields. */
       private logical literalQuestion = null;

       /** Explicit value of <code>COLUMN-LABEL</code> attribute for database table fields. */
       private character columnLabel = null;

       /** Explicit value of <code>FORMAT</code> attribute for database table fields. */
       private character format = null;

       /** Explicit value of <code>HELP</code> attribute for database table fields. */
       private character help = null;

       /** Explicit value of <code>LABEL</code> attribute for database table fields. */
       private character label = null;

       /** Explicit value of <code>VALIDATE-EXPRESSION</code> attribute for database table fields. */
       private character validateExpression = null;

       /** Explicit value of <code>VALIDATE-MESSAGE</code> attribute for database table fields. */
       private character validateMessage = null;

       /** Explicit value of <code>SERIALIZE-HIDDEN</code> attribute for database table fields. */
       private logical serializeHidden = null;

       /** Explicit value of <code>SERIALIZE-NAME</code> attribute for database table fields. */
       private character serializeName = null;

       /** Explicit value of <code>XML-DATA-TYPE</code> attribute for database table fields. */
       private character xmlDataType = null;

       /** Explicit value of <code>XML-NODE-NAME</code> attribute for database table fields. */
       private character xmlNodeName = null;

       /** Explicit value of <code>XML-NODE-TYPE</code> attribute for database table fields. */
       private character xmlNodeType = null;

       /** The calculated field's data-type. */
       private character dataType = null;

       /**
        * 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   instProc
        *          The parent buffer's instantiating-procedure.
        * @param   dmo
        *          DMO proxy.
        * @param   property
        *          Target property name.
        */
       BufferFieldImpl(Object instProc, DataModelObject dmo, String property)
       {
          this(instProc, dmo, property, false, null, 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   instProc
        *          The parent buffer's instantiating-procedure.
        * @param   dmo
        *          DMO proxy.
        * @param   property
        *          Target property name.
        * @param   bulk
        *          When the reference is an EXTENT field and {@code index} is {@code null} the bulk
        *          getter/setter will be used.
        */
       BufferFieldImpl(Object instProc, DataModelObject dmo, String property, boolean bulk)
       {
          this(instProc, dmo, property, false, null, bulk);
       }

       /**
        * 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   instProc
        *          The parent buffer's instantiating-procedure.
        * @param   dmo
        *          DMO proxy.
        * @param   property
        *          Target property name.
        * @param   uppercase
        *          <code>true</code> to force a <code>character</code> value 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.
        * @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.
        */
       private BufferFieldImpl(Object instProc,
                               DataModelObject dmo,
                               String property,
                               boolean uppercase,
                               NumberType index,
                               boolean bulk)
       {
          this.fieldRef = new FieldReference(dmo, property, uppercase, index, bulk);
          lfi = computeLegacyInfo();

          this.instProc = instProc == null ? null : new WeakReference<>(instProc);
       }

       /**
        * Create an instance of this class which accesses a specific element of an extent field, given a field
        * reference for the same DMO property, which does not reference a specific element of that field.
        *
        * @param   fieldRef
        *          A reference to an extent field, which does not have an index into a specific element.
        * @param   index
        *          A one-based index into an extent field.
        * @param   lfi
        *          The legacy field info (used in case of denormalized fields)
        */
       private BufferFieldImpl(FieldReference fieldRef, int index, TableMapper.LegacyFieldInfo lfi)
       {
          this.fieldRef = new FieldReference(fieldRef, index - 1, lfi.getExtent(), lfi.original);
          this.lfi = lfi;

          setInstantiatingProcedure(null);
       }

       /**
        * Buffer field resources are always generated lazily, and are being deleted by the parent BUFFER.  For
        * this reason, they will not be processed at the external program where they are lazily created.
        *
        * @return   Always <code>false</code>.
        */
       @Override
       protected boolean processResource()
       {
          return false;
       }

       /**
        * Get the <code>name</code> attribute of a BUFFER-FIELD, which is the legacy name of the
        * field referenced by this object.
        *
        * @return  See above.
        */
       @Override
       public character name()
       {
          if (name == null)
          {
             // assign directly to the variable, rather than through the API, to avoid a 4052 error
             this.name = lfi.getLegacyName();
          }

          return super.name();
       }

       /**
        * Get the <code>name</code> attribute of handle as a string.
        *
        * @return See above.
        */
       @Override
       public String _name()
       {
          if (name == null)
          {
             // assign directly to the variable, rather than through the API, to avoid a 4052 error
             this.name = lfi.getLegacyName();
          }

          return super._name();
       }

       /**
        * Indicate one of the following conditions.
        *
        * @return  <code>true</code> if the query buffer has a record with a field available that
        *                            corresponds to this buffer-field object handle;
        *          <code>false</code> if the query buffer has a record with the field missing that
        *                             corresponds to this buffer-field object handle;
        *          Unknown value (?) if the query buffer associated with this buffer-field object
        *                            handle has no record..
        */
       @Override
       public logical available()
       {
          if (!getBuffer()._available())
          {
             return new logical();
          }
          // TODO: can 'false' result ever happen?
          return new logical(true);
       }

       /**
        * Conversion of EXTENT attribute (KW_EXTENT).
        *
        * Getter of EXTENT attribute.
        *
        * @return the value of EXTENT attribute.
        *
        */
       @Override
       public integer extent()
       {
          return integer.of(_extent());
       }

       /**
        * Get the INITIAL attribute for the field.
        * <p>
        * The value of the INITIAL schema field (which is always CHARACTER), formatted with the
        * buffer-field's format. If the INITIAL schema field has the Unknown value (?), the
        * value of the INITIAL attribute is the null string ("").
        *
        * @return   Current value of INITIAL attribute.
        */
       @Override
       public character initial()
       {
          BaseDataType baseDataType = initialValue();
          return baseDataType.isUnknown() ? new character()
                                          : character.valueOf(baseDataType.toString());//getFormat().getValue()));
       }

       /**
        * Conversion of STRING-VALUE attribute (KW_STR_VAL).
        *
        * Getter of STRING-VALUE attribute.
        *
        * The STRING-VALUE( ) method uses the format attribute to convert the buffer value to
        * a string.
        *
        * @return  current value of STRING-VALUE attribute.
        */
       @Override
       public character stringValue()
       {
          int extent = lfi.getExtent();
          if (extent == 0)
          {
             return character.valueOf(value().toString(getFormat().getValue()));
          }
          else
          {
             ErrorManager.recordOrShowError(26, "Array subscript 0 is out of range", false);

             String msg = "Unable to extract BUFFER-VALUE for field %s";
             String property = fieldRef.getProperty();
             String err = String.format(msg, property);
             ErrorManager.recordOrShowError(7366, err, false);

             return new character();
          }
       }

       /**
        * Conversion of STRING-VALUE attribute (KW_STR_VAL).
        *
        * Getter of STRING-VALUE attribute.
        *
        * The STRING-VALUE( ) method uses the format attribute to convert the buffer value to
        * a string.
        *
        * @param   index
        *          An integer expression representing a subscript, for fields that
        *          have extents.
        *
        * @return  current value of STRING-VALUE attribute.
        */
       @Override
       public character stringValue(int index)
       {
          return stringValue(new integer(index));
       }

       /**
        * Conversion of STRING-VALUE attribute (KW_STR_VAL).
        *
        * Getter of STRING-VALUE attribute.
        *
        * The STRING-VALUE( ) method uses the format attribute to convert the buffer value to
        * a string.
        *
        * @param    index
        *           An integer expression representing a subscript, for fields that
        *           have extents.
        *
        * @return   Current value of STRING-VALUE attribute.
        */
       @Override
       public character stringValue(NumberType index)
       {
          return character.valueOf(value(index).toString(getFormat().getValue()));
       }

       /**
        * Conversion of BUFFER-VALUE attribute (KW_BUF_VAL).
        *
        * Getter of current value of a buffer-field object. If you modify the BUFFER-VALUE attribute, Progress
        * sets the buffer-field object to the new value.
        *
        * @param   index
        *          A base-1 INTEGER expression representing a subscript, for fields that have extents.
        *
        * @return  Current value of a buffer-field object.
        */
       @Override
       public BaseDataType value(int index)
       {
          return value(index, true);
       }

       /**
        * Conversion of BUFFER-VALUE attribute (KW_BUF_VAL).
        *
        * Getter of current value of a buffer-field object. If you modify the
        * BUFFER-VALUE attribute, Progress sets the buffer-field object to the new value.
        *
        * @param   index
        *          A base-1 INTEGER expression representing a subscript, for fields that have extents.
        *
        * @return  Current value of a buffer-field object if the object is an extent field or
        *          <code>unknown</code> if it is a scalar one.
        */
       @Override
       public BaseDataType value(NumberType index)
       {
          if (!validateBufferFieldIndex(index))
          {
             return fieldRef.unknownValue();
          }

          return value(index.intValue(), false);
       }

       /**
        * Conversion of BUFFER-VALUE attribute (KW_BUF_VAL).
        *
        * Setter of current value of a buffer-field object. If you modify the
        * BUFFER-VALUE attribute, Progress sets the buffer-field object to the
        * new value.
        *
        * @param   index
        *          An INTEGER expression representing a subscript, for fields
        *          that have extents.
        * @param   newValue
        *          New value of a buffer-field object.
        *
        */
       @Override
       public void changeValue(int index, Object newValue)
       {
          changeValue(index, newValue, true);
       }

       /**
        * Conversion of BUFFER-VALUE attribute (KW_BUF_VAL).
        *
        * Setter of current value of a buffer-field object. If you modify the
        * BUFFER-VALUE attribute, Progress sets the buffer-field object to the
        * new value.
        *
        * @param   index
        *          An INTEGER expression representing a subscript, for fields
        *          that have extents.
        * @param   newValue
        *          New value of a buffer-field object.
        *
        */
       @Override
       public void changeValue(NumberType index, Object newValue)
       {
          if (!this.validateBufferFieldIndex(index))
          {
             return;
          }

          changeValue(index.intValue(), newValue, false);
       }

       /**
        * Conversion of BUFFER-VALUE attribute (KW_BUF_VAL).
        *
        * Getter of current value of a buffer-field object. If you modify the
        * BUFFER-VALUE attribute, Progress sets the buffer-field object to the new value.
        *
        * @return  Current value of a buffer-field object. If the field is an extent field,
        *          <code>unknown</code> value is returned.
        */
       @Override
       public BaseDataType value()
       {
          Buffer buf = getBuffer();
          if (buf != null && buf.valid())
          {
             if (buf._available())
             {
                if (lfi.getExtent() != 0)
                {
                   return fieldRef.unknownValue();
                }

                BaseDataType retVal = fieldRef.get();
                if (retVal instanceof LargeObject)
                {
                   ((LargeObject) retVal).setFieldReference(fieldRef);
                }
                else if (retVal instanceof object && retVal.isUnknown())
                {
                   // remove the type information
                   retVal = new object<>();
                }
                return retVal;
             }
             else if (((BufferImpl) buf).buffer().isUnknownMode())
             {
                return fieldRef.unknownValue();
             }
          }

          String bn = buf == null || !buf.valid() ? "<UNKNOWN>" : buf.name().toStringMessage();

          String message1 = String.format("No %s record is available", bn);
          ErrorManager.recordOrShowDatabaseError(91, message1, false, true);

          String message2 = String.format("Unable to extract BUFFER-VALUE for field %s",
                                          name().toStringMessage());
          ErrorManager.recordOrShowDatabaseError(7366, message2, false, false);

          return fieldRef.unknownValue();
       }

       /**
        * Conversion of BUFFER-VALUE attribute (KW_BUF_VAL).
        * <p>
        * Setter of current value of a buffer-field object. If you modify the BUFFER-VALUE attribute,
        * Progress sets the buffer-field object to the new value.
        * <p>
        * Special care is taken when the field is a CHARACTER and the LITERAL-QUESTION is the default
        * value {@code false}; in this case the
        * <ul>
        *    <li>"?" is understand as the unknown literal (?) and</li>
        *    <li>trailing blanks (spaces) for strings with total length less than 9 are removed.
        *       Note that the {@code BUFFER-VALUE} specification does not mention any thing about the
        *       string length, and also upholds that enclosing quotes are removed. The current
        *       implementation reflects the 4GL's behaviour obtained by manual testing.</li>
        * </ul>
        * If LITERAL-QUESTION is set to {@code yes} the string are not affected.
        *
        * @param   newValue
        *          New value of a buffer-field object.
        *
        */
       @Override
       public void changeValue(Object newValue)
       {
          if (fieldRef.getParentBuffer() instanceof TemporaryBuffer &&
              fieldRef.getDMO() instanceof BufferImpl)
          {
             BufferImpl buf = (BufferImpl) fieldRef.getDMO();
             if (buf.isBeforeBuffer())
             {
                ErrorManager.recordOrShowError(12375, buf.doGetName(), "");
                // You may not update BEFORE-TABLE <name> record.
                ErrorManager.recordOrShowError(142, buf.doGetName(), "");
                // ** Unable to update <filename> Field.
                ErrorManager.recordOrShowError(3131, "BUFFER-VALUE", "", LegacyResource.BUFFER_FIELD);
                // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>
                ErrorManager.recordOrThrowError(142, buf.doGetName(), "");
                // ** Unable to update <filename> Field.
                return;
             }
          }

          if (fieldRef.getExtent() != null && fieldRef.getIndex() == -1)
          {
             // we must be in bulk mode
             Class<?> clazz = newValue.getClass();
             if (!clazz.isArray())
             {
                BufferImpl buf = (BufferImpl) fieldRef.getDMO();
                ErrorManager.recordOrShowError(26, "0", "");
                // ** Array subscript 0 is out of range. (26)
                ErrorManager.recordOrShowError(142, buf.doGetName(), "");
                // ** Unable to update tt1 Field. (142)
                ErrorManager.recordOrShowError(3131, "BUFFER-VALUE", "", LegacyResource.BUFFER_FIELD);
                // Unable to set attribute ? in widget  of type BUFFER-FIELD. (3131)
                ErrorManager.recordOrThrowError(142, buf.doGetName(), "");
                // ** Unable to update tt1 Field. (142)
                return;
             }

             Class<?> type = fieldRef.getType();
             if (!type.isArray())
             {
                // TODO: error?
                return;
             }

             Class<?> ctype = clazz.getComponentType();
             Class<?> ftype = type.getComponentType();
             if (ctype != ftype)
             {
                // if not the same type, convert the value
                BaseDataType[] newVal = new BaseDataType[fieldRef.getExtent()];
                int psize = Array.getLength(newValue);
                for (int i = 0; i < psize; i++)
                {
                   newVal[i] = BaseDataType.generateUnknown(ftype);
                   newVal[i].assign((BaseDataType) Array.get(newValue, i));
                }

                newValue = newVal;
             }

             fieldRef.set((BaseDataType[]) newValue);

             return;
          }

          BaseDataType bdt = null;
          String format = null;

          if (newValue instanceof Text && !(fieldRef.get() instanceof Text))
          {
             Text newValueText = (Text) newValue;
             if (!newValueText.isUnknown() && newValueText.toJavaType().equals(""))
             {
                fieldRef.set(fieldRef.unknownValue());
                return;
             }
          }

          if (!(newValue instanceof BaseDataType))
          {
             // try some simple conversions
             if (newValue instanceof Integer)
             {
                bdt = new integer((Integer) newValue);
             }
             else if (newValue instanceof Long)
             {
                bdt = new integer((Long) newValue);
             }
             else if (newValue instanceof Double)
             {
                bdt = new decimal((Double) newValue);
             }
             else if (newValue instanceof Boolean)
             {
                bdt = new logical((Boolean) newValue);
             }
             else if (newValue instanceof String)
             {
                bdt = new character((String) newValue);
             }
             else if (newValue.getClass().isArray())
             {
                ErrorManager.recordOrShowError(14910);
                // Dynamic BUFFER-VALUE array assignment for field f2 requires that extents match. (14910)
                ErrorManager.recordOrShowError(3131, "?", "", LegacyResource.BUFFER_FIELD);
                // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>
                ErrorManager.recordOrThrowError(142, ((BufferImpl) fieldRef.getDMO()).doGetName(), "");
                // ** Unable to update <filename> Field.
                return;
             }
             else
             {
                throw new ClassCastException(
                      "Can't convert " + newValue.getClass().getName() + " to BaseDataType!");
             }
          }
          else if (newValue instanceof unknown)
          {
             bdt = fieldRef.unknownValue();
          }
          else if (!fieldRef.getType().isAssignableFrom(newValue.getClass()))
          {
             try
             {
                // TODO: OM: newValue is not compatible with fieldRef
                bdt = (BaseDataType) BaseDataTypeFactory.instantiate(fieldRef.getType(),
                                                                     (BaseDataType) newValue);
             }
             catch (InvocationTargetException exc)
             {
                // the constructor could not manage to convert the passed value into a meaningful
                // object of the field's type
                Throwable cause = exc.getCause();
                if (cause instanceof ConditionException)
                {
                   throw (ConditionException) cause;
                }

                LOG.log(Level.SEVERE, "Error performing runtime type conversion", exc);

                return;
             }
             catch (ReflectiveOperationException |
                    IllegalArgumentException exc)
             {
                ErrorManager.recordOrThrowError(
                   5729,
                   "Incompatible datatypes found during runtime conversion");
                return;
             }
          }
          else
          {
             // finally... the simple case
             bdt = (BaseDataType) newValue;

             // special handling for character fields when LITERAL-QUESTION attribute is false
             if (bdt instanceof character && fieldRef.getType() == character.class && !_literalQuestion())
             {
                character newChar = (character) bdt.val();
                String stringMessage = newChar.toStringMessage();
                if ("?".equals(stringMessage))
                {
                   bdt = newChar.instantiateUnknown();
                }
                else if (stringMessage.length() < 9)
                {
                   // remove trailing spaces only if total length is at most 8 characters long
                   bdt = TextOps.rightTrim(newChar, " ");
                }
                if (!bdt.isUnknown())
                {
                   format = lfi.getFormat(getTempTable());
                   if (format != null && !"".equals(format.trim()) )
                   {
                      try
                      {
                         // TODO: get rid of dependency on UI classes.
                         bdt = GenericFrame.parseFieldValue(character.class, newChar, format);
                      }
                      catch (DisplayFormatCheckException e)
                      {
                         // Should never happen.
                         throw new ErrorConditionException(e.getMessage());
                      }
                   }
                }
                // otherwise use the character as it was passed in as [bdt]
             }
          }

          fieldRef.set(bdt);
       }

       /**
        * Conversion of BUFFER-NAME attribute (KW_BUF_NAME).
        *
        * Gets the name of the buffer object to which the buffer-field object belongs.
        *
        * @return  name of the buffer object to which the buffer-field object belongs.
        */
       @Override
       public character bufferName()
       {
          return new character(getParentBuffer().getLegacyName());
       }

       /**
        * Returns the handle of the buffer object to which the buffer-field belongs.
        *
        * @return the handle of the buffer object to which the buffer-field belongs.
        */
       @Override
       public handle bufferHandle()
       {
          return new handle(getBuffer());
       }

       /**
        * Returns the handle of the buffer object to which the buffer-field belongs.
        *
        * @return the handle of the buffer object to which the buffer-field belongs.
        */
       public Buffer getBuffer()
       {
          return (Buffer) getParentBuffer().getDMOProxy();
       }

       /**
        * Conversion of LITERAL-QUESTION attribute (KW_LIT_QSTN).
        *
        * Getter of LITERAL-QUESTION attribute.
        *
        * @return  current value of LITERAL-QUESTION attribute.
        */
       @Override
       public logical literalQuestion()
       {
          return logical.of(_literalQuestion());
       }

       /**
        * Conversion of LITERAL-QUESTION attribute (KW_LIT_QSTN).
        *
        * Setter of LITERAL-QUESTION attribute.
        *
        * @param   literalQuestion
        *          New value of LITERAL-QUESTION attribute.
        */
       @Override
       public void changeLiteralQuestion(logical literalQuestion)
       {
          if (!checkUnknown("LITERAL-QUESTION", literalQuestion))
          {
             return;
          }

          if (!mutableOptions)
          {
             this.literalQuestion = new logical(literalQuestion);
          }
          else
          {
             lfi.setLiteralQuestion(literalQuestion.toJavaType(), getTempTable());
          }
       }

       /**
        * Conversion of VALIDATE-MESSAGE attribute (KW_VAL_MSG).
        *
        * Getter of VALIDATE-MESSAGE attribute.
        *
        * @return  current value of VALIDATE-MESSAGE attribute.
        */
       @Override
       public character validateMessage()
       {
          if (validateMessage != null)
          {
             return new character(validateMessage);
          }

          return new character(lfi.getValidateMessage(getTempTable()));
       }

       /**
        * Conversion of VALIDATE-MESSAGE attribute (KW_VAL_MSG).
        *
        * Setter of VALIDATE-MESSAGE attribute.
        *
        * @param   validateMessage
        *          New value of VALIDATE-MESSAGE attribute.
        */
       @Override
       public void changeValidateMessage(character validateMessage)
       {
          if (!checkUnknown("VALIDATE-MESSAGE", validateMessage))
          {
             return;
          }

          if (!mutableOptions)
          {
             this.validateMessage = new character(validateMessage);
          }
          else
          {
             lfi.setValidateMessage(validateMessage.getValue(), getTempTable());
          }
       }

       /**
        * Conversion of MANDATORY attribute (KW_MAND).
        *
        * Getter of KW_MAND attribute.
        *
        * @return current value of KW_MAND attribute.
        */
       @Override
       public logical mandatory()
       {
          return new logical(lfi.isMandatory());
       }

       /**
        * Gets the read-only POSITION attribute.
        *
        * @return   Current value of POSITION attribute.
        */
       @Override
       public integer position()
       {
          int pos = -1;
          if (!fieldRef.getParentBuffer().isTemporary())  // ignore POSITION for temp-tables
          {
             pos = lfi.getPosition();
          }

          // pos == 0 for a schema where there is no POSITION and no ORDER
          // pos == -1 if there is no LegacyFieldInfo for this property
          if (pos < 1)
          {
             // use our internal ordering
             pos = lfi.getFieldId() + 1;
          }

          return new integer(pos);
       }

       /**
        * Conversion of VALIDATE-EXPRESSION attribute (KW_VAL_EXPR).
        *
        * Getter of VALIDATE-EXPRESSION attribute.
        *
        * @return current value of VALIDATE-EXPRESSION attribute.
        */
       @Override
       public character validateExpression()
       {
          if (validateExpression != null)
          {
             return new character(validateExpression);
          }

          return new character(lfi.getValidateExpression(getTempTable()));
       }

       /**
        * Conversion of VALIDATE-EXPRESSION attribute (KW_VAL_EXPR).
        *
        * Setter of VALIDATE-EXPRESSION attribute.
        *
        * @param  validateExpression
        *         New value of VALIDATE-EXPRESSION attribute.
        */
       @Override
       public void changeValidateExpression(character validateExpression)
       {
          if (!checkUnknown("VALIDATE-EXPRESSION", validateExpression))
          {
             return;
          }

          if (!mutableOptions)
          {
             this.validateExpression = new character(validateExpression);
          }
          else
          {
             lfi.setValidateExpression(validateExpression.getValue(), getTempTable());
          }
       }

       /**
        * Reports if this object is valid for use.
        *
        * @return   <code>true</code> if we are valid (can be used).
        */
       @Override
       public boolean valid()
       {
          return getDMOProxy().valid();
       }

       /**
        * Returns the state of the DBNAME attribute
        *
        * @return    Current value of the DBNAME attribute.
        */
       @Override
       public character getDbName()
       {
          Database database = fieldRef.getParentBuffer().getDatabase();
          return new character(
                SchemaDictionary.TEMP_TABLE_DB.equals(database.getName()) ? "PROGRESST" : database.getName());
       }

       /**
        * Returns the state of the TABLE attribute
        *
        * @return    Current value of the TABLE attribute.
        */
       @Override
       public character getTable()
       {
          return ((BufferImpl) fieldRef.getParentBuffer().getDMOProxy()).getTable();
       }

       /**
        * HEIGHT-CHARS attribute is not defined for BUFFER-FIELD handle. Calls {@link #invalidAttribute} to
        * display appropriate error.
        *
        * @return  unknown value.
        */
       @Override
       public decimal getHeightChars()
       {
          invalidAttribute("HEIGHT-CHARS", false);

          return new decimal();
       }

       /**
        * HEIGHT-CHARS attribute is not defined for BUFFER-FIELD handle. Calls
        * {@link #readOnlyError} to display appropriate error.
        *
        * @param   h
        *          Not used.
        */
       @Override
       public void setHeightChars(NumberType h)
       {
          readOnlyError("HEIGHT", handle.UNKNOWN_ARGUMENT);
       }

       /**
        * HEIGHT-CHARS attribute is not defined for BUFFER-FIELD handle. Calls
        * {@link #readOnlyError} to display appropriate error.
        *
        * @param   h
        *          Not used.
        */
       @Override
       public void setHeightChars(double h)
       {
          readOnlyError("HEIGHT", handle.UNKNOWN_ARGUMENT);
       }

       /**
        * Get the WIDTH-CHARS attribute of this BUFFER-FIELD handle.
        *
        * For buffer-field objects, the WIDTH-CHARS attribute is the number of characters in the
        * STRING-VALUE, which the AVM calculates using the FORMAT attribute.
        *
        * @return  See above.
        */
       @Override
       public decimal getWidthChars()
       {
          String strValue;

          if (getDMOProxy()._available() && fieldRef.getExtent() == null)
          {
             strValue = stringValue().toStringMessage();
          }
          else
          {
             BaseDataType defValue = BaseDataType.generateDefault(BaseDataType.fromTypeName(
                                                                            getDataType().getValue()));
             strValue = defValue.toString(getFormat().getValue());
          }

          return new decimal(strValue == null ? 0 : strValue.length());
       }

       /**
        * Get the WIDTH Progress attribute. The value is equal to the current component char
        * width.
        *
        * This attribute is an alias for <tt>WIDTH-CHARS attribute</tt>, this method shall
        * never be called directly, its solely purpose is to assert the call
        * <tt>CAN-QUERY(widget, "WIDTH")</tt> works properly.
        *
        * @return    The integer value of the WIDTH attribute.
        */
       @Override
       public decimal getWidth()
       {
          return getWidthChars();
       }

       /**
        * Set the WIDTH-CHARS attribute of this BUFFER-FIELD handle.
        *
        * The WIDTH-CHARS attribute of a buffer-field is readable but not writeable.
        *
        * @param   widthChars
        *          The new value for the WIDTH-CHARS attribute.
        */
       @Override
       public void setWidthChars(NumberType widthChars)
       {
          readOnlyError("WIDTH", handle.UNKNOWN_ARGUMENT);
       }

       /**
        * Set the WIDTH-CHARS attribute of this BUFFER-FIELD handle.
        *
        * The WIDTH-CHARS attribute of a buffer-field is readable but not writeable.
        *
        * @param   widthChars
        *          The new value for the WIDTH-CHARS attribute.
        */
       @Override
       public void setWidthChars(double widthChars)
       {
          readOnlyError("WIDTH", handle.UNKNOWN_ARGUMENT);
       }

       /**
        * Set the WIDTH Progress attribute.
        *
        * This attribute is an alias for <tt>WIDTH-CHARS attribute</tt>, this method shall
        * never be called directly, its solely purpose is to assert the call
        * <tt>CAN-SET(widget, "WIDTH")</tt> works properly.
        *
        * @param    widthChars
        *           The integer value for the new WIDTH value.
        */
       @Override
       public void setWidth(NumberType widthChars)
       {
          setWidthChars(widthChars);
       }

       /**
        * Get the HEIGHT Progress attribute. The value is equal to the current component char
        * height.
        *
        * This attribute is an alias for <tt>HEIGHT-CHARS attribute</tt>, this method shall
        * never be called directly, its solely purpose is to assert the call
        * <tt>CAN-QUERY(widget, "HEIGHT")</tt> works properly.
        *
        * @return    The integer value of the HEIGHT attribute.
        */
       @Override
       public decimal getHeight()
       {
          return getHeightChars();
       }

       /**
        * Set the HEIGHT Progress attribute.
        *
        * This attribute is an alias for <tt>HEIGHT-CHARS attribute</tt>, this method shall
        * never be called directly, its solely purpose is to assert the call
        * <tt>CAN-SET(widget, "HEIGHT")</tt> works properly.
        *
        * @param    heightChars
        *           The integer value for the new HEIGHT value.
        */
       @Override
       public void setHeight(NumberType heightChars)
       {
          setHeightChars(heightChars);
       }

       /**
        * WIDTH-PIXELS attribute is not defined for BUFFER-FIELD handle. Calls
        * {@link #invalidAttribute} to display appropriate error.
        *
        * @return  unknown value.
        */
       @Override
       public integer getWidthPixels()
       {
          invalidAttribute("WIDTH-PIXELS", false);

          return new integer();
       }

       /**
        * WIDTH-PIXELS attribute is not defined for BUFFER-FIELD handle. Calls
        * {@link #readOnlyError} to display appropriate error.
        *
        * @param    widthPixels
        *           Not used.
        */
       @Override
       public void setWidthPixels(NumberType widthPixels)
       {
          readOnlyError("WIDTH-PIXELS", handle.UNKNOWN_ARGUMENT);
       }

       /**
        * WIDTH-PIXELS attribute is not defined for BUFFER-FIELD handle. Calls
        * {@link #readOnlyError} to display appropriate error.
        *
        * @param    widthPixels
        *           Not used.
        */
       @Override
       public void setWidthPixels(int widthPixels)
       {
          readOnlyError("WIDTH-PIXELS", handle.UNKNOWN_ARGUMENT);
       }

       /**
        * HEIGHT-PIXELS attribute is not defined for BUFFER-FIELD handle. Calls
        * {@link #invalidAttribute} to display appropriate error.
        *
        * @return  unknown value.
        */
       @Override
       public integer getHeightPixels()
       {
          invalidAttribute("HEIGHT-PIXELS", false);

          return new integer();
       }

       /**
        * HEIGHT-PIXELS attribute is not defined for BUFFER-FIELD handle. Calls
        * {@link #readOnlyError} to display appropriate error.
        *
        * @param    heightPixels
        *           Not used.
        */
       @Override
       public void setHeightPixels(NumberType heightPixels)
       {
          readOnlyError("HEIGHT-PIXELS", handle.UNKNOWN_ARGUMENT);
       }

       /**
        * HEIGHT-PIXELS attribute is not defined for BUFFER-FIELD handle. Calls
        * {@link #readOnlyError} to display appropriate error.
        *
        * @param    heightPixels
        *           Not used.
        */
       @Override
       public void setHeightPixels(int heightPixels)
       {
          readOnlyError("HEIGHT-PIXELS", handle.UNKNOWN_ARGUMENT);
       }

       /**
        * Getter of FORMAT attribute.
        *
        * @return   current value of FORMAT attribute.
        */
       @Override
       public character getFormat()
       {
          if (format != null)
          {
             return new character(format);
          }

          return new character(lfi.getFormat(getTempTable()));
       }

       /**
        * Setter of FORMAT attribute.
        *
        * @param   format
        *          The new value.
        */
       @Override
       public void setFormat(final String format)
       {
          // Check if the format is valid for the type
          final Class<?> fieldDataClass = fieldRef.getType();

          if (datetime.class.isAssignableFrom(fieldDataClass))
          {
             new DatetimeFormatParser().parse(format);
          }
          else if (date.class.isAssignableFrom(fieldDataClass))
          {
             new DateFormatParser().parse(format);
          }
          else if (Text.class.isAssignableFrom(fieldDataClass))
          {
             new CharacterFormatParser().parse(format);
          }
          else if (logical.class.isAssignableFrom(fieldDataClass))
          {
             new LogicalFormatParser().parse(format);
          }
          else if (NumberType.class.isAssignableFrom(fieldDataClass))
          {
             new NumberFormatParser().parse(format);
          }
          else if (rowid.class.isAssignableFrom(fieldDataClass))
          {
             // FIXME: is this branch ever visited?
             // no-op: formats are ignored when rowid is formatted.
          }

          if (!mutableOptions)
          {
             this.format = new character(format);
          }
          else
          {
             lfi.setFormat(format, getTempTable());
          }
       }

       /**
        * Setter of FORMAt attribute.
        *
        * @param   format
        *          The new value.
        */
       @Override
       public void setFormat(character format)
       {
          if (!checkUnknown("FORM", format))  // "FORM" is correct
          {
             return;
          }

          setFormat(format.getValue());
       }

       /**
        * Getter of COLUMN-LABEL attribute.
        *
        * @return   current value of COLUMN-LABEL attribute.
        */
       @Override
       public character getColumnLabel()
       {
          if (columnLabel != null)
          {
             return new character(columnLabel);
          }

          return new character(lfi.getColumnLabel(getTempTable()));
       }

       /**
        * Setter of COLUMN-LABEL attribute.
        *
        * @param    columnLabel
        *           New value of a attribute.
        */
       @Override
       public void setColumnLabel(character columnLabel)
       {
          if (!checkUnknown("COLUMN-LABEL", columnLabel))
          {
             return;
          }

          setColumnLabel(columnLabel.getValue());
       }

       /**
        * Setter of COLUMN-LABEL attribute.
        *
        * @param    columnLabel
        *           New value of a attribute.
        */
       @Override
       public void setColumnLabel(String columnLabel)
       {
          if (!mutableOptions)
          {
             this.columnLabel = new character(columnLabel);
          }
          else
          {
             lfi.setColumnLabel(columnLabel, getTempTable());
          }
       }

       /**
        * Returns the value of the DATA-TYPE attribute
        *
        * @return   Current value of the DATA-TYPE attribute.
        */
       @Override
       public character getDataType()
       {
          if (dataType == null)
          {
             Class<?> type = fieldRef.getType();
             if (type.isArray())
             {
                type = type.getComponentType();
             }

             if (type == datetimetz.class)
             {
                // the only wrapper type currently whose simple Java name doesn't match 4GL name
                // TODO: this will change with 4GL OO support
                dataType = character.of("datetime-tz");
             }
             else
             {
                dataType = character.of(type.getSimpleName());
             }
          }

          return dataType;
       }

       /**
        * Set the <code>DATA-TYPE</code> attribute.
        *
        * @param    dataType
        *           The DATA-TYPE to be set.
        */
       @Override
       public void setDataType(String dataType)
       {
          readOnlyError("DATA-TYPE", handle.UNKNOWN_ARGUMENT);
       }

       /**
        * Set the <code>DATA-TYPE</code> attribute..
        *
        * @param    dataType
        *           The DATA-TYPE to be set.
        */
       @Override
       public void setDataType(character dataType)
       {
          readOnlyError("DATA-TYPE", handle.UNKNOWN_ARGUMENT);
       }

       /**
        * Returns the value of the LABEL attribute
        *
        * @return    Current value of the LABEL attribute.
        */
       @Override
       public character getLabel()
       {
          if (label != null)
          {
             return new character(label);
          }

          return new character(lfi.getLabel(getTempTable()));
       }

       /**
        * Set the <code>LABEL</code> attribute of handle.
        *
        * @param    label
        *           The LABEL to be set.
        */
       @Override
       public void setLabel(String label)
       {
          if (!mutableOptions)
          {
             this.label = new character(label);
          }
          else
          {
             lfi.setLabel(label, getTempTable());
          }
       }

       /**
        * Set the <code>LABEL</code> attribute of handle.
        *
        * @param    label
        *           The LABEL to be set.
        */
       @Override
       public void setLabel(character label)
       {
          // no unknown validation
          setLabel(label.toJavaType());
       }

       /**
        * Get the value of the ADM-DATA attribute.
        *
        * @return   See above.
        */
       @Override
       public character getADMData()
       {
          return new character(this.admData);
       }

       /**
        * Set the value of the ADM-DATA attribute.
        *
        * @param    value
        *           The new value.
        */
       @Override
       public void setADMData(String value)
       {
          this.admData = value;
       }

       /**
        * Set the value of the ADM-DATA attribute.
        *
        * @param    value
        *           The new value.
        */
       @Override
       public void setADMData(character value)
       {
          this.admData = value.toJavaType();
       }

       /**
        * Gets the the unique ID number associated to this object by the underlying system. Not the
        * same as the handle value itself.
        *
        * @return   The integer number of the children.
        */
       @Override
       public integer getUniqueID()
       {
          if (uniqueID == null)
          {
             uniqueID = UniqueIdGenerator.getUniqueId(IdKind.BUFFER_FIELD);
          }

          return new integer(this.uniqueID);
       }

       /**
        * Getter of CASE-SENSITIVE attribute. Indicates whether the buffer-field is case-sensitive.
        *
        * @return current value of CASE-SENSITIVE attribute.
        */
       @Override
       public logical caseSensitive()
       {
          return new logical(lfi.isCaseSensitive());
       }

       /**
        * Implementation of the read access of the {@code SERIALIZE-NAME} attribute.
        *
        * @return  the current value of the {@code SERIALIZE-NAME} attribute.
        */
       @Override
       public character getSerializeName()
       {
          if (serializeName != null)
          {
             return new character(serializeName);
          }

          String sName = lfi.getSerializeOptions(getTempTable()).getSerializeName();
          return new character(sName == null || sName.trim().isEmpty() ? lfi.getLegacyName() : sName);
       }

       /**
        * Implementation of the write access of the {@code SERIALIZE-NAME} attribute.
        *
        * @param   sName
        *          the new value for the {@code SERIALIZE-NAME} attribute.
        */
       @Override
       public void setSerializeName(Text sName)
       {
          setSerializeName(sName == null || sName.isUnknown() ? null : sName.getValue());
       }

       /**
        * Implementation of the write access of the {@code SERIALIZE-NAME} attribute.
        *
        * @param   sName
        *          the new value for the {@code SERIALIZE-NAME} attribute.
        */
       @Override
       public void setSerializeName(String sName)
       {
          if (!mutableOptions)
          {
             this.serializeName = new character(sName);
          }
          else
          {
             lfi.getSerializeOptions(getTempTable()).setSerializeName(sName);
          }
       }

       /**
        * Get the property name of this field.
        *
        * @return property name of this field.
        */
       public String getProperty()
       {
          return fieldRef.getProperty();
       }

       /**
        * Get the original property name of this field. If the original property is not defined (not a
        * denormalized  extent field), the normal property name is returned.
        *
        * @return property name of this field.
        */
       public String getOriginalPropertyName()
       {
          return lfi.original.isEmpty() ? fieldRef.getProperty() : lfi.original;
       }

       /**
        * Creates and returns a copy of the {@code FieldReference} held by this instance.
        *
        * @param   extIndex
        *          The extent index. If none, {@code null} is accepted.
        *
        * @return  Copy of the {@code FieldReference} held by this instance.
        */
       public FieldReference createFieldReference(Integer extIndex)
       {
          return new FieldReference(fieldRef, extIndex);
       }

       /**
        * Delete the resource.
        *
        * BUFFER-FIELD will be deleted only when the dynamic buffer is deleted. No resource-specific
        * delete is required.
        *
        * @return   <code>true</code> if the resource was deleted.
        */
       @Override
       protected boolean resourceDelete()
       {
          // this is always called after the buffer has been deleted, so allow it to delete
          return forceDelete || !valid();
       }

       /**
        * Check if this resource supports the PREV-SIBLING attribute.
        *
        * @return   Always <code>false</code>.
        */
       @Override
       protected boolean hasPrevSibling()
       {
          return false;
       }

       /**
        * Check if this resource supports the NEXT-SIBLING attribute.
        *
        * @return   Always <code>false</code>.
        */
       @Override
       protected boolean hasNextSibling()
       {
          return false;
       }

       /**
        * Get the DMO proxy which references parent buffer.
        *
        * @return  DMO proxy object.
        */
       Buffer getDMOProxy()
       {
          return (Buffer) fieldRef.getParentBuffer().getDMOProxy();
       }

       /**
        * Force the delete of this BUFFER-FIELD.
        */
       void forceDelete()
       {
          forceDelete = true;
          delete();
       }

       /**
        * Getter of EXTENT attribute.
        *
        * @return the value of EXTENT attribute.
        *
        */
       int _extent()
       {
          Integer extent = lfi.getExtent();
          return extent == null ? 0 : extent;
       }

       /**
        * Retrieve the value of a specific element of an extent field, optionally validating the
        * supplied subscript.
        *
        * @param   index
        *          One-based subscript of the extent field to be changed.
        * @param   validate
        *          {@code true} to validate {@code index}, else {@code false}.
        */
       private BaseDataType value(int index, boolean validate)
       {
          if (validate && !validateBufferFieldIndex(index))
          {
             return fieldRef.unknownValue();
          }

          BaseDataType ret = null;
          if (index == 0)
          {
             if (lfi.getExtent() == 0) // scalar case
             {
                ret = fieldRef.get();
             }
             else
             {
                // silently return unknown value for extent[0]
                ret = lfi.getInitialValue().instantiateUnknown();
             }
          }
          else
          {
             // normalized extents:
             if (!lfi.original.isEmpty())
             {
                // denormalized extents: the [fieldRef] contains a reference to one of the properties the original
                //                       field was denormalized into, not mandatory the [index]-th one. We need to
                //                       look-up the [index]-th property and invoke its [get()] property.
                String legacyName = lfi.getLegacyName();
                DmoMeta dmoInfo = ((BufferImpl) fieldRef.getDMO()).buffer().getDmoInfo();
                Iterator<Property> fields = dmoInfo.getFields(false);
                while (fields.hasNext())
                {
                   Property property = fields.next();
                   if (property.legacy.equalsIgnoreCase(legacyName))
                   {
                      index--;
                      if (index == 0)
                      {
                         ret = new FieldReference((DataModelObject) fieldRef.getDMO(),
                                                  property.name, fieldRef.isUpperCase(), null, null, false).get();
                         break;
                      }
                   }
                }

                // if there is no match, default to [fieldRef]
             }
          }

          if (ret == null)
          {
             ret = new FieldReference(fieldRef, index - 1).get();
          }

          if (ret instanceof object && ret.isUnknown())
          {
             // strip the type information
             ret = new object<>();
          }

          return ret;
       }

       /**
        * Change the value of a specific element of an extent field, optionally validating the
        * supplied subscript.
        *
        * @param   index
        *          One-based subscript of the extent field to be changed.
        * @param   newValue
        *          New value to set into the extent field.
        * @param   validate
        *          <code>true</code> to validate <code>index</code>, else <code>false</code>.
        */
       private void changeValue(int index, Object newValue, boolean validate)
       {
          if (validate && !validateBufferFieldIndex(index))
          {
             return;
          }

          if (index == 0)
          {
             changeValue(newValue);
          }
          else
          {
             BufferFieldImpl field = new BufferFieldImpl(fieldRef, index, lfi);
             try
             {
                field.changeValue(newValue);
             }
             finally
             {
                // force the delete, this instance is required for an indexed assign in an extent field, and this
                // instance is not a real BUFFER-FIELD resource.
                field.forceDelete();
             }
          }
       }

       /**
        * Validate the index passed to a BUFFER-VALUE method call. For scalar fields, this value must
        * be zero. For extent fields, this value must be a positive number less than or equal to the
        * size of the extent field. Unknown value is invalid in either case.
        *
        * @param   index
        *          Index value (unadjusted) provided by converted business logic.
        *
        * @return  <code>true</code> if <code>index</code> is valid, else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          if <code>index</code> is invalid and session is not in silent error mode.
        */
       private boolean validateBufferFieldIndex(NumberType index)
       {
          return validateBufferFieldIndex(index.isUnknown() ? -1 : index.intValue());
       }

       /**
        * Validate the index passed to a BUFFER-VALUE method call. For scalar fields, this value must
        * be zero. For extent fields, this value must be a positive number less than or equal to the
        * size of the extent field. Unknown value is invalid in either case.
        *
        * @param   index
        *          Index value (unadjusted) provided by converted business logic, or <code>-1</code>
        *          if that value was unknown.
        *
        * @return  <code>true</code> if <code>index</code> is valid, else <code>false</code>.
        *
        * @throws  ErrorConditionException
        *          if <code>index</code> is invalid and session is not in silent error mode.
        */
       private boolean validateBufferFieldIndex(int index)
       {
          int max = 0;

          if (index > 0)
          {
             int extent = lfi.getExtent();
             if (extent > 0)
             {
                max = extent;
             }
          }

          if (index < 0 || index > max || (max == 0 && index != 0))
          {
             String fieldName = lfi.getLegacyName();
             String msg = String.format("%s BUFFER-VALUE argument must be valid array index, less " +
                                        "than or equal to field extent %d",
                                        fieldName,
                                        max);
             LOG.severe(msg, new Throwable());
             ErrorManager.recordOrThrowError(7365, msg, false);

             return false;
          }

          return true;
       }

       /**
        * Check if the provided value of the attribute is unknown and raise error in this case.
        *
        * @param  attributeName
        *         Name of the attribute which value is checked.
        * @param  value
        *         Value of the attribute to be checked.
        *
        * @return {@code false} if the value is unknown.
        */
       private boolean checkUnknown(String attributeName, BaseDataType value)
       {
          if (value == null || value.isUnknown())
          {
             ErrorManager.recordOrShowError(4083, attributeName, type() + " widget");
             // **Unable to assign UNKNOWN value to attribute <attribute> on <widget id>.
             return false;
          }

          return true;
       }

       /**
        * Get the parent record buffer.
        *
        * @return parent record buffer.
        */
       private RecordBuffer getParentBuffer()
       {
          return fieldRef.getParentBuffer();
       }

       /**
        * Obtain the XML Schema data type for the buffer-field object.
        *
        * @return the XML Schema data type for the buffer-field object.
        */
       @Override
       public character getXmlDataType()
       {
          if (xmlDataType != null)
          {
             return new character(xmlDataType);
          }

          String xmlDataType = lfi.getSerializeOptions(getTempTable()).getXmlDataType();
          if (xmlDataType == null || xmlDataType.isEmpty())
          {
             xmlDataType = Util.getXmlType(BaseDataType.fromTypeName(getDataType().toJavaType()));
          }
          return new character(xmlDataType);
       }

       /**
        * Configures the XML Schema data type for the buffer-field object.
        *
        * @param newType the new XML Schema data type for the buffer-field object.
        */
       @Override
       public void setXmlDataType(String newType)
       {
          if (mutableOptions)
          {
             lfi.getSerializeOptions(getTempTable()).setXmlDataType(newType);
          }
          else
          {
             xmlDataType = new character(newType);
          }
       }

       /**
        * Configures the XML Schema data type for the buffer-field object.
        *
        * @param newType the new XML Schema data type for the buffer-field object.
        */
       @Override
       public void setXmlDataType(Text newType)
       {
          if (checkUnknown("XML-DATA-TYPE", newType))
          {
             setXmlDataType(newType.toJavaType());
          }
       }

       /**
        * Obtain the XML node type of this object.
        * This represents the getter for {@code XML-NODE-TYPE} ABL attribute.
        *
        * @return  the XML node type of this object.
        */
       @Override
       public character getXmlNodeType()
       {
          if (xmlNodeType != null)
          {
             return new character(xmlNodeType);
          }

          return new character(lfi.getSerializeOptions(getTempTable()).getXmlNodeType().name());
       }

       /**
        * Allows to specify how a this object will be represented in XML and XML Schema. Valid XML
        * node types are "ATTRIBUTE", "ELEMENT", "HIDDEN", and "TEXT".
        * This represents the setter for {@code XML-NODE-TYPE} ABL attribute.
        *
        * @param   newType
        *          The new type.
        */
       @Override
       public void setXmlNodeType(Text newType)
       {
          if (checkUnknown("XML-NODE-TYPE", newType))
          {
             setXmlNodeType(newType.toJavaType());
          }
       }

       /**
        * Allows to specify how a this object will be represented in XML and XML Schema. Valid XML
        * node types are "ATTRIBUTE", "ELEMENT", "HIDDEN", and "TEXT".
        * This represents the setter for {@code XML-NODE-TYPE} ABL attribute.
        *
        * @param   newType
        *          The new type.
        */
       @Override
       public void setXmlNodeType(String newType)
       {
          if (XmlNode.TEXT.equalsIgnoreCase(newType))
          {
             // In order to have a Temp-Table field with an XML-NODE-TYPE of 'TEXT', all other fields in the
             // Temp-Table must have an XML-NODE-TYPE of 'ATTRIBUTE' or 'HIDDEN'.
             BufferImpl buf = (BufferImpl) getParentBuffer().getDMOProxy();
             int n = buf._numFields();
             boolean allowed = true;

             for (int k = 0; k < n; k++)
             {
                BufferFieldImpl bf = (BufferFieldImpl) buf.bufferFieldNative(k + 1);
                if (bf != this)
                {
                   String nodeType = bf.getXmlNodeType().toJavaType();
                   if (!XmlNode.ATTRIBUTE.equals(nodeType) && !XmlNode.HIDDEN.equals(nodeType))
                   {
                      allowed = false;
                      break;
                   }
                }
             }

             if (!allowed)
             {
                ErrorManager.recordOrShowError(13233, _name(), "");
                // Cannot set XML-NODE-TYPE of field '<field>' to 'TEXT'. (13233)
                ErrorManager.recordOrThrowError(3131, "XML-NODE-TYPE", "", LegacyResource.BUFFER_FIELD);
                // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>. (3131)
             }
          }

          newType = newType.toUpperCase();

          if (mutableOptions)
          {
             lfi.getSerializeOptions(getTempTable()).setXmlNodeType(SerializeOptions.XmlNodeType.valueOf(newType));
          }
          else
          {
             xmlNodeType = new character(SerializeOptions.XmlNodeType.valueOf(newType).name());
          }
       }

       /**
        * Allows to specify how a this object will be represented in XML and XML Schema. Valid XML
        * node types are "ATTRIBUTE", "ELEMENT", "HIDDEN", and "TEXT".
        * This represents the setter for {@code XML-NODE-TYPE} ABL attribute.
        *
        * @param   newType
        *          The new type.
        */
       public void setXmlNodeType(SerializeOptions.XmlNodeType newType)
       {
          if (mutableOptions)
          {
             lfi.getSerializeOptions(getTempTable()).setXmlNodeType(newType);
          }
          else
          {
             xmlNodeType = new character(newType.name());
          }
       }

       /**
        * Checks whether fields are written when the temp-table is serialized, for example into
        * JSON or XML.
        * This represents the getter for {@code SERIALIZE-HIDDEN} ABL attribute.
        *
        * @return  {@code true} when the fields are hidden.
        */
       @Override
       public logical getSerializeHidden()
       {
          if (serializeHidden != null)
          {
             return new logical(serializeHidden);
          }

          return new logical(lfi.getSerializeOptions(getTempTable()).isSerializeHidden());
       }

       /**
        * Allow to configure whether fields are written when the temp-table is serialized, for example
        * into JSON or XML.
        * This represents the setter for {@code SERIALIZE-HIDDEN} ABL attribute.
        *
        * @param   val
        *          {@code true} when the fields are hidden.
        */
       @Override
       public void setSerializeHidden(logical val)
       {
          if (checkUnknown("SERIALIZE-HIDDEN", val))
          {
             setSerializeHidden(val.booleanValue());
          }
       }

       /**
        * Allow to configure whether fields are written when the temp-table is serialized, for example
        * into JSON or XML.
        * This represents the setter for {@code SERIALIZE-HIDDEN} ABL attribute.
        *
        * @param   val
        *          {@code true} when the fields are hidden.
        */
       @Override
       public void setSerializeHidden(boolean val)
       {
          if (mutableOptions)
          {
             lfi.getSerializeOptions(getTempTable()).setSerializeHidden(val);
          }
          else
          {
             serializeHidden = new logical(val);
          }
       }

       /**
        * Obtain the number of decimal places, after the decimal point, that are stored in this
        * buffer-field object that corresponds to a DECIMAL field.
        *
        * @return  the number of decimals after the decimal point for this buffer-field.
        */
       @Override
       public integer getDecimals()
       {
          return new integer(lfi.getDecimals(getTempTable()));
       }

       /**
        * Sets the number of decimal places, after the decimal point, that are stored in this buffer-field
        * object that corresponds to a DECIMAL field.
        * <p>
        * This attribute is writable for temp-tables but only read-only for permanent tables.
        *
        * @param   decimals
        *          The new number of decimals after the decimal point for this buffer-field.
        */
       @Override
       public void setDecimals(NumberType decimals)
       {
          if (!checkUnknown("DECIMALS", decimals))
          {
             return;
          }

          setDecimals(decimals.longValue());
       }

       /**
        * Sets the number of decimal places, after the decimal point, that are stored in this buffer-field
        * object that corresponds to a DECIMAL field.
        * <p>
        * This attribute is writable for temp-tables but only read-only for permanent tables.
        *
        * @param   decimals
        *          The new number of decimals after the decimal point for this buffer-field.
        */
       @Override
       public void setDecimals(long decimals)
       {
          if (!fieldRef.getParentBuffer().isTemporary())
          {
             ErrorManager.recordOrShowError(17092);
             // Cannot set the DECIMALS attribute for a database field.
             ErrorManager.recordOrShowError(4078, "DECIMALS", "BUFFER-FIELD widget");
             // **Unable to set attribute <attribute> for <widget id>
             return;
          }

          if (lfi.getDataType() != decimal.class)
          {
             ErrorManager.recordOrShowError(17093);
             // DECIMALS attribute only allowed on DECIMAL datatypes.
             ErrorManager.recordOrShowError(4078, "DECIMALS", "BUFFER-FIELD widget");
             // **Unable to set attribute <attribute> for <widget id>
             return;
          }

          lfi.setDecimals((int) decimals, getTempTable());
       }

       /**
        * Checks whether this field participates in an index.
        *
        * @return  {@code true} if this field is part of an index.
        */
       @Override
       public logical isKey()
       {
          if (key == null)
          {
             key = false;
             Iterator<P2JIndex> allIndexes = getParentBuffer().getDmoInfo().getDatabaseIndexes();
             while (!key && allIndexes.hasNext())
             {
                P2JIndex idx = allIndexes.next();
                ArrayList<P2JIndexComponent> comps = idx.components();
                for (int i = 0; !key && i < comps.size(); i++)
                {
                   P2JIndexComponent comp = comps.get(i);
                   if (comp.getPropertyName().equals(fieldRef.getProperty()))
                   {
                      key = true;
                      break;
                   }
                }
             }
          }

          return new logical(key);
       }

       /**
        * Obtain the name of the XML element or attribute representing the target object (either a
        * {@code DataSet}, a {@code TempTable}, a {@code TemporaryBuffer}, or a temp-table
        * {@code BufferField}) name in an XML document.
        *
        * @return the name of the XML element for this object.
        */
       @Override
       public character getXmlNodeName()
       {
          if (xmlNodeName != null)
          {
             return new character(xmlNodeName);
          }

          return new character(lfi.getSerializeOptions(getTempTable()).getXmlNodeName());
       }

       /**
        * Sets the name of the XML element or attribute representing the target object (either a
        * {@code DataSet}, a {@code TempTable}, a {@code TemporaryBuffer}, or a temp-table
        * {@code BufferField}) name in an XML document.
        *
        * @param name The new name of the XML element for this object.
        */
       @Override
       public void setXmlNodeName(String name)
       {
          if (mutableOptions)
          {
             lfi.getSerializeOptions(getTempTable()).setXmlNodeName(name);
          }
          else
          {
             xmlNodeName = new character(name);
          }
       }

       /**
        * Sets the name of the XML element or attribute representing the target object (either a
        * {@code DataSet}, a {@code TempTable}, a {@code TemporaryBuffer}, or a temp-table
        * {@code BufferField}) name in an XML document.
        *
        * @param name The new name of the XML element for this object.
        */
       @Override
       public void setXmlNodeName(Text name)
       {
          if (checkUnknown("XML-NODE-NAME", name))
          {
             setXmlNodeName(name.toJavaType());
          }
       }

       /**
        * Get the DEFAULT-VALUE attribute.  This is the initial value of the field in its proper data type.
        * This is a polymorphic return type which must be specified as a BDT value even though it will have
        * the actual data type of the field on return.
        *
        * @return   The default value of the field.
        */
       @Override
       public BaseDataType getDefaultValue()
       {
          return initialValue();
       }

       /**
        * Get the DEFAULT-STRING attribute.
        *
        * @return   See above.
        */
       @Override
       public character getDefaultString()
       {
          String   init     = lfi.getInitial();
          Class<?> cls      = fieldRef.getType();

          if (init == null                           ||
              BinaryData.class.isAssignableFrom(cls) ||
              handle.class.isAssignableFrom(cls)     ||
              rowid.class.isAssignableFrom(cls)      ||
              recid.class.isAssignableFrom(cls))
          {
             return new character();
          }

          if (character.class.isAssignableFrom(cls))
          {
             return new character(init);
          }
          else if (datetime.class.isAssignableFrom(cls) && init.trim().equalsIgnoreCase("NOW"))
          {
             // TODO: this value MUST NOT be cached
             return new character("now");
          }
          else if (date.class.isAssignableFrom(cls) && init.trim().equalsIgnoreCase("TODAY"))
          {
             // TODO: this value MUST NOT be cached
             return new character("today");
          }

          BaseDataType val = initialValue();
          if (val.isUnknown())
          {
             return new character();
          }
          else if (val instanceof date)
          {
             // also will handle datetime and datetime-tz because they are subclasses of date
             return new character(val.toStringMessage());
          }
          else if (val instanceof logical)
          {
             return new character(val.toString());
          }
          else if (val instanceof int64)
          {
             // also will handle integer because it is a subclass of int64
             return new character(init.trim().toLowerCase());
          }
          else if (val instanceof decimal)
          {
             char decSep = NumberType.getNumericDecimalPoint().getValue().charAt(0);
             return new character(init.trim().replace('.', decSep));
          }

          return new character();
       }

       /**
        * Set the DEFAULT-STRING attribute.
        *
        * @param    val
        *           The default-string value.
        */
       @Override
       public void setDefaultString(String val)
       {
          setDefaultString(new character(val));
       }

       /**
        * Set the DEFAULT-STRING attribute.
        *
        * @param    val
        *           The default-string value.
        */
       @Override
       public void setDefaultString(character val)
       {
          handle.invalidAttribute("DEFAULT-STRING", true, LegacyResource.BUFFER_FIELD);
       }

       /**
        * Returns the value of the HELP attribute
        *
        * @return    Current value of the HELP attribute.
        */
       @Override
       public character getHelp()
       {
          if (help != null)
          {
             return new character(help);
          }

          return new character(lfi.getHelp(getTempTable()));
       }

       /**
        * Set the <code>HELP</code> attribute of handle.
        *
        * @param    help
        *           The HELP to be set.
        */
       @Override
       public void setHelp(String help)
       {
          if (!mutableOptions)
          {
             this.help = new character(help);
          }
          else
          {
             lfi.setHelp(help, getTempTable());
          }
       }

       /**
        * Set the {@code HELP} attribute of handle.
        *
        * @param    help
        *           The HELP to be set.
        */
       @Override
       public void setHelp(character help)
       {
          // TODO: unknown validation ?
          setHelp(help.toJavaType());
       }

       /**
        * Provides a short description of this object used for debugging.
        *
        * @return  a short description of this object used for debugging.
        */
       @Override
       public String toString()
       {
          StringBuilder sb = new StringBuilder("BufferFieldImpl{");
          sb.append(fieldRef.getParentBuffer().getLegacyName()).append(".").append(lfi.getLegacyName());
          sb.append(":").append(fieldRef.getType().getSimpleName());
          if (lfi.getExtent() != 0)
          {
             sb.append("[").append(lfi.getExtent());
             if (fieldRef.getExtent() == null)
             {
                sb.append("*"); // denormalized
             }
             sb.append("]");
          }
          sb.append("}");
          return sb.toString();
       }

       /**
        * Checks whether the widget is write-protected.
        *
        * @return  {@code true} if the widget is write-protected.
        */
       @Override
       public logical isReadOnly()
       {
          UnimplementedFeature.experimental("READ-ONLY attribute: BufferFieldImpl.isReadOnly()");
          return new logical(readOnly);
       }

       /**
        * Sets the READ-ONLY attribute of the widget.
        *
        * @param   ro
        *          {@code true} if the widget should be write-protected.
        */
       @Override
       public void setReadOnly(boolean ro)
       {
          UnimplementedFeature.experimental("READ-ONLY attribute: BufferFieldImpl.setReadOnly()");
          this.readOnly = ro;
       }

       /**
        * Sets the READ-ONLY attribute of the widget.
        *
        * @param   ro
        *          {@code true} if the widget should be write-protected.
        */
       @Override
       public void setReadOnly(logical ro)
       {
          if (ro == null || ro.isUnknown())
          {
             ErrorManager.recordOrShowError(4058, "READ-ONLY", LegacyResource.BUFFER_FIELD);
             // **Unable to assign UNKNOWN value to attribute VIEW-AS on BUFFER-FIELD widget. (4083)
             return;
          }

          setReadOnly(ro.booleanValue());
       }

       /**
        * Get the BUFFER:VIEW-AS attribute.
        *
        * @return  the value of BUFFER:VIEW-AS attribute.
        */
       @Override
       public character viewAs()
       {
          return new character(viewAs);
       }

       /**
        * Set the BUFFER:VIEW-AS attribute.
        *
        * @param    viewAs
        *           The flag state.
        */
       @Override
       public void viewAs(String viewAs)
       {
          UnimplementedFeature.missing("BUFFER:VIEW-AS setter");

          if (viewAs == null)
          {
             ErrorManager.recordOrShowError(4058, "VIEW-AS", LegacyResource.BUFFER_FIELD);
             //**Unable to assign UNKNOWN value to attribute VIEW-AS on BUFFER-FIELD widget. (4083)
             return;
          }

          switch (viewAs.toUpperCase())
          {
             case COMBO_BOX:
             case DROP_DOWN:
             case DROP_DOWN_LIST:
             case FILL_IN:
             case TOGGLE_BOX:
                this.viewAs = viewAs;
                break;
             default:
                ErrorManager.recordOrShowError(4058, "VIEW-AS", LegacyResource.BUFFER_FIELD, viewAs);
                // **Attribute VIEW-AS for the BUFFER-FIELD widget has an invalid value of #VIEW-AS. (4058)
                return;
          }

          this.viewAs = viewAs;
       }

       /**
        * Set the BUFFER:VIEW-AS attribute.
        *
        * @param    viewAs
        *           The flag state.
        */
       @Override
       public void viewAs(character viewAs)
       {
          viewAs(viewAs.toJavaType());
       }

       /**
        * Convert the information about this field at the current moment into a {@code P2JField} object and
        * return it. Because some parts of the is not immutable, each time this method is invoked, a fresh
        * object is created to reflect the current status.
        *
        * @return  The attributes of the field encapsulated in a {@code P2JField} object.
        */
       P2JField asP2JField()
       {
          SerializeOptions serOpt = lfi.getSerializeOptions(getTempTable());
          return new P2JField(_name(), ParmType.fromClass(lfi.getDataType()), lfi.getExtent(),
                              lfi.getFormat(getTempTable()), lfi.getInitialValue(), lfi.getLabel(getTempTable()), lfi.getColumnLabel(getTempTable()),
                              lfi.isCaseSensitive(), lfi.getCodePage(), lfi.getHelp(getTempTable()),
                              serOpt.getXmlNodeType() == SerializeOptions.XmlNodeType.HIDDEN,
                              serOpt.getSerializeName(), serOpt.getXmlDataType(), serOpt.getXmlNodeName(),
                              serOpt.getXmlNodeType().toString(), lfi.mandatory, lfi.getDecimals(getTempTable()), 0/* MAX-WIDTH*/,
                              readOnly, lfi.getValidateExpression(getTempTable()), lfi.getValidateMessage(getTempTable()));
       }

       /**
        * Conversion of LITERAL-QUESTION attribute (KW_LIT_QSTN).
        *
        * Getter of LITERAL-QUESTION attribute.
        *
        * @return  current value of LITERAL-QUESTION attribute.
        */
       boolean _literalQuestion()
       {
          if (literalQuestion != null)
          {
             return literalQuestion.booleanValue();
          }

          return lfi.isLiteralQuestion(getTempTable());
       }

       /**
        * Get the lowercased legacy name.
        *
        * @return   The lowercased legacy name.
        */
       String getLcLegacyName()
       {
          return lfi.getLcLegacyName();
       }

       /**
        * Get the value of INITIAL attribute as a BaseDataType.
        *
        * @return   Current value of INITIAL attribute.
        */
       private BaseDataType initialValue()
       {
          return lfi.getInitialValue().duplicate();
    //      String       init     = lfi.getInitial();
    //      character    initial  = character.valueOf(init);
    //      Class<?>     cls      = fieldRef.getType();
    //
    //      character dateFmt = null;
    //
    //      if (date.class.isAssignableFrom(cls))
    //      {
    //         // save off the old date format
    //         dateFmt = SessionUtils.getDateFormat();
    //
    //         // override with MDY
    //         SessionUtils.setDateFormat("mdy");
    //      }
    //
    //      try
    //      {
    //         return (BaseDataType) (BaseDataTypeFactory.instantiate(character.class, initial));
    //      }
    //      catch (Exception exc)
    //      {
    //         throw new RuntimeException(exc);
    //      }
    //      finally
    //      {
    //         if (dateFmt != null)
    //         {
    //            // restore date format
    //            SessionUtils.setDateFormat(dateFmt);
    //         }
    //      }
       }

       /**
        * Extracts and returns the {@code LegacyFieldInfo} associated with this field. This method also updates
        * the {@code mutableSerializationOptions} if the field belongs to a temp-table.
        *
        * @return  the {@code LegacyFieldInfo} associated with this field.
        */
       private TableMapper.LegacyFieldInfo computeLegacyInfo()
       {
          RecordBuffer parentBuffer = fieldRef.getParentBuffer();
          DmoMeta dmoInfo = parentBuffer.getDmoInfo();
          if (dmoInfo.isTempTable())
          {
             // temp tables case
             mutableOptions = true;
             return TableMapper.getLegacyFieldInfo(parentBuffer.getParentTable(), fieldRef.getProperty());
          }
          else
          {
             // permanent tables: P4GL allows the attribute to be queried for permanent tables and
             // returns the legacy name.
             return TableMapper.getLegacyFieldInfo(dmoInfo, fieldRef.getProperty());
          }
       }

       /**
        * Get the parent temp-table from the {@link #fieldRef}.
        *
        * @return   The parent {@link TempTable} instance, or <code>null</code> if this is not a temporary buffer.
        */
       private TempTable getTempTable()
       {
          RecordBuffer parentBuffer = fieldRef.getParentBuffer();
          return parentBuffer.getParentTable();
       }
    }