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