# AbstractParameter.java

    /*
    ** Module   : AbstractParameter.java
    ** Abstract : Common logic for assigns [input-]output parameter values (database fields and normal
    **            variables) upon exit from procedure/function
    **
    ** Copyright (c) 2015-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
    ** 001 OM  20150702 First version based on extracted code form AbstractSimpleParameter and
    **                  AbstractExtentParameter for which this is the base class.
    ** 002 OM  20150723 IdentityHashMap replaces HashMap to keep references to simple parameters.
    ** 003 OM  20161011 Made getCurrentScope() static. Added Scope.isParameter().
    ** 004 CA  20181105 Fixed processing order of OUTPUT/INPUT-OUTPUT parameters, in cases when there
    **                  are mixed extent and non-extent arguments.
    ** 005 CA  20190710 Made scope-related APIs public.
    ** 006 CA  20200506 Fixed runtime for character vs longchar OO method calls.
    ** 007 CA  20210223 If a dynamic array parameter is resized by the top-level block code, then the reference
    **                  in the parameter instance must be replaced.
    **     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.
    **     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  20220906 Parameter scope notifications is required for all OUTPUT parameters, not just fields.
    **     CA  20220609 Release all the callee's resources for this parameter, after assignment has taken place.
    **     CA  20220613 Do not release memptr variables, as these are passed 'by pointer'.
    **     CA  20220630 Fixed a memory leak for object references.
    **     SVL 20230108 Improved performance by replacing some "for-each" loops with indexed "for" loops.
    **     CA  20230104 Track if an output parameter is associated with a table field, so batch processing is done
    **                  only in these cases, for output parameter assignment.
    **     CA  20230116 Avoid fromTypeName when the ref type is valid and known.
    ** 008 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 009 CA  20230724 Further reduce context-local usage.
    ** 010 CA  20231031 Added 'BlockDefinition' parameter to 'scopeStart'.
    */
    /*
    ** 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 com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.util.logging.*;

    import java.lang.reflect.*;
    import java.util.*;
    import java.util.logging.*;

    /**
     * Base class for all types of parameters. The hierarchy looks like this:
     * {@link AbstractParameter}
     * <ul>
     *    <li>{@link AbstractSimpleParameter}
     *       <ul>
     *          <li>{@link VariableAssigner}
     *          <li>{@link FieldAssigner}
     *       </ul>
     *    <li>{@link AbstractExtentParameter}
     *       <ul>
     *          <li>{@link OutputExtentParameter}
     *             <ul>
     *                <li>{@code ExtentExprN1} (generated-code)
     *                <li>{@link OutputExtentField}
     *             </ul>
     *          <li>{@link InputOutputExtentParameter}
     *             <ul>
     *                <li>{@code ExtentExprN2} (generated-code)
     *                <li>{@link InputOutputExtentField}
     *             </ul>
     *       </ul>
     * </ul>
     * The class does the managements of scopes of for called procedures for both simple and extent
     * parameters, using a {@link ContextLocal} {@code WorkArea}.
     */
    abstract public class AbstractParameter
    {
       /** Logger */
       protected static final CentralLogger LOG = CentralLogger.get(AbstractParameter.class.getName());

       /** Object which manages all instances of this class by context. */
       protected static final ContextLocal<WorkArea> context = new ContextLocal<WorkArea>()
       {
          /**
           * Get the weight of this context-local var.
           *
           * @return  Always {@link WeightFactor#LEVEL_5}.
           */
          @Override
          public WeightFactor getWeight()
          {
             return WeightFactor.LEVEL_5;
          }

          /** Retrieve the initial value of this context local variable. */
          @Override
          protected WorkArea initialValue()
          {
             return new WorkArea();
          }
       };

       /**
        * Release all the callee's resources for this parameter, after assignment has taken place.
        */
       protected abstract void release();

       /**
        * Default constructor - register the {@link WorkArea scopeable} instance as pending for the next started
        * block.
        */
       public AbstractParameter()
       {
          TransactionManager.registerPendingScopeable(getScopeable());
       }

       /**
        * Retrieve a {@code Scopeable} for the current context. This allows an external object to
        * notify this class' current context of scope open and close events.
        *
        * @return  Context local {@code Scopeable}.
        */
       public static Scopeable getScopeable()
       {
          return context.get();
       }

       /**
        * Obtain the current scope from the current context.
        *
        * @return  current scope from the current context
        */
       public static Scope getCurrentScope()
       {
          return context.get().getCurrentScope();
       }

       /**
        * Post-process the OUTPUT parameter - if a {@link Scope#withConversionCtor conversion constructor} was
        * registered, then create a {@link VariableAssigner}.
        * <p>
        * Also, normalize the {@link AbstractSimpleParameter#outputValue} so that the called method will use a
        * real BDT sub-type, and not an anonymous class.
        *
        * @param    origAssigner
        *           The assigner created bdt the BDT anonymous class.
        * @param    var
        *           The variable to be passed as parameter.
        *
        * @return   The normalized instance to be used as a parameter at the method.
        */
       protected static BaseDataType postProcessOutput(AbstractSimpleParameter origAssigner, BaseDataType var)
       {
          // the 'var' (which was passed by the caller as argument for this parameter) may have been overridden
          // by the runtime, for type mismatch cases (i.e. longchar argument for character parameter).

          // in this case, the runtime would have registered a 'withConversionCtor' entry - this wil contain the
          // original variable.  check that and:
          // - if the original variable has an assigner, call it
          // - otherwise, create a VariableAssigner and register it.

          Scope scope = getCurrentScope();
          Object[] other = scope.withConversionCtor.remove(var);
          AbstractSimpleParameter assigner = null;

          if (other != null)
          {
             BaseDataType key = (BaseDataType) other[0];
             Constructor<?> convCtor = (Constructor<?>) other[1];

             if (key.getAssigner() != null)
             {
                // initialize it
                assigner = (AbstractSimpleParameter) key.getAssigner().get();
             }
             else
             {
                assigner = new VariableAssigner(var, key);
             }

             assigner.convCtor = convCtor;
             assigner.outputValue = var;
          }

          if (origAssigner.outputValue.getClass().isAnonymousClass())
          {
             // do not leave behind an anonymous class - we need a real BDT implementation of a compatible legacy
             // type, and not an anonymous class - otherwise, any usage of 'getClass()' (for map lookup, c'tors,
             // etc) will fail

             if (origAssigner.outputValue instanceof ObjectVar)
             {
                ObjectVar newOutputValue = new ObjectVar((ObjectVar) origAssigner.outputValue);

                // release the reference
                origAssigner.release();

                origAssigner.outputValue = newOutputValue;
             }
             else
             {
                BaseDataType newOutputValue = origAssigner.outputValue.instantiateUnknown();
                newOutputValue.assign(origAssigner.outputValue);

                // release the reference
                origAssigner.release();

                origAssigner.outputValue = newOutputValue;
             }
          }

          return origAssigner.outputValue;
       }

       /**
        * Check if the {@link BaseDataType} passed as parameter is used for lookup of an
        * {@code OUTPUT} parameter.
        *
        * @param   key
        *          The variable for lookup in the current scope.
        *
        * @return  {@code true} if the content of {@code key} will be assigned back on return with
        *          success.
        */
       protected static boolean isParameter(BaseDataType key)
       {
          return getCurrentScope().isParameter(key);
       }

       /**
        * Check if the given array reference was passed as a parameter, by looking into all scopes.
        *
        * @param     key
        *            The array reference.
        *
        * @return    See above.
        */
       protected static boolean isExtentParameter(BaseDataType[] key)
       {
          WorkArea wa = context.get();
          for (Scope scope : wa.scopes)
          {
             if (scope.isExtentParameter(key))
             {
                return true;
             }
          }

          return false;
       }

       /**
        * Replace the extent array parameter reference (not the original argument) with the new one.  This is
        * required in cases when the parameter is resized by the top-level block.
        *
        * @param    old
        *           The previous array parameter reference.
        * @param    other
        *           The new reference.
        */
       protected static void replaceParameter(BaseDataType[] old, BaseDataType[] other)
       {
          for (Scope scope : context.get().scopes)
          {
             scope.replaceParameter(old, other);
          }
       }

       /**
        * Get the variable container used as parameter inside the procedure body. It makes sense only
        * for simple parameters.
        *
        * @return  {@code null}. Implementing classes will return the proper field.
        */
       protected BaseDataType getLocalParam()
       {
          return null;
       }

       /**
        * Check if the parameter can release its resources.
        *
        * @param    bdt
        *           The parameter instance.
        *
        * @return   <code>true</code> if the parameter can be released.
        */
       protected boolean canRelease(BaseDataType bdt)
       {
          return !(bdt instanceof memptr);
       }

       /**
        * Check if this parameter is associated with a table field, so a batch assign is required on output
        * parameter processing.
        *
        * @return   Always <code>false</code>.
        */
       protected boolean isTableField()
       {
          return false;
       }

       /**
        * Object which manages the context local state of field assigners. It manages a deque of
        * scopes, each of which contains zero or more {@code OutputParameterAssigner} objects. As the
        * currently executing program's block scopes open and close, these scopes are pushed and
        * popped from the deque.
        */
       protected static class WorkArea
       implements Scopeable
       {
          /** Deque containing field assigners by scope */
          private final Deque<Scope> scopes = new ArrayDeque<>();

          /**
           * Default constructor.
           */
          private WorkArea()
          {
             scopes.push(new Scope());
          }

          /**
           * Called as each block scope opens, at which time we push a scope onto the deque.
           *
           * @param    block
           *           The explicit block definition which required this notification.
           *
           * @see com.goldencode.p2j.util.Scopeable#scopeStart
           */
          @Override
          public void scopeStart(BlockDefinition block)
          {
             scopes.push(new Scope());
          }

          /**
           * Called as each block scope closes, at which time we pop a scope from the deque.
           *
           * @see com.goldencode.p2j.util.Scopeable#scopeFinished()
           */
          public void scopeFinished()
          {
             // pop the param list for the inner scope
             scopes.pop();
          }

          /** No-op. */
          @Override
          public void scopeDeleted()
          {
             // no-op
          }

          /**
           * Get the {@link ScopeId} for the instance.
           *
           * @return   {@link ScopeId#ABSTRACT_PARAMETER}.
           */
          @Override
          public ScopeId getScopeId()
          {
             return ScopeId.ABSTRACT_PARAMETER;
          }

          /**
           * Get the object which manages field assigners for the current scope.
           *
           * @return  Current scope.
           */
          protected Scope getCurrentScope()
          {
             return scopes.peek();
          }
       }

       /**
        * One instance of this class is created for each block scope which the currently executing
        * program enters.  It contains zero or more field assigners specific to that scope.
        * <p>
        * After exiting a function or procedure, the
        * {@link OutputParameterAssigner#processAssignments(String, boolean)} method is invoked, which
        * causes each field assigner to assign the current value of its parameter value back to the
        * associated DMO property.
        */
       public static class Scope
       implements com.goldencode.p2j.util.OutputParameterAssigner
       {
          /**
           * Map of output simple parameter assigners for this scope, created lazily. The wrapper
           * outputValue variables are used as keys for quick access.
           */
          private Map<BaseDataType, AbstractSimpleParameter> simpleParameters = null;

          /**
           * The list with the wrapped extent parameters, created lazily.
           */
          private List<AbstractExtentParameter> extentParameters = null;

          /** All parameter references, in their defining order. */
          private ArrayList<AbstractParameter> allParameters = new ArrayList<>();

          /**
           * Additional state computed by {@link ControlFlowOps} parameter validation, when the argument and
           * parameter type does not match.  This will be used when the {@link AbstractParameter#postProcessOutput}
           * is executed for an OUTPUT parameter.
           * <p>
           * WARNING: although this will always register via {@link #updateSimpleParameter} in the current scope,
           * it will be 'consumed' before the callee's scope opens, when the OUTPUT parameter is initialized.
           */
          private Map<BaseDataType, Object[]> withConversionCtor = new IdentityHashMap<>();

          /** Flag indicating if any parameter is associated with a table field. */
          private boolean hasTableFields = false;

          /**
           * Some error occurred. Discard any field assigners.
           */
          @Override
          public void abort()
          {
             clear();
          }

          /**
           * Cause each (if any) field assigner in the current scope to assign the current value of
           * its parameter value back to the DMO property with which it is associated.
           *
           * @param   endingRoutine
           *          The legacy name of the routine that is ending when these back assignments are
           *          performed.
           * @param   function
           *          {@code true} if the called routine is a function.
           */
          @Override
          public void processAssignments(String endingRoutine, boolean function)
          {
             // all parameters need to be processed in order - if one fails, the remaining parameters
             // will not be processed.
             if (allParameters == null || allParameters.isEmpty())
             {
                return;
             }

             boolean useBatch = hasTableFields;
             boolean batchError = true;
             BufferManager bufMan = null;
             if (useBatch)
             {
                bufMan = BufferManager.get();
                RecordBuffer.startBatch(bufMan, true);
             }

             try
             {
                for (int i = 0; i < allParameters.size(); i++)
                {
                   AbstractParameter p = allParameters.get(i);
                   try
                   {
                      if (p instanceof AbstractExtentParameter)
                      {
                         ((AbstractExtentParameter) p).assign(endingRoutine, function);
                      }
                      else if (p instanceof AbstractSimpleParameter)
                      {
                         ((AbstractSimpleParameter) p).assign();
                      }
                      else
                      {
                         throw new IllegalStateException("Could not assign parameter " + p.getClass());
                      }
                   }
                   finally
                   {
                      p.release();
                   }
                }

                batchError = false;
             }
             finally
             {
                clear();
                if (useBatch)
                {
                   RecordBuffer.endBatch(bufMan, batchError);
                }
             }
          }

          /**
           * Add a parameter assigner to this scope.
           *
           * @param   assigner
           *          Simple field assigner.
           */
          public void addSimpleParameter(AbstractSimpleParameter assigner)
          {
             if (simpleParameters == null)
             {
                simpleParameters = new IdentityHashMap<>(3);

                if (extentParameters == null)
                {
                   // register only once, if extentParameters is not null, then we already registered
                   TransactionManager.registerOutputParameterAssigner(this);
                }
             }

             hasTableFields = hasTableFields || assigner.isTableField();
             if (allParameters == null)
             {
                allParameters = new ArrayList<>();
             }
             allParameters.add(assigner);

             // use the procedure-local parameter as key for finding and assigning back the value at
             // the end of the procedure to initial Field
             AbstractSimpleParameter old = simpleParameters.put(assigner.getLocalParam(), assigner);
             if (old != null && old != assigner)
             {
                if (LOG.isLoggable(Level.WARNING))
                {
                   LOG.log(Level.WARNING,
                         "Two AbstractOutputParameterAssigner configured with same wrapper variable");
                }
             }
          }

          /**
           * Check if the {@link BaseDataType} passed as parameter is used for lookup of an
           * {@code OUTPUT} parameter.
           *
           * @param   key
           *          The variable for lookup in the current scope.
           *
           * @return  {@code true} if the content of {@code key} will be assigned back on return with
           *          success.
           */
          protected boolean isParameter(BaseDataType key)
          {
             return (simpleParameters != null) && simpleParameters.containsKey(key);
          }

          /**
           * Check if the given array reference was passed as a parameter, by looking into all scopes.
           *
           * @param     key
           *            The array reference.
           *
           * @return    See above.
           */
          protected boolean isExtentParameter(BaseDataType[] key)
          {
             if (extentParameters == null)
             {
                return false;
             }

             for (AbstractExtentParameter param : extentParameters)
             {
                if (param.getParameter() == key)
                {
                   return true;
                }
             }

             return false;
          }

          /**
           * Replace the extent array parameter reference (not the original argument) with the new one.  This is
           * required in cases when the parameter is resized by the top-level block.
           *
           * @param    old
           *           The previous array parameter reference.
           * @param    other
           *           The new reference.
           */
          protected void replaceParameter(BaseDataType[] old, BaseDataType[] other)
          {
             if (extentParameters == null)
             {
                return;
             }

             for (AbstractExtentParameter param : extentParameters)
             {
                if (param.getParameter() == old)
                {
                   param.setParameter(other);
                }
             }
          }

          /**
           * Update the wrapper variable for a {@code AbstractOutputParameterAssigner}.
           *
           * @param   key
           *          The old key variable. May be the intermediary wrapper for a field or the real
           *          variable passed as *OUTPUT parameter.
           * @param   newVar
           *          The new wrapper variable that will be used inside the calling procedure. At the
           *          end of the call its value will be converted using {@code convCtor} and assigned
           *          to wrapped field or original variable.
           * @param   convCtor
           *          The conversion constructor that will cast the new variable to old variable type
           *          in order to be assigned to field.
           */
          protected void updateSimpleParameter(BaseDataType key,
                                               BaseDataType newVar,
                                               Constructor<?> convCtor)
          {
             // save the details for later
             withConversionCtor.put(newVar, new Object[] { key, convCtor });
          }

          /**
           * Registers an extent parameter wrapper class to be included in the scope processing.
           *
           * @param   param
           *          Extent parameter wrapper class.
           */
          public void addExtentParameter(AbstractExtentParameter<?> param)
          {
             if (extentParameters == null)
             {
                extentParameters = new LinkedList<>();

                if (simpleParameters == null)
                {
                   // register only once, if simpleParameters is not null, then we already registered
                   TransactionManager.registerOutputParameterAssigner(this);
                }
             }

             extentParameters.add(param);
             if (allParameters == null)
             {
                allParameters = new ArrayList<>();
             }
             allParameters.add(param);
             hasTableFields = hasTableFields || param.isTableField();
          }

          /**
           * Clears internal data. All information about simple and extent parameters is removed.
           */
          protected void clear()
          {
             // drop all parameters
             hasTableFields = false;
             simpleParameters = null;
             extentParameters = null;
             if (allParameters != null)
             {
                allParameters.clear();
             }
          }
       }
    }