# DataSet.java

    /*
    ** Module   : DataSet.java
    ** Abstract : Abstracts the DATASET ABL structure.
    **
    ** Copyright (c) 2019-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 OM  20190403 Created initial version with stubbed methods.
    ** 002 OM  20190508 Ported createDynamicDataSet().
    **     CA  20190513 DATASET can use the '::' operator.  Added 'asHandle'.
    ** 003 OM  20190508 Added runtime implementation for ABL methods and attributes.
    **     HC  20190707 Implemented read(dataSet) and read(tempTable) methods of JsonObject legacy
    **                  class.
    ** 004 CA  20190712 Added missing associate API.
    **     OM  20190716 Fixed handling of OUTPUT dataset parameters.
    **     CA  20190720 Some fixes for BY-REFERENCE and OUTPUT parameters.  These fixes are not proved
    **                  and BY-REFERENCE, OUTPUT and DELETE combinations are most likely related to
    **                  NUM-REFERENCES attribute.
    **     OM  20190726 Callback DataSet handles are sent BY_REFERENCE.
    **     CA  20190728 If a callback raises an ERROR condition, invokeCallback must return false.
    **                  Also, any FILL operation must be aborted.
    **                  For BY-REFERENCE/REFERENCE-ONLY transfer the buffer's record, too.
    **     EVL 20190729 Fix from CA to skip same reference processing in associate() method.
    **     OM  20190731 Added REFERENCE-ONLY and NUM-REFERENCES support.
    **     CA  20190812 Allow this class to be mutable, to allow switching between resources, in cases
    **                  of BY-REFERENCE or REFERENCE-ONLY.  WARNING: it is not allowed to access
    **                  instance fields, if they are in another instance than 'this' reference!
    **                  Use the 'ref()' API to get the runtime instance or add the field to an
    **                  accessor method.
    **     CA  20190826 Added basic support for 'inverse binding', from callee to caller, via the BIND
    **                  parameter option.
    **     OM  20190909 Clear the ERROR flag in some methods.
    ** 005 CA  20191119 Track the executing legacy class, to be able to register any buffer to its
    **                  source definition class.
    **     CA  20200110 More fixes for DATASET parameters and resource delete.
    ** 006 CA  20200427 Allow a DATASET to be read from JSON argument, in case of a remote REST call.
    ** 007 OM  20201030 Invalid attribute API support for getters/setters.
    **     OM  20201118 Extracted SERIALIZE-HIDDEN into independent interface.
    **     OM  20201120 Fixed of methods and attributes implementations.
    **     OM  20201218 Fixed implementation for error and rejected attributes/hidden fields.
    **     OM  20210115 OE quirk/bug: allowed duplicate relations for same buffer pair.
    **     OM  20210219 bufferHandle() accepts int64 expression parameters.
    **     OM  20210309 Do not use DmoMeta as key in TableMapper because temp-tables may share the same
    **                  DmoMeta instance.
    **     ECF 20210511 Replaced DynamicTablesHelper.normalizeName with TextOps.rightTrimLower.
    **     CA  20210609 Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly
    **                  initialized at the method's execution, and not at the caller's arguments.
    **     SVL 20210614 Remote OUTPUT TABLE/DATASET-HANDLE parameters are treated like INPUT-OUTPUT parameters
    **                  with no input data rows, which allows to access table structure if non-unknown
    **                  table/dataset handle has been passed from the calling side.
    **     OM  20210809 Completed implementation of SERIALIZE-ROW|WRITE-JSON}(JsonObject|JsonArray).
    **     CA  20210831 For GET-CHANGES, only the tables which have a before-table are included - the changes
    **                  are available even if the TRACKING-CHANGES was disabled before this call.
    **     CA  20210928 Implicit deletion of buffers (when the dataset gets deleted) is done only for dynamic
    **                  buffers associated with dynamic temp-tables.
    **     CA  20211005 DATASET can't be added to unnamed pools.
    **     OM  20211006 Unbinding/output copy occur on procedure return instead of TM's scope finished.
    **     CA  20211112 Allow the DATASET and TABLE parameters for remote SOAP calls to be transferred via XML
    **                  (so that relations, schema, etc is done on the server-side).
    **     CA  20211227 Fixed the DATASET:NAME when neither name or serialize-name are set - defaults to ProDataSet.
    **     CA  20220105 readFromXml must force UTF-8 as codepage at the longchar created to hold the XML string.
    **     CA  20220106 Fixed CREATE-LIKE when relations are involved, their active flag was not being copied
    **                  properly.
    **     OM  20220422 Allow Xml/JsonImport to load records into buffer without exposing package private methods.
    **     OM  20221003 Added missing STREAM targets for JSON/XML serializations.
    **     CA  20220930 Refactored the callback invocation to be performed via a call-site and InvokeConfig, to
    **                  allow caching of the resolved target.
    **     CA  20220901 Refactored scope notification support: ScopeableFactory was removed, and the registration
    **                  is now specific to each type of scopeable.  For each case, the block will be registered
    **                  for scope support (for that particular scopeable) only when the scopeable is 'active'
    **                  (i.e. unnamed streams or accumulators are used).  This allows a lazy registration of
    **                  scopeables, to avoid the unnecessary overhead of processing all the scopeables for each
    **                  and every block.
    **     CA  20220923 Internal var definitions must be done via TypeFactory and not UndoableFactory.
    **     CA  20221006 UNIQUE-ID is kept as Java type instead of BDT.  Refs #6827
    **     CA  20221031 Added JMX instrumentation for DATASET[-HANDLE] parameter processing.
    **     OM  20221206 Reworked REFERENCE-ONLY attribute.
    **     CA  20220524 The scope ends when the procedure returns, not on 'scopeFinished', to allow parameter
    **                  processing.
    **     GES 20210501 Upgraded dataset parameter processing to match API changes. Reworked associate() and
    **                  createDynamicDataSet() to remove unnecessary arguments.
    **     CA  20220601 Added 'derefPoly', emitted when '::' is used as r-value in a dynamic extent assignment.
    **     OM  20220604 Improved dereferenciation support.
    **     CA  20220606 Fixed COPY-DATASET, automatically create a before-table in the destination if the
    **                  destination temp-table is dynamic.  Raise error 12753 only if the source before-table is
    **                  not empty and the destination table is static without a before-table.
    **     CA  20220608 Fixed CREATE-LIKE, copy the temp-table options (like XML-NODE-NAME), too.
    **     CA  20220618 Automatically delete datasets created for remote parameters.
    **     CA  20220704 A bound REFERENCE-ONLY dataset must act as a normal dataset if used as an OUTPUT parameter.
    **                  No instance field can be directly accessed from static methods, as the dataset instances
    **                  are proxied (added 'addOutputParam' method to fix 'outputParams' field access).
    **     OM  20220609 Fixed implementation of several 4GL attributes and methods.
    **     CA  20220727 Fixed OO dataset/table parameters (at the method definition and method call) when there is
    **                  an APPEND option.
    **     CA  20220918 Automatically delete top-nav queries created dynamically by the DATASET.
    **     CA  20221031 Added JMX instrumentation for DATASET[-HANDLE] parameter processing.
    **     IAS 20221108 Fixed support for SCHEMA-MARSHAL == NONE.
    **     IAS 20221123 Added 'resetNames()' method.
    **     CA  20221123 Static DATASET instances must not be added to SESSION:FIRST-DATASET chain.
    **     OM  20221129 Fine-tuning _areRelationsActive() based on recent findings.
    **     SVL 20230108 Improved performance by replacing some "for-each" loops with indexed "for" loops.
    **     CA  20230110 Cache the mutable info at the temp-table and not in a separate map at TableMapper.  The
    **                  mutable info for a field is calculated only if this info has changed, when creating a
    **                  'like' dataset.
    **     CA  20230110 Do not create 'callbacks', 'topNavQueries', 'deleteQueries' or 'refs' until they are
    **                  actually added something.
    **     CA  20230110 UNIQUE-ID is generated only when is read by the application.
    **                  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.
    **     HC  20230118 Eliminated some of the uses of String.toUpperCase and/or String.toLowerCase
    **                  for performance.
    **     CA  20230208 Backed out a change in 'OM/20221206 Reworked REFERENCE-ONLY attribute.', as it was causing
    **                  regressions - the 'thisIsParam' flag was changed to 'false' in 'associateImpl'.
    ** 008 CA  20230303 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.
    ** 009 IAS 20230405 Fixed FILL support for recursive DATA-RELATION
    **                  Fixed support for recursive DATA-SET.
    **                  Added 'getRelations()' method.
    ** 010 OM  20230315 The Dataset keeps the original proxy buffers and use these when copying data to output
    **                  parameters.
    ** 011 IAS 20230407 Fixed 'getChanges'.
    ** 012 VVT 20230405 Method _getNumTopBuffers made public and minor non-functional changes.
    ** 013 IAS 20230414 Fixed 'addParentIdRelation' method.
    **     IAS 20230501 Added 'emergency exit' for WRITE-XML
    ** 014 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 015 IAS 20230519 Added method for getting NS URI as String.
    ** 016 AL2 20230613 Use procedure helper where possible to avoid context look-ups.
    ** 017 CA  20230724 Further reduce context-local usage.
    ** 018 IT  20230809 temp-table member of a dataset cannot be delete  without deleting the dataset first.
    ** 019 CA  20230917 Backed out H018, the solution is to use 'allowDelete' called for an explicit DELETE OBJECT
    **                  statement.
    ** 020 OM  20231027 Added getBufferBySerializeName() method.
    ** 021 HC  20240222 Enabled JMX on FWD Client.
    ** 022 CA  20231026 Release the buffers after 'readJson' and 'readXml' complete.
    ** 023 CA  20240320 Avoid BDTs within internal FWD runtime.  Replaced iterators with Java 'for', where it
    **                  applies.
    ** 024 CA  20240322 SET-BUFFERS(handle...) must use the DEFAULT-BUFFER-HANDLE for any TEMP-TABLE in the list.
    ** 025 CA  20240328 Save and expose the proxy for a static dataset.
    ** 026 CA  20240324 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
    ** 027 CA  20240404 Static temp-table or dataset resolution by name must be done using the currently executing
    **                  type and the static temp-table/dataset's defining type.
    ** 028 CA  20240422 Allow the query to be reset to its initialization state, so the same instance can be used
    **                  to execute child buffer FILL operations.
    ** 029 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.
    ** 030 CA  20240512 All buffers (including peer buffers) from all temp-tables for this dataset must be flushed
    **                  when using WRITE-XML or WRITE-JSON.
    ** 031 EAB 20240423 Rewrote getDataAsJson method in order to imitate the result returned by writeJson().
    ** 032 CA  20240523 Remote requests over appserver allow the parameter dataset to be REFERENCE-ONLY.
    ** 033 AS  20240729 Added support for bind parameter option in createDynamicDataSetImpl.
    ** 034 CA  20240809 Skip using JMX timers when JMX_DEBUG flag is not set.
    ** 035 AS  20240808 registerOutputDataSetHandleCopier only for Proxy in createDynamicDataSetImpl.
    **                  Throw 13009 error only if the source dataset is not a handle and is not a reference.
    ** 036 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.
    ** 037 AS  20240924 Throw 4GL accurate errors in isCompatibleWith().
    ** 038 DDF 20241009 When provided a prefix (even the default one), the table name must not exceed 32
    **                  characters.
    ** 039 AP  20241017 Always return the bound buffer, if it exists.
    ** 040 ES  20241017 Added verification for possible misplacement of tables names in pairs-list parameter
    **                  in copyDatasetImpl.
    ** 041 CA  20241030 Improved 'isCompatibleWith' - do not check if the same DMO is used.
    ** 042 AS  20241104 Fixed _getNumTopBuffers for recursive relations.
    ** 043 RNC 20241030 Added support for XML serialization coming from a SOAP request.
    ** 044 AP  20241210 Improved dataset binding in order to take into account only the BIND on the caller side.
    ** 045 AP  20250129 Refactored getDataAsJson to use the RecordBufferSerializer and do the actual JSON
    **                  serialization using jackson library.
    ** 046 ICP 20250123 Used integer.of to leverage caches instances.
    ** 047 CA  20250321 Added 'setCallback' overloads.
    ** 048 AS  20250404 Take into account the reposition attribute of the relations for a fill operation.
    ** 049 AS  20250416 For create-like, use buffer's parent table name instead of the buffer name.
    ** 050 AS  20250529 Improved isCompatibleWith, use dmo signatures to check for dataset compatibility.
    **                  Improved copyTempTableOptions(), use existingFieldsMap()
    **                  from the dmo info for acquiring source buffer fields.
    */

    /*
    ** 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.fasterxml.jackson.core.*;
    import com.fasterxml.jackson.databind.*;
    import com.goldencode.p2j.jmx.*;
    import com.goldencode.p2j.persist.TemporaryBuffer.CopyTableMode;
    import com.goldencode.p2j.persist.orm.*;
    import com.goldencode.p2j.persist.serial.*;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.logging.*;
    import com.goldencode.util.*;

    import java.io.*;
    import java.sql.*;
    import java.util.*;
    import java.util.function.*;
    import java.util.stream.*;

    /**
     * {@code DataSet} objects are collections of one or more related temp-tables. Each temp-table of
     * these can attach to a {@code DataSource} object that allows filling of the temp-table from the
     * data source, or updating the data source from the temp-table. A {@code DataSet} object can
     * optionally contain a set of data relations between the temp-tables.
     */
    @LegacyResource(resource = LegacyResource.DATASET)
    public class DataSet
    extends HandleChain
    implements ADMData,
               BufferCollection,
               CallbackProcedure,
               Clearable,
               CreateLike,
               DataSetChangeable,
               DataSetInterface,
               DataSourceModifiable,
               Dereferenceable,
               DynamicResource,
               Errorable,
               Fillable,
               JsonData,
               NamedSerializable,
               NamespaceURI,
               Rejectable,
               RefCounter,
               SerializeHiddenable,
               TempTableDuplicator,
               UniqueID,
               XmlData,
               XmlNode
    {
       /** The {@code BEFORE-FILL} event string constant in upper case. */
       public static final String BEFORE_FILL_EVENT = "BEFORE-FILL";

       /** The {@code AFTER-FILL} event string constant in upper case. */
       public static final String AFTER_FILL_EVENT = "AFTER-FILL";

       /** The call-site for the {@link #BEFORE_FILL_EVENT} event callback. */
       private static final InvokeConfig BEFORE_FILL_CALL_SITE = new InvokeConfig();

       /** The call-site for the {@link #AFTER_FILL_EVENT} event callback. */
       private static final InvokeConfig AFTER_FILL_CALL_SITE = new InvokeConfig();

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

       /** Instrumentation for {@link #associate}. */
       private static final NanoTimer STATIC_PARAM = NanoTimer.getInstance(FwdServerJMX.TimeStat.OrmDataSetParam);

       /** Instrumentation for {@link #createDynamicDataSet}. */
       private static final NanoTimer DYN_PARAM = NanoTimer.getInstance(FwdServerJMX.TimeStat.OrmDynamicDataSetParam);

       /** Logger for objects of this type. */
       private static final CentralLogger log = CentralLogger.get(DataSet.class.getName());

       /** The value of {@code ADM-DATA} ABL attribute. */
       private String admData = null;

       /** The value of {@code NAMESPACE-URI} attribute of this {@code DataSet}. */
       private String nsUri = null;

       /** The namespace prefix of this {@code DataSet}. */
       private String nsPrefix = null;

       /** The name of the XML node for serialized data of this {@code DataSet}. */
       private String xmlNodeName = null;

       /** The serialization name of this {@code DataSet}. The default is dataset's name. */
       private String serializeName = null;

       /** The default value for active attribute for first {@code DataRelation}. */
       private boolean defaultActive = true;

       /**
        * The current value of {@code NAME} attribute. It can be modified for dynamic {@code DataSet}s
        * but immutable for static {@code DataSet}s.
        */
       private String name = "";

       /**
        * The current value of boolean {@code ERROR} attribute: indicates whether an error occurred
        * during a {@code FILL} or {@code SAVE-ROW-CHANGES} operation, but can also be altered
        * programmatically (writable).
        */
       private boolean error = false;

       /**
        * 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;

       /** A map with all known callbacks. The keys MUST be in UPPERCASE.*/
       private Map<String, CallbackData> callbacks = null;

       /**
        * Flags the dynamically constructed objects (using {@code CREATE} as opposed to statically
        * defined objects, with {@code DEFINE}).
        */
       private final boolean dynamic;

       /** Flags the {@code REFERENCE-ONLY} {@code DataSet}s. */
       protected boolean referenceOnly = false;

       /**
        * Flags the short period of time when the {@code Builder} constructs the object. During this
        * period, some setters are allowed for STATICally build DATASETs.
        */
       private boolean buildInProgress = false;

       /** Flag indicating the delete was explicitly called for a BY-REFERENCE handle. */
       private boolean postponedDelete = false;

       /**
        * Flags a hidden (name is not written) {@code DataSet} when serialized. This flag overlaps
        * with {@code XML-NODE-TYPE} attribute. A value of {@code false} is equivalent to
        * {@code XML-NODE-TYPE} set to "ELEMENT" (the default) and {@code false} means that the
        * {@code XML-NODE-TYPE} is "HIDDEN".
        */
       private boolean serializeHidden = false;

       /** Marks the valid objects. When destroyed, the flag is set to {@code false}. */
       private boolean deleted = false;

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

       /** The ordered list of buffers contained in this {@code DataSet}. */
       private final ArrayList<BufferImpl> buffers = new ArrayList<>();

       /** The original proxy object associated with the  buffers contained in this {@code DataSet}. */
       private final Map<BufferImpl, BufferImpl> proxies = new IdentityHashMap<>();

       /** The current set of {@code TOP-NAV-QUERY} values mapped by their buffers. */
       private Map<BufferImpl, QueryWrapper> topNavQueries = null;

       /** The set of queries created by this dataset which need to be cleaned. */
       private Set<QueryWrapper> deleteQueries = null;

       /** The list of {@code DataRelation} s from this data set. */
       private final ArrayList<DataRelation> relations = new ArrayList<>();

       /** Destination {@code DataSet} parameters for output copy if necessary before scope closes. */
       private Set<DataSetParameter> outputParams = null;

       /** The list of references of this object. */
       private Set<StaticDataSet> refs = null;

       /** Back-reference to the defining static dataset proxy. */
       private StaticDataSet proxyDataSet = null;

       /** Flag indicating that the fill query for buffers can be re-used. */
       private boolean reuseFillQuery;

       /** Flag indicating that this instance will be serialized due to a SOAP request. */
       private boolean serializedBySOAP;

       /**
        * Factory method. Corresponds to {@code CREATE DATASET} statement. Creates a new
        * {@code DataSet} and stores the object in the supplied handle.
        *
        * @param   dsHandle
        *          The destination handle.
        */
       public static void create(handle dsHandle)
       {
          DataSet.create(dsHandle, (character) null);
       }

       /**
        * Factory method. Corresponds to {@code CREATE DATASET} statement. Creates a new
        * {@code DataSet} and stores the object in the supplied handle.
        *
        * @param   dsHandle
        *          The destination handle.
        * @param   widgetPool
        *          The widget pool to created the new {@code DataSet} into.
        */
       public static void create(handle dsHandle, String widgetPool)
       {
          DataSet.create(dsHandle, new character(widgetPool));
       }

       /**
        * Factory method. Corresponds to {@code CREATE DATASET} statement. Creates a new
        * {@code DataSet} and stores the object in the supplied handle.
        *
        * @param   dsHandle
        *          The destination handle.
        * @param   widgetPool
        *          The widget pool to created the new {@code DataSet} into.
        */
       public static void create(handle dsHandle, character widgetPool)
       {
          // validate widget pool
          if (!WidgetPool.validWidgetPool(widgetPool))
          {
             return;
          }

          DataSet ds = new DataSet("", true, false);  // by default the name is empty string, not unknown
          dsHandle.assign(ds);
          if (widgetPool != null)
          {
             // unnamed widget pools are not allowed.
             ds.addToPool(widgetPool);
          }
       }

       /**
        * Associate two {@code DataSet} with one another, such that all relevant records can be copied
        * between them at prescribed times.
        *
        * @param   dsLocal
        *          The local object relative to the current procedure. If this is an INPUT parameter
        *          then this is the 'source'. This encapsulates the actual parameter sent by caller.
        * @param   dsTarget
        *          A dataset defined in current external procedure.
        * @param   input
        *          If {@code true}, records are copied from source table to destination table
        *          immediately upon invocation of this method, otherwise they are not.
        * @param   output
        *          When {@code true}, the records are copied from destination table back to source
        *          table when the current transaction or subtransaction is committed;  otherwise they
        *          are not.
        *
        * @see TemporaryBuffer#associate(TableParameter, Temporary, boolean, boolean)
        */
       public static void associate(DataSetParameter dsLocal, DataSet dsTarget, boolean input, boolean output)
       {
          associate(dsLocal, dsTarget, input, output, ParameterOption.NONE);
       }

       /**
        * Associate two {@code DataSet} with one another, such that all relevant records can be copied
        * between them at prescribed times.
        *
        * @param   dsSrc
        *          The local object relative to the current procedure. If this is an INPUT parameter
        *          then this is the 'source'. This encapsulates the actual parameter sent by caller.
        * @param   dsTarget
        *          A dataset defined in current external procedure.
        * @param   input
        *          If {@code true}, records are copied from source table to destination table
        *          immediately upon invocation of this method, otherwise they are not.
        * @param   output
        *          When {@code true}, the records are copied from destination table back to source
        *          table when the current transaction or subtransaction is committed;  otherwise they
        *          are not.
        * @param   option
        *          Dataset parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        *
        * @see TemporaryBuffer#associate(TableParameter, Temporary, boolean, boolean)
        */
       public static void associate(DataSetParameter dsSrc,
                                    DataSet          dsTarget,
                                    boolean          input,
                                    boolean          output,
                                    ParameterOption  option)
       {
          associate(dsSrc, dsTarget, input, output, EnumSet.of(option));
       }

       /**
        * Associate two {@code DataSet} with one another, such that all relevant records can be copied
        * between them at prescribed times.
        *
        * @param   dsSrc
        *          The local object relative to the current procedure. If this is an INPUT parameter
        *          then this is the 'source'. This encapsulates the actual parameter sent by caller.
        * @param   dsTarget
        *          A dataset defined in current external procedure.
        * @param   input
        *          If {@code true}, records are copied from source table to destination table
        *          immediately upon invocation of this method, otherwise they are not.
        * @param   output
        *          When {@code true}, the records are copied from destination table back to source
        *          table when the current transaction or subtransaction is committed;  otherwise they
        *          are not.
        * @param   options
        *          Dataset parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        *
        * @see TemporaryBuffer#associate(TableParameter, Temporary, boolean, boolean)
        */
       public static void associate(DataSetParameter dsSrc,
                                    DataSet          dsTarget,
                                    boolean          input,
                                    boolean          output,
                                    EnumSet<ParameterOption>  options)
       {
          dsSrc.setInputMode(input);
          dsSrc.setOutputMode(output);

          associate(dsSrc, dsTarget, options);
       }

       /**
        * Associate two {@code DataSet} with one another, such that all relevant records can be copied
        * between them at prescribed times.
        *
        * @param   dsLocal
        *          The local object relative to the current procedure. If this is an INPUT parameter
        *          then this is the 'source'. This encapsulates the actual parameter sent by caller.
        * @param   dsTarget
        *          A dataset defined in current external procedure.
        *
        * @see TemporaryBuffer#associate(TableParameter, Temporary)
        */
       public static void associate(DataSetParameter dsLocal, DataSet dsTarget)
       {
          associate(dsLocal, dsTarget, ParameterOption.NONE);
       }

       /**
        * Associate two {@code DataSet} with one another, such that all relevant records can be copied
        * between them at prescribed times.
        *
        * @param   dsSrc
        *          The local object relative to the current procedure. If this is an INPUT parameter
        *          then this is the 'source'. This encapsulates the actual parameter sent by caller.
        * @param   dsTarget
        *          A dataset defined in current external procedure.
        * @param   option
        *          Dataset parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        *
        * @see TemporaryBuffer#associate(TableParameter, Temporary)
        */
       public static void associate(final DataSetParameter dsSrc,
                                    final DataSet          dsTarget,
                                    ParameterOption        option)
       {
          associate(dsSrc, dsTarget, EnumSet.of(option));
       }

       /**
        * Associate two {@code DataSet} with one another, such that all relevant records can be copied
        * between them at prescribed times.
        *
        * @param   dsSrc
        *          The local object relative to the current procedure. If this is an INPUT parameter
        *          then this is the 'source'. This encapsulates the actual parameter sent by caller.
        * @param   dsTarget
        *          A dataset defined in current external procedure.
        * @param   options
        *          Dataset parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        *
        * @see TemporaryBuffer#associate(TableParameter, Temporary)
        */
       public static void associate(DataSetParameter dsSrc, DataSet dsTarget, EnumSet<ParameterOption> options)
       {
          if (FwdServerJMX.JMX_DEBUG)
          {
             STATIC_PARAM.timer(() -> associateImpl(dsSrc, dsTarget, options));
          }
          else
          {
             associateImpl(dsSrc, dsTarget, options);
          }
       }

       /**
        * Associate two {@code DataSet} with one another, such that all relevant records can be copied
        * between them at prescribed times.
        *
        * @param   dsSrc
        *          The local object relative to the current procedure. If this is an INPUT parameter
        *          then this is the 'source'. This encapsulates the actual parameter sent by caller.
        * @param   dsTarget
        *          A dataset defined in current external procedure.
        * @param   options
        *          Dataset parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        *
        * @see TemporaryBuffer#associate(TableParameter, Temporary)
        */
       private static void associateImpl(final DataSetParameter   dsSrc,
                                         final DataSet            dsTarget,
                                         EnumSet<ParameterOption> options)
       {
          boolean input  = dsSrc.isInputMode();
          boolean output = dsSrc.isOutputMode();
          boolean append = options.contains(ParameterOption.APPEND);

          DataSetManager.registerScopeable();

          StaticDataSet localDS = (StaticDataSet) dsTarget;
          if (!dsSrc.isRemoteParameter())
          {
             DataSet sourceDS = dsSrc.getOriginalDataSet();
             if (dsSrc.isBind() && options.contains(ParameterOption.BIND) &&
                 sourceDS != null && !localDS.referenceOnly)
             {
                if (sourceDS.referenceOnly)
                {
                   // bind from the target to the source; this is always permanent
                   // CA: 'thisIsParam' was changed to 'false' because it was found to cause regressions.
                   ((StaticDataSet) sourceDS).bind(localDS, input, output, true, false);
                }
                else if (dsSrc.isOriginalDataSetProxy())
                {
                   String pname = localDS.instantiatingProcedure().unwrap().name().toStringMessage();
                   ErrorManager.recordOrThrowError(13009, pname, localDS.name().toJavaType());
                   // <program name> BIND modifier not allowed for cases where neither the caller nor the called
                   // TABLE or DATASET parameter <parameter-name> has been defined REFERENCE-ONLY and neither
                   // caller nor called parameter is a TABLE-HANDLE or DATASET-HANDLE. (13009)
                }

                return;
             }
             if (localDS.referenceOnly)
             {
                if (dsSrc.isByReference() || dsSrc.isBind() || options.contains(ParameterOption.BIND))
                {
                   // temporary bind dsSrc.getDataset() to dsTarget
                   localDS.bind(dsSrc.getDataset(), input, output,
                                dsSrc.isBind() || options.contains(ParameterOption.BIND), false);
                   {
                      // TODO: check for both usages of [bind] what happens if the check fails for a
                      //       subsequent parameter in case there are many DataSet: roll back the
                      //       bindings for the former parameter? Keep them?
                   }
                   return;
                }

                if (!localDS.isBound())
                {
                   String pname = localDS.instantiatingProcedure().unwrap().name().toStringMessage();
                   String dsName = dsSrc.getDataset().name().toJavaType();
                   ErrorManager.recordOrThrowError(13012, pname, dsName);
                   // <program-name> BIND or BY-REFERENCE modifier required on RUN, FUNCTION or METHOD invocation
                   // parameter when called TABLE or DATASET parameter <parameter-name> is REFERENCE-ONLY and is
                   // still not bound. (13012)
                }

                // else, we continue with the normal processing, as the local REFERENCE-ONLY dataset is bound
             }

             if (dsSrc.isByReference())
             {
                handle h = dsSrc.getDatasetHandle();

                if (h.isUnknown())
                {
                   if (input)
                   {
                      // TODO: error, how to map the target DATASET?
                   }
                   else
                   {
                      DataSet targetRefDS = dsTarget.ref();
                      targetRefDS.addOutputParam(dsSrc);

                      targetRefDS.pm.executeOnReturn(() -> targetRefDS.performOutputCopy(dsSrc),
                                                     WeightFactor.FIRST);
                      return;
                   }
                }
                else
                {
                   // temporarily bind dsSrc.getDataset() to dsTarget
                   localDS.bind(dsSrc.getDataset(), input, output, false, false);
                   return;
                }
             }
             // NONE to BIND will do nothing, unless the REFERENCE-ONLY is used at target
             // BIND to NONE will error out
             else if (dsSrc.isBind() && options.contains(ParameterOption.NONE))
             {
                String procName = localDS.instantiatingProcedure().unwrap().name().toStringMessage();
                String dsName = dsSrc.getDataset().name().toJavaType();
                ErrorManager.recordOrThrowError(13009, procName, dsName);
                // <program name> BIND modifier not allowed for cases where neither the caller nor the
                // called TABLE or DATASET parameter <parameter-name> has been defined REFERENCE-ONLY and
                // neither caller nor called parameter is a TABLE-HANDLE or DATASET-HANDLE. (13009)
                return;
             }
          }

          if (dsSrc.getDataset() == dsTarget.ref())
          {
             // same reference, nothing to do; BY-REF/REF-ONLY still needs to do processing related
             // to keeping all mutable buffers in sync
             return;
          }

          if (input)
          {
             if (!dsSrc.isRemoteParameter())
             {
                DataSet dsSource = dsSrc.getDataset();
                if (dsSource != null)
                {
                   int bufCnt = dsSource.numBuffers().intValue();
                   for (int k = 1; k <= bufCnt; k++)
                   {
                      // copy data for each corresponding temp-table
                      Buffer srcBuf = dsSource.getBufferByIndex(k);
                      Temporary srcDMO = TemporaryBuffer.getDefaultBuffer(srcBuf.tableHandle());
                      BufferImpl targetDmo = dsTarget.getBufferByIndex(k);
                      rowid srcRec = dsTarget.referenceOnly && srcBuf._available() ? srcBuf.rowID() : null;
                      boolean res = TemporaryBuffer.copyAllRows(
                                        srcRec,
                                        srcDMO,
                                        (Temporary) targetDmo,
                                        append,
                                        TemporaryBuffer.CopyTableMode.INPUT_PARAM_MODE);
                      if (!res)
                      {
                         BlockManager.returnError();
                      }
                   }
                }
             }
             else if (dsSrc.getDatasetWrapper().getXmlDataset() != null)
             {
                String xmlDataset = dsSrc.getDatasetWrapper().getXmlDataset();
                readFromXml(xmlDataset, append, new handle(dsTarget));
             }
             else if (localDS.referenceOnly)
             {
                // even if we are remote, REFERENCE-ONLY is allowed - in this case, always bind target to source
                // argument
                localDS.bind(dsSrc.getDataset(), input, output, false, false);
             }
             else
             {
                int bufCnt = dsSrc.getDatasetWrapper().getTableDefs().size();
                Map<String, TableWrapper> legacyTableToWrapper = null;
                boolean fromJson = dsSrc.getDatasetWrapper().isFromJson();
                if (fromJson)
                {
                   legacyTableToWrapper = new HashMap<>();
                   for (int k = 1; k <= bufCnt; k++)
                   {
                      TableWrapper wrapper = dsSrc.getDatasetWrapper().getTableWrapper(k - 1);
                      legacyTableToWrapper.put(wrapper.getTableName().toLowerCase(), wrapper);
                   }
                }
                for (int k = 1; k <= bufCnt; k++)
                {
                   // copy data for each corresponding temp-table
                   BufferImpl targetDmo = dsTarget.getBufferByIndex(k);
                   TableWrapper wrapper = null;
                   if (fromJson)
                   {
                      AbstractTempTable tt = ((AbstractTempTable) targetDmo.tableHandleNative());
                      wrapper = legacyTableToWrapper.get(tt._name().toLowerCase());
                   }
                   else
                   {
                      wrapper = dsSrc.getDatasetWrapper().getTableWrapper(k - 1);
                   }

                   if (dsSrc.isRemoteParameter()  && !wrapper.isReceivedSchema())
                   {
                      RecordBuffer rb = ((BufferReference) targetDmo).buffer();
                      DmoMeta meta = rb.getDmoInfo();
                      Iterator<Property> props = meta.getFields(false);
                      List<PropertyDefinition> pdl = new ArrayList<>();
                      props.forEachRemaining(p -> pdl.add(new PropertyDefinition(p)));
                      wrapper.setProperties(pdl);
                   }
                   TemporaryBuffer.insertAllRows(
                         wrapper,
                         (Temporary) targetDmo,
                         append,
                         TemporaryBuffer.CopyTableMode.INPUT_PARAM_MODE,
                         dsSrc.getParameterIndex());
                }
             }
          }

          if (output)
          {
             DataSet targetRefDS = dsTarget.ref();
             targetRefDS.addOutputParam(dsSrc);

             targetRefDS.pm.executeOnReturn(() -> targetRefDS.performOutputCopy(dsSrc), WeightFactor.FIRST);
          }
       }

       /**
        * Dynamically create a {@code DataSet} from the {@code DataSetParameter} provided. The new
        * {@code DataSet} will be stored in {@code dsHandle}. This method is used at the beginning of
        * a method/function in order to properly convert the {@code PARAMETER DATASET-HANDLE}
        * constructs.
        *
        * @param    dsParam
        *           The {@code DataSetParameter}. This is the actual parameter for the routine.
        * @param    dsHandle
        *           A handle to store the reference to the new {@code DataSet} during the scope of this
        *           routine.
        */
       public static void createDynamicDataSet(DataSetParameter dsParam, handle dsHandle)
       {
          createDynamicDataSet(dsParam, dsHandle, ParameterOption.NONE);
       }

       /**
        * Dynamically create a {@code DataSet} from the {@code DataSetParameter} provided. The new
        * {@code DataSet} will be stored in {@code dsHandle}. This method is used at the beginning of
        * a method/function in order to properly convert the {@code PARAMETER DATASET-HANDLE}
        * constructs.
        *
        * @param    dsParam
        *           The {@code DataSetParameter}. This is the actual parameter for the routine.
        * @param    dsHandle
        *           A handle to store the reference to the new {@code DataSet} during the scope of this
        *           routine.
        * @param    input
        *           If {@code true} then this is an {@code INPUT} or {@code INPUT-OUTPUT} parameter.
        * @param    output
        *           If {@code true} then this is an {@code OUTPUT} or {@code INPUT-OUTPUT} parameter.
        */
       public static void createDynamicDataSet(DataSetParameter dsParam,
                                               handle           dsHandle,
                                               boolean          input,
                                               boolean          output)
       {
          createDynamicDataSet(dsParam, dsHandle, input, output, ParameterOption.NONE);
       }

       /**
        * Dynamically create a {@code DataSet} from the {@code DataSetParameter} provided. The new
        * {@code DataSet} will be stored in {@code dsHandle}. This method is used at the beginning of
        * a method/function in order to properly convert the {@code PARAMETER DATASET-HANDLE}
        * constructs.
        *
        * @param   dsParam
        *          The {@code DataSetParameter}. This is the actual parameter for the routine (src).
        * @param   dsHandle
        *          A handle to store the reference to the new {@code DataSet} during the scope of this
        *          routine (target).
        * @param   input
        *          If {@code true} then this is an {@code INPUT} or {@code INPUT-OUTPUT} parameter.
        * @param   output
        *          If {@code true} then this is an {@code OUTPUT} or {@code INPUT-OUTPUT} parameter.
        * @param   option
        *          Dataset parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        */
       public static void createDynamicDataSet(DataSetParameter dsParam,
                                               handle           dsHandle,
                                               boolean          input,
                                               boolean          output,
                                               ParameterOption  option)
       {
          dsParam.setInputMode(input);
          dsParam.setOutputMode(output);

          createDynamicDataSet(dsParam, dsHandle, option);
       }

       /**
        * Dynamically create a {@code DataSet} from the {@code DataSetParameter} provided. The new
        * {@code DataSet} will be stored in {@code dsHandle}. This method is used at the beginning of
        * a method/function in order to properly convert the {@code PARAMETER DATASET-HANDLE}
        * constructs.
        *
        * @param   dsParam
        *          The {@code DataSetParameter}. This is the actual parameter for the routine (src).
        * @param   dsHandle
        *          A handle to store the reference to the new {@code DataSet} during the scope of this
        *          routine (target).
        * @param   option
        *          Dataset parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        */
       public static void createDynamicDataSet(DataSetParameter dsParam,
                                               handle           dsHandle,
                                               ParameterOption  option)
       {
          if (FwdServerJMX.JMX_DEBUG)
          {
             DYN_PARAM.timer(() -> createDynamicDataSetImpl(dsParam, dsHandle, option));
          }
          else
          {
             createDynamicDataSetImpl(dsParam, dsHandle, option);
          }
       }

       /**
        * Dynamically create a {@code DataSet} from the {@code DataSetParameter} provided. The new
        * {@code DataSet} will be stored in {@code dsHandle}. This method is used at the beginning of
        * a method/function in order to properly convert the {@code PARAMETER DATASET-HANDLE}
        * constructs.
        *
        * @param   dsParam
        *          The {@code DataSetParameter}. This is the actual parameter for the routine (src).
        * @param   dsHandle
        *          A handle to store the reference to the new {@code DataSet} during the scope of this
        *          routine (target).
        * @param   option
        *          Dataset parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        */
       private static void createDynamicDataSetImpl(DataSetParameter dsParam,
                                                    handle           dsHandle,
                                                    ParameterOption  option)
       {
          DataSetManager manager = DataSetManager.instance();
          manager._registerScopeable();

          boolean input  = dsParam.isInputMode();
          boolean output = dsParam.isOutputMode();
          boolean append = (option == ParameterOption.APPEND) || dsParam.isAppend();
          boolean bind = dsParam.isBind();

          if (output)
          {
             manager.registerCurrent(dsHandle);
             if (bind && dsParam.getOriginalDataSet() == null)
             {
                ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();
                pm.executeOnReturn(() -> dsParam.getDatasetHandle().assign(dsHandle), WeightFactor.LEVEL_10);
                return;
             }
          }

          //if bind, assign source dataset to the new ds handle,
          //this will ensure that the new dataset has the same data source
          if (bind)
          {
             if (!dsParam.getDataset().referenceOnly)
             {
                dsHandle.assign(dsParam.getDataset());
             }

             if (output && dsParam.isOriginalDataSetProxy())
             {
                manager.registerOutputDataSetHandleCopier(dsParam, dsHandle);
             }
             return;
          }

          if (dsParam.isByReference())
          {
             dsHandle.assign(dsParam.getDataset());

             if (dsHandle._isValid())
             {
                DataSet ds = (DataSet) dsHandle.getResource();
                manager.addByReferenceParameter(ds);

                ds.pm.executeOnReturn(() -> manager.removeByReferenceParameter(ds),
                                      WeightFactor.LEVEL_10);
             }
             else if (output)
             {
                manager.registerOutputDataSetHandleCopier(dsParam, dsHandle);
             }

             return;
          }

          if (input || dsParam.isRemoteParameter())
          {
             if (dsParam.isRemoteParameter() && dsParam.getDatasetWrapper().getXmlDataset() != null)
             {
                String xmlDataset = dsParam.getDatasetWrapper().getXmlDataset();
                readFromXml(xmlDataset, append, dsHandle);
                return;
             }

             DataSet newDS = null;

             handle srcHandle = dsParam.getDatasetHandle();
             if (srcHandle._isValid())
             {
                if (dsParam.isByReference() || dsParam.isRemoteParameter())
                {
                   // check if all tables contain schema.
                   if (!dsParam.getDatasetWrapper().getTableDefs().stream().
                            allMatch(DsTableDefinition::isReceivedSchema))
                   {
                      // A NO-SCHEMA-MARSHAL table cannot be used as a parameter where the receiving side
                      // does not have a pre-prepared schema
                      ErrorManager.recordOrThrowError(12323);
                   }
                   // if [dsParam] is remote, the dataset was rehydrated when [dsParam] was created
                   // do not copy now for 2nd time. Since it is an [input] param we can use the
                   // dataset handle directly.
                   dsHandle.assign(dsParam.get());
                }
                else
                {
                   handle newDSh = TypeFactory.handle();
                   DataSet.create(newDSh);
                   newDS = (DataSet) newDSh.getResource();
                   logical res = newDS.copyDatasetImpl(srcHandle, false, false, false, null, false, "");
                   if (!res.booleanValue())
                   {
                      BlockManager.returnError();
                   }
                   dsHandle.assign(newDSh);
                }
             }
          }

          if (output)
          {
             manager.registerOutputDataSetHandleCopier(dsParam, dsHandle);
          }
       }

       /**
        * Constructor to allow building a dataset with no linkage (used for the case of the surrogate
        * resource {@link StaticDataSet}, which delegates the calls to either the default dataset
        * or the bound dataset.
        */
       DataSet()
       {
          super(false, false);
          this.dynamic = false;
          name = "";
       }

       /**
        * Initialize this resource by determining its type.
        *
        * @param   legacyName
        *          The legacy name of the {@code DataSet}. Used for lookup.
        * @param   dynamic
        *          Flag indicating if this is a static or dynamic resource.
        * @param   referenceOnly
        *          Flag indicating if this is a REFERENCE-ONLY resource.
        *
        * @throws  IllegalStateException
        *          If the resource type could not be determined from a {@link LegacyResource} annotation, after
        *          checking the interface hierarchy, or more than one interface is annotated.
        */
       DataSet(String legacyName, boolean dynamic, boolean referenceOnly)
       {
          super(dynamic, dynamic);
          this.dynamic = dynamic;
          this.referenceOnly = dynamic ? false : referenceOnly; // dynamic DSes cannot be reference-only
          name = legacyName; // the name not checked at this time, it was analyzed by the conversion
          if (!dynamic)
          {
             buildInProgress = true;
          }
       }

       /**
        * Set the back-reference to the defining static proxy dataset.
        *
        * @param    proxyDataSet
        *           The defining proxy dataset.
        */
       public void setProxyDataSet(StaticDataSet proxyDataSet)
       {
          this.proxyDataSet = proxyDataSet;
       }

       /**
        * Get the defining {@link #proxyDataSet};
        *
        * @return   See above.
        */
       public StaticDataSet getProxyDataSet()
       {
          return proxyDataSet;
       }

       /**
        * Convert this dataset to a handle.
        *
        * @return   See above.
        */
       public handle asHandle()
       {
          return new handle(this);
       }

       /**
        * Reset name, nsPrefix and nsURI.
        */
       public void resetNames()
       {
          name = "";
          nsPrefix = null;
          nsUri = "";
       }
       /**
        * Sets the name of this handle. The name cannot be unknown.
        *
        * @param   name
        *          The new name to be set.
        */
       @Override
       public void name(character name)
       {
          if (name == null || name.isUnknown())
          {
             unableToAssignUnknown("NAME", "DATASET widget");
             return;
          }

          name(name.getValue());
       }

       /**
        * Get the {@code name} attribute of handle.
        *
        * @return See above.
        */
       @Override
       public character name()
       {
          return new character(this.name);
       }

       /**
        * Get the <code>name</code> attribute of handle as a string.
        *
        * @return See above.
        */
       @Override
       public String _name()
       {
          return this.name;
       }

       /**
        * Sets the {@code name} attribute of the object referred by this handle.
        *
        * @param   name
        *          The new name to be set to target object.
        */
       @Override
       public void name(String name)
       {
          if (!isAlterable())
          {
             ErrorManager.recordOrShowError(12411);
             // Cannot set attribute for a static object.
             ErrorManager.recordOrThrowError(3131, "NAME", "",  LegacyResource.DATASET);
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
             return;
          }

          if (!validateName(name))
          {
             ErrorManager.recordOrThrowError(3131, "NAME", this.name,  LegacyResource.DATASET);
             // Unable to set attribute <attribute name> in widget <widget name> of type <widget-type>.
             return;
          }

          this.name = name;
       }

       /**
        * 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");
       }

       /**
        * 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)
       {
          // there are no arrays in a dataset, just independent buffers
          return dereference(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}
        * the unknown value is returned.
        *
        * @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(String memberName)
       {
          handle buffHnd = bufferHandle(memberName);
          if (buffHnd.isUnknown())
          {
             ErrorManager.recordOrShowError(12785, memberName, name().toStringMessage());
             // Table <name> not found in dataset <name>.
             return new handle();
          }
          return buffHnd;
       }

       /**
        * 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}
        * the unknown value is returned.
        *
        * @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 or unknown (?) if there is no such member.
        */
       @Override
       public <T extends BaseDataType> T dereference(Class<T> type, String memberName)
       {
          BaseDataType val = dereference(memberName);
          if (val.isUnknown() || !type.isAssignableFrom(val.getClass()))
          {
             ErrorManager.recordOrShowError(10068);
             // Lead attributes in a chained-attribute expression (a:b:c) must be type HANDLE or a user-defined type and valid (not UNKNOWN).
             return (T) BaseDataType.generateUnknown(type);
          }

          return (T) val;
       }

       /**
        * 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   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)
       {
          ErrorManager.recordOrThrowError(7372);
          // Object of type DATASET widget used where BUFFER-FIELD object required.
       }

       /**
        * Get the {@code PREV-SIBLING} attribute of this DataSet. Actually always returns {@code null} because
        * this attribute is not supported by DataSets. Only {@code NEXT-SIBLING} is supported.
        *
        * @return  Always {@code null}.
        */
       @Override
       public handle getPrevSibling()
       {
          handle.invalidAttribute("PREV-SIBLING", false, LegacyResource.DATASET);
          return new handle();
       }

       /**
        * Get the {@code NEXT-SIBLING} attribute of this DataSet. Actually always returns the super's
        * {@code getPrevSibling()} because DataSets are queued in reverse order(!).
        * <p>
        * TODO: investigate other handle resource that manifest the same from the point of NEXT/PREV-SIBLING.
        *
        * @return  Always {@code null}.
        */
       @Override
       public handle getNextSibling()
       {
          return super.getPrevSibling();
       }

       /**
        * Returns the resource to it's clear state. The buffers and relations are removed. However,
        * the content of the dataset tables is not dropped (ie, the tables are not cleared and will
        * keep their current data).
        *
        * @return true if operation is successful
        */
       @Override
       public logical clear()
       {
          if (!_dynamic())
          {
             ErrorManager.recordOrShowError(11967);
             // CLEAR may not be used on a static dataset.
             return logical.of(false);
          }

          clearImpl(false, true);

          // TODO: when is false returned?
          return logical.of(true);
       }

       /**
        * Clears the dataset. Deletes the relations, unlinks the buffers and reset all internal data
        * to default values.
        * <p>
        * After being invoked on a dynamic {@code DataSet} the object gets ready to be repopulated.
        * In this case the method can be invoked multiple times.
        * <p>
        * In the case of a static {@code DataSet}s, the method is called only once, when the object is
        * destroyed. It only makes it easy for GC to free the occupied resources.
        *
        * @param   delete
        *          The dataset will be deleted. If there are dynamic buffers associated with it having AUTO-DELETE
        *          attribute set they will be also deleted.
        * @param   resetProperties
        *          If {@code true} all attributes of the dataset are set to unknown values.
        *
        */
       private void clearImpl(boolean delete, boolean resetProperties)
       {
          for (int i = 0; i < relations.size(); i++)
          {
             DataRelation rel = relations.get(i);
             rel.delete();
          }
          relations.clear();

          for (int i = 0; i < buffers.size(); i++)
          {
             BufferImpl buf = buffers.get(i);
             // delete dynamic buffers associated with dynamic temp-tables, unless the buffer's AUTO-DELETE
             // attribute is set to FALSE
             if (delete                          &&
                 buf.autoDelete().booleanValue() &&
                 buf._dynamic()                  &&
                 buf.tableHandleNative().valid() &&
                 buf.tableHandleNative()._dynamic())
             {
                AbstractTempTable tt = (AbstractTempTable) buf.tableHandleNative();
                if (tt != null && tt.valid() && tt._dynamic())
                {
                   // delete the temp-table
                   tt.delete();
                }
                // delete the buffer
                buf.delete();
             }

             buf.setDataSet(null);
          }
          buffers.clear();
          proxies.clear();

          if (resetProperties)
          {
             callbacks = null;
             name = null;
             admData = null;
             nsUri = null;
             nsPrefix = null;
             xmlNodeName = null;
             serializeName = null;
             defaultActive = true;
             error = false;
             rejected = false;
          }
       }

       /**
        * Returns {@code true} if the buffer is dynamic.
        * <p>
        * This is not emitted by conversion, is used internally by FWD.
        *
        * @return {@code true} if the resource is dynamic, {@code false} otherwise.
        */
       @Override
       public boolean _dynamic()
       {
          return this.dynamic;
       }

       /**
        * Checks  whether the resource is dynamic or static.
        *
        * @return   {@code true} if the resource is dynamic, {@code false} otherwise.
        */
       @Override
       public logical dynamic()
       {
          return logical.of(dynamic);
       }

       /**
        * Loads the empty target ({@code DataSet} or {@code TempTable}) object with changed rows from
        * original (source) object.
        *
        * @param   original
        *          Handle to original object ({@code DataSet} or {@code TempTable}) that contains the
        *          changed rows to load into the target object.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical getChanges(handle original)
       {
          return getChanges(original, false);
       }

       /**
        * Loads the empty target ({@code DataSet} or {@code TempTable}) object with changed rows from
        * original (source) object.
        *
        * @param   original
        *          Handle to original object ({@code DataSet} or {@code TempTable}) that contains the
        *          changed rows to load into the target object.
        * @param   parentMode
        *          When {@code TRUE} the target gets the changed parent rows of each changed child row.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical getChanges(handle original, logical parentMode)
       {
          return getChanges(original,
                            parentMode != null && !parentMode.isUnknown() && parentMode.getValue());
       }

       /**
        * Loads the empty target ({@code DataSet} or {@code TempTable}) object with changed rows from
        * original (source) object.
        *
        * @param   original
        *          Handle to original object ({@code DataSet} or {@code TempTable}) that contains the
        *          changed rows to load into the target object.
        * @param   parentMode
        *          When {@code TRUE} the target gets the changed parent rows of each changed child row.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical getChanges(handle original, boolean parentMode)
       {
          // the parameter must be a valid dataset handle:
          if (original == null || original.isUnknown() || !original.isType(LegacyResource.DATASET))
          {
             ErrorManager.recordOrShowError(11953);
             // Dataset GET-CHANGES and MERGE-CHANGES must have a valid dataset handle as the first argument.
             return logical.of(false);
          }

          DataSet dsSource = (DataSet) original.getResource();
          // check 'original' to be a compatible dataset handle
          if (!isCompatibleWith(dsSource))
          {
             ErrorManager.recordOrShowError(11953);
             // Dataset GET-CHANGES and MERGE-CHANGES must have a change dataset and an original dataset with the same number of members and the member buffers must have matching schema.
             return logical.of(false);
          }

          // this keeps the set of parent jobs that need to be executed after all buffers were
          // processed in non-parentMode, otherwise they risk to be dropped if the parent is processed
          // after the child buffer.
          ArrayList<Object[]> parentJobs = new ArrayList<>();

          // processing stops at first non-matching table, returns FALSE, but KEEPS copied/processed tables
          for (int i = 0; i < buffers.size(); i++)
          {
             BufferImpl src = dsSource.getBuffers().get(i);
             TempTable srcTempTable = TempTableBuilder.asTempTable((Temporary) src);

             BufferImpl dst = buffers.get(i);
             logical ok = dst.getChanges(new handle(src));
             if (!ok.getValue())
             {
                return ok;
             }

             if (parentMode)
             {
                List<DataRelation> relationSet = dsSource.getRelations(src, false, true, true);
                if (!relationSet.isEmpty())
                {
                   // there can be maximum one of this relation because a buffer cannot be child in
                   // more then one active relation
                   DataRelation dataRelation = relationSet.get(0);
                   BufferImpl parentSrc = dataRelation.getParentBufferNative();
                   BufferImpl parentDst = null;
                   for (int k = 0; k < dsSource.getBuffers().size(); k++)
                   {
                      if (dsSource.getBuffers().get(k) == parentSrc.ref())
                      {
                         // get the corresponding buffer
                         parentDst = buffers.get(k);
                      }
                   }
                   if (parentDst != null)
                   {
                      // check whether the parentDst has an (PRIMARY?) UNIQUE index
                      boolean found = false;
                      int idxNo = 0;
                      TableMapper.LegacyIndexInfo index = TableMapper.getLegacyIndexInfo(parentDst.buffer(), idxNo);
                      while (index != null)
                      {
                         if (index.isUnique()) // and PRIMARY? At least this is implied from error message
                         {
                            found = true;
                            break;
                         }

                         index = TableMapper.getLegacyIndexInfo(parentDst.buffer(), ++idxNo);
                      }
                      if (!found)
                      {
                         ErrorManager.recordOrShowError(11885);
                         // A unique primary index is required in the target table, each field of which is mapped to some source field, in order to do a replace-mode or parent-mode COPY/GET/MERGE/FILL type of operation on a dataset table.
                         ErrorManager.recordOrShowError(11891);
                         // Found error during GET-CHANGES method.
                      }

                      // copy only unchanged records from [parentSrc] to [parentDst] for changed
                      // records from [src.before], using index [index] for iteration. These are
                      // normally skipped when [parentDst] was populated from [parentSrc] but now are
                      // required because of the [parentMode] flag. Only AFTER records are copied:
                      // since they were NOT modified they do not have a BEFORE pair.
                      String whereStr = dataRelation.getWhereString().toJavaType();
                      parentJobs.add(new Object[] { parentSrc, parentDst, src, whereStr});
                      // parentSrc.copyParentUnchangedRecords(parentDst, src, whereStr);
                   }
                }
             }
          }

          if (!parentJobs.isEmpty())
          {
             for (int i = 0; i < parentJobs.size(); i++)
             {
                Object[] job = parentJobs.get(i);
                BufferImpl parentSrc = (BufferImpl) job[0];
                BufferImpl parentDst = (BufferImpl) job[1];
                BufferImpl childSrc = (BufferImpl) job[2];
                String whereStr = (String) job[3];
                TemporaryBuffer.copyParentUnchangedRecords(parentDst, parentSrc, childSrc, whereStr);
             }
          }

          return logical.of(true);
       }

       /**
        * Merges the changed rows of all temp-tables or a single temp-table in a source
        * {@code DataSet} object loaded with the {@code GET-CHANGES()} method into the corresponding
        * rows of all temp-tables or a single temp-table (respectively) in the original (target)
        * {@code DataSet} object.
        *
        * @param   original
        *          A handle to the original (target) {@code DataSet} object or the buffer object of a
        *          single target {@code DataSet} temp-table to merge with the changed rows from the
        *          source object.
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical mergeChanges(handle original)
       {
          return mergeChanges(original, false);
       }

       /**
        * Merges the changed rows of all temp-tables or a single temp-table in a source
        * {@code DataSet} object loaded with the {@code GET-CHANGES()} method into the corresponding
        * rows of all temp-tables or a single temp-table (respectively) in the original (target)
        * {@code DataSet} object.
        *
        * @param   original
        *          A handle to the original (target) {@code DataSet} object or the buffer object of a
        *          single target {@code DataSet} temp-table to merge with the changed rows from the
        *          source object.
        * @param   allCopyMode
        *          Use {@code TRUE} to indicates that all rows of the source after-image table must be
        *          merged.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical mergeChanges(handle original, logical allCopyMode)
       {
          boolean copyAll = allCopyMode != null && !allCopyMode.isUnknown() && allCopyMode.getValue();
          return mergeChanges(original, copyAll);
       }

       /**
        * Merges the changed rows of all temp-tables or a single temp-table in a source
        * {@code DataSet} object loaded with the {@code GET-CHANGES()} method into the corresponding
        * rows of all temp-tables or a single temp-table (respectively) in the original (target)
        * {@code DataSet} object.
        *
        * @param   original
        *          A handle to the original (target) {@code DataSet} object or the buffer object of a
        *          single target {@code DataSet} temp-table to merge with the changed rows from the
        *          source object.
        * @param   allCopyMode
        *          Use {@code TRUE} to indicates that all rows of the source after-image table must be
        *          merged.
        *
        * @return {@code true} if operation is successful.
        */
       @Override
       public logical mergeChanges(handle original, boolean allCopyMode)
       {
          // the parameter must be a valid dataset handle:
          if (original == null || original.isUnknown() || !original.isType(LegacyResource.DATASET))
          {
             ErrorManager.recordOrShowError(11953);
             // Dataset GET-CHANGES and MERGE-CHANGES must have a valid dataset handle as the first argument.
             return logical.of(false);
          }

          DataSet dsOriginal = (DataSet) original.getResource();
          // check 'original' to be a compatible dataset handle
          if (!isCompatibleWith(dsOriginal))
          {
             ErrorManager.recordOrShowError(11953);
             // Dataset GET-CHANGES and MERGE-CHANGES must have a change dataset and an original dataset with the same number of members and the member buffers must have matching schema.
             return logical.of(false);
          }

          if (buffers.isEmpty())
          {
             return logical.of(true);
          }

          // NOTE: this is probably a bug / quirk: if an unrelated (ie. not original) dataset is passed as
          //       parameter, the method returns with success without altering the tables
          //       TODO: prove the last affirmation
          TempTable ttChanges = buffers.get(0).tableHandleNative();
          TempTable ttOriginal = dsOriginal.getBuffers().get(0).tableHandleNative();
          WrappedResource ufo = ttChanges.getOriginTable();
          if (ufo != null && ufo != ttOriginal)
          {
             return logical.of(true);
          }

          for (int i = 0; i < buffers.size(); i++)
          {
             BufferImpl myBuf = buffers.get(i);                    // contains the changes
             BufferImpl origBuf = dsOriginal.getBuffers().get(i);  // the target temp-table

             logical res = myBuf.mergeChanges(new handle(origBuf), allCopyMode);
             if (!res.getValue())
             {
                return res;
             }
          }

          error(false); // clear the ERROR attribute in the process
          return logical.of(true);
       }

       /**
        * Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
        * This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
        *
        * @param   hds
        *          A handle to a {@code DataSet} from which to copy the definitions.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical createLike(handle hds)
       {
          if (hds == null || hds.isUnknown() || !hds.isType(LegacyResource.DATASET))
          {
             ErrorManager.recordOrShowError(11968);
             // First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
             return logical.of(false);
          }

          return createLikeImpl((DataSet) hds.getResource(), null);
       }

       /**
        * Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
        * This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
        *
        * @param   dsName
        *          The name of {@code DataSet} from which to copy the definitions.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical createLike(String dsName)
       {
          DataSet sourceDS = DataSetManager.instance().byName(dsName);
          if (sourceDS == null || !sourceDS.valid())
          {
             ErrorManager.recordOrShowError(11968);
             // First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
             return logical.of(false);
          }

          return createLikeImpl(sourceDS, null);
       }

       /**
        * Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
        * This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
        *
        * @param   dsName
        *          The name of {@code DataSet} from which to copy the definitions.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical createLike(character dsName)
       {
          DataSet sourceDS = DataSetManager.instance().byName(dsName.toJavaType());
          if (sourceDS == null || !sourceDS.valid())
          {
             ErrorManager.recordOrShowError(11968);
             // First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
             return logical.of(false);
          }

          return createLikeImpl(sourceDS, null);
       }

       /**
        * Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
        * This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
        *
        * @param   hds
        *          A handle to a {@code DataSet} from which to copy the definitions.
        * @param   prefix
        *          The prefix to prepend to each of the source {@code DataSet} member buffer names,
        *          which creates a new name for each new member buffer.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical createLike(handle hds, String prefix)
       {
          if (hds == null || hds.isUnknown() || !hds.isType(LegacyResource.DATASET))
          {
             ErrorManager.recordOrShowError(11968);
             // First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
             return logical.of(false);
          }

          return createLikeImpl((DataSet) hds.getResource(), prefix);
       }

       /**
        * Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
        * This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
        *
        * @param   hds
        *          A handle to a {@code DataSet} from which to copy the definitions.
        * @param   prefix
        *          The prefix to prepend to each of the source {@code DataSet} member buffer names,
        *          which creates a new name for each new member buffer.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical createLike(handle hds, character prefix)
       {
          if (hds == null || hds.isUnknown() || !hds.isType(LegacyResource.DATASET))
          {
             ErrorManager.recordOrShowError(11968);
             // First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
             return logical.of(false);
          }

          return createLikeImpl((DataSet) hds.getResource(), prefix.toJavaType());
       }

       /**
        * Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
        * This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
        *
        * @param   dsName
        *          The name of {@code DataSet} from which to copy the definitions.
        * @param   prefix
        *          The prefix to prepend to each of the source {@code DataSet} member buffer names,
        *          which creates a new name for each new member buffer.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical createLike(String dsName, String prefix)
       {
          DataSet sourceDS = DataSetManager.instance().byName(dsName);
          if (sourceDS == null || !sourceDS.valid())
          {
             ErrorManager.recordOrShowError(11968);
             // First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
             return logical.of(false);
          }

          return createLikeImpl(sourceDS, prefix);
       }

       /**
        * Duplicates the structure of an existing {@code DataSet}, copying its entire structure.
        * This method is the P2J equivalent of {@code CREATE-LIKE} method of Progress 4GL.
        *
        * @param   dsName
        *          The name of {@code DataSet} from which to copy the definitions.
        * @param   prefix
        *          The prefix to prepend to each of the source {@code DataSet} member buffer names,
        *          which creates a new name for each new member buffer.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical createLike(character dsName, character prefix)
       {
          DataSet sourceDS = DataSetManager.instance().byName(dsName.toJavaType());
          if (sourceDS == null || !sourceDS.valid())
          {
             ErrorManager.recordOrShowError(11968);
             // First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name".
             return logical.of(false);
          }

          return createLikeImpl(sourceDS, prefix.toJavaType());
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        * <p>
        * The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
        * we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
        * referenced via handle interface, the {@code handle} would automatically unwrap it to an
        * {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
        * however, makes it mandatory that the {@code DataSet} to implement the interface.
        *
        * @param   other
        *          Some other temp-table to copy from.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other)
       {
          return copyTempTable(other, false, false, false, null);
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        * <p>
        * The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
        * we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
        * referenced via handle interface, the {@code handle} would automatically unwrap it to an
        * {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
        * however, makes it mandatory that the {@code DataSet} to implement the interface.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        *
        * @return  {@code true}  if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other, logical append)
       {
          return copyTempTable(other, append, logical.of(false), logical.of(false), new character());
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        * <p>
        * The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
        * we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
        * referenced via handle interface, the {@code handle} would automatically unwrap it to an
        * {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
        * however, makes it mandatory that the {@code DataSet} to implement the interface.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        *
        * @return  {@code true} if operation is successful
        */
       @Override
       public logical copyTempTable(handle other, logical append, logical replace)
       {
          return copyTempTable(other, append, replace, logical.of(false), new character());
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        * <p>
        * The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
        * we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
        * referenced via handle interface, the {@code handle} would automatically unwrap it to an
        * {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
        * however, makes it mandatory that the {@code DataSet} to implement the interface.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        * @param   looseCopy
        *          Loose copy mode.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       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-TABLE handle. By default, all current
        * records are dropped before copy operation.
        * <p>
        * The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
        * we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
        * referenced via handle interface, the {@code handle} would automatically unwrap it to an
        * {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
        * however, makes it mandatory that the {@code DataSet} to implement the interface.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        *
        * @return  {@code true}  if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other, boolean append)
       {
          return copyTempTable(other, append, false, false, null);
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        * <p>
        * The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
        * we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
        * referenced via handle interface, the {@code handle} would automatically unwrap it to an
        * {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
        * however, makes it mandatory that the {@code DataSet} to implement the interface.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other, boolean append, boolean replace)
       {
          return copyTempTable(other, append, replace, false, null);
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        * <p>
        * The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
        * we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
        * referenced via handle interface, the {@code handle} would automatically unwrap it to an
        * {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
        * however, makes it mandatory that the {@code DataSet} to implement the interface.
        *
        * @param   other
        *          Some other temp-table to copy from.
        * @param   append
        *          Append mode.
        * @param   replace
        *          Replace mode.
        * @param   looseCopy
        *          Loose copy mode.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other, boolean append, boolean replace, boolean looseCopy)
       {
          return copyTempTable(other, append, replace, looseCopy, null);
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        * Only the first parameter is mandatory so this method will probably be heavy overloaded.
        * <p>
        * The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
        * we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
        * referenced via handle interface, the {@code handle} would automatically unwrap it to an
        * {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
        * however, makes it mandatory that the {@code DataSet} to implement the interface.
        *
        * @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  {@code true} if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other,
                                    logical append,
                                    logical replace,
                                    logical looseCopy,
                                    character prefix)
       {
          handle.invalidAttribute("COPY-TEMP-TABLE", false, LegacyResource.DATASET);
          return logical.UNKNOWN;
       }

       /**
        * Copies the other TEMP-TABLE. Other must be a TEMP-TABLE handle. By default, all current
        * records are dropped before copy operation.
        * Only the first parameter is mandatory so this method will probably be heavy overloaded.
        * <p>
        * The only reason the {@link TempTableDuplicator} is implemented by {@code DataSet} is that
        * we found it static code (@code DATASET DSet:COPY-TEMP-TABLE()}). If the method was
        * referenced via handle interface, the {@code handle} would automatically unwrap it to an
        * {@code invalidAttrAccessProxy}. Because the method is accessed directly from the object,
        * however, makes it mandatory that the {@code DataSet} to implement the interface.
        *
        * @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  {@code true} if operation is successful.
        */
       @Override
       public logical copyTempTable(handle other,
                                    boolean append,
                                    boolean replace,
                                    boolean looseCopy,
                                    String prefix)
       {
          handle.invalidAttribute("COPY-TEMP-TABLE", false, LegacyResource.DATASET);
          return logical.UNKNOWN;
       }

       /**
        * Check if the buffer's fill query can be {@link #reuseFillQuery re-used}.
        */
       boolean isReuseFillQuery()
       {
          return reuseFillQuery;
       }

       /**
        * Fills a {@code DataSet} object, recursively, based on its defined data sources, data
        * relations, and queries. When invoked on one of its member {@code Buffer} objects a partial
        * fill is performed, by starting at the respective level.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical fill()
       {
          if (!invokeCallback(asHandle(), callbacks == null ? null : callbacks.get(BEFORE_FILL_EVENT)))
          {
             error(true);
             return logical.of(false);
          }

          ErrorManager.ErrorHelper errHlp = ErrorManager.getErrorHelper();
          boolean wmOn = errHlp.isWarningMode();
          if (!wmOn)
          {
             errHlp.setWarningMode(true);
          }

          try
          {
             this.reuseFillQuery = true;

             // start with the current set of top-buffers
             List<Buffer> topBuffers = getTopBufferList();
             for (Buffer crtBuffer : topBuffers)
             {
                // fill this buffer. It will invoke fill for all buffers in parent relation (nesting)
                logical fillOk = crtBuffer.fill();
                if (!fillOk.getValue())
                {
                   return fillOk;
                }
             }
          }
          finally
          {
             this.reuseFillQuery = false;
             for (BufferImpl buffer : buffers)
             {
                buffer.clearQuery();
             }

             if (!wmOn)
             {
                errHlp.setWarningMode(false);
             }
          }

          boolean ok = invokeCallback(asHandle(), callbacks == null ? null : callbacks.get(AFTER_FILL_EVENT));
          error(!ok);
          return logical.of(ok);
       }

       /**
        * Obtain the mode in which the {@link DataSet#fill()} method fills a {@code DataSet} member buffer. The
        * default mode is {@code MERGE}.
        * <p>
        * <strong>Note:</strong>
        *         Undocumented feature: query all buffers and returns the common fill mode if one exist. Otherwise
        *         returns unknown value.
        *
        * @return  always unknown value.
        */
       @Override
       public character fillMode()
       {
          String mode = null;
          for (int i = 0; i < buffers.size(); i++)
          {
             String crtFillMode = buffers.get(i).fillMode().toJavaType();
             if (i == 0)
             {
                // assume the fill-mode value of first buffer
                mode = crtFillMode;
             }
             else if (mode == null || !mode.equals(crtFillMode))
             {
                return new character();
             }
          }

          return new character(mode);
       }

       /**
        * Configures the mode in which the {@link DataSet#fill()} method fills a {@code DataSet} member buffer.
        * The allowed modes are: {@code APPEND}, {@code EMPTY}, {@code MERGE}, {@code NO-FILL}, and
        * {@code REPLACE}.
        * <p>
        * <strong>Note:</strong>
        *         Undocumented feature: set the fill mode on all buffers.
        *
        * @param   mode
        *          The new fill mode for the buffers of this dataset.
        */
       @Override
       public void fillMode(String mode)
       {
          if (mode == null)
          {
             unableToAssignUnknown("FILL-MODE", "DATASET widget");
             return;
          }

          String upperMode = mode.toUpperCase();
          switch (upperMode)
          {
             case FM_APPEND:
             case FM_EMPTY:
             case FM_MERGE:
             case FM_NOFILL:
             case FM_REPLACE:
                // one of these is expected
                break;
             default:
                // but this is not. Complain about it.
                ErrorManager.recordOrShowError(11960);
                // FILL-MODE must be one of NO-FILL, MERGE, EMPTY, etc.
                ErrorManager.recordOrShowError(4058, "FILL-MODE", "DATASET widget", mode);
                // **Attribute <attribute> for the <widget id> has an invalid value of <string>.
          }

          // set the fill mode on all buffers
          for (int i = 0; i < buffers.size(); i++)
          {
             BufferImpl buffer = buffers.get(i);
             buffer.fillMode(mode);
          }
       }

       /**
        * Configures the mode in which the {@link DataSet#fill()} method fills a {@code DataSet} member buffer.
        * The allowed modes are: {@code APPEND}, {@code EMPTY}, {@code MERGE}, {@code NO-FILL},
        * and {@code REPLACE}.
        * <p>
        * <strong>Note:</strong>
        *         Undocumented feature: set the fill mode on all buffers.
        *
        * @param   mode
        *          the new fill mode for this buffer.
        */
       @Override
       public void fillMode(Text mode)
       {
          fillMode(mode == null ? null : mode.toJavaType());
       }

       /**
        * Obtain number of references to a {@code Buffer}, {@code DataSet}, or {@code TempTable}
        * object that is defined as a parameter to which reference-only objects are bound.
        * This corresponds to the read-only {@code NUM-REFERENCES} attribute.
        *
        * @return  number of references to this object.
        */
       @Override
       public integer numReferences()
       {
          return new integer(refs == null ? 0 : refs.size());
       }

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

       /**
        * Accepts changes to the data in target object (a temp-table or all temp-tables in a
        * {@code DataSet}).
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical acceptChanges()
       {
          for (int i = 0; i < buffers.size(); i++)
          {
             BufferImpl buffer = buffers.get(i);
             if (!buffer.isAfterBuffer())
             {
                // skip this buffer
                continue;
             }

             logical ok = buffer.beforeBufferNative().acceptChanges();
             if (!ok.booleanValue())
             {
                return ok;
             }
          }

          error(false); // clear the ERROR attribute in the process
          return logical.of(true);
       }

       /**
        * Rejects changes to the data in target object (a temp-table or all temp-tables in a
        * {@code DataSet}).
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical rejectChanges()
       {
          for (int i = 0; i < buffers.size(); i++)
          {
             BufferImpl buffer = buffers.get(i);
             if (!buffer.isAfterBuffer())
             {
                // skip this buffer
                continue;
             }

             logical ok = buffer.beforeBufferNative().rejectChanges();
             if (!ok.booleanValue())
             {
                return ok;
             }
          }

          error(false); // clear the ERROR attribute in the process
          return logical.of(true);
       }

       /**
        * Applies a callback procedure, which allows execution of a defined event without duplicating
        * the event procedure definition.
        *
        * @param   eventName
        *          The event whose callback will be called.
        *
        * @return   {@code true} if operation is successful.
        */
       @Override
       public logical applyCallback(String eventName)
       {
          if (!isCallbackName(eventName))
          {
             return logical.of(false);
          }

          return logical.of(invokeCallback(asHandle(),
                                            callbacks == null ? null : callbacks.get(eventName.toUpperCase())));
       }

       /**
        * Retrieves the name of the internal procedure associated with the ABL callback for the
        * specified event.
        *
        * @param   eventName
        *          The name of the event.
        *
        * @return the name of the internal procedure associated with specified event.
        */
       @Override
       public character getCallbackProcName(String eventName)
       {
          if (!isCallbackName(eventName))
          {
             return new character();
          }

          CallbackData cbd = callbacks == null ? null : callbacks.get(eventName.toUpperCase());
          if (cbd == null)
          {
             return new character();
          }

          return new character(cbd.getTarget());
       }

       /**
        * Retrieves the handle of the procedure that contains the internal procedure associated
        * with the ABL callback for the specified event
        *
        * @param   eventName
        *          The name of the event.
        *
        * @return  a handle to the procedure that contains the callback procedure.
        */
       @Override
       public handle getCallbackProcContext(String eventName)
       {
          if (!isCallbackName(eventName))
          {
             return new handle();
          }

          CallbackData cbd = callbacks == null ? null : callbacks.get(eventName.toUpperCase());
          if (cbd == null || cbd.getProcedure() == null)
          {
             return new handle();
          }

          return new handle(cbd.getProcedure());
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid.
        *
        * @param    event
        *           The event name, must evaluate to one of the events supported by implementing object.
        * @param    proc
        *           The internal procedure to be executed for this event, which will be executed using
        *           the current THIS-PROCEDURE reference. To remove a callback mapping, set the this
        *           parameter to the empty string.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(String event, String proc)
       {
          return setCallbackProcedure(event, proc, null);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid.
        *
        * @param   event
        *          The event name.
        * @param   proc
        *          The internal procedure to be executed for this event, which will be executed using
        *          the current THIS-PROCEDURE reference. To remove a callback mapping, set the this
        *          parameter to the empty string.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(String event, character proc)
       {
          return setCallbackProcedure(event, proc.toJavaType(), null);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid.
        *
        * @param   event
        *          The event name.
        * @param   proc
        *          The internal procedure to be executed for this event, which will be executed using
        *          the current THIS-PROCEDURE reference. To remove a callback mapping, set the this
        *          parameter to the empty string.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(character event, character proc)
       {
          return setCallbackProcedure(event.toJavaType(), proc.toJavaType(), null);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid or if the procedure's context is
        * not a valid handle.
        *
        * @param    event
        *           The event name, must evaluate to one of the events supported by implementing object.
        * @param    proc
        *           The internal procedure to be executed for this event. To remove a callback
        *           mapping, set the this parameter to the empty string.
        * @param    context
        *           The procedure handle where the internal procedure will be searched and executed.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(character event, String proc, handle context)
       {
          return setCallbackProcedure(event.toJavaType(), proc, context);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid or if the procedure's context is
        * not a valid handle.
        *
        * @param   event
        *          The event name.
        * @param   proc
        *          The internal procedure to be executed for this event, which will be executed using
        *          the current THIS-PROCEDURE reference. To remove a callback mapping, set the this
        *          parameter to the empty string.
        * @param   context
        *          The procedure handle where the internal procedure will be searched and executed.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(String event, character proc, handle context)
       {
          return setCallbackProcedure(event, proc.toJavaType(), context);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid or if the procedure's context is not a valid
        * handle.
        *
        * @param   event
        *          The event name.
        * @param   proc
        *          The internal procedure to be executed for this event, which will be executed using the current
        *          THIS-PROCEDURE reference. To remove a callback mapping, set the this parameter to the empty
        *          string.
        * @param   context
        *          The procedure handle where the internal procedure will be searched and executed.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(character event, character proc, handle context)
       {
          return setCallbackProcedure(event.toJavaType(), proc.toJavaType(), context);
       }

       /**
        * Applies a callback procedure, which allows execution of a defined event without duplicating
        * the event procedure definition.
        *
        * @param   eventName
        *          The event whose callback will be called.
        *
        * @return  {@code true} if operation is successful.
        */
       @Override
       public logical applyCallback(Text eventName)
       {
          return applyCallback(eventName.toJavaType());
       }

       /**
        * Retrieves the name of the internal procedure associated with the ABL callback for the
        * specified event.
        *
        * @param   eventName
        *          The name of the event.
        *
        * @return  the name of the internal procedure associated with specified event.
        */
       @Override
       public character getCallbackProcName(Text eventName)
       {
          return getCallbackProcName(eventName.toJavaType());
       }

       /**
        * Retrieves the handle of the procedure that contains the internal procedure associated
        * with the ABL callback for the specified event
        *
        * @param   eventName
        *          The name of the event.
        *
        * @return  a handle to the procedure that contains the callback procedure.
        */
       @Override
       public handle getCallbackProcContext(Text eventName)
       {
          return getCallbackProcContext(eventName.toJavaType());
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(Text callbackName, Text routineName, object<?> context)
       {
          return setCallback(callbackName.toJavaType(), routineName.toJavaType(), context);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(String callbackName, String routineName, handle context)
       {
          return setCallbackProcedure(callbackName, routineName, context);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(Text callbackName, Text routineName, handle context)
       {
          return setCallbackProcedure(callbackName.toJavaType(), routineName.toJavaType(), context);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(String callbackName, Text routineName, handle context)
       {
          return setCallbackProcedure(callbackName, routineName.toJavaType(), context);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(String callbackName, String routineName)
       {
          return setCallback(callbackName, routineName, (object<?>) null);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(Text callbackName, Text routineName)
       {
          return setCallback(callbackName.toJavaType(), routineName.toJavaType(), (object<?>) null);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(Text callbackName, String routineName)
       {
          return setCallback(callbackName.toJavaType(), routineName);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(String callbackName, Text routineName)
       {
          return setCallback(callbackName, routineName.toJavaType());
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(String callbackName, Text routineName, object<?> context)
       {
          return setCallback(callbackName, routineName.toJavaType(), context);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(Text callbackName, String routineName, object<?> context)
       {
          return setCallback(callbackName.toJavaType(), routineName, context);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if operation is successful and {@code false} if the parameters are
        *          invalid.
        */
       @Override
       public logical setCallback(Text callbackName, String routineName, handle context)
       {
          return setCallback(callbackName.toJavaType(), routineName, context);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        * <p>
        * This method returns false if the event name is not valid.
        *
        * @param   event
        *          The event name.
        * @param   proc
        *          The internal procedure to be executed for this event, which will be executed using
        *          the current THIS-PROCEDURE reference. To remove a callback mapping, set the this
        *          parameter to the empty string.
        *
        * @return   {@code true} if the event was registered.
        */
       @Override
       public logical setCallbackProcedure(character event, String proc)
       {
          return setCallbackProcedure(event.toJavaType(), proc, null);
       }

       /**
        * Configures a callback. Creates an association between a method within a class instance or
        * an internal procedure within a persistent procedure, with an ABL callback event.
        *
        * @param   callbackName
        *          The name of a callback.
        * @param   routineName
        *          The name of a method or an internal procedure to be associated.
        * @param   context
        *          The context in which the callback will be executed.
        *
        * @return  {@code true} if {@code callbackName} is a correct callback name. The rest of
        *          parameters are ignored (note: manual states otherwise).
        */
       @Override
       public logical setCallback(String callbackName, String routineName, object<?> context)
       {
          if (!isCallbackName(callbackName))
          {
             return logical.of(false);
          }

          if (routineName == null)
          {
             return logical.of(true);
          }

          if (routineName.isEmpty())
          {
             if (callbacks != null)
             {
                callbacks.remove(callbackName.toUpperCase());
             }
             return logical.of(true);
          }

          CallbackData cbd;
          if (context == null || context.isUnknown())
          {
             // defaulting to THIS-OBJECT or to THIS-PROCEDURE, depending on the context
             if (ObjectOps.thisObject().isUnknown())
             {
                InvokeConfig callSite = getCallSite(callbackName);
                cbd = new CallbackData(routineName, pm.thisProcedure(), null, callSite);
             }
             else
             {
                cbd = new CallbackData(routineName, null, ObjectOps.thisObject(), null);
             }
          }
          else
          {
             cbd = new CallbackData(routineName, null, context, null);
          }

          if (callbacks == null)
          {
             callbacks = new HashMap<>();
          }
          callbacks.put(callbackName.toUpperCase(), cbd);

          return logical.of(true);
       }

       /**
        * Associates an internal procedure with an ABL callback event.
        *
        * @param   eventName
        *          The eventName of the event.
        * @param   callback
        *          The eventName of the internal procedure associated with the callback event.
        * @param   context
        *          A handle to a procedure that contains the internal procedure specified by
        *          {@code callback}. If not specified, {@code THIS-PROCEDURE} is used as the procedure
        *          context.
        *
        * @return  {@code true} when the method ends with success.
        */
       @Override
       public logical setCallbackProcedure(String eventName, String callback, handle context)
       {
          if (!isCallbackName(eventName))
          {
             return logical.of(false);
          }

          if (callback == null)
          {
             return logical.of(true);
          }

          if (callback.isEmpty())
          {
             if (callbacks != null)
             {
                callbacks.remove(eventName.toUpperCase());
             }
             return logical.of(true);
          }

          if (context == null || context.isUnknown())
          {
             // defaulting to THIS-PROCEDURE
             context = pm.thisProcedure();
          }
          else
          {
             if (!context._isValid() || !(context.getResource() instanceof PersistentProcedure))
             {
                ErrorManager.recordOrShowError(13273);
                // Third argument to SET-CALLBACK must be a valid procedure handle or object reference.
                return logical.of(false);
             }
          }

          InvokeConfig callSite = getCallSite(eventName);
          if (callbacks == null)
          {
             callbacks = new HashMap<>();
          }
          callbacks.put(eventName.toUpperCase(), new CallbackData(callback, context, null, callSite));
          return logical.of(true);
       }

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

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

          nsUri = uri;
       }

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

       /**
        * Obtain the name of the XML element or attribute representing the target object (a
        * {@code DataSet}) name in an XML document. If not set the {@code name} is returned. If the
        * {@code name} is empty {@code "ProDataSet"} is returned.
        *
        * @return  the name of the XML element for this object.
        */
       @Override
       public character getXmlNodeName()
       {
          return new character(getXmlNodeNameInternal());
       }

       /**
        * Obtain the name of the XML element or attribute representing the target object. May return
        * {@code null}, unlike the {@code getXmlNodeName()} attribute implementation.
        *
        * @return  the name of the XML element for this object.
        */
       public String getXmlNodeNameInternal()
       {
          return xmlNodeName != null ? xmlNodeName :
                 serializeName != null ? serializeName :
                 !name.isEmpty() ? name : "ProDataSet";
       }

       /**
        * Returns the value of the the NAMESPACE-URI attribute as String.
        *
        * @return See above.
        */
       public String getNsUri()
       {
          return nsUri == null ? "" : nsUri;
       }
       /**
        * Sets the name of the XML element or attribute representing the target object (either a
        * {@code DataSet}, a {@code TempTable}, a {@code TemporaryBuffer}, or a temp-table
        * {@code BufferField}) name in an XML document.
        *
        * @param   name
        *          The new name of the XML element for this object.
        */
       @Override
       public void setXmlNodeName(String name)
       {
          if (name != null)
          {
             xmlNodeName = name;
          }
          // no error is raised otherwise
       }

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

       /**
        * 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()
       {
          String jsName = serializeName != null ? serializeName : name;
          if (jsName == null || jsName.isEmpty())
          {
             jsName = "ProDataSet";
          }

          return new character(jsName);
       }

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

       /**
        * Implementation of the write access of the {@code SERIALIZE-NAME} attribute.
        *
        * @param sName the new value for the {@code SERIALIZE-NAME} attribute.
        */
       @Override
       public void setSerializeName(String sName)
       {
          if (sName != null)
          {
             serializeName = sName;
          }
          // no error is raised otherwise
       }

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

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

       /**
        * Setter for ERROR attribute.
        *
        * @param value The new value of ERROR attribute.
        */
       @Override
       public void error(logical value)
       {
          if (value == null || value.isUnknown())
          {
             unableToAssignUnknown("ERROR", "DATASET widget");
             return;
          }
          error(value.getValue());
       }

       /**
        * 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)
       {
          this.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", "DATASET widget");
             return;
          }
          rejected(value.getValue());
       }

       /**
        * Reports if this object is valid for use. The {@code DataSet} is valid
        *
        * @return {@code true} if we are valid (can be used).
        */
       @Override
       public boolean valid()
       {
          return !buildInProgress && !deleted;
       }

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

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

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

       /**
        * Empties this {@code DataSet} object of all records in its associated temp-tables.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical emptyDataset()
       {
          // TODO: When you invoke this method, the ABL Virtual Machine (AVM) sets the ORIGIN-HANDLE attribute on
          //       any changed temp-tables corresponding to the source temp-tables of this DataSet to the Unknown
          //       value (?).
          //       If TRACKING-CHANGES is true, the AVM raises an error.

          for (int i = 0; i < buffers.size(); i++)
          {
             BufferImpl buffer = buffers.get(i);
             buffer.deleteAll();
          }

          error(false); // clear the ERROR attribute in the process
          return logical.of(true);
       }

       /**
        * Gets the handle of the specified data-relation object.
        *
        * @return  the handle of the specified data-relation object.
        */
       @Override
       public handle getRelation()
       {
          return getRelation(1);
       }

       /**
        * Gets the handle of the specified data-relation object.
        *
        * @return  the handle of the specified data-relation object.
        */
       public DataRelation getRelationNative()
       {
          return getRelationNative(1);
       }

       /**
        * Gets the handle of the specified data-relation object.
        *
        * @param   index
        *          The index (1-based) of the data-relation to be returned.
        *
        * @return  the handle of the specified data-relation object.
        */
       @Override
       public handle getRelation(NumberType index)
       {
          if (index == null || index.isUnknown())
          {
             return new handle();
          }

          return getRelation(index.longValue());
       }

       /**
        * Gets the handle of the specified data-relation object.
        *
        * @param   index
        *          The index (1-based) of the data-relation to be returned.
        *
        * @return  the handle of the specified data-relation object.
        */
       @Override
       public handle getRelation(long index)
       {
          return new handle(getRelationNative(index));
       }

       /**
        * Gets the handle of the specified data-relation object.
        *
        * @param   index
        *          The index (1-based) of the data-relation to be returned.
        *
        * @return  the handle of the specified data-relation object.
        */
       public DataRelation getRelationNative(long index)
       {
          if (relations.isEmpty())
          {
             return null;
          }

          return index - 1 >= relations.size() ? null : relations.get((int) index - 1);
       }

       /**
        * Get relations as stream.
        *
        * @return  relations as stream
        */
       public java.util.stream.Stream<DataRelation> getRelations()
       {
          return relations.stream();
       }

       /**
        * Gets the handle of the specified data-relation object.
        *
        * @param   name
        *          The name of the data-relation to be returned.
        *
        * @return  the handle of the specified data-relation object.
        */
       @Override
       public handle getRelation(Text name)
       {
          if (name == null || name.isUnknown())
          {
             return new handle();
          }

          return getRelation(name.getValue());
       }

       /**
        * Gets the handle of the specified data-relation object.
        *
        * @param   name
        *          The name of the data-relation to be returned.
        *
        * @return  the handle of the specified data-relation object.
        */
       @Override
       public handle getRelation(String name)
       {
          for (int i = 0; i < relations.size(); i++)
          {
             DataRelation relation = relations.get(i);
             if (relation.name().toStringMessage().equalsIgnoreCase(name))
             {
                return new handle(relation);
             }
          }
          return new handle();
       }

       /**
        * Gets the top-level buffer of this {@code DataSet} object at the specified index position.
        *
        * @return  the top-level buffer at the position 1.
        */
       @Override
       public handle getTopBuffer()
       {
          return getTopBuffer(1);
       }

       /**
        * Gets the top-level buffer of this {@code DataSet} object at the specified index position.
        *
        * @param   index
        *          The 1-based index of the top-level buffer.
        *
        * @return  the top-level buffer at the specified index position.
        */
       @Override
       public handle getTopBuffer(long index)
       {
          return new handle(getTopBufferNative(index));
       }

       /**
        * Gets the top-level buffer of this {@code DataSet} object at the specified index position.
        *
        * @param   index
        *          The 1-based index of the top-level buffer.
        *
        * @return  the top-level buffer at the specified index position.
        */
       public BufferImpl getTopBufferNative(long index)
       {
          if (index < 1)
          {
             return null;
          }

          List<Buffer> candidates = getTopBufferList();
          if (_getNumTopBuffers() != candidates.size())
          {
             // well, this should NEVER happen!
             throw new RuntimeException("The size of TOP-BUFFER list does not match NUM-TOP-BUFFER");
          }

          return index - 1 >= candidates.size() ? null : (BufferImpl) candidates.get((int) (index - 1));
       }

       /**
        * Gets the top-level buffer of this {@code DataSet} object at the specified index position.
        *
        * @param   index
        *          The 1-based index of the top-level buffer.
        *
        * @return  the top-level buffer at the specified index position.
        */
       @Override
       public handle getTopBuffer(NumberType index)
       {
          if (index == null || index.isUnknown())
          {
             return new handle();
          }
          return getTopBuffer(index.longValue());
       }

       /**
        * Obtain default navigation query for a top-level buffer of this {@code DataSet} object.
        * This method correspond to the getter side of {@code TOP-NAV-QUERY} ABL attribute.
        *
        * @return  the default navigation query for a top-level buffer.
        */
       @Override
       public handle getTopNavQuery()
       {
          return getTopNavQuery(1);
       }

       /**
        * Obtain default navigation query for a top-level buffer of this {@code DataSet} object.
        * This method correspond to the getter side of {@code TOP-NAV-QUERY} ABL attribute.
        *
        * @param   index
        *          The 1-based index of the top-level buffer.
        *
        * @return  the default navigation query for a top-level buffer.
        */
       @Override
       public handle getTopNavQuery(long index)
       {
          if (index < 0 || index > _getNumTopBuffers())
          {
             return new handle();
          }

          BufferImpl topBuffer = getTopBufferNative(index);
          if (topBuffer == null)
          {
             return new handle();
          }
          return getTopNavQuery(topBuffer);
       }

       /**
        * Obtain default navigation query for a top-level buffer of this {@code DataSet} object.
        * This method correspond to the getter side of {@code TOP-NAV-QUERY} ABL attribute.
        *
        * @param   index
        *          The 1-based index of the top-level buffer.
        *
        * @return  the default navigation query for a top-level buffer.
        */
       @Override
       public handle getTopNavQuery(NumberType index)
       {
          if (index == null || index.isUnknown())
          {
             return new handle();
          }

          return getTopBuffer(index.intValue());
       }

       /**
        * Obtain default navigation query for a top-level buffer of this {@code DataSet} object.
        * This method correspond to the getter side of {@code TOP-NAV-QUERY} ABL attribute.
        *
        * @param   bufName
        *          The name of the top-level buffer.
        *
        * @return  the default navigation query for a top-level buffer.
        */
       @Override
       public handle getTopNavQuery(String bufName)
       {
          return getTopNavQuery(getBufferByName(bufName));
       }

       /**
        * Obtain default navigation query for a top-level buffer of this {@code DataSet} object.
        * This method correspond to the getter side of {@code TOP-NAV-QUERY} ABL attribute.
        *
        * @param   bufName
        *          The name of the top-level buffer.
        *
        * @return  the default navigation query for a top-level buffer.
        */
       @Override
       public handle getTopNavQuery(Text bufName)
       {
          if (bufName == null || bufName.isUnknown())
          {
             return new handle();
          }

          return getTopNavQuery(bufName.getValue());
       }

       /**
        * Configures the default navigation query for a top-level buffer of this {@code DataSet}
        * object. This method correspond to the setter side of {@code TOP-NAV-QUERY} ABL attribute.
        *
        * @param   queryHandle
        *          The new default navigation query for a top-level buffer.
        */
       @Override
       public void setTopNavQuery(handle queryHandle)
       {
          setTopNavQuery(1, queryHandle);
       }

       /**
        * Configures the default navigation query for a top-level buffer of this {@code DataSet}
        * object. This method correspond to the setter side of {@code TOP-NAV-QUERY} ABL attribute.
        *
        * @param   index
        *          The 1-based index of the top-level buffer.
        * @param   queryHandle
        *          The new default navigation query for a top-level buffer.
        */
       @Override
       public void setTopNavQuery(long index, handle queryHandle)
       {
          if (index < 1 || index > _getNumTopBuffers())
          {
             invalidTopNavQuery(4056, "");
             return;
          }

          if (queryHandle.getResource() == null)
          {
             // silently accept invalid values
             return;
          }

          BufferImpl topBuffer = getTopBufferNative(index);
          _setTopBufferQuery(queryHandle, topBuffer);
       }

       /**
        * Configures the default navigation query for a top-level buffer of this {@code DataSet}
        * object. This method correspond to the setter side of {@code TOP-NAV-QUERY} ABL attribute.
        *
        * @param   index
        *          The 1-based index of the top-level buffer.
        * @param   queryHandle
        *          The new default navigation query for a top-level buffer.
        */
       @Override
       public void setTopNavQuery(NumberType index, handle queryHandle)
       {
          setTopNavQuery((index == null || index.isUnknown()) ? -1 : index.longValue(), queryHandle);
       }

       /**
        * Configures the default navigation query for a top-level buffer of this {@code DataSet}
        * object. This method correspond to the setter side of {@code TOP-NAV-QUERY} ABL attribute.
        *
        * @param   bufName
        *          The name of the top-level buffer.
        * @param   queryHandle
        *          The new default navigation query for a top-level buffer.
        */
       @Override
       public void setTopNavQuery(String bufName, handle queryHandle)
       {
          if (queryHandle.getResource() == null)
          {
             // silently accept invalid values
             return;
          }

          _setTopBufferQuery(queryHandle, getBufferByName(bufName));
       }

       /**
        * Configures the default navigation query for a top-level buffer of this {@code DataSet}
        * object. This method correspond to the setter side of {@code TOP-NAV-QUERY} ABL attribute.
        *
        * @param   bufName
        *          The name of the top-level buffer.
        * @param   queryHandle
        *          The new default navigation query for a top-level buffer.
        */
       @Override
       public void setTopNavQuery(Text bufName, handle queryHandle)
       {
          setTopNavQuery(bufName.toJavaType(), queryHandle);
       }

       /**
        * Checks whether <strong>AT LEAST</strong> one data-relation objects in this {@code DataSet} object is
        * active. All data-relation objects in a {@code DataSet} object are active by default.
        *
        * @return  {@code true} when all data-relation objects are active.
        */
       @Override
       public logical areRelationsActive()
       {
          return logical.of(_areRelationsActive());
       }

       /**
        * Configures whether all data-relation objects in this {@code DataSet} object to be active or
        * inactive. Setting this attribute to {@code TRUE} will activate all data-relation objects.
        * Setting it to {@code FALSE} will deactivate all data-relation objects instead.
        *
        * @param   active
        *          The new value for {@code RELATIONS-ACTIVE} attribute.
        */
       @Override
       public void setRelationsActive(logical active)
       {
          if (active.isUnknown())
          {
             unableToAssignUnknown("RELATIONS-ACTIVE", "DATASET widget");
             return;
          }

          setRelationsActive(active.getValue());
       }

       /**
        * Configures whether all data-relation objects in this {@code DataSet} object to be active or
        * inactive. Setting this attribute to {@code TRUE} will activate all data-relation objects.
        * Setting it to {@code FALSE} will deactivate all data-relation objects instead.
        *
        * @param   active
        *          The new value for {@code RELATIONS-ACTIVE} attribute.
        */
       @Override
       public void setRelationsActive(boolean active)
       {
          if (_areRelationsActive() == active)
          {
             // 'nothing' to do.
             // NOTE: this is a bad assumption but is how ABL works!
             return;
          }

          if (relations.isEmpty())
          {
             defaultActive = active;
             return;
          }

          // check first allowsActiveChild() for all inactive relations
          if (active)
          {
             List<Integer> childBuffers = new LinkedList<>();
             for (int i = 0; i < relations.size(); i++)
             {
                DataRelation rel = relations.get(i);
                Buffer cb = rel.getChildBufferNative();
                if (cb == null)
                {
                   continue;
                }
                int uuid = cb.getUniqueID().intValue();
                if (childBuffers.contains(uuid))
                {
                   ErrorManager.recordOrShowError(11979, cb.name().toStringMessage());
                   // Buffer <name> can have only 1 active parent relation.
                   return;
                }

                childBuffers.add(uuid);
             }
          }

          for (int i = 0; i < relations.size(); i++)
          {
             DataRelation rel = relations.get(i);
             rel.setActive(active);
          }
       }

       /**
        * Get the number of data-relation objects of this {@code DataSet} object.
        *
        * @return  the number of data-relation objects of this {@code DataSet} object.
        */
       @Override
       public integer getNumRelations()
       {
          return new integer(relations.size());
       }

       /**
        * Obtain the number of top-level buffers in this {@code DataSet} object.
        *
        * @return  the number of top-level buffers in this {@code DataSet} object.
        */
       @Override
       public integer getNumTopBuffers()
       {
          return new integer(_getNumTopBuffers());
       }

       /**
        * Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
        * object. This method applies only to {@code DataSet} objects dynamically created.
        *
        * @param   parent
        *          The handle to the parent buffer in the data-relation object.
        * @param   child
        *          The handle to the child buffer in the data-relation object.
        * @param   pairs
        *          A comma-delimited list of parent-field, child-field pairs describing the relationship between
        *          parent and child buffers in the new data-relation object.
        *
        * @return  A {@code handle} to the newly created {@code DataRelation}.
        */
       @Override
       public handle addRelation(handle parent, handle child, Text pairs)
       {
          return addRelationImpl(parent, child, pairs, null, null, null, null, null, false);
       }

       /**
        * Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
        * object. This method applies only to {@code DataSet} objects dynamically created.
        *
        * @param   parent
        *          The handle to the parent buffer in the data-relation object.
        * @param   child
        *          The handle to the child buffer in the data-relation object.
        * @param   pairs
        *          A comma-delimited list of parent-field, child-field pairs describing the relationship between
        *          parent and child buffers in the new data-relation object.
        * @param   reposition
        *          The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
        *          Otherwise the relation mode is {@code SELECTION}. The default value is {@code SELECTION}.
        *
        * @return  A {@code handle} to the newly created {@code DataRelation}.
        */
       @Override
       public handle addRelation(handle parent, handle child, Text pairs, logical reposition)
       {
          return addRelationImpl(parent, child, pairs, reposition, null, null, null, null, false);
       }

       /**
        * Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
        * object. This method applies only to {@code DataSet} objects dynamically created.
        *
        * @param   parent
        *          The handle to the parent buffer in the data-relation object.
        * @param   child
        *          The handle to the child buffer in the data-relation object.
        * @param   pairs
        *          A comma-delimited list of parent-field, child-field pairs describing the relationship between
        *          parent and child buffers in the new data-relation object.
        * @param   reposition
        *          The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
        *          Otherwise the relation mode is {@code SELECTION}. The default value is {@code SELECTION}.
        * @param   nested
        *          Use {@code TRUE} to nest child rows of {@code DataSet} buffers within their parent
        *          rows when writing the XML representation of data. When {@code FALSE} is passed, all
        *          child rows are written after all parent rows are written. The default value is {@code FALSE}.
        *
        * @return  A {@code handle} to the newly created {@code DataRelation}.
        */
       @Override
       public handle addRelation(handle parent,
                                 handle child,
                                 Text pairs,
                                 logical reposition,
                                 logical nested)
       {
          return addRelationImpl(parent, child, pairs, reposition, nested, null, null, null, false);
       }

       /**
        * Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
        * object. This method applies only to {@code DataSet} objects dynamically created.
        *
        * @param   parent
        *          The handle to the parent buffer in the data-relation object.
        * @param   child
        *          The handle to the child buffer in the data-relation object.
        * @param   pairs
        *          A comma-delimited list of parent-field, child-field pairs describing the relationship between
        *          parent and child buffers in the new data-relation object.
        * @param   reposition
        *          The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
        *          Otherwise the relation mode is {@code SELECTION}. The default value is {@code SELECTION}.
        * @param   nested
        *          Use {@code TRUE} to nest child rows of {@code DataSet} buffers within their parent
        *          rows when writing the XML representation of data. When {@code FALSE} is passed, all
        *          child rows are written after all parent rows are written. The default value is {@code FALSE}.
        * @param   active
        *          Use {@code false} to make the newly created relation inactive.
        *
        * @return  A {@code handle} to the newly created {@code DataRelation}.
        */
       @Override
       public handle addRelation(handle parent,
                                 handle child,
                                 Text pairs,
                                 logical reposition,
                                 logical nested,
                                 logical active)
       {
          return addRelationImpl(parent, child, pairs, reposition, nested, active, null, null, false);
       }

       /**
        * Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
        * object. This method applies only to {@code DataSet} objects dynamically created.
        *
        * @param   parent
        *          The handle to the parent buffer in the data-relation object.
        * @param   child
        *          The handle to the child buffer in the data-relation object.
        * @param   pairs
        *          A comma-delimited list of parent-field, child-field pairs describing the relationship between
        *          parent and child buffers in the new data-relation object.
        * @param   reposition
        *          The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
        *          Otherwise the relation mode is {@code SELECTION}. The default value is {@code SELECTION}.
        * @param   nested
        *          Use {@code TRUE} to nest child rows of {@code DataSet} buffers within their parent
        *          rows when writing the XML representation of data. When {@code FALSE} is passed, all
        *          child rows are written after all parent rows are written. The default value is {@code FALSE}.
        * @param   active
        *          Use {@code false} to make the newly created relation inactive.
        * @param   recursive
        *          Using {@code TRUE} till causes the data-relation to be filled recursively.
        *
        * @return  A {@code handle} to the newly created {@code DataRelation}.
        */
       @Override
       public handle addRelation(handle parent,
                                 handle child,
                                 Text pairs,
                                 logical reposition,
                                 logical nested,
                                 logical active,
                                 logical recursive)
       {
          return addRelationImpl(parent, child, pairs, reposition, nested, active, recursive, null, false);
       }

       /**
        * Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
        * object. This method applies only to {@code DataSet} objects dynamically created.
        * <p>
        * <b>Note:</b><br>
        *    There is a bug/quirk in OE when this is performed. If a relation (for same buffer pair) is added a
        *    second time, it is accepted, leading to two "parallel" relations to be created. FWD will warn this in
        *    log. When/if OE decides to fix the bug/quirk, the handling code should also be removed.
        *
        * @param   parent
        *          The handle to the parent buffer in the data-relation object.
        * @param   child
        *          The handle to the child buffer in the data-relation object.
        * @param   pairs
        *          A comma-delimited list of parent-field, child-field pairs describing the relationship between
        *          parent and child buffers in the new data-relation object.
        * @param   reposition
        *          The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
        *          Otherwise the relation mode is {@code SELECTION}. The default value is {@code SELECTION}.
        * @param   nested
        *          Use {@code TRUE} to nest child rows of {@code DataSet} buffers within their parent
        *          rows when writing the XML representation of data. When {@code FALSE} is passed, all
        *          child rows are written after all parent rows are written. The default value is {@code FALSE}.
        * @param   active
        *          Use {@code false} to make the newly created relation inactive.
        * @param   recursive
        *          Using {@code TRUE} till causes the data-relation to be filled recursively.
        * @param   fkeyHidden
        *          When set to {@code TRUE} the foreign key fields in nested data-relations will be omitted when
        *          writing the XML representation of data. If this option is used, the {@code NESTED} argument
        *          must be {@code TRUE}.
        *
        * @return  A {@code handle} to the newly created {@code DataRelation}.
        */
       @Override
       public handle addRelation(handle parent,
                                 handle child,
                                 Text pairs,
                                 logical reposition,
                                 logical nested,
                                 logical active,
                                 logical recursive,
                                 logical fkeyHidden)
       {
          return addRelationImpl(parent, child, pairs, reposition, nested, active, recursive, fkeyHidden, false);
       }

       /**
        * Actual implementation of {@code addRelation}.<p>
        *
        * Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet}
        * object. This method applies only to {@code DataSet} objects dynamically created.
        * <p>
        * <b>Note:</b><br>
        *    There is a bug/quirk in OE when this is performed. If a relation (for same buffer pair) is added a
        *    second time, it is accepted, leading to two "parallel" relations to be created. FWD will warn this in
        *    log. When/if OE decides to fix the bug/quirk, the handling code should also be removed.
        *
        * @param   parent
        *          The handle to the parent buffer in the data-relation object.
        * @param   child
        *          The handle to the child buffer in the data-relation object.
        * @param   pairs
        *          A comma-delimited list of parent-field, child-field pairs describing the relationship between
        *          parent and child buffers in the new data-relation object.
        * @param   reposition
        *          The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
        *          Otherwise the relation mode is {@code SELECTION}. The default value is {@code SELECTION}.
        * @param   nested
        *          Use {@code TRUE} to nest child rows of {@code DataSet} buffers within their parent
        *          rows when writing the XML representation of data. When {@code FALSE} is passed, all
        *          child rows are written after all parent rows are written. The default value is {@code FALSE}.
        * @param   active
        *          Use {@code false} to make the newly created relation inactive.
        * @param   recursive
        *          Using {@code TRUE} till causes the data-relation to be filled recursively.
        * @param   fkeyHidden
        *          When set to {@code TRUE} the foreign key fields in nested data-relations will be omitted when
        *          writing the XML representation of data. If this option is used, the {@code NESTED} argument
        *          must be {@code TRUE}.
        * @param   nullOnDuplicate
        *          if {@code true} the method will return {@code null} if a duplicate relation is detected.
        *          Otherwise it will throw error condition #11979, eventually returning invalid handle.
        *
        * @return  A {@code handle} to the newly created {@code DataRelation}.
        */
       public handle addRelationImpl(handle parent,
                                     handle child,
                                     Text pairs,
                                     logical reposition,
                                     logical nested,
                                     logical active,
                                     logical recursive,
                                     logical fkeyHidden,
                                     boolean nullOnDuplicate)
       {
          if (parent == null || parent.isUnknown() || !parent.isType(LegacyResource.BUFFER) ||
              child == null  || child.isUnknown()  || !child.isType(LegacyResource.BUFFER))
          {
             ErrorManager.recordOrShowError(11955);
             // The argument to Dataset-handle:ADD-RELATION method was invalid.
             return new handle();
          }

          BufferImpl bufParent = ((BufferImpl) parent.getResource()).ref();
          BufferImpl bufChild = ((BufferImpl) child.getResource()).ref();
          boolean throw11979 = false;

          if (!buffers.contains(bufParent) || !buffers.contains(bufChild))
          {
             ErrorManager.recordOrShowError(11961);
             // Buffers in ADD-RELATION must be members of the dataset.
             return new handle();
          }

          boolean bActive = _areRelationsActive();
          if (active != null && !active.isUnknown())
          {
             // if this flag is not specified, use the negated value of RELATIONS-ACTIVE
             bActive = active.getValue();
          }

          // check whether a new relation can be added at this moment for the child buffer. If the new relation is
          // inactive it can be added anyway
          if (bActive)
          {
             DataRelation dr = allowsActiveChild(bufChild);
             if (dr != null)
             {
                //Note: There is a bug/quirk in OE when this is performed. If a relation (for same buffer pair) is
                //      added a second time, it is accepted, leading to two "parallel" relations to be created.
                //      FWD will warn this in * log. When/if OE decides to fix the bug/quirk, this code must be
                //      removed or conditioned on emulated OE version
                BufferImpl existingParent = dr.getParentBufferNative();
                if (existingParent != bufParent)
                {
                   ErrorManager.recordOrShowError(11979, bufChild.doGetName());
                   // Buffer <name> can have only 1 active parent relation.
                   return new handle();
                }
                else
                {
                   if (nullOnDuplicate)
                   {
                      return null;
                   }
                   throw11979 = true;
                }
             }
          }

          if (pairs == null || pairs.isUnknown())
          {
             ErrorManager.recordOrShowError(11964);
             // ADD-RELATION join pairs list was not valid.
             return new handle();
          }

          if (bufParent == bufChild)
          {
             if (!recursive.booleanValue())
             {
                ErrorManager.recordOrShowError(11965);
                // Attempt to relate dataset buffer <name> to parent with itself in ancestry.
                return new handle();
             }
          }

          String strPairs = pairs.getValue();
          String[] fields = strPairs.split(",");
          if (fields.length == 0 || fields.length == 1 && fields[0].isEmpty())
          {
             ErrorManager.recordOrShowError(11964);
             // ADD-RELATION join pairs list was not valid.
             return new handle();
          }
          else if (fields.length % 2 == 1)
          {
             ErrorManager.recordOrShowError(11962);
             // Odd number of join pairs in ADD-RELATON.
             return new handle();
          }

          for (int i = 0; i < fields.length; i += 2)
          {
             // make sure the legacy names are in lowercase (BUT why?)
             String fParent = fields[i];
             String fChild = fields[i + 1];
             String errfld = null;

             if (!bufParent.hasField(fParent))
             {
                errfld = fParent;
             }
             else if (!bufChild.hasField(fChild))
             {
                errfld = fChild;
             }

             if (errfld != null)
             {
                ErrorManager.recordOrShowError(11963, errfld);
                // ADD-RELATION join pairs list entry <name> not valid--list must have unqualified
                // field names with no embedded blanks, in parent-fld,child-fld order, e.g. 'cust-num,cust-num'.
                return new handle();
             }
          }

          boolean bRepos = reposition != null && !reposition.isUnknown() && reposition.getValue();
          boolean bNested = nested != null && !nested.isUnknown() && nested.getValue();
          boolean bRecurs = recursive != null && !recursive.isUnknown() && recursive.getValue();
          boolean bfkHidden = fkeyHidden != null && !fkeyHidden.isUnknown() && fkeyHidden.getValue();

          handle ret = addRelationWorker(null, bufParent, bufChild, fields, strPairs,
                                         bRepos, bNested, bActive, bRecurs, bfkHidden, true);

          if (throw11979)
          {
             ErrorManager.recordOrShowError(11979, bufChild.doGetName());
             // Buffer <name> can have only 1 active parent relation.
             return new handle();
          }

          return ret;
       }

       /**
        * Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet} based
        * on the {@code RECID} of the parent buffer to this {@code DataSet} object. This method applies only to
        * {@code DataSet} objects dynamically created. The newly created object will have the
        * {@code PARENT-ID-RELATION} attribute set to {@code TRUE}.
        *
        * @param   parent
        *          The handle to the parent buffer in the data-relation object.
        * @param   child
        *          The handle to the child buffer in the data-relation object.
        *
        * @return  A handle to the newly created {@link DataRelation} object.
        */
       @Override
       public handle addParentIdRelation(handle parent, handle child)
       {
          return addParentIdRelation(parent, child, null, null, null);
       }

       /**
        * Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet} based
        * on the {@code RECID} of the parent buffer to this {@code DataSet} object. This method applies only to
        * {@code DataSet} objects dynamically created. The newly created object will have the
        * {@code PARENT-ID-RELATION} attribute set to {@code TRUE}.
        *
        * @param   parent
        *          The handle to the parent buffer in the data-relation object.
        * @param   child
        *          The handle to the child buffer in the data-relation object.
        * @param   parentIdField
        *          Denotes the {@code RECID} field in child-buffer-handle on which this
        *          parent-id-relation object is based.
        *
        * @return  A handle to the newly created {@link DataRelation} object.
        */
       @Override
       public handle addParentIdRelation(handle parent, handle child, Text parentIdField)
       {
          return addParentIdRelation(parent, child, parentIdField, null, null);
       }

       /**
        * Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet} based
        * on the {@code RECID} of the parent buffer to this {@code DataSet} object. This method applies only to
        * {@code DataSet} objects dynamically created. The newly created object will have the
        * {@code PARENT-ID-RELATION} attribute set to {@code TRUE}.
        *
        * @param   parent
        *          The handle to the parent buffer in the data-relation object.
        * @param   child
        *          The handle to the child buffer in the data-relation object.
        * @param   parentIdField
        *          Denotes the {@code RECID} field in child-buffer-handle on which this
        *          parent-id-relation object is based.
        * @param   parentFieldsBefore
        *          A list of comma-separated fields from the parent record to be written to XML before
        *          any nested child-buffer-handle records.
        *
        * @return  A handle to the newly created {@link DataRelation} object.
        */
       @Override
       public handle addParentIdRelation(handle parent,
                                         handle child,
                                         Text parentIdField,
                                         Text parentFieldsBefore)
       {
          return addParentIdRelation(parent, child, parentIdField, parentFieldsBefore, null);
       }

       /**
        * Creates a new data-relation object for a pair of parent and child buffers of this {@code DataSet} based
        * on the {@code RECID} of the parent buffer to this {@code DataSet} object. This method applies only to
        * {@code DataSet} objects dynamically created. The newly created object will have the
        * {@code PARENT-ID-RELATION} attribute set to {@code TRUE}.
        *
        * @param   parent
        *          The handle to the parent buffer in the data-relation object.
        * @param   child
        *          The handle to the child buffer in the data-relation object.
        * @param   parentIdField
        *          Denotes the {@code RECID} field in child-buffer-handle on which this
        *          parent-id-relation object is based. The default value is the name of the
        *          parent-buffer-handle appended with {@code _id}.
        * @param   parentFieldsBefore
        *          A list of comma-separated fields from the parent record to be written to XML before
        *          any nested child-buffer-handle records.
        * @param   parentFieldsAfter
        *          A list of comma-separated fields from the parent record to be written to XML after
        *          all nested child-buffer-handle records.
        *
        * @return  A handle to the newly created {@link DataRelation} object.
        */
       @Override
       public handle addParentIdRelation(handle parent,
                                         handle child,
                                         Text parentIdField,
                                         Text parentFieldsBefore,
                                         Text parentFieldsAfter)
       {
          // validate buffers:
          if (parent == null || parent.isUnknown() || !parent.isType(LegacyResource.BUFFER) ||
              child == null  || child.isUnknown()  || !child.isType(LegacyResource.BUFFER))
          {
             ErrorManager.recordOrShowError(16025);
             // The argument to Dataset-handle:ADD-PARENT-ID-RELATION method was invalid.
             return new handle();
          }

          // validate link to parent:
          BufferImpl bufParent = ((BufferImpl) parent.getResource()).ref();
          BufferImpl bufChild = ((BufferImpl) child.getResource()).ref();

          if (!buffers.contains(bufParent) || !buffers.contains(bufChild))
          {
             ErrorManager.recordOrShowError(16027);
             // Buffers in ADD-PARENT-ID-RELATION must be members of the dataset.
             return new handle();
          }

    //      if (!allowsActiveChild(bufChild))
    //      {
    //         return new handle();
    //      }

          String parLink = null;
          if (parentIdField == null || parentIdField.isUnknown() || parentIdField.toJavaType().isEmpty())
          {
             parLink = bufParent._name() + "_id";
          }
          else
          {
             parLink = parentIdField.getValue();
          }

          // check if the field exist in the child buffer
          if (!bufChild.hasField(parLink))
          {
             ErrorManager.recordOrShowError(16028, parLink);
             // ADD-PARENT-ID-RELATION parent-id-field <field> is not valid.
             return new handle();
          }
          else
          {
             // and, if so, it is of the proper type (RECID)
             BufferField fparent = bufChild.bufferFieldNative(parLink);
             if (fparent == null || !"RECID".equalsIgnoreCase(fparent.getDataType().getValue()))
             {
                ErrorManager.recordOrShowError(16028, parLink);
                // ADD-PARENT-ID-RELATION parent-id-field <field> is not valid.
                return new handle();
             }
          }

          // validate before fields
          List<String> beforeFields = new ArrayList<>();
          if (parentFieldsBefore != null && !parentFieldsBefore.isUnknown())
          {
             String[] beforeStrFields = parentFieldsBefore.getValue().split(",");
             for (String field : beforeStrFields)
             {
                if (!bufParent.hasField(field))
                {
                   ErrorManager.recordOrShowError(16029, field);
                   // ADD-PARENT-ID-RELATION parent-fields-before entry <field> is not valid.
                   return new handle();
                }
                beforeFields.add(field);
             }
          }

          List<String> afterFields = new ArrayList<>();
          // validate after fields
          if (parentFieldsAfter!= null && !parentFieldsAfter.isUnknown())
          {
             String[] afterStrFields = parentFieldsAfter.getValue().split(",");
             for (String field : afterStrFields)
             {
                if (!bufParent.hasField(field))
                {
                   ErrorManager.recordOrShowError(16030, field);
                   // ADD-PARENT-ID-RELATION parent-fields-after entry <field> is not valid.
                   return new handle();
                }

                if (beforeFields.contains(field))
                {
                   ErrorManager.recordOrShowError(16484, field);
                   // Duplicate field <field> found in PARENT-FIELDS-BEFORE / PARENT-FIELDS-AFTER.
                   return new handle();
                }

                afterFields.add(field);
             }
          }

          DataRelation dR = new DataRelation(this, bufParent, bufChild, parLink, beforeFields, afterFields, true);
          // the REPOSITION attribute might be set, depending on the OE version
          // dR.setReposition(true);
          dR.name("RELATION" + (relations.size() + 1));
          relations.add(dR);
          return new handle(dR);
       }

       /**
        * Copies a source {@code DataSet} object into this {@code DataSet} object.
        *
        * @param   srcDS
        *          The source {@code DataSet}.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical copyDataset(handle srcDS)
       {
          return copyDatasetImpl(srcDS, false, false, false, null, false, "cpy");
       }

       /**
        * Copies a source {@code DataSet} object into this {@code DataSet} object.
        *
        * @param   srcDS
        *          The source {@code DataSet}.
        * @param   append
        *          When {@code true} the source temp-tables are appended to current temp-tables.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical copyDataset(handle srcDS, logical append)
       {
          boolean isAppend = append != null && !append.isUnknown() && append.getValue();
          return copyDatasetImpl(srcDS, isAppend, false, false, null, false, "cpy");
       }

       /**
        * Copies a source {@code DataSet} object into this {@code DataSet} object.
        *
        * @param   srcDS
        *          The source {@code DataSet}.
        * @param   append
        *          When {@code true} the source temp-tables are appended to current temp-tables.
        * @param   replace
        *          When {@code true} the records in this temp-tables are replaced with records from
        *          the temp-tables of source {@code DataSet}.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical copyDataset(handle srcDS, logical append, logical replace)
       {
          boolean isAppend = append != null && !append.isUnknown() && append.getValue();
          boolean isReplace = replace != null && !replace.isUnknown() && replace.getValue();
          return copyDatasetImpl(srcDS, isAppend, isReplace, false, null, false, "cpy");
       }

       /**
        * Copies a source {@code DataSet} object into this {@code DataSet} object.
        *
        * @param   srcDS
        *          The source {@code DataSet}.
        * @param   append
        *          When {@code true} the source temp-tables are appended to current temp-tables.
        * @param   replace
        *          When {@code true} the records in this temp-tables are replaced with records from
        *          the temp-tables of source {@code DataSet}.
        * @param   looseCopy
        *          When {@code TRUE}, each temp-table in the source DataSet object is copied to
        *          this {@code DataSet} object based on a field mapping between the source and target
        *          temp-table buffers.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical copyDataset(handle srcDS, logical append, logical replace, logical looseCopy)
       {
          boolean isAppend = append != null && !append.isUnknown() && append.getValue();
          boolean isReplace = replace != null && !replace.isUnknown() && replace.getValue();
          boolean isLoose = looseCopy != null && !looseCopy.isUnknown() && looseCopy.getValue();
          return copyDatasetImpl(srcDS, isAppend, isReplace, isLoose, null, false, "cpy");
       }

       /**
        * Copies a source {@code DataSet} object into this {@code DataSet} object.
        *
        * @param   srcDS
        *          The source {@code DataSet}.
        * @param   append
        *          When {@code true} the source temp-tables are appended to current temp-tables.
        * @param   replace
        *          When {@code true} the records in this temp-tables are replaced with records from
        *          the temp-tables of source {@code DataSet}.
        * @param   looseCopy
        *          When {@code TRUE}, each temp-table in the source DataSet object is copied to
        *          this {@code DataSet} object based on a field mapping between the source and target
        *          temp-table buffers.
        * @param   pairList
        *          A list of the target and source temp-table PAIRS to be copied, comma-delimited.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical copyDataset(handle srcDS,
                                  logical append,
                                  logical replace,
                                  logical looseCopy,
                                  Text pairList)
       {
          boolean isAppend = append != null && !append.isUnknown() && append.getValue();
          boolean isReplace = replace != null && !replace.isUnknown() && replace.getValue();
          boolean isLoose = looseCopy != null && !looseCopy.isUnknown() && looseCopy.getValue();
          return copyDatasetImpl(
                srcDS, isAppend, isReplace, isLoose, pairList.toJavaType(), false, "cpy");
       }

       /**
        * Copies a source {@code DataSet} object into this {@code DataSet} object.
        *
        * @param   srcDS
        *          The source {@code DataSet}.
        * @param   append
        *          When {@code true} the source temp-tables are appended to current temp-tables.
        * @param   replace
        *          When {@code true} the records in this temp-tables are replaced with records from
        *          the temp-tables of source {@code DataSet}.
        * @param   looseCopy
        *          When {@code TRUE}, each temp-table in the source DataSet object is copied to
        *          this {@code DataSet} object based on a field mapping between the source and target
        *          temp-table buffers.
        * @param   pairList
        *          A list of the target and source temp-table PAIRS to be copied, comma-delimited.
        * @param   currentOnly
        *          Use {@code TRUE} indicates that the AVM copy only the current record from each
        *          temp-table at each level in the source {@code DataSet} object to the target
        *          {@code DataSet} object. The default value is {@code FALSE}.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical copyDataset(handle srcDS,
                                  logical append,
                                  logical replace,
                                  logical looseCopy,
                                  Text pairList,
                                  logical currentOnly)
       {
          boolean isAppend = append != null && !append.isUnknown() && append.getValue();
          boolean isReplace = replace != null && !replace.isUnknown() && replace.getValue();
          boolean isLoose = looseCopy != null && !looseCopy.isUnknown() && looseCopy.getValue();
          boolean isCOnly = currentOnly != null && !currentOnly.isUnknown() && currentOnly.getValue();
          return copyDatasetImpl(
                srcDS, isAppend, isReplace, isLoose, pairList.toJavaType(), isCOnly, "cpy");
       }

       /**
        * Copies a source {@code DataSet} object into this {@code DataSet} object.
        *
        * @param   srcDS
        *          The source {@code DataSet}.
        * @param   append
        *          When {@code true} the source temp-tables are appended to current temp-tables.
        * @param   replace
        *          When {@code true} the records in this temp-tables are replaced with records from
        *          the temp-tables of source {@code DataSet}.
        * @param   looseCopy
        *          When {@code TRUE}, each temp-table in the source DataSet object is copied to
        *          this {@code DataSet} object based on a field mapping between the source and target
        *          temp-table buffers.
        * @param   pairList
        *          A list of the target and source temp-table PAIRS to be copied, comma-delimited.
        * @param   currentOnly
        *          Use {@code TRUE} indicates that the AVM copy only the current record from each
        *          temp-table at each level in the source {@code DataSet} object to the target
        *          {@code DataSet} object. The default value is {@code FALSE}.
        * @param   prefix
        *          An expression used as the prefix for (re-)naming the target {@code DataSet}.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical copyDataset(handle srcDS,
                                  logical append,
                                  logical replace,
                                  logical looseCopy,
                                  Text pairList,
                                  logical currentOnly,
                                  Text prefix)
       {
          boolean isAppend = append != null && !append.isUnknown() && append.getValue();
          boolean isReplace = replace != null && !replace.isUnknown() && replace.getValue();
          boolean isLoose = looseCopy != null && !looseCopy.isUnknown() && looseCopy.getValue();
          boolean isCOnly = currentOnly != null && !currentOnly.isUnknown() && currentOnly.getValue();

          return copyDatasetImpl(
                srcDS, isAppend, isReplace, isLoose, pairList.toJavaType(), isCOnly,
                prefix == null || prefix.isUnknown() ? "cpy" : prefix.getValue());
       }

       /**
        * Binds all buffers for a dynamic {@code DataSet} object at the same time. Any buffers
        * previously added to this {@code DataSet} object are removed.
        *
        * @param   hBuffers
        *          Variable argument list of buffer handles to be set. In 4GL, the maximum number of
        *          buffers per query is 18.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical setBuffers(handle... hBuffers)
       {
          if (!isAlterable())
          {
             ErrorManager.recordOrShowError(11858);
             // Cannot add/set buffers on a static dataset.
             return logical.of(false);
          }

          // the list of buffers is cleared even if errors are encountered and method is aborted
          this.buffers.clear();
          this.proxies.clear();

          List<BufferImpl> localBuffers = new ArrayList<>(hBuffers.length);
          for (int i = 0; i < hBuffers.length; i++)
          {
             // references must be valid
             if (hBuffers[i].isUnknown())
             {
                ErrorManager.recordOrShowError(7319, Integer.toString(i + 1));
                // ADD/SET-BUFFERS argument <argnumber> was invalid or not found.
                return logical.of(false);
             }

             if (hBuffers[i].isType(LegacyResource.TEMP_TABLE))
             {
                hBuffers[i] = hBuffers[i].unwrapTempTable().defaultBufferHandle();
             }

             // references must be BUFFER handles (the error number is rather strange!)
             if (!hBuffers[i].isType(LegacyResource.BUFFER))
             {
                ErrorManager.recordOrShowError(11860, Integer.toString(i + 1));
                // Buffer <argnumber> may not appear more than once in any dataset
                return logical.of(false);
             }

             BufferImpl proxy = (BufferImpl) hBuffers[i].getResource();
             BufferImpl buffer = proxy.ref();

             if (!(buffer instanceof Temporary))
             {
                ErrorManager.recordOrShowError(11884, buffer.doGetName());
                // Dataset buffer <name> must be for a temp-table.
                return logical.of(false);
             }

             if (buffer.isBeforeBuffer())
             {
                ErrorManager.recordOrShowError(15008, buffer.doGetName());
                // You may not add before-table buffer <buffer> to a DATASET.
                return logical.of(false);
             }

             // check for duplications:
             DataSet ds = buffer._dataSet();
             if (localBuffers.contains(buffer) || (ds != null) && ds.valid() && ds != this)
             {
                ErrorManager.recordOrShowError(11860, buffer.doGetName());
                // Buffer <name> may not appear more than once in any dataset.
                return logical.of(false);
             }

             localBuffers.add(buffer);
             proxies.put(buffer, proxy);
          }

          // when everything is fine:
          this.buffers.addAll(localBuffers);
          this.buffers.forEach(b -> b.setDataSet(this));

          return logical.of(true);
       }

       /**
        * Binds all buffers for a dynamic {@code DataSet} object at the same time. Any buffers
        * previously added to this {@code DataSet} object are removed.
        *
        * @param   bufs
        *          Variable argument list of buffer handles to be set. In 4GL, the maximum number of
        *          buffers per query is 18.
        *
        * @return  {@code true} on success.
        */
       @Override
       public logical setBuffers(Buffer... bufs)
       {
          return logical.of(setBuffersImpl(bufs));
       }

       /**
        * Binds all buffers for a dynamic {@code DataSet} object at the same time. Any buffers
        * previously added to this {@code DataSet} object are removed.
        *
        * @param   bufs
        *          Variable argument list of Buffer to be set, a mixed list of {@link Buffer},
        *          {@link handle} or {@link character} instances.
        *
        * @return  {@code true} on success
        */
       @Override
       public logical setBuffers(Object... bufs)
       {
          if (!isAlterable())
          {
             ErrorManager.recordOrShowError(11858);
             // Cannot add/set buffers on a static dataset.
             return logical.of(false);
          }

          // the list of buffers is cleared even if errors are encountered and method is aborted
          this.buffers.clear();
          this.proxies.clear();

          BufferManager bm = null;
          List<BufferImpl> localBuffers = new ArrayList<>(bufs.length);
          for (int i = 0; i < bufs.length; i++)
          {
             BufferImpl proxy = null;
             if (bufs[i] instanceof Buffer)
             {
                proxy = (BufferImpl) bufs[i];
             }
             else if (bufs[i] instanceof handle)
             {
                handle asHandle = (handle) bufs[i];
                // references must be valid:
                if (asHandle.isUnknown() || !asHandle.isType(LegacyResource.BUFFER))
                {
                   ErrorManager.recordOrShowError(7319, Integer.toString(i + 1));
                   // ADD/SET-BUFFERS argument <argnumber> was invalid or not found.
                   return logical.of(false);
                }
                proxy = (BufferImpl) asHandle.getResource();
             }
             else if (bufs[i] instanceof character || bufs[i] instanceof String)
             {
                String name = (bufs[i] instanceof String)
                      ? (String) bufs[i] : ((character) bufs[i]).toStringMessage();

                name = TextOps.rightTrimNative(name);

                if (bm == null)
                {
                   bm = BufferManager.get();
                }
                RecordBuffer rb = bm.lookupByName(name);
                if (rb != null)
                {
                   proxy = (BufferImpl) rb.getDMOProxy();
                }
                else
                {
                   ErrorManager.recordOrShowError(7319, Integer.toString(i + 1));
                   // ADD/SET-BUFFERS argument <argnumber> was invalid or not found.
                   return logical.of(false);
                }
             }
             else
             {
                // unknown/invalid data type
                ErrorManager.recordOrShowError(7319, Integer.toString(i + 1));
                // ADD/SET-BUFFERS argument <arg-number> was invalid or not found.
                return logical.of(false);
             }

             // check for duplications:
             BufferImpl crtBuff = proxy.ref();
             if (crtBuff.isBeforeBuffer())
             {
                ErrorManager.recordOrShowError(15008, crtBuff.doGetName());
                // You may not add before-table buffer <buffer> to a DATASET.
                return logical.of(false);
             }

             DataSet ds = crtBuff._dataSet();
             if (localBuffers.contains(crtBuff) || (ds != null) && ds.valid() && ds != this)
             {
                ErrorManager.recordOrShowError(11860, crtBuff.doGetName());
                // Buffer <name> may not appear more than once in any dataset.
                return logical.of(false);
             }

             localBuffers.add(crtBuff);
             proxies.put(crtBuff, proxy);
          }

          // when everything is fine:
          this.buffers.addAll(localBuffers);
          this.buffers.forEach(b -> b.setDataSet(this));

          return logical.of(true);
       }

       /**
        * Adds one new buffer to a query object or dynamic {@code DataSet} object, without affecting
        * the other buffers, if any. Use the SET-BUFFERS( ) method to remove all prior buffers and set
        * all buffers for the object at the same time.
        *
        * @param   bufHandle
        *          A buffer handle to add.
        *
        * @return  true on success
        */
       @Override
       public logical addBuffer(handle bufHandle)
       {
          if (!isAlterable())
          {
             ErrorManager.recordOrShowError(11858);
             // Cannot add/set buffers on a static dataset.
             return logical.of(false);
          }

          if (bufHandle.isUnknown())
          {
             ErrorManager.recordOrShowError(7319, "1");
             // ADD/SET-BUFFERS argument <argnumber> was invalid or not found.
             return logical.of(false);
          }

          WrappedResource res = bufHandle.getResource();
          if (res instanceof TempTable)
          {
             res = ((TempTable) res).defaultBufferHandleNative();
          }

          if (!(res instanceof Buffer))
          {
             // NOTE: trying to add an invalid handle - this is probably OE bug, should throw other error
             ErrorManager.recordOrShowError(11860, bufHandle.unwrap().name().toStringMessage());
             // Buffer <name> may not appear more than once in any dataset. (11860)
             return logical.of(false);
          }

          return addBuffer((Buffer) res);
       }

       /**
        * Adds one new buffer to a query object or dynamic {@code DataSet} object, without affecting
        * the other buffers, if any. Use the SET-BUFFERS( ) method to remove all prior buffers and set
        * all buffers for the object at the same time.
        *
        * @param   buf
        *          A buffer to add.
        *
        * @return  true on success
        */
       @Override
       public logical addBuffer(Buffer buf)
       {
          if (!isAlterable())
          {
             ErrorManager.recordOrShowError(11858);
             // Cannot add/set buffers on a static dataset.
             return logical.of(false);
          }

          if (!(buf instanceof Temporary))
          {
             ErrorManager.recordOrShowError(11884, buf.name().toStringMessage());
             // Dataset buffer <name> must be for a temp-table.
             return logical.of(false);
          }

          BufferImpl proxy = (BufferImpl) buf;
          BufferImpl bufImpl = proxy.ref();
          if (bufImpl.isBeforeBuffer())
          {
             ErrorManager.recordOrShowError(15008, bufImpl.doGetName());
             // You may not add before-table buffer <buffer> to a DATASET.
             return logical.of(false);
          }

          DataSet ds = bufImpl._dataSet();
          if (buffers.contains(bufImpl) || (ds != null) && ds.valid() && ds != this)
          {
             ErrorManager.recordOrShowError(11860, bufImpl.doGetName());
             // Buffer <name> may not appear more than once in any dataset.
             return logical.of(false);
          }

          buffers.add(bufImpl);
          proxies.put(bufImpl, proxy);
          bufImpl.setDataSet(this);

          return logical.of(true);
       }

       /**
        * Adds one new buffer to a query object or dynamic {@code DataSet} object, without affecting
        * the other buffers, if any. Use the SET-BUFFERS( ) method to remove all prior buffers and set
        * all buffers for the object at the same time.
        *
        * @param   bufName
        *          Legacy buffer name to add.
        *
        * @return  true on success
        */
       @Override
       public logical addBuffer(String bufName)
       {
          if (!isAlterable())
          {
             ErrorManager.recordOrShowError(11858);
             // Cannot add/set buffers on a static dataset.
             return logical.of(false);
          }

          bufName = TextOps.rightTrimNative(bufName);
          BufferManager bm = BufferManager.get();
          RecordBuffer rb = bm.lookupByName(bufName);
          if (rb == null)
          {
             ErrorManager.recordOrShowError(7319, Integer.toString(1));
             // ADD/SET-BUFFERS argument <argnumber> was invalid or not found.
             return logical.of(false);
          }

          return addBuffer((Buffer) rb.buffer());
       }

       /**
        * Adds one new buffer to a query object or dynamic {@code DataSet} object, without affecting
        * the other buffers, if any. Use the SET-BUFFERS( ) method to remove all prior buffers and set
        * all buffers for the object at the same time.
        *
        * @param   bufName
        *          Legacy buffer name to add.
        *
        * @return  true on success
        */
       @Override
       public logical addBuffer(character bufName)
       {
          if (!isAlterable())
          {
             ErrorManager.recordOrShowError(11858);
             // Cannot add/set buffers on a static dataset.
             return logical.of(false);
          }

          if (bufName.isUnknown())
          {
             ErrorManager.recordOrShowError(7319, "1");
             // ADD/SET-BUFFERS argument <argnumber> was invalid or not found.
             return logical.of(false);
          }

          return addBuffer(bufName.getValue());
       }

       /**
        * Obtain the number of buffers in this {@code DataSet}.
        *
        * @return  the number of buffers in this {@code DataSet}.
        */
       @Override
       public integer numBuffers()
       {
          return integer.of(buffers.size());
       }

       /**
        * Obtain the handle to a particular buffer of this {@code DataSet}.
        *
        * @return  a handle to a particular buffer of this {@code DataSet}.
        */
       @Override
       public handle bufferHandle()
       {
          return bufferHandle(1);
       }

       /**
        * Obtain the handle to a particular buffer of this {@code DataSet}.
        *
        * @param   bufIndex
        *          The sequence number of the desired buffer.
        *
        * @return  a handle to a particular buffer of this {@code DataSet}.
        */
       @Override
       public handle bufferHandle(int bufIndex)
       {
          return bufferHandle(integer.of(bufIndex));
       }

       /**
        * Obtain the handle to a particular buffer of this {@code DataSet}.
        *
        * @param   bufIndex
        *          The sequence number of the desired buffer.
        *
        * @return  a handle to a particular buffer of this {@code DataSet}.
        */
       @Override
       public handle bufferHandle(int64 bufIndex)
       {
          int idx = bufIndex.isUnknown() ? 0 : bufIndex.intValue();
          return new handle(getBufferByIndex(idx));
       }

       /**
        * Obtain the handle to a particular buffer of this {@code DataSet}.
        *
        * @param   buffName
        *          The name of the desired buffer.
        *
        * @return  a handle to a particular buffer of this {@code DataSet}.
        */
       @Override
       public handle bufferHandle(String buffName)
       {
          return new handle(getBufferByName(buffName));
       }

       /**
        * Obtain the handle to a particular buffer of this {@code DataSet}.
        *
        * @param   buffName
        *          The name of the desired buffer.
        *
        * @return  a handle to a particular buffer of this {@code DataSet}.
        */
       @Override
       public handle bufferHandle(character buffName)
       {
          if (buffName == null || buffName.isUnknown())
          {
             return new handle();
          }

          return new handle(getBufferByName(buffName.getValue()));
       }

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

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

          setSerializeHidden(val.booleanValue());
       }

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

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

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

          this.nsPrefix = 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());
       }

       /**
        * Implementation of the READ-JSON method. Reads JSON data from the specified data source into
        * this object.
        *
        * @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)
       {
          // normalize parameters
          JsonImport jsonImport = new JsonImport(source, readMode.toJavaType());

          try
          {
             // perform import
             return logical.of(jsonImport.readDataset(this));
          }
          catch (PersistenceException exc)
          {
             // TODO: log
             return logical.of(false);
          }
          finally
          {
             // release and flush all buffers
             for (int i = 0; i < buffers.size(); i++)
             {
                buffers.get(i).release();
             }
          }
       }

       /**
        * Implementation of the WRITE-JSON method. Writes JSON data from this object into the
        * specified data target.
        *
        * @param   target
        *          Data target.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target)
       {
          return writeJson(target, null, null, null, null, null);
       }

       /**
        * 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}.
        *
        * @return  {@code True} if the operation completed successfully, else {@code false}.
        */
       @Override
       public logical writeJson(TargetData target, logical formatted)
       {
          return writeJson(target, formatted, null, null, null, null);
       }

       /**
        * 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.
        *
        * @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, null, null, null);
       }

       /**
        * 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   omitInitials
        *          {@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 omitInitials)
       {
          return writeJson(target, formatted, encoding, omitInitials, null, null);
       }

       /**
        * 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   omitInitials
        *          {@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 omitInitials,
                                logical omitOuterObject)
       {
          return writeJson(target, formatted, encoding, omitInitials, omitOuterObject, null);
       }

       /**
        * 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)
       {
          boolean pretty = formatted != null && !formatted.isUnknown() && formatted.getValue();
          boolean noInit = omitInitialValues != null      &&
                           !omitInitialValues.isUnknown() &&
                           omitInitialValues.getValue();
          boolean noOuter = omitOuterObject != null      &&
                            !omitOuterObject.isUnknown() &&
                            omitOuterObject.getValue();
          boolean beforeImage = writeBeforeImage != null      &&
                                !writeBeforeImage.isUnknown() &&
                                writeBeforeImage.getValue();

          boolean ret;
          try
          {
             com.fasterxml.jackson.core.JsonEncoding jsonEncoding = JsonExport.parseEncoding(encoding);
             if (jsonEncoding == null)
             {
                ErrorManager.recordOrShowError(15342, encoding.toStringMessage(), "WRITE-JSON");
                // Invalid encoding '<encoding>' argument for <method>.
                return logical.of(false);
             }

             if (!flushAllBuffers())
             {
                return logical.FALSE;
             }

             JsonExport export = new JsonExport();
             ret = export.exportDataSet(this, target, pretty, jsonEncoding, noInit, noOuter, beforeImage);
          }
          catch (PersistenceException exc)
          {
             // TODO: log ?
             ret = false;
          }
          finally
          {
             try
             {
                target.close();
             }
             catch (IOException e)
             {
                // TODO: log ?
                ret = false;
             }
          }
          return logical.of(ret);
       }

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

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

       /**
        * 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()
       {
          return new character(serializeHidden ? XmlNode.HIDDEN : XmlNode.ELEMENT);
       }

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

       /**
        * Allows to specify how a this object will be represented in XML and XML Schema. Valid XML
        * node types are "ELEMENT" and "HIDDEN". It overlaps the{@code }SERIALIZE-HIDDEN} attribute.
        * This represents the setter for {@code XML-NODE-TYPE} ABL attribute.
        *
        * @param   newType
        *          The new type.
        */
       @Override
       public void setXmlNodeType(String newType)
       {
          if (newType == null)
          {
             unableToAssignUnknown("XML-NODE-TYPE", "DATASET widget");
             return;
          }

          String newTypeUp = newType.toUpperCase();
          if (!newTypeUp.equals(XmlNode.HIDDEN) && !newTypeUp.equals(XmlNode.ELEMENT))
          {
             ErrorManager.recordOrShowError(16031, newType, name().toStringMessage());
             // Invalid XML-NODE-TYPE <nodetype> for Dataset <dataset>.
             ErrorManager.recordOrShowError(4058, "XML-NODE-TYPE", "DATASET widget", newType);
             // **Attribute <attribute> for the <widget id> has an invalid value of <string>.
             return;
          }

          serializeHidden = newTypeUp.equals(XmlNode.HIDDEN);
       }

       /**
        * 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   strMapping
        *          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 strMapping,
                              character verifySchemaMode)
       {
          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
                }
             }
          }

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

          // perform import
          try
          {
             return logical.of(xmlImport.importDataset(this, RecordBuffer::loadRecord));
          }
          finally
          {
             // release and flush all buffers
             for (int i = 0; i < buffers.size(); i++)
             {
                buffers.get(i).release();
             }
          }
       }

       /**
        * Implementation of the WRITE-XML method. Writes XML data from this object into the specified
        * data target.
        *
        * @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.
        *
        * @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.
        *
        * @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.
        *
        * @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.
        *
        * @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.
        *
        * @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.
        *
        * @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)
       {
          XmlExport export = new XmlExport(
                target,
                formatted != null && !formatted.isUnknown() && formatted.getValue(),
                encoding == null ? null : encoding.toJavaType());

          if (!flushAllBuffers())
          {
             return logical.FALSE;
          }

          boolean ret;
          try
          {
             ret = export.writeDataset(
                   this,
                   schemaLocation == null ? null : schemaLocation.toJavaType(),
                   writeXmlSchema != null && !writeXmlSchema.isUnknown() && writeXmlSchema.getValue(),
                   minXmlSchema != null && !minXmlSchema.isUnknown() && minXmlSchema.getValue(),
                   beforeImage != null && !beforeImage.isUnknown() && beforeImage.getValue(),
                   noInitialVals != null && !noInitialVals.isUnknown() && noInitialVals.getValue());
          }
          catch(XmlExport.Fatal e) // Emergency exit - for the 4GL compatibility
          {
             // Error was already reported, just stop;
             ret = false;
          }
          finally
          {
             try
             {
                target.close();
             }
             catch (IOException e)
             {
                // TODO: log
                ret = false;
             }
          }
          return logical.of(ret);
       }

       /**
        * Writes an XML representation of the definition of this {@code DataSet}.
        *
        * @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 DataSet}.
        *
        * @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 DataSet}.
        *
        * @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 DataSet}.
        *
        * @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 DataSet}.
        *
        * @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)
       {
          XmlExport export = new XmlExport(
                target,
                formatted != null && !formatted.isUnknown() && formatted.getValue(),
                encoding == null ? null : encoding.toJavaType());

          boolean ret;
          try
          {
             ret = export.writeDatasetSchema(
                      this,
                      minXmlSchema != null && !minXmlSchema.isUnknown() && minXmlSchema.getValue(),
                      omitInitVal != null && !omitInitVal.isUnknown() && omitInitVal.getValue());
          }
          finally
          {
             try
             {
                target.close();
             }
             catch (IOException e)
             {
                // TODO: log
                ret = false;
             }
          }
          return logical.of(ret);
       }

       /**
        * Reads XML Schema from an XML document and uses that schema to either create a schema for a
        * {@code DataSet} or {@code TempTable} or verify existing schema in a {@code DataSet},
        * {@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, null, null);
       }

       /**
        * Reads XML Schema from an XML document and uses that schema to either create a schema for a
        * {@code DataSet} or {@code TempTable} or verify existing schema in a {@code DataSet},
        * {@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, null);
       }

       /**
        * Reads XML Schema from an XML document and uses that schema to either create a schema for a
        * {@code DataSet} or {@code TempTable} or verify existing schema in a {@code DataSet},
        * {@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.importDatasetSchema(this));
       }

       /**
        * Obtain an integer values that uniques identifies this resource.
        *
        * @return  an integer values that uniques identifies this resource.
        */
       @Override
       public integer getUniqueID()
       {
          if (uuid == null)
          {
             uuid = UniqueIdGenerator.getUniqueId(UniqueIdGenerator.IdKind.DATASET);
          }
          return new integer(this.uuid);
       }

       /**
        * Set the {@link #postponedDelete} flag.
        *
        * @param    postponed
        *           Flag indicating that delete is postponed.
        */
       public void setPostponedDelete(boolean postponed)
       {
          this.postponedDelete = postponed;
       }

       /**
        * Searches whether a buffer is part of the dataset. The before buffers are not taken into
        * consideration so for these arguments the method returns -1.
        *
        * @param   buffer
        *          The {@code BufferImpl} to be search.
        *
        * @return  The 0-base index of the buffer if it is part of the {@code DataSet} and -1
        *          otherwise.
        */
       public int getIndexOf(BufferImpl buffer)
       {
          for (int i = 0; i < buffers.size(); i++)
          {
             if (buffer == buffers.get(i))
             {
                return i;
             }
          }
          return -1; // buffer not found
       }

       /**
        * Check if the resource can veto the delete or not, before attempting the {@link #delete()} call.
        *
        * @param    ref
        *           The exact handle holding the reference, on which the delete is attempted.
        * @param    explicit
        *           Flag indicating this is called via <code>DELETE OBJECT</code> from the application.
        *
        * @return   <code>false</code> if the dataset is registered as a OUTPUT DATASET-HANDLE parameter
        *           or a BY-REFERENCE parameter.
        */
       @Override
       public boolean allowDelete(handle ref, boolean explicit)
       {
          DataSetManager dmgr = DataSetManager.instance();

          if (dmgr.isOutputDataSetHandle(this))
          {
             setPostponedDelete(true);
             return false;
          }

          if (dmgr.isByReferenceParameter(this))
          {
             if (_dynamic())
             {
                // 12327 error is thrown only for dynamic datasets
                ErrorManager.recordOrThrowError(12327,
                                                "Cannot delete a BY-REFERENCE PARAMETER " +
                                                "dataset or table in the called procedure",
                                                false, false);
             }

             return false;
          }

          return true;
       }

       /**
        * Delete the resource.
        *
        * @return   {@code true} if the resource was deleted.
        */
       @Override
       protected boolean resourceDelete()
       {
          if (!_dynamic() && !implicitDeletion())
          {
             // TODO: show error ?
             return false;
          }

          clearImpl(true, true);
          deleted = true;

          ControlFlowOps.invalidateCallSiteCache(this);

          if (deleteQueries != null)
          {
             for (QueryWrapper query : deleteQueries)
             {
                if (query.valid())
                {
                   query.delete();
                }
             }
          }

          return true;
       }

       /**
        * Allows each resource to declare whether it has some naming constraints.
        *
        * @return  This method always returns {@code false}, requiring that its name should be
        *          validated before assignment.
        *
        * @see  #validateName
        */
       @Override
       protected boolean hasNameConstraints()
       {
          return true;
       }

       /**
        * Checks if this {@code REFERENCE-ONLY} {@code DataSet} was bound to a real {@code DataSet}.
        *
        * @return  This object is dynamically built so by convention this method always returns
        *          {@code true} (it is bound to itself).
        */
       @SuppressWarnings("static-method")
       protected boolean isBound()
       {
          return true;
       }

       /**
        * Check if this dataset was attempted to be deleted, but was postponed because of the handle
        * being a DATASET-HANDLE BY-REFERENCE.
        *
        * @return   The {@link #postponedDelete}.
        */
       protected boolean isPostponedDelete()
       {
          return postponedDelete;
       }

       /**
        * Test the compatibility of two datasets. The number of buffers and their field number is
        * checked first. The datatype of each one is not (yet).
        *
        * @param   other
        *          The {@code DataSet} to compare with this instance.
        *
        * @return  {@code true} if the two dataset are compatible (ie one can receive data from
        *          another).
        */
       protected boolean isCompatibleWith(DataSet other)
       {
          if (other == null || !other.valid())
          {
             // The use of ErrorManager.recordOrThrowError() is not very effective here because in FWD
             // the bind is done after the called procedure has already started. The ErrorManager will
             // stop only the execution of the called procedure/method. In 4GL the associate / bind
             // operation is performed in caller and, in case of errors the caller is stopped before
             // the invocation of the called method
             TransactionManager.triggerErrorInCaller(12314, ErrorManager.replaceTokens(12314));
             return false;
          }
          int bufSize = buffers.size();
          if (bufSize != other.getBuffers().size())
          {
             TransactionManager.triggerErrorInCaller(12310, ErrorManager.replaceTokens(12310));
             return false;
          }

          RecordBuffer buf1;
          RecordBuffer buf2;

          for (int i = 0; i < bufSize; i++)
          {
             buf1 = buffers.get(i).buffer();
             buf2 = other.getBuffers().get(i).buffer();

             if (buf1.getDMOBufInterface() == buf2.getDMOBufInterface())
             {
                // same DMO, no longer needed to check individual fields.
                continue;
             }

             if (!buf1.getDmoInfo().getSignature().getQuickSignature().equals
                 (buf2.getDmoInfo().getSignature().getQuickSignature()))
             {
                TransactionManager.triggerErrorInCaller(12311,
                                                        ErrorManager.replaceTokens(12311,
                                                                                   buf1.getLegacyName(),
                                                                                   buf2.getLegacyName()));
                return false;
             }
          }

          // if all validations passed:
          return true;
       }

       /**
        * Obtains the original {@code DataSet} definition. This is for internal usage, in cases when
        * that information is needed and the proxy interface will mask and make the information unavailable.
        *
        * @return  the original {@code DataSet} definition.
        */
       public DataSet ref()
       {
          return this;
       }

       /**
        * Get the list of {@link #buffers} for the runtime instance of this resource.
        *
        * @return   The list of buffers.
        */
       public List<BufferImpl> getBuffers()
       {
          return ref().buffers; // NOTE: breaking encapsulation !
       }

       /**
        * Getter for the internal variable {@link #serializedBySOAP}.
        *
        * @return  <code>true</code> if this instance is currently serialized due to a SOAP request,
        *          <code>false</code> otherwise
        */
       public boolean getSerializedBySOAP()
       {
          return serializedBySOAP;
       }

       /**
        * Lookup a buffer by its index.
        *
        * @param   bufIndex
        *          The index to search among the list of buffers.
        *
        * @return  The buffer whose index was specified or {@code null} when there is no match.
        */
       public BufferImpl getBufferByIndex(int bufIndex)
       {
          if (bufIndex < 1 || bufIndex > buffers.size())
          {
             return null;
          }

          BufferImpl src = buffers.get(bufIndex - 1);
          BufferImpl srcProxy = proxies.get(src);
          return (srcProxy != null) ? srcProxy.ref() : src;
       }

       /**
        * Copy output data for the linked {@code DataSet} parameter, if any.
        *
        * @param   outputParam
        *          The {@code DataSet} parameter where to copy the data.
        */
       protected void performOutputCopy(DataSetParameter outputParam)
       {
          if (!outputParams.contains(outputParam))
          {
             // was already processed (by i.e. scope close)
             return;
          }

          boolean res = true;
          if (outputParam.isRemoteParameter())
          {
             handle pds = outputParam.getDatasetHandle();

             DatasetWrapper oldWrapper = outputParam.getDatasetWrapper();

             if (oldWrapper.isAsXml())
             {
                String dsXml = writeToXml(new handle(this),
                                          oldWrapper.isUseBeforeImage(),
                                          oldWrapper.isDatasetHandle());
                oldWrapper.setXmlDataset(dsXml);
                return;
             }

             // fill result set(s)
             DataSetContainer rs = new DataSetContainer(this,
                                                        oldWrapper.isInput(),
                                                        oldWrapper.isOutput(),
                                                        oldWrapper.isAppend());
             DatasetWrapper wrapper = new DatasetWrapper(rs);
             wrapper.setDatasetName(this.name().getValue());
             outputParam.setDatasetWrapper(wrapper);

             if (pds._isValid())
             {
                // delete it, nothing else to do with it.  done last as the 'pds' can be the same as 'this bound'.
                ((DataSet) pds.getResource()).delete();
             }
          }
          else
          {
             handle hds = outputParam.getDatasetHandle();
             if (hds.isUnknown())
             {
                handle thisHandle = new handle(this);
                DataSet.create(hds);
                DataSet callingDataset = outputParam.getDataset();
                callingDataset.createLike(thisHandle);
                callingDataset.copyDataset(thisHandle);
             }
             else
             {
                DataSet callingDataset = outputParam.getDataset();

                // copies all the temp-tables in the order they were defined or added
                // extra temp-tables from both source & destination are ignored
                int bufCopyCount = Math.min(callingDataset.getBuffers().size(), this.buffers.size());
                for (int i = 0; i < bufCopyCount; i++)
                {
                   BufferImpl src = buffers.get(i);
                   BufferImpl srcProxy = proxies.get(src);
                   src = (srcProxy != null) ? srcProxy.ref() : src; // dynamically compute the .ref()

                   BufferImpl dst = callingDataset.ref().buffers.get(i);
                   BufferImpl dstProxy = callingDataset.ref().proxies.get(dst);
                   dst = (dstProxy != null) ? dstProxy.ref() : dst; // dynamically compute the .ref()

                   rowid srcRec = (referenceOnly || outputParam.isByReference()) && src._available()
                         ? src.rowID()
                         : null;

                   Temporary srcDMO = (Temporary) src;
                   Temporary dstDMO = (Temporary) dst;

                   res = TemporaryBuffer.copyAllRows(srcRec,
                                                     srcDMO,
                                                     dstDMO,
                                                     outputParam.isAppend(),
                                                     CopyTableMode.OUTPUT_TABLE_PARAM_MODE);

                   if (!res)
                   {
                      break;
                   }
                }
             }
          }

          if (!res)
          {
             TemporaryBuffer.deferCopyError();
          }

          outputParams.remove(outputParam);
       }

       /**
        * Obtain the number of top-level buffers in this {@code DataSet} object.
        *
        * @return  the number of top-level buffers in this {@code DataSet} object.
        */
       public int _getNumTopBuffers()
       {
          if (buffers.isEmpty())
          {
             return 0;
          }
          else if (relations.isEmpty())
          {
             return buffers.size();
          }

          // initially all buffers are TOP-LEVEL. For each active relation, one buffer becomes a child
          // so it is removed from TOP-LEVEL list. Since a buffer can be a child in not more than a
          // relation, each relation will have unique child buffers. As result, the number of
          // TOP-LEVEL buffer list is the difference between the buffer number and the number of
          // active relations.
          int topBuffs = buffers.size();
          for (int i = 0; i < relations.size(); i++)
          {
             DataRelation relation = relations.get(i);
             if (relation._isActive() && !relation._isReposition() && !relation.isRecursive().booleanValue())
             {
                topBuffs--;
             }
          }
          // NOTE: because of the #11979 issue described in addRelationImpl() the [topBuffs] may be negative !!!
          return topBuffs;
       }

       /**
        * Checks whether a buffer can be at this moment the child side in a relation of this {@code DataSet}. When
        * an existing active relation is detected having the buffer as child the error 11979 is displayed before
        * returning {@code false}.
        *
        * @param   childBuffer
        *          The child buffer to test.
        *
        * @return  {@code null} if the requested buffer can be a child buffer in a relation in this
        *          {@code DataSet} in current configuration or the {@code DataRelation} that caused the conflict.
        */
       protected DataRelation allowsActiveChild(Buffer childBuffer)
       {
          int childUid = childBuffer.getUniqueID().intValue();

          for (int i = 0; i < relations.size(); i++)
          {
             DataRelation relation = relations.get(i);
             if (relation._isActive()      &&
                 !relation._isReposition() &&
                 relation.getChildBufferNative().getUniqueID().intValue() == childUid)
             {
                return relation;
             }
          }

          return null;
       }

       /**
        * Queries the list of {@code DataRelation} s for a buffer. Depending on the requests, only the
        * relations where the buffer is parent, child or any of them is returned.
        *
        * @param   buffer
        *          The buffer to be queried on.
        * @param   parent
        *          If {@code true}, all the relations where the buffer is parent are included.
        * @param   child
        *          If {@code true}, all the relations where the buffer is child are included.
        * @param   active
        *          If {@code true}, ONLY the active relations are included. Otherwise, all relations
        *          that match above constraints will be returned.
        *
        * @return  The set of relations where the requested buffer is part of, depending on the value
        *          of the flags.
        */
       public ArrayList<DataRelation> getRelations(Buffer buffer, boolean parent, boolean child, boolean active)
       {
          return getRelations(buffer, parent, child, active, false);
       }

       /**
        * Queries the list of {@code DataRelation} s for a buffer. Depending on the requests, only the
        * relations where the buffer is parent, child or any of them is returned.
        *
        * @param   buffer
        *          The buffer to be queried on.
        * @param   parent
        *          If {@code true}, all the relations where the buffer is parent are included.
        * @param   child
        *          If {@code true}, all the relations where the buffer is child are included.
        * @param   active
        *          If {@code true}, ONLY the active relations are included. Otherwise, all relations
        *          that match above constraints will be returned.
        * @param   forFill
        *          A flag indicating that the relations are requested by a fill operation.
        *
        * @return  The set of relations where the requested buffer is part of, depending on the value
        *          of the flags.
        */
       public ArrayList<DataRelation> getRelations(Buffer buffer,
                                                   boolean parent,
                                                   boolean child,
                                                   boolean active,
                                                   boolean forFill)
       {
          buffer = ((BufferImpl) buffer).ref();

          ArrayList<DataRelation> ret = new ArrayList<>(relations.size());
          for (int i = 0; i < relations.size(); i++)
          {
             DataRelation rel = relations.get(i);
             if (active && !rel._isActive())
             {
                continue;
             }
             // If the relation is reposition and is used for a fill,
             // ignore the relation and make the child a top-level buffer.
             if (child && rel.getChildBufferNative() == buffer && !(rel._isReposition() && forFill))
             {
                ret.add(rel);
             }
             if (parent && rel.getParentBufferNative() == buffer)
             {
                ret.add(rel);
             }
          }
          return ret;
       }

       /**
        * Query the relation by its buffer.
        *
        * @param   buffer
        *          The buffer to be queried on.
        *
        * @return  The optional relation for the buffer
        */
       public Optional<DataRelation> getRelation(Buffer buffer)
       {
          return relations.stream().filter(rel -> rel.getChildBuffer().getResource() == buffer).
                           findFirst();
       }

       /**
        * Invokes a callback procedure or a callback method. The callback procedure/method must have
        * an {@code INPUT} parameter of type {@code DATASET} or {@code DATASET-HANDLE}. The passed
        * parameter is this {@code DataSet} object.
        *
        * @param   cbd
        *          A {@code CallbackData} object holding the data necessary for invocation. May be
        *          {@code null}, in which case this method will silently return {@code yes} even
        *          though nothing is performed.
        * @param   self
        *          The object on which the event is applied (buffer or dataset).
        *
        * @return  {@code true} if no errors were encountered during execution.
        */
       protected boolean invokeCallback(handle self, CallbackData cbd)
       {
          if (cbd == null)
          {
             return true; // yep, nothing to do here
          }

          ErrorManager.ErrorHelper errHlp = ErrorManager.getErrorHelper();
          boolean wasSilent = errHlp.isSilent();
          try
          {
             if (!wasSilent)
             {
                errHlp.setSilent(true);
             }

             SelfManager.pushSelf(self);
             // reference already assumes input-output, do not emit append
             DataSetParameter dsp = new DataSetParameter(this, ParameterOption.BY_REFERENCE);
             if (cbd.getObject() != null)
             {
                // TODO:
                // Method '<methodname>' for callback must be valid public method. (13452)

                ObjectOps.invokeStandalone(cbd.getObject(), cbd.getTarget(), "I", dsp);
             }
             else if (cbd.getProcedure() != null && !cbd.getProcedure().isUnknown())
             {
                InvokeConfig callSite = cbd.getCallSite();
                InvokeConfig cfg = callSite == null ? new InvokeConfig() : callSite.clone();
                cfg.setTarget(cbd.getTarget())
                   .setInHandle(cbd.getProcedure())
                   .setModes("I")
                   .setArguments(dsp)
                   .executeForResource(this);
             }

             // NOTE: sometimes the method return *NO* even if the callback was successfully called
             return !errHlp.isPending(); // !ErrorManager.isError()
          }
          catch (ErrorConditionException e)
          {
             if (e.getProgressErrorCode() == 14457)
             {
                ErrorManager.recordOrShowError(13452, cbd.getTarget());
             }
             return false;
          }
          finally
          {
             SelfManager.popSelf();
             if (!wasSilent)
             {
                errHlp.setSilent(false);
             }
          }
       }

       /**
        * Register an output dataset parameter.
        *
        * @param    dsParam
        *           The dataset parameter.
        */
       protected void addOutputParam(DataSetParameter dsParam)
       {
          if (outputParams == null)
          {
             outputParams = new HashSet<>();
          }
          outputParams.add(dsParam);
       }

       /**
        * The implementation of {@code COPY-DATASET} method. Copies all records from all tables of a
        * source {@code DataSet} object into this {@code DataSet} object.
        *
        * @param   hSrcDS
        *          The source {@code DataSet}.
        * @param   append
        *          When {@code true} the source temp-tables are appended to current temp-tables.
        * @param   replace
        *          When {@code true} the records in this temp-tables are replaced with records from
        *          the temp-tables of source {@code DataSet}.
        * @param   looseCopy
        *          When {@code TRUE}, each temp-table in the source DataSet object is copied to
        *          this {@code DataSet} object based on a field mapping between the source and target
        *          temp-table buffers.
        * @param   pairList
        *          A list of the target and source temp-table PAIRS to be copied, comma-delimited.
        * @param   currentOnly
        *          Use {@code TRUE} indicates that the AVM copy only the current record from each
        *          temp-table at each level in the source {@code DataSet} object to the target
        *          {@code DataSet} object. The default value is {@code FALSE}.
        * @param   prefix
        *          An expression used as the prefix for (re-)naming the target {@code DataSet}.
        *
        * @return  {@code true} on success.
        */
       protected logical copyDatasetImpl(handle hSrcDS,
                                         boolean append,
                                         boolean replace,
                                         boolean looseCopy,
                                         String pairList,
                                         boolean currentOnly,
                                         String prefix)
       {
          if (hSrcDS == null || hSrcDS.isUnknown() || !hSrcDS.isType(LegacyResource.DATASET))
          {
             ErrorManager.recordOrShowError(12362);
             // COPY-DATASET must have a valid dataset handle as first argument.
             return logical.of(false);
          }

          String[] pairs = null;
          if (pairList != null && !pairList.isEmpty())
          {
             pairs = pairList.split(",");
             if (pairs.length % 2 == 1)
             {
                ErrorManager.recordOrShowError(12783);
                // Odd number of tables in pairs list parameter in COPY-DATASET.
                return logical.of(false);
             }
          }

          DataSet srcDS = (DataSet) hSrcDS.getResource();

          // if this is a freshly created DATASET, we copy the structure from the source DS
          if (this.buffers.isEmpty())
          {
             createLikeImpl(srcDS, prefix);
          }

          BiFunction<BufferImpl,BufferImpl, Boolean> checkDestB4Buffer = (src, dst) ->
          {
             boolean isEmptyBefore = src.peerBuffer.buffer().isTableDefinitelyEmpty();

             if (!isEmptyBefore)
             {
                TempTableBuilder  ttb = ((TempTableBuilder) dst.tableHandleNative());
                if (ttb._dynamic())
                {
                   // automatically create implicit before table for this dynamic temp-table
                   String tname = name().toJavaType();
                   ttb.createBeforeTable("BI" + tname, tname);
                }
                else
                {
                   ErrorManager.recordOrShowError(12753, dst.doGetName());
                   // In COPY-TEMP-TABLE operation the static target table <name> must have an empty BEFORE-TABLE if the source table has any BEFORE-TABLE records.
                   return false;
                }
             }
             return true;
          };

          logical res = new logical();
          if (pairs == null)
          {
             // copies all the temp-tables in the order they were defined or added
             // extra temp-tables from both source & destination are ignored
             int bufCopyCount = Math.min(buffers.size(), srcDS.getBuffers().size());
             for (int i = 0; i < bufCopyCount; i++)
             {
                BufferImpl src = srcDS.getBuffers().get(i);
                BufferImpl dst = buffers.get(i);
                if (src.isAfterBuffer() && !dst.isAfterBuffer() && !checkDestB4Buffer.apply(src, dst))
                {
                   res = logical.of(false);
                   break; // i-for
                }

                if (currentOnly)
                {
                   if (!append)
                   {
                      dst.deleteAll();
                   }
                   if (!dst._available())
                   {
                      // if destination buffer is empty create a new placeholder for the record to be copied
                      dst.create();
                   }

                   // copy only the current record from the specified temp-table buffers
                   res = dst.bufferCopy(src);
                   if (!res.getValue())
                   {
                      break; // i-for
                   }
                }
                else
                {
                   // copy the entire AFTER temp-table
                   res = dst.copyTempTable(new handle(src), append, replace, looseCopy, prefix);
                   if (!res.getValue())
                   {
                      break; // i-for
                   }
                }
             }
          }
          else
          {
             // copy only the specified/mapped tables
             for (int i = 0; i < pairs.length; i+=2)
             {
                BufferImpl dst = getBufferByName(pairs[i]);
                BufferImpl src = srcDS.getBufferByName(pairs[i + 1]);

                if (dst == null && src == null)
                {
                   dst = getBufferByName(pairs[i + 1]);
                   src = srcDS.getBufferByName(pairs[i]);
                }

                if (dst == null || src == null)
                {
                   ErrorManager.recordOrShowError(12784, pairs[i], pairs[i + 1]);
                   // COPY-DATASET pair <name> and <name> must have a table in each dataset.
                   return logical.of(false);
                }
                if (src.isAfterBuffer() && !dst.isAfterBuffer() && !checkDestB4Buffer.apply(src, dst))
                {
                   res = logical.of(false);
                   break; // i-for
                }

                if (currentOnly)
                {
                   // copy only the current record from the specified temp-table buffers
                   res = src.bufferCopy(dst);
                   if (!res.getValue())
                   {
                      break; // i-for
                   }
                }
                else
                {
                   // copy the entire temp-table
                   res = dst.copyTempTable(new handle(src), append, replace, looseCopy, prefix);
                   if (!res.getValue())
                   {
                      break; // i-for
                   }
                }
             }
          }

          return res;
       }

       /**
        * Adds a new reference to this dataset. The {@code NUM-REFERENCES} is updated if needed.
        *
        * @param   staticDataSet
        *          The new {@code DataSet} that references this object.
        */
       final void addReference(StaticDataSet staticDataSet)
       {
          if (refs == null)
          {
             refs = Collections.newSetFromMap(new IdentityHashMap<>());
          }
          refs.add(staticDataSet);
       }

       /**
        * Removes a reference to this dataset. The {@code NUM-REFERENCES} is updated if needed.
        *
        * @param   staticDataSet
        *          The new {@code DataSet} that does not references this object any more.
        */
       final void removeReference(StaticDataSet staticDataSet)
       {
          if (refs != null)
          {
             refs.remove(staticDataSet);
          }
       }

       /**
        * Write the specified DATASET handle to a XML.
        * <p>
        * This is used when the remote call sent the DATASET as XML and it must receive it in the same format
        * (used by SOAP callers).
        *
        * @param    dsHandle
        *           The handle referencing the dataset.
        * @param    useBeforeImage
        *           Flag indicating if the before table needs to be serialized.
        * @param    isHandle
        *           Flag indicating that this is used for a DATASET-HANDLE parameter.
        *
        * @return   The XML representation of this dataset.
        */
       @SuppressWarnings("resource")
       static String writeToXml(handle dsHandle, boolean useBeforeImage, boolean isHandle)
       {
          if (!dsHandle._isValid())
          {
             return null;
          }

          longchar dsXml = new longchar("");
          DataSet ds = (DataSet) dsHandle.getResource();
          ds.serializedBySOAP = true;
          try
          {
             ds.writeXml(new TargetData(Serializator.TYPE_LONGCHAR, dsXml),
                         logical.of(false),  // this must be false
                         null,
                         null,
                         logical.of(isHandle), // schema is sent only for DATASET-HANDLE
                         null,
                         logical.of(useBeforeImage),
                         null);

          }
          finally
          {
             ds.serializedBySOAP = false;
          }
          return dsXml.getValue();
       }

       /**
        * Binds all buffers for a dynamic {@code DataSet} object at the same time. Any buffers
        * previously added to this {@code DataSet} object are removed.
        *
        * @param   bufs
        *          Variable argument list of buffer handles to be set. In 4GL, the maximum number of
        *          buffers per query is 18.
        *
        * @return  {@code true} on success.
        */
       boolean setBuffersImpl(Buffer... bufs)
       {
          if (!isAlterable())
          {
             ErrorManager.recordOrShowError(11858);
             // Cannot add/set buffers on a static dataset.
             return false;
          }

          // the list of buffers is cleared even if errors are encountered and method is aborted
          this.buffers.clear();

          BufferManager bm = null;
          Class<?> initClass = pm.getInstantiatingExternalProgramClass();

          List<BufferImpl> localBuffers = new ArrayList<>(bufs.length);
          for (Buffer buf : bufs)
          {
             if (buf == null)
             {
                continue;
             }
             BufferImpl proxy = (BufferImpl) buf;
             BufferImpl bufRef = proxy.ref();
             if (initClass == null)
             {
                // when initiating a legacy OE class, we don't know the source code where this is
                // happening - but the buffer has the definition class emitted; so force that
                if (bm == null)
                {
                   bm = bufRef.buffer().bufferManager;
                }
                Class<?> def = bm.getPendingBufferClass(bufRef.buffer());
                pm.setInstantingExternalProgramClass(def);
             }

             if (bufRef.isBeforeBuffer())
             {
                ErrorManager.recordOrShowError(15008, bufRef.doGetName());
                // You may not add before-table buffer <buffer> to a DATASET.
                return false;
             }

             // check for duplications:
             DataSet ds = bufRef._dataSet();
             if (localBuffers.contains(bufRef) || (ds != null) && ds.valid() && ds != this)
             {
                ErrorManager.recordOrShowError(11860, bufRef.doGetName());
                // Buffer <name> may not appear more than once in any dataset.
                return false;
             }

             localBuffers.add(bufRef);
             proxies.put(bufRef, proxy);
          }

          pm.setInstantingExternalProgramClass(initClass);

          // when everything is fine:
          this.buffers.addAll(localBuffers);
          localBuffers.forEach(b -> b.setDataSet(this));

          return true;
       }

       /**
        * Read the DATASET from XML.
        * <p>
        * This is used when the remote call sent the DATASET as XML and it must receive it in the same format
        * (used by SOAP callers).
        *
        * @param    xmlDataset
        *           The XML serialized dataset.
        * @param    append
        *           Flag indicating if the read mode is 'append'. Otherwise, is 'empty.
        * @param    ds
        *           The handle where to read the dataset.
        */
       @SuppressWarnings("resource")
       private static void readFromXml(String xmlDataset, boolean append, handle ds)
       {
          character readMode = new character(append ? "APPEND" : "EMPTY");

          if (!ds._isValid())
          {
             DataSet.create(ds);
          }

          DataSet dsTarget = (DataSet) ds.getResource();
          longchar lc = new longchar();
          longchar.fixCodePage(lc, "utf-8");
          lc.assign(xmlDataset);
          dsTarget.readXml(new SourceData("longchar", lc),
                           readMode,
                           new character(),
                           logical.of(false));
       }

       /**
        * Lookup a buffer by its name.
        *
        * @param   name
        *          The name to search among the list of buffers.
        *
        * @return  The buffer whose name was specified or {@code null} when there is no match.
        */
       public BufferImpl getBufferByName(String name)
       {
          List<BufferImpl> proxyBuffers = proxies.values().stream().collect(Collectors.toList());

          for (int i = 0; i < proxyBuffers.size(); i++)
          {
             BufferImpl buf = proxyBuffers.get(i);
             if (buf._name().equalsIgnoreCase(name))
             {
                return buf;
             }
          }
          return null;
       }

       /**
        * Lookup a buffer by its serialize-name.
        *
        * @param   serializeName
        *          The serialize-name to search among the list of buffers.
        *
        * @return  The buffer whose serialize-name was specified or {@code null} when there is no match.
        */
       public BufferImpl getBufferBySerializeName(String serializeName)
       {
          if (serializeName == null || serializeName.isEmpty())
          {
             return null; //quick out
          }
          List<BufferImpl> proxyBuffers = proxies.values().stream().collect(Collectors.toList());

          for (int i = 0; i < proxyBuffers.size(); i++)
          {
             BufferImpl buf = proxyBuffers.get(i);
             if (serializeName.equalsIgnoreCase(buf.getSerializeName().toJavaType()))
             {
                return buf;
             }
          }
          return null;
       }

       /**
        * Flush all buffers (including secondary buffers and associated before-buffers) used in this dataset.
        *
        * @return   {@code true} if all buffers were flushed.
        */
       private boolean flushAllBuffers()
       {
          for (Buffer buf : buffers)
          {
             RecordBuffer buffer = ((BufferImpl) buf).buffer();
             if (!buffer.getParentTable().flushBuffers())
             {
                return false;
             }
          }

          return true;
       }

       /**
        * Obtain the current {@code TOP-NAV-QUERY} for a specified buffer. Uses lazy initialization:
        * if not exist one is created (and prepared, but not open) now.
        *
        * @param   buffer
        *          The buffer whose {@code TOP-NAV-QUERY} is to be returned.
        *
        * @return  A {@code handle} to {@code TOP-NAV-QUERY} for specified buffer.
        */
       private handle getTopNavQuery(BufferImpl buffer)
       {
          buffer = buffer.ref();

          QueryWrapper query = topNavQueries == null ? null : topNavQueries.get(buffer);
          if (query == null || !query.valid())
          {
             query = QueryWrapper.createQueryUnnamedPool();
             query.setBuffers(buffer);
             query.prepare("FOR EACH " + buffer._name());
             if (topNavQueries == null)
             {
                topNavQueries = new HashMap<>();
             }
             topNavQueries.put(buffer, query);
             if (deleteQueries == null)
             {
                deleteQueries = Collections.newSetFromMap(new IdentityHashMap<>());
             }
             deleteQueries.add(query);
          }

          return new handle(query);
       }

       /**
        * Notifies the user about an attempt to set an invalid value for {@code TOP-NAV-QUERY } attribute. Two
        * error messages will be issued, the former being 14293 and the latter the one specified as parameter.
        *
        * @param   errCode
        *          The message code (4056 or 4061).
        * @param   bufName
        *          The buffer's name.
        */
       private static void invalidTopNavQuery(int errCode, String bufName)
       {
          ErrorManager.recordOrShowError(14293, bufName);
          // TOP-NAV-QUERY requires a valid query on top dataset buffer <name>
          ErrorManager.recordOrShowError(errCode, "TOP-NAV-QUERY", "DATASET widget");
          // **Attribute <attribute> for the <widget id> has an invalid value of UNKNOWN. (4056)
          //    or
          // **Attribute  for the <widget id> has an invalid value. (4061)
       }

       /**
        * Low level implementation of {@code TOP-NAV-QUERY} method. Does the final parameter checking before
        * mapping the query to the buffer.
        *
        * @param   queryHandle
        *          The query to be set.
        * @param   topBuffer
        *          The top level buffer.
        */
       private void _setTopBufferQuery(handle queryHandle, BufferImpl topBuffer)
       {
          if (queryHandle == null || queryHandle.isUnknown())
          {
             invalidTopNavQuery(4056, topBuffer.name().toStringMessage());
             return;
          }

          if (!queryHandle.isType(LegacyResource.QUERY))
          {
             invalidTopNavQuery(4061, topBuffer.name().toStringMessage());
             return;
          }

          QueryWrapper qv = (QueryWrapper) queryHandle.getResource();
          if (qv.bufferHandleNative() != topBuffer)
          {
             invalidTopNavQuery(4061, topBuffer.name().toStringMessage());
             return;
          }

          if (topNavQueries == null)
          {
             topNavQueries = new HashMap<>();
          }
          topNavQueries.put(topBuffer, qv);
       }

       /**
        * Checks whether <strong>AT LEAST</strong> one data-relation objects in this {@code DataSet}
        * object is active. All data-relation objects in a {@code DataSet} object are active by default.
        *
        * @return  {@code true} when all data-relation objects are active.
        */
       boolean _areRelationsActive()
       {
          if (relations.isEmpty() /*|| !dynamic*/)
          {
             // initial testcases suggested that, in case of relations definined in static dataset the [active] is
             // still true although all relations were individually deactivated. It seems that this behaviour is
             // caused by other (combination of) factors
             return defaultActive;
          }

          for (int i = 0; i < relations.size(); i++)
          {
             DataRelation rel = relations.get(i);
             if (rel._isActive())
             {
                return true;
             }
          }

          // could not find any active relation
          return false;
       }

       /**
        * Adds a data-relation object for a pair of parent and child buffers to this {@code DataSet} object.
        *
        * @param   relName
        *          The name of the relation.
        * @param   parent
        *          The handle to the parent buffer in the data-relation object.
        * @param   child
        *          The handle to the child buffer in the data-relation object.
        * @param   fields
        *          An array of (parent-field, child-field) pairs describing the relationship between
        *          parent and child buffers in the new data-relation object.
        * @param   strPairs
        *          The pair of fields, as string as they appear in the legacy statement.
        * @param   reposition
        *          The reposition mode. If {@code TRUE}, the relation mode is {@code REPOSITION}.
        *          Otherwise the relation mode is {@code SELECTION}. The default value is
        *          {@code SELECTION}.
        * @param   nested
        *          Use {@code TRUE} to nest child rows of {@code DataSet} buffers within their parent
        *          rows when writing the XML representation of data. When {@code FALSE} is passed, all
        *          child rows are written after all parent rows are written. The default value is {@code FALSE}.
        * @param   active
        *          Use {@code true} to make the newly created relation active.
        * @param   recursive
        *          Using {@code TRUE} will cause the data-relation to be filled recursively.
        * @param   fkeyHidden
        *          When set to {@code TRUE} the foreign key fields in nested data-relations will be
        *          omitted when writing the XML representation of data. If this option is used, the
        *          {@code NESTED} argument must be {@code TRUE}.
        * @param   dynamic
        *          Use {@code true} for dynamically created relations.
        *
        * @return  A {@code handle} to the newly created {@code DataRelation}.
        */
       private handle addRelationWorker(String relName,
                                        Buffer parent,
                                        Buffer child,
                                        String[] fields,
                                        String strPairs,
                                        boolean reposition,
                                        boolean nested,
                                        boolean active,
                                        boolean recursive,
                                        boolean fkeyHidden,
                                        boolean dynamic)
       {
          DataRelation dR = new DataRelation(this, parent, child, fields, strPairs, recursive);
          dR.name(relName != null ? relName : "RELATION" + (relations.size() + 1));
          dR.setReposition(reposition);
          dR.setNested(nested);
          dR.setActive(active);
          dR.setForeignKeyHidden(fkeyHidden);
          if (dynamic)
          {
             dR.confirmDynamic();
          }

          relations.add(dR);
          return new handle(dR);
       }

       /**
        * Checks whether this object can be altered (added buffers and relations). Normally, the
        * static defined {@code DataSet} s are not alterable, but they can 'altered' by their
        * {@code Builder} while the object is being constructed.
        *
        * @return  {@code true} if new buffers or relations can be added.
        */
       private boolean isAlterable()
       {
          return _dynamic() || buildInProgress;
       }

       /**
        * Test whether a string is a valid CALLBACK name for {@code DataSet}s. This method is responsible of
        * raising the 11950 error if the name specified is unknown or not a name of a known callback for
        * {@code DataSet}.
        *
        * @param   someName
        *          The name to test.
        *
        * @return  {@code true} only if {@code someName} is equals to "BEFORE-FILL" or "AFTER-FILL",
        *          in ignore-case mode.
        */
       private static boolean isCallbackName(String someName)
       {
          if (someName == null ||
              !BEFORE_FILL_EVENT.equalsIgnoreCase(someName) &&
              !AFTER_FILL_EVENT.equalsIgnoreCase(someName))
          {
             ErrorManager.recordOrShowError(11950, someName != null ? someName : "");
             // Argument <arg> to CALLBACK method must be a valid buffer or dataset event name.
             return false;
          }

          return true;
       }

       /**
        * Get the {@link InvokeConfig callsite} for the specified event.
        *
        * @param    eventName
        *           The event name.
        *
        * @return   see above
        */
       private static InvokeConfig getCallSite(String eventName)
       {
          return (BEFORE_FILL_EVENT.equals(eventName.toUpperCase())
                    ? BEFORE_FILL_CALL_SITE
                    : AFTER_FILL_CALL_SITE);
       }

       /**
        * Obtain the list of top-buffers. From the list of all buffers are removed those buffers that
        * are found as child buffers in an active relation.
        *
        * @return  the list of top-buffers.
        */
       private List<Buffer> getTopBufferList()
       {
          List<Buffer> topBuffers = new LinkedList<>(buffers);

          for (int i = 0; i < relations.size(); i++)
          {
             DataRelation relation = relations.get(i);
             if (relation._isActive() &&
                 !relation._isReposition() &&
                 !relation.isRecursive().booleanValue())
             {
                topBuffers.remove(relation.getChildBufferNative());
             }
          }

          return topBuffers;
       }

       /**
        * Actual implementation of {@code CREATE-LIKE} method. The {@code sourceDS} structure,
        * including {@code TempTable} and {@code DataRelation} definitions, is copied to the this
        * {@code DataSet}; the source data, however, is not copied.
        *
        * @param   sourceDS
        *          The source {@code DataSet}.
        * @param   prefix
        *          The name prefix for the newly created  {@code DatSet}.
        *
        * @return  {@code true} on success (and this object is a copy of the {@code sourceDS}), and
        *          {@code false} if the copy operation failed.
        */
       private logical createLikeImpl(DataSet sourceDS, String prefix)
       {
          if (prefix == null)
          {
             prefix = "";
          }

          if (!validateName(prefix))
          {
             // no error thrown now, one was already issued by validateName()
             return logical.of(false);
          }

          if (!_dynamic())
          {
             ErrorManager.recordOrShowError(11969);
             // Unable to CLEAR target CREATE-LIKE dataset.
             return logical.of(false);
          }

          // make sure the old content is gone:
          clearImpl(false, false);

          name(prefix + sourceDS.name().getValue());

          BufferImpl[] srcBuffers = sourceDS.ref().buffers.toArray(new BufferImpl[sourceDS.getBuffers().size()]);
          BufferImpl[] newBuffers = new BufferImpl[srcBuffers.length];
          for (int i = 0; i < newBuffers.length; i++)
          {
             handle atth = TypeFactory.handle();
             TempTableBuilder.create(atth);
             BufferImpl srcAfterBuff = srcBuffers[i];
             BufferImpl srcB4Buff = srcAfterBuff.beforeBufferNative();
             String beforeName = srcB4Buff != null ? prefix + srcB4Buff._name() : null;

             String tableName = prefix + srcAfterBuff.buffer().getParentTable()._name();
             if (tableName.length() > 32)
             {
                // max length is 32
                tableName = tableName.substring(0, 32);
             }

             TempTableBuilder newAtt = (TempTableBuilder) atth.getResource();
             newAtt.createLike(new handle(srcAfterBuff));
             newAtt.tempTablePrepareImpl(tableName, beforeName, false);

             // TODO: anything to copy at the before-table?
             copyTempTableOptions((AbstractTempTable) srcAfterBuff.tableHandleNative(), newAtt);

             newBuffers[i] = (BufferImpl) newAtt.defaultBufferHandleNative();
          }
          setBuffersImpl(newBuffers);
          // in OE 11.6 the [xmlNodeName] was observed to be copied in a different fashion
          // if (xmlNodeName == null)
          // {
          //    setXmlNodeName(prefix + sourceDS.getXmlNodeName().toStringMessage());
          // }
          // in OE the attributes are reportedly set as follows:
          nsUri = null;     //unknown
          nsPrefix = null;  //unknown
          xmlNodeName = sourceDS.xmlNodeName;       // copied
          serializeName = sourceDS.serializeName;   // copied

          ArrayList<DataRelation> dataRelations = sourceDS.ref().relations;
          for (int i = 0; i < dataRelations.size(); i++)
          {
             DataRelation srcRel = dataRelations.get(i);
             BufferImpl srcChBuffer = srcRel.getChildBufferNative();
             BufferImpl srcParBuffer = srcRel.getParentBufferNative();

             BufferImpl bufParent = newBuffers[positionOf(srcBuffers, srcParBuffer)];
             Buffer bufChild = newBuffers[positionOf(srcBuffers, srcChBuffer)];
             if (srcRel.isParentIdRelation().booleanValue())
             {
                DataRelation dR = new DataRelation(this, bufParent, bufChild,
                            srcRel._getRelationFields(), new ArrayList<>(), new ArrayList<>(), true);
                relations.add(dR);

                // in OE 12+ it is reported that the name is not copied
                //    dR.name(srcRel.name());
                // instead it is generated automatically:
                dR.name("RELATION" + relations.size());
                // TODO: test whether this happens for simple (non parent-id) relations
             }
             else
             {
                addRelationWorker(srcRel.name().toStringMessage(),
                                  bufParent,
                                  bufChild,
                                  srcRel.getRelationFieldArray(),
                                  srcRel._getRelationFields(),
                                  srcRel._isReposition(),
                                  srcRel.isNested().getValue(),
                                  srcRel._isActive(),
                                  srcRel.isRecursive().getValue(),
                                  srcRel.getForeignKeyHidden().getValue(),
                                  true);
             }
          }

          // everything is fine:
          return logical.of(true);
       }

       /**
        * Copy the temp-table options from the source to the destination.
        *
        * @param    src
        *           The source table.
        * @param    dst
        *           The destination table.
        */
       private static void copyTempTableOptions(AbstractTempTable src, AbstractTempTable dst)
       {
          // TODO: any other options to copy?

          // copy the following from temp-table:
          // XML-NODE-NAME
          // SERIALIZE-NAME

          if (!src.getXmlNodeName().isUnknown())
          {
             dst.setXmlNodeName(src.getXmlNodeName());
          }
          if (!src.getSerializeName().isUnknown())
          {
             dst.setSerializeName(src.getSerializeName());
          }

          // copy the following from fields:
          // SERIALIZE-NAME
          // SERIALIZE-HIDDEN
          // XML-DATA-TYPE
          // XML-NODE-TYPE
          // XML-NODE-NAME

          BufferImpl bufSrc = (BufferImpl) src.defaultBuffer().getDMOProxy();
          AbstractTempTable srcTT = (AbstractTempTable) src.defaultBuffer().tableHandleResource();
          BufferImpl bufDst = (BufferImpl) dst.defaultBuffer().getDMOProxy();
          int numFields = bufSrc._numFields();
          Object[] fields =  (Object[]) bufSrc.buffer().getDmoInfo().getExistingFields().toArray();
          for (int i = 0; i < numFields; i++)
          {
             BufferFieldImpl dstField = bufDst.bufferFieldNative(i + 1);

             // check if the sources has mutable info which has changed
             TableMapper.MutableFieldInfo srcFieldInfo = srcTT.getMutableInfo(((P2JField) fields[i])
                                                                              .getName().toLowerCase(),
                                                                              false,
                                                                              null);
             if (srcFieldInfo != null)
             {
                // if this has been mutated in the source temp-table, then copy it; otherwise, let the defaults

                SerializeOptions srcSerOpts = srcFieldInfo.getSerializeOptions();

                if (srcSerOpts.getSerializeName() != null)
                {
                   dstField.setSerializeName(srcSerOpts.getSerializeName());
                }
                dstField.setSerializeHidden(srcSerOpts.isSerializeHidden());
                if (srcSerOpts.getXmlDataType() != null)
                {
                   dstField.setXmlDataType(srcSerOpts.getXmlDataType());
                }
                if (srcSerOpts.getXmlNodeType() != null)
                {
                   dstField.setXmlNodeType(srcSerOpts.getXmlNodeType());
                }
                if (srcSerOpts.getXmlNodeName() != null)
                {
                   dstField.setXmlNodeName(srcSerOpts.getXmlNodeName());
                }
                if (srcFieldInfo.getColumnLabel() != null)
                {
                   dstField.setColumnLabel(srcFieldInfo.getColumnLabel());
                }
                if (srcFieldInfo.getHelp() != null)
                {
                   dstField.setHelp(srcFieldInfo.getHelp());
                }
                if (srcFieldInfo.getLabel() != null)
                {
                   dstField.setLabel(srcFieldInfo.getLabel());
                }
             }
          }
       }

       /**
        * Locates a {@code BufferImpl} in an array of objects of same type.
        *
        * @param   buffers
        *          A buffers of items.
        * @param   item
        *          The item to be located.
        *
        * @return  the first occurrence of the {@code item} in the {@code buffers} or -1 if not found.
        */
       private static int positionOf(BufferImpl[] buffers, BufferImpl item)
       {
          for (int i = 0; i < buffers.length; i++)
          {
             if (item.ref() == buffers[i].ref())
             {
                return i;
             }
          }

          return -1;
       }

       /**
        * Declares this static {@code DataSet} as build. This will prohibit some of the setters to be
        * called.
        */
       private void finishBuild()
       {
          buildInProgress = false;
       }

       /**
        * Builder class that helps construct a {@code DataSet} object statically, that is, using the
        * {@code DEFINE DATASET} statement. It exposes a set of chaining methods that take each
        * option parameters and set them into the {@code DataSet} being constructed. The chaining must
        * finish with {@link #generate()} to obtain the final {@code DataSet} object. Before
        * returning, the {@code generate()} will do the final checks and register the object to be
        * returned with the {@link DataSetManager}.
        */
       public static class Builder
       {
          /** This is a builder for a shared {@code DataSet}. */
          private final boolean isShared;

          /** This is a builder for a shared new {@code DataSet}. */
          private final boolean isNew;

          /** The legacy name of the {@code DataSet}. */
          private final String legacyName;

          /** This is a reference of a {@code DataSet} defined in another procedure / class. */
          private boolean referenceOnly = false;

          /** The build actions to perform. */
          private final ArrayList<Runnable> buildChain = new ArrayList<>(7);

          /** The build dataset instance. */
          private DataSet target = null;

          /** The defining type where this dataset is constructed. */
          private Class<?> definingType = null;

          /**
           * The constructor. Initialize the private final fields that will be used in the final step
           * of the construction: to parametrize the registration of the {@code DataSet}.
           *
           * @param   legacyName
           *          The legacy name of the {@code DataSet}.
           * @param   isShared
           *          This is a shared {@code DataSet}.
           * @param   isNew
           *          This is a new {@code DataSet}. Only in combination with {@code isShared}.
           */
          public Builder(String legacyName, boolean isShared, boolean isNew)
          {
             this.legacyName = legacyName;
             this.isShared = isShared;
             this.isNew = isNew;
          }

          /**
           * Set the defining type where this dataset is constructed.
           *
           * @param    type
           *           The defining type.
           *
           * @return   A reference of this object in order to help chaining.
           */
          public Builder setDefiningType(Class<?> type)
          {
             this.definingType = type;
             return this;
          }

          /**
           * Sets the namespace attribute. Corresponds to {@code NAMESPACE-URI} option from
           * {@code DEFINE DATASET} statement.
           *
           * @param   namespace
           *          The namespace for the {@code DataSet} to be constructed.
           *
           * @return  A reference of this object in order to help chaining.
           */
          public Builder setNamespaceUri(String namespace)
          {
             buildChain.add(() -> target.namespaceURI(namespace));
             return this;
          }

          /**
           * Sets the ns prefix attribute. Corresponds to {@code NAMESPACE-PREFIX} option from
           * {@code DEFINE DATASET} statement.
           *
           * @param   prefix
           *          The ns prefix for the {@code DataSet} to be constructed.
           *
           * @return  A reference of this object in order to help chaining.
           */
          public Builder setNamespacePrefix(String prefix)
          {
             buildChain.add(() -> target.namespacePrefix(prefix));
             return this;
          }

          /**
           * Sets the name of the xml node attribute. Corresponds to {@code XML-NODE-NAME} option from
           * {@code DEFINE DATASET} statement.
           *
           * @param   nodeName
           *          The xml node name for the {@code DataSet} to be constructed.
           *
           * @return  A reference of this object in order to help chaining.
           */
          public Builder setXmlNodeName(String nodeName)
          {
             buildChain.add(() -> target.setXmlNodeName(nodeName));
             return this;
          }

          /**
           * Sets the serialize name attribute. Corresponds to {@code SERIALIZE-NAME} option from
           * {@code DEFINE DATASET} statement.
           *
           * @param   serName
           *          The serialize name for the {@code DataSet} to be constructed.
           *
           * @return  A reference of this object in order to help chaining.
           */
          public Builder setSerializeName(String serName)
          {
             buildChain.add(() -> target.setSerializeName(serName));
             return this;
          }

          /**
           * Sets the xml node type attribute. Corresponds to {@code XML-NODE-TYPE} option from
           * {@code DEFINE DATASET} statement.
           *
           * @param   nodeType
           *          The node type for the {@code DataSet} to be constructed.
           *
           * @return  A reference of this object in order to help chaining.
           */
          public Builder setXmlNodeType(String nodeType)
          {
             buildChain.add(() -> target.setXmlNodeType(nodeType));
             return this;
          }

          /**
           * Mark the DataSet's serialize-hidden attribute. Corresponds to {@code SERIALIZE-HIDDEN}
           * option from {@code DEFINE DATASET} statement.
           *
           * @return  A reference of this object in order to help chaining.
           */
          public Builder setSerializeHidden()
          {
             buildChain.add(() -> target.setSerializeHidden(true));
             return this;
          }

          /**
           * Mark the DataSet as reference only. Corresponds to {@code REFERENCE-ONLY} option from
           * {@code DEFINE DATASET} statement.
           *
           * @return  A reference of this object in order to help chaining.
           */
          public Builder setReferenceOnly()
          {
             this.referenceOnly = true;
             return this;
          }

          /**
           * Configures the set of buffers for the {@code DataSet}. Corresponds to {@code FOR} option
           * from {@code DEFINE DATASET} statement.
           *
           * @param   bufs
           *          The set buffers for the {@code DataSet} to be constructed.
           *
           * @return  A reference of this object in order to help chaining.
           */
          public Builder forBuffers(Buffer... bufs)
          {
             buildChain.add(() -> target.setBuffersImpl(bufs));
             return this;
          }

          /**
           * Configure a data relation. Corresponds to {@code DATA-RELATION} set of options from
           * {@code DEFINE DATASET} statement.
           *
           * @param   relName
           *          The relation name.
           * @param   reposition
           *          Specifies the relation mode as {@code REPOSITION} as opposed to the default
           *          {@code SELECTION} relation mode.
           * @param   nested
           *          If {@code true}, the child rows of a {@code DataSet} buffer are nested within
           *          their parent rows when writing a JSON representation of data or an XML
           *          representation of data or schema.
           * @param   fkHidden
           *          Specifies that foreign key fields in nested data relations are omitted when
           *          writing JSON or XML data, and that foreign key fields are optional in the XML
           *          Schema representation of the child table definition.
           *          If {@code NESTED} option is {@code false}, this option is ignored.
           * @param   inactive
           *          Setting this to {@code true} allows to have two relations between the same two
           *          DataSet temp-table buffers, but only have one relation active at a time.
           * @param   recursive
           *          Allows DataSet {@code FILL} method to load self-referencing elements.
           * @param   parent
           *          The parent buffer in this data-relation. This must be one of the buffers
           *          identified by the buffers added with {@code forBuffers()} above.
           * @param   child
           *          The child buffer in this data-relation. This must be one of the buffers
           *          identified by the buffers added with {@code forBuffers()} above.
           * @param   relationFieldPairs
           *          A set of field pairs (parent, child)*.
           *
           * @return  A reference of this object in order to help chaining.
           */
          public Builder addDataRelation(String relName,
                                         boolean reposition,
                                         boolean nested,
                                         boolean fkHidden,
                                         boolean inactive,
                                         boolean recursive,
                                         Buffer parent,
                                         Buffer child,
                                         FieldPair... relationFieldPairs)
          {
             logical ok = logical.of(true);

             buildChain.add(() -> {
                BufferImpl bufParent = ((BufferImpl) parent).ref();
                BufferImpl bufChild = ((BufferImpl) child).ref();
                if (!target.getBuffers().contains(bufParent) || !target.getBuffers().contains(bufChild))
                {
                   ErrorManager.throwError(12384);
                   // DATA-RELATION parent and child buffers must also be in the dataset.
                   ok.assign(false);
                   return;
                   // this is a compile-time error, followed by "Could not understand line <number>. (196)
                }

                StringBuilder strPairs = new StringBuilder();
                String[] fields = new String[relationFieldPairs.length * 2];
                for (int i = 0; i < relationFieldPairs.length; i++)
                {
                   if (strPairs.length() != 0)
                   {
                      strPairs.append(",");
                   }

                   String parentField = relationFieldPairs[i].parentField;
                   if (StringHelper.startsWithIgnoreCase(parentField, bufParent.doGetName() + "."))
                   {
                      parentField = parentField.substring(bufParent.doGetName().length() + 1);
                   }
    // this will cause an NPE because the buffer scope is not yet open so the parent table is not
    // configured
    //            if (!bufParent.hasField(parentField))
    //            {
    //               ErrorManager.throwError(12385);
    //               // Pairs in DATA-RELATION RELATION-FIELDS phrase must be in the form: parent-field, child-field, parent-field, child-field etc.
    //               return null;
    //               // this is a compile-time error, followed by "Could not understand line <number>. (196)
    //            }

                   String childField = relationFieldPairs[i].childField;
                   if (StringHelper.startsWithIgnoreCase(childField, bufChild.doGetName() + "."))
                   {
                      childField = childField.substring(bufChild.doGetName().length() + 1);
                   }
    // this will cause an NPE because the buffer scope is not yet open so the parent table is not
    // configured
    //            if (!bufChild.hasField(childField))
    //            {
    //               ErrorManager.throwError(12385);
    //               // Pairs in DATA-RELATION RELATION-FIELDS phrase must be in the form: parent-field, child-field, parent-field, child-field etc.
    //               return null;
    //               // this is a compile-time error, followed by "Could not understand line <number>. (196)
    //            }

                   fields[2 * i] = parentField;
                   fields[2 * i + 1] = childField;
                   strPairs.append(parentField).append(",").append(childField);
                }

                target.addRelationWorker(relName, parent, child, fields, strPairs.toString(),
                                         reposition, nested, !inactive, recursive, fkHidden, false);
             });

             return ok.getValue() ? this : null;
          }

          /**
           * This method chains a {@code PARENT-ID-RELATION} option to the {@code DataSet} builder.
           *
           * @param   relName
           *          A name for the data relation.
           * @param   parentBuffer
           *          The parent buffer.
           * @param   childBuffer
           *          The child buffer.
           * @param   parentIdField
           *          The field from child buffer of {@code RECID} type that points to a record in
           *          parent buffer.
           *
           * @return  A reference of this object in order to help chaining.
           */
          public Builder addParentIdRelation(String relName,
                                             Buffer parentBuffer,
                                             Buffer childBuffer,
                                             String parentIdField)
          {
             buildChain.add(() -> {
                DataRelation dR = new DataRelation(target, parentBuffer, childBuffer,
                                                   parentIdField, new ArrayList<>(), new ArrayList<>(), false);

                dR.name(relName);
                // the REPOSITION attribute might be set, depending on the OE version
                // dR.setReposition(true);
                target.ref().relations.add(dR);
             });
             return this;
          }

          /**
           * This method chains a {@code PARENT-ID-RELATION} option to the {@code DataSet} builder.
           *
           * @param   relName
           *          A name for the data relation.
           * @param   parentBuffer
           *          The parent buffer.
           * @param   childBuffer
           *          The child buffer.
           * @param   parentIdField
           *          The field from child buffer of {@code RECID} type that points to a record in
           *          parent buffer.
           * @param   parentFieldsBefore
           *          The list of fields from the {@code parentBuffer} temp-table record that are to
           *          be written to XML before any {@code childBuffer} temp-table records.
           * @param   parentFieldsAfter
           *          The list of fields from the {@code parentBuffer} temp-table record that are to
           *          be written to XML after all {@code childBuffer} temp-table records.
           *
           * @return  A reference of this object in order to help chaining.
           */
          public Builder addParentIdRelation(String relName,
                                             Buffer parentBuffer,
                                             Buffer childBuffer,
                                             String parentIdField,
                                             String[] parentFieldsBefore,
                                             String[] parentFieldsAfter)
          {
             buildChain.add(() -> {
                DataRelation dR = new DataRelation(target,
                                                   parentBuffer,
                                                   childBuffer,
                                                   parentIdField,
                                                   Arrays.asList(parentFieldsBefore),
                                                   Arrays.asList(parentFieldsAfter),
                                                   false);
                dR.name(relName);
                // the REPOSITION attribute might be set, depending on the OE version
                // dR.setReposition(true);
                target.ref().relations.add(dR);
             });
             return this;
          }

          /**
           * Validates and registers the {@code DataSet} to {@code DataSetManager}, making it valid.
           *
           * @return  the parent {@code DataSet}.
           */
          public DataSet generate()
          {
             target = new DataSet(legacyName, false, referenceOnly);

             // the actual build of the dataset
             for (int i = 0; i < buildChain.size(); i++)
             {
                Runnable step = buildChain.get(i);
                step.run();
             }

             // prevent further options to be set
             target.finishBuild();

             target = StaticDataSet.create(target);

             ((StaticDataSet) target).setDefiningType(definingType);

             // register with DataSetManager.
             DataSetManager.instance().register(target, legacyName, isShared, isNew);

             // end chain by returning the final object
             return target;
          }
       }

       /**
        * Container immutable class that abstracts a field mapping between the parent and child
        * tables/buffers in a data relation.
        */
       public static class FieldPair
       {
          /** The parent field of the relation. */
          private final String parentField;

          /** The child field of the relation. */
          private final String childField;

          /**
           * The constructor
           * @param   parentField
           *          The parent field of the relation.
           * @param   childField
           *          The child field of the relation.
           */
          public FieldPair(String parentField, String childField)
          {
             this.parentField = parentField;
             this.childField = childField;
          }
       }

       /**
        * Flag for debugging level. When {@code true} the full dataset is returned at the expense of CPU clocks.
        * When {@code false} only the essential dataset structure is returned. Intentionally this is defined as
        * non-final private here and has no accessors so that it can be r/w only at debug time.
        */
       private static boolean fullDebugString = false;

       /**
        * Obtain a short description of the dataset used for debugging. If {@code fullDebugString} is {@code true}
        * the full dataset (all rows of all buffers) is dumped as a JSON string. If only the dataset structure
        * is needed, set {@code fullDebugString} to {@code false}.
        *
        * @return  a short description of the dataset used for debugging.
        */
       @Override
       public String toString()
       {
          return fullDebugString ? getDataAsJson() : getSchema();
       }

       /**
        * Obtain a short description of the dataset structure: buffers and relations between them.
        *
        * @return  a string describing the important elements of the dataset structure.
        */
       private String getSchema()
       {
          StringBuilder sb = new StringBuilder();
          sb.append("{").append(name).append(": ");
          for (int i = 0; i < buffers.size(); i++)
          {
             BufferImpl buffer = buffers.get(i);
             sb.append(buffer.doGetName()).append(" ");
          }
          sb.append("(");
          for (int i = 0; i < relations.size(); i++)
          {
             DataRelation rel = relations.get(i);
             sb.append(rel.name().getValue()).append("=");
             sb.append(rel.getParentBufferNative().doGetName()).append("+");
             sb.append(rel.getChildBufferNative().doGetName()).append(" ");
          }
          sb.append(") }");
          return sb.toString();
       }

       /**
        * Get the JSON representation of data of this DATASET. The returned value contains data from all tables of
        * the dataset, but not the relations between them.
        *
        * @return   See above.
        */
       private String getDataAsJson()
       {
          ObjectMapper objectMapper = new ObjectMapper();
          Map<String, Object> fullResult = new HashMap<>();
          Map<String, Object> datasetResult = new HashMap<>();

          fullResult.put(name, datasetResult);
          for (BufferImpl buffer : buffers)
          {
             String tableName = buffer.buffer().getDmoInfo().legacyTable;
             RecordBufferSerializer recordBufferSerializer = new RecordBufferSerializer(buffer.buffer());
             try
             {
                datasetResult.put(tableName, recordBufferSerializer.sqlTableContent());
             }
             catch (PersistenceException | SQLException e)
             {
                log.severe("Error getting data: " + e.getMessage());
                datasetResult.put("error", "Error getting data: " + e.getMessage());
             }
          }
          try
          {
             return objectMapper.writeValueAsString(fullResult);
          }
          catch (JsonProcessingException e)
          {
             log.severe("JSON processing error: " + e.getMessage());
             return "{\"error\": \"Error generating JSON\"}";
          }
       }

       /**
        * Get the SQL representation of data of this DATASET. The returned value contains data from all tables of
        * the dataset, but not the relations between them.
        *
        * @return   See above.
        */
       @SuppressWarnings("unused")
       private String getSqlData()
       {
          StringBuilder sb = new StringBuilder();
          for (int i = 0; i < buffers.size(); i++)
          {
             BufferImpl buffer = buffers.get(i);
             // output the main/after buffer content
             if (sb.length() != 0)
             {
                sb.append("\n");
             }
             try
             {
                sb.append(buffer.buffer().sqlTableContent(0));
             }
             catch (PersistenceException | SQLException e)
             {
                sb.append("Error getting data: ").append(e.getMessage()).append("\n");
             }

             // if exists, output the before buffer content, too
             BufferImpl before = buffer.beforeBufferNative();
             if (before != null && before.valid())
             {
                try
                {
                   sb.append("\n").append(before.buffer().sqlTableContent(0));
                }
                catch (PersistenceException | SQLException e)
                {
                   sb.append("Error getting data: ").append(e.getMessage()).append("\n");
                }
             }
          }
          return sb.toString();
       }
    }