InputOutputExtentParameter.java

/*
** Module   : InputOutputExtentParameter.java
** Abstract : Base class for special wrapper classes used for variables passed as 
**            input-output extent parameters to functions or procedures.
**
** Copyright (c) 2014-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 HC  20140613 Created initial version.
** 002 HC  20141106 Javadocs fixed
** 003 OM  20150706 Changed errors in initialization. Added implicit conversion support.
** 004 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  20210818 Fixed memptr parameter runtime - they share the 'pointer structure' with the passed 
**                  argument.
**     TJD 20220504 Java 11 compatibility minor changes
*/
/*
** 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.lang.reflect.*;
import java.util.logging.*;

/**
 * Special wrapper used for variables passed as input-output extent parameters to 
 * functions or procedures. On normal function and procedure exit, if the reference 
 * for an output or input-output extent parameter has changed, this will update 
 * the caller's variable reference too.
 */
public abstract class InputOutputExtentParameter<T extends BaseDataType> 
extends AbstractExtentParameter<T>
{
   /**
    * The constructor responsible for instance initialization by calling 
    * {@link AbstractExtentParameter#AbstractExtentParameter(BaseDataType[])}.
    */
   public InputOutputExtentParameter()
   {
      super(null);
   }
   
   /**
    * This constructor allows to provide additional initialization logic of the variable 
    * reference in the extending classes. The initialization logic is encapsulated in 
    * {@link AbstractExtentParameter#AbstractExtentParameter(BaseDataType[])}.
    * 
    * @param   variable
    *          A valid and initialized variable reference.
    */
   protected InputOutputExtentParameter(T[] variable)
   {
      super(variable);
   }
   
   /**
    * Initializes the indeterminate or determinate extent parameter reference depending
    * on the extent (array size) of the value returned by {@link #getVariable()}. 
    * A zero value will initialize an indeterminate extent, a positive integer will 
    * initialize a determinate extent. The extent will be registered with 
    * {@link ArrayAssigner} accordingly. 
    * 
    * @return  The initialized indeterminate extent parameter reference.
    */
   @Override
   public T[] initParameter()
   {
      T[] var = getVariableSafe();
      return initParameter(var.length);
   }

   /**
    * Initializes the indeterminate or determinate extent parameter reference depending
    * on the {@code extent} value. A zero value will initialize an indeterminate
    * extent, a positive integer will initialize a determinate extent. The extent
    * will be registered with {@link ArrayAssigner} accordingly. 
    * 
    * @param   extent
    *          The parameter extent size.
    * 
    * @return  The initialized indeterminate extent parameter reference.
    */
   @Override
   @SuppressWarnings("unchecked")
   public T[] initParameter(int extent)
   {
      // get the parameter reference
      T[] param = getParameter();
      if (param != null)
      {
         // the parameter is already initialized, just return it
         return param;
      }
      
      // initialize the parameter reference according to the variable reference
      T[] var = getVariableSafe();
      Class<?> varComponentClass = (paramType != null)
                                   ? paramType
                                   : var.getClass().getComponentType();
      boolean isMemptr = memptr.class.isAssignableFrom(varComponentClass);
      if (var.length < extent)
      {
         // The passed-in variable is an unfixed indeterminate extent, allocate
         // the parameter variable to the expected extent size. 
         // Don't use ArrayAssigner.resize in this case, as it also copies decimal
         // precision. According to Progress behavior the newly allocated array 
         // will have the default decimal precision.
         param = (T[]) Array.newInstance(varComponentClass, extent);
         for (int i = 0; i < extent; i++)
         {
            param[i] = (T) BaseDataType.generateUnknown(varComponentClass);
            
            if (isMemptr && i < var.length)
            {
               ((memptr) param[i]).asParameter((memptr) var[i]);
            }
         }
         
         setParameter(param);
      }
      else
      {
         // The passed-in variable is a fixed extent, allocate the parameter 
         // variable to its extent size. 
         param = (T[]) Array.newInstance(varComponentClass, var.length);
         for (int i = 0; i < extent; i++)
         {
            // duplicate the array content
            try
            {
               if (isMemptr)
               {
                  memptr m = new memptr();
                  m.asParameter((memptr) var[i]);
                  param[i] = (T) m;
               }
               else
               {
                  param[i] = (convCtor == null)
                             ? (T) var[i].duplicate()
                             : (T) convCtor.newInstance(var[i]);
               }
            }
            catch (ReflectiveOperationException e)
            {
               // constructor-related exception should not happen, the constructor have been
               // detected based on actual data types
               String msg = "Failed to convert INPUT-OUTPUT value from " + var[i].getClass() +
                            " to " + param[i].getTypeName() + " using " + convCtor +
                            " conversion constructor.";
               LOG.log(Level.WARNING, msg);
            }
            if (decimal.class.equals(varComponentClass))
            {
               // and set to the default decimal precision
               ((decimal) param[i]).setPrecision(decimal.MAX_SCALE);
            }
         }
         
         setParameter(param);
         
         if (extent == 0)
         {
            // the result is registered as dynamic array 
            ArrayAssigner.registerDynamicArray(param);
            
            // make sure this extent parameter is tracked by ArrayAssigner,
            // to handle parameter value updates on resizes for example
            ArrayAssigner.registerExtentParameter(this);
         }
      }
      
      return param;
   }
}