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