# DataSetParameter.java

    /*
    ** Module   : DataSetParameter.java
    ** Abstract : Container for DataSet reference/handle and calling-side APPEND option.
    **
    ** Copyright (c) 2019-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 OM  20190403 Created initial version.
    ** 002 OM  20190510 Added dataset runtime support. Changed to parameter passing option as enum
    **                  which simplifies API and adds mutual exclusion to options.
    **     HC  20190707 Implemented read(dataSet) and read(tempTable) methods of JsonObject legacy
    **                  class.
    ** 003 CA  20190712 Temporarily added c'tors with a DataSetParameter - this is because conversion
    **                  rules incorrectly emit a DataSetParameter for the variable handle, in case of
    **                  DATASET-HANDLE argument.  This needs to be fixed in TRPL.
    **     OM  20190716 Dropped changes in H003 after fixing TRPL. Recreate dataset from remote
    **                  parameter (table included).
    **     CA  20190802 TempTableBuilder.createRemoteTable is responsible to create a temp-table.
    **     CA  20190826 Expose the original dataset passed by the caller, so that it can be used for
    **                  'inverse binding' (from callee to caller).
    ** 004 CA  20191203 Added toString().
    ** 005 CA  20200427 Allow a DATASET to be read from JSON argument, in case of a remote REST call.
    ** 006 OM  20210106 Dropped 2nd parameter of TEMP-TABLE-PREPARE.
    **     CA  20210213 Fixed a NPE in createDataSet - the SIMPLE table was not processed.
    **     OM  20210215 Created before table and linked to its peer/after table before populating.
    **     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.
    **     CA  20210831 Fixed deserialization of a remote dataset which has data in the before-table(s).
    **     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.
    **     GES 20210501 Reworked to enable subclasses which reduce parameter processing from common use cases.
    **     CA  20220601 Any TableParameter or DataSetParameter instances at a legacy OO method or constructor call
    **                  must be transformed to their mode'ed version, depending on the INPUT, OUTPUT or
    **                  INPUT-OUTPUT mode, and the DATASET-HANDLE, DATASET, TABLE-HANDLE or TABLE versions.
    **     CA  20220727 Fixed OO dataset/table parameters (at the method definition and method call) when there is
    **                  an APPEND option.
    **     IAS 20221108 Fixed support for SCHEMA-MARSHAL == NONE. Re-worked indexes (de)serialization.
    **     IAS 20221111 Fixed support of relations' for SCHEMA-MARSHAL == NONE and issues with
    **                  processing 'schemaMarshalLevel', 'xmlNodeName', 'nsURI', and 'nsPrefix'.
    **     CA  20221116 Do not call setters for 'xmlNodeName', 'nsURI', and 'nsPrefix' if the value is null.
    **     CA  20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
    ** 007 OM  20231027 Added support for passing the ERROR attribute with the dataset.
    ** 008 CA  20240409 Allow the parameter mode to be set (useful for CALL parameter).
    ** 009 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.
    ** 010 OM  20240429 Being more lenient when processing the field pairs.
    ** 011 AS  20240808 Added originalDataSet type awareness.
    ** 012 DDF 20250211 Removed unused DataSetParameter(DataSetParameter) constructor.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.persist;

    import com.goldencode.p2j.util.*;

    import java.util.*;

    /**
     * Represents a dataset or dataset-handle parameter, the mode (INPUT, INPUT-OUTPUT or OUTPUT) and any
     * parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).  Allows subclasses to specialize this
     * behavior.  A core feature is to implement interchangeability between DATASET and DATASET-HANDLE parameters
     * (in the 4GL you can pass a DATASET parameter to a procedure that takes DATASET-HANDLE and vice versa).
     * <p>
     * The parameter mode can be implemented in this class or it can be overridden in a subclass.  If it is
     * implemented here, then the input and output flags must be passed to the constructor.  If the subclass
     * overrides the {@link #isInputMode} and {@link #isOutputMode} methods then the input and output boolean
     * members will be ignored.  This is sub-optimal but it was done deliberately to enable the base class to
     * be used as a full replacement for any of the subclasses.
     */
    public class DataSetParameter
    {
       /** Reference of the wrapped dataset. */
       private final handle /*DataSet*/ dataset;

       /** Parameter passing mode APPEND, BY-VALUE, BIND or BY-REFERENCE option. */
       private EnumSet<ParameterOption> options;

       /** Determines if this is a remote (passed to appserver) parameter. */
       private boolean remoteParameter = false;

       /**
        * Calling- or called-side result set (for remote parameters).
        * <p>
        * (equivalent of {@code TableWrapper resultSet} in {@code TableParameter}).
        */
       private DatasetWrapper datasetWrapper = null;

       /**
        * 1-based index of the dataset parameter (in declaration of the function). {@code 0} when
        * not defined.
        */
       private int parameterIndex = 0;

       /** The original dataset passed as an argument. */
       private DataSet originalDataSet;

       /**
        * true if the original dataset is a Proxy
        */
       private boolean isOriginalDataSetProxy = false;

       /** {@code true} if the parameter mode is INPUT or INPUT-OUTPUT. */
       private boolean input = false;

       /** {@code true} if the parameter mode is OUTPUT or INPUT-OUTPUT. */
       private boolean output = false;

       /**
        * Create a new parameter for the REMOTE dataset.
        *
        * @param    wrapper
        *           The REMOTE dataset.
        */
       public DataSetParameter(DatasetWrapper wrapper)
       {
          BufferManager.registerScopeable(null);

          this.datasetWrapper = wrapper;
          this.options = EnumSet.of(wrapper.isAppend() ? ParameterOption.APPEND : ParameterOption.NONE);
          this.remoteParameter = true;

          dataset = TypeFactory.handle();
          if (!wrapper.isFromJson())
          {
             createDataSet(wrapper);
          }
       }

       /**
        * Create an instance using a dataset reference.
        *
        * @param   dataset
        *          Reference of the wrapped dataset.
        */
       public DataSetParameter(DataSet dataset)
       {
          this(dataset, ParameterOption.NONE);
       }

       /**
        * Create an instance using a dataset reference.
        *
        * @param   dataset
        *          Reference of the wrapped dataset.
        * @param   option
        *          Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        */
       public DataSetParameter(DataSet dataset, ParameterOption option)
       {
          this(dataset, option, false, false);
       }

       /**
        * Create an instance using a dataset reference.
        *
        * @param   dataset
        *          Reference of the wrapped dataset.
        * @param   options
        *          Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        */
       public DataSetParameter(DataSet dataset, EnumSet<ParameterOption> options)
       {
          this(dataset, options, false, false);
       }

       /**
        * Create an instance using a dataset reference.
        *
        * @param    dataset
        *           Reference of the wrapped dataset.
        * @param    option
        *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        * @param    input
        *           {@code true} if the parameter mode is INPUT or INPUT-OUTPUT.
        * @param    output
        *           {@code true} if the parameter mode is OUTPUT or INPUT-OUTPUT.
        */
       public DataSetParameter(DataSet dataset, ParameterOption option, boolean input, boolean output)
       {
          this(dataset, EnumSet.of(option), input, output);
       }

       /**
        * Create an instance using a dataset reference.
        *
        * @param    dataset
        *           Reference of the wrapped dataset.
        * @param    options
        *           Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        * @param    input
        *           {@code true} if the parameter mode is INPUT or INPUT-OUTPUT.
        * @param    output
        *           {@code true} if the parameter mode is OUTPUT or INPUT-OUTPUT.
        */
       public DataSetParameter(DataSet dataset, EnumSet<ParameterOption> options, boolean input, boolean output)
       {
          BufferManager.registerScopeable(null);

          this.originalDataSet        = dataset;
          this.dataset                = new handle(dataset);
          this.options                = options;
          // TODO: check default value for mode if ParameterOption.NONE is passed in
          this.input                  = input;
          this.output                 = output;
          this.isOriginalDataSetProxy = true;
       }

       /**
        * @return true is the original dataset is a Proxy
        */
       public boolean isOriginalDataSetProxy()
       {
          return isOriginalDataSetProxy;
       }

       /**
        * Create an instance using a dataset handle.
        *
        * @param    dsHandle
        *           Handle to the wrapped dataset.
        */
       public DataSetParameter(handle dsHandle)
       {
          this(dsHandle, ParameterOption.NONE);
       }

       /**
        * Create an instance using a dataset handle.
        *
        * @param    dsHandle
        *           Handle to the wrapped dataset.
        * @param    option
        *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        */
       public DataSetParameter(handle dsHandle, ParameterOption option)
       {
          this(dsHandle, option, false, false);
       }

       /**
        * Create an instance using a dataset handle.
        *
        * @param    dsHandle
        *           Handle to the wrapped dataset.
        * @param    options
        *           Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        */
       public DataSetParameter(handle dsHandle, EnumSet<ParameterOption> options)
       {
          this(dsHandle, options, false, false);
       }

       /**
        * Create an instance using a dataset handle.
        *
        * @param    dsHandle
        *           Handle to the wrapped dataset.
        * @param    option
        *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        * @param    input
        *           {@code true} if the parameter mode is INPUT or INPUT-OUTPUT.
        * @param    output
        *           {@code true} if the parameter mode is OUTPUT or INPUT-OUTPUT.
        */
       public DataSetParameter(handle dsHandle, ParameterOption option, boolean input, boolean output)
       {
          this(dsHandle, EnumSet.of(option), input, output);
       }

       /**
        * Create an instance using a dataset handle.
        *
        * @param    dsHandle
        *           Handle to the wrapped dataset.
        * @param    options
        *           Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        * @param    input
        *           {@code true} if the parameter mode is INPUT or INPUT-OUTPUT.
        * @param    output
        *           {@code true} if the parameter mode is OUTPUT or INPUT-OUTPUT.
        */
       public DataSetParameter(handle dsHandle, EnumSet<ParameterOption> options, boolean input, boolean output)
       {
          BufferManager.registerScopeable(null);

          this.originalDataSet = (DataSet)dsHandle.getResource();
          this.dataset = dsHandle;
          this.options = options; // TODO: check default value for mode if ParameterOption.NONE is passed in
          this.input   = input;
          this.output  = output;
       }

       /**
        * Get the table parameter option for this argument.
        *
        * @return   The table parameter option.
        */
       public EnumSet<ParameterOption> getParameterOptions()
       {
          return options;
       }

       /**
        * Set the table parameter option for this argument.
        *
        * @param   options
        *          The table parameter option.
        */
       public void setParameterOptions(EnumSet<ParameterOption> options)
       {
          this.options = options;
       }

       /**
        * Obtain the {@code DataSet} handle.
        *
        * @return  a handle to the dataset passed as parameter.
        */
       public handle get()
       {
          return new handle(getDataset());
       }

       /**
        * Get the original dataset, as passed by the caller.
        *
        * @return    The {@link #originalDataSet}.
        */
       public DataSet getOriginalDataSet()
       {
          return originalDataSet;
       }

       /**
        * Determines if this parameter is INPUT or INPUT-OUTPUT.
        *
        * @return   {@code true} if this is INPUT or INPUT-OUTPUT.
        */
       public boolean isInputMode()
       {
          return input;
       }

       /**
        * Set the flag for whether this parameter is INPUT or INPUT-OUTPUT.
        *
        * @param    input
        *           {@code true} if this is INPUT or INPUT-OUTPUT.
        */
       public void setInputMode(boolean input)
       {
          this.input = input;
       }

       /**
        * Determines if this parameter is OUTPUT or INPUT-OUTPUT.
        *
        * @return   {@code true} if this is OUTPUT or INPUT-OUTPUT.
        */
       public boolean isOutputMode()
       {
          return output;
       }

       /**
        * Set the flag for whether this parameter is OUTPUT or INPUT-OUTPUT.
        *
        * @param    output
        *           {@code true} if this is OUTPUT or INPUT-OUTPUT.
        */
       public void setOutputMode(boolean output)
       {
          this.output = output;
       }

       /**
        * Returns the unwrapped dataset instance.
        *
        * @return  see above.
        */
       public DataSet getDataset()
       {
          return dataset == null || dataset.isUnknown() ? null : (DataSet) dataset.getResource();
       }

       /**
        * Returns the wrapped dataset instance.
        *
        * @return  see above.
        */
       public handle getDatasetHandle()
       {
          return dataset;
       }

       /**
        * Checks whether this parameter was passed with {@code APPEND} option.
        *
        * @return   {@code true} if this parameter was passed with {@code APPEND} option.
        */
       public boolean isAppend()
       {
          return options.contains(ParameterOption.APPEND);
       }

       /**
        * Checks whether this parameter was passed with {@code BY-REFERENCE} option.
        *
        * @return  {@code true} if this parameter was passed with {@code BY-REFERENCE} option.
        */
       public boolean isByReference()
       {
          return options.contains(ParameterOption.BY_REFERENCE);
       }

       /**
        * Checks whether this parameter was passed with {@code BY-VALUE} option.
        *
        * @return  {@code true} if this parameter was passed with {@code BY-VALUE} option.
        */
       public boolean isByValue()
       {
          return options.contains(ParameterOption.BY_VALUE);
       }

       /**
        * Checks whether this parameter was passed with {@code BIND} option.
        *
        * @return  {@code true} if this parameter was passed with {@code BIND} option.
        */
       public boolean isBind()
       {
          return options.contains(ParameterOption.BIND);
       }

       /**
        * Checks whether this is a remote parameter.
        *
        * @return  {@code true} if this is a remote parameter, {@code false} if this is a local
        *          parameter.
        */
       public boolean isRemoteParameter()
       {
          return remoteParameter;
       }

       /**
        * Get the {@link #datasetWrapper}.
        *
        * @return   See above.
        */
       public DatasetWrapper getDatasetWrapper()
       {
          return datasetWrapper;
       }

       /**
        * Set the {@link #datasetWrapper}.
        *
        * @param    datasetWrapper
        *           Calling- or called-side result set (for remote parameters).
        */
       public void setDatasetWrapper(DatasetWrapper datasetWrapper)
       {
          this.datasetWrapper = datasetWrapper;
       }

       /**
        * Get 1-based index of the dataset parameter (in declaration of the function).
        *
        * @return  1-based index of the dataset parameter or {@code 0} when not defined.
        */
       public int getParameterIndex()
       {
          return parameterIndex;
       }

       /**
        * Set 1-based index of the dataset parameter (in declaration of the function).
        *
        * @param   parameterIndex
        *          1-based index of the dataset parameter.
        */
       public void setParameterIndex(int parameterIndex)
       {
          this.parameterIndex = parameterIndex;
       }

       /**
        * Create a new DATASET resource from the specified wrapper, and save it in the {@link #dataset}
        * handle.
        *
        * @param    wrapper
        *           The dataset from the remote side.
        */
       public void createDataSet(DatasetWrapper wrapper)
       {
          if (wrapper.getDatasetName() == null)
          {
             dataset.setUnknown();
             return;
          }

          DataSet.create(dataset);
          DataSet newDS = (DataSet) dataset.getResource();
          newDS.name(wrapper.getDatasetName());
          newDS.error(wrapper.hasError());
          if (wrapper.namespaceURI() != null)
          {
             newDS.namespaceURI(wrapper.namespaceURI());
          }
          if (wrapper.namespacePrefix() != null)
          {
             newDS.namespacePrefix(wrapper.namespacePrefix());
          }
          if (wrapper.getXmlNodeName() != null)
          {
             newDS.setXmlNodeName(wrapper.getXmlNodeName());
          }

          List<DsTableDefinition> tableDefs = wrapper.getTableDefs();
          Set<String> tablesWithoutSchema = new HashSet<>();
          if (tableDefs != null && !tableDefs.isEmpty())
          {
             BufferImpl[] buffs = new BufferImpl[tableDefs.size()];
             for (int k = 0; k < tableDefs.size(); k++)
             {
                DsTableDefinition tableDef = tableDefs.get(k);
                if (!tableDef.isReceivedSchema())
                {
                   tablesWithoutSchema.add(tableDef.getName());
                   continue;
                }
                if (!tableDef.isBeforeTable())
                {
                   Iterator<PropertyDefinition> props = tableDef.propertyIterator();
                   String idx = tableDef.getIndexes();
                   String xmlns = tableDef.getXmlns();
                   String prefix = tableDef.getXmlPrefix();
                   List<IndexDefinition> indexDefs = tableDef.getIndexDefs();
                   TempTableBuilder ttb = new TempTableBuilder();
                   if (indexDefs != null)
                   {
                      buffs[k] = ttb.createRemoteTable(tableDef.getName(),
                               props, () -> indexDefs.stream().forEach(ix -> ix.addTo(ttb)),
                               xmlns, prefix, tableDef.getSchemaMarshalLevel());
                   }
                   else
                   {
                      buffs[k] = ttb.createRemoteTable(tableDef.getName(),
                               props, idx, xmlns, prefix, tableDef.getSchemaMarshalLevel());
                   }

                   if (tableDef.isAfterTable() && tableDef.getPeerDef() != null)
                   {
                      // ask the after-table to create its before table in order to have them correctly linked
                      TempTableBuilder afterTable = (TempTableBuilder) buffs[k].tableHandleNative();
                      DsTableDefinition beforeTableDef = tableDef.getPeerDef();
                      afterTable.createBeforeTable(beforeTableDef.getName(), afterTable._name());
                   }
                }

                // flush data from [tableDef.getRows()] to [ttb] / buffs[k]
                TemporaryBuffer.insertAllRows(wrapper.getTableWrapper(k),
                                              ((Temporary) buffs[k]),
                                              wrapper.isAppend(),
                                              TemporaryBuffer.CopyTableMode.INPUT_PARAM_MODE,
                                              getParameterIndex());
             }
             newDS.setBuffersImpl(buffs);
          }

          List<DsRelationDefinition> relationsDefs = wrapper.getRelationsDefs();
          if (relationsDefs != null && !relationsDefs.isEmpty())
          {
             for (DsRelationDefinition relDef : relationsDefs)
             {
                if (tablesWithoutSchema.contains(relDef.getParentBuffer()) ||
                    tablesWithoutSchema.contains(relDef.getChildBuffer()))
                {
                   continue;
                }

                // the ADD-RELATION method of DataSet will raise error condition (11963) if the fields are
                // qualified, even if they are correct. Trying to be more lenient by dropping the buffer name,
                // in case that it is possible
                String pairs = relDef.getFieldPairs();
                String[] fields = pairs.split(",");
                boolean touched = false;
                for (int j = 0; j < fields.length; j++)
                {
                   int i = fields[j].indexOf('.');
                   if (i > 0)
                   {
                      // drop the buffer qualifier:
                      fields[j] = fields[j].substring(i + 1);
                      touched = true;
                   }
                }
                if (touched)
                {
                   pairs = String.join(",", fields);
                }

                handle hRel = newDS.addRelation(
                      newDS.bufferHandle(relDef.getParentBuffer()),
                      newDS.bufferHandle(relDef.getChildBuffer()),
                      new character(pairs),
                      new logical(relDef.isReposition()),
                      new logical(relDef.isNested()),
                      new logical(relDef.isActive()),
                      new logical(relDef.isRecursive()),
                      new logical(relDef.isFkeyHidden()));
                ((CommonHandleChain) hRel.getResource()).name(relDef.getName());
             }
          }
       }

       /**
        * Get a string representation of this dataset.
        *
        * @return   See above.
        */
       @Override
       public String toString()
       {
          return datasetWrapper != null
                   ? datasetWrapper.toString()
                   : originalDataSet != null
                      ? originalDataSet.toString()
                      : dataset != null && !dataset.isUnknown()
                         ? dataset.get().toString()
                         : super.toString();
       }
    }