# Call.java

    /*
    ** Module   : Call.java
    ** Abstract : The implementation of a CALL resource.
    **
    ** Copyright (c) 2018-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- -------------------------------------Description----------------------------------------
    ** 001 CA  20181029 Created initial version.
    ** 002 CA  20181220 Changed setParameter to have int64 as first argument.
    ** 003 CA  20190219 Some minor fixes.
    ** 004 IAS 20190501 Added SECURITY-POLICY support.
    **         20190506 Added AUDIT-POLICY, AUDIT-CONTROL support.
    ** 005 CA  20190628 Changes caused by moving InvokeConfig.invoke to ControlFlowOps.
    ** 006 OM  20190523 Unified parameter options handling for TempTables and DataSet parameters.
    ** 007 CA  20190710 Extracted inner classes to their own top-level type, so they can be reused by OO
    **                  ParameterList.
    ** 008 CA  20200122 Javadoc fixes.
    ** 009 ME  20200810 Improvements in parameter processing including error handling and delayed validation for
    **                  table/dataset handle input parameters.
    **     CA  20200924 Replaced Method.invoke with ReflectASM.
    **     SVL 20201009 Reflected changes in FieldReference.
    **     ME  20201009 Keep initial value for input/input-output parameters, 4GL uses the value at the time
    **                  setParameter was called although the variable might mutate before invoke.
    **     CA  20210216 Fixed an issue when setParameter passes an unknown literal as the value argument.
    **     OM  20210309 Removed double display of error message number.
    **     ME  20210507 Improve error handling for POLY conversion, OO parameter validation.
    **     CA  20210525 Fixed a problem with Progress.Lang.Object arguments.
    **     CA  20210609 Fixed OO property references used as arguments.
    **                  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  20220120 Do not used TypeFactory.object when the OO reference must not be tracked or registered.
    **     ME  20220214 Fix variable class type check for block's inline variables.
    **     CA  20220930 Refactored the callback invocation to be performed via a call-site and InvokeConfig, to
    **                  allow caching of the resolved target.
    **     CA  20221006 UNIQUE-ID is kept as Java type instead of BDT.  Refs #6827
    **     OM  20221124 Added special processing for buffer-fields passed as argument via
    **                  CALL:SET-PARAMETER or to Progress.Lang.ParameterList:SetParameter.
    **     OM  20221216 Reworked table and dataset handle parameters.
    **     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 using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
    ** 010 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 011 CA  20230724 Further reduce context-local usage.
    ** 012 CA  20230802 Reset the OutputParameterAssigner after invoke finishes, as the parameters are copied
    **                  explicitly, without the OutputParameterAssigner support.
    ** 013 CA  20231031 Fixed unknown literal parameter.
    ** 014 CA  20231107 Fixed string representation of the call mode, which must not include any option.
    ** 015 CA  20240215 Fixed issues using CALL property/field references as arguments, associated with a
    **                  DATASET/TABLE-HANDLE parameter.
    ** 016 CA  20240708 Allow RETURN ERROR to propagate to the caller for Progress.Lang.Class:Invoke and CALL:Invoke.
    ** 017 PBB 20250509 Added validation for SetParameter method when parameter is an extent.
    ** 018 PBB 20250522 Fixed part of the behavior related to SetParameter object method when giving a
    **                  DYNAMIC-PROPERTY call as argument.
    ** 019 ES  20250522 Added setterAssignment method that validates and assign the value to variable instance.
    */

    /*
    ** 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.*;
    import java.util.logging.*;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;

    import com.goldencode.p2j.convert.*;
    import com.goldencode.p2j.library.*;
    import com.goldencode.p2j.oo.lang._BaseObject_;
    import com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.uast.*;
    import com.goldencode.p2j.ui.*;
    import com.goldencode.p2j.util.UniqueIdGenerator.IdKind;
    import com.goldencode.p2j.util.logging.*;

    /**
     * Defines a CALL resources.
     */
    public class Call
    extends HandleChain
    implements CallResource
    {
       /** Constant mapping the legacy PROCEDURE-CALL-TYPE value. */
       public static final int PROCEDURE_CALL_TYPE = 1;

       /** Constant mapping the legacy FUNCTION-CALL-TYPE value. */
       public static final int FUNCTION_CALL_TYPE = 2;

       /** Constant mapping the legacy GET-ATTR-CALL-TYPE value. */
       public static final int GET_ATTR_CALL_TYPE = 3;

       /** Constant mapping the legacy SET-ATTR-CALL-TYPE value. */
       public static final int SET_ATTR_CALL_TYPE = 4;

       /** Constant mapping the legacy DLL-CALL-TYPE value. */
       public static final int DLL_CALL_TYPE = 5;

       /** Logger */
       static final CentralLogger LOG = CentralLogger.get(Call.class.getName());

       /** The call-site associated with the call's invocation. */
       private static final InvokeConfig CALL_SITE = new InvokeConfig();

       /** The first value of valid call types. */
       private static final int BEGIN_CALL_TYPE = PROCEDURE_CALL_TYPE;

       /** The last value of valid call types. */
       private static final int END_CALL_TYPE = DLL_CALL_TYPE;

       /** A cache of attributes and methods token types to their actual type. */
       private static final Map<Integer, Integer> ATTRIBUTES_AND_METHODS =
                                                  ProgressParser.getAttributesAndMethods();

       /** A cache of system handle keywords. */
       private static final Set<String> SYS_HANDLES = ProgressParser.getSysHandles();

       /** A cache of data types. */
       private static final Set<String> DATA_TYPES = new HashSet<>();

       private static Pattern dataTypePattern = Pattern.compile("^(CLASS\\s+)?([\\w|\\.]+)(\\s+EXTENT(\\s+\\d+)?)?$", Pattern.CASE_INSENSITIVE);

       static
       {
          DATA_TYPES.add("BLOB");
          DATA_TYPES.add("CHARACTER");
          DATA_TYPES.add("CLOB");
          DATA_TYPES.add("DATASET-HANDLE");
          DATA_TYPES.add("DATE");
          DATA_TYPES.add("DATETIME");
          DATA_TYPES.add("DATETIME-TZ");
          DATA_TYPES.add("DECIMAL");
          DATA_TYPES.add("HANDLE");
          DATA_TYPES.add("INT64");
          DATA_TYPES.add("INTEGER");
          DATA_TYPES.add("LOGICAL");
          DATA_TYPES.add("LONGCHAR");
          DATA_TYPES.add("MEMPTR");
          DATA_TYPES.add("RAW");
          DATA_TYPES.add("RECID");
          DATA_TYPES.add("ROWID");
          DATA_TYPES.add("TABLE-HANDLE");
       }

       /** The value of the ADM-DATA attribute. */
       private String admData = null;

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

       /** Flag indicating if this resource has been deleted or not. */
       private boolean deleted = false;

       /** The value of the CALL-NAME attribute. */
       private String callName;

       /** The value of the CALL-TYPE attribute. */
       private int callType;

       /** The value of the IN-HANDLE attribute. */
       private handle inHandle;

       /** The value of the NUM-PARAMETERS attribute. */
       private int numParameters;

       /** The value of the RETURN-VALUE attribute. */
       private BaseDataType returnValue;

       /** The value of the RETURN-VALUE-DATA-TYPE attribute. */
       private String returnValueDataType;

       /** The value of the SERVER attribute. */
       private handle server;

       /** The value of the PERSISTENT attribute. */
       private boolean persistent;

       /** Flag indicating if this dynamic run will be emulated in SINGLE-RUN mode. */
       private boolean singleRun;

       /** Flag indicating if this dynamic run will be emulated in SINGLETON mode. */
       private boolean singleton;

       /** The value of the PROCEDURE-TYPE attribute. */
       private String procedureType;

       /** The value of the THREAD-SAFE attribute. */
       private boolean threadSafe;

       /** The value of the ASYNCHRONOUS attribute. */
       private boolean async;

       /** The value of the ASYNC-REQUEST-HANDLE attribute. */
       private handle asyncHandle;

       /** The value of the EVENT-PROCEDURE attribute. */
       private String eventProcedure;

       /** The value of the EVENT-PROCEDURE-CONTEXT attribute. */
       private handle eventProcedureContext;

       /** The value of the ORDINAL attribute. */
       private Integer ordinal;

       /** The value of the LIBRARY attribute. */
       private String library;

       /** The value of the RETURN-VALUE-DLL-TYPE attribute. */
       private String returnValueDllType;

       /** The value of the LIBRARY-CALLING-CONVENTION attribute. */
       private String libraryCallingConvention;

       /**
        * The list of configured parameters.  Created when {@link #setNumParameters NUM-PARAMETERS}
        * attribute is set.
        */
       private CallParameter[] parameters;

       /**
        * Create a new resource and {@link #clear() initialize} it with the default values.
        */
       private Call()
       {
          super(true, false);
          clear();
       }

       /**
        * Create a new resource and assign it to the specified handle.
        *
        * @param    h
        *           The handle where to save the resource.  Must be not-null.
        */
       public static void create(handle h)
       {
          create(h, (character) null);
       }

       /**
        * Create a new resource and assign it to the specified handle.
        * <p>
        * If a widget pool is not specified, then the resource will be added to the SESSION pool, and
        * not the closest unnamed pool.
        *
        * @param    h
        *           The handle where to save the resource.  Must be not-null.
        * @param    widgetPool
        *           The named widget pool where to save the widget. If <code>null</code>, the SESSION
        *           widget pool will be used.
        */
       public static void create(handle h, character widgetPool)
       {
          // validate widget pool before anything else
          // unnamed pools are not used here, if one not specified, the SESSION pool will be used
          if (widgetPool != null && !WidgetPool.validWidgetPool(widgetPool))
          {
             return;
          }

          Call c = new Call();
          h.assign(c);

          if (widgetPool != null)
          {
             c.addToPool(widgetPool);
          }
       }

       /**
        * Create a new resource and assign it to the specified handle.
        * <p>
        * If a widget pool is not specified, then the resource will be added to the SESSION pool, and
        * not the closest unnamed pool.
        *
        * @param    h
        *           The handle where to save the resource.  Must be not-null.
        * @param    widgetPool
        *           The named widget pool where to save the widget. If <code>null</code>, the SESSION
        *           widget pool will be used.
        */
       public static void create(handle h, String widgetPool)
       {
          create(h, new character(widgetPool));
       }

       /**
        * Used for wrapping the last parameter of {@code CALL:SET-PARAMETER} method in order for the method to
        * properly detect whether it is a valid output parameter. In affirmative case, this static method will
        * return a {@code BaseDataTypeVariable}. Otherwise, the original object is returned.
        * <p>
        * This wrapper is injected only when the last parameter (the value) of {@code SET-PARAMETER} is detected
        * at conversion to be a {@code BUFFER-VALUE()} result.
        *
        * @param   aHandle
        *          A handle, possible of a buffer-field.
        *
        * @return  If the handle is indeed a valid BUFFER-FIELD, a custom-made {@code BaseDataTypeVariable} is
        *          created and returned. Otherwise, the original code is restored.
        */
       public static Object valueReference(handle aHandle)
       {
          if (aHandle.isType(LegacyResource.BUFFER_FIELD))
          {
             BufferFieldImpl field = (BufferFieldImpl) aHandle.getResource();
             return field.createFieldReference(null);
          }

          return aHandle.unwrapBufferField().value();
       }

       /**
        * Used for wrapping the last parameter of {@code CALL:SET-PARAMETER} method in order for the method to
        * properly detect whether it is a valid output parameter. In affirmative case, this static method will
        * return a {@code BaseDataTypeVariable}. Otherwise, the original object is returned.
        * <p>
        * This wrapper is injected only when the last parameter (the value) of {@code SET-PARAMETER} is detected
        * at conversion to be a {@code BUFFER-VALUE(k)} result.
        *
        * @param   aHandle
        *          A handle, possible of a buffer-field.
        * @param   extIndex
        *          The index for a possible extent field.
        *
        * @return  If the handle is indeed a valid BUFFER-FIELD, a custom-made {@code BaseDataTypeVariable} is
        *          created and returned. Otherwise, the original code is restored.
        */
       public static Object valueReference(handle aHandle, Integer extIndex)
       {
          if (aHandle.isType(LegacyResource.BUFFER_FIELD))
          {
             BufferFieldImpl field = (BufferFieldImpl) aHandle.getResource();
             return field.createFieldReference(extIndex);
          }

          return aHandle.unwrapBufferField().value(extIndex);
       }

       /**
        * Used for wrapping the last parameter of {@code CALL:SET-PARAMETER} method in order for the method to
        * properly detect whether it is a valid output parameter. In affirmative case, this static method will
        * return a {@code BaseDataTypeVariable}. Otherwise, the original object is returned.
        * <p>
        * This wrapper is injected only when the last parameter (the value) of {@code SET-PARAMETER} is detected
        * at conversion to be a {@code BUFFER-VALUE(k)} result.
        *
        * @param   aHandle
        *          A handle, possible of a buffer-field.
        * @param   k
        *          The index for a possible extent field.
        *
        * @return  If the handle is indeed a valid BUFFER-FIELD, a custom-made {@code BaseDataTypeVariable} is
        *          created and returned. Otherwise, the original code is restored.
        */
       public static Object valueReference(handle aHandle, NumberType k)
       {
          return valueReference(aHandle, k.isUnknown() ? null : k.intValue());
       }

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

       /**
        * Gets the the unique ID number associated to this object by the
        * underlying system. Not the same as the handle value itself.
        *
        * @return   The integer number of the children.
        */
       @Override
       public integer getUniqueID()
       {
          if (uniqueID == null)
          {
             uniqueID = UniqueIdGenerator.getUniqueId(IdKind.CALL);
          }

          return new integer(uniqueID);
       }

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

       /**
        * Get the <code>persistent</code> attribute of this procedure instance.
        *
        * @return   See above.
        */
       @Override
       public logical isPersistent()
       {
          return new logical(persistent);
       }

       /**
        * Set the <code>persistent</code> attribute of this CALL request.
        *
        * @param    persistent
        *           The flag indicating how the call will be performed.
        */
       @Override
       public void setPersistent(boolean persistent)
       {
          if (singleRun || singleton)
          {
             int[] nums = { 17255, 3131 };
             String[] texts =
             {
                "Cannot set PERSISTENT attribute to TRUE when PROCEDURE-TYPE attribute has " +
                "already been set to " + procedureType + " (17255)",
                "Unable to set attribute PERSISTENT in widget  of type CALL. (3131)"
             };

             ErrorManager.recordOrThrowError(nums, texts, false, true);
             return;
          }

          this.persistent = persistent;
          this.procedureType = "PERSISTENT";
       }

       /**
        * Set the <code>persistent</code> attribute of this CALL request.
        *
        * @param    persistent
        *           The flag indicating how the call will be performed.
        */
       @Override
       public void setPersistent(logical persistent)
       {
          if (persistent.isUnknown())
          {
             unknownWarning("PERSISTENT");
             return;
          }

          setPersistent(persistent.booleanValue());
       }

       /**
        * Returns a handle to the app server in which the procedure is ran.
        *
        * @return   See above
        */
       @Override
       public handle getServerHandle()
       {
          return server == null ? new handle() : new handle(server);
       }

       /**
        * Sets a handle for the app server in which the procedure will be ran.
        *
        * @param    h
        *           The server handle.
        */
       @Override
       public void setServerHandle(handle h)
       {
          if (h.isUnknown())
          {
             unknownWarning("SERVER");
             return;
          }

          server = new handle(h);
       }

       /**
        * Get the <code>CALL-NAME</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public character getCallName()
       {
          return callName == null ? new character() : new character(callName);
       }

       /**
        * Set the <code>CALL-NAME</code> attribute of this CALL resource.
        *
        * @param    callName
        *           The target's name (attribute, method, RUN's invoke target, native API).
        */
       @Override
       public void setCallName(character callName)
       {
          if (callName.isUnknown())
          {
             unknownWarning("CALL-NAME");
             return;
          }

          setCallName(callName.getValue());
       }

       /**
        * Set the <code>CALL-NAME</code> attribute of this CALL resource.
        *
        * @param    callName
        *           The target's name (attribute, method, RUN's invoke target, native API).
        */
       @Override
       public void setCallName(String callName)
       {
          if (ordinal != null)
          {
             callOrdinalError("CALL-NAME");
             return;
          }

          this.callName = callName;
       }

       /**
        * Get the <code>CALL-TYPE</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public integer getCallType()
       {
          return new integer(callType);
       }

       /**
        * Set the <code>CALL-TYPE</code> attribute of this CALL resource.
        *
        * @param    callType
        *           One of the call-type constant values.
        */
       @Override
       public void setCallType(NumberType callType)
       {
          if (callType.isUnknown())
          {
             unknownWarning("CALL-TYPE");
             return;
          }

          setCallType(callType.intValue());
       }

       /**
        * Set the <code>CALL-TYPE</code> attribute of this CALL resource.
        *
        * @param    callType
        *           One of the call-type constant values.
        */
       @Override
       public void setCallType(int callType)
       {
          if (!(callType >= BEGIN_CALL_TYPE && callType <= END_CALL_TYPE))
          {
             unableToSetAttribute(10066,
                                  "CALL object CALL-TYPE must be an integer between 1 and 5",
                                  "CALL-TYPE");
             return;
          }

          this.callType = callType;
       }

       /**
        * Check if the specified parameter is set.
        *
        * @param    paramNum
        *           The 1-based parameter index.
        *
        * @return   <code>true</code> if the parameter is set.
        */
       @Override
       public logical isParameterSet(NumberType paramNum)
       {
          if (paramNum.isUnknown())
          {
             // no warning is shown
             return new logical();
          }

          return isParameterSet(paramNum.intValue());
       }

       /**
        * Check if the specified parameter is set.
        *
        * @param    paramNum
        *           The 1-based parameter index.
        *
        * @return   <code>true</code> if the parameter is set.
        */
       @Override
       public logical isParameterSet(int paramNum)
       {
          if (parameters == null)
          {
             // no warning is shown
             return new logical();
          }

          if (paramNum < 1 || paramNum > numParameters)
          {
             // no warning is shown
             return new logical();
          }

          return new logical(parameters[paramNum - 1] != null);
       }

       /**
        * Get the <code>NUM-PARAMETERS</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public integer getNumParameters()
       {
          return new integer(numParameters);
       }

       /**
        * Set the <code>NUM-PARAMETERS</code> attribute of this CALL resource.
        *
        * @param    num
        *           The number of parameters.
        */
       @Override
       public void setNumParameters(NumberType num)
       {
          if (num.isUnknown() || num.intValue() < 1)
          {
             if (num.isUnknown())
             {
                unknownWarning("NUM-PARAMETERS");
             }

             parameters = null;

             if (!num.isUnknown())
             {
                numParameters = num.intValue();
             }

             return;
          }

          numParameters = num.intValue();
          parameters = new CallParameter[numParameters];
       }

       /**
        * Set the <code>NUM-PARAMETERS</code> attribute of this CALL resource.
        *
        * @param    num
        *           The number of parameters.
        */
       @Override
       public void setNumParameters(long num)
       {
          setNumParameters(new integer(num));
       }

       /**
        * Get the <code>IN-HANDLE</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public handle getInHandle()
       {
          return inHandle == null ? new handle() : new handle(inHandle);
       }

       /**
        * Set the <code>IN-HANDLE</code> attribute of this CALL resource.
        *
        * @param    h
        *           The external program where the target function/procedure will be invoked or the
        *           resource for which the attribute/method will be accessed.
        */
       @Override
       public void setInHandle(handle h)
       {
          if (h.isUnknown())
          {
             unknownWarning("IN-HANDLE");
             return;
          }

          inHandle = new handle(h);
       }

       /**
        * Set the <code>IN-HANDLE</code> attribute of this CALL resource.
        *
        * @param    resource
        *           The external program where the target function/procedure will be invoked or the
        *           resource for which the attribute/method will be accessed.
        */
       @Override
       public void setInHandle(character resource)
       {
          setInHandle(resource.isUnknown() ? null : resource.getValue());
       }

       /**
        * Set the <code>IN-HANDLE</code> attribute of this CALL resource.
        *
        * @param    resource
        *           The external program where the target function/procedure will be invoked or the
        *           resource for which the attribute/method will be accessed.
        */
       @Override
       public void setInHandle(String resource)
       {
          boolean valid = (resource != null);

          if (!valid)
          {
             unknownWarning("IN-HANDLE");
             return;
          }
          else
          {
             // the IN-HANDLE is reset now
             inHandle = null;

             String tokenText = ProgressAst._keyword(resource);

             valid = (tokenText != null && SYS_HANDLES.contains(tokenText));

             if (valid)
             {
                int tokenType = ProgressAst.lookupKeywordToken(resource);

                switch (tokenType)
                {
                   case ProgressParserTokenTypes.KW_ACT_WIN:
                      inHandle = LogicalTerminal.activeWindow();
                      break;
                   case ProgressParserTokenTypes.KW_AUD_CTRL:
                      inHandle = AuditControlManager.asHandle();
                      break;
                   case ProgressParserTokenTypes.KW_AUD_POL:
                      inHandle = AuditPolicyManager.asHandle();
                      break;
                   case ProgressParserTokenTypes.KW_CLIP:
                      inHandle = ClipboardManager.asHandle();
                      break;
                   case ProgressParserTokenTypes.KW_CODEBASE:
                      // TODO: assign this after FWD supports this resource
                      break;
                   case ProgressParserTokenTypes.KW_COLR_TAB:
                      inHandle = ColorTable.asHandle();
                      break;
                   case ProgressParserTokenTypes.KW_COM_SELF:
                      // TODO: assign this after FWD supports this resource
                      break;
                   case ProgressParserTokenTypes.KW_COMPILER:
                      // TODO: assign this after FWD supports this resource
                      break;
                   case ProgressParserTokenTypes.KW_CUR_WIN:
                      inHandle = LogicalTerminal.currentWindow();
                      break;
                   case ProgressParserTokenTypes.KW_DEBUGGER:
                      // TODO: assign this after FWD supports this resource
                      break;
                   case ProgressParserTokenTypes.KW_DEF_WIN:
                      inHandle = LogicalTerminal.defaultWindow();
                      break;
                   case ProgressParserTokenTypes.KW_DSL_MGR:
                      inHandle = LegacyLogOps.dsLogMgrHandle();
                      break;
                   case ProgressParserTokenTypes.KW_ERR_STAT:
                      inHandle = ErrorManager.asHandle();
                      break;
                   case ProgressParserTokenTypes.KW_FIL_INFO:
                      inHandle = FileSystemOps.asHandle();
                      break;
                   case ProgressParserTokenTypes.KW_FOCUS:
                      inHandle = LogicalTerminal.focus();
                      break;
                   case ProgressParserTokenTypes.KW_FONT_TAB:
                      inHandle = FontTable.asHandle();
                      break;
                   case ProgressParserTokenTypes.KW_LAST_EVT:
                      inHandle = KeyReader.asHandle();
                      break;
                   case ProgressParserTokenTypes.KW_LOG_MGR:
                      inHandle = LegacyLogOps.logMgrHandle();
                      break;
                   case ProgressParserTokenTypes.KW_PROFILER:
                      inHandle = ProfilerUtils.asHandle();
                      break;
                   case ProgressParserTokenTypes.KW_RCOD_INF:
                      inHandle = RcodeInfoOps.asHandle();
                      break;
                   case ProgressParserTokenTypes.KW_SECUR_P:
                       inHandle = SecurityPolicyManager.asHandle();
                      break;
                   case ProgressParserTokenTypes.KW_SELF:
                      inHandle = LogicalTerminal._self();
                      break;
                   case ProgressParserTokenTypes.KW_SESSION:
                      inHandle = SessionUtils.asHandle();
                      break;
                   case ProgressParserTokenTypes.KW_SRC_PROC:
                      inHandle = ProcedureManager.sourceProcedure();
                      break;
                   case ProgressParserTokenTypes.KW_TAR_PROC:
                      inHandle = ProcedureManager.targetProcedure();
                      break;
                   case ProgressParserTokenTypes.KW_THIS_PRC:
                      inHandle = ProcedureManager.thisProcedure();
                      break;
                   case ProgressParserTokenTypes.KW_WEB_CTX:
                      // TODO: assign this after FWD supports this resource
                      break;
                }

                valid = (inHandle != null);
             }
          }

          if (!valid)
          {
             int[] nums = new int[] { 10064, 3131 };
             String[] texts = new String[]
             {
                "Invalid character system handle expression  given to IN-HANDLE (must be a system " +
                "handle name like SESSION, FILE-INFO, etc.)",
                "Unable to set attribute IN-HANDLE in widget  of type CALL"
             };
             ErrorManager.recordOrThrowError(nums, texts, false);

             return;
          }
       }

       /**
        * Get the <code>PROCEDURE-TYPE</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public character getProcedureType()
       {
          return procedureType == null ? new character() : new character(procedureType);
       }

       /**
        * Set the <code>PROCEDURE-TYPE</code> attribute of this CALL resource.
        *
        * @param    procType
        *           The procedure's type, one of PERSISTENT, SINGLE-RUN or SINGLETON strings.
        */
       @Override
       public void setProcedureType(character procType)
       {
          if (procType.isUnknown())
          {
             // no error is raised
             procType = null;
             return;
          }

          setProcedureType(procType.getValue());
       }

       /**
        * Set the <code>PROCEDURE-TYPE</code> attribute of this CALL resource.
        *
        * @param    procType
        *           The procedure's type, one of PERSISTENT, SINGLE-RUN or SINGLETON strings.
        */
       @Override
       public void setProcedureType(String procType)
       {
          String stype = procType.toUpperCase();
          if (!("PERSISTENT".equals(stype) || "SINGLE-RUN".equals(stype) || "SINGLETON".equals(stype)))
          {
             // Unable to set PROCEDURE-TYPE CALL attribute (15482)
             ErrorManager.recordOrShowError(15482,
                                            "Unable to set PROCEDURE-TYPE CALL attribute (15482)",
                                            false, false, false, true);
             return;
          }

          if (persistent && !"PERSISTENT".equals(stype))
          {
             int[] nums = { 17254, 3131 };
             String[] texts =
             {
                "Cannot set PROCEDURE-TYPE attribute to " + stype + " when PERSISTENT attribute " +
                "has already been set to TRUE (17254)",
                "Unable to set attribute PROCEDURE-TYPE in widget  of type CALL. (3131)"
             };

             ErrorManager.recordOrThrowError(nums, texts, false, true);
             return;
          }

          this.procedureType = stype;
          this.persistent = "PERSISTENT".equals(stype);
          this.singleRun = "SINGLE-RUN".equals(stype);
          this.singleton = "SINGLETON".equals(stype);
       }

       /**
        * Get the <code>THREAD-SAFE</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public logical isThreadSafe()
       {
          return new logical(threadSafe);
       }

       /**
        * Set the <code>THREAD-SAFE</code> attribute of this CALL resource.
        *
        * @param    threadSafe
        *           The new state of this flag.
        */
       @Override
       public void setThreadSafe(logical threadSafe)
       {
          if (threadSafe.isUnknown())
          {
             unknownWarning("THREAD-SAFE");
             return;
          }

          setThreadSafe(threadSafe.booleanValue());
       }

       /**
        * Set the <code>THREAD-SAFE</code> attribute of this CALL resource.
        *
        * @param    threadSafe
        *           The new state of this flag.
        */
       @Override
       public void setThreadSafe(boolean threadSafe)
       {
          this.threadSafe = threadSafe;
       }

       /**
        * Get the <code>RETURN-VALUE</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public BaseDataType getReturnValue()
       {
          return returnValue == null ? new character() : returnValue;
       }

       /**
        * Get the <code>RETURN-VALUE-DATA-TYPE</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public character getReturnValueDataType()
       {
          return returnValueDataType == null ? new character() : new character(returnValueDataType);
       }

       /**
        * Set the <code>RETURN-VALUE-DATA-TYPE</code> attribute of this CALL resource.
        *
        * @param    dataType
        *           The return type for the next call.
        */
       @Override
       public void setReturnValueDataType(character dataType)
       {
          if (dataType.isUnknown())
          {
             unknownWarning("RETURN-VALUE-DATA-TYPE");
             return ;
          }

          setReturnValueDataType(dataType.getValue());
       }

       /**
        * Set the <code>RETURN-VALUE-DATA-TYPE</code> attribute of this CALL resource.
        *
        * @param    dataType
        *           The return type for the next call.
        */
       @Override
       public void setReturnValueDataType(String dataType)
       {
          dataType = ProgressAst._keyword(dataType);
          if ("log".equalsIgnoreCase(dataType))
          {
             // we have a conflict with 'log' function
             dataType = "logical";
          }

          if (BaseDataType.fromTypeName(dataType) == null)
          {
             unableToSetAttribute(10067,
                                  "Unable to set CALL object RETURN-VALUE-DATA-TYPE to " + dataType,
                                  "RETURN-VALUE-DATA-TYPE");
             return;
          }

          this.returnValueDataType = dataType;
       }

       /**
        * Get the <code>LIBRARY</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public character getLibrary()
       {
          return library == null ? new character() : new character(library);
       }

       /**
        * Set the <code>LIBRARY</code> attribute of this CALL resource.
        *
        * @param    libName
        *           The native library name.
        */
       @Override
       public void setLibrary(character libName)
       {
          if (libName.isUnknown())
          {
             unknownWarning("LIBRARY");
             return;
          }

          setLibrary(libName.getValue());
       }

       /**
        * Set the <code>LIBRARY</code> attribute of this CALL resource.
        *
        * @param    libName
        *           The native library name.
        */
       @Override
       public void setLibrary(String libName)
       {
          if (this.library != null)
          {
             // this is reset if the library name was previously set
             callName = "";
          }

          this.library = libName;
       }

       /**
        * Get the <code>LIBRARY-CALLING-CONVENTION</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public character getLibraryCallingConvention()
       {
          return libraryCallingConvention == null ? new character()
                                                  : new character(libraryCallingConvention);
       }

       /**
        * Set the <code>LIBRARY-CALLING-CONVENTION</code> attribute of this CALL resource.
        *
        * @param    libCall
        *           The library's calling convention, CDECL or STDCALL.
        */
       @Override
       public void setLibraryCallingConvention(character libCall)
       {
          if (libCall.isUnknown())
          {
             unknownWarning("LIBRARY-CALLING-CONVENTION");
             return;
          }

          setLibraryCallingConvention(libCall.getValue());
       }

       /**
        * Set the <code>LIBRARY-CALLING-CONVENTION</code> attribute of this CALL resource.
        *
        * @param    libCall
        *           The library's calling convention, CDECL or STDCALL.
        */
       @Override
       public void setLibraryCallingConvention(String libCall)
       {
          libCall = libCall.toUpperCase();

          if (!("STDCALL".equals(libCall) || "CDECL".equals(libCall)))
          {
             // Unable to set LIBARARY-CALLING-COVENTION CALL attribute (15482)
             ErrorManager.recordOrShowError(15482,
                                            "Unable to set LIBARARY-CALLING-COVENTION CALL attribute (15482)",
                                            false, false, false, true);
             return;
          }

          libraryCallingConvention = libCall;
       }

       /**
        * Get the <code>ORDINAL</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public integer getOrdinal()
       {
          return ordinal == null ? new integer() : new integer(ordinal);
       }

       /**
        * Set the <code>ORDINAL</code> attribute of this CALL resource.
        *
        * @param    ordinal
        *           The native API ordinal, if CALL-NAME is not specified.
        */
       @Override
       public void setOrdinal(NumberType ordinal)
       {
          if (ordinal.isUnknown())
          {
             unknownWarning("ORDINAL");
             return;
          }

          setOrdinal(ordinal.intValue());
       }

       /**
        * Set the <code>ORDINAL</code> attribute of this CALL resource.
        *
        * @param    ordinal
        *           The native API ordinal, if CALL-NAME is not specified.
        */
       @Override
       public void setOrdinal(int ordinal)
       {
          if (callName != null)
          {
             callOrdinalError("ORDINAL");
             return;
          }

          this.ordinal = ordinal;
       }

       /**
        * Get the <code>RETURN-VALUE-DLL-TYPE</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public character getReturnValueDllType()
       {
          return returnValueDllType == null ? new character() : new character(returnValueDllType);
       }

       /**
        * Set the <code>RETURN-VALUE-DLL-TYPE</code> attribute of this CALL resource.
        *
        * @param    dataType
        *           The return type for the next native API call.
        */
       @Override
       public void setReturnValueDllType(character dataType)
       {
          if (dataType.isUnknown())
          {
             unknownWarning("RETURN-VALUE-DLL-TYPE");
             return;
          }

          setReturnValueDllType(dataType.getValue());
       }

       /**
        * Set the <code>RETURN-VALUE-DLL-TYPE</code> attribute of this CALL resource.
        *
        * @param    dataType
        *           The return type for the next native API call.
        */
       @Override
       public void setReturnValueDllType(String dataType)
       {
          dataType = dataType.toLowerCase();

          if (!validDllType(dataType))
          {
             unableToSetAttribute(15483,
                                  "Unable to set CALL object RETURN-VALUE-DLL-TYPE to " + dataType,
                                  "RETURN-VALUE-DLL-TYPE");
             return;
          }

          this.returnValueDllType = dataType;
       }

       /**
        * Get the <code>ASYNCHRONOUS</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public logical isAsync()
       {
          return new logical(async);
       }

       /**
        * Set the <code>ASYNCHRONOUS</code> attribute of this CALL resource.
        *
        * @param    l
        *           The new state of this flag.
        */
       @Override
       public void setAsync(logical l)
       {
          if (l.isUnknown())
          {
             unknownWarning("ASYNCHRONOUS");
             return;
          }

          setAsync(l.booleanValue());
       }

       /**
        * Set the <code>ASYNCHRONOUS</code> attribute of this CALL resource.
        *
        * @param    l
        *           The new state of this flag.
        */
       @Override
       public void setAsync(boolean l)
       {
          this.async = l;
       }

       /**
        * Get the <code>ASYNC-REQUEST-HANDLE</code> attribute of this CALL resource.
        *
        * @return   See above.
        */
       @Override
       public handle getAsyncHandle()
       {
          return new handle(asyncHandle);
       }

       /**
        * Get the <code>EVENT-PROCEDURE</code> attribute of this async request.
        *
        * @return   See above.
        */
       @Override
       public character getEventProcedure()
       {
          return eventProcedure == null ? new character() : new character(eventProcedure);
       }

       /**
        * Set the <code>EVENT-PROCEDURE</code> attribute of this async request.
        *
        * @param    procName
        *           The procedure name.
        */
       @Override
       public void setEventProcedure(character procName)
       {
          if (procName.isUnknown())
          {
             unknownWarning("EVENT-PROCEDURE");
             return;
          }

          setEventProcedure(procName.getValue());
       }

       /**
        * Set the <code>EVENT-PROCEDURE</code> attribute of this async request.
        *
        * @param    procName
        *           The procedure name.
        */
       @Override
       public void setEventProcedure(String procName)
       {
          this.eventProcedure = procName;
       }

       /**
        * Get the <code>EVENT-PROCEDURE-CONTEXT</code> attribute of this async request.
        *
        * @return   See above.
        */
       @Override
       public handle getEventProcedureContext()
       {
          return eventProcedureContext == null ? new handle() : new handle(eventProcedureContext);
       }

       /**
        * Set the <code>EVENT-PROCEDURE-CONTEXT</code> attribute of this async request.
        *
        * @param    h
        *           The procedure handle.
        */
       @Override
       public void setEventProcedureContext(handle h)
       {
          if (h.isUnknown())
          {
             unknownWarning("EVENT-PROCEDURE-CONTEXT");
             return;
          }

          eventProcedureContext = new handle(h);
       }

       /**
        * Returns the resource to its clear state.
        *
        * @return  true if operation is successful
        */
       @Override
       public logical clear()
       {
          callName = null;
          callType = PROCEDURE_CALL_TYPE;
          inHandle = null;
          server = null;
          numParameters = 0;
          parameters = null;
          returnValue = null;
          returnValueDataType = null;
          persistent = false;
          procedureType = null;
          threadSafe = false;

          async = false;
          asyncHandle = new handle();
          eventProcedure = null;
          eventProcedureContext = null;

          ordinal = null;
          library = null;
          returnValueDllType = null;
          libraryCallingConvention = "STDCALL";

          return new logical(true);
       }

       /**
        * Execute this dynamic invoke, based on the current CALL configuration.
        * <p>
        * The dynamic invoke will be delegated to {@link #accessAttributeOrMethod} in case of
        * {@link #GET_ATTR_CALL_TYPE} and {@link #SET_ATTR_CALL_TYPE} modes.
        * <p>
        * For {@link #PROCEDURE_CALL_TYPE} and {@link #FUNCTION_CALL_TYPE}, the call will be delegated
        * to {@link ControlFlowOps#invoke(InvokeConfig)}, which in turn will relies on the normal
        * {@link ControlFlowOps} APIs to perform the call.  If the {@link #PROCEDURE_CALL_TYPE} mode
        *  is used and the target is resolved as an <code>PROCEDURE ... EXTERNAL</code>, then this
        *  will still be invoked using the RUN emulated call.
        * <p>
        * The {@link #DLL_CALL_TYPE} mode will rely on the
        * {@link NativeInvoker#invoke(NativeAPIEntry, boolean, String, Class[], String, Object...)}:
        * this will use a synthetic {@link NativeAPIEntry}, which will be configured as if a
        * <code>PROCEDURE ... EXTERNAL</code> was defined in the 4GL code, using the current
        * {@link Call CALL's} configuration.  The actual invoke will bypass the {@link ControlFlowOps}
        * APIs for this mode.
        */
       @Override
       public void invoke()
       {
          if (callName == null && ordinal == null)
          {
             ErrorManager.recordOrThrowError(14351,
                                             "CALL-NAME or ORDINAL must be specified before using " +
                                             "INVOKE with a CALL object",
                                             false);
             return;
          }

          if (parameters != null)
          {
             for (int i = 0; i < parameters.length; i++)
             {
                if (parameters[i] == null)
                {
                   ErrorManager.recordOrThrowError(10044,
                                                   "All parameters must be specified with " +
                                                   "SET-PARAMETER before using INVOKE for " + callName,
                                                   false);
                   return;
                }
             }
          }

          int ct = callType;
          if (ct == DLL_CALL_TYPE && (library == null || inHandle != null))
          {
             ct = PROCEDURE_CALL_TYPE;
          }

          switch (ct)
          {
             case FUNCTION_CALL_TYPE:
             case PROCEDURE_CALL_TYPE:
                // if inHandle is set, must be a procedure - but validate it at the CFO call
                break;

             case DLL_CALL_TYPE:
                // nothing to do
                break;

             case GET_ATTR_CALL_TYPE:
             case SET_ATTR_CALL_TYPE:
                // must be a valid resource
                if (inHandle == null || !inHandle._isValid())
                {
                   ErrorManager.recordOrShowError(10091,
                                                  "CALL:INVOKE finds an invalid CALL:IN-HANDLE for " +
                                                  "GET/SET-ATTR-CALL-TYPE.  It must be a valid 4gl " +
                                                  "OBJECT HANDLE",
                                                  false);
                   return;
                }

                break;
          }

          boolean silent = ErrorManager.isSilent();
          boolean function = false;
          switch (ct)
          {
             case FUNCTION_CALL_TYPE:
                function = true;
             case PROCEDURE_CALL_TYPE:
                InvokeConfig cfg = buildInvokeConfig(function);

                if (!silent)
                {
                   // assume a silent mode, so that errors can be captured and re-raised
                   ErrorManager.setSilent(true);
                }

                boolean ran = false;
                try
                {
                   if (parameters != null)
                   {
                      for (CallParameter cparam : parameters)
                      {
                         Object arg = cparam.getArgument();
                         if (arg instanceof AbstractExtentParameter)
                         {
                            AbstractParameter.getCurrentScope()
                                              .addExtentParameter((AbstractExtentParameter<?>) arg);
                         }
                         else if (arg instanceof AbstractSimpleParameter)
                         {
                            AbstractParameter.getCurrentScope()
                                             .addSimpleParameter((AbstractSimpleParameter) arg);
                         }
                      }
                   }

                   BaseDataType val = cfg.executeForResource(this);
                   ran = true;

                   if (!silent && ErrorManager.isPendingError())
                   {
                      // raise a condition, this is from a RETURN ERROR - will be re-thrown in catch
                      throw new ErrorConditionException("return error");
                   }

                   // now, we need to copy back the output and input-output arguments
                   if (parameters != null)
                   {
                      for (CallParameter cparam : parameters)
                      {
                         if (cparam.mode.output || cparam.mode.inputOutput)
                         {
                            if (!cparam.copyOutput())
                            {
                               // if a parameter copy failed, stop processing
                               break;
                            }
                         }
                      }
                   }

                   // set return type
                   if (function && val != null)
                   {
                      returnValue = val;
                      returnValueDataType = val.getTypeName().toLowerCase();
                   }
                   else
                   {
                      returnValue = null;
                      returnValueDataType = null;
                   }
                }
                catch (LegacyErrorException lex)
                {
                   throw lex;
                }
                catch (ErrorConditionException ece)
                {
                   ErrorManager.setSilent(silent);

                   if (ran || ControlFlowOps.hadInvalidArguments())
                   {
                      // just re-throw it, this was an invalid argument issue
                      throw ece;
                   }

                   boolean pendingError = ErrorManager.isPendingError();
                   ErrorManager.clearPending();

                   int[] nums =
                   {
                      ece.getProgressErrorCode(),
                      5729,
                      10089
                   };
                   String[] texts =
                   {
                      ece.getMessage(),
                      "Incompatible datatypes found during runtime conversion",
                      "Unable to update output parameter for dynamic INVOKE"
                   };

                   if (function || !pendingError)
                   {
                      ErrorManager.recordOrShowError(nums, texts, false, false, false, true);
                   }
                   else
                   {
                      ErrorManager.recordOrThrowError(nums, texts, false, true);
                   }
                }
                finally
                {
                   ErrorManager.setSilent(silent);

                   OutputParameterAssigner opa = TransactionManager.deregisterOutputParameterAssigner();
                   if (opa != null)
                   {
                      opa.abort();
                   }
                }
                break;

             case DLL_CALL_TYPE:
                boolean hasRetParam = returnValueDllType != null;
                Object[] args = new Object[numParameters + (hasRetParam ? 1 : 0)];
                Class<?>[] signInput = new Class[numParameters + (hasRetParam ? 1 : 0)];
                List<Parameter> nativeParameters = new ArrayList<>();
                boolean noInvoke = false;

                for (int i = 0; i < numParameters; i++)
                {
                   CallParameter cparam = parameters[i];

                   if (!(cparam instanceof NativeCallParameter ||
                         cparam instanceof ExtentCallParameter))
                   {
                      // in this case, the run is aborted - it will only look if the entry exists in
                      // the library; no actual invoke is performed
                      noInvoke = true;
                   }

                   Object arg = cparam.getArgument();
                   if (arg instanceof AbstractExtentParameter)
                   {
                      AbstractParameter.getCurrentScope()
                                        .addExtentParameter((AbstractExtentParameter<?>) arg);
                   }
                   else if (arg instanceof AbstractSimpleParameter)
                   {
                      AbstractParameter.getCurrentScope()
                                       .addSimpleParameter((AbstractSimpleParameter) arg);
                   }

                   args[i] = arg;
                   signInput[i] = args[i].getClass();

                   CallMode cmode = parameters[i].mode;
                   String smode = cmode.input ? "INPUT"
                                              : (cmode.output ? "OUTPUT" : "INPUT-OUTPUT");
                   Parameter param = new Parameter("call-param" + i, cparam.dataType, smode);
                   nativeParameters.add(param);
                }

                if (hasRetParam)
                {
                   // add a new parameter with mode "RETURN", last
                   Parameter param = new Parameter("CALL-RET", returnValueDllType, "RETURN");
                   nativeParameters.add(param);

                   BaseDataType retParam = null;
                   switch (returnValueDllType)
                   {
                      case "byte":
                      case "short":
                      case "long":
                      case "unsigned-long":
                      case "unsigned-short":
                         retParam = new integer();
                         break;
                      case "int64":
                         retParam = new int64();
                         break;
                      case "character":
                         retParam = new character();
                         break;
                      case "double":
                      case "float":
                         retParam = new decimal();
                         break;
                      case "memptr":
                         retParam = new memptr();
                         break;
                   }

                   args[numParameters] = retParam;
                   signInput[numParameters] = retParam.getClass();
                }

                NativeAPIEntry nativeEntry = new NativeAPIEntry(callName, callName);
                nativeEntry.setLibraryName(library);
                nativeEntry.setPersistent(persistent);
                if (ordinal != null)
                {
                   nativeEntry.setOrdinal(ordinal);
                }
                nativeEntry.setThreadSafe(threadSafe);
                nativeEntry.setCallingConvention(CallingConvention.valueOf(libraryCallingConvention));
                nativeEntry.setParameterList(nativeParameters);
                nativeEntry.initialize();

                String modes = nativeEntry.getParameterModes();

                boolean failed = false;
                OutputParameterAssigner opa = TransactionManager.deregisterOutputParameterAssigner();
                try
                {
                   NativeInvoker.invoke(nativeEntry, noInvoke, "DYNAMIC CALL", signInput, modes, args);
                   failed = ErrorManager.isPendingError();
                }
                catch (Exception exc)
                {
                   failed = true;
                   throw exc;
                }
                finally
                {
                   if (opa != null)
                   {
                      if (failed)
                      {
                         opa.abort();
                      }
                      else
                      {
                         opa.processAssignments(callName, false);
                      }
                   }
                }

                if (returnValueDllType != null)
                {
                   returnValue = (BaseDataType) args[args.length - 1];
                   returnValueDataType = returnValue.getTypeName().toLowerCase();
                }
                else
                {
                   returnValue = null;
                   returnValueDataType = null;
                }

                break;

             case GET_ATTR_CALL_TYPE:
                accessAttributeOrMethod(false);
                break;

             case SET_ATTR_CALL_TYPE:
                accessAttributeOrMethod(true);
                break;
          }
       }

       /**
        * Set the specified parameter for this dynamic invocation.
        *
        * @param    parmNum
        *           The 1-based parameter index.
        * @param    dataType
        *           The parameter's data type.
        * @param    ioMode
        *           The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
        *           suffix).
        * @param    value
        *           The argument's value.   Can be only a variable/field-ref or extent, if mode is
        *           OUTPUT or INPUT-OUTPUT.
        *
        * @return   <code>true</code> if the parameter was set successfully.
        */
       @Override
       public logical setParameter(int64 parmNum, character dataType, character ioMode, Object value)
       {
          if (numParameters <= 0)
          {
             // show error, do not raise
             // Cannot SET-PARAMETER before specifying NUM-PARAMETERS for the CALL object. (10051)
             ErrorManager.recordOrShowError(10051,
                                            "Cannot SET-PARAMETER before specifying NUM-PARAMETERS " +
                                            "for the CALL object",
                                            false, false, false);
             return new logical(false);
          }

          if (parmNum.isUnknown())
          {
             // SET-PARAMETER index must be 1 based and less than or equal to1. (10052)
             ErrorManager.recordOrThrowError(10052,
                                             "SET-PARAMETER index must be 1 based and less than or " +
                                             "equal to" + numParameters,
                                             false);
             return new logical(false);
          }

          int pnum = parmNum.intValue();
          if (pnum < 1 || pnum > numParameters)
          {
             // SET-PARAMETER index must be 1 based and less than or equal to1. (10052)
             ErrorManager.recordOrThrowError(10052,
                                             "SET-PARAMETER index must be 1 based and less than or " +
                                             "equal to" + numParameters,
                                             false);
             return new logical(false);
          }

          CallMode cmode = CallMode.buildMode(ioMode);
          if (cmode == null)
          {
             ErrorManager.recordOrThrowError(10057,
                                             "SET-PARAMETER input-output mode must be one of " +
                                             "INPUT, OUTPUT, INPUT-OUTPUT, OUTPUT-APPEND, enclosed " +
                                             "in quotes or in an expression.. (10057)",
                                             false,
                                             true);
             return new logical(false);
          }

          if (!cmode.input && !validOutputParameter(pnum, value))
          {
             return new logical(false);
          }

          if (dataType.isUnknown())
          {
             ErrorManager.recordOrThrowError(10053,
                                             "SET-PARAMETER datatype ? is not supported for CALL object",
                                             false);
             return new logical(false);
          }

          CallParameter param = createParameter(dataType.getValue(), cmode, value, library != null, false);

          if (param == null)
          {
             return new logical(false);
          }

          parameters[pnum - 1] = param;

          return new logical(true);
       }

       /**
        * Set the specified parameter for this dynamic invocation.
        *
        * @param    parmNum
        *           The 1-based parameter index.
        * @param    dataType
        *           The parameter's data type.
        * @param    ioMode
        *           The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
        *           suffix).
        * @param    value
        *           The argument's value.   Can be only a variable/field-ref or extent, if mode is
        *           OUTPUT or INPUT-OUTPUT.
        *
        * @return   <code>true</code> if the parameter was set successfully.
        */
       @Override
       public logical setParameter(long parmNum, character dataType, character ioMode, Object value)
       {
          return setParameter(new integer(parmNum), dataType, ioMode, value);
       }

       /**
        * Set the specified parameter for this dynamic invocation.
        *
        * @param    parmNum
        *           The 1-based parameter index.
        * @param    dataType
        *           The parameter's data type.
        * @param    ioMode
        *           The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
        *           suffix).
        * @param    value
        *           The argument's value.   Can be only a variable/field-ref or extent, if mode is
        *           OUTPUT or INPUT-OUTPUT.
        *
        * @return   <code>true</code> if the parameter was set successfully.
        */
       @Override
       public logical setParameter(int64 parmNum, String dataType, character ioMode, Object value)
       {
          return setParameter(parmNum, new character(dataType), ioMode, value);
       }

       /**
        * Set the specified parameter for this dynamic invocation.
        *
        * @param    parmNum
        *           The 1-based parameter index.
        * @param    dataType
        *           The parameter's data type.
        * @param    ioMode
        *           The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
        *           suffix).
        * @param    value
        *           The argument's value.   Can be only a variable/field-ref or extent, if mode is
        *           OUTPUT or INPUT-OUTPUT.
        *
        * @return   <code>true</code> if the parameter was set successfully.
        */
       @Override
       public logical setParameter(long parmNum, String dataType, character ioMode, Object value)
       {
          return setParameter(new integer(parmNum), new character(dataType), ioMode, value);
       }

       /**
        * Set the specified parameter for this dynamic invocation.
        *
        * @param    parmNum
        *           The 1-based parameter index.
        * @param    dataType
        *           The parameter's data type.
        * @param    ioMode
        *           The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
        *           suffix).
        * @param    value
        *           The argument's value.   Can be only a variable/field-ref or extent, if mode is
        *           OUTPUT or INPUT-OUTPUT.
        *
        * @return   <code>true</code> if the parameter was set successfully.
        */
       @Override
       public logical setParameter(int64 parmNum, character dataType, String ioMode, Object value)
       {
          return setParameter(parmNum, dataType, new character(ioMode), value);
       }

       /**
        * Set the specified parameter for this dynamic invocation.
        *
        * @param    parmNum
        *           The 1-based parameter index.
        * @param    dataType
        *           The parameter's data type.
        * @param    ioMode
        *           The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
        *           suffix).
        * @param    value
        *           The argument's value.   Can be only a variable/field-ref or extent, if mode is
        *           OUTPUT or INPUT-OUTPUT.
        *
        * @return   <code>true</code> if the parameter was set successfully.
        */
       @Override
       public logical setParameter(long parmNum, character dataType, String ioMode, Object value)
       {
          return setParameter(new integer(parmNum), dataType, new character(ioMode), value);
       }

       /**
        * Set the specified parameter for this dynamic invocation.
        *
        * @param    parmNum
        *           The 1-based parameter index.
        * @param    dataType
        *           The parameter's data type.
        * @param    ioMode
        *           The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
        *           suffix).
        * @param    value
        *           The argument's value.   Can be only a variable/field-ref or extent, if mode is
        *           OUTPUT or INPUT-OUTPUT.
        *
        * @return   <code>true</code> if the parameter was set successfully.
        */
       @Override
       public logical setParameter(int64 parmNum, String dataType, String ioMode, Object value)
       {
          return setParameter(parmNum, new character(dataType), new character(ioMode), value);
       }

       /**
        * Set the specified parameter for this dynamic invocation.
        *
        * @param    parmNum
        *           The 1-based parameter index.
        * @param    dataType
        *           The parameter's data type.
        * @param    ioMode
        *           The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
        *           suffix).
        * @param    value
        *           The argument's value.   Can be only a variable/field-ref or extent, if mode is
        *           OUTPUT or INPUT-OUTPUT.
        *
        * @return   <code>true</code> if the parameter was set successfully.
        */
       @Override
       public logical setParameter(long parmNum, String dataType, String ioMode, Object value)
       {
          return setParameter(new integer(parmNum),
                              new character(dataType),
                              new character(ioMode),
                              value);
       }

       /**
        * Check if this resource supports the NAME attribute.
        *
        * @return   Always <code>false</code>.
        */
       @Override
       protected boolean hasName()
       {
          return false;
       }

       /**
        * Check if this resource supports the NAME attribute in read-only mode.
        *
        * @return   Always <code>false</code>.
        */
       @Override
       protected boolean hasNameReadOnly()
       {
          return false;
       }

       /**
        * Check if this resource can be reported as chained.
        *
        * @return   Always <code>false</code>.
        */
       @Override
       protected boolean isChained()
       {
          return false;
       }

       /**
        * Check if this resource supports the NEXT-SIBLING attribute.
        *
        * @return   Always <code>false</code>.
        */
       @Override
       protected boolean hasNextSibling()
       {
          return false;
       }

       /**
        * Check if this resource supports the PREV-SIBLING attribute.
        *
        * @return   Always <code>false</code>.
        */
       @Override
       protected boolean hasPrevSibling()
       {
          return false;
       }

       /**
        * Check if this resource has as parent the specified resource.
        *
        * @param    parent
        *           The parent for which the first child is needed.  If <code>null</code>, the first
        *           resource with no parent set is returned.
        *
        * @return   Always <code>false</code>.
        */
       @Override
       protected boolean hasParent(HandleChain parent)
       {
          return false;
       }

       /**
        * Worker to be implemented by each resource.  Called by {@link #delete()}.
        *
        * @return   <code>true</code> if the resource was deleted.
        */
       @Override
       protected boolean resourceDelete()
       {
          deleted = true;

          // release all references
          clear();

          ControlFlowOps.invalidateCallSiteCache(this);

          return true;
       }

       /**
        * Check if the resource may be implicitly deleted.
        *
        * @return   Always <code>false</code>.
        */
       @Override
       protected boolean implicitDeletion()
       {
          return false;
       }

       /**
        * Check if the specified data-type is valid to be used via {@link #setParameter SET-PARAMETER},
        * for a native library API call.
        *
        * @param    dataType
        *           The data type to be validated.
        *
        * @return   <code>true</code> if this is a valid data type.
        */
       public static boolean validDllType(String dataType)
       {
          dataType = ProgressAst._keyword(dataType);

          return dataType.equals("byte")          ||
                 dataType.equals("character")     ||
                 dataType.equals("double")        ||
                 dataType.equals("float")         ||
                 dataType.equals("int64")         ||
                 dataType.equals("long")          ||
                 dataType.equals("memptr")        ||
                 dataType.equals("short")         ||
                 dataType.equals("unsigned-long") ||
                 dataType.equals("unsigned-short");
       }

       /**
        * Verify if the given parameter value can be used in OUTPUT or INPUT-OUTPUT mode.  In these
        * modes, only variables, temp-table fields or subscripted vars/fields are allowed.
        *
        * @param    pnum
        *           The parameter number.
        * @param    value
        *           The parameter's value.
        *
        * @return   <code>true</code> if the value can be used.
        */
       public static boolean validOutputParameter(int pnum, Object value)
       {
          boolean isVar = (value instanceof BaseDataTypeVariable);
          if (isVar)
          {
             value = ((BaseDataTypeVariable) value).get();
          }

          if (value instanceof BaseDataType)
          {
             BaseDataType bdt = (BaseDataType) value;
             if (isVar)
             {
                return true;
             }
          }
          else if (value instanceof PropertyReference)
          {
             PropertyReference pref = (PropertyReference) value;
             // TODO: other validation?
             return true;
          }
          else if (value instanceof FieldReference)
          {
             FieldReference fref = (FieldReference) value;
             if (!(fref.getParentBuffer() instanceof TemporaryBuffer))
             {
                String msg = "Buffer for output parameter " + pnum + " not available during SET-PARAMETER";
                ErrorManager.recordOrThrowError(10062, msg, false, false);
                return false;
             }

             Buffer buf = (Buffer) fref.getParentBuffer().getDMOProxy();
             if (!buf._available())
             {
                // 4GL abends... we just continue

                String msg = "Buffer for output parameter " + pnum + " not available during SET-PARAMETER";
                ErrorManager.recordOrThrowError(10062, msg, false, false);
                return false;
             }

             // docs state that undoable temp-tables are not allowed, but looks like they are
             return true;
          }
          else if (value.getClass().isArray())
          {
             return true;
          }
          else if (value instanceof AbstractExtentParameter)
          {
             return true;
          }
          else if (value instanceof TableParameter || value instanceof DataSetParameter)
          {
             return true;
          }

          ErrorManager.recordOrThrowError(10060,
                                          "SET-PARAMETER output parameter " +  pnum +
                                          " must be a program variable or temp-table field " +
                                          "(subscript expressions are ok)", false);
          return false;
       }

       /**
        * Create a new parameter with the specified details, if at all possible.
        * <p>
        * If validation fails (invalid data type, mode or value combinations), then a <code>null</code>
        * value will be returned.
        *
        * When calling external dynamic/shared library routine parameter data type is validated for
        * supported external data types.
        *
        * SetParameter method in ParametersList does an extra validation on input value against data type.
        *
        * @param    dataType
        *           The data type to be used.
        * @param    mode
        *           The parameter's  mode.
        * @param    value
        *           The parameter's value.
        * @param    library
        *           Flag set to true if a dynamic/shared library routine is invoked.
        * @param    validateType
        *           Optional data type validation
        * @return   A new {@link CallParameter instance} with the parameter configuration.
        */
       public static CallParameter createParameter(
             String dataType, CallMode mode, Object value, boolean library, boolean validateType)
       {
          boolean isVar = (value instanceof BaseDataTypeVariable);
          if (isVar)
          {
             value = ((BaseDataTypeVariable) value).get();
          }

          boolean isClass = false;
          boolean isExtent = false;

          Matcher dataTypeMatch = dataTypePattern.matcher(dataType);
          if (dataTypeMatch.matches())
          {
             isClass = dataTypeMatch.group(1) != null;
             dataType = dataTypeMatch.group(2);
             isExtent = dataTypeMatch.group(3) != null;
          }

          if (!"DATASET".equalsIgnoreCase(dataType) && !"DATASET-HANDLE".equalsIgnoreCase(dataType) &&
              !"TABLE".equalsIgnoreCase(dataType)   && !"TABLE-HANDLE".equalsIgnoreCase(dataType))
          {
             dataType = resolveDataType(dataType);
          }

          if (library)
          {
             if (!validDllType(dataType))
             {
                // if LIBRARY is set, then only lib-specific datatypes can be set...
                String msg = "SET-PARAMETER datatype " + dataType +
                             " is not supported for WINDOWS DLL or UNIX library CALL object";
                ErrorManager.recordOrThrowError(15481, msg, false);
                return null;
             }
          }
          else
          {
             // dataset and table parameter
             if (value instanceof DataSetParameter || value instanceof TableParameter ||
                 "DATASET".equalsIgnoreCase(dataType) || "TABLE".equalsIgnoreCase(dataType)||
                 "DATASET-HANDLE".equalsIgnoreCase(dataType) || "TABLE-HANDLE".equalsIgnoreCase(dataType))
             {
                Class<?> valueCls = value == null ? null : value.getClass();
                Object argVal = value;
                if (valueCls == FieldReference.class)
                {
                   valueCls = ((FieldReference) value).getType();
                   argVal = ((FieldReference) value).get();
                }
                else if (valueCls == PropertyReference.class)
                {
                   valueCls = ((PropertyReference) value).getType();
                   argVal = ((PropertyReference) value).get();
                }

                if (isExtent                                                                                  ||
                    (!"DATASET".equalsIgnoreCase(dataType)       && value instanceof DataSetParameter)        ||
                    (!"TABLE".equalsIgnoreCase(dataType)         && value instanceof TableParameter)          ||
                    ("DATASET".equalsIgnoreCase(dataType)        && !(value instanceof DataSetParameter))     ||
                    ("TABLE".equalsIgnoreCase(dataType)          && !(value instanceof TableParameter))       ||
                    ("DATASET-HANDLE".equalsIgnoreCase(dataType) && !handle.class.isAssignableFrom(valueCls)) ||
                    ("TABLE-HANDLE".equalsIgnoreCase(dataType)   && !handle.class.isAssignableFrom(valueCls)))
                {
                   String msg = "If either the 2nd or 4th parameters to SetParameter are TABLE or DATASET types," +
                                " then they both must be.";
                   ErrorManager.recordOrThrowError(15563, msg, false);
                   return null;
                }

                return new CallParameter(dataType, isClass, mode, argVal);
             }

             Class<? extends BaseDataType> cls = !isClass && dataType != null
                      && DATA_TYPES.contains(dataType.toUpperCase())
                               ? BaseDataType.fromTypeName(dataType)
                               : ObjectOps.resolveClass(dataType);
             isClass = cls != null && _BaseObject_.class.isAssignableFrom(cls);
             Runnable invalidType = () -> {
                ErrorManager.recordOrThrowError(10059,
                         "Unable to convert SET-PARAMETER value to datatype passed", false);
             };

             Runnable incompatibleType = () -> {
                int[] nums = { 5729, 10059 };

                String[] texts = { "Incompatible datatypes found during runtime conversion",
                                   "Unable to convert SET-PARAMETER value to datatype passed" };

                ErrorManager.recordOrThrowError(nums, texts, false, false);
             };

             if (cls == null)
             {
                invalidType.run();
             }

             if (value != null)
             {
                Class valueCls = value.getClass();
                BaseDataType val = value instanceof BaseDataType ? (BaseDataType) value : null;

                if (valueCls == unknown.class)
                {
                   valueCls = cls;
                }

                boolean isValExtent = valueCls.isArray() || value instanceof AbstractExtentParameter;

                //TODO: Warning: the following condition is the subject of a conflict in the behavior
                // of 2 similar but different methods. What works for one fails for the other.
                // The methods are SetParameter for objects and SetParameter for handles.

                // Validate parameter and throw the coresponding error if the declared datatype is not
                // an extent but the value is
                if (!isExtent && isValExtent)
                {
                   String errMsg = ("SetParameter requires that the EXTENT keyword must " +
                            "FOLLOW the datatype when the value is an array, " +
                            "but not when the value is a scalar");
                   ErrorManager.recordOrThrowError(15308, errMsg, false);
                   return null;
                }

                if (value instanceof FieldReference)
                {
                   FieldReference fieldRef = (FieldReference) value;
                   isValExtent = fieldRef.getExtent() != null && fieldRef.getIndex() == -1;
                   valueCls = fieldRef.getType();

                   if (isValExtent && !fieldRef.hasIndex())
                   {
                      // actually the error occurs when the called procedure returns, but this validation
                      // fails with NPE because the (output) value is evaluated now.
                      // because of that some information are not available here
                      Nameable caller = (Nameable) ProcedureManager.thisProcedure().getResource();
                      String msg = "Array parameter with extent  from procedure " + caller.name().toJavaType() +
                                   " is mismatched with parameter in procedure " +/*"callName" +*/" with extent N";
                      ErrorManager.recordOrThrowError(11428, msg, false);
                      return null;
                   }

                   val = fieldRef.get();
                }
                else if (value instanceof PropertyReference)
                {
                   PropertyReference propRef = (PropertyReference) value;

                   isValExtent = propRef.getExtent() != null && propRef.getIndex() == null;

                   valueCls = propRef.getType();
                }
                else if (valueCls.isArray())
                {
                   valueCls = valueCls.getComponentType();
                }
                else if (value instanceof object)
                {
                   object ref = (object) value;
                   if (ref._isValid())
                   {
                      valueCls = ((object) value).ref.getClass();
                   }
                   else
                   {
                      valueCls = _BaseObject_.class;
                   }
                }
                else if (value instanceof BaseDataType && value.getClass() != unknown.class)
                {
                   valueCls = ((BaseDataType) value).getType().getCls();
                }

                if ((valueCls.isAssignableFrom(memptr.class)) && !"MEMPTR".equalsIgnoreCase(dataType)
                         || ("MEMPTR".equalsIgnoreCase(dataType) && !(valueCls.isAssignableFrom(memptr.class))))
                {
                   ErrorManager.recordOrThrowError(10058,
                            "MEMPTR parameters may not be converted to any other " +
                            "type in dynamic invoke or SetParameter",
                            false);
                   return null;
                }
                else if (("TABLE-HANDLE".equalsIgnoreCase(dataType) || "DATASET-HANDLE".equalsIgnoreCase(dataType)) &&
                         !(valueCls.isAssignableFrom(handle.class)))
                {
                   invalidType.run();
                }

                if (!isValExtent && (!_BaseObject_.class.isAssignableFrom(cls) || !object.class.isAssignableFrom(valueCls))) {
                   // run-time data type conversion seems to be attempted first even if data types does not match unless is extent
                   // oddly when class parameter and value is object the assignment is not done, only compatibility check
                   try
                   {
                      BaseDataType inst = _BaseObject_.class.isAssignableFrom(cls)
                                              ? new object(cls)
                                              : BaseDataType.generateUnknown(cls);
                      if (!inst.isIncompatibleTypesOnConversion(val))
                         inst.assign(val);
                   }
                   catch (ErrorConditionException ece)
                   {
                      ErrorManager.addError(10059, "Unable to convert SET-PARAMETER value to datatype passed", true, false);
                      throw ece;
                   }
                   catch (Exception ex)
                   {
                      LOG.log(Level.SEVERE, "Exception encountered when instantiating a value", ex);
                   }
                }

                if (validateType)
                {
                   boolean isCompatible = false;

                   // numeric data types are compatible to some extent
                   // decimal accepts int/int64
                   // int64 accepts int
                   // recid is not accepted by neither decimal/int/int64
                   if (cls.equals(decimal.class))
                   {
                      isCompatible = !valueCls.equals(recid.class) && NumberType.class.isAssignableFrom(valueCls);
                   }
                   else if (cls.equals(int64.class))
                   {
                      isCompatible = !valueCls.equals(recid.class) && int64.class.isAssignableFrom(valueCls);
                   }
                   else if (NumberType.class.isAssignableFrom(cls))
                   {
                      // break int64->integer->recid hierarchy
                      isCompatible = valueCls.equals(cls);
                   }
                   else if (cls.equals(longchar.class))
                   {
                      // longchar accepts also character but not clob
                      isCompatible = !valueCls.equals(clob.class) && Text.class.isAssignableFrom(valueCls);
                   }
                   else if (_BaseObject_.class.isAssignableFrom(cls))
                   {
                      // value not an object
                      if (!(value instanceof object || value instanceof unknown))
                      {
                         isCompatible = false;
                      }
                      // value is object array
                      else if (isValExtent)
                      {
                         isCompatible = isAssignableFrom((object[]) value, cls);
                      }
                      else
                      {
                         if (value instanceof unknown)
                         {
                            object objVal = new object();
                            objVal.assign(value);
                            isCompatible = true;
                         }
                         else
                         {
                            object oVal = (object) value;
                            isCompatible = cls.isAssignableFrom(oVal.isUnknown() ? oVal.type() : oVal.ref().getClass());
                         }
                      }
                   }
                   else
                   {
                   // date->datetime->datetimetz hierarchy works
                      isCompatible = valueCls.isAssignableFrom(cls);
                   }

                   if (!isCompatible)
                   {
                      ErrorManager.recordOrThrowError(15315,
                               "SetParameter found incompatible data types used in dynamic parameter",
                               false);
                      return null;
                   }

                   if (isExtent && !isValExtent)
                   {
                      ErrorManager.recordOrThrowError(15308,
                               "SetParameter requires that the EXTENT keyword must FOLLOW the datatype when " +
                               "the value is an array, but not when the value is a scalar",
                               false);
                      return null;
                   }

                }
             }

          }

          if (isExtent)
          {
             return new ExtentCallParameter(dataType, isClass, mode, value);
          }

          CallParameter cparam = library ? new NativeCallParameter(dataType, mode, value)
                   : new CallParameter(dataType, isClass, mode, value);

          return cparam;
       }

       /**
        * Validates and assign the value to variable instance.
        * <p>
        * If validation fails (invalid data type or value combinations), an error is
        * thrown.
        *
        * @param    variable
        *           Instance of the field.
        * @param    value
        *           The parameter's value.
        * @param    dataType
        *           Data type of the variable to be set.
        * @param    isExtent
        *           Flag indicating that a variable instance is of type extent.
        * @param    validateType
        *           Optional data type validation.
        */
       public static void setterAssignment(Object variable,
                                          Object value,
                                          String dataType,
                                          boolean isExtent)
       {
          boolean isVar = (value instanceof BaseDataTypeVariable);
          if (isVar)
          {
             value = ((BaseDataTypeVariable) value).get();
          }

          if (!"DATASET".equalsIgnoreCase(dataType) && !"DATASET-HANDLE".equalsIgnoreCase(dataType) &&
              !"TABLE".equalsIgnoreCase(dataType)   && !"TABLE-HANDLE".equalsIgnoreCase(dataType))
          {
             dataType = resolveDataType(dataType);
          }

          Class<? extends BaseDataType> cls = (Class<? extends BaseDataType>) variable.getClass();
          Runnable invalidType = () -> {
                ErrorManager.recordOrThrowError(5678,
                         "Unable to do run-time conversion of datatypes", false);
          };

          if (cls == null)
          {
             invalidType.run();
             return;
          }

          if (value != null)
          {
             Class valueCls = value.getClass();
             BaseDataType val = value instanceof BaseDataType ? (BaseDataType) value : null;

             if (valueCls == unknown.class)
             {
                valueCls = cls;
             }

             boolean isValExtent = valueCls.isArray() || value instanceof AbstractExtentParameter;

             if (value instanceof FieldReference)
             {
                FieldReference fieldRef = (FieldReference) value;
                isValExtent = fieldRef.getExtent() != null && fieldRef.getIndex() == -1;
                valueCls = fieldRef.getType();

                if (isValExtent && !fieldRef.hasIndex())
                {
                   // actually the error occurs when the called procedure returns, but this validation
                   // fails with NPE because the (output) value is evaluated now.
                   // because of that some information are not available here
                   Nameable caller = (Nameable) ProcedureManager.thisProcedure().getResource();
                   String msg = "Array parameter with extent  from procedure " + caller.name().toJavaType() +
                                " is mismatched with parameter in procedure " +/*"callName" +*/" with extent N";
                   ErrorManager.recordOrThrowError(11428, msg, false);
                   return;
                }

                val = fieldRef.get();
             }
             else if (value instanceof PropertyReference)
             {
                PropertyReference propRef = (PropertyReference) value;

                isValExtent = propRef.getExtent() != null && propRef.getIndex() == null;

                valueCls = propRef.getType();
             }
             else if (valueCls.isArray())
             {
                valueCls = valueCls.getComponentType();
             }
             else if (value instanceof object)
             {
                object ref = (object) value;
                if (ref._isValid())
                {
                   valueCls = ((object) value).ref.getClass();
                }
                else
                {
                   valueCls = _BaseObject_.class;
                }
             }
             else if (value instanceof BaseDataType && value.getClass() != unknown.class)
             {
                valueCls = ((BaseDataType) value).getType().getCls();
             }

             if ((valueCls.isAssignableFrom(memptr.class)) && !"MEMPTR".equalsIgnoreCase(dataType)
                         || ("MEMPTR".equalsIgnoreCase(dataType) && !(valueCls.isAssignableFrom(memptr.class))))
             {
                ErrorManager.recordOrThrowError(10058,
                            "MEMPTR parameters may not be converted to any other " +
                            "type in dynamic invoke or SetParameter",
                            false);
                return;
             }
             else if (("TABLE-HANDLE".equalsIgnoreCase(dataType) || "DATASET-HANDLE".equalsIgnoreCase(dataType)) &&
                      !(valueCls.isAssignableFrom(handle.class)))
             {
                invalidType.run();
                return;
             }

             if (!isValExtent && (!_BaseObject_.class.isAssignableFrom(cls) || !object.class.isAssignableFrom(valueCls)))
             {
                // run-time data type conversion seems to be attempted first even if data types does not match unless is extent
                // oddly when class parameter and value is object the assignment is not done, only compatibility check
                try
                {
                   BaseDataType inst = (BaseDataType) variable;
                   if (!inst.isIncompatibleTypesOnConversion(val))
                   {
                      inst.assign(val);
                   }
                }
                catch (ErrorConditionException ece)
                {
                   invalidType.run();
                   return;
                }
                catch (Exception ex)
                {
                   LOG.log(Level.SEVERE, "Exception encountered when instantiating a value", ex);
                }
             }
          }
       }

       /**
        * Check if all values in the array/extent are compatible with the given data type.
        *
        * @param values The values array (extent).
        * @param cls The data type to validate values against.
        * @return True is all values in the array are compatible with the data type.
        */
       private static boolean isAssignableFrom(object<? extends _BaseObject_>[] values, Class cls)
       {
          boolean isEmpty = true;
          boolean isAssignable = true;

          for (object<? extends _BaseObject_> value : values)
          {
             if (!value.isUnknown()) {
                isEmpty = false;
                // check the actual instance class, object type might be an invalid superclass like P.L.O
                isAssignable = cls.isAssignableFrom(value.ref.getClass());

                if (!isAssignable)
                   break;
             }
          }

          if (!isEmpty)
             return isAssignable;

          Class valueCls = unknown.class;

          if (values.length > 0)
          {
             valueCls = values[0].type();
          }
          else if (ArrayAssigner.isDynamicArray(values))
          {
             valueCls = ArrayAssigner.getObjectType(values);
          }

          return cls.isAssignableFrom(valueCls);
       }

       /**
        * Worker method for the {@link #GET_ATTR_CALL_TYPE} and {@link #SET_ATTR_CALL_TYPE} modes.
        * <p>
        * It will try to access the attribute or method, using the current configuration.
        *
        * @param    setter
        *           When <code>true</code>, we are in {@link #SET_ATTR_CALL_TYPE} mode, and only
        *           writable attributes are accessed; otherwise, all readable attributes and methods.
        */
       private void accessAttributeOrMethod(boolean setter)
       {
          // first, check if this is a valid attribute/method token
          int tokenType = ProgressAst.lookupKeywordToken(callName);
          if (!ATTRIBUTES_AND_METHODS.containsKey(tokenType))
          {
             ErrorManager.recordOrShowError(10092,
                                            "CALL:INVOKE finds an invalid CALL:CALL-NAME for " +
                                            "GET/SET-ATTR-CALL-TYPE.  It must be a valid 4gl widget " +
                                            "attribute or method",
                                            false, false, false);
             return;
          }

          if (setter && numParameters != 1)
          {
             ErrorManager.recordOrShowError(10094,
                                            "SET-ATTR-CALL-TYPE requires exactly 1 SET-PARAMETER " +
                                            "which describes the value to be set",
                                            false, false, false);
             return;
          }

          Runnable invalidAttr = () ->
          {
             ErrorManager.recordOrShowError(4052,
                                            "**" + callName + " is not a " +
                                            (setter ? "settable" : "queryable") +
                                            " attribute for " + inHandle.unwrap().name().getValue() +
                                            " widget",
                                            false, false, false);
          };

          // finally, check if this is a valid attribute or method name, in this resource
          if (!((!setter && HandleOps.canQuery(inHandle, callName).booleanValue()) ||
                (setter && HandleOps.canSet(inHandle, callName).booleanValue())))
          {
             invalidAttr.run();
             return;
          }

          if (tokenType == ProgressParser.KW_INVOKE)
          {
             ErrorManager.recordOrShowError(10050,
                                            "INVOKE method must occur as a stand-alone statement: " +
                                            "hCall:INVOKE.  It cannot occur inside an expression " +
                                            "or on either side of an equal sign",
                                            false, false, false);
             return;
          }

          if (!setter && tokenType == ProgressParser.KW_HANDLE)
          {
             // in this case, we can only read, and just return the resource
             returnValueDataType = "handle";

             returnValue = new handle(inHandle);
             return;
          }

          // for getter, the parameters are optional: find either with no parameters or with exact match
          String methodName = HandleOps.findLegacy(inHandle, callName, true, setter);
          if (methodName == null && !setter)
          {
             // if is not a setter, look for methods, too
             methodName = HandleOps.findLegacy(inHandle, callName, false, false);
          }

          if (methodName == null)
          {
             invalidAttr.run();
             return;
          }

          // reset this now (we may be accessing RETURN-VALUE from this same handle, don't leak prev
          // call's value).
          returnValue = null;

          if (returnValueDataType == null)
          {
             int ttype = ATTRIBUTES_AND_METHODS.get(tokenType);
             returnValueDataType = ExpressionConversionWorker.simpleExpressionType(ttype);
          }

          WrappedResource target = inHandle.getResource();
          Class<? extends WrappedResource> tclass = target.getClass();
          Method mthd = null;

          if (!setter)
          {
             try
             {
                // this is a no-param method call or an attribute getter - check if we can resolve
                // a no-argument method
                mthd = tclass.getMethod(methodName);
             }
             catch (Exception e)
             {
                // do nothing, fallback
             }
          }

          BaseDataType val = null;
          if (mthd != null)
          {
             try
             {
                // if a no-argument method was found, then invoke it
                val = (BaseDataType) Utils.invoke(mthd, target);
             }
             catch (Exception e)
             {
                LOG.log(Level.SEVERE,
                        "Problem calling " + callName + " from " + inHandle.unwrap().name(),
                        e);

                val = null;
             }
          }
          else
          {
             // now, look for a method call, attribute getter with an index arg or attribute setter

             List<Class<? extends BaseDataType>> argTypes = new ArrayList<>();

             // using METH_INT forces to check for a method's signature
             ParmType[] lptypes = SignatureHelper.getSignature(tokenType, ProgressParser.METH_INT);
             if (lptypes != null)
             {
                // this is a method call or an attribute getter which receives an extent arg
                if (parameters != null && lptypes.length <= parameters.length)
                {
                   // arguments are validated this way:
                   // - if an OUTPUT parameter is found, then abort
                   // - if the expected method signature is found, then continue with the call
                   boolean allParamsInput = true;
                   for (int i = 0; i < lptypes.length; i++)
                   {
                      if (parameters[i].mode.output)
                      {
                         // when an OUTPUT parameter is found, abort the call
                         allParamsInput = false;
                         break;
                      }

                      Class<? extends BaseDataType> ptype =
                         (Class<? extends BaseDataType>) lptypes[i].getParamType();
                      argTypes.add(ptype);
                   }

                   if (!allParamsInput)
                   {
                      // nothing to do
                      return;
                   }
                }
                else
                {
                   // we can't match with a method signature (not enough arguments)
                   // abort the call, no invoke will be performed and no error will be shown
                   return;
                }
             }
             else
             {
                // now, we are in an attribute setter - the argument type is resolved from the
                // attribute's data type
                if (parameters != null && parameters.length == 1 && !parameters[0].mode.output)
                {
                   argTypes.add(BaseDataType.fromTypeName(returnValueDataType));
                }
                else
                {
                   // incorrect number of arguments or data type, abort
                   return;
                }
             }

             // look for a method with the specified data types
             Class<? extends BaseDataType>[] ptypes = argTypes.toArray(new Class[0]);

             try
             {
                mthd = tclass.getMethod(methodName, ptypes);
             }
             catch (Exception e)
             {
                mthd = null;
             }

             if (mthd == null)
             {
                // if still not found, search for a method with the same name and number of arguments,
                // but having compatible data types
                for (Method m1 : tclass.getMethods())
                {
                   if (!m1.getName().equals(methodName))
                   {
                      continue;
                   }

                   Class<?>[] m1types = m1.getParameterTypes();
                   if (m1types.length != ptypes.length)
                   {
                      continue;
                   }

                   boolean ok = true;
                   for (int i = 0; i < ptypes.length; i++)
                   {
                      if (!m1types[i].isAssignableFrom(ptypes[i]))
                      {
                         ok = false;
                         break;
                      }
                   }

                   if (ok)
                   {
                      mthd = m1;
                      break;
                   }
                }
             }

             if (mthd != null && ptypes.length == numParameters)
             {
                // found a good method, convert the arguments to the specified data types and call it
                Object[] args = new Object[ptypes.length];

                for (int i = 0; i < ptypes.length; i++)
                {
                   args[i] = parameters[i].getArgument();

                   if (args[i] == null)
                   {
                      LOG.log(Level.WARNING,
                              "Argumenmt " + (i + 1) + " should have been prepared before INVOKE");

                      return;
                   }
                }
                try
                {
                   val = (BaseDataType) Utils.invoke(mthd, target, args);
                }
                catch (Exception e)
                {
                   LOG.log(Level.SEVERE,
                            "Problem calling " + callName + " from " + inHandle.unwrap().name(),
                            e);

                   val = null;
                }
             }
             else
             {
                invalidAttr.run();
                return;
             }
          }

          if (!BaseDataType.class.isAssignableFrom(mthd.getReturnType()))
          {
             // there is an indeterminate behavior here: in some cases of uninitialized attributes
             // (like PRIVATE-DATA and PROCEDURE-TYPE), the data-type for the returned (unknown)
             // value is not predictable.  we don't duplicate this, just take the attr/method
             // return type and use that
             returnValueDataType = null;
          }
          else if (returnValueDataType == null && val != null)
          {
             returnValueDataType = ((BaseDataType) val).getTypeName().toLowerCase();
          }

          if (returnValueDataType != null)
          {
             // this may be set explicitly by the user (or prior call usage)
             // above we compute the implicit data-type
             BaseDataType res = BaseDataType.generateUnknown(BaseDataType.fromTypeName(returnValueDataType));

             if (val != null)
             {
                res.assign(val);
             }

             returnValue = res;
          }
          else
          {
             returnValue = new unknown();
          }
       }

       /**
        * Show a warning that the given attribute is unknown.
        *
        * @param    attr
        *           The attribute name.
        */
       private void unknownWarning(String attr)
       {
          String msg = "**Unable to assign UNKNOWN value to attribute " + attr + " on " + type() +
                       "widget. (4083)";
          ErrorManager.displayWarning(4083, msg, false);
       }

       /**
        * Raise an ERROR condition when both the ORDINAL and CALL-NAME attributes are being set.
        *
        * @param    attr
        *           The attribute being set.
        */
       private void callOrdinalError(String attr)
       {
          int[] nums = { 15566, 3131 };
          String[] texts =
          {
             "Cannot set CALL-NAME and ORDINAL attributes at the same time",
             "Unable to set attribute " + attr + " in widget  of type CALL"
          };

          ErrorManager.recordOrThrowError(nums, texts, false);
       }

       /**
        * Raise an error that the attribute can't be set.
        *
        * @param    err1
        *           The first error code.
        * @param    msg1
        *           The first error message.
        * @param    attr
        *           The attribute's name.
        */
       private void unableToSetAttribute(int err1, String msg1, String attr)
       {
          String[] errMsg =
          {
             msg1,
             "Unable to set attribute " + attr + " in widget  of type CALL"
          };

          int[] errorCodes = { err1, 3131 };
          ErrorManager.recordOrThrowError(errorCodes, errMsg, false, false);
       }

       /**
        * Build an {@link InvokeConfig configuration} to emulate a RUN or dynamic function call, using
        * this CALL's configuration.
        * <p>
        * This can be later used to perform the actual {@link ControlFlowOps#invoke(InvokeConfig) invoke}.
        *
        * @param    function
        *           Flag indicating if this is a function call.
        *
        * @return   The new configuration.
        */
       private InvokeConfig buildInvokeConfig(boolean function)
       {
          InvokeConfig cfg = CALL_SITE.clone().setTarget(callName);

          cfg.setFunction(function)
             .setInHandle(inHandle)
             .setPersistent(persistent && inHandle == null)
             .setSingleRun(singleRun && inHandle == null)
             .setSingleton(singleton && inHandle == null);

          // some adjustments related to CALL handle
          if (cfg.isPersistent() || cfg.isSingleRun() || cfg.isSingleton())
          {
             // the persistent procedure will be saved in 'IN-HANDLE' argument
             inHandle = new handle();
             cfg.setProcedureHandle(inHandle);
          }

          if (server != null)
          {
             cfg.setServerHandle(server);
          }

          cfg.setAsynchronous(async)
             .setAsyncHandle(asyncHandle);
          if (eventProcedure != null)
          {
             cfg.setEventProcedure(eventProcedure);
          }
          if (eventProcedureContext != null)
          {
             cfg.setEventProcedureContext(eventProcedureContext);
          }

          if (numParameters > 0)
          {
             String modes = "";
             Object[] args = new Object[numParameters];

             for (int i = 0; i < numParameters; i++)
             {
                CallParameter cparam = parameters[i];

                modes = modes + cparam.mode.asString();

                args[i] = cparam.getArgument();
             }

             cfg.setModes(modes)
                .setArguments(args);
          }

          return cfg;
       }

       private static String resolveDataType(String dataType)
       {
          String primitiveType = ProgressAst._keyword(dataType);

          if (primitiveType != null && (DATA_TYPES.contains(primitiveType.toUpperCase())
                   || validDllType(primitiveType)))
          {
             return "log".equalsIgnoreCase(primitiveType) ? "logical" : primitiveType;
          }

          // if not a primitive data type check a valid OO class
          Class<_BaseObject_> cls = ObjectOps.resolveClass(dataType);

          return cls != null ? dataType : null;
       }

       /**
        * Given a decimal value, convert it to a byte representation; this representation is assumed
        * as an 'in memory representation' of the value, usable for converting to another data type,
        * where:
        * <ul>
        * <li>rightmost bit is "0x8#", where "#" is the number of fractional digits in hex</li>
        * <li>concatenate the fractional and non-fractional digits</li>
        * <li>if there is an odd number of digits, pad with 'F'</li>
        * </ul>
        *
        * @param    val
        *           The decimal value.
        *
        * @return   The byte representation.
        */
       static byte[] decimalBytes(decimal val)
       {
          String sval = ((decimal) val).toStringMessage();
          int dot = sval.indexOf('.');
          String fract = dot < 0 ? "" : sval.substring(dot + 1);
          String dec = dot < 0 ? sval : sval.substring(0, dot);
          String all = dec + fract;

          int blen = 1 + (all.length() / 2) + (all.length() % 2);
          byte[] bytes = new byte[blen];
          bytes[0] = (byte) ((0x8 << 4) + fract.length());
          int i = 1;
          while (!all.isEmpty())
          {
             String chars = all.length() == 1 ? (all + "F") : all.substring(0,  2);
             bytes[i++] = (byte) (Integer.parseInt(chars, 16) & 0xFF);

             all = all.length() == 1 ? all.substring(1) : all.substring(2);
          }

          return bytes;
       }

       /**
        * A wrapper for extent parameters (without a subscript).
        */
       private static class ExtentCallParameter
       extends CallParameter
       {
          /**
           * Create a new wrapper for a parameter which will be used for a native OS API call.
           *
           * @param    dataType
           *           The parameter's data type.
           * @param    isClass
           *           Flag indicating that the datatype is a legacy OO class.
           * @param    mode
           *           The call mode.
           * @param    value
           *           The parameter's value.
           */
          public ExtentCallParameter(String dataType, boolean isClass, CallMode mode, Object value)
          {
             super(dataType, isClass, mode, value);

             convertTo(null);

             OutputParameterAssigner opa = TransactionManager.deregisterOutputParameterAssigner();
             if (opa != null)
             {
                opa.abort();
             }
          }

          /**
           * Convert this parameter's {@link CallParameter#value value} to be usable as an argument.
           * <p>
           * Once converted, it will be cached in the {@link #argument} variable.
           *
           * @return   The {@link #argument}.
           */
          @Override
          public Object convertTo(Class<?> cls)
          {
             if (argument != null)
             {
                return argument;
             }

             if (initialValue instanceof FieldReference)
             {
                FieldReference fr = (FieldReference) initialValue;
                cls = fr.getType();

                if (mode.output)
                {
                   argument = new OutputExtentField((DataModelObject) fr.getDMO(), fr.getProperty());
                }
                else if (mode.inputOutput)
                {
                   argument = new InputOutputExtentField((DataModelObject) fr.getDMO(), fr.getProperty());
                }
                else
                {
                   argument = new FieldReference((DataModelObject) fr.getDMO(),
                                                 fr.getProperty(),
                                                 fr.isUpperCase(),
                                                 (NumberType) null,
                                                 true).getExtentValues();
                }

                return argument;
             }
             else if (initialValue instanceof AbstractExtentParameter)
             {
                // we already have an extent parameter

                if (mode.input)
                {
                   // in this case, just get the value
                   argument = ((AbstractExtentParameter) initialValue).getVariableSafe();
                }
                else if (mode.output && initialValue instanceof InputOutputExtentParameter)
                {
                   argument = new OutputExtentParameter<BaseDataType>()
                   {
                      @Override
                      public BaseDataType[] getVariable()
                      {
                         return ((AbstractExtentParameter) initialValue).getVariableSafe();
                      }

                      @Override
                      public void setVariable(BaseDataType[] reference)
                      {
                         ((AbstractExtentParameter) initialValue).setVariable(reference);
                      }

                      @Override
                      protected boolean isTableField()
                      {
                         return ((AbstractExtentParameter) initialValue).isTableField();
                      }
                   };
                }

                return argument != null ? argument : super.convertTo(cls);
             }

             // final case is a simple array reference

             if (!mode.input)
             {
                // only INPUT is allowed - the other modes require conversion rules
                throw new IllegalStateException("OUTPUT and INPUT-OUTPUT extent vars need to be " +
                                                "passed as AbstractExtentParameter instances!");
             }

             argument = (BaseDataType[]) value;

             return argument;
          }

          /**
           * Extent arguments are automatically copied via {@link AbstractExtentParameter} wrappers,
           * nothing to do.
           *
           * @return    Always <code>true</code>.
           */
          public boolean copyOutput()
          {
             // nothing to do, is done automatically by the AbstractExtentParameter wrappers
             return true;
          }
       }

       /**
        * A wrapper for parameters which will be passed to a direct native OS API call.
        */
       private static class NativeCallParameter
       extends CallParameter
       {
          /**
           * Create a new wrapper for a parameter which will be used for a native OS API call.
           *
           * @param    dataType
           *           The parameter's data type.
           * @param    mode
           *           The call mode.
           * @param    value
           *           The parameter's value.
           */
          public NativeCallParameter(String dataType, CallMode mode, Object value)
          {
             super(dataType, false, mode, value);
          }

          /**
           * Convert this parameter's {@link CallParameter#value value} to be usable as an argument.
           * <p>
           * Once converted, it will be cached in the {@link #argument} variable.
           *
           * @return   The {@link #argument}.
           */
          public Object convertTo(Class<?> cls)
          {
             if (argument != null)
             {
                return argument;
             }

             if (initialValue instanceof BaseDataType || initialValue instanceof FieldReference)
             {
                // this handles both output and input-output modes
                argument = initialValue;
                return argument;
             }

             if (mode.input)
             {
                switch (dataType)
                {
                   case "byte":
                   case "short":
                   case "long":
                   case "unsigned-long":
                   case "unsigned-short":
                      if (initialValue instanceof Number)
                      {
                         argument = new integer(((Number) initialValue).longValue());
                      }
                      else
                      {
                         argument = initialValue == null ? new integer() : new integer(initialValue.toString());
                      }
                      break;
                   case "int64":
                      if (initialValue instanceof Number)
                      {
                         argument = new int64(((Number) initialValue).longValue());
                      }
                      else
                      {
                         argument = initialValue == null ? new int64() : new int64(initialValue.toString());
                      }
                      break;
                   case "character":
                      argument = initialValue == null ? new character() : new character(initialValue.toString());
                      break;
                   case "double":
                   case "float":
                      if (initialValue instanceof Number)
                      {
                         argument = new decimal(((Number) initialValue).doubleValue());
                      }
                      else
                      {
                         argument = initialValue == null ? new decimal() : new decimal(initialValue.toString());
                      }
                      break;
                }
             }

             return argument;
          }
       }

    }