# TableParameter.java

    /*
    ** Module   : TableParameter.java
    ** Abstract : Parameter support for TABLE and TABLE-HANDLE.
    **
    ** Copyright (c) 2013-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 SVL 20130226 Created initial version.
    ** 002 SVL 20140106 Added table handle variable.
    ** 003 CA  20140110 Added support for usage from java side (when there is no DMO passed).
    ** 004 SVL 20140414 Added support for remote parameters.
    ** 005 SVL 20140611 Do not allow tableHandle to be null.
    ** 006 SVL 20140709 Added parameterIndex.
    ** 007 CA  20171030 Added support for BY-REFERENCE table parameter mode.
    ** 008 OM  20190510 Added error handling when incorrect handle is passed to constructor.
    **     OM  20190523 Unified parameter options handling for TempTables and DataSet params.
    ** 009 ECF 20190823 Temporary workaround for regression with output parameters.
    **                  TODO: proper fix needed.
    ** 010 HC  20190719 Fixed unexpected error when passing TABLE-HANDLE as output parameter.
    **     CA  20190731 Create a dynamic table from a REMOTE table, when INPUT is used.
    **     CA  20190826 If a table-handle parameter is for a temp-table, then set the 'table'
    **                  Java field for the default-buffer, and not the temp-table.
    ** 011 CA  20200427 Allow a TABLE to be read from JSON argument, in case of a remote REST call.
    ** 012 OM  20210106 Dropped 2nd parameter of TEMP-TABLE-PREPARE.
    **     SVL 20210517 Added isValid().
    **     SVL 20210517 Fixed isValid() for the case of a result set.
    **     CA  20211125 Fixed an issue when the TABLE-HANDLE handle is not unknown, but invalid - getTable() must
    **                  return null.
    **     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 20210430 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 20221029 Added SCHEMA-MARSHAL attribute support.
    **     IAS 20221108 Replaced static 'TempTableBuilder.createRemoteTable' with a regular one.
    **     IAS 20221111 Fixed 'schemaMarshalLevel' support.
    **     CA  20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
    ** 013 CA  20240409 Allow the parameter mode to be set (useful for CALL parameter).
    */

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

    import java.util.*;

    import com.goldencode.p2j.persist.*;

    /**
     * Represents a table or table-handle parameter, the mode (INPUT, INPUT-OUTPUT or OUTPUT) and any table
     * parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).  Allows subclasses to specialize this
     * behavior.  A core feature is to implement interchangeability between TABLE and TABLE-HANDLE parameters
     * (in the 4GL you can pass a TABLE parameter to a procedure that takes TABLE-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 TableParameter
    {
       /** Buffer of the wrapped table. */
       private Temporary table;

       /** Table handle. */
       private final handle tableHandle;

       /** Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE). */
       private EnumSet<ParameterOption> options;

       /** Calling- or called-side result set (for remote parameters). */
       private TableWrapper resultSet = null;

       /** Determines if this is a remote (passed to appserver) parameter. */
       private boolean remoteParameter = 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;

       /** Table SCHEMA-MARSHAL attribute value encodes as int */
       private int schemaMarshalLevel;

       /** Flag indicating this is a TABLE-HANDLE argument. */
       private boolean isTableHandle = false;

       /**
        * 1-based index of the table parameter (in declaration of the function). <code>0</code>
        * if it is not defined.
        */
       private int parameterIndex = 0;

       /**
        * Create an instance using a buffer.
        *
        * @param    table
        *           Buffer of the wrapped table.
        */
       public TableParameter(Temporary table)
       {
          this(table, ParameterOption.NONE);
       }

       /**
        * Create an instance using a buffer.
        *
        * @param    table
        *           Buffer of the wrapped table.
        * @param    option
        *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        */
       public TableParameter(Temporary table, ParameterOption option)
       {
          this(table, option, false, false);
       }

       /**
        * Create an instance using a buffer.
        *
        * @param    table
        *           Buffer of the wrapped table.
        * @param    options
        *           Table parameter options (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        */
       public TableParameter(Temporary table, EnumSet<ParameterOption> options)
       {
          this(table, options, false, false);
       }

       /**
        * Create an instance using a buffer.
        *
        * @param    table
        *           Buffer of the wrapped table.
        * @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 TableParameter(Temporary table, ParameterOption option, boolean input, boolean output)
       {
          this(table, EnumSet.of(option), input, output);
       }

       /**
        * Create an instance using a buffer.
        *
        * @param    table
        *           Buffer of the wrapped table.
        * @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 TableParameter(Temporary table, EnumSet<ParameterOption> options, boolean input, boolean output)
       {
          BufferManager.registerScopeable(null);

          this.table       = (Temporary) ((BufferImpl) table).ref();
          this.tableHandle = ((BufferReference) table).buffer().tableHandle();
          this.options     = options;
          this.input       = input;
          this.output      = output;
          this.schemaMarshalLevel = ((AbstractTempTable)this.tableHandle.getResource()).getSchemaMarshalLevel();
       }

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

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

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

       /**
        * Create an instance using a table handle.
        *
        * @param    tableHandle
        *           Handle to the wrapped table.
        * @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 TableParameter(handle tableHandle, ParameterOption option, boolean input, boolean output)
       {
          this(tableHandle, EnumSet.of(option), input, output);
       }

       /**
        * Create an instance using a table handle.
        *
        * @param    tableHandle
        *           Handle to the wrapped table.
        * @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 TableParameter(handle tableHandle, EnumSet<ParameterOption> options, boolean input, boolean output)
       {
          BufferManager.registerScopeable(null);

          WrappedResource res = tableHandle.getResource();
          isTableHandle = true;

          if (tableHandle._isValid())
          {
             if (res instanceof TempTable)
             {
                this.table = TemporaryBuffer.getDefaultBuffer(tableHandle);
             }
             else if (res instanceof Temporary)
             {
                this.table = (Temporary) res;
             }
             else
             {
                String msg1 = "TEMP-TABLE handle parameter requires valid and prepared handle";
                String msg2 = "Error attempting to push run time parameters onto the stack";
                ErrorManager.recordOrThrowError(new int[] { 9068, 984 },
                                                new String[] { msg1, msg2 }, false);
             }
          }
          else
          {
             this.table = null;
          }

          this.tableHandle = tableHandle;
          this.options     = options;
          this.input       = false;
          this.output      = false;
       }

       /**
        * Create an instance using a result set (for remote parameters).
        *
        * @param    resultSet
        *           Result set containing input data.
        */
       public TableParameter(TableWrapper resultSet)
       {
          this(resultSet, ParameterOption.NONE);
       }

       /**
        * Create an instance using a result set (for remote parameters).
        *
        * @param    resultSet
        *           Result set containing input data.
        * @param    option
        *           Table parameter option (APPEND, BY-VALUE, BIND or BY-REFERENCE).
        */
       public TableParameter(TableWrapper resultSet, ParameterOption option)
       {
          this(resultSet, option, false, false);
       }

       /**
        * Create an instance using a result set (for remote parameters).
        *
        * @param    resultSet
        *           Result set containing input data.
        * @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 TableParameter(TableWrapper resultSet, ParameterOption option, boolean input, boolean output)
       {
          this(resultSet, EnumSet.of(option), input, output);
       }

       /**
        * Create an instance using a result set (for remote parameters).
        *
        * @param    resultSet
        *           Result set containing input data.
        * @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 TableParameter(TableWrapper resultSet, EnumSet<ParameterOption> options, boolean input, boolean output)
       {
          BufferManager.registerScopeable(null);

          this.table           = null;
          this.tableHandle     = new handle();
          this.resultSet       = resultSet;
          this.options         = options;
          this.remoteParameter = true;
          this.input           = input;
          this.output          = output;

          // if INPUT, create a dynamic table
          if (resultSet.isInput() && !resultSet.isFromJson() && resultSet.isAfterTable())
          {
             BufferImpl buff = new TempTableBuilder().createRemoteTable(resultSet.getTableName(),
                                                                  resultSet.propertyIterator(),
                                                                  resultSet.getIndexes(),
                                                                  resultSet.getXmlns(),
                                                                  resultSet.getXmlPrefix(),
                                                                  resultSet.getSchemaMarshalLevel());

             TemporaryBuffer.insertAllRows(resultSet,
                                           (Temporary) buff,
                                           resultSet.isAppend(),
                                           TemporaryBuffer.CopyTableMode.INPUT_PARAM_MODE,
                                           getParameterIndex());

             this.table = (Temporary) buff;
          }
       }

       /**
        * Get the state of the {@link #isTableHandle} flag.
        *
        * @return   See above.
        */
       public boolean isTableHandle()
       {
          return isTableHandle;
       }

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

       /**
        * Determines if 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 result set (for remote parameters).
        *
        * @return   Result set.
        */
       public TableWrapper getResultSet()
       {
          return resultSet;
       }

       /**
        * Set the result set (for remote parameters).
        *
        * @param    resultSet
        *           Result set, normally containing output data.
        */
       public void setResultSet(TableWrapper resultSet)
       {
          this.resultSet = resultSet;
       }

       /**
        * Get the table-handle which references the wrapped table.
        *
        * @return   The table-handle (contains the wrapped table).
        */
       public handle getTableHandle()
       {
          return tableHandle;
       }

       /**
        * Set the parameter's table.
        *
        * @param    table
        *           The table
        */
       public void setTable(Temporary table)
       {
          this.table = table;
       }

       /**
        * Get the buffer of the wrapped table.
        *
        * @return   buffer of the wrapped table.
        */
       public Temporary getTable()
       {
          if (table == null)
          {
             if (tableHandle._isValid())
             {
                AbstractTempTable tt = (AbstractTempTable) tableHandle.getResource();
                return (tt == null) ? null : (Temporary) tt.defaultBufferHandleNative();
             }
             else
             {
                return null;
             }
          }
          else
          {
             return table;
          }
       }

       /**
        * Determines if the APPEND option was specified on the calling side.
        *
        * @return    {@code true} if the APPEND option was specified on the calling side.
        */
       public boolean isAppend()
       {
          return options.contains(ParameterOption.APPEND);
       }

       /**
        * Determines if the {@code BY-VALUE} option was specified on the calling side.
        *
        * @return   {@code true} if the {@code BY-VALUE} option was specified on the calling side.
        */
       public boolean isByValue()
       {
          return options.contains(ParameterOption.BY_VALUE);
       }

       /**
        * Determines if the {@code BIND} option was specified on the calling side.
        *
        * @return   {@code true} if the {@code BIND} option was specified on the calling side.
        */
       public boolean isBind()
       {
          return options.contains(ParameterOption.BIND);
       }

       /**
        * Determines if the {@code BY-REFERENCE} option was specified on the calling side.
        *
        * @return   {@code true} if the {@code BY-REFERENCE} option was specified on the calling side.
        */
       public boolean isByReference()
       {
          return options.contains(ParameterOption.BY_REFERENCE);
       }

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

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

       /**
        * Get 1-based index of the table parameter (in declaration of the function).
        *
        * @return    1-based index of the table parameter or <code>0</code> if it is not defined.
        */
       public int getParameterIndex()
       {
          return parameterIndex;
       }

       /** Get encoded table SCHEMA-MARSHAL attribute value.
        *
        * @return encoded table SCHEMA-MARSHAL attribute value.
        */
       public int getSchemaMarshalLevel()
       {
          return schemaMarshalLevel;
       }

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

       /**
        * Return <code>true</code> if the table parameter is valid. It can be invalid, say, if an unknown table
        * handle has been assigned to it.
        *
        * @return <code>true</code> if the table parameter is valid.
        */
       public boolean isValid()
       {
          return table != null || (tableHandle != null && tableHandle._isValid()) || resultSet != null;
       }
    }