# AbstractTempTable.java

    /*
    ** Module   : AbstractTempTable.java
    ** Abstract : Abstract base class for static and dynamic temp-table 4GL objects.
    **
    ** Copyright (c) 2013-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 SVL 20131115 Created initial version.
    ** 002 CA  20131209 The dynamic flag must be passed up the c'tor call chain.
    ** 003 HC  20131215 Implemented ADM-DATA and UNIQUE-ID attribute.
    ** 004 SVL 20140106 ERROR-STRING attribute and some error handling functions are now handled by
    **                  this class.
    ** 005 SVL 20140124 Some functionality was moved here from the subclasses. Implemented
    **                  hasRecords().
    ** 006 SVL 20140205 Got some functionality from subclasses, including COPY-TEMP-TABLE.
    ** 007 SVL 20140709 Reflect changes in copyAllRows signature.
    ** 008 OM  20151023 Extracted _hasRecords() worker method. Implemented UNDO attribute management.
    ** 009 CA  20151219 defaultBuffer() returns a TemporaryBuffer.  Added deleteAllBuffers().
    ** 010 ECF 20160501 Replaced direct SQL use in _hasRecords with a more efficient implementation.
    ** 011 ECF 20160609 SQL performance improvement.
    ** 012 OM  20190405 Renamed createTableLike() to createLike().
    ** 013 OM  20190513 Implemented deleteAll() from interface EmptyTempTable.
    ** 014 OM  20190530 Extends XmlData interface. Added support for original/changed tables.
    ** 015 HC  20190719 Fixed an abend during temp-table resource cleanup.
    **     CA  20190826 Fixed READ-XML - delegate to default buffer.
    ** 016 OM  20190910 Reversed READ-XML delegatation to allow unprepared temp-tables to be read
    **                  (with schema) from XML file.
    ** 017 ECF 20200906 New ORM implementation.
    ** 018 AIL 20200911 Implemented Dereferenceable interface.
    **     AIL 20200914 Fixed checking prepared state when dereferencing.
    **     AIL 20200915 Moved prepared method from subclasses here.
    **     OM  20201002 Use DmoMeta cached information instead of map lookups.
    **     OM  20201120 Added SCHEMA-MARSHAL implementation. Improved XML serialization support.
    **     OM  20201208 Added REJECTED attribute.
    **     OM  20210115 For dynamic temp-tables the before-table is automatically created when needed.
    **     OM  20210328 Added validation for all buffers.
    **     ECF 20210506 Use RecordBuffer.getDmoInfo() getter instead of direct field access to prevent
    **                  NPE in proxy case.
    **     ECF 20210612 Added buffer deregistration to fix a memory leak.
    **     OM  20210825 Added implementation of READ-JSON.
    **     OM  20210921 Added implementations of NAMESPACE-PREFIX and XML-NODE-NAME buffer attribute.
    **     OM  20210922 Fixed default values for XML-NODE-NAME and SERIALIZE-NAME.
    **     CA  20210928 TRACKING-CHANGES can be disabled regardless if the temp-table is attached to a dataset or
    **                  not.
    **     OM  20220120 Small optimization.
    **     CA  20220214 The slave buffers are cleaned up only when the associated temp-table gets deleted.
    **     OM  20220422 Allow Xml/JsonImport to load records into buffer without exposing package private methods.
    **     CA  20221006 UNIQUE-ID is kept as a Java type instead of BDT.  Refs #6827
    ** 019 BS  20221129 direct calls to ErrorManager.displayError replaced with ErrorManager.recordOrShowError
    **     CA  20220426 Added PRIMARY attribute stubs.
    **     CA  20220524 deleteAll() must allow explicit bulk delete of the before-table.
    **     CA  20220601 Added 'derefPoly', emitted when '::' is used as r-value in a dynamic extent assignment.
    **     OM  20220603 TRACKING-CHANGES can be set to a temp-table if at least one of its buffers is a dataset
    **                  component.
    **     OM  20220604 Improved dereferenciation support.
    **     OM  20220609 Fixed/added implemenentation of several attributes.
    **     CA  20220920 Buffer implicit deletion performed on temp-table delete must use a ProcedureManager to
    **                  delete it instead of handle.delete.
    **     IAS 20221003 Fixed error message for error 3131
    **     IAS 20221111 Added setter for the 'schemaMarshalLevel', fixed one for the 'namespaceURI'.
    **     RAA 20230109 Changed inline statement(s) to prepared statement(s).
    **     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.
    **                  Removed 'changesTables' as is never used.
    **                  Cache the helper for ProcedureManager, ObjectOps and others (as needed), to reduce
    **                  context-local lookup.
    **     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.
    ** 020 CA  20230302 When deleting a temp-table, the delete can be vetoed if any buffer for the temp-table is
    **                  associated with an OUTPUT dataset.  Refactored to move the resource-specific code in a
    **                  'allowDelete' method.
    ** 021 IAS 20230325 Fixed source validation. Implemented support for dynamic TEMP-TABLE right
    **                  after CREATE TEMP-TABLE.
    ** 022 IAS 20230524 Added 'getFields()'
    **     IAS 20230529 Added 'readTable' method.
    ** 023 CA  20230724 Further reduce context-local usage.
    ** 024 AL2 20230928 Made _hasRecords make use of fastHasRecords API with direct-access.
    ** 025 CA  20231129 Moved the LegacyTableInfo instance from TableMapper to AbstractTempTable (only for temp-
    **                  tables.
    ** 026 DDF 20240122 Integrate BufferManager.activeBuffers() calls into _hasRecords() and reduce the number
    **                  of buffers iterated by only going over the buffers of the current table.
    **     DDF 20240130 Refactor _hasRecords() by using isActiveBuffer().
    **     DDF 20240130 Inlined condition in _hasRecords().
    ** 027 OM  20241009 The content from all buffers of a temp-table are flushed before using any buffer for
    **                  serializing as JSON/XML.
    ** 028 CA  20240324 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
    ** 029 CA  20240404 Error 9047 does not set ERROR-STATUS:ERROR flag.
    **                  Static temp-table resolution by name must be done using the currently executing type and
    **                  the static temp-table's defining type.
    ** 030 CA  20240502 When a BEFORE-TABLE is being built too, for a dynamic temp-table, the associated DMOs for
    **                  both the before and after table must have the after/beforeTable annotations set.
    ** 031 CA  20240512 'flushBuffers' must flush the peer-table's buffers, too.
    ** 032 CA  20240525 Bulk delete can't be executed if TRACKING-CHANGES is on, FWD runtime needs to emulate an
    **                  actual FOR EACH to delete all records.
    ** 033 CA  20240918 Removed default methods from interfaces and moved them as concrete implementations, as
    **                  default methods cause the Java metaspace to spike one order of magnitude.
    ** 034 OM  20241128 Multi-tenant runtime support: selected the proper persistence context, eventually
    **                  based on [sharedDb] parameter.
    ** 035 ICP 20241202 In copyTempTable fixed call to copyAllRows.
    ** 036 OM  20250206 TEMP-TABLE:ORIGIN-HANDLE is reset on EMPTY-DATASET() and EMPTY-TEMP-TABLE().
    **                  TEMP-TABLE:ORIGIN-HANDLE it's actually writable, in spite of what docs say.
    ** 037 ICP 20250131 Used character.of to leverage cached instances.
    ** 038 ES  20250314 Added label methods.
    ** 039 ES  20250313 Moved TempTable:writeXml from temp-table to the default buffer.
    */

    /*
    ** 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 com.goldencode.p2j.persist.TableMapper.MutableFieldInfo;
    import com.goldencode.p2j.persist.orm.*;
    import com.goldencode.p2j.persist.serial.*;
    import com.goldencode.p2j.util.*;

    import java.util.*;
    import java.util.function.*;

    /**
     * Abstract base class for static and dynamic temp-table 4GL objects.
     */
    public abstract class AbstractTempTable
    extends HandleChain
    implements TempTable
    {
       /**
        * The default {@code SCHEMA-MARSHAL} level. Encodes the {@code unknown} value of the attribute. It is
        * semantically equivalent to {@code SM_FULL}.
        */
       public static final int SM_DEFAULT = 0; // = unknown

       /** Encodes the {@code NONE} {@code SCHEMA-MARSHAL} level. No attributes are marshaled. */
       public static final int SM_NONE = 1;

       /** Encodes the {@code MIN} {@code SCHEMA-MARSHAL} level. The marshaled schema is minimized. At this level
        * the following data will still BE marshaled:
        * <ul>
        *    <li>field names, data types, and extents;</li>
        *    <li>temp-table ERROR-STRING</li>
        * </ul>
        * while following data will NOT be marshal:
        * <ul>
        *    <li>index descriptions;</li>
        *    <li>field information (label, help, field validation expression, and so on)</li>
        * </ul>
        */
       public static final int SM_MIN = 2;

       /** Encodes the {@code MIN} {@code SCHEMA-MARSHAL} level. The full schema is marshalled. */
       public static final int SM_FULL = 3;

       /** Default prefix for the name of a temp-table copied with COPY-TEMP-TABLE. */
       protected static final String DEFAULT_COPY_TEMP_TABLE_PREFIX = "cpy";

       /** Default buffer. */
       protected BufferImpl defaultBuffer = null;

       /** Corresponds ERROR-STRING attribute. */
       protected String errorString = null;

       /** Corresponds ERROR attribute. **/
       protected Boolean error = false;

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

       /** {@code true} if changes made to the records the temporary table can be undone. */
       protected boolean undoable = false;

       /** Direct reference to the table mapper's structure. */
       TableMapper.LegacyTableInfo tableInfo = null;

       /**
        * The current value of the {@code SCHEMA-MARSHAL} attribute.
        * <p>
        * The following encoding is used internally: 0 = unknown, 1: NONE, 2: MIN, 3: FULL (see above, the SM_*
        * static constants).
        */
       private int schemaMarshal = SM_DEFAULT;

       /**
        * The current value of boolean {@code REJECTED} attribute: indicates whether a change to the
        * data of this {@code DataSet} is rejected. The attribute is writable.
        */
       private boolean rejected = false;

       /**
        * The set of child buffers, in registration order. The first buffer added (the default buffer) is always
        * guaranteed to be the last processed. Its existence was first needed when a buffer or complete table was
        * serialized and all the buffers are validated in this order, default buffer last.
        */
       protected final Set<Buffer> allBuffers = new LinkedHashSet<>();

       /**
        * Enumeration which represents some temp-table functions. Required because error handing may
        * vary depending on the calling function.
        */
       protected enum CallingFunction
       {
          COPY_TEMP_TABLE,
          ADD_FIELDS_FROM,
          CREATE_LIKE
       }

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

       /** The current value of {@code TRACKING-CHANGES} attribute. */
       private boolean trackingChanges = false;

       /** The current value resource of the {@code ORIGIN-HANDLE} attribute. */
       private TempTable originTable = null;

       /** Is this an AFTER-TABLE or BEFORE-TABLE? Cached after value is computed. */
       protected BeforeType beforeTableType = BeforeType.UNKNOWN;

       /**
        * The cached peer buffer. For AFTER-TABLE it is the BEFORE-TABLE and vice-versa. The SIMPLE
        * buffers do not have a peer table.
        */
       protected TempTable peerTable = null;

       /** Current value of the {@code DATA-SOURCE-MODIFIED} attribute. */
       private boolean dataSourceModified = false;

       /** The mutable field info for this temp-table instance. */
       private Map<String, TableMapper.MutableFieldInfo> fieldInfo = null;

       /**
        * Default constructor.
        *
        * @param    dynamic
        *           Flag indicating if this is a static or a dynamic temp-table.
        */
       public AbstractTempTable(boolean dynamic)
       {
          super(dynamic);
       }

       /**
        * Get the PRIMARY attribute.
        *
        * @return   The primary index.
        */
       @Override
       public character primaryIndex()
       {
          if (defaultBuffer == null)
          {
             return new character();
          }

          return defaultBuffer.primaryIndex();
       }

       /**
        * Set the PRIMARY attribute.
        *
        * @param    primary
        *           The primary index.
        */
       @Override
       public void primaryIndex(Text primary)
       {
          primaryIndex(primary.toJavaType());
       }

       /**
        * Set the PRIMARY attribute. This method handles the cases of static or prepared tables and the case of
        * null parameter (all with errors).
        *
        * @param   primary
        *          The primary index.
        */
       @Override
       public void primaryIndex(String primary)
       {
          if (primary == null)
          {
             ErrorManager.recordOrShowError(4083, "PRIMARY", LegacyResource.TEMP_TABLE + " widget");
             // **Unable to assign UNKNOWN value to attribute <attribute> on <widget id>. (4083)
             return;
          }

          ErrorManager.recordOrShowError(9065, "", "");
          // Cannot set PRIMARY on a TEMP-TABLE that has been prepared with TEMP-TABLE-PREPARE. (9065)

          ErrorManager.recordOrThrowError(3131, "PRIMARY", "", LegacyResource.TEMP_TABLE);
          // Unable to set attribute <attribute name> in widget <widget name> of type <widget-tpe>. (3131)
       }

       /**
        * 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(UniqueIdGenerator.IdKind.TEMP_TABLE);
          }

          return new integer(this.uniqueId);
       }

       /**
        * Copies the other temp-table. other must be a temp-tables handle By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        *
        * @return  true if operation is successful
        */
       public logical copyTempTable(handle other)
       {
          return copyTempTable(other, logical.of(false));
       }

       /**
        * Copies the other temp-table. other must be a temp-tables handle By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        *
        * @return  true if operation is successful
        */
       public logical copyTempTable(handle other, logical append)
       {
          return copyTempTable(other, append, logical.of(false));
       }

       /**
        * Copies the other temp-table. other must be a temp-tables handle By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        *
        * @return  true if operation is successful
        */
       public logical copyTempTable(handle other, logical append, logical replace)
       {
          return copyTempTable(other, append, replace, logical.of(false));
       }

       /**
        * Copies the other temp-table. other must be a temp-tables handle By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        * @param   looseCopy
        *          Loose copy mode.
        *
        * @return  true if operation is successful
        */
       public logical copyTempTable(handle other, logical append, logical replace, logical looseCopy)
       {
          return copyTempTable(other, append, replace, looseCopy, new character());
       }

       /**
        * Copies the other temp-table. other must be a temp-tables handle By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        *
        * @return  true if operation is successful
        */
       public logical copyTempTable(handle other, boolean append)
       {
          return copyTempTable(other, logical.of(append));
       }

       /**
        * Copies the other temp-table. other must be a temp-tables handle By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        *
        * @return  true if operation is successful
        */
       public logical copyTempTable(handle other, boolean append, boolean replace)
       {
          return copyTempTable(other, logical.of(append), logical.of(replace));
       }

       /**
        * Copies the other temp-table. other must be a temp-tables handle By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        * @param   looseCopy
        *          Loose copy mode.
        *
        * @return  true if operation is successful
        */
       public logical copyTempTable(handle other, boolean append, boolean replace, boolean looseCopy)
       {
          return copyTempTable(other,
                logical.of(append),
                logical.of(replace),
                logical.of(looseCopy));
       }

       /**
        * Copies the other temp-table. other must be a temp-tables handle By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        * @param   looseCopy
        *          Loose copy mode.
        * @param   prefix
        *          Prefix for naming the copied temp-table.
        *
        * @return  true if operation is successful
        */
       public logical copyTempTable(handle  other,
                                    boolean append,
                                    boolean replace,
                                    boolean looseCopy,
                                    String  prefix)
       {
          return copyTempTable(other,
                logical.of(append),
                logical.of(replace),
                logical.of(looseCopy),
                new character(prefix));
       }

       /**
        * Copies the other temp-table. other must be a temp-tables handle By default, all current
        * records are dropped before copy operation.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        * @param   looseCopy
        *          Loose copy mode.
        * @param   prefix
        *          Prefix for naming the copied temp-table.
        *
        * @return  true if operation is successful
        */
       public logical copyTempTable(handle    other,
                                    logical   append,
                                    logical   replace,
                                    logical   looseCopy,
                                    character prefix)
       {
          RecordBuffer otherBuf = getRecordBufferByHandle(other, CallingFunction.COPY_TEMP_TABLE);
          if (otherBuf == null)
          {
             return logical.of(false);
          }

          if (!prepared().booleanValue())
          {
             if (addFunctionWasCalled())
             {
                displayCopyTempTableHalfDefined();
                return logical.of(false);
             }

             boolean res = createLike(other).booleanValue();
             if (!res)
             {
                return logical.of(false);
             }
             String tableName = (prefix.isUnknown() ? DEFAULT_COPY_TEMP_TABLE_PREFIX : prefix.getValue()) +
                                otherBuf.getParentTable()._name();
             tempTablePrepare(tableName);
          }

          Temporary srcDMO = (Temporary) otherBuf.getDMOProxy();
          Temporary dstDMO = (Temporary) defaultBufferHandleNative();

          boolean res = TemporaryBuffer.copyAllRows(null,
                                                    srcDMO,
                                                    dstDMO,
                                                    append.booleanValue() || replace.booleanValue(),
                                                    TemporaryBuffer.CopyTableMode.COPY_TEMP_TABLE_MODE,
                                                    append.booleanValue() || replace.booleanValue(),
                                                    replace.booleanValue(),
                                                    looseCopy.booleanValue());
          return logical.of(res);
       }

       /**
        * Emitted for <code>::</code> operator used as r-value in a dynamic extent assignment, as the conversion
        * rules doesn't know if the target field is extent or scalar.
        *
        * @param    memberName
        *           The name of the member to be extracted.
        *
        * @return   The value of the member (scalar or extent) or unknown (?) if there is no such member.
        */
       @Override
       public Object derefPoly(String memberName)
       {
          if (!_prepared())
          {
             displayNotPrepared();
             return null;
          }

          // delegate to default buffer
          return defaultBuffer.derefPoly(memberName);
       }

       /**
        * Dereference a named member from this collection. If this collection is a ProDataSet then
        * the returned value is a {@link handle} to a named buffer, if the collection is a buffer
        * the returned value is the field value with the respective name. In the case that this
        * collection does not have a named member with the name specified <code>memberName</code>
        * the unknown value is returned.
        *
        * @param   index
        *          The subscript to be applied to an extent field.
        * @param   memberName
        *          The name of the member to be extracted.
        *
        * @return  The value of the member or unknown (?) if there is no such member.
        */
       @Override
       public BaseDataType dereference(long index, String memberName)
       {
          UnimplementedFeature.todo("subscripted dereference is not implemented");
          return new unknown();
       }

       /**
        * This overloaded method is used when the dereference is the left-side of an assignment.
        * The implementation will extract the member from the collection and assign it the new value.
        *
        * @param   index
        *          The subscript to be applied to an extent field.
        * @param   memberName
        *          The name of the member to be extracted.
        * @param   value
        *          The new value that will be assigned to the extracted field.
        */
       @Override
       public void dereference(long index, String memberName, Object value)
       {
          UnimplementedFeature.todo("subscripted dereference is not implemented");
       }

       /**
        * Dereference a named member from this collection. If this collection is a ProDataSet then
        * the returned value is a {@link handle} to a named buffer, if the collection is a buffer
        * the returned value is the field value with the respective name. In the case that this
        * collection does not have a named member with the name specified <code>memberName</code>
        * the unknown value is returned.
        *
        * @param   index
        *          The subscript to be applied to an extent field.
        * @param   memberName
        *          The name of the member to be extracted.
        *
        * @return  The value of the member or unknown (?) if there is no such member.
        */
       @Override
       public BaseDataType dereference(NumberType index, String memberName)
       {
          UnimplementedFeature.todo("subscripted dereference is not implemented");
          return new unknown();
       }

       /**
        * This overloaded method is used when the dereference is the left-side of an assignment.
        * The implementation will extract the member from the collection and assign it the new value.
        *
        * @param   index
        *          The subscript to be applied to an extent field.
        * @param   memberName
        *          The name of the member to be extracted.
        * @param   value
        *          The new value that will be assigned to the extracted field.
        */
       @Override
       public void dereference(NumberType index, String memberName, Object value)
       {
          UnimplementedFeature.todo("subscripted dereference is not implemented");
       }

       /**
        * All kind of temp-tables can be dereferenced. This should be propagated to the
        * {@link #defaultBuffer} if not null. Otherwise, the table was not prepared
        * (for dynamic temp-tables).
        *
        * @param   memberName
        *          The name of the member to be extracted.
        *
        * @return  The value of the member - processed by the {@link #defaultBuffer}.
        */
       @Override
       public BaseDataType dereference(String memberName)
       {
          if (!_prepared())
          {
             displayNotPrepared();
             return null;
          }

          return defaultBuffer.dereference(memberName);
       }

       /**
        * All kind of temp-tables can be dereferenced. This should be propagated to the
        * {@link #defaultBuffer} if not null. Otherwise, the table was not prepared
        * (for dynamic temp-tables).
        *
        * @param   type
        *          The expected type of the returned value.
        * @param   memberName
        *          The name of the member to be extracted.
        *
        * @return  The value of the member - processed by the {@link #defaultBuffer}.
        */
       @Override
       public <T extends BaseDataType> T dereference(Class<T> type, String memberName)
       {
          if (!_prepared())
          {
             displayNotPrepared();
             return null;
          }

          return defaultBuffer.dereference(type, memberName);
       }

       /**
        * All kind of temp-tables can be dereferenced. This should be propagated to the
        * {@link #defaultBuffer} if not null. Otherwise, the table was not prepared
        * (for dynamic temp-tables) and the uninitialized error should be displayed.
        *
        * @param   memberName
        *          The name of the member to be extracted.
        * @param   value
        *          The new value that will be assigned to the extracted field.
        */
       @Override
       public void dereference(String memberName, Object value)
       {
          if (!_prepared())
          {
             displayUninitialized();
             return;
          }

          defaultBuffer.dereference(memberName, value);
       }

       /**
        * Returns <code>true</code> if this temp-table is in prepared state (after a successful call
        * to {@link #tempTablePrepare}).
        * This method implements the read-only PREPARED attribute from 4GL.
        *
        * @return  true if operation is successful
        */
       public logical prepared()
       {
          return logical.of(_prepared());
       }

       /**
        * Sets the ERROR-STRING attribute of 4GL temp-table.
        *
        * @param   errStr
        *          The new value for the ERROR-STRING attribute.
        */
       public void changeErrorString(character errStr)
       {
          if (!prepared().getValue())
          {
             ErrorManager.recordOrShowError(12346,
                                            "ERROR-STRING can only be used with a PREPARED temp-table" +
                                            " that is not a BEFORE-TABLE",
                                            true,
                                            false);
             ErrorManager.recordOrThrowError(3131,
                                             "Unable to set attribute ERROR-STRING in widget of type TEMP-TABLE",
                                             false);
             return;
          }

          this.errorString = errStr.getValue();
       }

       /**
        * Obtain the current ERROR-STRING attribute of 4GL temp-table.
        *
        * @return  the current ERROR-STRING of the temp-table
        */
       public character errorString()
       {
          return new character(errorString);
       }

       /**
        * 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.getValue();
       }

       /**
        * Returns the state of the ERROR attribute
        *
        * @return    Current state of the ERROR attribute.
        */
       @Override
       public logical error()
       {
          if (!prepared().booleanValue())
          {
             displayNotPrepared();
          }

          return logical.of(error);
       }

       /**
        * Setter for ERROR attribute.
        *
        * @param    value
        *           The new value of ERROR attribute.
        */
       @Override
       public void error(boolean value)
       {
          error(logical.of(value));
       }

       /**
        * Setter for ERROR attribute.
        *
        * @param    value
        *           The new value of ERROR attribute.
        */
       @Override
       public void error(logical value)
       {
          if (value.isUnknown())
          {
             unableToAssignUnknown("ERROR", "TEMP-TABLE widget");
             return;
          }

          if (!prepared().booleanValue())
          {
             displayNotPrepared();
             unableToAssignUnknown("ERROR", "TEMP-TABLE widget");
             return;
          }

          error = value.toJavaType();
       }

       /**
        * Sets the UNDO attribute of 4GL temp-table. The change is allowed only if at the moment of the
        * call, the following are true:
        *  - the table was not prepared yet (static temp-tables are always prepared);
        *  - there is no transaction in progress (so UNDO operations cannot be executed with current state);
        *  - there are no records in the table (an undo operation won't affect records in this table).
        *
        * @param   undo
        *          The new value for the attribute.
        */
       public void setCanUndo(logical undo)
       {
          if (undo.isUnknown())
          {
             displayUnableAssignUnknownUndo();
             return;
          }

          final boolean isPrepared = _prepared();
          if (isPrepared && TransactionManager.isTransaction() && _hasRecords())
          {
             String[] msgs =
             {
                "From within a transaction, UNDO cannot be set on a TEMP-TABLE " +
                "that has been already prepared with TEMP-TABLE-PREPARE",
                "Unable to set attribute UNDO in widget  of type TEMP-TABLE"
             };
             ErrorManager.recordOrThrowError(new int[] { 9064, 3131 }, msgs, false);
             return;
          }

          this.undoable = undo.booleanValue();
       }

       /**
        * Obtain the current UNDO attribute of 4GL temp-table.
        *
        * @return  true if the temp-table can undo.
        */
       public logical canUndo()
       {
          return logical.of(isUndoable());
       }

       /**
        * Checks whether this temp-table contains any records.
        *
        * @return  true if this temp-table has records.
        */
       @Override
       public logical hasRecords()
       {
          return logical.of(_hasRecords());
       }

       /**
        * Get the current value of UNDO attribute of this temp-table.
        *
        * @return  the current value of UNDO attribute of this temp-table.
        */
       @Override
       public final boolean isUndoable()
       {
          return this.undoable;
       }

       /**
        * Bulk delete all records associated with the {@code TempTable}.
        *
        * @return  {@code true} if the delete was possible.
        *
        * @throws  ErrorConditionException
        *          If an error occurs performing the bulk delete operation.
        */
       @Override
       public logical deleteAll()
       {
          if (trackingChanges)
          {
             ErrorManager.recordOrShowError(12758, _name(), "");
             // You may not EMPTY temp-table <name> when TRACKING-CHANGES is true.
             return logical.of(false);
          }

          if (computeTableType() == BeforeType.BEFORE && defaultBuffer.hasBeforeBufferConstraints())
          {
             ErrorManager.recordOrShowError(11946);
             // You cannot EMPTY a BEFORE-TABLE, Use ACCEPT-CHANGES or EMPTY the AFTER-TABLE.
             return logical.of(false);
          }

          AbstractTempTable bTable = (AbstractTempTable) getBeforeTableNative();
          if (bTable != null)
          {
             ((BufferImpl) bTable.defaultBuffer().getDMOProxy()).setUpBeforeBuffer(true);
             try
             {
                // drop the before images
                bTable.deleteAllImpl();
             }
             finally
             {
                ((BufferImpl) bTable.defaultBuffer().getDMOProxy()).setUpBeforeBuffer(false);
             }
          }

          deleteAllImpl();
          originTable = null;

          return logical.of(true);
       }

       /**
        * Bulk delete all records associated with the {@code Buffer} or {@code TempTable}.  This allows bulk
        * delete when TRACKING-CHANGES is on, as this emulates a {@code FOR EACH tt1. DELETE tt1. end.} block.
        *
        * @throws  ErrorConditionException
        *          If an error occurs performing the bulk delete operation.
        */
       @Override
       public void deleteForAll()
       {
          if (trackingChanges ||
              computeTableType() == BeforeType.BEFORE && defaultBuffer.hasBeforeBufferConstraints())
          {
             // emulate a FOR EACH block

             TemporaryBuffer buffer = defaultBuffer();
             TempTableBuffer proxy = (TempTableBuffer) buffer.getDMOProxy();
             String alias = buffer.getDMOAlias();

             AdaptiveQuery query1 = new AdaptiveQuery();
             BlockManager.forEach(query1, "##deleteForAll", new Block((Init) () ->
             {
                query1.initialize(proxy, ((String) null), null, alias + ".recid asc");
             },
             (Body) () ->
             {
                proxy.deleteRecord();
             }));

             return;
          }

          deleteAllImpl();
       }

       /**
        * Obtain the current {@code SCHEMA-MARSHAL} level. Refer to {@code SM_DEFAULT}, {@code SM_NONE},
        * {@code SM_MIN}, and {@code SM_FULL} static constants for details.
        *
        * @return  the current {@code SCHEMA-MARSHAL} level.
        */
       public int getSchemaMarshalLevel()
       {
          return schemaMarshal;
       }

       /**
        * Set the current {@code SCHEMA-MARSHAL} level. Refer to {@code SM_DEFAULT}, {@code SM_NONE},
        * {@code SM_MIN}, and {@code SM_FULL} static constants for details.
        */
       public void setSchemaMarshalLevel(int schemaMarshalLevel)
       {
          this.schemaMarshal = schemaMarshalLevel;
       }

       /**
        * Implements the {@code NO-SCHEMA-MARSHAL} attribute getter.
        *
        * @return  {@code true} when {@code SCHEMA-MARSHAL} attribute is set to {@code "NONE"}.
        */
       @Override
       public logical isNoSchemaMarshal()
       {
          return logical.of(schemaMarshal == SM_NONE);
       }

       /**
        * Sets the {@code SCHEMA-MARSHAL} attribute to {@code "NO"} value when {@code on = true} or
        * back to default value {@code on = false}.
        *
        * @param   on
        *          The new value of {@code SCHEMA-MARSHAL} attribute.
        */
       @Override
       public void setNoSchemaMarshal(boolean on)
       {
          schemaMarshal = on ? SM_NONE : SM_DEFAULT;
       }

       /**
        * Sets the {@code SCHEMA-MARSHAL} attribute to {@code "NO"} value when {@code on = true} or
        * back to default value {@code on = false}.
        *
        * @param   on
        *          The new value of {@code SCHEMA-MARSHAL} attribute.
        */
       @Override
       public void setNoSchemaMarshal(logical on)
       {
          if (on == null || on.isUnknown())
          {
             unableToAssignUnknown("NO-SCHEMA-MARSHALL", "TEMP-TABLE widget");
             return;
          }

          setNoSchemaMarshal(on.toJavaType());
       }

       /**
        * Implements the {@code MIN-SCHEMA-MARSHAL} attribute getter.
        *
        * @return  {@code true} when {@code SCHEMA-MARSHAL} attribute is set to {@code "MIN"}.
        */
       @Override
       public logical isMinSchemaMarshal()
       {
          return logical.of(schemaMarshal == SM_MIN);
       }

       /**
        * Sets the {@code SCHEMA-MARSHAL} attribute to {@code "MIN"} value when {@code on = true} or
        * back to default value {@code on = false}.
        *
        * @param   on
        *          The new value of {@code SCHEMA-MARSHAL} attribute.
        */
       @Override
       public void setMinSchemaMarshal(boolean on)
       {
          schemaMarshal = on ? SM_MIN : SM_DEFAULT;
       }

       /**
        * Sets the {@code SCHEMA-MARSHAL} attribute to {@code "MIN"} value when {@code on = true} or
        * back to default value {@code on = false}.
        *
        * @param   on
        *          The new value of {@code SCHEMA-MARSHAL} attribute.
        */
       @Override
       public void setMinSchemaMarshal(logical on)
       {
          if (on == null || on.isUnknown())
          {
             unableToAssignUnknown("MIN-SCHEMA-MARSHAL", "TEMP-TABLE widget");
             return;
          }

          setMinSchemaMarshal(on.toJavaType());
       }

       /**
        * Obtain the current value of {@code SCHEMA-MARSHAL} attribute.
        *
        * @return  the current value of {@code SCHEMA-MARSHAL} attribute.
        */
       @Override
       public character getSchemaMarshal()
       {
          switch (schemaMarshal)
          {
             case SM_DEFAULT:
                return new character();
             case SM_NONE:
                return new character(TempTable.SCHEMA_MARSHAL_NONE);
             case SM_MIN:
                return new character(TempTable.SCHEMA_MARSHAL_MIN);
             case SM_FULL:
                return new character(TempTable.SCHEMA_MARSHAL_FULL);
          }
          throw new IllegalStateException("Illegal state for TEMP-TABLE:SCHEMA-MARSHAL attribute.");
       }

       /**
        * Sets a new value for {@code SCHEMA-MARSHAL} attribute.
        *
        * @param   level
        *          the new value or {@code SCHEMA-MARSHAL} attribute.
        */
       @Override
       public void setSchemaMarshal(String level)
       {
          if (level == null)
          {
             schemaMarshal = SM_DEFAULT;
             return;
          }

          switch (level.toUpperCase())
          {
             case SCHEMA_MARSHAL_FULL:
                schemaMarshal = SM_FULL;
                // full table schema is serialized with table parameter
                break;

             case SCHEMA_MARSHAL_MIN:
                schemaMarshal = SM_MIN;
                // marshals:
                //    field names, data types, and extents;
                //    temp-table ERROR-STRING
                // does NOT marshal:
                //    index descriptions
                //    field information (label, help, field validation expression, and so on (?))
                break;

             case SCHEMA_MARSHAL_NONE:
                schemaMarshal = SM_NONE;
                // just table data (?)
                break;

             default:
                ErrorManager.recordOrThrowError(12790, level, "");
                // Invalid value given for SCHEMA-MARSHAL <value>.
          }
       }

       /**
        * Sets a new value for {@code SCHEMA-MARSHAL} attribute.
        *
        * @param   level
        *          the new value or {@code SCHEMA-MARSHAL} attribute.
        */
       @Override
       public void setSchemaMarshal(Text level)
       {
          setSchemaMarshal(level == null ? null : level.toJavaType());
       }

       /**
        * Checks whether changes in this temp-table is enabled for a {@code DataSet}.
        *
        * @return  The current value for {@code TRACKING-CHANGES} as described above.
        */
       @Override
       public logical isTrackingChanges()
       {
          return logical.of(trackingChanges);
       }

       /**
        * Returns the state of the REJECTED attribute
        *
        * @return Current state of the REJECTED attribute.
        */
       @Override
       public logical rejected()
       {
          return logical.of(rejected);
       }

       /**
        * Setter for REJECTED attribute.
        *
        * @param value The new value of REJECTED attribute.
        */
       @Override
       public void rejected(boolean value)
       {
          error(true); // TODO probably

          rejected = value;
       }

       /**
        * Setter for REJECTED attribute.
        *
        * @param value The new value of REJECTED attribute.
        */
       @Override
       public void rejected(logical value)
       {
          if (value == null || value.isUnknown())
          {
             unableToAssignUnknown("REJECTED", "TEMP-TABLE widget");
             return;
          }

          rejected(value.getValue());
       }

       /**
        * Enable or disable changes tracking this temp-table is enabled for a {@code DataSet}.
        *
        * @param   val
        *          The new value for {@code TRACKING-CHANGES} attribute.
        */
       @Override
       public void setTrackingChanges(logical val)
       {
          if (val == null || val.isUnknown())
          {
             unableToAssignUnknown("TRACKING-CHANGES", "TEMP-TABLE widget");
             return;
          }
          setTrackingChanges(val.getValue());
       }

       /**
        * Enable or disable changes tracking this temp-table is enabled for a {@code DataSet}.
        *
        * @param   on
        *          The new value for {@code TRACKING-CHANGES} attribute.
        */
       @Override
       public void setTrackingChanges(boolean on)
       {
          if (!on)
          {
             // quick out: disabling TRACKING-CHANGES, nothing to validate
             trackingChanges = false;
             return;
          }

          // look for a dataset buffer
          handle dataSet = null;
          for (Buffer buffer : allBuffers)
          {
             dataSet = buffer.dataSet();
             if (!dataSet.isUnknown() && dataSet._isValid())
             {
                break; // found a buffer with a valid dataset, not mandatory the default buffer
             }

             dataSet = null;
          }

          if (dataSet == null)
          {
             ErrorManager.recordOrShowError(12355);
             // TRACKING-CHANGES may not be turned on for a non-dataset temp-table.
             ErrorManager.recordOrThrowError(3131, "TRACKING-CHANGES", "", "TEMP-TABLE");
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
             return;
          }

          TempTable bt = getBeforeTableNative();
          if (bt == null)
          {
             if (_dynamic())
             {
                // automatically create implicit before table for this dynamic temp-table
                ((TempTableBuilder) this).createBeforeTable("BI" + _name(), _name());
             }
             else
             {
                ErrorManager.recordOrShowError(12344);
                // Cannot turn on TRACKING-CHANGES for a static temp-table unless DEFINED with a BEFORE-TABLE phrase.
                ErrorManager.recordOrThrowError(3131, "TRACKING-CHANGES", "", "TEMP-TABLE");
                // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
                return;
             }
          }

          // all good, set the flag
          trackingChanges = true;
       }

       /**
        * Get the handle of the before-image table that corresponds to the after-image table
        * currently associated with this temp-table handle.
        *
        * @return  the value of {@code BEFORE-TABLE} as described above.
        */
       @Override
       public handle getBeforeTable()
       {
          return new handle(getBeforeTableNative());
       }

       /**
        * Get the handle of the before-image table that corresponds to the after-image table
        * currently associated with this temp-table handle.
        *
        * @return  the value of {@code BEFORE-TABLE} as described above.
        */
       @Override
       public TempTable getBeforeTableNative()
       {
          if (computeTableType() == BeforeType.AFTER)
          {
             return peerTable;
          }

          return null;
       }

       /**
        * Get the handle of the after-image table that corresponds to the before-image table
        * currently associated with this temp-table handle.
        *
        * @return  the value of {@code AFTER-TABLE} as described above.
        */
       @Override
       public handle getAfterTable()
       {
          return new handle(getAfterTableNative());
       }

       /**
        * Get the handle of the after-image table that corresponds to the before-image table
        * currently associated with this temp-table handle.
        *
        * @return  the value of {@code AFTER-TABLE} as described above.
        */
       @Override
       public TempTable getAfterTableNative()
       {
          if (computeTableType() == BeforeType.BEFORE)
          {
             return peerTable;
          }

          return null;
       }

       /**
        * Get the handle of the TEMP-TABLE in the original source {@code DataSet} object that
        * corresponds to the TEMP-TABLE currently associated with this TEMP-TABLE handle.
        *
        * @return  the current value of the {@code ORIGIN-HANDLE} attribute.
        */
       @Override
       public handle getOriginHandle()
       {
          return new handle(originTable);
       }

       /**
        * Set the handle of the TEMP-TABLE in the original source {@code DataSet} object that
        * corresponds to the TEMP-TABLE currently associated with this TEMP-TABLE handle.
        * <p>
        * NOTE:
        * In spite of reference manual specifying this is a read-only attribute, in practice this is not the case!
        *
        * @param  newVal
        *         The new value of the {@code ORIGIN-HANDLE} attribute.
        */
       @Override
       public void setOriginHandle(handle newVal)
       {
          originTable = newVal.unwrapTempTable();
       }

       /**
        * Get the handle of the TEMP-TABLE in the original source {@code DataSet} object that
        * corresponds to the TEMP-TABLE currently associated with this TEMP-TABLE handle.
        *
        * @return  the current origin table.
        */
       @Override
       public TempTable getOriginTable()
       {
          return originTable;
       }

       /**
        * Implementation of the READ-XML method. Reads XML data from the specified data source into
        * this object.
        * <p>
        * Defaults applied:
        * <ul>
        *    <li>Default field type mappings</li>
        *    <li>LOOSE schema verification mode</li>
        * </ul>
        *
        * @param   source
        *          Data source.
        * @param   readMode
        *          Mode in which data is read into buffer. Must evaluate to {@code APPEND},
        *          {@code EMPTY}, {@code MERGE}, or {@code REPLACE}.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to use the
        *          {@code xsi:noNamespaceSchemaLocation} value set in the XML data, if any.
        * @param   overrideDefaultMapping
        *          {@code true} to override the default string-to-binary data type mappings when
        *          creating a temp-table schema from an XML schema.
        *
        * @return  {@code true} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical readXml(SourceData source,
                              character readMode,
                              character schemaLocation,
                              logical overrideDefaultMapping)
       {
          return readXml(source, readMode, schemaLocation, overrideDefaultMapping,
                         new character(), new character());
       }

       /**
        * Implementation of the READ-XML method. Reads XML data from the specified data source into
        * this object.
        *
        * @param   source
        *          Data source.
        * @param   readMode
        *          Mode in which data is read into buffer. Must evaluate to {@code APPEND}, {@code
        *          EMPTY}, {@code MERGE}, or {@code REPLACE}.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to use the
        *          {@code xsi:noNamespaceSchemaLocation} value set in the XML data, if any.
        * @param   overrideDefaultMapping
        *          {@code True} to override the default string-to-binary data type mappings when
        *          creating a temp-table schema from an XML schema.
        * @param   fieldTypeMapping
        *          Comma-delimited list of field name and data type name pairs to use to override
        *          data type mappings of specific fields. Specify unknown value to use default
        *          mappings.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical readXml(SourceData source,
                              character readMode,
                              character schemaLocation,
                              logical overrideDefaultMapping,
                              character fieldTypeMapping)
       {
          return readXml(source, readMode, schemaLocation, overrideDefaultMapping,
                         fieldTypeMapping, new character());
       }

       /**
        * Implementation of the READ-XML method. Reads XML data from the specified data source into
        * this object.
        *
        * @param   source
        *          Data source.
        * @param   readMode
        *          Mode in which data is read into buffer. Must evaluate to {@code APPEND}, {@code
        *          EMPTY}, {@code MERGE}, or {@code REPLACE}.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to use the
        *          {@code xsi:noNamespaceSchemaLocation} value set in the XML data, if any.
        * @param   overrideDefaultMapping
        *          {@code True} to override the default string-to-binary data type mappings when
        *          creating a temp-table schema from an XML schema.
        * @param   fieldTypeMapping
        *          Comma-delimited list of field name and data type name pairs to use to override
        *          data type mappings of specific fields. Specify unknown value to use default
        *          mappings.
        * @param   verifySchemaMode
        *          Schema verification mode. Must evaluate to {@code IGNORE}, {@code LOOSE}, or
        *          {@code STRICT}. Default is {@code LOOSE}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical readXml(SourceData source,
                              character readMode,
                              character schemaLocation,
                              logical overrideDefaultMapping,
                              character fieldTypeMapping,
                              character verifySchemaMode)
       {
          if (!_prepared() && !_clear())
          {
             ErrorManager.recordOrShowError(13159);
             // Dynamic temp-table must be in CLEAR or PREPARED state for READ-XML Methods.
             return logical.of(false);
          }

          Map<String, String> mapping = null;
          if (fieldTypeMapping != null)
          {
             String javaStrMapping = fieldTypeMapping.toJavaType();
             if (javaStrMapping != null)
             {
                mapping = new HashMap<>();
                String[] splits = javaStrMapping.split(",");
                int len = splits.length;
                if (len == 0 || len % 2 == 1)
                {
                   ErrorManager.recordOrShowError(13200);
                   // Odd number of values in field-type-mapping argument.
                   return logical.of(false);
                }
                // no other validation at this time

                for (int k = 0; k < len; k += 2)
                {
                   mapping.putIfAbsent(splits[k], splits[k + 1]); // do not overwrite if field is duplicated
                }
             }
          }

          Buffer defaultBuffer = defaultBufferHandleNative();
          // normalize parameters
          XmlImport xmlImport = new XmlImport(
                source,
                readMode.toJavaType(),
                schemaLocation.toJavaType(),
                !overrideDefaultMapping.isUnknown() && overrideDefaultMapping.booleanValue(),
                mapping,
                verifySchemaMode.toJavaType());

          // perform import
          return logical.of(xmlImport.importTable(defaultBuffer, RecordBuffer::loadRecord));
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>Unformatted text</li>
        * <li>UTF-8 encoding</li>
        * <li>xsi:noNamespaceSchemaLocation=?</li>
        * <li>No XML schema written</li>
        * <li>No before-image data written</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeXml(TargetData target)
       {
          return writeXml(target, null, null, null, null, null, null, null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>UTF-8 encoding</li>
        * <li>xsi:noNamespaceSchemaLocation=?</li>
        * <li>No XML schema written</li>
        * <li>No before-image data written</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeXml(TargetData target, logical formatted)
       {
          return writeXml(target, formatted, null, null, null, null, null, null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>xsi:noNamespaceSchemaLocation=?</li>
        * <li>No XML schema written</li>
        * <li>No before-image data written</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeXml(TargetData target, logical formatted, character encoding)
       {
          return writeXml(target, formatted, encoding, null, null, null, null, null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>No XML schema written</li>
        * <li>No before-image data written</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to set the
        *          {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeXml(TargetData target,
                               logical formatted,
                               character encoding,
                               character schemaLocation)
       {
          return writeXml(target, formatted, encoding, schemaLocation, null, null, null, null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>No before-image data written</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to set the
        *          {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
        * @param   writeXmlSchema
        *          {@code True} to write XML schema information, else {@code false}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeXml(TargetData target,
                               logical formatted,
                               character encoding,
                               character schemaLocation,
                               logical writeXmlSchema)
       {
          return writeXml(target, formatted, encoding, schemaLocation, writeXmlSchema, null, null, null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>No before-image data written</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to set the
        *          {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
        * @param   writeXmlSchema
        *          {@code True} to write XML schema information, else {@code false}.
        * @param   minXmlSchema
        *          {@code True} to write minimal XML schema information, else {@code false}. Must be
        *          {@code false} if {@code writeXmlSchema} is {@code false}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeXml(TargetData target,
                               logical formatted,
                               character encoding,
                               character schemaLocation,
                               logical writeXmlSchema,
                               logical minXmlSchema)
       {
          return writeXml(target,
                          formatted,
                          encoding,
                          schemaLocation,
                          writeXmlSchema,
                          minXmlSchema,
                          null,
                          null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to set the
        *          {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
        * @param   writeXmlSchema
        *          {@code True} to write XML schema information, else {@code false}.
        * @param   minXmlSchema
        *          {@code True} to write minimal XML schema information, else {@code false}. Must be
        *          {@code false} if {@code writeXmlSchema} is {@code false}.
        * @param   writeBeforeImage
        *          {@code True} to write before-image data and error information, else {@code false}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeXml(TargetData target,
                               logical formatted,
                               character encoding,
                               character schemaLocation,
                               logical writeXmlSchema,
                               logical minXmlSchema,
                               logical writeBeforeImage)
       {
          return writeXml(target,
                          formatted,
                          encoding,
                          schemaLocation,
                          writeXmlSchema,
                          minXmlSchema,
                          writeBeforeImage,
                          null);
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the XML to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   schemaLocation
        *          Name of an external XML schema file, or empty string or unknown value to set the
        *          {@code xsi:noNamespaceSchemaLocation} to empty string or unknown value.
        * @param   writeXmlSchema
        *          {@code True} to write XML schema information, else {@code false}.
        * @param   minXmlSchema
        *          {@code True} to write minimal XML schema information, else {@code false}. Must be
        *          {@code false} if {@code writeXmlSchema} is {@code false}.
        * @param   beforeImage
        *          {@code True} to write before-image data and error information, else {@code false}.
        * @param   noInitialVals
        *          {@code True} to omit data for fields whose values match their initial values.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeXml(TargetData target,
                               logical formatted,
                               character encoding,
                               character schemaLocation,
                               logical writeXmlSchema,
                               logical minXmlSchema,
                               logical beforeImage,
                               logical noInitialVals)
       {
          if (!_prepared())
          {
             ErrorManager.recordOrShowError(13160);
             // Dynamic temp-table must be in PREPARED state for WRITE-XML Methods.
             return logical.of(false);
          }

          Buffer buf = defaultBufferHandleNative();
          if (buf == null)
          {
             return logical.of(false);
          }

          // buffers will be flushed in [buf.writeJson()]
          return buf.writeXml(target,
                              formatted,
                              encoding,
                              schemaLocation,
                              writeXmlSchema,
                              minXmlSchema,
                              beforeImage,
                              noInitialVals);
       }

       /**
        * Writes an XML representation of the definition of this {@code TempTable}.
        *
        * @param   target
        *          Data target.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical writeXmlSchema(TargetData target)
       {
          return writeXmlSchema(target, null, null, null, null);
       }

       /**
        * Writes an XML representation of the definition of this {@code TempTable}.
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          Use {@code true} for a more human-readable document.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical writeXmlSchema(TargetData target, logical formatted)
       {
          return writeXmlSchema(target, formatted, null, null, null);
       }

       /**
        * Writes an XML representation of the definition of this {@code TempTable}.
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          Use {@code true} for a more human-readable document.
        * @param   encoding
        *          The character encoding. The default is UTF-8.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical writeXmlSchema(TargetData target, logical formatted, Text encoding)
       {
          return writeXmlSchema(target, formatted, encoding, null, null);
       }

       /**
        * Writes an XML representation of the definition of this {@code TempTable}.
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          Use {@code true} for a more human-readable document.
        * @param   encoding
        *          The character encoding. The default is UTF-8.
        * @param   minXmlSchema
        *          If {@code true}, the minimum amount of schema for the object will be written.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical writeXmlSchema(TargetData target,
                                     logical formatted,
                                     Text encoding,
                                     logical minXmlSchema)
       {
          return writeXmlSchema(target, formatted, encoding, minXmlSchema, null);
       }

       /**
        * Writes an XML representation of the definition of this {@code TempTable}.
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          Use {@code true} for a more human-readable document.
        * @param   encoding
        *          The character encoding. The default is UTF-8.
        * @param   minXmlSchema
        *          If {@code true}, the minimum amount of schema for the object will be written.
        * @param   omitInitVal
        *          Use {@code true} to omit the initial values.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical writeXmlSchema(TargetData target,
                                     logical formatted,
                                     Text encoding,
                                     logical minXmlSchema,
                                     logical omitInitVal)
       {
          if (!_prepared())
          {
             ErrorManager.recordOrShowError(13160);
             // Dynamic temp-table must be in PREPARED state for WRITE-XML Methods.
             return logical.of(false);
          }

          Buffer buf = defaultBufferHandleNative();
          if (buf == null)
          {
             return logical.of(false);
          }

          return buf.writeXmlSchema(target, formatted, encoding, minXmlSchema, omitInitVal);
       }

       /**
        * Reads XML Schema from an XML document and uses that schema to either create a schema for a
        * {@code TempTable} or {@code TempTable} or verify existing schema in a {@code TempTable},
        * {@code TempTable} or temp-table {@code Buffer} object.
        *
        * @param   source
        *          Data source.
        * @param   override
        *          If {@code true} the default mapping between XML Schema string and binary data types
        *          and ABL data types when creating an ABL temp-table schema from an XML Schema will
        *          be overwritten.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical readXmlSchema(SourceData source, logical override)
       {
          return readXmlSchema(source, override, new character(), new character());
       }

       /**
        * Reads XML Schema from an XML document and uses that schema to either create a schema for a
        * {@code TempTable} or {@code TempTable} or verify existing schema in a {@code TempTable},
        * {@code TempTable} or temp-table {@code Buffer} object.
        *
        * @param   source
        *          Data source.
        * @param   override
        *          If {@code true} the default mapping between XML Schema string and binary data types
        *          and ABL data types when creating an ABL temp-table schema from an XML Schema will
        *          be overwritten.
        * @param   mapping
        *          A comma-delimited list of field name, data type pairs. This allows to specify the
        *          data type for a specific field.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical readXmlSchema(SourceData source, logical override, Text mapping)
       {
          return readXmlSchema(source, override, mapping, new character());
       }

       /**
        * Reads XML Schema from an XML document and uses that schema to either create a schema for a
        * {@code TempTable} or {@code TempTable} or verify existing schema in a {@code TempTable},
        * {@code TempTable} or temp-table {@code Buffer} object.
        *
        * @param   source
        *          Data source.
        * @param   override
        *          If {@code true} the default mapping between XML Schema string and binary data types
        *          and ABL data types when creating an ABL temp-table schema from an XML Schema will
        *          be overwritten.
        * @param   strMapping
        *          A comma-delimited list of field name, data type pairs. This allows to specify the
        *          data type for a specific field.
        * @param   verifyMode
        *          Specifies how the XML will be verified against the schema. Valid values: "LOOSE" or
        *          "STRICT".
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical readXmlSchema(SourceData source,
                                    logical override,
                                    Text strMapping,
                                    Text verifyMode)
       {
          Map<String, String> mapping = null;
          if (strMapping != null)
          {
             String javaStrMapping = strMapping.toJavaType();
             if (javaStrMapping != null)
             {
                mapping = new HashMap<>();
                String[] splits = javaStrMapping.split(",");
                int len = splits.length;
                if (len == 0 || len % 2 == 1)
                {
                   ErrorManager.recordOrShowError(13200);
                   // Odd number of values in field-type-mapping argument.
                   return logical.of(false);
                }
                // no other validation at this time

                for (int k = 0; k < len; k += 2)
                {
                   mapping.putIfAbsent(splits[k], splits[k + 1]); // do not overwrite if field is duplicated
                }
             }
          }

          XmlImport xmlImport = new XmlImport(source, null, null,
                                              !override.isUnknown() && override.booleanValue(),
                                              mapping,
                                              verifyMode == null ? null : verifyMode.toJavaType());
          return logical.of(xmlImport.importTempTableSchema(this));
       }

       /**
        * Implementation of the READ-JSON method. Reads JSON data from the specified data source into
        * this object.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>{@code MERGE} read mode</li>
        * </ul>
        *
        * @param   source
        *          Data source.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical readJson(SourceData source)
       {
          return readJson(source, new character("MERGE"));
       }

       /**
        * Implementation of the READ-JSON method. Reads JSON data from the specified data source into
        * this object.
        *
        * @param   source
        *          Data source.
        * @param   readMode
        *          Mode in which data is read into buffer. Must evaluate to {@code APPEND}, {@code
        *          EMPTY}, {@code MERGE}, or {@code REPLACE}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical readJson(SourceData source, character readMode)
       {
          if (!source.configureSupportedSources(SourceData.SD_READ_JSON,
                   LegacyResource.TEMP_TABLE + " widget"))
          {
             return logical.of(false);
          }
          if (_dynamic() && !_prepared())
          {
             // does not have a table structure. We will query the source and build the object. Then the 'read'
             // will go on as usual.
             if (Boolean.FALSE.equals(
                      JsonImport.createTableStructure((TempTableBuilder) this, source)))
             {
                return logical.of(false);
             }
          }

          RecordBuffer buffer = name == null ? null :
                            ((BufferImpl) defaultBufferHandleNative()).buffer();

          // sanity
          if (buffer != null && !buffer.isTemporary())
          {
             ErrorManager.recordOrShowError(15378);
             // JSON methods only allowed on TEMP-TABLE buffers.
             return logical.of(false);
          }

          // normalize parameters
          JsonImport jsonImport = new JsonImport(source, readMode.toJavaType());
          boolean success;
          try
          {
             // perform import
             success = buffer == null ?
                      jsonImport.readTable((TempTableBuilder)this, RecordBuffer::loadRecord) :
                      jsonImport.readTable((TemporaryBuffer) buffer, RecordBuffer::loadRecord);
          }
          catch (PersistenceException exc)
          {
             // TODO: log
             success = false;
          }

          return logical.of(success);
       }

       /**
        * Implementation of the WRITE-JSON method. Writes JSON data from this object into the
        * specified data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>Unformatted text</li>
        * <li>UTF-8 encoding</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * <li>Do not omit data for the outermost object, unless {@code target} represents a {@code
        *     JsonArray} object.
        * <li>No before-image data written</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target)
       {
          return writeJson(target,
                           logical.of(false),
                           new character(),
                           logical.of(false),
                           logical.of(false),
                           logical.of(false));
       }

       /**
        * Implementation of the WRITE-JSON method. Writes JSON data from this object into the
        * specified data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>UTF-8 encoding</li>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * <li>Do not omit data for the outermost object, unless {@code target} represents a {@code
        *     JsonArray} object.
        * <li>No before-image data written</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the JSON to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target, logical formatted)
       {
          return writeJson(target,
                           formatted,
                           new character(),
                           logical.of(false),
                           logical.of(false),
                           logical.of(false));
       }

       /**
        * Implementation of the WRITE-JSON method. Writes JSON data from this object into the
        * specified data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>Do not omit temp-table fields which contain their initial values</li>
        * <li>Do not omit data for the outermost object, unless {@code target} represents a {@code
        *     JsonArray} object.
        * <li>No before-image data written</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the JSON to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target, logical formatted, character encoding)
       {
          return writeJson(target,
                           formatted,
                           encoding,
                           logical.of(false),
                           logical.of(false),
                           logical.of(false));
       }

       /**
        * Implementation of the WRITE-JSON method. Writes JSON data from this object into the
        * specified data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>Do not omit data for the outermost object, unless {@code target} represents a {@code
        *     JsonArray} object.
        * <li>No before-image data written</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the JSON to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   omitInitialValues
        *          {@code True} to omit data for fields whose values match their initial values.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target,
                                logical formatted,
                                character encoding,
                                logical omitInitialValues)
       {
          return writeJson(target,
                           formatted,
                           encoding,
                           omitInitialValues,
                           logical.of(false),
                           logical.of(false));
       }

       /**
        * Implementation of the WRITE-JSON method. Writes JSON data from this object into the
        * specified data target.
        * <p>
        * Defaults applied:
        * <ul>
        * <li>No before-image data written</li>
        * </ul>
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the JSON to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   omitInitialValues
        *          {@code True} to omit data for fields whose values match their initial values.
        * @param   omitOuterObject
        *          {@code True} to omit data for the outermost object from the output.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target,
                                logical formatted,
                                character encoding,
                                logical omitInitialValues,
                                logical omitOuterObject)
       {
          return writeJson(target,
                           formatted,
                           encoding,
                           omitInitialValues,
                           omitOuterObject,
                           logical.of(false));
       }

       /**
        * Implementation of the WRITE-JSON method. Writes JSON data from this object into the
        * specified data target.
        *
        * @param   target
        *          Data target.
        * @param   formatted
        *          {@code True} to format the JSON to make it more readable; {@code false} to write it
        *          as unformatted text. Unknown value is treated as {@code false}.
        * @param   encoding
        *          Name of the character encoding to use.
        * @param   omitInitialValues
        *          {@code True} to omit data for fields whose values match their initial values.
        * @param   omitOuterObject
        *          {@code True} to omit data for the outermost object from the output.
        * @param   writeBeforeImage
        *          {@code True} to write before-image data and error information, else {@code false}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target,
                                logical formatted,
                                character encoding,
                                logical omitInitialValues,
                                logical omitOuterObject,
                                logical writeBeforeImage)
       {
          if (defaultBufferHandle() == null)
          {
             // throw something?
             return logical.of(false);
          }

          Buffer buf = defaultBufferHandleNative();
          if (buf == null)
          {
             return logical.of(false);
          }

          // buffers will be flushed in [buf.writeJson()]
          return buf.writeJson(target, formatted, encoding, omitInitialValues, omitOuterObject, writeBeforeImage);
       }

       /**
        * Obtain the name of the XML element or attribute representing the target object (a {@code TEMP-TABLE})
        * name in an XML document. If not set the {@code name} is returned.
        *
        * @return  the name of the XML element for this object.
        */
       @Override
       public character getXmlNodeName()
       {
          return defaultBuffer.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)
       {
          defaultBuffer.setXmlNodeName(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)
       {
          setXmlNodeName(name.toJavaType());
       }

       /**
        * 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()
       {
          handle.invalidAttribute("XML-DATA-TYPE", false, LegacyResource.TEMP_TABLE);
          return new character();
       }

       /**
        * 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)
       {
          handle.invalidAttribute("XML-DATA-TYPE", true, LegacyResource.TEMP_TABLE);
       }

       /**
        * 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)
       {
          handle.invalidAttribute("XML-DATA-TYPE", true, LegacyResource.TEMP_TABLE);
       }

       /**
        * 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()
       {
          handle.invalidAttribute("XML-NODE-TYPE", false, LegacyResource.TEMP_TABLE);
          return new character();
       }

       /**
        * Allows specifying how 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)
       {
          handle.invalidAttribute("XML-NODE-TYPE", true, LegacyResource.TEMP_TABLE);
       }

       /**
        * Allows specifying how 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)
       {
          handle.invalidAttribute("XML-NODE-TYPE", true, LegacyResource.TEMP_TABLE);
       }

       /**
        * Getter for {@code NUM-REFERENCES} attribute: obtains number of references to this {@code TempTable}
        * object that is defined as a parameter to which reference-only objects are bound.
        * <p>
        * @return  number of references to this object.
        */
       @Override
       public integer numReferences()
       {
          UnimplementedFeature.missing("TEMP-TABLE:NUM-REFERENCES");
          return new integer();
       }

       /**
        * Returns the value of the NAMESPACE-URI attribute.
        *
        * @return  the value of the NAMESPACE-URI attribute.
        */
       @Override
       public character namespaceURI()
       {
          return defaultBuffer.namespaceURI();
       }

       /**
        * Setter for NAMESPACE-URI attribute.
        *
        * @param   uri
        *          The new value of the NAMESPACE-URI attribute.
        */
       @Override
       public void namespaceURI(String uri)
       {
          if (uri == null)
          {
             ErrorManager.recordOrShowError(4083, " NAMESPACE-URI", LegacyResource.TEMP_TABLE + " widget");
             return;
          }

          defaultBuffer.namespaceURI(uri);
       }

       /**
        * Setter for NAMESPACE-URI attribute.
        *
        * @param   uri
        *          The new value of the NAMESPACE-URI attribute.
        */
       @Override
       public void namespaceURI(character uri)
       {
          namespaceURI(uri.toJavaType());
       }

       /**
        * Returns the value of the NAMESPACE-PREFIX attribute. Only for ELEMENT or ATTRIBUTE, for other
        * attributes return unknown value.
        *
        * @return  the current value of the the NAMESPACE-PREFIX attribute.
        */
       public character namespacePrefix()
       {
          return defaultBuffer.namespacePrefix();
       }

       /**
        * Setter for NAMESPACE-PREFIX attribute.
        *
        * @param   prefix
        *          The new value of the NAMESPACE-PREFIX attribute.
        */
       @Override
       public void namespacePrefix(String prefix)
       {
          if (prefix == null)
          {
             ErrorManager.recordOrShowError(4083, "NAMESPACE-PREFIX", LegacyResource.TEMP_TABLE + " widget");
             return;
          }

          defaultBuffer.namespacePrefix(prefix);
       }

       /**
        * Setter for NAMESPACE-PREFIX attribute.
        *
        * @param   prefix
        *          The new value of the NAMESPACE-PREFIX attribute.
        */
       @Override
       public void namespacePrefix(character prefix)
       {
          namespacePrefix(prefix.toJavaType());
       }

       /**
        * Checks whether the data in the data source has been modified.
        *
        * @return  {@code true} when the data has been modified.
        */
       @Override
       public logical isDataSourceModified()
       {
          return logical.of(dataSourceModified);
       }

       /**
        * Manually mark the data in the target structure as modified.
        *
        * @param   mod
        *          {@code true} when the data is marked as modified and {@code false} otherwise.
        */
       @Override
       public void setDataSourceModified(logical mod)
       {
          if (mod == null || mod.isUnknown())
          {
             unableToAssignUnknown("DATA-SOURCE-MODIFIED", "DATASET widget");
             return;
          }

          setDataSourceModified(mod.getValue());
       }

       /**
        * Manually mark the data in the target structure as modified.
        *
        * @param   mod
        *          {@code true} when the data is marked as modified and {@code false} otherwise.
        */
       @Override
       public void setDataSourceModified(boolean mod)
       {
          this.dataSourceModified = mod;
       }

       /**
        * 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()
       {
          return defaultBuffer.getSerializeName();
       }

       /**
        * 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)
       {
          defaultBuffer.setSerializeName(sName);
       }

       /**
        * Implementation of the read access of the {@code LABEL}.
        *
        * @return  The current value of the {@code LABEL}.
        */
       public character label()
       {
          ErrorManager.recordOrShowError(4052, "LABEL", "queryable", LegacyResource.TEMP_TABLE + " widget");
          return character.UNKNOWN;
       }

       /**
        * Implementation of the write access of the {@code LABEL}.
        *
        * @param   sName
        *          the new value for the {@code LABEL}.
        */
       public void label(Text sName)
       {
          ErrorManager.recordOrShowError(4052, "LABEL", "setable" /*sic*/, LegacyResource.TEMP_TABLE + " widget");
       }

       /**
        * Implementation of the write access of the {@code LABEL}.
        *
        * @param  sName
        *         The new value for the {@code LABEL}.
        */
       public void label(String sName)
       {
          ErrorManager.recordOrShowError(4052, "LABEL", "setable" /*sic*/, LegacyResource.TEMP_TABLE + " widget");
       }

       /**
        * Constructs and returns a string containing the P4GL schema definition of the table. The result is not
        * cached so this method is to be used only for debug.
        *
        * @return  a string containing the P4GL legacy schema definition of the table.
        */
       public String tableDefinition()
       {
          WrappedResource r = this.defaultBuffer;
          if (r != null && r.valid())
          {
             return ((BufferImpl) r).tableDefinition();
          }
          return "(unknown)";
       }

       /**
        * Validate all buffers from this table, in the reverse order they were created. It is guaranteed that the
        * default buffer is the last one processed.
        *
        * @return  {@code true} on success.
        *
        * @throws  ErrorConditionException
        *          If any of the buffer fails validation.
        */
       public boolean validateAllBuffers()
       {
          // We use a LinkedHashSet for fast remove performance, but this set stores its elements in the
          // opposite order we need to iterate here. So, we create an intermediate list and iterate it backwards.
          // This operation will be slower than a simple iteration because of having to add all the elements to
          // the intermediate list, but this method should be invoked much less frequently than removeBuffer,
          // so the tradeoff is deemed acceptable.
          List<Buffer> list = new ArrayList<>(allBuffers);
          ListIterator<Buffer> iter = list.listIterator(list.size());
          while (iter.hasPrevious())
          {
             Buffer buffer = iter.previous();
             buffer.validate();
          }

          // if we reach this point, the content of all buffers passed the validation.
          return true;
       }

       /**
        * Get the collection of table fields.
        *
        * @return  the collection of table fields.
        */
       public Collection<P2JField> getFields()
       {
          return Collections.emptyList();
       }

       /**
        * Register a new buffer with this table.
        *
        * @param   buf
        *          The buffer to be registered. Must not be {@code null}.
        */
       void registerBuffer(Buffer buf)
       {
          allBuffers.add(buf);
       }

       /**
        * Deregister a buffer which has gone out of scope.
        *
        * @param   buf
        *          The buffer to be deregistered. Must not be {@code null}.
        */
       void deregisterBuffer(Buffer buf)
       {
          allBuffers.remove(buf);
       }

       /**
        * Get default buffer for the static temp-table.
        *
        * @return default buffer for the static temp-table.
        */
       TemporaryBuffer defaultBuffer()
       {
          if (defaultBuffer == null)
          {
             return null;
          }

          return (TemporaryBuffer) RecordBuffer.get((DataModelObject) defaultBuffer);
       }

       /**
        * Checks whether changes in this temp-table is enabled for a {@code DataSet}.
        *
        * @return  The current value for {@code TRACKING-CHANGES} as described above.
        */
       boolean _isTrackingChanges()
       {
          return trackingChanges;
       }

       /**
        * Determine if an ADD-* function was called for the table (CLEAR function resets the state).
        * Makes sense only for unprepared tables.
        *
        * @return <code>true</code> if an ADD-* function was called for the table.
        */
       protected abstract boolean addFunctionWasCalled();

       /**
        * Read XML/JSON data from the input source and store it in the just created empty TEMP-TABLE.
        *
        * @param   tableName
        *          table name
        * @param   reader
        *          XML/JSON source reader and table creator.
        *
        * @return  {@code true} on success.
        *
        * @throws  PersistenceException
        *          if there is any error reading or storing XML/JSON data.
        */
       public abstract boolean readTable(String tableName, TableReader reader)
       throws PersistenceException;

       /**
        * Delete all the buffers (master and slave) associated with this temporary table (static or
        * dynamic).
        */
       protected void deleteAllBuffers()
       {
          TemporaryBuffer defaultBuffer = defaultBuffer();
          if (defaultBuffer == null)
          {
             return;
          }

          Consumer<TemporaryBuffer> cleanup = (buff) ->
          {
             Buffer buffer = (Buffer) buff.getDMOProxy();

             if (buffer.valid())
             {
                pm.deleteResource(buffer);
             }

             // clean up the domains for this buffer
             buff.cleanupSlaveBuffers();
          };

          Set<TemporaryBuffer> allBuffers = defaultBuffer.getAllSlaveBuffers();
          if (allBuffers != null)
          {
             allBuffers.add(defaultBuffer);

             for (TemporaryBuffer buff : allBuffers)
             {
                cleanup.accept(buff);
             }
          }
          else
          {
             cleanup.accept(defaultBuffer);
          }
       }

       /**
        * Worker for {@code hasRecords}. Checks whether this temp-table contains any records.
        *
        * @return  true if this temp-table has records.
        */
       protected boolean _hasRecords()
       {
          TemporaryBuffer defBuffer = defaultBuffer();
          Boolean hasRecords = defBuffer.fastHasRecords();
          // if we detected that there is no record the fast-way, fall-back
          // to the bufferManager usage
          if (hasRecords != null && hasRecords)
          {
             return hasRecords;
          }

          Persistence persistence = defBuffer.getPersistence();
          // couldn't detect if there is any record in the fast-way
          if (hasRecords == null)
          {
             StringBuilder sb = new StringBuilder("select 1 from ");
             sb.append(defBuffer.getTable());
             sb.append(" where ");
             sb.append(TemporaryBuffer.MULTIPLEX_FIELD_NAME);
             sb.append(" = ?");
             Object[] args = new Object[] {defBuffer.getMultiplexID()};
             try
             {
                Object res = persistence.getSingleSQLResult(sb.toString(), Persistence.TEMP_CTX, args);
                if (res != null)
                {
                   return true;
                }
             }
             catch (PersistenceException e)
             {
                ErrorManager.recordOrThrowError(e);

                return false;
             }
          }

          // Iterate the buffers of the current table and check for any active buffer.
          Iterator<Buffer> iter = allBuffers.iterator();
          while (iter.hasNext())
          {
             RecordBuffer next = ((BufferReference) iter.next()).buffer();
             if (defBuffer.bufferManager.isActiveBuffer(next, persistence, true) &&
                 next.getParentTable().equals(this))
             {
                return true;
             }
          }

          return false;
       }

       /**
        * Display "CREATE-LIKE must be the first method on a newly CREATEd TEMP-TABLE, or a newly
        * CLEARed one" error message.
        */
       protected void displayCreateLikeNotFirst()
       {
          ErrorManager.recordOrShowError(9041,
                                         "CREATE-LIKE must be the first method on a newly CREATEd TEMP-TABLE," +
                                         " or a newly CLEARed one",
                                         true,
                                         false);
       }

       /**
        * Display "Definitional TEMP-TABLE methods (ADD-NEW/LIKE) may not be called after
        * TEMP-TABLE-PREPARE has been called unless a CLEAR is done first, and may never be called on
        * a DEFINED static table" error message.
        */
       protected void displayPrepared()
       {
          ErrorManager.recordOrShowError(9042,
                                         "Definitional TEMP-TABLE methods (ADD-NEW/LIKE) may not be called after " +
                                         "TEMP-TABLE-PREPARE has been called unless a CLEAR is done first, and may never " +
                                         "be called on a DEFINED static table",
                                         true,
                                         false);
       }

       /**
        * Display "TEMP-TABLE is already prepared--ignoring" error message.
        */
       protected void displayPreparedIgnoring()
       {
          ErrorManager.recordOrShowError(9043, "TEMP-TABLE is already prepared--ignoring", true, false);
       }

       /**
        * Display "Unable to assign UNKNOWN value to attribute UNDO on TEMP-TABLE widget" error
        * message.
        */
       protected void displayUnableAssignUnknownUndo()
       {
          ErrorManager.recordOrShowError(4083,
                                         "Unable to assign UNKNOWN value to attribute UNDO on TEMP-TABLE widget",
                                         true,
                                         true);
       }

       /**
        * Display "CLEAR method may not be called on a static temp-table object, nor on a dynamic one
        * with records; use buffer:EMPTY-TEMP-TABLE" error message.
        */
       protected void displayCannotClear()
       {
          ErrorManager.recordOrShowError(9085,
                                         "CLEAR method may not be called on a static temp-table object, " +
                                               "nor on a dynamic one with records; use buffer:EMPTY-TEMP-TABLE",
                                         true,
                                         false);
       }

       /**
        * Display "TEMP-TABLE must be PREPARED before referencing NAME/DEFAULT-BUFFER-HANDLE etc"
        * error message.
        */
       protected void displayNotPrepared()
       {
          ErrorManager.recordOrShowError(9040,
                                         "TEMP-TABLE must be PREPARED before referencing NAME/DEFAULT-BUFFER-HANDLE etc",
                                         true,
                                         false);

       }

       /**
        * Display "Attempt to reference uninitialized temp-table" error message.
        */
       protected void displayUninitialized()
       {
          ErrorManager.recordOrShowError(12378,
                                         "Attempt to reference uninitialized temp-table",
                                         true,
                                         false);
       }

       /**
        * Display "Unable to CREATE-LIKE for TEMP-TABLE--try using a smaller source table" error
        * message.
        */
       protected void displayUnableCreateLike()
       {
          ErrorManager.recordOrShowError(9047,
                                         "Unable to CREATE-LIKE for TEMP-TABLE--try using a smaller source table",
                                         true,
                                         false,
                                         false, // ERROR-STATUS:ERROR is not set
                                         false);
       }

       /**
        * Display "Unable to ADD-FIELDS-FROM for TEMP-TABLE" error message.
        */
       protected void displayUnableAddFieldsFrom()
       {
          ErrorManager.recordOrShowError(9049, "Unable to ADD-FIELDS-FROM for TEMP-TABLE", true, false);
       }

       /**
        * Display "TEMP-TABLE method could not find LIKE table from BUFFER handle" error message.
        */
       protected void displayCouldNotFindLikeTableFromHandle()
       {
          ErrorManager.recordOrShowError(9058,
                                         "TEMP-TABLE method could not find LIKE table from BUFFER handle",
                                         true,
                                         false);
       }

       /**
        * Display "COPY-TEMP-TABLE must have valid buffer/temp-table handle as first argument" error
        * message.
        */
       protected void displayCopyTempTableInvalidFirstArg()
       {
          ErrorManager.recordOrShowError(12339,
                                         "COPY-TEMP-TABLE must have valid buffer/temp-table handle as first argument",
                                         true,
                                         false);
       }

       /**
        * Display "COPY-TEMP-TABLE target temp-table must be either PREPARED or CLEAR, but not
        * half-defined" error message.
        */
       protected void displayCopyTempTableHalfDefined()
       {
          ErrorManager.recordOrShowError(12341,
                                         "COPY-TEMP-TABLE target temp-table must be either PREPARED or CLEAR," +
                                         " but not half-defined",
                                         true,
                                         false);
       }

       /**
        * Get record buffer by buffer or temp-table handle. Displays error if the handle is not valid
        * or of a wrong type.
        *
        * @param  hbuf
        *         Buffer or temp-table handle.
        * @param  callingFunction
        *         Calling function. Affects error messaging.
        *
        * @return target record buffer or <code>null</code> if the case of error.
        */
       protected RecordBuffer getRecordBufferByHandle(handle hbuf, CallingFunction callingFunction)
       {
          if (!hbuf._isValid())
          {
             switch (callingFunction)
             {
                case COPY_TEMP_TABLE:
                   displayCopyTempTableInvalidFirstArg();
                   break;
                case ADD_FIELDS_FROM:
                   displayUnableAddFieldsFrom();
                   break;
                case CREATE_LIKE:
                   displayUnableCreateLike();
                   break;
             }

             return null;
          }

          Object handleValue = hbuf.getResource();
          Class handleClass = handleValue.getClass();
          Buffer buffer;

          if (Buffer.class.isAssignableFrom(handleClass))
          {
             buffer = (Buffer) handleValue;
          }
          else if (TempTable.class.isAssignableFrom(handleClass))
          {
             TempTable tempTable = (TempTable) handleValue;
             handle defaultBufferHandle = tempTable.prepared().booleanValue() ?
                   tempTable.defaultBufferHandle() :
                   null;
             if (defaultBufferHandle == null)
             {
                switch (callingFunction)
                {
                   case COPY_TEMP_TABLE:
                      displayCopyTempTableInvalidFirstArg();
                      break;
                   case ADD_FIELDS_FROM:
                      displayCouldNotFindLikeTableFromHandle();
                      displayUnableAddFieldsFrom();
                      break;
                   case CREATE_LIKE:
                      displayCouldNotFindLikeTableFromHandle();
                      displayUnableCreateLike();
                      break;
                }
                return null;
             }

             buffer = (Buffer) defaultBufferHandle.getResource();
          }
          else
          {
             switch (callingFunction)
             {
                case COPY_TEMP_TABLE:
                   displayCopyTempTableInvalidFirstArg();
                   break;
                case ADD_FIELDS_FROM:
                   displayCouldNotFindLikeTableFromHandle();
                   displayUnableAddFieldsFrom();
                   break;
                case CREATE_LIKE:
                   displayCouldNotFindLikeTableFromHandle();
                   displayUnableCreateLike();
                   break;
             }
             return null;
          }

          return ((BufferReference) buffer).buffer();
       }

       /**
        * Returns {@code true} if this temp-table is in prepared state (after a successful call to
        * {@link #tempTablePrepare}).
        * This method implements the read-only PREPARED getter attribute from 4GL.
        *
        * @return  {@code true} if table is in PREPARED state.
        */
       public abstract boolean _prepared();

       /**
        * Check whether this TEMP-TABLE is in CLEAR state: I.e. the temp-table is first created or immediately
        * after the CLEAR() method is applied. The other two state of a temp-table are: UNPREPARED and PREPARED.
        * The UNPREPARED state between the first definitional method has been applied and before the
        * TEMP-TABLE-PREPARE() method is applied.
        * <p>
        * This method is FWD-internal. The programmer can check whether the temp-table is in an UNPREPARED or
        * PREPARED state by checking the PREPARED attribute, but the CLEAR state is transparent.
        *
        * @return  {@code true} if this table is in CLEAR state.
        */
       public abstract boolean _clear();

       /**
        * Sets the origin table. Implementation internal method. The origin table is the table where
        * the content of this table came from (source of the {@code GET-CHANGES method}).
        *
        * @param   origin
        *          The origin table.
        */
       void setOriginTable(TempTable origin)
       {
          this.originTable = origin;
       }

       /**
        * Make sure content from all buffers of this temp-table are flushed. If the flush operation fails (usually
        * because of failing validation) the message is displayed for first buffer, as usual and {@code false}
        * is returned.
        * <p>
        * All buffers from both this table and its peer table will be flushed (in other words, the after table
        * and the before table, if it exists, will flush all buffers).
        *
        * @return  {@code true} on success.
        */
       boolean flushBuffers()
       {
          Function<Set<Buffer>, Boolean> flush = (allBuffers) ->
          {
             for (Buffer aBuff : allBuffers)
             {
                try
                {
                   ((BufferImpl) aBuff).buffer().flush();
                }
                catch (ValidationException e)
                {
                   ErrorManager.recordOrShowError(e.getNumber(), e.getMessage(), true, true);
                   return false;
                }
             }
             return true;
          };

          if (!flush.apply(allBuffers))
          {
             return false;
          }

          if (peerTable != null && !flush.apply(((AbstractTempTable) peerTable).allBuffers))
          {
             return false;
          }

          return true;
       }

       /**
        * Computes the temp-table type and if it is an {@code AFTER_TABLE} or a {@code BEFORE_TABLE}
        * the peer (complementary) buffer is also stored in {@code peerTable}.
        *
        * @return  The computed type (which is the value of the {@code beforeTableType}).
        */
       private BeforeType computeTableType()
       {
          if (beforeTableType != BeforeType.UNKNOWN)
          {
             return beforeTableType; // this line should not execute
          }

          if (!this._dynamic())
          {
             RecordBuffer defBuffer = defaultBuffer();
             Class<?> defType = defBuffer.getDefiningType();
             DmoMeta dmoInfo = defBuffer.getDmoInfo();
             String beforeTable = dmoInfo.beforeTable;
             if (!beforeTable.isEmpty())
             {
                peerTable = defBuffer.bufferManager.getStaticTempTable(defType, beforeTable);
                beforeTableType = BeforeType.AFTER;
                return beforeTableType;
             }
             else
             {
                String afterTable = dmoInfo.afterTable;

                if (!afterTable.isEmpty())
                {
                   peerTable = defBuffer.bufferManager.getStaticTempTable(defType, afterTable);
                   beforeTableType = BeforeType.BEFORE;
                   return beforeTableType;
                }
             }
             beforeTableType = BeforeType.SIMPLE;
          }
          else
          {
             // this should also not happen, because the type is set by TempTableBuilder after the
             // table was prepared
          }

          return beforeTableType;
       }

       /**
        * Implementation of bulk delete all records associated with the {@code TempTable}.
        *
        * @throws  ErrorConditionException
        *          If an error occurs performing the bulk delete operation.
        */
       void deleteAllImpl()
       {
          TemporaryBuffer defBuffer = defaultBuffer();
          if (defBuffer == null)
          {
             return; // nothing to delete, this dynamic TEMP-TABLE was not prepared yet.
          }

          defBuffer.deleteAll();
       }

       /**
        * Resolve the mutable information for the given field.  If this is for a getter call, return
        * <code>null</code> if the info has not yet been added to {@link #fieldInfo}.
        *
        *
        * @param    fieldLegacyName
        *           The legacy field name, already lowercased.
        * @param    setter
        *           Flag indicating if this call is for a setter or a getter call.
        * @param    field
        *           The function to calculate the mutable field info.
        *
        * @return   The mutable field info from {@link #fieldInfo} or calculated, or <code>null</code> in case of
        *           a getter call and this info has not yet been calculated.
        */
       MutableFieldInfo getMutableInfo(String                                          fieldLegacyName,
                                       boolean                                         setter,
                                       BiFunction<TempTable, String, MutableFieldInfo> field)
       {
          if (!setter)
          {
             TableMapper.MutableFieldInfo info = (fieldInfo == null ? null : fieldInfo.get(fieldLegacyName));
             return info;
          }

          if (fieldInfo == null)
          {
             fieldInfo = new HashMap<>();
          }

          TableMapper.MutableFieldInfo info = fieldInfo.get(fieldLegacyName);
          if (info == null)
          {
             fieldInfo.put(fieldLegacyName, info = field.apply(this, fieldLegacyName));
          }
          return info;
       }
    }