# ControlFlowOps.java

    /*
    ** Module   : ControlFlowOps.java
    ** Abstract : Progress compatible control flow helper methods
    **
    ** Copyright (c) 2005-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
    ** 001 GES 20050616   @21506 Created initial version, supporting PAUSE.
    ** 002 SIY 20050815   @22152 Added invoke() method used by RUN VALUE().
    ** 003 GES 20050829   @22381 Added error condition processing.
    ** 004 GES 20060117   @23955 Removed PAUSE (it now resides in UI code).
    ** 005 GES 20060123   @24026 Added return-value support as context-local.
    ** 006 GES 20060314   @25063 Fixed exception processing in invoke().
    ** 007 ECF 20060329   @25259 Removed fillInStackTrace calls to exceptions
    **                           caught and re-thrown in invoke(). These were
    **                           masking the true origins of the exceptions.
    ** 008 GES 20060427   @25774 Safety code to allow wrapper classes to be
    **                           passed as a program name.
    ** 009 GES 20061030   @30804 Better processing of condition exceptions.
    ** 010 GES 20070220   @32194 Added support for RUN IN.
    ** 011 GES 20080619   @38885 If a RUN VALUE (actually any RUN statement)
    **                           cannot find the procedure to execute, a STOP
    **                           condition is raised (after some error text
    **                           has been displayed on the terminal), not an
    **                           ERROR. This means that silent error mode does
    **                           not apply, the exception is always thrown and
    **                           the ERROR-STATUS handle is not updated.
    ** 012 GES 20080619   @39166 Fix for RUN VALUE of an internal procedure in
    **                           compact mode.
    ** 013 SIY 20080804   @39280 Changed signature of simple invoke(). This
    **                           enables correct calls to internal procedures
    **                           without redundant instantiation of class and
    **                           greatly simplifies compact mode.
    **                           Some minor cleanups and potential NPE fix.
    ** 014 GES 20090422   @41906 Converted to standard string formatting.
    ** 015 GES 20101005          Added a stub (non-functional) for a RUN statement
    **                           that references a PROCEDURE EXTERNAL definition.
    **                           Added a stub for DELETE PROCEDURE.
    ** 016 CA  20130116          Major rework to provide support for IN handle and
    **                           IN SUPER functions and procedures, plus RUN SUPER
    **                           and SUPER statements.
    ** 017 CA  20130126          Added support for RELEASE EXTERNAL (converts to
    **                           a ControlFlowOps.release(<library>) call, which
    **                           is only a stub at this time).
    ** 018 CA  20130213          Fixed some cases of extent parameter validation.
    ** 019 CS  20130213          Added APIs supporting the ON server and ASYNC clauses for the RUN
    **                           statement.
    ** 020 CA  20130223          Added conversion support for the RUN ... SET hPort ON hWebServer stmt
    **                           to invoke web services.
    ** 021 CA  20130315          Added support for cases when a WrappedResource is passed to a handle
    **                           parameter.
    ** 022 OM  20130404          Fixed unknown parameter in function/procedure calls.
    **                           Abort function/procedure call if errors occurred when converting
    **                           compatible parameters (ex: 32bit overflow from decimal/int64).
    ** 023 CA  20130529          Added support for RUN ... [PERSISTENT [handle]] ON SERVER server and
    **                           RUN ... IN remoteProcHandle.
    ** 024 CA  20130708          Fixed validation of arrays with non-BDT components (these will be
    **                           validated by attempting to instantiate a wrapper using a c'tor which
    **                           accepts this array as a parameter).
    ** 025 CA  20130813          Added support for async procedure calls.
    ** 026 SVL 20131203          Changes caused by renaming of Nameable.get/setName to Nameable.name.
    ** 027 CA  20131116          Added support for the FUNCTION ... MAP TO clause.  Fixed runtime for
    **                           RUN SUPER and SUPER() calls, in search-target and search-self modes.
    ** 028 EVK 20131204          Made enum ArgValidationErrors public. Added method
    **                           reachableInternalEntry() to check reachability of procedure or
    **                           function in 4gl handle.
    ** 029 CA  20131220          Refactored the async invocation code and the remote invocation code
    **                           to support both web service and appserver requests.
    ** 030 GES 20140116          Added support for native library calls.
    ** 031 CA  20140218          Fixed SOURCE- and TARGET-PROCEDURE when invoking external programs.
    **                           Error messages must use the program name as it was used at the RUN
    **                           statement. Internally, the absolute name must be used (i.e. when
    **                           invoking SourceNameMapper APIs).
    ** 032 ECF 20140613          Replaced renamed RecordBuffer method in getSignature.
    ** 033 MAG 20140626          Prevent fault messages to be displayed twice when invoke a web
    **                           service.
    ** 034 VMN 20140702          Added method convertValue() for correct processing unknown value
    **                           in invoke... methods.
    ** 035 HC  20140613          Improved extent parameter support and added logging.
    ** 036 SVL 20140709          Handle InvalidParameterConditionException in invokeImpl.
    ** 037 CA  20141106          Changed H035 - log only exceptions which are not propagated.
    ** 038 CA  20150504          A persistent procedure needs to be saved in its specified handle only
    **                           if it completes properly.
    ** 039 ECF 20150527          Added a cache to InternalEntryCaller as a performance enhancement.
    ** 040 OM  20150624          Improved procedure parameters type checking for OUT and IN-OUT modes.
    **                           Added implicit conversion for parameters.
    ** 041 OM  20150722          In calls to ControlFlowOps.runSuper(), the modes are unknown.
    ** 042 CA  20150813          Added BlockManager.markPendingDynCall to initializeExternalProcedure.
    **                           This is required so that the remote procedure call is not interpreted
    **                           like a java-style method call, when a proxy procedure is used by an
    **                           appserver.
    ** 043 ECF 20150815          Performance improvements in CacheKey c'tor and getSignature.
    ** 044 CA  20151024          Fixed assignment of DYNAMIC-FUNCTION ... NO-ERROR when the function
    **                           can not be found.
    ** 045 OM  20161012          Fixed variable parameter passing in OUTPUT & INPUT-OUTPUT mode.
    ** 046 CA  20151219          In case of ERROR conditions raised by shared resource management,
    **                           if the caller has a NO-ERROR clause, then the condition must NOT be
    **                           raised, but logged; also, the external program execution must stop,
    **                           and return back to the caller immediately.
    ** 047 OM  20160212          Called cleanupPending() for various Managers on procedure
    **                           instantiation failure.
    ** 048 CA  20160404          Enhanced the appserver invocations, to allow implicit or explicit
    **                           termination of requests being executing on a certain connection.
    ** 049 CA  20160418          Added infrastructure for message passing from converted code to a
    **                           customer-specific application or calls from the customer-specific
    **                           application into the converted code (i.e. invoke external programs,
    **                           procedures or functions).  Can be used only when the P2J client runs
    **                           embedded in a customer-specific application.
    ** 050 OM  20160302          ExternalProgramResolver resolves .r code filenames and resolves them
    **                           as the .p/.w source code procedure.
    ** 051 OM  20160527          SourceNam eMapper.convertName() changed to convertNameToClass().
    ** 052 HC  20160720          RUN, DYNAMIC-FUNCTION and function invocation must not show an error
    **                           when the given target handle is invalid and NO-ERROR is specified.
    ** 053 CA  20160728          Fixed undoable support for data in external programs ran persistent.
    ** 054 HC  20160817          Fixed PUBLISH to resolve the target internal procedure in the
    **                           subscribed procedure as well as in its SUPER procedures or SESSION
    **                           procedures.
    **                           Fixed RUN invocation logic to set up the correct TARGET-PROCEDURE
    **                           when the resolved internal procedure is found in a SUPER procedure.
    ** 055 HC  20160824          Fixed a regression from the previous change, FUNCTION ... IN
    **                           statement did not properly resolve the function from the super
    **                           procedures.
    ** 056 HC  20160901          Fixed error handling of procedure handle validation in a RUN or
    **                           function invocation, a regression introduced in H052.
    ** 057 ECF 20160912          Flattened invocation calling hierarchy. Although this is somewhat
    **                           less maintainable due to argument list duplication across multiple
    **                           methods, it avoids several layers of method calls in heavily executed
    **                           code paths.
    ** 058 CA  20161020          Fixed case-insensitive resolution with .r (rcode) ext program names.
    ** 059 CA  20170328          Fixed a problem with parameter validation when invoked by a remote
    **                           non-P2J side (i.e. API invoked by an embedded client).
    ** 060 GES 20171206          Switch to ErrorManager.silent(). Added TODOs.
    ** 061 ECF 20171227          Performance improvement.
    **     GES 20180102          Clarified error handling comments.
    ** 062 EVL 20180202          Filter pre/post steps for native library calls.
    ** 063 CA  20180510          Delegate lockWindowUpdate user32.dll calls to LT.disableRedraw.
    **     CA  20180521          The IN SUPER function definition in the current file may provide a
    **                           return type different than its actual implementation - this affects
    **                           function calls, so that it must not fail if return types differ.
    ** 064 CA  20181029          Added support for CALL dynamic invoke.
    ** 065 CA  20181112          Added some error processing related to RUN ... TRANSACTION DISTINCT.
    **                           Fixed a case of RUN ... PERSISTENT SET ... REMOTE ASYNC, where the
    **                           remote handle is the SESSION.
    **                           function calls, so that it must not fail if return types differ.
    ** 066 EVL 20181102          Fix for persistent procedure to be removed when error happens during
    **                           it's execution.
    ** 067 CA  20190122          Added legacy class instantiation support.
    **         20190128          Added legacy class initialization support (static c'tor).
    **     CA  20190201          When both 'IN handle' and local func definition exist, local def
    **                           must not be hidden.  Callers of this function will resolve it via
    **                           the 'IN handle' forward definition.
    **     CA  20190220          Added runtime support for DYNAMIC-INVOKE, DYNAMIC-NEW.
    **     HC  20190226          When procedure is invoked persistently the target handle with the
    **                           persistent procedure must be assigned before the procedure is
    **                           invoked.
    **     CA  20190324          Optimizations of InternalEntryResolver, to avoid multiple hits to
    **                           SourceNameMapper.
    **     CA  20190325          Implemented ProcedureHelper instead of direct usage of the static
    **                           API.  This allows the elimination of context local usage in
    **                           ProcedureManager.
    **                           Refactored some APIs into ControlFlowOpsHelper, to be able to use
    **                           this class without a context-local hit.
    **     ECF 20190326          Replace String.format with less expensive concatenation in a commonly
    **                           used error message.
    ** 068 CA  20190508          OutputParameter.wrap must be fully generic.
    **     CA  20190526          Fixed runtime and conversion support for legacy class properties used
    **                           as OUTPUT or INPUT-OUTPUT arguments.
    ** 069 CA  20190613          A legacy class member can be invoked remotely, as an entry point.
    ** 070 CA  20190628          Added RUN SINGLE-RUN and RUN SINGLETON support.
    ** 071 CA  20190710          Fixed some issue with legacy object arguments.
    **     CA  20190723          All the static members must be 'get' before the static class c'tor is
    **                           executed, so they get registered with the surrogate instance for the
    **                           static class.
    ** 072 IAS 20190926          Fixed Legacy Class initialization
    ** 073 CA  20190927          Added support for direct Java access from 4GL code.
    ** 074 CA  20191119          Track the executing legacy class, to be able to register any buffer
    **                           to its source definition class.  Fixed an issue with disambiguating
    **                           a legacy OE class method, during dynamic invoke.
    **     CA  20191211          Fixed a NPE for async requests.
    **     CA  20191212          Fixed constructor lookup (look for super-class or interface match).
    ** 075 CA  20200213          Added support for RUN ... AS-THREAD [SET ht]. statement.
    ** 076 CA  20200412          A dynamic OO method invoke from an instance context can look for a
    **                           static method (this is allowed to solve a parser issue, where a
    **                           non-dynamic method call is marked as dynamic, as the caller as BDT
    **                           arguments and we need to let the runtime to resolve the target).
    ** 077 CA  20200324          A fix for legacy object instantiation - use the ctor method found
    **                           after argument validation.
    ** 078 CA  20200330          More fixes related to OE-compatible error management, in constructor
    **                           case and 'implicit' 4GL ERROR conditions.
    ** 079 CA  20200427          Refactored so that the remote appserver calls use InvokeConfig, to
    **                           allow transfer of the SERVER:REQUEST-INFO to Agent's
    **                           SESSION:CURRENT-REQUEST-INFO.
    **     CA  20200514          Added DATETIME-TZ alias to supported types.
    ** 080 ME  20200521          Make sure we register the full class hierarchy for qualified classes
    **                           used as parameters in constructors as it might have not been registered.
    ** 081 VVT 20200805          Comhandle input procedure parameters can now be converted to handles.
    **                           See #4841 and #4831 issues.
    ** 082 CA  20200827          Reworked asynchronous invocations to perform all context-local work on the
    **                           Conversation thread, and let only the actual invocation be performed in an
    **                           AssociatedThread.
    ** 083 AIL 20200622          Made use of the new procedure delete signature.
    **     CA  20200811          SUPER() function has as first argument the caller's return type.
    **     GES 20200813          Removed come context-local lookups for ErrorManager.
    **     AIL 20200901          Parameter type check is made on a clean pendingError flag.
    **     CA  20200927          Added ControlFlowOpsHelper.setReturnValue(String).
    **     CA  20200927          Avoid context-local lookups by relying on the helper instance.
    **     CA  20201003          Replaced Guava identity HashSet with Collections.newSetFromMap(IdentityHashMap).
    **     RFB 20201015          Since thisProcedure can return null, validHandle needed to protect the error
    **                           message buildup. Ref. 4936.
    **     SVL 20210127          Added isReturnValueSetInRootScope.
    **     SVL 20210202          Assign return value to an invocation result only if the value was set during this
    **                           call.
    **     CA  20210203          Fixed dynamic vs non-dynamic extent argument validation for OO calls.
    **     CA  20210216          Arithmetic operations are always int64 - this requires special processing when
    **                           passing such an expression as an argument, so the conversion-time type of the
    **                           expression is preserved.
    **     OM  20210309          Decreased the amount of "incompatible type" messages printed to log.
    **     CA  20210421          When validating the arguments, convert them to a BDT instance if they are Java
    **                           types as the call passed Java literals.
    **     CA  20210511          A RUN statement executed from the global block will always target an external
    **                           program executed.
    **     CA  20210428          A DYNAMIC-FUNCTION/FUNCTION call must raise an ERROR condition only and only if
    **                           the failure is from a remote appserver agent call, and not a call from within
    **                           the appserver agent execution.
    **     CA  20210609          Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly
    **                           initialized at the method's execution, and not at the caller's arguments.
    **     AIL 20210615          Check if handle should be set before or after run ... persistent set.
    **     CA  20210810          Raised error 6456 if an empty string is passed to 'RUN ... IN handle'.
    **     HC  20211011          Implemented i18n support.
    **     CA  20211025          If argument validation failed, then cleanup all pending resources.
    **     CA  20211222          Fixed a bug in invokeLegacyMethod - if there is an exception, throw it, so it can
    **                           be processed, and not just return null.
    **     CA  20220120          Performance improvement for pushing a new calee scope, keep the already resolved
    **                           internal entry legacy name.
    **     CA  20220206          returnValueSetInRootScope must be set only when the RETURN is executed from the
    **                           root nesting level.
    **     AL2 20220319          Added value proxy logic: wrap for dynamic and unwrap for static.
    **     RFB 20220712          Use a new tm method isRootExternal() instead of isRootNestingLevel() to determine
    **                           nesting level in setReturnValue methods so that assignments in a DO determine it
    **                           properly. Ref #6587.
    **     VVT 20220722          Added method isHandleValidProcedure() to support the fix for #6606.
    **     VVT 20220722          Method isHandleValidProcedure() removed as unneeded. See #6606-12.
    **     TW  20220803          Error-status:error must be set for return error in method, while return error
    **                           in old-style ABL function must remain the same. Refs: #6567.
    **     TW  20220806          Error-status:error must be set for return error in dynamic-invoke of method
    **                           of ABL class. Fixes xfer testc ErrorStatusFlowTester.w Test06.
    **     TW  20220817          Error-status:error must be set for return error in static method.
    **     CA  20220930          Refactored the callback invocation to be performed via a call-site and
    **                           InvokeConfig, to allow caching of the resolved target.
    **     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the
    **                           registration is now specific to each type of scopeable.  For each case, the block
    **                           will be registered for scope support (for that particular scopeable) only when
    **                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
    **                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
    **                           processing all the scopeables for each and every block.
    **     CA  20221010          Fixed problems in CA/20220930:
    **                           - for typed FUNCTION calls, convert the returned value to the expected type.
    **                           - fixed caching issues when the call does not complete due to errors or for
    **                           recursive calls.
    **     TJD 20220504          Java 11 compatibility minor changes
    **     CA  20221117          When resolving a SUPER procedure or function, if the resolution ends up in the
    **                           SOURCE-PROCEDURE or one of the already processed super-procedure, then ignore
    **                           it and move to another super-procedure.
    **     TW  20221123          Augmented the error feedback, which omitted the target invocation name if
    **                           no parameters expected. Troubleshooting went haywire after that, since
    **                           root causes were sought at the wrong frame in the stack (refs: #6906).
    **     CA  20220520          The constructor (static or instance) and destructor lookup now relies on the
    **                           LegacySignature annotation to resolve them, instead of the Java method name -
    **                           this is required for ctor overload support at conversion and runtime.
    **     CA  20220524          Fixed destructor execution.
    **     CA  20220601          Any TableParameter or DataSetParameter instances at a legacy OO method or
    **                           constructor call must be transformed to their mode'ed version, depending on the
    **                           INPUT, OUTPUT or INPUT-OUTPUT mode, and the DATASET-HANDLE, DATASET, TABLE-HANDLE
    **                           or TABLE versions.
    **     CA  20220606          Added lazy loading of legacy converted classes.
    **                           Fixed invokeFailure, must raise a STOP condition, and not a legacy exception.
    **     CA  20220727          Fixed OO dataset/table parameters (at the method definition and method call) when
    **                           there is an APPEND option.
    **     CA  20221010          Fixed problems in CA/20220930:
    **                           - for typed FUNCTION calls, convert the returned value to the expected type.
    **                           - fixed caching issues when the call does not complete due to errors or for
    **                           recursive calls.
    **     SVL 20230113          Improved performance by replacing some "for-each" loops with indexed "for" loops.
    **     HC  20230125          Added caching of resolved procedure internal entries.
    ** 084 CA  20230215          Added unknown.UNKNOWN singleton (used by FWD runtime at this time).
    ** 085 GBB 20230315          Redundant ErrorManager.buildErrorText removed before ErrorManager.displayError.
    ** 086 GBB 20230502          Run external procedure to produce error always if missing.
    ** 087 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    ** 088 CA  20230615          Small optimization - avoid using obj.ref() and use instead obj.ref, if the 'obj'
    **                           instance is known to be valid.
    ** 089 ME  20230905          Make invokeLegacyMethod public and add public resolveLegacyEntry (used by
    **                           reflection).
    **     ME  20230919          Try to avoid index out of bounds exceptions on prepareParameters if more
    **                           arguments used.
    ** 090 CA  20230928          The openedge method resolution when the match is done via a poly argument
    **                           (like h::f1), it will assume the return type of the last found overload - the
    **                           runtime will still resolve the target based on the arguments, but the lookup must
    **                           be done on the type resolved by the conversion, and not the runtime time of the
    **                           lvalue on the chain call. For this reason, and to allow the chained call to fail
    **                           properly at runtime, both the lookup type and the return type of the call are
    **                           emitted at the 'invokePoly' API call.
    **     CA  20231009          private methods can be invoked only if they are defined in the the current
    **                           executing type.
    ** 091 CA  20231019          Proxy BDT instances must have the 'val' resolved before looking the target for
    **                           dynamic calls.
    ** 092 GBB 20231023          SecurityManager context methods calls updated.
    ** 093 CA  20231023          Fixed 'resolveLegacyEntry' for 'unknown' type arguments.
    ** 094 CA  20231031          The constructor and destructor 'internal entry name' is the exact legacy name
    **                           for this entry, as it is reported by PROGRAM-NAME and error callstack.
    ** 095 CA  20231030          Fixed dynamic invoke via legacy interface types.
    ** 096 CA  20231208          POLY OO method invocations must use the var's declared type and not the runtime
    **                           type.
    ** 097 CA  20231213          The synthetic 'execute' methods emitted for legacy classes can be dropped if there
    **                           is nothing emitted in them.  These Java methods are also marked with Type.EXECUTE
    **                           LegacySignature annotation, and their BlockManager API removed - the FWD runtime
    **                           will run this only once.
    ** 098 SVL 20240207          Preserve case sensitivity of the returned character value of a dynamically called
    **                           function.
    ** 099 CA  20240331          Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime, so
    **                           logical type checks must include sub-classes, too.
    ** 100 SC  20240508          Added method argument for checking if the function call is dynamic
    ** 101 GBB 20240610          StopConditionException from remote procedure invokation on appservers to be
    **                           handled by the BlockManager.
    ** 102 GBB 20240105          Fixes QUIT executed on an appserver terminates the client.
    ** 103 SP  20240610          Fixed prepareArgs(modes, args) to set the correct mode'ed version for TABLE-HANDLE.
    ** 104 ES  20240715          Added public cleanupPending method.
    ** 105 AL2 20240712          Improved checking extent types (non-extent argument, extent parameter).
    ** 106 DDF 20240311          Modified initializeLegacyObject() method to avoid executing the constructor
    **                           method based on an additional parameter passed.
    ** 107 SP  20240729          Added a case when we shouldn't cache the failed result for
    **                           fuzzy parameter matching.
    ** 108 GBB 20240923          Expose error code number for procedure not found.
    ** 109 CA  20241030          Added immutable character constant support.
    ** 110 CA  20241105          Fixed resolution of native/external procedure calls which are resolved from
    **                           super-procedures.
    ** 111 CA  20241211          RUN ... PERSISTENT ON SERVER hs SET hp1. must treat 'hp1' (which is actually the
    **                           port-handle if we follow the syntax of RUN statement) as the persistent handle
    **                           only if 'hs' is not the SESSION handle.
    ** 112 DDF 20250131          When a buffer is passed as a parameter, create a proxy from the expected type
    **                           to the argument's instance, but only if the two dmos match.
    **     DDF 20250210          Added an additional parameter to isSchemaMatch() call.
    ** 113 GBB 20250403          Support child connections. Make some methods public to be accessed from the
    **                           appserver package.
    ** 114 AS  20250408          Wrap the argument in a TableHandle in prepareArgs only if the handle is null.
    ** 115 PBB 20250410          Fixed bug with method resolution where a method receiving an object parameter
    **                           would not be able to match on the exact signature
    ** 116 ES  20240221          Switch to throwing the StopConditionException through ErrorManager.
    **     ES  20250304          Raise a StopError legacy exception in case a procedure name is not found.
    ** 117 PBB 20250509          Improved the process of method resolution when parameters are extents.
    ** 118 PBB 20250522          Avoid the wrongful failure of the process of method resolution when the
    **                           method is declared as taking a dynamic extent.
    **                           Fixed the problem related to the java.lang.NoSuchMethodException error being
    **                           printed at the server stdout when trying to cast an array to a BDT.
    */

    /*
    ** 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.ref.*;
    import java.lang.reflect.*;
    import java.util.*;
    import java.util.concurrent.*;
    import java.util.function.*;
    import java.util.logging.*;

    import com.esotericsoftware.reflectasm.*;
    import com.goldencode.cache.*;
    import com.goldencode.p2j.*;
    import com.goldencode.p2j.library.*;
    import com.goldencode.p2j.main.*;
    import com.goldencode.p2j.net.*;
    import com.goldencode.p2j.oo.lang.*;
    import com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.persist.orm.DmoMetadataManager;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.security.SecurityManager;
    import com.goldencode.p2j.ui.*;
    import com.goldencode.p2j.util.BlockManager.TransactionType;
    import com.goldencode.p2j.util.InvocationRequestPayload.InvokeType;
    import com.goldencode.p2j.util.logging.*;

    /**
     * Progress compatible control-flow related static utility methods. Current
     * implementation contains support for the:
     * <p>
     * <pre>
     * Progress 4GL Feature                  Java Implementation
     * -----------------------------------   ------------------------------------
     * RUN [ &lt;procedure&gt; | VALUE() ]   invoke()
     * RUN ... IN &lt;handle&gt;             invokeIn()
     * RUN ... PERSIST                       invokePersistent()
     * RUN ... PERSIST SET &lt;handle&gt;    invokePersistentSet()
     * RETURN-VALUE "global variable"        getReturnValue()/setReturnValue()
     * RUN SUPER                             runSuper()
     * simple function invocation            invokeFunction()
     * FUNCTION ... IN handle                invokeFunctionIn()
     * DYNAMIC-FUNCTION                      invokeDynamicFunction()
     * DYNAMIC-FUNCTION(... IN)              invokeDynamicFunctionIn()
     * SUPER()                               runSuper()
     * RELEASE EXTERNAL [PROCEDURE]          release()
     * </pre>
     */
    public class ControlFlowOps
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(ControlFlowOps.class);

       /** Enum with the list of possible argument validation errors. */
       public enum ArgValidationErrors
       {
          NO_ERROR,
          INCORRECT_COUNT,
          UNEXPECTED_PARAM,
          INCORRECT_TYPES,
          DYN_ARRAY_FOR_NON_ARRAY,
          NON_ARRAY_FOR_DYN_ARRAY,
          ARRAY_FOR_NON_ARRAY,
          NON_ARRAY_FOR_ARRAY,
          BAD_ARRAY_DIMENSION,
          DEFER_VALIDATION
       }

       /** Exit code for procedure not found. */
       public static final int EXIT_CODE_PROCEDURE_NOT_FOUND = 293;

       /** Stores context-local state variables. */
       private static ContextContainer work = new ContextContainer();

       /**
        * Get an instance of {@link ControlFlowOpsHelper}, which has the context-local instance saved,
        * so API access can be done without a context-local query.
        *
        * @return   See above.
        */
       public static ControlFlowOpsHelper getHelper()
       {
          return new ControlFlowOpsHelper(work.get());
       }

       /**
        * Attempts to unlpad the specified library from memory (the RELEASE EXTERNAL statement).
        * This code just delegates the processing to the {@link NativeInvoker#release} method.
        *
        * @param    libname
        *           The name of the library to attempt to unload.
        */
       public static void release(character libname)
       {
          if (libname == null)
          {
             // quick/silent out, there is no way to unload a library in this case
             return;
          }

          if (libname.isUnknown())
          {
             WorkArea wa = work.obtain();

             // strange case where the error is raised in normal mode (without any message
             // display) and in silent mode only the error flag is set (but normal details
             // are not recorded)
             wa.em.noRecordOrThrowError("Unknown cannot be given for a libname!");
          }
          else
          {
             release(libname.toStringMessage());
          }
       }

       /**
        * Attempts to unload the specified library from memory (the RELEASE EXTERNAL statement).
        * This code just delegates the processing to the {@link NativeInvoker#release} method.
        *
        * @param    libname
        *           The name of the library to attempt to unload. If <code>null</code> or the
        *           empty string are passed, this is a no-operation.
        */
       public static void release(String libname)
       {
          if (libname == null || libname.length() == 0)
          {
             // quick/silent out, there is no way to unload a library in this case
             return;
          }

          NativeInvoker.release(libname);
       }

       /**
        * Gets the most recent RETURN-VALUE for the current user's session.
        *
        * @return   The RETURN-VALUE which defaults to the empty string if a
        *           the equivalent of a Progress 4GL RUN statement has executed
        *           but no explicit RETURN-VALUE was set.
        */
       public static character getReturnValue()
       {
          return work.obtain().returnValue;
       }

       /**
        * Returns <code>true</code> if <code>RETURN-VALUE</code> was set during the call of the current root
        * external program. Used to determine if we need to update <code>RETURN-VALUE</code> after an appserver
        * call.
        *
        * @return <code>true</code> if <code>RETURN-VALUE</code> was set during the call of the current root
        *          external program.
        */
       public static boolean isReturnValueSetInRootScope()
       {
          return work.obtain().returnValueSetInRootScope;
       }

       /**
        * Reset the {@link WorkArea#returnValueSetInRootScope} flag.
        */
       public static void resetReturnValueSetInRootScope()
       {
          work.obtain().returnValueSetInRootScope = false;
       }

       /**
        * Sets the most recent RETURN-VALUE for the current user's session.
        *
        * @param    returnValue
        *           The RETURN-VALUE to set or <code>null</code> to assign the
        *           empty string.
        */
       public static void setReturnValue(character returnValue)
       {
          WorkArea wa = work.obtain();
          if (returnValue == null)
          {
             wa.returnValue.assign(character.EMPTY_STRING);
          }
          else
          {
             wa.returnValue.assign(returnValue);
          }

          wa.returnValueSetInRootScope = wa.tm.isRootExternal();
       }

       /**
        * Sets the most recent RETURN-VALUE for the current user's session.
        *
        * @param    returnValue
        *           The RETURN-VALUE to set or <code>null</code> to assign the
        *           empty string.
        */
       public static void setReturnValue(String returnValue)
       {
          WorkArea wa = work.obtain();
          wa.returnValue.assign(returnValue == null ? "" : returnValue);
          wa.returnValueSetInRootScope = wa.tm.isRootExternal();
       }

       /**
        * Execute a SUPER() function call, forcing the returned value to be of the specified type.
        *
        * @param    type
        *           The caller function's return type.
        * @param    iename
        *           The internal-entry's legacy 4GL name.
        * @param    args
        *           The argument list.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default, on error or for
        *           procedures.
        */
       public static <T extends BaseDataType> T runSuper(Class<T> type, String iename, Object... args)
       {
          BaseDataType superVal = runSuper(iename, args);

          T ret = null;
          if (type.isAssignableFrom(superVal.getClass()))
          {
             ret = (T) superVal;
          }
          else
          {
             ret = (T) BaseDataType.generateUnknown(type);
             ret.assign(superVal);
          }

          return ret;
       }

       /**
        * Execute the RUN SUPER statement or SUPER function. Runtime decides on
        * whether this is a function or procedure, based on the
        * {@link TransactionManager#nearestExecutingBlock() nearest block}.
        *
        * @param    iename
        *           The internal-entry's legacy 4GL name.
        * @param    args
        *           The argument list.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default, on error or for
        *           procedures.
        */
       public static BaseDataType runSuper(String iename, Object... args)
       {
          WorkArea wa = work.obtain();

          BlockDefinition block = wa.tm.nearestExecutingBlock();
          boolean function = (block.type == BlockType.FUNCTION);
          handle target = wa.pm.getSuperHandle(iename, function);
          if (target == null)
          {
             return unknown.UNKNOWN;
          }

          character name = new character(iename);

          if (function)
          {
             return invokeFunctionImpl(name, target, false, true, null, args);
          }
          else
          {
             invokeInImpl(name, target, true, null, args);
             return unknown.UNKNOWN;
          }
       }

       /**
        * Execute a <code>RUN ... SINGLE-RUN ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
        *
        * @param    name
        *           The external program name.
        * @param    remoteHandle
        *           The remote server.
        * @param    transactionDistinct
        *           Flag indicating if TRANSACTION-DISTINCT was set.
        */
       public static void invokeRemoteSingleRun(String    name,
                                                handle    remoteHandle,
                                                boolean   transactionDistinct)
       {
          invokeSingletonOrSingleRun(true, new character(name), null, remoteHandle, transactionDistinct);
       }

       /**
        * Execute a <code>RUN ... SINGLE-RUN SET h</code> statement.
        *
        * @param    name
        *           The external program name.
        */
       public static void invokeSingleRun(String name)
       {
          invokeSingletonOrSingleRun(true, new character(name), null, null, false);
       }

       /**
        * Execute a <code>RUN ... SINGLETON ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
        *
        * @param    name
        *           The external program name.
        * @param    remoteHandle
        *           The remote server.
        * @param    transactionDistinct
        *           Flag indicating if TRANSACTION-DISTINCT was set.
        */
       public static void invokeRemoteSingleton(String    name,
                                                handle    remoteHandle,
                                                boolean   transactionDistinct)
       {
          invokeSingletonOrSingleRun(false, new character(name), null, remoteHandle, transactionDistinct);
       }

       /**
        * Execute a <code>RUN ... SINGLETON</code> statement.
        *
        * @param    name
        *           The external program name.
        */
       public static void invokeSingleton(String name)
       {
          invokeSingletonOrSingleRun(false, new character(name), null, null, false);
       }

       /**
        * Execute a <code>RUN ... SINGLE-RUN SET h ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
        *
        * @param    name
        *           The external program name.
        * @param    hproc
        *           The handle where to save the deferred configuration.
        * @param    remoteHandle
        *           The remote server.
        * @param    transactionDistinct
        *           Flag indicating if TRANSACTION-DISTINCT was set.
        */
       public static void invokeRemoteSingleRunSet(String    name,
                                                   handle    hproc,
                                                   handle    remoteHandle,
                                                   boolean   transactionDistinct)
       {
          invokeSingletonOrSingleRun(true, new character(name), hproc, remoteHandle, transactionDistinct);
       }

       /**
        * Execute a <code>RUN ... SINGLE-RUN SET h</code> statement.
        *
        * @param    name
        *           The external program name.
        * @param    hproc
        *           The handle where to save the deferred configuration.
        */
       public static void invokeSingleRunSet(String name, handle hproc)
       {
          invokeSingletonOrSingleRun(true, new character(name), hproc, null, false);
       }

       /**
        * Execute a <code>RUN ... SINGLETON SET h ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
        *
        * @param    name
        *           The external program name.
        * @param    hproc
        *           The handle where to save the deferred configuration.
        * @param    remoteHandle
        *           The remote server.
        * @param    transactionDistinct
        *           Flag indicating if TRANSACTION-DISTINCT was set.
        */
       public static void invokeRemoteSingletonSet(String    name,
                                                   handle    hproc,
                                                   handle    remoteHandle,
                                                   boolean   transactionDistinct)
       {
          invokeSingletonOrSingleRun(false, new character(name), hproc, remoteHandle, transactionDistinct);
       }

       /**
        * Execute a <code>RUN ... SINGLETON SET h</code> statement.
        *
        * @param    name
        *           The external program name.
        * @param    hproc
        *           The handle where to save the deferred configuration.
        */
       public static void invokeSingletonSet(String name, handle    hproc)
       {
          invokeSingletonOrSingleRun(false, new character(name), hproc, null, false);
       }

       /**
        * Execute a <code>RUN ... SINGLETON ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
        *
        * @param    name
        *           The external program name.
        * @param    remoteHandle
        *           The remote server.
        * @param    transactionDistinct
        *           Flag indicating if TRANSACTION-DISTINCT was set.
        */
       public static void invokeRemoteSingleRun(character name,
                                                handle    remoteHandle,
                                                boolean   transactionDistinct)
       {
          invokeSingletonOrSingleRun(true, name, null, remoteHandle, transactionDistinct);
       }

       /**
        * Execute a <code>RUN ... SINGLE-RUN</code> statement.
        *
        * @param    name
        *           The external program name.
        */
       public static void invokeSingleRun(character name)
       {
          invokeSingletonOrSingleRun(true, name, null, null, false);
       }

       /**
        * Execute a <code>RUN ... SINGLETON ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
        *
        * @param    name
        *           The external program name.
        * @param    remoteHandle
        *           The remote server.
        * @param    transactionDistinct
        *           Flag indicating if TRANSACTION-DISTINCT was set.
        */
       public static void invokeRemoteSingleton(character name,
                                                handle    remoteHandle,
                                                boolean   transactionDistinct)
       {
          invokeSingletonOrSingleRun(false, name, null, remoteHandle, transactionDistinct);
       }

       /**
        * Execute a <code>RUN ... SINGLETON.</code> statement.
        *
        * @param    name
        *           The external program name.
        */
       public static void invokeSingleton(character name)
       {
          invokeSingletonOrSingleRun(false, name, null, null, false);
       }

       /**
        * Execute a <code>RUN ... SINGLE-RUN SET h ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
        *
        * @param    name
        *           The external program name.
        * @param    hproc
        *           The handle where to save the deferred configuration.
        * @param    remoteHandle
        *           The remote server.
        * @param    transactionDistinct
        *           Flag indicating if TRANSACTION-DISTINCT was set.
        */
       public static void invokeRemoteSingleRunSet(character name,
                                                   handle    hproc,
                                                   handle    remoteHandle,
                                                   boolean   transactionDistinct)
       {
          invokeSingletonOrSingleRun(true, name, hproc, remoteHandle, transactionDistinct);
       }

       /**
        * Execute a <code>RUN ... SINGLE-RUN SET h</code> statement.
        *
        * @param    name
        *           The external program name.
        * @param    hproc
        *           The handle where to save the deferred configuration.
        */
       public static void invokeSingleRunSet(character name, handle hproc)
       {
          invokeSingletonOrSingleRun(true, name, hproc, null, false);
       }

       /**
        * Execute a <code>RUN ... SINGLETON SET h ON SERVER h [TRANSACTION-DISTINCT]</code> statement.
        *
        * @param    name
        *           The external program name.
        * @param    hproc
        *           The handle where to save the deferred configuration.
        * @param    remoteHandle
        *           The remote server.
        * @param    transactionDistinct
        *           Flag indicating if TRANSACTION-DISTINCT was set.
        */
       public static void invokeRemoteSingletonSet(character name,
                                                   handle    hproc,
                                                   handle    remoteHandle,
                                                   boolean   transactionDistinct)
       {
          invokeSingletonOrSingleRun(false, name, hproc, remoteHandle, transactionDistinct);
       }

       /**
        * Execute a <code>RUN ... SINGLETON SET h</code> statement.
        *
        * @param    name
        *           The external program name.
        * @param    hproc
        *           The handle where to save the deferred configuration.
        */
       public static void invokeSingletonSet(character name, handle    hproc)
       {
          invokeSingletonOrSingleRun(false, name, hproc, null, false);
       }

       /**
        * Invalidate the entire {@link WorkArea#callSiteCache cache}.
        */
       public static void invalidateCallSiteCache()
       {
          WorkArea wa = work.obtain();
          wa.callSiteCache.clear();
       }

       /**
        * Invalidate the {@link WorkArea#callSiteCache cache} for the specified instance.
        *
        * @param    referent
        *           The instance to remove.
        */
       public static void invalidateCallSiteCache(Object referent)
       {
          WorkArea wa = work.obtain();
          wa.callSiteCache.remove(referent);
       }

       /**
        * Using the current invoke configuration, perform the call.
        *
        * @param    cfg
        *           The configuration to invoke.
        *
        * @return   The return value from the block (or its nested blocks) which is the
        *           <code>unknown value</code> by default, on error or for procedures.
        */
       public static BaseDataType invoke(InvokeConfig cfg)
       {
          return invoke(null, null, cfg);
       }

       /**
        * Using the current invoke configuration, perform the call.
        *
        * @param    cls
        *           The return type in case of function calls.
        * @param    resource
        *           When not-null, this is an FWD internal call, associated with a callback for a resource.
        * @param    cfg
        *           The configuration to invoke.
        *
        * @return   The return value from the block (or its nested blocks) which is the
        *           <code>unknown value</code> by default, on error or for procedures.
        */
       public static BaseDataType invoke(Class cls, WrappedResource resource, InvokeConfig cfg)
       {
          WorkArea wa = work.obtain();

          boolean cachedExternalProgram = false;

          if (cfg.getCallSite() != null    &&
              !cfg.isAsynchronous()        &&
              !cfg.isAsThread()            &&
              !cfg.isSingleRun()           &&
              !cfg.isSingleton()           &&
              !cfg.isTransactionDistinct() &&
              !cfg.isClass()               &&
              !(cfg.getInHandle() != null && cfg.getInHandle().get() instanceof DeferredProgram))
          {
             // SINGLE-RUN and SINGLETON are not being cached.  this is because the returned handle is always
             // a deferred program.  the same for "IN handle" targeting a DeferredProgram

             // AS-THREAD is not being cached, as the resolution is done on a separate thread.

             // TRANSACTION DISTINCT is always for remote calls - if ON SERVER SESSION is specified, it will fail

             // ASYNC or ON SERVER are not being cached

             // TODO: OO calls are not supported in conversion yet

             Map<InvokeConfig, LastInvokeDetails> thisCache = null;
             Object thisCaller = (resource == null ? wa.pm._thisProcedure() : resource);

             if (cfg.isPersistent())
             {
                // look only in the external program cache
                thisCache = wa.extProgcallSiteCache;
             }
             else
             {
                // first check the this-procedure cache, and after that check again the external program cache.
                thisCache = wa.callSiteCache.get(thisCaller);
             }

             LastInvokeDetails lastInvoke = thisCache == null ? null : thisCache.get(cfg.getCallSite());

             if (lastInvoke == null && !cfg.isPersistent())
             {
                // check external programs again
                thisCache = wa.extProgcallSiteCache;
                lastInvoke = thisCache.get(cfg.getCallSite());
             }

             cachedExternalProgram = lastInvoke != null &&
                                     lastInvoke.isExternalProgram() &&
                                     lastInvoke.matchesRun(cfg);
             if (cachedExternalProgram)
             {
                // for an external program, the method can't be used - we cache only the target class name and
                // for to run this as an external program (persistent or not)
                wa.externalResolver.className = (String) lastInvoke.reference.get();
             }
             else if (lastInvoke != null && (lastInvoke.matchesRun(cfg) || lastInvoke.matchesFunction(cfg)))
             {
                // use the cache for RUN statement

                Object reference = lastInvoke.reference.get(); // reference can never be null for RUN, only for OO

                // only for internal entries for now
                Object[] args = cfg.getArguments();
                InternalEntryCaller caller = new InternalEntryCaller(wa);
                caller.callerInstance = reference;
                caller.wa = wa;
                caller.mthd = lastInvoke.method;
                caller.methodName = lastInvoke.method.getName();
                caller.ie = null;
                caller.iename = cfg.getTarget();

                ArgValidationErrors argValid = ArgValidationErrors.NO_ERROR;
                if (args != null && args.length > 0)
                {
                   // pass a copy of the 'args' array, otherwise the non-cached call will use the modified args
                   Object[] copy = Arrays.copyOf(args, args.length);
                   argValid = InternalEntryCaller.valid(caller, cfg.isFunction(), cfg.getModes(), new int[2], copy);
                   if (argValid == ArgValidationErrors.NO_ERROR)
                   {
                      args = copy;
                   }
                }

                if (argValid == ArgValidationErrors.NO_ERROR)
                {
                   Exception deferred = null;
                   Class<?> def = reference.getClass();
                   boolean superCall = cfg.isSuperCall();
                   Object target = cfg.getInHandle() == null ? wa.pm._thisProcedure() : cfg.getInHandle().get();
                   String iename = cfg.getTarget();
                   String pname = null;
                   String legacyName = lastInvoke.legacyName;
                   try
                   {
                      wa.pm.pushCalleeInfo(def,
                                           superCall,
                                           target,
                                           reference,
                                           iename,
                                           pname,
                                           cfg.isFunction(),
                                           cfg.isPersistent(),
                                           cfg.isSingleton(),
                                           cfg.isSingleRun());

                      // set the legacy name as it was resolved.
                      wa.pm.peekCalleeInfo().setLegacyName(legacyName);

                      // this is NOT a pure Java call, avoid the BlockManager.checkJavaCall overhead
                      wa.bm.markPendingDynCall();

                      try
                      {
                         BaseDataType res = (BaseDataType) lastInvoke.method.invoke(reference, args);
                         res = checkInvokeResult(res, iename, cfg.isFunction(), cfg.isDynamicFunction());

                         if (cls != null && cfg.isFunction())
                         {
                            res = convertValue(cls, res);
                         }

                         return res;
                      }
                      finally
                      {
                         wa.pm.popCalleeInfo();
                      }
                   }

                   // allow Progress compatible exceptions to flow through
                   catch (ConditionException | RetryUnwindException ce)
                   {
                      throw ce;
                   }

                   catch (InvocationTargetException exc)
                   {
                      deferred = checkInvocationException(exc);
                   }

                   // all other cases
                   catch (Exception exp)
                   {
                      deferred = exp;
                   }
                   if (deferred != null)
                   {
                      invokeError(caller, iename, deferred);
                   }
                }
             }
             else if (lastInvoke != null)
             {
                thisCache.remove(cfg.getCallSite());
             }

             // otherwise, if cached value can't be used, let it execute and potentially cache
             wa.currentInvoke = cfg;
             wa.currentCaller = thisCaller;
          }

          try
          {
             if (cfg.isAsynchronous() && cfg.getAsyncHandle() == null)
             {
                cfg.setAsyncHandle(new handle());
             }
             if (cfg.isPersistent() && cfg.getProcedureHandle() == null)
             {
                boolean validPortHandle = cfg.getPortHandle() != null &&
                                          cfg.getServerHandle() != null &&
                                          cfg.getServerHandle()._isValid();
                if (validPortHandle && cfg.getServerHandle().get() instanceof ServerImpl)
                {
                   // port-handle is 'morhped' into the procedure handle only if something is ran persistent and
                   // remote
                   cfg.setProcedureHandle(cfg.getPortHandle());
                   cfg.setPortHandle(null);
                }
                else
                {
                   // otherwise, ensure no port-handle is set if we are running something on SESSION
                   if (validPortHandle && cfg.getServerHandle().get() instanceof CommonSession)
                   {
                      cfg.setPortHandle(null);
                   }

                   // ensure we have a temp handle here
                   cfg.setProcedureHandle(new handle());
                }
             }

             // TODO: flatten the stack depth - compute here the final ControlFlow API call for each case

             if (cfg.isSuperCall())
             {
                String target = wa.pm.peekCalleeInfo().getInternalEntryName();

                if (cls == null)
                {
                   return ControlFlowOps.runSuper(target, cfg.getArguments());
                }
                else
                {
                   return ControlFlowOps.runSuper(cls, target, cfg.getArguments());
                }
             }
             else if (cfg.isFunction())
             {
                if (cls == null)
                {
                   if (cfg.isDynamicFunction())
                   {
                      if (cfg.getInHandle() == null)
                      {
                         return ControlFlowOps.invokeDynamicFunctionWithMode(cfg.getTarget(),
                                                                             cfg.getModes(),
                                                                             cfg.getArguments());
                      }
                      else
                      {
                         return ControlFlowOps.invokeDynamicFunctionInWithMode(cfg.getTarget(),
                                                                               cfg.getInHandle(),
                                                                               cfg.getModes(),
                                                                               cfg.getArguments());
                      }
                   }
                   else
                   {
                      if (cfg.getInHandle() == null)
                      {
                         return ControlFlowOps.invokeFunctionWithMode(cfg.getTarget(),
                                                                      cfg.getModes(),
                                                                      cfg.getArguments());
                      }
                      else
                      {
                         return ControlFlowOps.invokeFunctionInWithMode(cfg.getTarget(),
                                                                        cfg.getInHandle(),
                                                                        cfg.getModes(),
                                                                        cfg.getArguments());
                      }
                   }
                }
                else
                {
                   if (cfg.isDynamicFunction())
                   {
                      if (cfg.getInHandle() == null)
                      {
                         return ControlFlowOps.invokeDynamicFunctionWithMode(cls,
                                                                             cfg.getTarget(),
                                                                             cfg.getModes(),
                                                                             cfg.getArguments());
                      }
                      else
                      {
                         return ControlFlowOps.invokeDynamicFunctionInWithMode(cls,
                                                                               cfg.getTarget(),
                                                                               cfg.getInHandle(),
                                                                               cfg.getModes(),
                                                                               cfg.getArguments());
                      }
                   }
                   else
                   {
                      if (cfg.getInHandle() == null)
                      {
                         return ControlFlowOps.invokeFunctionWithMode(cls,
                                                                      cfg.getTarget(),
                                                                      cfg.getModes(),
                                                                      cfg.getArguments());
                      }
                      else
                      {
                         return ControlFlowOps.invokeFunctionInWithMode(cls,
                                                                        cfg.getTarget(),
                                                                        cfg.getInHandle(),
                                                                        cfg.getModes(),
                                                                        cfg.getArguments());
                      }
                   }
                }
             }
             else if (cfg.isAsThread())
             {
                if (cfg.getProcedureHandle() != null)
                {
                   ControlFlowOps.invokeAsThreadWithMode(cfg.getTarget(),
                                                         cfg.getModes(),
                                                         cfg.getArguments());
                }
                else
                {
                   ControlFlowOps.invokeAsThreadSetWithMode(cfg.getTarget(),
                                                            cfg.getProcedureHandle(),
                                                            cfg.getModes(),
                                                            cfg.getArguments());
                }

                return unknown.UNKNOWN;
             }
             else
             {
                if (cfg.getServerHandle() != null)
                {
                   if (cfg.isAsynchronous())
                   {
                      if (cfg.isPersistent())
                      {
                         ControlFlowOps.invokeRemotePersistentSetAsyncWithMode(cfg.getTarget(),
                                                                               cfg.getProcedureHandle(),
                                                                               cfg.getPortHandle(),
                                                                               cfg.getServerHandle(),
                                                                               cfg.isTransactionDistinct(),
                                                                               cfg.getAsyncHandle(),
                                                                               cfg.getEventProcedure(),
                                                                               cfg.getEventProcedureContext(),
                                                                               cfg.getModes(),
                                                                               cfg.getArguments());
                      }
                      else
                      {
                         ControlFlowOps.invokeRemoteAsyncWithMode(cfg.getTarget(),
                                                                  cfg.getPortHandle(),
                                                                  cfg.getServerHandle(),
                                                                  cfg.isTransactionDistinct(),
                                                                  cfg.getAsyncHandle(),
                                                                  cfg.getEventProcedure(),
                                                                  cfg.getEventProcedureContext(),
                                                                  cfg.getModes(),
                                                                  cfg.getArguments());
                      }
                   }
                   else
                   {
                      if (cfg.isPersistent())
                      {
                         ControlFlowOps.invokeRemotePersistentSetWithMode(cfg.getTarget(),
                                                                          cfg.getProcedureHandle(),
                                                                          cfg.getPortHandle(),
                                                                          cfg.getServerHandle(),
                                                                          cfg.isTransactionDistinct(),
                                                                          cfg.getModes(),
                                                                          cfg.getArguments());
                      }
                      else if (cfg.isSingleRun())
                      {
                         ControlFlowOps.invokeRemoteSingleRunSet(cfg.getTarget(),
                                                                 cfg.getProcedureHandle(),
                                                                 cfg.getServerHandle(),
                                                                 cfg.isTransactionDistinct());
                      }
                      else if (cfg.isSingleton())
                      {
                         ControlFlowOps.invokeRemoteSingletonSet(cfg.getTarget(),
                                                                 cfg.getProcedureHandle(),
                                                                 cfg.getServerHandle(),
                                                                 cfg.isTransactionDistinct());
                      }
                      else
                      {
                         ControlFlowOps.invokeRemoteWithMode(cfg.getTarget(),
                                                             cfg.getPortHandle(),
                                                             cfg.getServerHandle(),
                                                             cfg.isTransactionDistinct(),
                                                             cfg.getModes(),
                                                             cfg.getArguments());
                      }
                   }
                }
                else if (cfg.isPersistent())
                {
                   // this will automatically use any resolved and cached class name
                   ControlFlowOps.invokePersistentSetWithMode(cfg.getTarget(),
                                                              cfg.getProcedureHandle(),
                                                              cfg.getModes(),
                                                              cfg.getArguments());
                }
                else if (cfg.isSingleRun())
                {
                   // not cached
                   ControlFlowOps.invokeSingleRunSet(cfg.getTarget(), cfg.getProcedureHandle());
                }
                else if (cfg.isSingleton())
                {
                   // not cached
                   ControlFlowOps.invokeSingletonSet(cfg.getTarget(), cfg.getProcedureHandle());
                }
                else if (cfg.getInHandle() == null)
                {
                   if (cachedExternalProgram || TransactionManager.isGlobalBlock())
                   {
                      // force an external program invocation
                      ControlFlowOps.invokeExternalProcedure(new character(cfg.getTarget()),
                                                             false, null, false, null,
                                                             cfg.getModes(), cfg.getArguments());
                   }
                   else
                   {
                      ControlFlowOps.invokeWithMode(cfg.getTarget(), cfg.getModes(), cfg.getArguments());
                   }
                }
                else
                {
                   if (cfg.isAsynchronous())
                   {
                      // not cached
                      ControlFlowOps.invokeInAsyncWithMode(cfg.getTarget(),
                                                           cfg.getInHandle(),
                                                           cfg.getAsyncHandle(),
                                                           cfg.getEventProcedure(),
                                                           cfg.getEventProcedureContext(),
                                                           cfg.getModes(),
                                                           cfg.getArguments());
                   }
                   else
                   {
                      // internal procedure
                      ControlFlowOps.invokeInWithMode(cfg.getTarget(),
                                                      cfg.getInHandle(),
                                                      cfg.getModes(),
                                                      cfg.getArguments());
                   }
                }

                return unknown.UNKNOWN;
             }
          }
          finally
          {
             // the call may have failed (argument validation, etc), reset these
             wa.currentCaller = null;
             wa.currentInvoke = null;
          }
       }

       /**
        * Invoke the procedure with the given name (emitted for a standard RUN
        * statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeWithMode(String    name,
                                         String    modes,
                                         Object... args)
       {
          invokeWithMode(new character(name), modes, args);
       }

       /**
        * Invoke the procedure with the given name (emitted for a standard RUN
        * statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeWithMode(character name,
                                         String    modes,
                                         Object... args)
       {
          invokeImpl((handle) null, null, name, false, false, false, false, modes, args);
       }

       /**
        * Invoke the procedure with the given name (emitted for a standard RUN
        * statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invoke(String name, Object... args)
       {
          invoke(new character(name), args);
       }

       /**
        * Invoke the procedure with the given name (emitted for a standard RUN
        * statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invoke(character name, Object... args)
       {
          invokeImpl((handle) null, null, name, false, false, false, false, null, args);
       }

       /**
        * Invoke the procedure with the given name (emitted for a standard RUN statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemoteWithMode(String    name,
                                               handle    portHandle,
                                               handle    remoteHandle,
                                               boolean   transactionDistinct,
                                               String    modes,
                                               Object... args)
       {
          invokeRemoteWithMode(new character(name), portHandle,
                               remoteHandle, transactionDistinct,
                               modes, args);
       }

       /**
        * Invoke the procedure with the given name (emitted for a standard RUN statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemoteWithMode(character name,
                                               handle    portHandle,
                                               handle    remoteHandle,
                                               boolean   transactionDistinct,
                                               String    modes,
                                               Object... args)
       {
          invokeRemoteWithModeImpl(null, name,
                                   portHandle, remoteHandle,
                                   transactionDistinct,
                                   modes, args);
       }

       /**
        * Invoke the procedure with the given name (emitted for a standard RUN statement).
        *
        * @param    asyncReq
        *           When not-null, an {@link AsyncRequestImpl} instance for the associated async
        *           request.
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       private static void invokeRemoteWithModeImpl(AsyncRequestImpl asyncReq,
                                                    character        name,
                                                    handle           portHandle,
                                                    handle           remoteHandle,
                                                    boolean          transactionDistinct,
                                                    String           modes,
                                                    Object...        args)
       {
          if (portHandle != null)
          {
             // this is a RUN ... SET ... ON SERVER webService
             invokePersistentImpl(asyncReq, remoteHandle, name, null, portHandle,
                                  transactionDistinct, modes, args);
          }
          else
          {
             // this is a RUN ... ON SERVER appServer
             invokeImpl(asyncReq, remoteHandle, null, name,
                        false, false, false,
                        transactionDistinct, modes, args);
          }
       }

       /**
        * Invoke the procedure with the given name on a remote server (emitted for a standard RUN ...
        * ON ... statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemote(String    name,
                                       handle    portHandle,
                                       handle    remoteHandle,
                                       boolean   transactionDistinct,
                                       Object... args)
       {
          invokeRemote(new character(name), portHandle, remoteHandle, transactionDistinct, args);
       }

       /**
        * Invoke the procedure with the given name on a remote server  (emitted for a standard RUN ...
        * ON ... statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemote(character name,
                                       handle    portHandle,
                                       handle    remoteHandle,
                                       boolean   transactionDistinct,
                                       Object... args)
       {
          invokeRemoteWithMode(new character(name), portHandle,
                               remoteHandle, transactionDistinct,
                               null, args);
       }

       /**
        * Invoke the procedure with the given name on a remote server (emitted for a standard RUN ...
        * ON ... ASYNC SET ... statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemoteAsyncWithMode(String    name,
                                                    handle    portHandle,
                                                    handle    remoteHandle,
                                                    boolean   transactionDistinct,
                                                    handle    asyncHandle,
                                                    String    eventProcName,
                                                    handle    inEventprocHandle,
                                                    String    modes,
                                                    Object... args)
       {
          invokeRemoteAsyncWithMode(new character(name), portHandle,
                                    remoteHandle, transactionDistinct,
                                    asyncHandle, new character(eventProcName), inEventprocHandle,
                                    modes, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ... ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemoteAsyncWithMode(character name,
                                                    handle    portHandle,
                                                    handle    remoteHandle,
                                                    boolean   transactionDistinct,
                                                    handle    asyncHandle,
                                                    String    eventProcName,
                                                    handle    inEventprocHandle,
                                                    String    modes,
                                                    Object... args)
       {
          invokeRemoteAsyncWithMode(name, portHandle, remoteHandle, transactionDistinct,
                                    asyncHandle, new character(eventProcName), inEventprocHandle,
                                    modes, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ... ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemoteAsyncWithMode(String    name,
                                                    handle    portHandle,
                                                    handle    remoteHandle,
                                                    boolean   transactionDistinct,
                                                    handle    asyncHandle,
                                                    character eventProcName,
                                                    handle    inEventprocHandle,
                                                    String    modes,
                                                    Object... args)
       {
          invokeRemoteAsyncWithMode(new character(name), portHandle,
                                    remoteHandle, transactionDistinct,
                                    asyncHandle, eventProcName, inEventprocHandle,
                                    modes, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ... ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemoteAsyncWithMode(character name,
                                                    handle    portHandle,
                                                    handle    remoteHandle,
                                                    boolean   transactionDistinct,
                                                    handle    asyncHandle,
                                                    character eventProcName,
                                                    handle    inEventprocHandle,
                                                    String    modes,
                                                    Object... args)
       {
          AsyncCall remoteRequest = new AsyncCall(remoteHandle, portHandle, name, new handle(),
                                                  asyncHandle, eventProcName, inEventprocHandle,
                                                  transactionDistinct,
                                                  modes, args)
          {
             @Override
             public void run()
             {
                // use a copy here, to not allow the user to change the reference
                invokeRemoteWithModeImpl(asyncReq, pName,
                                         portHandle, hServer,
                                         transactionDistinct,
                                         asyncReq.getModes(), asyncReq.getArgs());
             }
          };

          invokeAsyncImpl(remoteRequest, remoteHandle, asyncHandle, new handle());
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ... ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemoteAsync(String    name,
                                            handle    portHandle,
                                            handle    remoteHandle,
                                            boolean   transactionDistinct,
                                            handle    asyncHandle,
                                            String    eventProcName,
                                            handle    inEventprocHandle,
                                            Object... args)
       {
          invokeRemoteAsync(new character(name), portHandle, remoteHandle , transactionDistinct,
                            asyncHandle, new character(eventProcName), inEventprocHandle,
                            args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ... ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemoteAsync(character name,
                                            handle    portHandle,
                                            handle    remoteHandle,
                                            boolean   transactionDistinct,
                                            handle    asyncHandle,
                                            String    eventProcName,
                                            handle    inEventprocHandle,
                                            Object... args)
       {
          invokeRemoteAsync(name, portHandle, remoteHandle, transactionDistinct,
                            asyncHandle, new character(eventProcName), inEventprocHandle,
                            args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ... ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemoteAsync(String    name,
                                            handle    portHandle,
                                            handle    remoteHandle,
                                            boolean   transactionDistinct,
                                            handle    asyncHandle,
                                            character eventProcName,
                                            handle    inEventprocHandle,
                                            Object... args)
       {
          invokeRemoteAsync(new character(name), portHandle, remoteHandle, transactionDistinct,
                            asyncHandle, eventProcName, inEventprocHandle,
                            args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ... ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemoteAsync(character name,
                                            handle    portHandle,
                                            handle    remoteHandle,
                                            boolean   transactionDistinct,
                                            handle    asyncHandle,
                                            character eventProcName,
                                            handle    inEventprocHandle,
                                            Object... args)
       {
          invokeRemoteAsyncWithMode(name, portHandle, remoteHandle, transactionDistinct,
                                    asyncHandle, eventProcName, inEventprocHandle,
                                    null, args);
       }

       /**
        * Invoke the procedure with the given name, defined in the given handle
        * (emitted for a standard RUN ... IN handle statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which this procedure belongs. When
        *           <code>null</code>, defaults to THIS-PROCEDURE.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeInWithMode(String    name,
                                           handle    h,
                                           String    modes,
                                           Object... args)
       {
          invokeInImpl(new character(name), h, false, modes, args);
       }

       /**
        * Invoke the procedure with the given name, defined in the given handle
        * (emitted for a standard RUN ... IN handle statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which this procedure belongs. When
        *           <code>null</code>, defaults to THIS-PROCEDURE.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeInWithMode(character name,
                                           handle    h,
                                           String    modes,
                                           Object... args)
       {
          invokeInImpl(name, h, false, modes, args);
       }

       /**
        * Invoke the procedure with the given name, defined in the given handle
        * (emitted for a standard RUN ... IN handle statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which this procedure belongs. When
        *           <code>null</code>, defaults to THIS-PROCEDURE.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeIn(String name, handle h, Object... args)
       {
          invokeInImpl(new character(name), h, false, null, args);
       }

       /**
        * Invoke the procedure with the given name, defined in the given handle
        * (emitted for a standard RUN ... IN handle statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which this procedure belongs. When
        *           <code>null</code>, defaults to THIS-PROCEDURE.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeIn(character name, handle h, Object... args)
       {
          invokeInImpl(name, h, false, null, args);
       }

       /**
        * Invoke the procedure with the given name, defined in the given handle
        * (emitted for a standard RUN ... IN handle statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which this procedure belongs. When
        *           <code>null</code>, defaults to THIS-PROCEDURE.
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeInAsyncWithMode(String    name,
                                                handle    h,
                                                handle    asyncHandle,
                                                String    eventProcName,
                                                handle    inEventprocHandle,
                                                String    modes,
                                                Object... args)
       {
          invokeInAsyncWithMode(new character(name), h,
                                asyncHandle, new  character(eventProcName), inEventprocHandle,
                                modes, args);
       }

       /**
        * Invoke the procedure with the given name, defined in the given handle
        * (emitted for a standard RUN ... IN ASYNC.. handle statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which this procedure belongs. When
        *           <code>null</code>, defaults to THIS-PROCEDURE.
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeInAsyncWithMode(character name,
                                                handle    h,
                                                handle    asyncHandle,
                                                String    eventProcName,
                                                handle    inEventprocHandle,
                                                String    modes,
                                                Object... args)
       {
          invokeInAsyncWithMode(name, h,
                                asyncHandle, new character(eventProcName), inEventprocHandle,
                                modes, args);
       }

       /**
        * Invoke the procedure with the given name, defined in the given handle
        * (emitted for a standard RUN ... IN ASYNC.. handle statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which this procedure belongs. When
        *           <code>null</code>, defaults to THIS-PROCEDURE.
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeInAsyncWithMode(String    name,
                                                handle    h,
                                                handle    asyncHandle,
                                                character eventProcName,
                                                handle    inEventprocHandle,
                                                String    modes,
                                                Object... args)
       {
          invokeInAsyncWithMode(new character(name), h, asyncHandle, eventProcName, inEventprocHandle,
                                modes, args);
       }

       /**
        * Invoke the procedure with the given name, defined in the given handle
        * (emitted for a standard RUN ... IN handle statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which this procedure belongs. When
        *           <code>null</code>, defaults to THIS-PROCEDURE.
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeInAsyncWithMode(character name,
                                                handle    h,
                                                handle    asyncHandle,
                                                character eventProcName,
                                                handle    inEventprocHandle,
                                                String    modes,
                                                Object... args)
       {
          ServerImpl server = null;
          if (h != null && h._isValid())
          {
             WrappedResource res = h.getResource();
             if (res instanceof PersistentProcedure)
             {
                handle hserver = ((PersistentProcedure) res).getServerHandle();

                if (!hserver.isUnknown())
                {
                   server = (ServerImpl) hserver.getResource();
                }
             }
          }

          // if no server, default to SESSION
          handle hServer = (server != null ? new handle(server) : SessionUtils.asHandle());

          AsyncCall remoteRequest = new AsyncCall(hServer, null, name, h,
                                                  asyncHandle, eventProcName, inEventprocHandle,
                                                  false,
                                                  modes, args)
          {
             @Override
             public void run()
             {
                // use a copy here, to not allow the user to change the reference
                invokeImpl(asyncReq, (handle) null, pHandle, pName,
                           false, false, false, false,
                           asyncReq.getModes(), asyncReq.getArgs());
             }
          };

          invokeAsyncImpl(remoteRequest, hServer, asyncHandle, h);
       }

       /**
        * Invoke the procedure with the given name, defined in the given handle
        * (emitted for a standard RUN ... IN handle statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which this procedure belongs. When
        *           <code>null</code>, defaults to THIS-PROCEDURE.
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeInAsync(String    name,
                                        handle    h,
                                        handle    asyncHandle,
                                        String    eventProcName,
                                        handle    inEventprocHandle,
                                        Object... args)
       {
          invokeInAsync(new character(name), h,
                        asyncHandle, new  character(eventProcName), inEventprocHandle, args);
       }

       /**
        * Invoke the procedure with the given name, defined in the given handle
        * (emitted for a standard RUN ... IN handle ASYNC..  statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which this procedure belongs. When
        *           <code>null</code>, defaults to THIS-PROCEDURE.
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeInAsync(character name,
                                        handle    h,
                                        handle    asyncHandle,
                                        String    eventProcName,
                                        handle    inEventprocHandle,
                                        Object... args)
       {
          invokeInAsync(name, h, asyncHandle, new character(eventProcName), inEventprocHandle, args);
       }

       /**
        * Invoke the procedure with the given name, defined in the given handle
        * (emitted for a standard RUN ... IN handle ASYNC.. statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which this procedure belongs. When
        *           <code>null</code>, defaults to THIS-PROCEDURE.
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeInAsync(String    name,
                                        handle    h,
                                        handle    asyncHandle,
                                        character eventProcName,
                                        handle    inEventprocHandle,
                                        Object... args)
       {
          invokeInAsync(new character(name), h, asyncHandle, eventProcName, inEventprocHandle, args);
       }

       /**
        * Invoke the procedure with the given name, defined in the given handle
        * (emitted for a standard RUN ... handle IN statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which this procedure belongs. When
        *           <code>null</code>, defaults to THIS-PROCEDURE.
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeInAsync(character name,
                                        handle    h,
                                        handle    asyncHandle,
                                        character eventProcName,
                                        handle    inEventprocHandle,
                                        Object... args)
       {
          invokeInAsyncWithMode(name, h,
                                asyncHandle, eventProcName, inEventprocHandle,
                                null, args);
       }

       /**
        * Invoke the procedure with the given name, and set it as PERSISTENT
        * (emitted for a RUN ... PERSISTENT statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokePersistentWithMode(String    name,
                                                   String    modes,
                                                   Object... args)
       {
          invokePersistentWithMode(new character(name), modes, args);
       }

       /**
        * Invoke the procedure with the given name, and set it as PERSISTENT
        * (emitted for a RUN ... PERSISTENT statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokePersistentWithMode(character name,
                                                   String    modes,
                                                   Object... args)
       {
          invokePersistentImpl(null, name, null, null, false, modes, args);
       }

       /**
        * Invoke the procedure with the given name, and set it as PERSISTENT
        * (emitted for a RUN ... PERSISTENT statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokePersistent(String name, Object... args)
       {
          invokePersistent(new character(name), args);
       }

       /**
        * Invoke the procedure with the given name, and set it as PERSISTENT
        * (emitted for a RUN ... PERSISTENT ON ... statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokePersistent(character name, Object... args)
       {
          invokePersistentImpl(null, name, null, null, false, null, args);
       }

       /**
        * Invoke the procedure with the given name, and save its procedure handle
        * in the given handle (emitted for a RUN ... PERSISTENT set handle
        * statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which the procedure reference must be saved.
        *           When <code>null</code>, just adds the procedure to the
        *           persistent procedure list.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokePersistentSetWithMode(String    name,
                                                      handle    h,
                                                      String    modes,
                                                      Object... args)
       {
          invokePersistentSetWithMode(new character(name), h, modes, args);
       }

       /**
        * Invoke the procedure with the given name, and save its procedure handle
        * in the given handle (emitted for a RUN ... PERSISTENT set handle
        * statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which the procedure reference must be saved.
        *           When <code>null</code>, just adds the procedure to the
        *           persistent procedure list.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokePersistentSetWithMode(character name,
                                                      handle    h,
                                                      String    modes,
                                                      Object... args)
       {
          invokePersistentImpl(null, name, h, null, false, modes, args);
       }

       /**
        * Invoke the procedure with the given name, and save its procedure handle
        * in the given handle (emitted for a RUN ... PERSISTENT set handle
        * statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which the procedure reference must be saved.
        *           When <code>null</code>, just adds the procedure to the
        *           persistent procedure list.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokePersistentSet(String    name,
                                              handle    h,
                                              Object... args)
       {
          invokePersistentSet(new character(name), h, args);
       }

       /**
        * Invoke the procedure with the given name, and save its procedure handle
        * in the given handle (emitted for a RUN ... PERSISTENT set handle
        * statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which the procedure reference must be saved.
        *           When <code>null</code>, just adds the procedure to the
        *           persistent procedure list.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokePersistentSet(character name,
                                              handle    h,
                                              Object... args)
       {
          invokePersistentImpl(null, name, h, null, false, null, args);
       }

       /**
        * Invoke the procedure with the given name (emitted for a standard RUN
        * statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle in which it will put the persistent external procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentSetWithMode(String    name,
                                                            handle    persistHandle,
                                                            handle    portHandle,
                                                            handle    remoteHandle,
                                                            boolean   transactionDistinct,
                                                            String    modes,
                                                            Object... args)
       {
          invokeRemotePersistentSetWithMode(new character(name), persistHandle, portHandle,
                                            remoteHandle, transactionDistinct,
                                            modes, args);
       }

       /**
        * Invoke the procedure with the given name (emitted for a standard RUN
        * statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle in which it will put the persistent external procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentSetWithMode(character name,
                                                            handle    persistHandle,
                                                            handle    portHandle,
                                                            handle    remoteHandle,
                                                            boolean   transactionDistinct,
                                                            String    modes,
                                                            Object... args)
       {
          invokePersistentImpl(remoteHandle, name,
                               persistHandle, portHandle,
                               transactionDistinct, modes, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name, and set it as PERSISTENT
        * (emitted for a RUN ... PERSISTENT SET ON ... statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle in which it will put the persistent external procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentSet(String    name,
                                                    handle    persistHandle,
                                                    handle    portHandle,
                                                    handle    remoteHandle,
                                                    boolean   transactionDistinct,
                                                    Object... args)
       {
          invokeRemotePersistentSet(new character(name), persistHandle, portHandle,
                                    remoteHandle, transactionDistinct,
                                    args);
       }

       /**
        * Invoke the procedure on a remote server with the given name, and set it as PERSISTENT
        * (emitted for a RUN ... PERSISTENT SET ON ... statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle in which it will put the persistent external procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentSet(character name,
                                                    handle    persistHandle,
                                                    handle    portHandle,
                                                    handle    remoteHandle,
                                                    boolean   transactionDistinct,
                                                    Object... args)
       {
          invokeRemotePersistentSetWithMode(name, persistHandle, portHandle,
                                            remoteHandle, transactionDistinct,
                                            null, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name, and set it as PERSISTENT
        * (emitted for a RUN ... PERSISTENT  ON ... statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentWithMode(String    name,
                                                         handle    portHandle,
                                                         handle    remoteHandle,
                                                         boolean   transactionDistinct,
                                                         String    modes,
                                                         Object... args)
       {
          invokeRemotePersistentWithMode(new character(name), portHandle,
                                         remoteHandle, transactionDistinct,
                                         modes, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name, and set it as PERSISTENT
        * (emitted for a RUN ... PERSISTENT  ON ... statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentWithMode(character name,
                                                         handle    portHandle,
                                                         handle    remoteHandle,
                                                         boolean   transactionDistinct,
                                                         String    modes,
                                                         Object... args)
       {
          invokeRemotePersistentSetWithMode(name, null, portHandle,
                                            remoteHandle, transactionDistinct,
                                            modes, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name, and set it as PERSISTENT
        * (emitted for a RUN ... PERSISTENT ON ... statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistent(String    name,
                                                 handle    portHandle,
                                                 handle    remoteHandle,
                                                 boolean   transactionDistinct,
                                                 Object... args)
       {
          invokeRemotePersistent(new character(name), portHandle,
                                 remoteHandle, transactionDistinct,
                                 args);
       }

       /**
        * Invoke the procedure on a remote server with the given name, and set it as PERSISTENT
        * (emitted for a RUN ... PERSISTENT ON ... statement).
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistent(character name,
                                                 handle    portHandle,
                                                 handle    remoteHandle,
                                                 boolean   transactionDistinct,
                                                 Object... args)
       {
          invokeRemotePersistentWithMode(name, portHandle,
                                         remoteHandle, transactionDistinct,
                                         null, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ... ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentAsyncWithMode(String    name,
                                                              handle    portHandle,
                                                              handle    remoteHandle,
                                                              boolean   transactionDistinct,
                                                              handle    asyncHandle,
                                                              String    eventProcName,
                                                              handle    inEventprocHandle,
                                                              String    modes,
                                                              Object... args)
       {
          invokeRemotePersistentAsyncWithMode(new character(name), portHandle,
                                              remoteHandle, transactionDistinct, asyncHandle,
                                              new character(eventProcName), inEventprocHandle,
                                              modes, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ...PERSISTENT ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentAsyncWithMode(character name,
                                                              handle    portHandle,
                                                              handle    remoteHandle,
                                                              boolean   transactionDistinct,
                                                              handle    asyncHandle,
                                                              String    eventProcName,
                                                              handle    inEventprocHandle,
                                                              String    modes,
                                                              Object... args)
       {
          invokeRemotePersistentAsyncWithMode(name, portHandle,
                                              remoteHandle, transactionDistinct,
                                              asyncHandle, new character(eventProcName),
                                              inEventprocHandle,
                                              modes, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ...PERSISTENT ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentAsyncWithMode(String    name,
                                                              handle    portHandle,
                                                              handle    remoteHandle,
                                                              boolean   transactionDistinct,
                                                              handle    asyncHandle,
                                                              character eventProcName,
                                                              handle    inEventprocHandle,
                                                              String    modes,
                                                              Object... args)
       {
          invokeRemotePersistentAsyncWithMode(new character(name), portHandle,
                                              remoteHandle, transactionDistinct,
                                              asyncHandle, eventProcName, inEventprocHandle,
                                              modes, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ...PERSISTENT ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentAsyncWithMode(character name,
                                                              handle    portHandle,
                                                              handle    remoteHandle,
                                                              boolean   transactionDistinct,
                                                              handle    asyncHandle,
                                                              character eventProcName,
                                                              handle    inEventprocHandle,
                                                              String    modes,
                                                              Object... args)
       {
          invokeRemotePersistentSetAsyncWithMode(name, null, portHandle,
                                                 remoteHandle, transactionDistinct,
                                                 asyncHandle, eventProcName, inEventprocHandle,
                                                 modes, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ...PERSISTENT ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentAsync(String    name,
                                                      handle    portHandle,
                                                      handle    remoteHandle,
                                                      boolean   transactionDistinct,
                                                      handle    asyncHandle,
                                                      String    eventProcName,
                                                      handle    inEventprocHandle,
                                                      Object... args)
       {
          invokeRemotePersistentAsync(new character(name), portHandle,
                                      remoteHandle, transactionDistinct,
                                      asyncHandle, new character(eventProcName), inEventprocHandle,
                                      args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ...PERSISTENT ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentAsync(character name,
                                                      handle    portHandle,
                                                      handle    remoteHandle,
                                                      boolean   transactionDistinct,
                                                      handle    asyncHandle,
                                                      String    eventProcName,
                                                      handle    inEventprocHandle,
                                                      Object... args)
       {
          invokeRemotePersistentAsync(name, portHandle,
                                      remoteHandle, transactionDistinct,
                                      asyncHandle, new character(eventProcName), inEventprocHandle,
                                      args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ... ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentAsync(String    name,
                                                      handle    portHandle,
                                                      handle    remoteHandle,
                                                      boolean   transactionDistinct,
                                                      handle    asyncHandle,
                                                      character eventProcName,
                                                      handle    inEventprocHandle,
                                                      Object... args)
       {
          invokeRemotePersistentAsync(new character(name), portHandle,
                                      remoteHandle, transactionDistinct,
                                      asyncHandle, eventProcName, inEventprocHandle, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ...PERSISTENT ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentAsync(character name,
                                                      handle    portHandle,
                                                      handle    remoteHandle,
                                                      boolean   transactionDistinct,
                                                      handle    asyncHandle,
                                                      character eventProcName,
                                                      handle    inEventprocHandle,
                                                      Object... args)
       {
          invokeRemotePersistentSetAsync(name, null, portHandle,
                                         remoteHandle, transactionDistinct,
                                         asyncHandle, eventProcName, inEventprocHandle,
                                         args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ...PERSISTENT SET.. ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle in which it will put the persistent external procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentSetAsyncWithMode(String    name,
                                                                 handle    persistHandle,
                                                                 handle    portHandle,
                                                                 handle    remoteHandle,
                                                                 boolean   transactionDistinct,
                                                                 handle    asyncHandle,
                                                                 String    eventProcName,
                                                                 handle    inEventprocHandle,
                                                                 String    modes,
                                                                 Object... args)
       {
          invokeRemotePersistentSetAsyncWithMode(new character(name), persistHandle, portHandle,
                                                 remoteHandle, transactionDistinct,
                                                 asyncHandle, new character(eventProcName),
                                                 inEventprocHandle,
                                                 modes, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ...PERSISTENT SET.. ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle in which it will put the persistent external procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentSetAsyncWithMode(character name,
                                                                 handle    persistHandle,
                                                                 handle    portHandle,
                                                                 handle    remoteHandle,
                                                                 boolean   transactionDistinct,
                                                                 handle    asyncHandle,
                                                                 String    eventProcName,
                                                                 handle    inEventprocHandle,
                                                                 String    modes,
                                                                 Object... args)
       {
          invokeRemotePersistentSetAsyncWithMode(name, persistHandle, portHandle,
                                                 remoteHandle, transactionDistinct,
                                                 asyncHandle, new character(eventProcName),
                                                 inEventprocHandle,
                                                 modes, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ...PERSISTENT SET.. ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle in which it will put the persistent external procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentSetAsyncWithMode(String    name,
                                                                 handle    persistHandle,
                                                                 handle    portHandle,
                                                                 handle    remoteHandle,
                                                                 boolean   transactionDistinct,
                                                                 handle    asyncHandle,
                                                                 character eventProcName,
                                                                 handle    inEventprocHandle,
                                                                 String    modes,
                                                                 Object... args)
       {
          invokeRemotePersistentSetAsyncWithMode(new character(name), persistHandle, portHandle,
                                                 remoteHandle, transactionDistinct,
                                                 asyncHandle, eventProcName, inEventprocHandle,
                                                 modes, args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ...PERSISTENT SET.. ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle in which it will put the persistent external procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentSetAsyncWithMode(character name,
                                                                 handle    persistHandle,
                                                                 handle    portHandle,
                                                                 handle    remoteHandle,
                                                                 boolean   transactionDistinct,
                                                                 handle    asyncHandle,
                                                                 character eventProcName,
                                                                 handle    inEventprocHandle,
                                                                 String    modes,
                                                                 Object... args)
       {
          // validate server first
          if (!validServer(remoteHandle, null, name))
          {
             return;
          }

          boolean isServer = (remoteHandle.getResource() instanceof ServerImpl);
          ServerImpl server = null;

          if (!isServer)
          {
             if (transactionDistinct)
             {
                invalidHandle(name);
                return;
             }
          }
          else
          {
             server = (ServerImpl) remoteHandle.getResource();
             if (server.isWebService())
             {
                final String err =
                   "Unexpected Web service client error. Copy the following message and contact " +
                   "technical support. Web service procedure not initialized in cwsSend (11461)";
                ErrorManager.recordOrThrowError(11461, err, false, true);
                return;
             }
          }

          // do this only if the server is still connected; if not, it will fallback to non-async
          // processing to produce the appropriate error
          final boolean obtainProxy = isServer &&
                                      server._connected() &&
                                      server.getServerHelper().isSessionFree();

          AsyncCall remoteRequest = new AsyncCall(remoteHandle, portHandle, name, new handle(),
                                                  asyncHandle, eventProcName, inEventprocHandle,
                                                  transactionDistinct,
                                                  modes, args)
          {
             @Override
             public void run()
             {
                if (obtainProxy)
                {
                   // if truly-async request, must run the "execute" method on the resolved proxy
                   // if the proxy is invalid or can't map attributes or other error, then error
                   // messages are generated now
                   initializeProxy(this);
                }
                else
                {
                   // if not a server, go the normal way
                   invokePersistentImpl(asyncReq, hServer, pName, pHandle, portHandle,
                                        transactionDistinct,
                                        asyncReq.getModes(), asyncReq.getArgs());
                }
             }
          };

          if (obtainProxy)
          {
             AppServerHelper helper = (AppServerHelper) server.getServerHelper();
             ProxyProcedureWrapper proxy = helper.obtainProxy(name);
             if (proxy.getChildConnectionId() != null)
             {
                remoteRequest.asyncReq.setConnectionId(proxy.getChildConnectionId());
             }
             persistHandle.assign(proxy);
             remoteRequest.pHandle.assign(proxy);
          }

          invokeAsyncImpl(remoteRequest, remoteHandle, asyncHandle, new handle());
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ...PERSISTENT SET ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle in which it will put the persistent external procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentSetAsync(String    name,
                                                         handle    persistHandle,
                                                         handle    portHandle,
                                                         handle    remoteHandle,
                                                         boolean   transactionDistinct,
                                                         handle    asyncHandle,
                                                         String    eventProcName,
                                                         handle    inEventprocHandle,
                                                         Object... args)
       {
          invokeRemotePersistentSetAsync(new character(name), persistHandle, portHandle,
                                         remoteHandle, transactionDistinct,
                                         asyncHandle, new character(eventProcName), inEventprocHandle,
                                         args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ...PERSISTENT SET ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle in which it will put the persistent external procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentSetAsync(character name,
                                                         handle    persistHandle,
                                                         handle    portHandle,
                                                         handle    remoteHandle,
                                                         boolean   transactionDistinct,
                                                         handle    asyncHandle,
                                                         String    eventProcName,
                                                         handle    inEventprocHandle,
                                                         Object... args)
       {
          invokeRemotePersistentSetAsync(name, persistHandle, portHandle,
                                         remoteHandle, transactionDistinct, asyncHandle,
                                         new character(eventProcName), inEventprocHandle,
                                         args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ... ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle in which it will put the persistent external procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentSetAsync(String    name,
                                                         handle    persistHandle,
                                                         handle    portHandle,
                                                         handle    remoteHandle,
                                                         boolean   transactionDistinct,
                                                         handle    asyncHandle,
                                                         character eventProcName,
                                                         handle    inEventprocHandle,
                                                         Object... args)
       {
          invokeRemotePersistentSetAsync(new character(name), persistHandle, portHandle,
                                         remoteHandle, transactionDistinct,
                                         asyncHandle, eventProcName, inEventprocHandle,
                                         args);
       }

       /**
        * Invoke the procedure on a remote server with the given name
        * (emitted for a standard RUN ...PERSISTENT SET ON ... ASYNC SET ...
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle in which it will put the persistent external procedure.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    remoteHandle
        *           The handle to the remote object used to run the procedure
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    eventProcName
        *           This represents the name of the internal procedure for the EVENT-PROCEDURE clause,
        *           null if this clause is not specified.
        * @param    inEventprocHandle
        *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
        *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeRemotePersistentSetAsync(character name,
                                                         handle    persistHandle,
                                                         handle    portHandle,
                                                         handle    remoteHandle,
                                                         boolean   transactionDistinct,
                                                         handle    asyncHandle,
                                                         character eventProcName,
                                                         handle    inEventprocHandle,
                                                         Object... args)
       {
          invokeRemotePersistentSetAsyncWithMode(name, persistHandle, portHandle,
                                                 remoteHandle, transactionDistinct,
                                                 asyncHandle, eventProcName, inEventprocHandle,
                                                 null, args);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION statement.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T
       invokeDynamicFunctionWithMode(Class<T>  cls,
                                     String    name,
                                     String    modes,
                                     Object... args)
       {
          BaseDataType res = invokeFunctionImpl(new character(name), null, true, false, modes, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION statement.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T
       invokeDynamicFunctionWithMode(Class<T>  cls,
                                     character name,
                                     String    modes,
                                     Object... args)
       {
          BaseDataType res = invokeFunctionImpl(name, null, true, false, modes, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T
       invokeDynamicFunctionInWithMode(Class<T>  cls,
                                       String    name,
                                       handle    h,
                                       String    modes,
                                       Object... args)
       {
          BaseDataType res = invokeFunctionImpl(new character(name), h, true, false, modes, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T
       invokeDynamicFunctionInWithMode(Class<T>  cls,
                                       character name,
                                       handle    h,
                                       String    modes,
                                       Object... args)
       {
          BaseDataType res = invokeFunctionImpl(name, h, true, false, modes, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION statement.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T
       invokeDynamicFunction(Class<T>  cls,
                             String    name,
                             Object... args)
       {
          BaseDataType res = invokeFunctionImpl(new character(name), null, true, false, null, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION statement.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T
       invokeDynamicFunction(Class<T>  cls,
                             character name,
                             Object... args)
       {
          BaseDataType res = invokeFunctionImpl(name, null, true, false, null, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T
       invokeDynamicFunctionIn(Class<T>  cls,
                               String    name,
                               handle    h,
                               Object... args)
       {
          BaseDataType res = invokeFunctionImpl(new character(name), h, true, false, null, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T
       invokeDynamicFunctionIn(Class<T>  cls,
                               character name,
                               handle    h,
                               Object... args)
       {
          BaseDataType res = invokeFunctionImpl(name, h, true, false, null, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION statement.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeDynamicFunctionWithMode(String    name,
                                                                String    modes,
                                                                Object... args)
       {
          return invokeFunctionImpl(new character(name), null, true, false, modes, args);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION statement.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeDynamicFunctionWithMode(character name,
                                                                String    modes,
                                                                Object... args)
       {
          return invokeFunctionImpl(name, null, true, false, modes, args);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeDynamicFunctionInWithMode(String    name,
                                                                  handle    h,
                                                                  String    modes,
                                                                  Object... args)
       {
          return invokeFunctionImpl(new character(name), h, true, false, modes, args);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeDynamicFunctionInWithMode(character name,
                                                                  handle    h,
                                                                  String    modes,
                                                                  Object... args)
       {
          return invokeFunctionImpl(name, h, true, false, modes, args);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION statement.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeDynamicFunction(String    name,
                                                        Object... args)
       {
          return invokeFunctionImpl(new character(name), null, true, false, null, args);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION statement.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeDynamicFunction(character name,
                                                        Object... args)
       {
          return invokeFunctionImpl(name, null, true, false, null, args);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeDynamicFunctionIn(String    name,
                                                          handle    h,
                                                          Object... args)
       {
          return invokeFunctionImpl(new character(name), h, true, false, null, args);
       }

       /**
        * Emitted for the DYNAMIC-FUNCTION(fname IN handle, ... ) statement.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeDynamicFunctionIn(character name,
                                                          handle    h,
                                                          Object... args)
       {
          return invokeFunctionImpl(name, h, true, false, null, args);
       }

       /**
        * Emitted for plain function calls.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T
       invokeFunctionWithMode(Class<T>  cls, String name, String modes, Object... args)
       {
          BaseDataType res = invokeFunctionImpl(new character(name), null, false, false, modes, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for plain function calls.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T
       invokeFunctionWithMode(Class<T>  cls, character name, String modes, Object... args)
       {
          BaseDataType res = invokeFunctionImpl(name, null, false, false, modes, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for the plain function calls, when they are defined as
        * FUNCTION ... IN handle.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T
       invokeFunctionInWithMode(Class<T>  cls, String name, handle h, String modes, Object... args)
       {
          BaseDataType res = invokeFunctionImpl(new character(name), h, false, false, modes, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for the plain function calls, when they are defined as
        * FUNCTION ... IN handle.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T
       invokeFunctionInWithMode(Class<T> cls, character name, handle h, String modes, Object... args)
       {
          BaseDataType res = invokeFunctionImpl(name, h, false, false, modes, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for plain function calls.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T invokeFunction(Class<T>  cls,
                                                               String    name,
                                                               Object... args)
       {
          BaseDataType res = invokeFunctionImpl(new character(name), null, false, false, null, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for plain function calls.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T invokeFunction(Class<T>  cls,
                                                               character name,
                                                               Object... args)
       {
          BaseDataType res = invokeFunctionImpl(name, null, false, false, null, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for the plain function calls, when they are defined as
        * FUNCTION ... IN handle.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T invokeFunctionIn(Class<T>  cls,
                                                                 String    name,
                                                                 handle    h,
                                                                 Object... args)
       {
          BaseDataType res = invokeFunctionImpl(new character(name), h, false, false, null, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for the plain function calls, when they are defined as
        * FUNCTION ... IN handle.
        *
        * @param    cls
        *           Class.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static <T extends BaseDataType> T invokeFunctionIn(Class<T>  cls,
                                                                 character name,
                                                                 handle    h,
                                                                 Object... args)
       {
          BaseDataType res = invokeFunctionImpl(name, h, false, false, null, args);
          return convertValue(cls, res);
       }

       /**
        * Emitted for plain function calls.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeFunctionWithMode(String    name,
                                                         String    modes,
                                                         Object... args)
       {
          return invokeFunctionImpl(new character(name), null, false, false, modes, args);
       }

       /**
        * Emitted for plain function calls.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeFunctionWithMode(character name,
                                                         String    modes,
                                                         Object... args)
       {
          return invokeFunctionImpl(name, null, false, false, modes, args);
       }

       /**
        * Emitted for the plain function calls, when they are defined as
        * FUNCTION ... IN handle.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeFunctionInWithMode(String    name,
                                                           handle    h,
                                                           String    modes,
                                                           Object... args)
       {
          return invokeFunctionImpl(new character(name), h, false, false, modes, args);
       }

       /**
        * Emitted for the plain function calls, when they are defined as
        * FUNCTION ... IN handle.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeFunctionInWithMode(character name,
                                                           handle    h,
                                                           String    modes,
                                                           Object... args)
       {
          return invokeFunctionImpl(name, h, false, false, modes, args);
       }

       /**
        * Emitted for plain function calls.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeFunction(String name, Object... args)
       {
          return invokeFunctionImpl(new character(name), null, false, false, null, args);
       }

       /**
        * Emitted for plain function calls.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeFunction(character name, Object... args)
       {
          return invokeFunctionImpl(name, null, false, false, null, args);
       }

       /**
        * Emitted for the plain function calls, when they are defined as
        * FUNCTION ... IN handle.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeFunctionIn(String name, handle h, Object... args)
       {
          return invokeFunctionImpl(new character(name), h, false, false, null, args);
       }

       /**
        * Emitted for the plain function calls, when they are defined as
        * FUNCTION ... IN handle.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       public static BaseDataType invokeFunctionIn(character name, handle h, Object... args)
       {
          return invokeFunctionImpl(name, h, false, false, null, args);
       }

       /**
        * Invoke the procedure with the given name in a related thread.
        * <p>
        * This implements the RUN ... AS-THREAD SET ht. statement.
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeAsThreadWithMode(String name, String modes, Object... args)
       {
          invokeAsThreadSetWithMode(name, null, modes, args);
       }

       /**
        * Invoke the procedure with the given name in a related thread, and save the resource which
        * can manage the related thread at the caller in the specified handle.
        * <p>
        * This implements the RUN ... AS-THREAD SET ht. statement.
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    ht
        *           The handle where to save the resource to manage the related thread.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       public static void invokeAsThreadSetWithMode(String    name,
                                                    handle    ht,
                                                    String    modes,
                                                    Object... args)
       {
          invokeImpl(work.obtain().resolvers,
                     null,
                     new character(name),
                     false,
                     false,
                     false,
                     false,
                     true,
                     ht,
                     modes,
                     new ArrayArgumentResolver(args));

       }

       /**
        * Invoke the procedure with the given name in a related thread.
        * <p>
        * This implements the RUN ... AS-THREAD. statement.
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        */
       public static void invokeAsThread(String name)
       {
          invokeAsThreadSetWithMode(name, null, null);
       }

       /**
        * Invoke the procedure with the given name in a related thread, and save the resource which
        * can manage the related thread at the caller in the specified handle.
        * <p>
        * This implements the RUN ... AS-THREAD SET ht. statement.
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    ht
        *           The handle where to save the resource to manage the related thread.
        */
       public static void invokeAsThreadSet(String name, handle ht)
       {
          invokeAsThreadSetWithMode(name, ht, null);
       }

       /**
        * Check if an internal procedure or function is reachable and valid. This searches the
        * specified handle and all the super-procedures.
        *
        * @param    h
        *           A valid procedure handle.
        * @param    name
        *           The procedure or function name.
        * @param    function
        *           Flag indicating if the search is for a function or for an internal procedure.
        * @param    modes
        *           The parameter modes.
        * @param    validateModes
        *           Flag indicating if the parameter modes should be validated or not.
        * @param    args
        *           The passed arguments.
        *
        * @return   null if the internal entry can't be found; otherwise ArgValidationErrors instance
        *           with error status or {@link ArgValidationErrors#NO_ERROR} if error is not exists.
        */
       public static ArgValidationErrors reachableInternalEntry(handle    h,
                                                                String    name,
                                                                boolean   function,
                                                                String    modes,
                                                                boolean   validateModes,
                                                                Object... args)
       {
          WorkArea wa = work.obtain();
          InternalEntryCaller caller = null;
          ArrayList<Resolver> inHandleResolvers = wa.inHandleResolvers;
          for (int i = 0; i < inHandleResolvers.size(); i++)
          {
             Resolver r = inHandleResolvers.get(i);
             caller = r.resolve(h, name, function, false, null);

             if (caller != null)
             {
                break;
             }
          }

          if (caller == null)
          {
             // was not found
             return null;
          }

          ArgValidationErrors valid = caller.valid(function, modes, new int[2], args);
          if (valid != ArgValidationErrors.NO_ERROR)
          {
             return valid;
          }

          // validate the parameter modes
          String definedModes = caller.getParameterModes(function);
          if (validateModes &&
              (modes != null && !modes.equals(definedModes) ||
               (modes == null && definedModes != null)))
          {
             return ArgValidationErrors.INCORRECT_TYPES;
          }

          return valid;
       }

       /**
        * This is a special API which is used to delegate a call from converted 4GL code
        * (via _P2J_REMOTE_CALL_ functions) to the remote side.  Usually, this is used when P2J client
        * runs in embedded mode, i.e. the P2J screens are embedded in another customer-specific
        * application.  The call will be remoted into the customer-specific application.
        * <p>
        * This API is expected to block waiting for a response.
        *
        * @param    content
        *           The request data incoming from 4GL.
        *
        * @return   The response data from the customer-specific application, which is sent back to
        *           the converted 4GL code.
        */
       public static character remoteCall(character content)
       {
          if (content == null)
          {
             content = new character();
          }

          UnformattedPayload payload = new UnformattedPayload();
          payload.payload = content.toStringMessage();
          UnformattedPayload response = LogicalTerminal.remoteCall(payload);

          return new character(response.payload);
       }

       /**
        * This is a special API which is used to delegate a call from converted 4GL code
        * (via _P2J_REMOTE_CALL_ functions) to the remote side.  Usually, this is used when P2J client
        * runs in embedded mode, i.e. the P2J screens are embedded in another customer-specific
        * application.  The call will be remoted into the customer-specific application.
        * <p>
        * This API is expected to block waiting for a response.
        *
        * @param    content
        *           The request data incoming from 4GL.
        *
        * @return   The response data from the customer-specific application, which is sent back to
        *           the converted 4GL code.
        */
       public static character remoteCall(String content)
       {
          return remoteCall(new character(content));
       }

       /**
        * This API is used when P2J client runs in embedded mode (i.e. the P2J screens are embedded
        * into another customer-specific application).  It allows the customer-specific to execute
        * converted 4GL code.  Only external programs, procedures and functions can be invoked.  The
        * result of this invocation (and any additional OUTPUT parameters, errors, etc) will be
        * returned back to the customer-specific application.
        *
        * @param    payload
        *           The details about the request.
        *
        * @return   The response for this request.
        */
       public static InvocationResponsePayload invoke(InvocationRequestPayload payload)
       {
          WorkArea wa = work.obtain();

          handle h;
          boolean persistent;

          switch (payload.type)
          {
             case EXTERNAL_PROGRAM:
             {
                persistent = payload.persistent;

                h = (persistent ? new handle() : null);
                break;
             }
             default:
             {
                persistent = false;

                long hid = payload.handle;
                h = (hid == 0 ? null : handle.fromResourceId(hid));

                if (h != null && h._isValid() && !wa.pm.isRemotePersistentProcedure(h.get()))
                {
                   String msg = "Attempting to access a persistent procedure which was not obtained " +
                                "by the remote, customer-specific application!";
                   if (LOG.isLoggable(Level.SEVERE))
                   {
                      LOG.severe(msg);
                   }

                   throw new SilentUnwindException(new IllegalStateException(msg));
                }

                break;
             }
          }

          character cname = new character(payload.name);
          boolean function = payload.type == InvokeType.FUNCTION;
          MapArgumentResolver argResolver = new MapArgumentResolver(payload.arguments);

          BlockRunner block = new BlockRunner((BlockRunner br) ->
          {
             BaseDataType result = null;

             work.obtain().remoteInvoke = true;

             if (payload.type == InvokeType.EXTERNAL_PROGRAM)
             {
                invokeExternalProcedure(cname, persistent, false, false, h, false, null, null, argResolver);
                result = wa.returnValue;

                if (persistent && h != null && h._isValid())
                {
                   wa.pm.registerRemotePersistentProcedure(h.get());
                }
             }
             else
             {
                result = invokeImpl(wa.inHandleResolvers, h, cname, function,
                                    false, false, false, false, null, null, argResolver);

                if (payload.type == InvokeType.PROCEDURE)
                {
                   // if it is a procedure, get the result from RETURN-VALUE
                   result = wa.returnValue;
                }
             }

             return result;
          },
          () ->
          {
             // if the API was resolved and validated, the flag will be reset just before the entry
             // is executed (so any downstream calls are not seen as "remote"); if there are any
             // errors, and the entry could not be executed, the flag needs to be reset.
             // so, we ensure the flag is reset here, always.
             wa.remoteInvoke = false;
          }, payload.noError);

          block.execute();

          InvocationResponsePayload response = new InvocationResponsePayload();
          // the handle is sent always (so that the customer app knows in which handle the program
          // was ran... so it can delete it (maybe)
          response.handle = (h != null && h._isValid() ? h.getResourceId() : 0);
          response.returnValue = (block.result == null ? null : block.result.toStringMessage());
          response.error = block.error;

          if (block.error)
          {
             response.errorNumber  = block.errorNumber;
             response.errorMessage = block.errorMessage;
          }

          response.stop = block.stop;
          response.quit = block.quit;
          response.outputArguments = argResolver.getOutputArguments();

          return response;
       }

       /**
        * Check if last attempted invoke failed because of invalid arguments.
        *
        * @return   The state of the {@link WorkArea#invalidArgs} flag.
        */
       public static boolean hadInvalidArguments()
       {
          return work.obtain().invalidArgs;
       }

       /**
        * Resolve the specified legacy method.
        *
        * @param    isPoly
        *           Flag indicating if the call is for a non-dynamic POLY call.
        * @param    type
        *           The legacy class where to look.
        * @param    name
        *           The legacy class name.
        * @param    method
        *           The legacy method name.
        * @param    isStatic
        *           Flag indicating if we are looking for a static method.
        * @param    modes
        *           The argument's modes.  May be <code>null</code>.
        * @param    origArgs
        *           The arguments.
        *
        * @return   The resolved internal entry.
        */
       public static InternalEntry resolveLegacyEntry(boolean   isPoly,
                                                      Class<?>  type,
                                                      String    name,
                                                      String    method,
                                                      boolean   isStatic,
                                                      String    modes,
                                                      Object... origArgs)
       {
          return resolveLegacyEntry(work.obtain(), isPoly, true, isStatic, type, name, method, modes, origArgs);
       }

       /**
        * The implementation of all function and procedure calls. This disambiguates and implements
        * the following calls:
        * <ul>
        *    <li>if <code>h</code> is not null, then this is a RUN ... IN handle call.</li>
        *    <li>if the <code>exports</code> is not null, then this is a RUN ... ON SERVER call, so
        *        only external programs will be searched.  Note that this parameter will be not-null
        *        only when this call is executed on the remote, appserver, side.</li>
        *    <li>else, this is a RUN statement call with no special clauses.</li>
        * </ul>
        *
        * @param    h
        *           The handle to which the procedure belongs. If <code>null</code>, defaults to
        *           THIS-PROCEDURE.
        * @param    name
        *           The legacy 4GL name for the function or procedure.
        * @param    function
        *           <code>true</code> if this is a function call.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        * @param    superCall
        *           <code>true</code> if this is a RUN SUPER or SUPER() call.
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The arguments.
        *
        * @return   The return value from the block (or its nested blocks) which is the <code>unknown
        *           value</code> by default, on error or for procedures.
        */
       public static BaseDataType invoke(handle    h,
                                         character name,
                                         boolean   function,
                                         boolean   dynamicFunction,
                                         boolean   superCall,
                                         boolean   transactionDistinct,
                                         String    modes,
                                         Object... args)
       {
          // h will never be a proxy procedure handle at the time of this call. if it ends up a
          // remote procedure handle here, we have a problem in the runtime implementation
          if (ProcedureManager.isProxy(h))
          {
             throw new IllegalArgumentException("The supplied procedure handle is a proxy handle!");
          }

          WorkArea wa = work.obtain();
          List<Resolver> resolvers = null;
          if (h != null)
          {
             // The target of a "RUN proc" stmt or DYNAMIC-FUNCTION call ends on
             // first find in:
             // - THIS-PROCEDURE
             // - THIS-PROCEDURE:super-procedures
             // - SESSION-super-procedures
             resolvers = wa.inHandleResolvers;
          }
          else
          {
             // The target of a "RUN proc" stmt or DYNAMIC-FUNCTION call ends on
             // first find in:
             // - THIS-PROCEDURE
             // - THIS-PROCEDURE:super-procedures
             // - SESSION-super-procedures
             // - if an external program with the given name is found (for the
             // "RUN proc" statement without "IN handle" clause)
             // - search distinguishes between procedures and functions with the
             // same name. On first name match, will attempt to invoke it.
             // - is not necessarily for the target procedure to have its header
             // defined in the current procedure. Applies only to "RUN proc"
             // statement.
             // - "RUN proc0." and "proc0()" will end up calling different code
             // (one is a super-proc, the other is a super-function).
             // The body for these two may be in different super-procedures.
             resolvers = wa.resolvers;
          }

          return invokeImpl(resolvers,
                            h,
                            name,
                            function,
                            dynamicFunction,
                            superCall,
                            transactionDistinct,
                            false,
                            null,
                            modes,
                            new ArrayArgumentResolver(args));
       }

       /**
        * Resolve the external program with the specified name.
        *
        * @param    name
        *           The legacy 4GL name for the function or procedure.
        * @param    exports
        *           The list of allowed external procedures to be invoked with a RUN ... ON SERVER
        *           statement.
        *
        * @return  The instantiated (not yet initialized) {@link ExternalProgramWrapper}.
        */
       public static ExternalProgramWrapper resolveExternalProcedure(String name, character exports)
       {
          WorkArea wa = work.obtain();
          InternalEntryCaller caller = wa.externalResolver
                                         .resolve(null, name, false, false, exports, true);

          ExternalProgramWrapper extProg = null;

          if (caller != null)
          {
             extProg = new ExternalProgramWrapper(caller.getCallerInstance());

             wa.pm.addProcedure(new handle(extProg), name, true);
          }

          return extProg;
       }

       /**
        * Initialize the specified external procedure. This is done by calling the associated
        * <code>execute</code> method.
        *
        * @param    extProg
        *           The associated external program.
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The arguments.
        */
       public static void initializeExternalProcedure(ExternalProgramWrapper extProg,
                                                      boolean                transactionDistinct,
                                                      String                 modes,
                                                      Object...              args)
       {
          WorkArea wa = work.obtain();
          InternalEntryCaller caller = new ExternalProcedureCaller(wa, extProg.get());

          // resolve the name early, as at the time the invokeError is called the proc handle is gone
          String relName = wa.pm.getRelativeName(extProg.referent);

          Exception deferred = null;
          handle hProc = new handle(extProg);
          try
          {
             if (!validArguments(wa, relName, caller, false, modes, args))
             {
                return;
             }

             boolean error = false;
             try
             {
                Object referent = extProg.get();

                Runnable push = () ->
                {
                   wa.pm.pushCalleeInfo(referent.getClass(),
                                        false, referent, referent,
                                        ProcedureManager.EXTERNAL_PROGRAM, relName,
                                        false, true);
                };
                caller.setPushWorker(push);

                caller.invoke(modes, args);
             }
             catch (Exception e)
             {
                // any exceptions here cause the persistent procedure to no longer be available
                error = true;

                throw e;
             }
             finally
             {
                if (error || wa.em.isPending())
                {
                   ProcedureManager.removePersistentProcedure(hProc);
                }
             }
          }

          // allow Progress compatible exceptions to flow through
          catch (ConditionException | RetryUnwindException ce)
          {
             throw ce;
          }

          // handle reflection exceptions
          catch (NoSuchMethodException nsm)
          {
             invokeFailure(caller.getMethodName());
             return;
          }

          catch (InvocationTargetException exc)
          {
             deferred = checkInvocationException(exc);
          }

          // all other cases
          catch (Exception exp)
          {
             deferred = exp;
          }

          if (deferred != null)
          {
             invokeError(caller, relName, deferred);
          }
       }

       /**
        * Execute the specified destructor in the given class.
        *
        * @param   referent
        *          The legacy object.
        * @param   dtor
        *          The destructor.
        */
       static void executeDestructor(_BaseObject_ referent, InternalEntry dtor)
       {
          WorkArea wa = work.obtain();
          String relName = wa.pm.getRelativeName(referent);

          Object saved = wa.tm.getNowIterating();
          wa.tm.setNowIterating(null);

          handle phandle = new handle(new ExternalProgramWrapper(referent));
          InternalEntryCaller dtorCaller = new InternalEntryCaller(wa, phandle, dtor.jname, dtor.pname);
          Runnable push = () ->
          {
             Class<?> def = dtor.getMethod().getDeclaringClass();
             wa.pm.pushCalleeInfo(def, false, referent, referent, dtor.pname, relName, false, false);
          };

          dtorCaller.setPushWorker(push);
          dtorCaller.ie = dtor;
          if (!validArguments(wa, relName, dtorCaller, false, null))
          {
             return;
          }
          try
          {
             dtorCaller.invoke(null);
          }
          catch (Throwable t)
          {
             // ignore errors
             // TODO: only from implicit delete - on explicit delete these need to be thrown back to
             // the caller.
          }
          finally
          {
             wa.tm.setNowIterating(saved);
          }
       }

       /**
        * Given a legacy class, initialize it by executing its static constructor.
        *
        * @param    cls
        *           The legacy class.
        * @param    name
        *           The legacy class name.
        *
        * @return   <code>true</code> if the initialization completed or if there is no static
        *           constructor.
        */
       static boolean initializeLegacyClass(Class<? extends _BaseObject_> cls, Object referent, String name)
       {
          WorkArea wa = work.obtain();

          // if the class was loaded, then register it with the SourceNameMapper
          SourceNameMapper.registerLegacyClass(cls);

          handle phandle = new handle(new ExternalProgramWrapper(referent));
          wa.pm.addProcedure(phandle, name, true, false, false, true);

          InternalEntry iector = SourceNameMapper.getStaticConstructor(cls);

          // resolve the name early, as at the time the invokeError is called the proc handle is gone
          String relName = wa.pm.getRelativeName(referent);

          Exception deferred = null;
          try
          {
             BufferManager bm = BufferManager.get();

             // call a ContextLocal.get() for any defined static member in this class, regardless of
             // access mode; this is required so that the resources are created BEFORE the c'tor is
             // invoked, so they get registered with the surrogate external program
             Method mget = ContextLocal.class.getMethod("get");
             String pkgroot = SourceNameMapper.getPackageRoot();
             for (Field f : cls.getDeclaredFields())
             {
                if (!Modifier.isStatic(f.getModifiers()))
                {
                   continue;
                }

                Class<?> ftype = f.getType();
                if (ftype.isPrimitive() || ftype == InvokeConfig.class)
                {
                   continue;
                }
                if (ftype.isArray() && ftype.getComponentType().isPrimitive())
                {
                   continue;
                }

                String fname = f.getName();
                if (fname.equals(TranslationManager.TR_MANAGER_FIELD_NAME) ||
                    fname.equals(TranslationManager.REFERENT_ID_FIELD_NAME))
                {
                   continue;
                }

                if (!ContextLocal.class.isAssignableFrom(ftype))
                {
                   String msg = "Field " + f.toString() + " in class " +
                                cls.toString() + " is static, but not ContextLocal!";
                   String fpkg = ftype.getPackage().getName();
                   if (!fpkg.startsWith("com.goldencode.p2j.util.logging") &&
                      (fpkg.startsWith("com.goldencode") || fpkg.startsWith(pkgroot)))
                   {
                      throw new IllegalStateException(msg);
                   }
                   else if (LOG.isLoggable(Level.FINE))
                   {
                      LOG.log(Level.FINE, msg);
                   }

                   continue;
                }

                f.setAccessible(true);

                Object obj = mget.invoke(f.get(null));
                if (obj instanceof BufferReference)
                {
                   bm.registerPendingBufferClass(((BufferReference) obj).buffer(), cls);
                }
             }

             boolean error = false;
             try
             {
                Object[] args = null;
                Method mtor = null;
                try
                {
                   mtor = iector == null ? null : cls.getMethod(iector.jname);
                }
                catch (Exception e)
                {
                   // ignore
                }
                if (mtor == null)
                {
                   // emulate an empty 'execute' method
                   mtor = ObjectOps.class.getDeclaredMethod("defaultStaticConstructor", Class.class);
                   mtor.setAccessible(true);
                   args = new Object[] { cls };
                }

                InternalEntryCaller ctorCaller = new ExternalProcedureCaller(wa, referent);
                ctorCaller.mthd = mtor;
                Class<?> def = mtor.getDeclaringClass();

                Runnable push = () ->
                {
                   wa.pm.pushCalleeInfo(def, false, referent, referent,
                                        ProcedureManager.EXTERNAL_PROGRAM, relName,
                                        false, true);
                };

                ctorCaller.setPushWorker(push);
                ctorCaller.invoke(null, args);
             }
             catch (Exception e)
             {
                // any exceptions here cause the persistent procedure to no longer be available
                error = true;

                // no reason to re-throw, the method will return false in the finally block
             }
             finally
             {
                if (error || wa.em.isPending())
                {
                   return false;
                }
             }

             return true;
          }

          // allow Progress compatible exceptions to flow through
          catch (ConditionException | RetryUnwindException ce)
          {
             throw ce;
          }

          // handle reflection exceptions
          catch (NoSuchMethodException nsm)
          {
             invokeFailure(name);
          }

          catch (InvocationTargetException exc)
          {
             deferred = checkInvocationException(exc);
          }

          // all other cases
          catch (Exception exp)
          {
             deferred = exp;
          }

          if (deferred != null)
          {
             invokeError(null, relName, deferred);
          }

          return true;
       }

       /**
        * Given a legacy class instance, execute the initialization methods.  These are comprised from
        * the synthetic execute methods (for all super-classes), and the synthetic constructor method
        * for this class.
        *
        * @param    referent
        *           The object instance.
        * @param    name
        *           The instantiating legacy class name.
        * @param    executes
        *           The list of 'execute' initialization methods.
        * @param    modes
        *           The parameter modes for the constructor method.
        * @param    bypassConstructor
        *           Avoid executing the constructor method during initialization if <code>true</code>.
        * @param    args
        *           The arguments for the constructor method.
        *
        * @return   <code>true</code> if the initialization completed.
        */
       static boolean initializeLegacyObject(Object    referent,
                                             String    name,
                                             Method[]  executes,
                                             String    modes,
                                             boolean   bypassConstructor,
                                             Object... args)
       {
          WorkArea wa = work.obtain();
          handle phandle = new handle(new ExternalProgramWrapper(referent));
          wa.pm.addProcedure(phandle, name, true);

          BufferManager.get().resolvePendingBufferClasses(referent);

          InternalEntry iEntry = null;
          if (!bypassConstructor)
          {
             List<InternalEntry> ctors = SourceNameMapper.getConstructors((Class) referent.getClass());

             iEntry = resolveLegacyEntry(wa, false, true, false, referent.getClass(), name, null, modes, ctors, args);
             if (iEntry == null)
             {
                // not found
                return false;
             }
          }

          // resolve the name early, as at the time the invokeError is called the proc handle is gone
          String relName = wa.pm.getRelativeName(referent);

          Exception deferred = null;
          try
          {
             boolean error = false;
             try
             {
                // the pending scopeables must be registered for each 'execute' or constructor call, as these
                // belong to the instance, not the method.
                Scopeable[] scopeables = wa.tm.getPendingScopeables();

                try
                {
                   Class<?> def = referent.getClass();
                   Body body = executes == null ? null : () ->
                   {
                      if (executes != null)
                      {
                         for (int i = 0; i < executes.length; i++)
                         {
                            try
                            {
                               executes[i].invoke(referent);
                            }
                            catch (ReflectiveOperationException e)
                            {
                               LOG.log(Level.SEVERE, "Error initializing legacy object of type " + relName, e);
                            }
                         }
                      }
                   };
                   BlockManager.externalProcedure(def, referent, TransactionType.NONE, new Block(body));
                }
                finally
                {
                   wa.tm.setPendingScopeables(scopeables);
                }

                if (!bypassConstructor)
                {
                   InternalEntry ie = iEntry;
                   InternalEntryCaller ctorCaller = new InternalEntryCaller(wa, phandle, ie.jname, ie.pname);
                   ctorCaller.mthd = ie.getMethod();
                   ctorCaller.ie = ie;
                   prepareArguments(ie, args);

                   if (!validArguments(wa, name, ctorCaller, false, modes, args))
                   {
                      return false;
                   }

                   // use the method resolved after arg validation.
                   ctorCaller.mthd = ie.getMethod();

                   Runnable push = () ->
                   {
                      Class<?> def = ie.getMethod().getDeclaringClass();
                      wa.pm.pushCalleeInfo(def, false, referent, referent, ie.pname, relName, false, false);
                   };

                   ctorCaller.setPushWorker(push);
                   ctorCaller.invoke(null, args);
                }
             }

             // allow Progress compatible exceptions to flow through
             catch (ConditionException | RetryUnwindException ce)
             {
                error = true;
                throw ce;
             }

             catch (InvocationTargetException exc)
             {
                deferred = checkInvocationException(exc);
             }
             finally
             {
                wa.tm.deregisterOutputParameterAssigner();

                if (!error && wa.em.isPending())
                {
                   return false;
                }
             }

             return true;
          }

          // allow Progress compatible exceptions to flow through
          catch (ConditionException | RetryUnwindException ce)
          {
             throw ce;
          }

          // handle reflection exceptions
          catch (NoSuchMethodException nsm)
          {
             invokeFailure(name);
          }

          catch (InvocationTargetException exc)
          {
             deferred = checkInvocationException(exc);
          }

          // all other cases
          catch (Exception exp)
          {
             deferred = exp;
          }

          if (deferred != null)
          {
             invokeError(null, relName, deferred);
          }

          return true;
       }

       /**
        * Invoke the specified legacy method in a legacy OO class.
        *
        * @param    voidReturn
        *           Flag indicating that the method can return void.
        * @param    referent
        *           The referent where the method belongs.  This can be a {@link Class} instance in
        *           case of static methods or a {@link _BaseObject_} instance in case of instance
        *           methods.
        * @param    name
        *           The legacy class name.
        * @param    method
        *           The legacy method name.
        * @param    modes
        *           The argument's modes.  May be <code>null</code>.
        * @param    args
        *           The arguments.
        *
        * @return   The returned value or <code>null</code> if this is a void method.
        */
       static Object invokeLegacyMethod(boolean  voidReturn,
                                        Object   referent,
                                        String   name,
                                        String   method,
                                        String   modes,
                                        Object[] args)
       {
          boolean isStatic = referent instanceof Class;
          Class<? extends _BaseObject_> type;
          if (isStatic)
          {
             type = (Class<? extends _BaseObject_>) referent;
             referent = ObjectOps.getStaticInstance(type);
          }
          else
          {
             type = (Class<? extends _BaseObject_>) referent.getClass();
          }

          InternalEntry ie = resolveLegacyEntry(work.obtain(), false, true, isStatic, type, name, method, modes, args);
          if (ie == null && !isStatic)
          {
             isStatic = true;
             type = (Class<? extends _BaseObject_>) referent.getClass();
             referent = ObjectOps.getStaticInstance(type);

             // allow a static method to be invoked from an instance context
             ie = resolveLegacyEntry(work.obtain(), false, true, isStatic, type, name, method, modes, args);
          }

          if (ie == null)
          {
             // not found

             ErrorManager.recordOrThrowError(14457,
                                             "Could not locate method '" + method + "' with " +
                                             "matching signature in class '" + name + "'",
                                             false);

             return unknown.UNKNOWN;
          }

          return invokeLegacyMethod(ie, voidReturn, referent, name, method, modes, args);
       }

       /**
        * Invoke the specified legacy method in a legacy OO class.
        *
        * @param    ie
        *           The target entry associated with this method.
        * @param    voidReturn
        *           Flag indicating that the method can return void.
        * @param    referent
        *           The referent where the method belongs.  This can be a {@link Class} instance in
        *           case of static methods or a {@link _BaseObject_} instance in case of instance
        *           methods.
        * @param    name
        *           The legacy class name.
        * @param    method
        *           The legacy method name.
        * @param    modes
        *           The argument's modes.  May be <code>null</code>.
        * @param    args
        *           The arguments.
        *
        * @return   The returned value or <code>null</code> if this is a void method.
        */
       public static Object invokeLegacyMethod(InternalEntry ie,
                                        boolean       voidReturn,
                                        Object        referent,
                                        String        name,
                                        String        method,
                                        String        modes,
                                        Object[]      args)
       {
          Method mthd = ie.getMethod();

          boolean voidMethod = mthd.getReturnType() == void.class ||
                               mthd.getReturnType() == Void.class;
          // if void return is not allowed, and the method is void, then fail
          if (!voidReturn && voidMethod)
          {
             String msg = String.format("Invalid use of dynamic invoke of VOID method '%s' in an " +
                                        "expression", method);
             ErrorManager.recordOrThrowError(15304, msg, false, false);
             return unknown.UNKNOWN;
          }

          WorkArea wa = work.obtain();

          // the method was found
          handle phandle = new handle(new ExternalProgramWrapper(referent));
          InternalEntryCaller caller = new InternalEntryCaller(wa, phandle, mthd.getName(), method);
          caller.mthd = ie.getMethod();
          caller.ie = ie;
          prepareArguments(ie, args);

          if (!validArguments(wa, method, caller, false, modes, args))
          {
             return unknown.UNKNOWN;
          }

          // resolve the name early, as at the time the invokeError is called the proc handle is gone
          String relName = wa.pm.getRelativeName(referent);

          Exception deferred = null;
          try
          {
             try
             {
                Object ref = referent;
                Runnable push = () ->
                {
                   Class<?> def = ie.getMethod().getDeclaringClass();
                   wa.pm.pushCalleeInfo(def, false, ref, ref, method, relName, true, false);
                   wa.pm.peekCalleeInfo().setLegacyName(ie.pname);
                };

                caller.setPushWorker(push);
                caller.mthd = mthd;
                mthd.setAccessible(true);
                return caller.invoke(ie.getParameterModes(), args);
             }
             finally
             {
                wa.tm.deregisterOutputParameterAssigner();
             }
          }

          // allow Progress compatible exceptions to flow through
          catch (ConditionException | RetryUnwindException ce)
          {
             throw ce;
          }

          // handle reflection exceptions
          catch (NoSuchMethodException nsm)
          {
             invokeFailure(name);
          }

          catch (InvocationTargetException exc)
          {
             deferred = checkInvocationException(exc);
          }

          // all other cases
          catch (Exception exp)
          {
             deferred = exp;
          }

          if (deferred != null)
          {
             invokeError(null, relName, deferred);
          }

          return null;
       }

       /**
        * Prepare the arguments to be passed to the {@link ControlFlowOps} APIs.  This requires
        * wrapping any output parameters and other extent, OUTPUT or INPUT-OUTPUT related work.
        *
        * @param    ie
        *           The internal entry to be executed.
        * @param    args
        *           The arguments.
        */
       private static void prepareArguments(InternalEntry ie, Object...args)
       {
          LegacySignature lsig = ie.getMethod() == null
                                     ? null
                                     : ie.getMethod().getAnnotation(LegacySignature.class);

          String modes = ie.getParameterModes();
          for (int i = 0; i < args.length; i++)
          {
             char mode = modes != null && i < modes.length() ? modes.charAt(i) : InternalEntry.INPUT_MODE;
             Object arg = args[i];

             if (arg instanceof BaseDataTypeVariable)
             {
                arg = ((BaseDataTypeVariable) arg).get();
                args[i] = arg;
             }
             else if (arg instanceof Resolvable)
             {
                Resolvable r = (Resolvable) arg;
                arg = r.resolve();
                args[i] = arg;

                if (mode == InternalEntry.INPUT_MODE && r.getType() == integer.class && arg instanceof int64)
                {
                   arg = InputParameter.cast(integer.class, (int64) arg);
                   args[i] = arg;
                }
             }

             if (mode == InternalEntry.OUTPUT_MODE || mode == InternalEntry.INPUT_OUTPUT_MODE)
             {
                boolean io = mode == InternalEntry.INPUT_OUTPUT_MODE;

                if (arg instanceof FieldReference)
                {
                   FieldReference fr = (FieldReference) arg;
                   if (fr.getExtent() != null)
                   {
                      args[i] = OutputParameter.wrap(fr.getParentBuffer().getDMOProxy(),
                                                     fr.getProperty(),
                                                     (Class) fr.getType(),
                                                     fr.getExtent().intValue(),
                                                     io);
                   }
                   else
                   {
                      args[i] = OutputParameter.wrap(fr.getParentBuffer().getDMOProxy(),
                                                     fr.getProperty(),
                                                     (Class) fr.getType(),
                                                     io);
                   }
                }
                else if (arg instanceof PropertyReference)
                {
                   PropertyReference fr = (PropertyReference) arg;
                   args[i] = fr.wrap(io);
                }
                else if (arg instanceof BaseDataType)
                {
                   if (lsig == null)
                   {
                      args[i] = OutputParameter.wrap((BaseDataType) arg, io);
                   }
                   else
                   {
                      LegacyParameter lpar = i < lsig.parameters().length ? lsig.parameters()[i] : null;
                      if (lpar != null && lpar.type().equals("JOBJECT") &&
                          lpar.qualified() != null      &&
                          !lpar.qualified().isEmpty())
                      {
                         if (arg instanceof jobject)
                         {
                            // nothing to do, just wrap
                            args[i] = OutputParameter.wrap((BaseDataType) arg, io);
                         }
                         else
                         {
                            // wrap to Java
                            try
                            {
                               args[i] = OutputParameter.wrapToJava(Class.forName(lpar.qualified()),
                                                                    (BaseDataType) arg,
                                                                    io);
                            }
                            catch (ClassNotFoundException e)
                            {
                               throw new RuntimeException("Unexpected qualified type " +
                                                          lpar.qualified(), e);
                            }
                         }
                      }
                      else if (arg instanceof jobject)
                      {
                         // wrap from Java
                         args[i] = OutputParameter.wrapFromJava(BaseDataType.fromTypeName(lpar.type()),
                                                                (jobject<?>) arg,
                                                                io);
                      }
                   }
                }
                else if (!(arg instanceof AbstractExtentParameter ||
                           arg instanceof TableParameter ||
                           arg instanceof DataSetParameter))
                {
                   throw new RuntimeException("Unexpected OUTPUT arg " + arg);
                }
             }
             else if (arg instanceof PropertyReference)
             {
                PropertyReference fr = (PropertyReference) arg;
                args[i] = fr.get();
             }
          }
       }

       /**
        * Normalize the given argument to be compatible to a BDT type.
        *
        * @param    arg
        *           The argument.
        * @param    sigType
        *           The argument's original type.
        * @param    candidateType
        *           The method definition parameter type.
        */
       private static Object normalizeType(Object arg, Class<?> sigType, Class<?> candidateType)
       {
          // convert non-BDT argument to BDT
          if (arg instanceof BaseDataTypeVariable)
          {
             arg = ((BaseDataTypeVariable) arg).get();
          }
          else if (!BaseDataType.class.isAssignableFrom(sigType) &&
                   !(sigType.isArray() ||
                     arg instanceof AbstractParameter ||
                     arg instanceof FieldReference ||
                     arg instanceof PropertyReference))
          {
             Object aval = null;

             if (sigType == Integer.class)
             {
                if (candidateType == integer.class)
                {
                   aval = new integer((Integer) arg);
                }
                else if (candidateType == int64.class)
                {
                   aval = new int64((Integer) arg);
                }
                else if (candidateType == decimal.class)
                {
                   aval = new decimal((Integer) arg);
                }
                else if (candidateType == recid.class)
                {
                   aval = new recid((Integer) arg);
                }
             }
             else if (sigType == Long.class)
             {
                if (candidateType == integer.class)
                {
                   aval = new integer((Long) arg);
                }
                else if (candidateType == int64.class)
                {
                   aval = new int64((Long) arg);
                }
                else if (candidateType == decimal.class)
                {
                   aval = new decimal((Long) arg);
                }
                else if (candidateType == recid.class)
                {
                   aval = new recid((Long) arg);
                }
             }
             else if (sigType == Double.class)
             {
                if (candidateType == integer.class)
                {
                   aval = new integer((Double) arg);
                }
                else if (candidateType == int64.class)
                {
                   aval = new int64((Double) arg);
                }
                else if (candidateType == decimal.class)
                {
                   aval = new decimal((Double) arg);
                }
             }
             else if (sigType == Float.class)
             {
                if (candidateType == integer.class)
                {
                   aval = new integer((Float) arg);
                }
                else if (candidateType == int64.class)
                {
                   aval = new int64((Float) arg);
                }
                else if (candidateType == decimal.class)
                {
                   aval = new decimal((Float) arg);
                }
             }
             else if (sigType == Boolean.class)
             {
                aval = new logical((Boolean) arg);
             }
             else if (sigType == String.class)
             {
                aval = new character((String) arg);
             }

             if (aval != null)
             {
                arg = aval;
             }
             else
             {
                // TODO: can't convert, what next?
             }
          }

          return arg;
       }

       /**
        * Resolve the specified legacy method.
        *
        * @param    wa
        *           The context-local state in {@link WorkArea}.
        * @param    isPoly
        *           Flag indicating if the call is for a non-dynamic POLY call.
        * @param    firstType
        *           Flag indicating if we are looking in the first type (this is not for a super-type).
        * @param    isStatic
        *           Flag indicating if we are looking for a static method.
        * @param    type
        *           The legacy class where to look.
        * @param    name
        *           The legacy class name.
        * @param    method
        *           The legacy method name.
        * @param    modes
        *           The argument's modes.  May be <code>null</code>.
        * @param    origArgs
        *           The arguments.
        *
        * @return   The resolved internal entry.
        */
       private static InternalEntry resolveLegacyEntry(WorkArea  wa,
                                                       boolean   isPoly,
                                                       boolean   firstType,
                                                       boolean   isStatic,
                                                       Class<?>  type,
                                                       String    name,
                                                       String    method,
                                                       String    modes,
                                                       Object... origArgs)
       {
          if (type == null || !_BaseObject_.class.isAssignableFrom(type))
          {
             // reached beyond last 'legacy superclass', nowhere else to search
             return null;
          }

          // get all overrides for this legacy method name
          List<InternalEntry> overrides = SourceNameMapper.getOverrides(type, method);

          return resolveLegacyEntry(wa, isPoly, firstType, isStatic, type, name, method, modes, overrides, origArgs);
       }

       /**
        * Given a list of overloaded methods, resolve the target matching the passed arguments.
        *
        * @param    wa
        *           The context-local state in {@link WorkArea}.
        * @param    isPoly
        *           Flag indicating if the call is for a non-dynamic POLY call.
        * @param    firstType
        *           Flag indicating if we are looking in the first type (this is not for a super-type).
        * @param    isStatic
        *           Flag indicating if we are looking for a static method.
        * @param    type
        *           The legacy class where to look.
        * @param    name
        *           The legacy class name.
        * @param    method
        *           The legacy method name.
        * @param    modes
        *           The argument's modes.  May be <code>null</code>.
        * @param    overloads
        *           The list of method overloads.
        * @param    origArgs
        *           The arguments.
        *
        * @return   The resolved internal entry.
        */
       private static InternalEntry resolveLegacyEntry(WorkArea  wa,
                                                       boolean   isPoly,
                                                       boolean   firstType,
                                                       boolean   isStatic,
                                                       Class<?>  type,
                                                       String    name,
                                                       String    method,
                                                       String    modes,
                                                       List<InternalEntry> overloads,
                                                       Object... origArgs)
       {
          prepareArgs(modes, origArgs);

          // (argument, method)
          final Class<?>[][] validDatasetPairs =
          {
             { InputDataSetParameter.class, InputDataSetHandle.class },
             { InputDataSetHandle.class, InputDataSetParameter.class },
             { OutputDataSetParameter.class, OutputDataSetHandle.class },
             { OutputDataSetHandle.class, OutputDataSetParameter.class },
             { InputOutputDataSetParameter.class, InputOutputDataSetHandle.class },
             { InputOutputDataSetHandle.class, InputOutputDataSetParameter.class }
          };

          // (argument, method)
          final Class<?>[][] validTablePairs =
          {
             { InputTableParameter.class, InputTableHandle.class },
             { InputTableHandle.class, InputTableParameter.class },
             { OutputTableParameter.class, OutputTableHandle.class },
             { OutputTableHandle.class, OutputTableParameter.class },
             { InputOutputTableParameter.class, InputOutputTableHandle.class },
             { InputOutputTableHandle.class, InputOutputTableParameter.class }
          };

          // refine the methods based on the signature
          int argSize = origArgs.length;
          Iterator<InternalEntry> iter = overloads.iterator();
          while (iter.hasNext())
          {
             InternalEntry ie = iter.next();
             String pmodes = ie.getParameterModes();
             int psize = ie.getParameterListSize();
             Method m = ie.getMethod();
             Class<?>[] mtypes = m == null ? null : m.getParameterTypes();

             // the signature match is different from validating a procedure/function signature: there
             // is no automatic conversion from one type to another. only widening is possible.
             if (argSize != psize)
             {
                iter.remove();
                continue;
             }

             Object[] args = new Object[origArgs.length];
             System.arraycopy(origArgs, 0, args, 0, argSize);
             for (int i = 0; i < argSize; i++)
             {
                Object arg = args[i];
                Class<?> sigType = arg.getClass();
                if (sigType.isAnonymousClass())
                {
                   sigType = sigType.getSuperclass();
                }

                if (arg instanceof Resolvable)
                {
                   Resolvable r = (Resolvable) arg;
                   sigType = r.getType();
                   arg = r.resolve();
                }

                if (mtypes != null && mtypes[i] == sigType && object.class != sigType)
                {
                   args[i] = arg;
                   continue;
                }

                Parameter param = ie.getParameter(i);
                String ptype = param.getType();
                Class<?> candidateType = BaseDataType.fromTypeName(ptype);

                args[i] = normalizeType(arg, sigType, candidateType);
             }

             boolean ok = true;
             for (int i = 0; i < psize; i++)
             {
                if (modes != null && pmodes.charAt(i) != modes.charAt(i))
                {
                   // modes not matching
                   ok = false;
                   break;
                }

                Object arg = args[i];

                if (arg.getClass() == unknown.class)
                {
                   continue;
                }

                final Class<?> sigType = calculateType(arg);

                if (mtypes != null && mtypes[i] == sigType && object.class != sigType)
                {
                   continue;
                }

                // check dataset/dataset-handle and table/table-handl;e
                Class<?>[][] dsTbPairs = null;
                if (DataSetParameter.class.isAssignableFrom(sigType))
                {
                   dsTbPairs = validDatasetPairs;
                }
                else if (TableParameter.class.isAssignableFrom(sigType))
                {
                   dsTbPairs = validTablePairs;
                }

                if (dsTbPairs != null)
                {
                   boolean found = false;
                   for (Class<?>[] pair : dsTbPairs)
                   {
                      if (pair[0] == sigType && pair[1] == mtypes[i])
                      {
                         found = true;
                         break;
                      }
                   }

                   if (found)
                   {
                      continue;
                   }
                }

                if (arg instanceof BaseDataTypeVariable)
                {
                   arg = ((BaseDataTypeVariable) arg).get();
                }

                Parameter param = ie.getParameter(i);
                String ptype = param.getType();
                Class<?> candidateType = BaseDataType.fromTypeName(ptype);
                char candidateMode = pmodes.charAt(i);

                if (unknown.class == sigType)
                {
                   // TODO: this can be ambiguous...
                   arg = BaseDataType.generateUnknown(candidateType);
                   args[i] = arg;
                }

                if (param.getExtent() != SourceNameMapper.NO_EXTENT)
                {
                   // TODO: match via AbstractParameter and AbstractExtentParameter
                   int candidateExtent = param.getExtent();
                   int sigExtent = arg.getClass().isArray()
                                      ? Array.getLength(arg)
                                      : SourceNameMapper.NO_EXTENT;

                   if (sigExtent == SourceNameMapper.NO_EXTENT)
                   {
                      if ((param.getMode().equals("OUTPUT") && arg instanceof OutputExtentParameter) ||
                          (param.getMode().equals("INPUT-OUTPUT") && arg instanceof InputOutputExtentParameter))
                      {
                         // TODO: array lengths ?? dynamic vs extent
                         continue;
                      }

                      //TODO: Trying to match methods with the wrong extent?
                      // Trying to match methods with given parameters represending the unknown value?
                      ok = false;
                      break;
                   }

                   boolean okExtent = true;
                   // depending on modes
                   if (sigExtent != candidateExtent)
                   {
                      if (candidateMode == InternalEntry.OUTPUT_MODE)
                      {
                         // only dynamic to non-dynamic
                         if (sigExtent != SourceNameMapper.DYNAMIC_EXTENT)
                         {
                            okExtent = false;
                         }
                      }
                      else if (candidateMode == InternalEntry.INPUT_MODE)
                      {
                         // only non-dynamic to dynamic
                         if (sigExtent == SourceNameMapper.DYNAMIC_EXTENT)
                         {
                            okExtent = false;
                         }
                      }
                      else if (candidateMode == InternalEntry.INPUT_OUTPUT_MODE)
                      {
                         // only the same
                         okExtent = false;
                      }
                   }

                   if (!okExtent)
                   {
                      // TODO: 4GL generates error... can't do this easily for dynamic mode.
                      ok = false;
                      break;
                   }
                }

                Class<?> clegacy = null;
                Class<?> slegacy = null;
                boolean untypedSigRef = false;

                if (candidateType == object.class && sigType == object.class)
                {
                   object<?> objectArg = null;
                   if (arg.getClass().isArray())
                   {
                      Object[] array = (Object[]) arg;
                      for (int j = 0; j < array.length; j++)
                      {
                         if (array[j] != null)
                         {
                            objectArg = (object<?>) array[j];
                            if (!((object<?>) array[j]).isUnknown())
                            {
                               break;
                            }
                         }
                      }
                   }
                   else
                   {
                      objectArg = (object<?>) arg;
                   }

                   slegacy = objectArg != null ? objectArg.type() : null;
                   untypedSigRef = slegacy == null && (objectArg == null || objectArg.isUnknown());

                   if (objectArg != null && objectArg._isValid())
                   {
                      untypedSigRef = false;
                      // for POLY invocations, we care about the declared type, and not the actual type
                      if (!isPoly)
                      {
                         slegacy = objectArg.ref.getClass();
                      }
                   }
                   clegacy = param.getQualified() == null ? _BaseObject_.class
                                                          : ObjectOps.resolveClass(param.getQualified());

                   // qualified parameter name might be a super class/interface
                   if (clegacy == null && !untypedSigRef)
                   {
                      clegacy = ObjectOps.getAssignableClass(param.getQualified(),
                                                             (Class<? extends _BaseObject_>) slegacy);
                   }

                   // if not qualified defaults to P.L.O (interface)
                   if (clegacy == null || clegacy.equals(BaseObject.class))
                   {
                      clegacy = _BaseObject_.class;
                   }

                }
                else if (candidateType == jobject.class && sigType == jobject.class)
                {
                   try
                   {
                      clegacy = Class.forName(param.getQualified());

                      object<?> objectArg = null;
                      if (arg.getClass().isArray())
                      {
                         Object[] array = (Object[]) arg;
                         for (int j = 0; j < array.length; j++)
                         {
                            if (array[j] != null)
                            {
                               objectArg = (object<?>) array[j];
                            }
                         }
                      }
                      else
                      {
                         objectArg = (object<?>) arg;
                      }

                      slegacy = objectArg != null ? objectArg.type() : null;
                   }
                   catch (ClassNotFoundException e)
                   {
                      throw new RuntimeException(e);
                   }
                }

                if (candidateType == jobject.class)
                {
                   Class<?> atype = null;
                   try
                   {
                      atype = Class.forName(param.getQualified());
                   }
                   catch (ClassNotFoundException e)
                   {
                      throw new RuntimeException(e);
                   }

                   // if is one of the primitive types, then let it go
                   if (atype == java.lang.Integer.class ||
                       atype == java.lang.Short.class   ||
                       atype == java.lang.Byte.class)
                   {
                      candidateType = integer.class;
                   }
                   else if (atype == java.lang.Double.class ||
                            atype == java.lang.Float.class  ||
                            atype == java.math.BigDecimal.class)
                   {
                      candidateType = decimal.class;
                   }
                   else if (atype == java.lang.Boolean.class)
                   {
                      candidateType = logical.class;
                   }
                   else if (atype == java.lang.String.class)
                   {
                      candidateType = character.class;
                   }
                   else if (atype == java.util.Date.class)
                   {
                      candidateType = date.class;
                   }
                   else if (atype == java.sql.Timestamp.class)
                   {
                      candidateType = datetime.class;
                   }
                }

                // sigType/slegacy are the argument's type
                // candidateType/clegacy are the method parameter's type.
                if (candidateType != sigType)
                {
                   // check the parameter mode at the definition
                   if (candidateMode == InternalEntry.INPUT_MODE)
                   {
                      // widening matches are OK, narrowing are NOT OK
                      if ((int64.class == sigType     && decimal.class == candidateType)     ||
                          (integer.class == sigType   && (decimal.class == candidateType     ||
                                                          int64.class == candidateType))     ||
                          (character.class == sigType && longchar.class == candidateType)    ||
                          (datetime.class == sigType  && datetimetz.class == candidateType)  ||
                          (date.class == sigType      && (datetime.class == candidateType    ||
                                                          datetimetz.class == candidateType)))
                      {
                         // ok
                      }
                      else if (candidateType == object.class && sigType == object.class)
                      {
                         // for INPUT, 4GL allows only the same type or a sub-class
                         // of the parameter's type

                         if (!untypedSigRef && !clegacy.isAssignableFrom(slegacy))
                         {
                            ok = false;
                            break;
                         }

                         // ok
                      }
                      else if (candidateType == jobject.class && sigType == jobject.class)
                      {
                         // for INPUT, 4GL allows only the same type or a sub-class
                         // of the parameter's type

                         if (!untypedSigRef && !clegacy.isAssignableFrom(slegacy))
                         {
                            ok = false;
                            break;
                         }

                         // ok
                      }
                      else
                      {
                         // not a match
                         ok = false;
                         break;
                      }
                   }
                   else if (candidateMode == InternalEntry.OUTPUT_MODE)
                   {
                      // narrowing matches are OK, widening are NOT OK
                      if ((int64.class == sigType      && integer.class == candidateType)    ||
                          (decimal.class == sigType    && (integer.class == candidateType    ||
                                                           int64.class == candidateType))    ||
                          (longchar.class == sigType   && character.class == candidateType)  ||
                          (datetime.class == sigType   && date.class == candidateType)       ||
                          (datetimetz.class == sigType && (date.class == candidateType       ||
                                                           datetime.class == candidateType)))
                      {
                         // ok
                      }
                      else if (candidateType == object.class && sigType == object.class)
                      {
                         // for OUTPUT, 4GL allows only the same type or a super-class
                         // of the parameter's type

                         if (!untypedSigRef && !slegacy.isAssignableFrom(clegacy))
                         {
                            ok = false;
                            break;
                         }

                         // ok
                      }
                      else if (candidateType == jobject.class && sigType == jobject.class)
                      {
                         // for OUTPUT, 4GL allows only the same type or a super-class
                         // of the parameter's type

                         if (!untypedSigRef && !slegacy.isAssignableFrom(clegacy))
                         {
                            ok = false;
                            break;
                         }

                         // ok
                      }
                      else
                      {
                         ok = false;
                         break;
                      }
                   }
                   else if (candidateMode == InternalEntry.INPUT_OUTPUT_MODE)
                   {
                      // INPUT-OUTPUT requires exact match
                      if (candidateType == object.class && sigType == object.class)
                      {
                         if (!untypedSigRef && clegacy != slegacy)
                         {
                            ok = false;
                            break;
                         }
                      }
                      else if (candidateType == jobject.class && sigType == jobject.class)
                      {
                         if (!untypedSigRef && clegacy != slegacy)
                         {
                            ok = false;
                            break;
                         }
                      }

                      // ok
                   }
                }
                else if (candidateType == object.class && sigType == object.class)
                {
                   // check the parameter mode at the definition
                   if (candidateMode == InternalEntry.INPUT_MODE)
                   {
                      // for INPUT, 4GL allows only the same type or a sub-class
                      // of the parameter's type

                      if (!untypedSigRef && !clegacy.isAssignableFrom(slegacy))
                      {
                         ok = false;
                         break;
                      }

                      // ok
                   }
                   else if (candidateMode == InternalEntry.OUTPUT_MODE)
                   {
                      // for OUTPUT, 4GL allows only the same type or a super-class
                      // of the parameter's type

                      if (!untypedSigRef && !slegacy.isAssignableFrom(clegacy))
                      {
                         ok = false;
                         break;
                      }

                      // ok
                   }
                   else if (candidateMode == InternalEntry.INPUT_OUTPUT_MODE)
                   {
                      // INPUT-OUTPUT requires exact match
                      if (!untypedSigRef && clegacy != slegacy)
                      {
                         ok = false;
                         break;
                      }

                      // ok
                   }
                }
             }

             if (!ok)
             {
                // no match
                iter.remove();
             }
          }

          if (overloads.isEmpty())
          {
             return method == null
                      ? null
                      : resolveLegacyEntry(wa, isPoly, false, isStatic, type.getSuperclass(), name, method, modes, origArgs);
          }

          // step 2: resolve defined methods based on the access mode
          // a. if we are not in a legacy class, then only PUBLIC methods are allowed
          // b. if we are in a legacy class and referent's class is not the same as or a super-class
          //    of THIS-OBJECT, then only PUBLIC methods are allowed
          // c. if we are in a legacy class and referent's class is not the same as but is a
          //    super-class of THIS-OBJECT, then only PUBLIC and PROTECTED methods are allowed
          // d. if we are in a legacy class and THIS-OBJECT is the same as referent's class, then
          //   any mode is allowed
          boolean canPrivate = true;
          boolean canProtected = true;
          Object thisRef = wa.pm._thisProcedure();
          Class<?> thisClass = thisRef == null ? null : thisRef.getClass();
          if (thisClass == Object.class)
          {
             // convert it to the type where the static call is performed
             thisClass = ObjectOps.getStaticClass(thisRef);
          }
          // an Object can be a 'root level block' in case of remote instantiation via an appserver
          if (thisClass == null ||
              !_BaseObject_.class.isAssignableFrom(thisClass) ||
              !type.isAssignableFrom(thisClass))
          {
             // cases 2.a. and 2.b.
             canPrivate = false;
             canProtected = false;
          }
          else if (!firstType || type != wa.pm.getExecuting())
          {
             // case 2.c. - private methods can't be invoked if we are not in the same type as the executing type,
             // or if we went in a super-class
             canPrivate = false;
          }

          if (overloads.size() > 1)
          {
             // if we are not in the same class, remove the non-public ones
             if (!(canPrivate || canProtected))
             {
                iter = overloads.iterator();
                while (iter.hasNext())
                {
                   InternalEntry ie = iter.next();

                   Method mthd = ie.getMethod();
                   int mods = mthd.getModifiers();
                   if ((!canPrivate && Modifier.isPrivate(mods)) ||
                       (!canProtected && Modifier.isProtected(mods)))
                   {
                      iter.remove();
                   }
                }
             }

             if (overloads.isEmpty())
             {
                return resolveLegacyEntry(wa, isPoly, false, isStatic, type.getSuperclass(), name, method, modes, origArgs);
             }
          }

          if (overloads.size() > 1)
          {
             // check if there is an exact match
             for (InternalEntry ie : overloads)
             {
                Method m = ie.getMethod();
                Class<?>[] mtypes = m == null ? null : m.getParameterTypes();

                boolean ok = true;
                for (int i = 0; i < argSize; i++)
                {
                   Object arg = origArgs[i];
                   Class<?> sigType = calculateType(arg);
                   if (sigType.isAnonymousClass())
                   {
                      sigType = sigType.getSuperclass();
                   }
                   Class<?> candidateType = mtypes[i];

                   if (candidateType != sigType)
                   {
                      arg = normalizeType(arg, sigType, candidateType);
                      sigType = arg.getClass();
                   }

                   if (candidateType != sigType)
                   {
                      ok = false;
                      break;
                   }

                   int candidateExtent = ie.getParameter(i).getExtent();
                   int sigExtent = arg.getClass().isArray()
                            ? Array.getLength(arg)
                            : SourceNameMapper.NO_EXTENT;

                   if (candidateExtent != sigExtent)
                   {
                      ok = false;
                      break;
                   }
                }

                if (ok)
                {
                   return ie;
                }
             }

             // TODO: before fuzzy searching, we need exact matches in super-classes; if none found, perform
             // fuzzy match

             FuzzyMethodRt fuzzy = new FuzzyMethodRt((Class<? extends _BaseObject_>) type, overloads);

             InternalEntry ie = fuzzy.lookup(thisClass, name, isStatic, modes, origArgs);
             if (ie != null)
             {
                // remove everything except what was found
                overloads.removeIf((t) -> !t.getMethod().equals(ie.getMethod()));
             }
             else
             {
                // complete fail
                return null;
             }
          }

          InternalEntry ie = overloads.get(0);
          Method mthd = ie.getMethod();
          int mods = mthd.getModifiers();

          // step 1: check static vs non-static methods (depending on the call type).
          if (Modifier.isStatic(mods) != isStatic)
          {
             // an incorrect match was found, don't search any more
             String msg = (isStatic
                            ? "Non-static method '%s' of class '%s' may not be invoked as if it were static"
                            : "Static method '%s' may not be invoked via an instance of class '%s'");
             int num = (isStatic ? 15320 : 15319);
             msg = String.format(msg, method, name);

             ErrorManager.recordOrThrowError(num, msg, false, false);
             return null;
          }

          if ((!canPrivate && Modifier.isPrivate(mods)) ||
              (!canProtected && Modifier.isProtected(mods)))
          {
             // an incorrect match was found, don't search any more

             // TODO: error

             return null;
          }

          Class<?>[] mtypes = ie.getMethod().getParameterTypes();
          for (int i = 0; i < origArgs.length; i++)
          {
             if (mtypes[i].isAssignableFrom(origArgs[i].getClass()))
             {
                int expectedExtent = ie.getParameter(i).getExtent();

                if (expectedExtent != SourceNameMapper.NO_EXTENT)
                {
                   if (!origArgs[i].getClass().isArray())
                   {
                      // Match on wrong method
                      return null;
                   }

                   // If the coresponding parameter has dynamic extent type or
                   // the extents match then it's ok
                   if (!(expectedExtent == 0 || expectedExtent == Array.getLength(origArgs[i])))
                   {
                      // Match on wrong method
                      return null;
                   }
                   else
                   {
                      continue;
                   }
                }

                continue;
             }

             if (mtypes[i] == InputDataSetHandle.class)
             {
                origArgs[i] = new InputDataSetHandle((InputDataSetParameter) origArgs[i]);
             }
             else if (mtypes[i] == OutputDataSetHandle.class)
             {
                origArgs[i] = new OutputDataSetHandle((OutputDataSetParameter) origArgs[i]);
             }
             else if (mtypes[i] == InputOutputDataSetHandle.class)
             {
                origArgs[i] = new InputOutputDataSetHandle((InputOutputDataSetParameter) origArgs[i]);
             }
             else if (mtypes[i] == InputDataSetParameter.class)
             {
                origArgs[i] = new InputDataSetParameter((InputDataSetHandle) origArgs[i]);
             }
             else if (mtypes[i] == OutputDataSetParameter.class)
             {
                origArgs[i] = new OutputDataSetParameter((OutputDataSetHandle) origArgs[i]);
             }
             else if (mtypes[i] == InputOutputDataSetParameter.class)
             {
                origArgs[i] = new InputOutputDataSetParameter((InputOutputDataSetHandle) origArgs[i]);
             }

             // TABLE/TABLE-HANDLE
             else if (mtypes[i] == InputTableHandle.class)
             {
                origArgs[i] = new InputTableHandle((InputTableParameter) origArgs[i]);
             }
             else if (mtypes[i] == OutputTableHandle.class)
             {
                origArgs[i] = new OutputTableHandle((OutputTableParameter) origArgs[i]);
             }
             else if (mtypes[i] == InputOutputTableHandle.class)
             {
                origArgs[i] = new InputOutputTableHandle((InputOutputTableParameter) origArgs[i]);
             }
             else if (mtypes[i] == InputTableParameter.class)
             {
                origArgs[i] = new InputTableParameter((InputTableHandle) origArgs[i]);
             }
             else if (mtypes[i] == OutputTableParameter.class)
             {
                origArgs[i] = new OutputTableParameter((OutputTableHandle) origArgs[i]);
             }
             else if (mtypes[i] == InputOutputTableParameter.class)
             {
                origArgs[i] = new InputOutputTableParameter((InputOutputTableHandle) origArgs[i]);
             }

             if (!mtypes[i].isAssignableFrom(origArgs[i].getClass()))
             {
                if (origArgs[i] instanceof BaseDataType && BaseDataType.class.isAssignableFrom(mtypes[i]))
                {
                   // last resort - try to convert
                   try
                   {
                      Constructor<?> ctor = mtypes[i].getConstructor(BaseDataType.class);
                      origArgs[i] = ctor.newInstance(origArgs[i]);
                   }
                   catch (ReflectiveOperationException exc)
                   {
                      exc.printStackTrace();
                      // TODO: what ?
                   }
                }
                else
                {
                   // TODO: what to do?
                }
             }
          }

          return ie;
       }

       /**
        * Given an instance passed as an argument to a method call, calculate its actual type, for cases such when
        * the value is wrapped in field or property reference, etc.
        *
        * @param    arg
        *           The argument.
        *
        * @return   The actual type.
        */
       static Class<?> calculateType(final Object arg)
       {
          Class<?> sigType = arg.getClass();
          if (sigType.isAnonymousClass())
          {
             sigType = sigType.getSuperclass();
          }

          if (arg instanceof BaseDataTypeVariable)
          {
             sigType = arg.getClass();
          }
          else if (arg instanceof FieldReference)
          {
             sigType = ((FieldReference) arg).getType();
          }
          else if (arg instanceof PropertyReference)
          {
             sigType = ((PropertyReference) arg).getType();
          }
          else if (arg instanceof ObjectVar)
          {
             sigType = object.class;
          }
          else if (arg instanceof BaseDataTypeConstant)
          {
             // resolve the super-class type for this constant.
             sigType = sigType.getSuperclass();
          }

          if (unknown.class == sigType)
          {
             sigType = null;
          }

          if (sigType == null)
          {
             // TODO: BUFFER or TEMP-TABLE, how to match...
          }

          if (sigType.isArray())
          {
             sigType = sigType.getComponentType();
          }

          if (sigType == jobject.class)
          {
             Class<?> atype = ((jobject) arg).type();
             // if is one of the primitive types, then let it go
             if (atype == java.lang.Integer.class ||
                 atype == java.lang.Short.class   ||
                 atype == java.lang.Byte.class)
             {
                sigType = integer.class;
             }
             else if (atype == java.lang.Double.class ||
                      atype == java.lang.Float.class  ||
                      atype == java.math.BigDecimal.class)
             {
                sigType = decimal.class;
             }
             else if (atype == java.lang.Boolean.class)
             {
                sigType = logical.class;
             }
             else if (atype == java.lang.String.class)
             {
                sigType = character.class;
             }
             else if (atype == java.util.Date.class)
             {
                sigType = date.class;
             }
             else if (atype == java.sql.Timestamp.class)
             {
                sigType = datetime.class;
             }
          }

          return sigType;
       }

       /**
        * The implementation of all RUN ... PERSISTENT calls or appserver calls for an external
        * procedure. If the <code>exports</code> parameter is not null, then the ON SERVER clause is
        * in effect; this parameter can be not-null only on the remote, appserver, side.
        *
        * @param    name
        *           The legacy 4GL name for the function or procedure.
        * @param    persistent
        *           Flag indicating that this call needs to be persistent.
        * @param    h
        *           The handle where to save the persistent procedure.
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    exports
        *           The list of allowed external procedures to be invoked with a RUN ... ON SERVER
        *           statement.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The arguments.
        */
       public static void invokeExternalProcedure(character name,
                                                  boolean   persistent,
                                                  handle    h,
                                                  boolean   transactionDistinct,
                                                  character exports,
                                                  String    modes,
                                                  Object... args)
       {
          invokeExternalProcedure(name,
                                  persistent,
                                  false,
                                  false,
                                  h,
                                  transactionDistinct,
                                  exports,
                                  modes,
                                  new ArrayArgumentResolver(args));
       }

       /**
        * The implementation of all RUN ... PERSISTENT calls or appserver calls for an external
        * procedure. If the <code>exports</code> parameter is not null, then the ON SERVER clause is
        * in effect; this parameter can be not-null only on the remote, appserver, side.
        *
        * @param    name
        *           The legacy 4GL name for the function or procedure.
        * @param    persistent
        *           Flag indicating that this call needs to be persistent.
        * @param    singleton
        *           Flag indicating that this call needs to be ran as singleton.
        * @param    singleRun
        *           Flag indicating that this call needs to be ran as single-run.
        * @param    h
        *           The handle where to save the persistent procedure.
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    exports
        *           The list of allowed external procedures to be invoked with a RUN ... ON SERVER
        *           statement.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The arguments.
        */
       static void invokeExternalProcedure(character name,
                                           boolean   persistent,
                                           boolean   singleton,
                                           boolean   singleRun,
                                           handle    h,
                                           boolean   transactionDistinct,
                                           character exports,
                                           String    modes,
                                           Object... args)
       {
          invokeExternalProcedure(name,
                                  persistent,
                                  singleton,
                                  singleRun,
                                  h,
                                  transactionDistinct,
                                  exports,
                                  modes,
                                  new ArrayArgumentResolver(args));
       }

       /**
        * The implementation of all RUN ... PERSISTENT calls or appserver calls for an external
        * procedure. If the <code>exports</code> parameter is not null, then the ON SERVER clause is
        * in effect; this parameter can be not-null only on the remote, appserver, side.
        *
        * @param    name
        *           The legacy 4GL name for the function or procedure.
        * @param    persistent
        *           Flag indicating that this call needs to be persistent.
        * @param    singleton
        *           Flag indicating that this call needs to be ran as singleton.
        * @param    singleRun
        *           Flag indicating that this call needs to be ran as single-run.
        * @param    h
        *           The handle where to save the persistent procedure.
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    exports
        *           The list of allowed external procedures to be invoked with a RUN ... ON SERVER
        *           statement.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    argResolver
        *           The argument resolver.
        */
       static void invokeExternalProcedure(character        name,
                                           boolean          persistent,
                                           boolean          singleton,
                                           boolean          singleRun,
                                           handle           h,
                                           boolean          transactionDistinct,
                                           character        exports,
                                           String           modes,
                                           ArgumentResolver argResolver)
       {
          WorkArea wa = work.obtain();

          String pname = name.toStringMessage();

          InternalEntryCaller caller = null;
          Exception deferred = null;
          try
          {
             caller = wa.externalResolver.resolve(null, pname, false, false, exports,
                                                             persistent);

             if (caller == null)
             {
                if (!wa.em.isPending())
                {
                   // not a pending error, it means the program was not found
                   invokeFailure(pname);
                }

                return;
             }

             Object[] args = argResolver.resolve(caller, pname, false);

             if (!validArguments(wa, pname, caller, false, modes, args))
             {
                return;
             }

             Object instance = caller.getCallerInstance();

             boolean error = false;
             try
             {
                Runnable push = () ->
                {
                   Class<?> def = instance.getClass();
                   wa.pm.pushCalleeInfo(def, false, instance, instance,
                                        ProcedureManager.EXTERNAL_PROGRAM, pname,
                                        false, persistent, singleton, singleRun);
                };
                caller.setPushWorker(push);
                boolean delaySet = h != null && h.delayPersistentSet();
                if (h != null && !delaySet)
                {
                   // the procedure instance must be available to the legacy code, for example when
                   // the invoked caller calls back the call site
                   h.set(new ExternalProgramWrapper(instance));
                }

                caller.invoke(modes, args);

                if (h != null && delaySet)
                {
                   // the handle should be set to its old value while the called legacy code is executed,
                   // the resource change happens after the called procedure ends
                   h.set(new ExternalProgramWrapper(instance));
                }
             }
             catch (Exception e)
             {
                error = true;

                throw e;
             }
             finally
             {
                if (persistent)
                {
                   if ((error || wa.em.isPending()))
                   {
                      // any exceptions here cause the persistent procedure to no longer
                      // be available
                      if (h != null)
                      {
                         h.setUnknown();
                      }
                      wa.pm.delete(instance, false);
                   }
                   else if (h != null)
                   {
                      // if the handle is not also passed as an OUTPUT argument, then assign it
                      boolean asArg = false;
                      String argModes = caller.getParameterModes(false);
                      int i = 0;
                      for (Object arg : args)
                      {
                         if (argModes == null || i >= argModes.length())
                         {
                            break;
                         }

                         char mode = argModes.charAt(i);

                         if (arg == h && mode != 'I')
                         {
                            // not an input mode, set it to unknown
                            asArg = true;
                            break;
                         }

                         i++;
                      }

                      if (asArg)
                      {
                         h.setUnknown();
                      }
                   }
                }
             }
          }

          // allow Progress compatible exceptions to flow through
          catch (ConditionException | RetryUnwindException ce)
          {
             throw ce;
          }

          // handle reflection exceptions
          catch (NoSuchMethodException nsm)
          {
             invokeFailure(caller.getMethodName());
             return;
          }

          catch (InvocationTargetException exc)
          {
             deferred = checkInvocationException(exc);
          }

          // all other cases
          catch (Exception exp)
          {
             deferred = exp;
          }

          if (deferred != null)
          {
             invokeError(caller, pname, deferred);
          }
       }

       /**
        * Resolve the program handle of an internal procedure <code>name</code>.
        * <p>
        * The method will try to find the internal procedure <code>name</code> in program
        * <code>h</code>. If <code>name</code> is not found in <code>h</code>, super procedures of
        * <code>h</code> are searched. If <code>name</code> is not found in super procedures of
        * <code>h</code> <code>SESSION</code> super procedures are searched. If <code>name</code>
        * is not found, <code>null</code> is returned.
        *
        * @param    h
        *           The program handle.
        * @param    name
        *           The internal procedure name.
        *
        * @return   Program handle or <code>null</code> if not found.
        */
       static handle resolveProgramForProcedure(handle h, String name)
       {
          WorkArea wa = work.obtain();
          InternalEntryCaller caller = null;
          ArrayList<Resolver> inHandleResolvers = wa.inHandleResolvers;
          for (int i = 0; i < inHandleResolvers.size(); i++)
          {
             Resolver r = inHandleResolvers.get(i);
             caller = r.resolve(h, name, false, false, null);
             if (caller != null)
             {
                return caller.phandle;
             }
          }
          return null;
       }

       /**
        * Given a deferred program configuration, resolve its singleton or single-run instance,
        * depending on how the original RUN statement (or REST service) was configured.  This will
        * either create a new class (if no singleton exists at this time or the mode is single-run),
        * or reuse an instance via {@link ProcedureManager#getSingleton(Class)}.
        *
        * @param    deferred
        *           The deferred program configuration.
        * @param    isClass
        *           Flag indicating if this instance must be a legacy 4GL class.
        *
        * @return   A {@link handle} referencing either a {@link ObjectResource} or an
        *           {@link ExternalProgramWrapper}.
        */
       public static handle resolveInstance(DeferredProgram deferred, boolean isClass)
       {
          if (isClass)
          {
             Class<? extends _BaseObject_> cls = ObjectOps.resolveClass(deferred.getName());
             if (cls == null)
             {
                invokeFailure(deferred.getName());
                return null;
             }

             _BaseObject_ instance = deferred.isSingleRun()
                                        ? null
                                        : (_BaseObject_) ProcedureManager.getSingleton(cls);
             if (instance == null)
             {
                object<?> obj = new ObjectVar<>(cls);
                obj.assign(ObjectOps.newInstance(cls));

                if (!obj._isValid())
                {
                   invokeFailure(deferred.getName());
                   return null;
                }

                instance = obj.ref;

                if (deferred.isSingleton())
                {
                   ProcedureManager.addSingleton(cls, instance);
                }
             }

             return new handle(ObjectOps.asResource(instance));
          }

          String extProgName = SourceNameMapper.normalizeLegacyName(deferred.getName());
          String className = SourceNameMapper.convertNameToClass(extProgName);
          if (className == null)
          {
             invokeFailure(deferred.getName());
             return null;
          }

          Class<?> cls = ExternalProcedureCaller.classCache.get(className);
          if (cls == null)
          {
             try
             {
                cls = Class.forName(className);
                ExternalProcedureCaller.classCache.putIfAbsent(className, cls);
             }
             catch (ClassNotFoundException e)
             {
                cls = null;
                invokeFailure(deferred.getName());
             }
          }

          if (cls == null)
          {
             return null;
          }

          handle hres = new handle();

          if (deferred.isSingleRun())
          {
             invokeExternalProcedure(new character(deferred.getName()),
                                     true, false, true, hres,
                                     false, null, null);
          }
          else
          {
             Object instance = ProcedureManager.getSingleton(cls);

             if (instance == null)
             {
                invokeExternalProcedure(new character(deferred.getName()),
                                        true, true, false, hres,
                                        false, null, null);
             }
             else
             {
                hres.assign(new ExternalProgramWrapper(instance));
             }
          }
          return hres;
       }

       /**
        * Convert BaseDataType value to instance of given class. If value is <code>null</code>,
        * returns <code>null</code>. If value is <code>unknown value</code>, creates a new instance
        * of the given type that represents the <code>unknown value</code>, otherwise returns value
        * casted to given type.
        *
        * @param    cls
        *           Class.
        * @param    value
        *           Value.
        *
        * @return   If value is <code>null</code>, returns <code>null</code>. If value is
        *           <code>unknown value</code>, creates a new instance of the given type that
        *           represents the <code>unknown value</code>, otherwise returns value casted to
        *           given type.
        *
        * @throws   IllegalStateException
        *           If value is not compatible with given class.
        */
       private static <T extends BaseDataType> T convertValue(Class<T> cls, BaseDataType value)
       {
          if (value == null)
          {
             return null;
          }

          // if we need to ensure that the value is of a specific data type, it means that
          // we shouldn't use wrapper BDT proxy anymore and fire the implicit conversion
          value = value.fallback();

          if (value instanceof unknown)
          {
             value = BaseDataType.generateUnknown(cls);
          }

          if (!isValueCompatible(value, cls))
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                LOG.warning("Value of type " + value.getClass().getName() +
                            " is not compatible with expected return type " + cls.getName());
             }
          }

          return (T) value;
       }

       /**
        * Test whether a value can be ultimately converted to a specific type/class.
        *
        * @param   value
        *          The value to be tested.
        * @param   cls
        *          The expected type of the parameter. A subclass of {@code BaseDataType}.
        * @param   <T>
        *          The parameter type - a subclass of {@code BaseDataType}.
        *
        * @return  {@code true} if the value is compatible with the specified parameter type.
        */
       private static <T extends BaseDataType> boolean isValueCompatible(BaseDataType value, Class<T> cls)
       {
          // basic test which assures de facto compatibility
          // value can be a proxy, so ensure that the cls is the one assignable from value
          if (cls.isAssignableFrom(value.getClass()))
          {
             return true;
          }

          // all integer values can be assigned one to another because the assignment is performed using
          // longValue() method. In a particular case, this avoid extra messages when the value is an object of
          // an anonymous class overriding isAssignDirect() method.
          if (int64.class.isAssignableFrom(cls) && value instanceof int64)
          {
             return true;
          }

          // IN SUPER may be defined with i.e. CHARACTER but actual definition may be LONGCHAR
          // TODO: rewrite this condition as: 'Text.class.isAssignableFrom(cls) && value instanceof Text' ?
          if (longchar.class == cls && value instanceof character ||
              character.class == cls && value instanceof longchar)
          {
             return true;
          }

          // TODO: add other compatible types here

          return false;
       }

       /**
        * Execute an async procedure call.
        *
        * @param    request
        *           The request which will execute the procedure call.
        * @param    hServer
        *           The server used to execute this async call.
        * @param    asyncHandle
        *           The handle to the asynchronous request.
        * @param    pHandle
        *           The handle to which the procedure belongs, if it is RUN ... IN handle statement.
        */
       private static void invokeAsyncImpl(AsyncCall request,
                                           handle    hServer,
                                           handle    asyncHandle,
                                           handle    pHandle)
       {
          // expose the async resource to the caller
          if (asyncHandle != null)
          {
             asyncHandle.assign(request.asyncReq);
          }

          boolean isServer = (hServer.getResource() instanceof ServerImpl);
          ServerImpl server = isServer ? (ServerImpl) hServer.getResource() : null;

          // register for notifications the server and the procedure handle
          if (isServer)
          {
             request.asyncReq.registerListener(server);
          }
          // check the procedure handle
          if (pHandle._isValid() && pHandle.getResource() instanceof AsyncRequestListener)
          {
             request.asyncReq.registerListener((AsyncRequestListener) pHandle.getResource());
          }

          // notify all listeners an async call is in effect
          request.asyncReq.notifyStart();

          // perform invocation. everything is executed on the Conversation thread until we reach the actual
          // appserver invocation - when that is reached, we create an 'asyncAction' which will be ran on a
          // separate thread
          request.asyncReq.execute(isServer && server._connected());
       }

       /**
        * The implementation for all function calls.
        *
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    h
        *           The handle to which the function belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        * @param    superCall
        *           <code>true</code> if this is a SUPER() call.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default or on error.
        */
       private static BaseDataType invokeFunctionImpl(character name,
                                                      handle    h,
                                                      boolean   dynamicFunction,
                                                      boolean   superCall,
                                                      String    modes,
                                                      Object... args)
       {
          // TODO: add support for functions returning an extent
          return invokeImpl((handle) null, h, name,
                            true, dynamicFunction, superCall,
                            false, modes, args);
       }

       /**
        * The implementation for RUN SUPER and RUN ... IN handle calls.
        *
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    h
        *           The handle to which the procedure belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    superCall
        *           <code>true</code> if this is a RUN SUPER call.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The functions's arguments.
        */
       private static void invokeInImpl(character name,
                                        handle    h,
                                        boolean   superCall,
                                        String    modes,
                                        Object... args)
       {
          invokeImpl((handle) null, h, name, false, false, superCall, false, modes, args);
       }

       /**
        * The implementation of function or procedure calls which do not use the
        * IN handle clause.
        *
        * @param    server
        *           The target appserver, to which this call will be dispatched.
        *           Is <code>null</code> if the RUN statement doesn't have the ON SERVER clause.
        * @param    name
        *           The legacy 4GL name for the function or procedure.
        * @param    h
        *           The handle to which the procedure belongs. If <code>null</code> defaults to
        *           THIS-PROCEDURE. If this is a proxy procedure, the call is dispatched to the
        *           procedure's server.
        * @param    function
        *           <code>true</code> if this is a function call.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        * @param    superCall
        *           <code>true</code> if this is a RUN SUPER or SUPER() call.
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default, on error or for
        *           procedures.
        */
       private static BaseDataType invokeImpl(handle    server,
                                              handle    h,
                                              character name,
                                              boolean   function,
                                              boolean   dynamicFunction,
                                              boolean   superCall,
                                              boolean   transactionDistinct,
                                              String    modes,
                                              Object... args)
       {
          return invokeImpl(null, server, h, name,
                            function, dynamicFunction, superCall, transactionDistinct,
                            modes, args);
       }

       /**
        * The implementation of function or procedure calls which do not use the
        * IN handle clause.
        *
        * @param    asyncReq
        *           When not-null, an {@link AsyncRequestImpl} instance for the associated async
        *           request.
        * @param    server
        *           The target appserver, to which this call will be dispatched.
        *           Is <code>null</code> if the RUN statement doesn't have the ON SERVER clause.
        * @param    name
        *           The legacy 4GL name for the function or procedure.
        * @param    h
        *           The handle to which the procedure belongs. If <code>null</code> defaults to
        *           THIS-PROCEDURE. If this is a proxy procedure, the call is dispatched to the
        *           procedure's server.
        * @param    function
        *           <code>true</code> if this is a function call.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        * @param    superCall
        *           <code>true</code> if this is a RUN SUPER or SUPER() call.
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The arguments.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default, on error or for
        *           procedures.
        */
       private static BaseDataType invokeImpl(AsyncRequestImpl asyncReq,
                                              handle           server,
                                              handle           h,
                                              character        name,
                                              boolean          function,
                                              boolean          dynamicFunction,
                                              boolean          superCall,
                                              boolean          transactionDistinct,
                                              String           modes,
                                              Object...        args)
       {
          // save the original received server instance.
          final handle origServer = server;
          DeferredProgram deferred = null;

          if (server == null && h != null && h._isValid())
          {
             WrappedResource res = h.getResource();

             // get the server from the handle
             if (ProcedureManager.isProxy(h))
             {
                server = new handle(((ProxyProcedureWrapper) res).getServer());
             }
             else if (res instanceof PortTypeWrapper)
             {
                server = new handle(((PortTypeWrapper) res).getServer());
             }
             else if (res instanceof DeferredProgramWrapper)
             {
                deferred = (DeferredProgram) ((DeferredProgramWrapper) res).get();
                server = deferred.getServer() == null ? null : new handle(deferred.getServer());
             }
          }

          // if the server is the SESSION handle, then this is a local call
          if (server == null || CompareOps._isEqual(server, SessionUtils.asHandle()))
          {
             if (transactionDistinct)
             {
                invalidHandle(name);
                return unknown.UNKNOWN;
             }

             handle htarget = h;

             if (deferred != null)
             {
                htarget = resolveInstance(deferred, false);

                if (htarget == null || htarget.isUnknown())
                {
                   return unknown.UNKNOWN;
                }
             }

             try
             {
                // this is a normal call, invoke immediately
                return invoke(htarget, name, function, dynamicFunction, superCall, false, modes, args);
             }
             finally
             {
                if (deferred != null && deferred.isSingleRun())
                {
                   ProcedureManager.deleteInstance(htarget, false);
                }
             }
          }
          else
          {
             if (!validServer(server, h, name))
             {
                return unknown.UNKNOWN;
             }

             ServerImpl s = (ServerImpl) server.get();
             boolean isWebService = s.isWebService();

             if (isWebService && origServer != null && h == null)
             {
                // the target portHandle must always be specified in case of
                // RUN port-type ON SERVER h-web-server statements.
                // if is missing, the RUN statement is a no-op

                return unknown.UNKNOWN;
             }

             BaseDataType result = unknown.UNKNOWN;
             try
             {
                // if sync mode, no async requests must be running
                if (asyncReq == null && s.hasAsyncRequests())
                {
                   return unknown.UNKNOWN;
                }

                // invoke appServer
                ServerHelper helper = s.getServerHelper();
                InvokeConfig cfg = new InvokeConfig(name);
                cfg.setAsynchronous(asyncReq != null)
                   .setFunction(function)
                   .setDynamicFunction(dynamicFunction)
                   .setSuperCall(superCall)
                   .setTransactionDistinct(transactionDistinct)
                   .setModes(modes)
                   .setArguments(args)
                   .setInHandle(h);

                if (!isWebService)
                {
                   if (deferred != null)
                   {
                      cfg.setSingleRun(deferred.isSingleRun())
                         .setSingleton(deferred.isSingleton());
                   }

                   // send the server:REQUEST
                   cfg.storeRequest(s.getRequestInfo());

                   if (asyncReq != null)
                   {
                      asyncReq.getRequestInfo().ref.initialize(s.getRequestInfo().ref, true);
                   }

                   // if ON SERVER is used (h is null), only external procedures can be invoked.
                   // this is managed by the remote side.
                   result = wrapAppServerCall(() -> helper.invoke(0, asyncReq, cfg), origServer);

                   if (!function && asyncReq == null && result != null)
                   {
                      // TODO: the return-value is set for web-service requests only if the web-service
                      // is configured and exported via Progress.

                      setReturnValue((character) result);
                      result = unknown.UNKNOWN;
                   }
                }
                else
                {
                   // only web service requests
                   result = helper.invoke(0, asyncReq, name, h,
                                          function, dynamicFunction, superCall,
                                          transactionDistinct, modes, args);
                }
             }
             catch (ConditionException e)
             {
                if ((e instanceof StopConditionException && server != null) ||
                      isWebService ||
                      !(e.getCause() instanceof NumberedException))
                {
                   throw e;
                }
                ErrorManager.recordOrThrowError((NumberedException) e.getCause());
             }
             catch (Exception e)
             {
                LOG.severe("Exception thrown by executing the appserver invoke callable.", e);
             }

             return result;
          }
       }

       /**
        * The implementation of all function and procedure calls.
        *
        * @param    resolvers
        *           The {@link Resolver} instances to use to resolve the internal
        *           entry to a Java method, based on the passed parameters.
        * @param    h
        *           The handle to which the procedure belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    cname
        *           The legacy 4GL name for the function.
        * @param    function
        *           <code>true</code> if this is a function call.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        * @param    superCall
        *           <code>true</code> if this is a RUN SUPER or SUPER() call.
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    asThread
        *           This marks if "AS-THREAD" option is specified with the RUN stmt.
        * @param    ht
        *           When non-null, it will hold a reference to a resource which manages the new
        *           thread.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    argResolver
        *           The argument resolver.
        *
        * @return   The return value from the block (or its nested blocks) which
        *           is the <code>unknown value</code> by default, on error or for
        *           procedures.
        */
       private static BaseDataType invokeImpl(List<Resolver>   resolvers,
                                              handle           h,
                                              character        cname,
                                              boolean          function,
                                              boolean          dynamicFunction,
                                              boolean          superCall,
                                              boolean          transactionDistinct,
                                              boolean          asThread,
                                              handle           ht,
                                              String           modes,
                                              ArgumentResolver argResolver)
       {
          WorkArea wa = work.obtain();

          BaseDataType result = unknown.UNKNOWN;

          String name = cname.toStringMessage();

          // handle validation is done first
          boolean explicit = h != null;
          if (!explicit)
          {
             h = wa.pm.thisProcedure();
          }

          // this is a special case only for functions. if the handle has been
          // deleted and it still contains the reference to the procedure, then
          // it defaults to this-procedure
          if (function && !h._isValid() && ProcedureManager.isProcedure(h))
          {
             h = wa.pm.thisProcedure();
          }

          if (!validHandle(wa, h, name, explicit, function, dynamicFunction))
          {
             return result;
          }

          if (cname.isUnknown() || (!explicit && cname.toStringMessage().length() == 0))
          {
             String err = null;
             int errid = -1;

             if (function && dynamicFunction)
             {
                err = "Could not invoke dynamic user-defined function";
                errid = 5640;
             }

             if (err == null)
             {
                if (cname.isUnknown())
                {
                   invokeFailure(name);
                   return result;
                }
                else
                {
                   err = "No PROGRESS program specified for RUN or COMPILE";
                   errid = 467;
                }
             }

             ErrorManager.recordOrThrowError(errid, err);
             return result;
          }

          if (explicit && cname.toStringMessage().length() == 0)
          {
             String pname = wa.pm.getRelativeName(h.get());
             String msg = "Procedure " + pname + " has no entry point for " + cname.toStringMessage();
             ErrorManager.recordOrThrowError(6456, msg, false);
             return result;
          }

          Exception deferred = null;
          InternalEntryCaller caller = null;
          try
          {
             for (Resolver r : resolvers)
             {
                caller = r.resolve(h, name, function, superCall, null);

                if (caller instanceof MapToNotFound)
                {
                   // if a map-to was searched and not explicitly found, search in super-procedures
                   name = caller.getInternalEntryName();
                   caller = null;
                }

                if (caller != null)
                {
                   break;
                }

                if (r instanceof ExternalProgramResolver && wa.em.isPending())
                {
                   // instantiation failed (i.e. due to a SHARED problem), do nothing
                   return result;
                }
             }

             if (caller == null)
             {
                String pname = wa.pm.getRelativeName(h.get());
                if (function)
                {
                   // if the function is defined but could not be resolved,
                   // then show error
                   if (dynamicFunction)
                   {
                      final String msg =
                         "User-defined function '%s' invoked  dynamically but could not be found";
                      if (SessionUtils._isRemote() && wa.tm.isGlobalBlock())
                      {
                         ErrorManager.recordOrThrowError(5639, String.format(msg, name), false);
                      }
                      else
                      {
                         ErrorManager.recordOrShowError(5639, String.format(msg, name),
                                                        false, false, false);
                      }
                   }
                   else
                   {
                      if (SessionUtils._isRemote() && wa.tm.isGlobalBlock())
                      {
                         final String msg =
                            "User-defined function '%s' invoked but could not be found";
                         ErrorManager.recordOrThrowError(6445, String.format(msg, name), false);
                      }
                      else
                      {
                         final String msg = "External user defined function '%s' is not defined in " +
                                            "the procedure context given";
                         ErrorManager.recordOrShowError(2779, String.format(msg, name),
                                                        false, false, false);
                      }
                   }

                   return result;
                }
                else if (explicit)
                {
                   final String msg = "Procedure " + pname + " has no entry point for " + name;
                   ErrorManager.recordOrThrowError(6456, msg, false);
                   return result;
                }

                invokeFailure(name);
                return result;
             }

             if (function)
             {
                // caller.phandle is null in cases of external programs
                handle callerHandle = caller != null ? caller.getPhandle() : null;
                boolean sameHandles = callerHandle != null &&
                                      explicit             &&
                                      h != null            &&
                                      CompareOps._isEqual(callerHandle, h);
                if (callerHandle != null && !sameHandles)
                {
                   // TODO: this should recursively call invokeImpl(server, handle, ....), as the
                   // new handle might be for a remote procedure

                   // handle has switched, validate the new one. dynamic-function
                   // needs to be false here.
                   if (!validHandle(wa, callerHandle, name, explicit, function, false))
                   {
                      return result;
                   }
                }

                handle funcProg = callerHandle != null ? callerHandle : h;

                // this recursive call is needed only for functions, as only they
                // can be set as IN handle.
                String progName = ProcedureManager.getAbsoluteName(funcProg.get());
                if (!sameHandles && SourceNameMapper.isInHandle(progName, caller.ie, name, function))
                {
                   try
                   {
                      return invokeImpl(resolvers,
                                        funcProg,
                                        cname,
                                        function,
                                        dynamicFunction,
                                        superCall,
                                        transactionDistinct,
                                        asThread,
                                        ht,
                                        modes,
                                        argResolver);
                   }
                   catch (StackOverflowError t)
                   {
                      final String msg = "Giving up search for external user defined function " +
                                         "'%s' after 64 indirections";
                      String err = String.format(msg, name);
                      int num = 2757;

                      ErrorManager.displayError(num, err, false);

                      return result;
                   }
                }
             }

             Object[] args = argResolver.resolve(caller, name, function);

             if (!validArguments(wa, name, caller, function, modes, args))
             {
                return result;
             }

             handle hTarget;
             boolean external = (caller instanceof ExternalProcedureCaller);

             final String iename;
             final String pname;
             if (external)
             {
                // we are invoking an external program. the target-procedure will be the
                // to-be-invoked procedure.
                ExternalProgramWrapper extProg = new ExternalProgramWrapper(caller.callerInstance);
                hTarget = new handle(extProg);

                iename = ProcedureManager.EXTERNAL_PROGRAM;
                pname = name;
             }
             else
             {
                // use the original program handle as the target, consider
                // the case when the internal procedure is resolved from
                // the SUPER program
                hTarget = new handle(h);
                iename = name;
                pname = null;

                if (TransactionImpl.isTxInitProcedure(caller.callerInstance) &&
                    !(TransactionImpl.isOpenByTxInitProc()      ||
                      wa.tm.getNestingLevel() == 2 ||
                      TransactionImpl.isTxInitProcedure(wa.pm._thisProcedure())))
                {
                   // if the target is the TX:TRANS-INIT-PROCEDURE, then we can call this only if we
                   // are TX:IS-OPEN (by TRANS-INIT-PROCEDURE) or THIS-PROCEDURE is
                   // TX:TRANS-INIT-PROCEDURE
                   ErrorManager.recordOrThrowError(7337,
                                                   "Must run methods or internal procedures of a " +
                                                   "TRANSACTION-MODE AUTOMATIC .p remotely as top " +
                                                   "level procedures on an application server",
                                                   false);
                   return result;
                }
             }

             Object callerInstance = caller.callerInstance;
             Object[] res = new Object[1];
             Exception[] exc = new Exception[1];
             InternalEntryCaller[] acaller = new InternalEntryCaller[1];
             acaller[0] = caller;

             Runnable worker = () ->
             {
                WorkArea localWa = work.obtain();
                acaller[0].wa = localWa;

                Runnable push = () ->
                {
                   // need to use my own workarea instance
                   Class<?> def = callerInstance.getClass();
                   localWa.pm.pushCalleeInfo(def, superCall, hTarget.get(), callerInstance,
                                             iename, pname, function, false, dynamicFunction);
                   if (acaller[0].ie != null)
                   {
                      localWa.pm.peekCalleeInfo().setLegacyName(acaller[0].ie.pname);
                   }
                };
                acaller[0].setPushWorker(push);

                try
                {
                   res[0] = acaller[0].invoke(modes, args);
                }
                catch (Exception t)
                {
                   exc[0] = t;
                }
             };

             if (asThread)
             {
                // exceptions or returned value are of no use in the caller's thread
                runAsThread(ht, worker);
             }
             else
             {
                worker.run();
                if (exc[0] != null)
                {
                   if (exc[0] instanceof ConditionException)
                   {
                      throw (ConditionException) exc[0];
                   }
                   else if (exc[0] instanceof RetryUnwindException)
                   {
                      throw (RetryUnwindException) exc[0];
                   }
                   else if (exc[0] instanceof NoSuchMethodException)
                   {
                      throw (NoSuchMethodException) exc[0];
                   }
                   else if (exc[0] instanceof InvocationTargetException)
                   {
                      throw (InvocationTargetException) exc[0];
                   }
                   else if (exc[0] instanceof Exception)
                   {
                      throw (Exception) exc[0];
                   }
                   else
                   {
                      throw new RuntimeException(exc[0]);
                   }
                }

                result = checkInvokeResult(res[0], name, function, dynamicFunction);
             }
          }

          // allow Progress compatible exceptions to flow through
          catch (ConditionException | RetryUnwindException ce)
          {
             throw ce;
          }

          // handle reflection exceptions
          catch (NoSuchMethodException nsm)
          {
             // should never happen, unless name_map.xml is corrupt
             deferred = nsm;
          }

          catch (InvocationTargetException exc)
          {
             deferred = checkInvocationException(exc);
          }

          // all other cases
          catch (Exception exp)
          {
             deferred = exp;
          }

          if (deferred != null)
          {
             invokeError(caller, name, deferred);
          }

          return result;
       }

       /**
        * The implementation for the RUN ... PERSISTENT [SET handle] [ON SERVER server] statement.
        *
        * @param    server
        *           The server instance on which to run the procedure.  This will save the procedure
        *           in the remote persistent procedure list, not on the requester side.  May be null.
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle to which the procedure reference must be saved.
        *           When <code>null</code> and not ran remotely, just adds the procedure to the
        *           persistent procedure list.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       private static void invokePersistentImpl(handle    server,
                                                character name,
                                                handle    persistHandle,
                                                handle    portHandle,
                                                boolean   transactionDistinct,
                                                String    modes,
                                                Object... args)
       {
          invokePersistentImpl(null, server, name,
                               persistHandle, portHandle,
                               transactionDistinct,
                               modes, args);
       }

       /**
        * Initialize the proxy procedure associated with the async request.
        *
        * @param    call
        *           The async request which currently holds a not-initialized proxy procedure.
        */
       private static void initializeProxy(AsyncCall call)
       {
          if (!validServer(call.hServer, call.pHandle, call.pName))
          {
             return;
          }

          ServerImpl s = (ServerImpl) call.hServer.get();
          if (s.isWebService())
          {
             throw new IllegalStateException(
                                    "initializeProxy can be used only with an appserver connection!");
          }
          else
          {
             try
             {
                // execute the proxy
                AppServerHelper appServer = (AppServerHelper) s.getServerHelper();
                appServer.initializeProxy(call.asyncReq,
                                          (ProxyProcedureWrapper) call.pHandle.getResource(),
                                          call.transactionDistinct,
                                          call.asyncReq.getModes(),
                                          call.asyncReq.getArgs());
             }
             catch (ConditionException e)
             {
                if (e.getCause() instanceof NumberedException)
                {
                   ErrorManager.recordOrThrowError((NumberedException) e.getCause());
                }
                else
                {
                   throw e;
                }
             }
          }
       }

       /**
        * Invoke the specified external program in either SINGLETON or SINGLE-RUN modes.
        * <p>
        * This just creates a {@link DeferredProgram} configuration, saving the target name and any
        * remote server configuration specified at the RUN statement.
        *
        * @param    singleRun
        *           Flag indicating if we are using SINGLE-RUN (if <code>true</code>);
        *           <code>false</code> for SINGLETON.
        * @param    name
        *           The external program name.
        * @param    hproc
        *           The handle where to save this deferred resource.
        * @param    server
        *           A server configuration - may be unknown or <code>null</code>.
        * @param    transactionDistinct
        *           Flag indicating if the TRANSACTION-DISTINCT option was used.
        */
       private static void invokeSingletonOrSingleRun(boolean   singleRun,
                                                      character name,
                                                      handle    hproc,
                                                      handle    server,
                                                      boolean   transactionDistinct)
       {
          // if the server is the SESSION handle, then this is a local call
          if (server == null || CompareOps._isEqual(server, SessionUtils.asHandle()))
          {
             if (transactionDistinct)
             {
                invalidHandle(name);
                return;
             }

             // make it a local call
             server = null;
          }

          DeferredProgram deferred = null;
          if (server != null)
          {
             if (!validServer(server, null, name))
             {
                return;
             }

             ServerImpl s = (ServerImpl) server.get();

             if (s.isWebService())
             {
                final String err =
                         "The " + (singleRun ? "SINGLE-RUN" : "SINGLETON") +
                         " keyword is invalid when used with a SERVER handle whose " +
                         "SUBTYPE is 'WEBSERVICE'";
                ErrorManager.recordOrThrowError(singleRun ? 17217 : 17215, err, false);
                return;
             }

             deferred = new DeferredProgram(name.toStringMessage(), s, transactionDistinct);
          }
          else
          {
             deferred = new DeferredProgram(name.toStringMessage());
          }

          deferred.setSingleRun(singleRun);
          deferred.setSingleton(!singleRun);

          WorkArea wa = work.obtain();

          // no special type is required - just create an unique instance, which will identify this
          // 'proxy' program
          handle hres = new handle();
          hres.assign(new DeferredProgramWrapper(deferred));
          wa.pm.addProcedure(hres, name.toStringMessage(), !singleRun, singleRun);

          if (hproc != null)
          {
             hproc.assign(hres);
          }
       }
       /**
        * The implementation for the RUN ... PERSISTENT [SET handle] [ON SERVER server] statement.
        *
        * @param    asyncReq
        *           When not-null, an {@link AsyncRequestImpl} instance for the associated async
        *           request.
        * @param    server
        *           The server instance on which to run the procedure.  This will save the procedure
        *           in the remote persistent procedure list, not on the requester side.  May be null.
        * @param    name
        *           The legacy 4GL name for the procedure.
        * @param    persistHandle
        *           The handle to which the procedure reference must be saved.
        *           When <code>null</code> and not ran remotely, just adds the procedure to the
        *           persistent procedure list.
        * @param    portHandle
        *           A handle variable where to save the web service. May be null.
        * @param    transactionDistinct
        *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON stmt.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The procedure's arguments.
        */
       private static void invokePersistentImpl(AsyncRequestImpl asyncReq,
                                                handle           server,
                                                character        name,
                                                handle           persistHandle,
                                                handle           portHandle,
                                                boolean          transactionDistinct,
                                                String           modes,
                                                Object...        args)
       {
          // this will always be an external procedure call
          if (name.isUnknown() || name.toStringMessage().length() == 0)
          {
             String err = "No PROGRESS program specified for RUN or COMPILE";
             ErrorManager.recordOrThrowError(467, err);
             return;
          }

          // if the server is the SESSION handle, then this is a local call
          if (server == null || CompareOps._isEqual(server, SessionUtils.asHandle()))
          {
             if (transactionDistinct)
             {
                invalidHandle(name);
                return;
             }

             // this is a normal call, invoke immediately
             invokeExternalProcedure(name, true, persistHandle, false, null, modes, args);
          }
          else
          {
             // validate server first
             if (!validServer(server, null, name))
             {
                return;
             }

             ServerImpl s = (ServerImpl) server.get();

             if (s.isWebService())
             {
                if (portHandle == null)
                {
                   final String err =
                      "The PERSISTENT keyword is invalid when used with a SERVER handle whose " +
                      "SUBTYPE is 'WEBSERVICE'";
                   ErrorManager.recordOrThrowError(11481, err, false);
                   return;
                }

                if (asyncReq != null)
                {
                   final String err =
                      "Unexpected Web service client error. Copy the following message and contact " +
                      "technical support. Web service procedure not initialized in cwsSend";
                   ErrorManager.recordOrThrowError(11461, err, false);
                   return;
                }

                // this will establish a port connection
                WebServiceHelper helper = (WebServiceHelper) s.getServerHelper();
                PersistentProcedure portProcedure = helper.runPort(name);
                if (portProcedure != null)
                {
                   // the port was ran, assign it
                   portHandle.assign(portProcedure);

                   // this is not a persistent procedure, and does not get registered with the
                   // ProcedureManager
                }

                return;
             }
             else
             {
                if (portHandle != null)
                {
                   final String err =
                      "'RUN ... ON SERVER server-handle' with the [SET handle] option is not valid " +
                      "without the PERSISTENT keyword unless the server is a Web service";
                   ErrorManager.recordOrThrowError(14587, err, false);
                   return;
                }

                // if sync mode, no async requests must be running
                if (asyncReq == null && s.hasAsyncRequests())
                {
                   return;
                }

                ProxyProcedureWrapper proxyProcedure = null;
                handle h = persistHandle;

                AppServerHelper appServer = (AppServerHelper) s.getServerHelper();
                try
                {
                   if (h == null)
                   {
                      h = new handle();
                   }
                   InvokeConfig cfg = new InvokeConfig(name);
                   cfg.setAsynchronous(asyncReq != null)
                      .setProcedureHandle(h)
                      .setTransactionDistinct(transactionDistinct)
                      .setModes(modes)
                      .setArguments(args);

                   // send the server:REQUEST
                   cfg.storeRequest(s.getRequestInfo());

                   if (asyncReq != null)
                   {
                      asyncReq.getRequestInfo().ref.initialize(s.getRequestInfo().ref, true);
                   }

                   BaseDataType result = wrapAppServerCall(() -> appServer.invoke(0, asyncReq, cfg), cfg.getServerHandle());

                   if (asyncReq == null && result != null)
                   {
                      setReturnValue(new character(result));
                   }

                   proxyProcedure = (ProxyProcedureWrapper) h.getResource();
                   if (persistHandle != null)
                   {
                      persistHandle.set(proxyProcedure);
                   }
                   proxyProcedure.setServer(s);
                }
                catch (ConditionException e)
                {
                   if (e.getCause() instanceof NumberedException)
                   {
                      ErrorManager.recordOrThrowError((NumberedException) e.getCause());
                   }
                   else
                   {
                      throw e;
                   }
                }
                catch (Exception e)
                {
                   LOG.severe("Exception thrown by executing the appserver invoke callable.", e);
                }

                // chain the procedure
                handle last = s.lastProcedure();
                if (last.isUnknown())
                {
                   s.setFirstProcedure(proxyProcedure);
                   s.setLastProcedure(proxyProcedure);
                }
                else
                {
                   ProxyProcedureWrapper lr = (ProxyProcedureWrapper) last.getResource();
                   lr.setNextSibling(proxyProcedure);
                   proxyProcedure.setPrevSibling(lr);
                   s.setLastProcedure(proxyProcedure);
                }
             }
          }
       }

       /**
        * Saves the current list of client types and sets the new values from the server connect type. Executes
        * the appserver call and reverts the client types. Handles QUIT condition thrown by the appserver call.
        *
        * @param    serverCall
        *           The appserver call.
        * @param    serverHandle
        *           The server handle.
        *
        * @return   The result of the appserver call.
        *
        * @throws   Exception
        *           Exception thrown by the callable.
        */
       private static BaseDataType wrapAppServerCall(Callable<BaseDataType> serverCall, handle serverHandle)
       throws Exception
       {

          ClientType[] origClientTypes = SessionUtils.getClientTypes();

          if (serverHandle != null && serverHandle.getResource() instanceof ServerImpl)
          {
             ClientType[] newClientTypes = ((ServerImpl) serverHandle.getResource()).getClientTypes();
             SessionUtils.setClientTypes(newClientTypes);
          }

          try
          {
             return serverCall.call();
          }
          catch (QuitConditionException e)
          {
             if (serverHandle == null)
             {
                // don't propagate to the outer procedure QUIT executed on an appserver
                throw e;
             }
          }
          finally
          {
             if (serverHandle != null)
             {
                SessionUtils.setClientTypes(origClientTypes);
             }
          }
          return null;
       }

       /**
        * Check if the caller can validate the arguments. If not, show an appropriate error message
        * and throw error condition.
        * <p>
        * Note that the content of the {@code args} array can be changed. An item will be changed if
        * any of following occurs:
        * <ul>
        *    <li>variables are replaced in O/U modes with local variables
        *    <li>handle access discrepancies
        *    <li>types of parameters do not match but they are compatible
        * </ul>
        *
        * @param    wa
        *           The context-local state in {@link WorkArea}.
        * @param    name
        *           The legacy program name.
        * @param    caller
        *           The caller used to validate the arguments.
        * @param    function
        *           <code>true</code> if this is a function call.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The argument list.
        *
        * @return   <code>true</code> if the arguments are valid.
        */
       private static boolean validArguments(WorkArea            wa,
                                             String              name,
                                             InternalEntryCaller caller,
                                             boolean             function,
                                             String              modes,
                                             Object...           args)
       {
          boolean res = false;

          try
          {
             res = validArgumentsInt(wa, name, caller, function, modes, args);
          }
          finally
          {
             if (!res)
             {
                cleanupPending(wa);
             }
          }

          return res;
       }

       /**
        * Check if the caller can validate the arguments. If not, show an appropriate error message
        * and throw error condition.
        * <p>
        * Note that the content of the {@code args} array can be changed. An item will be changed if
        * any of following occurs:
        * <ul>
        *    <li>variables are replaced in O/U modes with local variables
        *    <li>handle access discrepancies
        *    <li>types of parameters do not match but they are compatible
        * </ul>
        *
        * @param    wa
        *           The context-local state in {@link WorkArea}.
        * @param    name
        *           The legacy program name.
        * @param    caller
        *           The caller used to validate the arguments.
        * @param    function
        *           <code>true</code> if this is a function call.
        * @param    modes
        *           A string representation of the modes of each parameter.
        * @param    args
        *           The argument list.
        *
        * @return   <code>true</code> if the arguments are valid.
        */
       private static boolean validArgumentsInt(WorkArea            wa,
                                                String              name,
                                                InternalEntryCaller caller,
                                                boolean             function,
                                                String              modes,
                                                Object...           args)
       {
          wa.invalidArgs = false;

          int[] extentInfo = new int[2]; // [0] = argument extent, [1] = parameter (expected) extent
          ArgValidationErrors argsValid = caller.valid(function, modes, extentInfo, args);

          // deferred validation needs no further processing here
          if (argsValid == ArgValidationErrors.DEFER_VALIDATION)
          {
             return true;
          }

          if (caller.iename != null)
          {
             // we have an internal entry, determine the relative name used to start this referent
             name = wa.pm.getRelativeName(caller.callerInstance);
          }

          if (argsValid == ArgValidationErrors.NO_ERROR)
          {
             // validate the parameter's modes
             if (modes != null && !modes.isEmpty())
             {
                String definedModes = caller.getParameterModes(function);
                if (!modes.equals(definedModes))
                {
                   wa.invalidArgs = true;

                   final String errp =
                      "Mismatched parameters types passed to procedure %s %s";
                   final String errf =
                      "Mismatched parameter INPUT/OUTPUT mode passed to routine %s %s";

                   String err = function ? errf : errp;
                   int errno = function ? 6349 : 3230;
                   String msg = String.format(err, caller.getInternalEntryName(), name);
                   ErrorManager.recordOrThrowError(errno, msg, false);
                   return false;
                }
             }

             return true;
          }

          wa.invalidArgs = true;

          String[] errs = null;
          int[] errIds = null;

          String callingProcedure =
             SessionUtils._isRemote()
             ? "(Remote client)"
             : wa.pm.getStackEntry(0);

          String invocationTarget =
             wa != null && wa.currentInvoke != null
             ? wa.currentInvoke.getTarget()
             : "";

          invocationTarget =
             invocationTarget.equals("") || invocationTarget == null || invocationTarget.equals(name)
             ? ""
             : ":" + invocationTarget;

          switch (argsValid)
          {
             case UNEXPECTED_PARAM:
                errIds = new int[] { 1005 };
                String msg = "Procedure %s passed parameters to %s%s, which did not expect any";
                msg = String.format(msg, callingProcedure, name, invocationTarget);
                errs = new String[] { msg };
                break;
             case INCORRECT_COUNT:
                errIds = new int[] { 3234 };
                msg = "Mismatched number of parameters passed to routine %s %s%s";
                msg = String.format(msg, caller.getInternalEntryName(), name, invocationTarget);
                errs = new String[] { msg };
                break;
             case INCORRECT_TYPES:
                errIds = new int[] { 5729, 2570 };
                msg = "Routine %s sent called routine %s %s%s mismatched parameters";
                msg = String.format(msg,
                                    callingProcedure,
                                    caller.getInternalEntryName(),
                                    name,
                                    invocationTarget);

                errs = new String[]
                {
                   "Incompatible datatypes found during runtime conversion",
                   msg
                };
                break;
             case ARRAY_FOR_NON_ARRAY:
             case NON_ARRAY_FOR_ARRAY:
             case BAD_ARRAY_DIMENSION:
                errIds = new int[] { 11428 };
                // TODO: msg doesn't end in DOT in 4GL
                msg = "Extent parameter dimension of %d from procedure %s is mismatched with " +
                      "sub-procedure %s %s%s parameter dimension of %d ...(328)";
                msg = String.format(msg,
                                    extentInfo[0],
                                    callingProcedure,
                                    caller.getInternalEntryName(),
                                    name,
                                    invocationTarget,
                                    extentInfo[1]);
                errs = new String[] { msg };
                break;
             case DYN_ARRAY_FOR_NON_ARRAY:
                errIds = new int[] { 12290 };
                msg = "Calling procedure %s cannot pass an indeterminate extent parameter if the " +
                      "sub-procedure %s %s%s parameter is a non-array parameter";
                msg = String.format(msg,
                                    callingProcedure,
                                    caller.getInternalEntryName(),
                                    name,
                                    invocationTarget);
                errs = new String[] { msg };
                break;
             case NON_ARRAY_FOR_DYN_ARRAY:
                errIds = new int[] { 15472 };
                msg =
                   "Routine %s passed a non-array parameter to an array parameter of routine %s %s%s";
                msg = String.format(msg,
                                    callingProcedure,
                                    caller.getInternalEntryName(),
                                    name,
                                    invocationTarget);
                errs = new String[] { msg };
                break;
          }

          ErrorManager.recordOrThrowError(errIds, errs, false);

          return false;
       }

       /**
        * Check if the handle is valid for function or procedure invocation.
        * If is invalid, show an appropriate message.
        *
        * @param    wa
        *           The context-local state in {@link WorkArea}.
        * @param    h
        *           The handle to which the procedure belongs. If <code>null</code>
        *           defaults to THIS-PROCEDURE.
        * @param    name
        *           The legacy 4GL name for the function.
        * @param    explicit
        *           <code>true</code> if the passed handle was explicitly set (for
        *           RUN ... IN or FUNCTION ... IN handle cases.
        * @param    function
        *           <code>true</code> if this is a function call.
        * @param    dynamicFunction
        *           <code>true</code> if this is a DYNAMIC-FUNCTION call.
        *
        * @return   <code>true</code> if the handle is valid for function or
        *           procedure invocation.
        */
       private static boolean validHandle(WorkArea wa,
                                          handle  h,
                                          String  name,
                                          boolean explicit,
                                          boolean function,
                                          boolean dynamicFunction)
       {
          if (function)
          {
             boolean isProcedure = ProcedureManager.isProcedure(h);
             if (h.isUnknown() || !isProcedure)
             {
                String err = null;
                int num = -1;

                if (!h.isUnknown() && !isProcedure)
                {
                   final String msg =
                      "Could not locate external function '%s'. The handle to " +
                      "the procedure context is invalid";
                   err = String.format(msg, name);
                   num = 2777;
                }
                else if (explicit && dynamicFunction)
                {
                   err = "DYNAMIC-FUNCTION function given invalid or Unknown context to run IN";
                   num = 5705;
                }
                else
                {
                   final String msg =
                      "Could not evaluate the expression describing the " +
                      "context of external function '%s'";
                   err = String.format(msg, name);
                   num = 2767;
                }

                ErrorManager.recordOrThrowError(num, err, false);
                return false;
             }
          }
          else
          {
             if (explicit && h.isUnknown())
             {
                String err = null;
                int num = -1;
                final String msg =
                   "Could not evaluate procedure handle expression while trying to execute %s";
                err = String.format(msg, name);
                num = 2125;

                ErrorManager.recordOrThrowError(num, err, false);
                return false;
             }
             if (!wa.pm.hasReferent(h.get()))
             {
                String err = null;
                int num = -1;
                final String msg = "Invalid or inappropriate handle value given to RUN...IN " +
                                   "statement. Procedure '%s':%d";
                num = 2128;

                handle pthis = wa.pm.thisProcedure();
                String thisName = (pthis != null) ? wa.pm.getRelativeName(pthis.get()) : "null";
                err = String.format(msg, thisName, 0);

                ErrorManager.recordOrThrowError(num, err, false);
                return false;
             }
          }

          return true;
       }

       /**
        * Check if the given handle is a valid, connected appserver.
        *
        * @param    hServer
        *           The handle to validate as an appserver.
        * @param    hProc
        *           A procedure handle for IN handle cases.
        * @param    name
        *           The target 4GL legacy name.
        *
        * @return   <code>true</code> if the given handle is valid.
        */
       private static boolean validServer(handle hServer, handle hProc, character name)
       {
          final String err2 =
             "Invalid or inappropriate server handle specified for RUN %s ... ON SERVER statement";
          final int code2 = 5453;

          if (hServer.isUnknown())
          {
             ErrorManager.recordOrThrowError(code2, err2, false);
             return false;
          }

          Object resource = hServer.get();
          if (!(resource instanceof ServerImpl))
          {
             final String err =
                "The handle for \"RUN ... ON SERVER\" must be either a SERVER handle or " +
                "a SESSION handle";
             ErrorManager.recordOrThrowError(8998, err, false);

             return false;
          }

          ServerImpl server = (ServerImpl) resource;
          if (!server._connected())
          {
             final String err1 = "SERVER  is not connected";
             final int code1 = 5451;

             int[] codes = null;
             String[] msgs = null;

             if (hProc != null && hProc._isValid() &&
                 (ProcedureManager.isProxy(hProc) || hProc.get() instanceof PortTypeWrapper))
             {
                codes = new int[] { code1 };
                msgs = new String[] { err1 };
             }
             else
             {
                codes = new int[] { code1, code2 };
                msgs = new String[] { err1, String.format(err2, name.toStringMessage()) };
             }

             ErrorManager.recordOrThrowError(codes, msgs, false);

             return false;
          }

          return true;
       }

       /**
        * Common helper to display a standard error message and then throw a
        * STOP condition when the target procedure cannot be found. Silent
        * error mode has no effect on this since this is not an ERROR condition.
        * That means that this method never returns normally AND that the
        * ERROR-STATUS data is unchanged.
        *
        * @param    target
        *           The procedure that cannot be found.
        */
       private static void invokeFailure(String target)
       throws StopConditionException
       {
          int errCode = EXIT_CODE_PROCEDURE_NOT_FOUND;
          String spec = "\"%s\" was not found";
          String errMsg  = String.format(spec, target);
          String err = ErrorManager.buildErrorText(errCode, errMsg, true);

          // RUN external.p should never be a silent error
          boolean isSilentOriginally = ErrorManager.isSilent();
          ErrorManager.setSilent(false);
          ErrorManager.addError(errCode, errMsg, false, true);
          ErrorManager.displayError(errCode, errMsg, true);
          ErrorManager.setSilent(isSilentOriginally);

          throw new StopConditionException(err, new NumberedException(err, errCode));
       }

       /**
        * Common helper to generate error 5453 and then throw an error there is an invalid handle.
        *
        * @param    name
        *           The RUN statement target.
        */
       private static void invalidHandle(character name)
       throws ErrorConditionException
       {
          ErrorManager.recordOrThrowError(5453,
                                          "Invalid or inappropriate server handle specified " +
                                          "for RUN " + name.toStringMessage() +
                                          " ... ON SERVER statement",
                                          false);
       }

       /**
        * Throw a {@link RuntimeException}, as the function or procedure
        * invocation has raised an exception.
        *
        * @param    caller
        *           {@link InternalEntryCaller} caller instance used to invoke the
        *           function or procedure. May be null.
        * @param    name
        *           The legacy 4GL name passed to the function or procedure call
        *           statement.
        * @param    deferred
        *           The deferred error.
        */
       private static void invokeError(InternalEntryCaller caller,
                                       String name,
                                       Exception deferred)
       {
          if (caller != null)
          {
             throw new RuntimeException("invoke() of class "   +
                                        caller.getClassName()  +
                                        " and  method "        +
                                        caller.getMethodName() +
                                        " failed",
                                        deferred);
          }
          else
          {
             throw new RuntimeException("invoke() of program "   +
                                        name + " failed", deferred);
          }
       }

       /**
        * Run the specified code in a related thread.
        *
        * @param    ht
        *           When non-null, it will save a resource which can manage the related thread doing
        *           this work.
        * @param    worker
        *           The work to be executed.
        */
       private static void runAsThread(handle ht, Runnable worker)
       {
          // inherit batch mode, project token and stopdisp
          String projectToken = Utils.getProjectToken();
          logical batchMode = EnvironmentOps.isBatchMode();
          int stopDisp = Utils.getDirectoryNodeInt(null, "stop_disposition", 1);

          RelatedThread thread = new RelatedThread(() ->
          {
             try
             {
                // this is always in 'headless' mode
                SecurityManager.getInstance().contextSm.setHeadless();

                EnvironmentOps.setBatchMode(batchMode.booleanValue());
                Utils.setProjectToken(projectToken);
                // TODO: load project-specific search-path? this is done in StandardServer.standardEntry
                // we may want to make this logic common (i.e. loading project-specific state).

                StandardServer.invoke(stopDisp, new Isolatable()
                {
                   @Override
                   public Object execute()
                   {
                      // TODO: for internal entries (or even external programs), the worker should
                      // check for @ThreadSafe annotation
                      worker.run();
                      return null;
                   }
                });
             }
             catch (NullPointerException         |
                    ReflectiveOperationException e)
             {
                throw new RuntimeException(e);
             }
          });
          String currentThread = Thread.currentThread().getName();
          thread.setName(currentThread + " - related");
          // start the thread
          thread.start();

          if (ht != null)
          {
             // TODO: assign the handle to a resource which can manage this related thread
          }
       }

       /**
        * Override this native call to a FWD API call.
        *
        * @param    phandle
        *           The procedure handle where this external entry is defined.
        * @param    libname
        *           The library name where this native procedure should be found.
        * @param    pname
        *           The legacy name of the external procedure from which this API is being called.
        * @param    iename
        *           The internal-entry name being called which is usually the same as the entry
        *           point name in the library except where an ordinal is being used.
        * @param    ie
        *           The already resolved internal entry.
        *
        * @return   A non-null {@link InternalEntryCaller} instance, if this call can be overridden.
        */
       private static InternalEntryCaller overrideNativeCall(handle        phandle,
                                                             String        libname,
                                                             String        pname,
                                                             String        iename,
                                                             InternalEntry ie)
       {
          // overrides lockWindowUpdate user32.dll calls to LogicalTerminal.disableRedraw

          if (!iename.equalsIgnoreCase("lockWindowUpdate") ||
              libname == null                              ||
              libname.toLowerCase().indexOf("user32") < 0)
          {
             return null;
          }

          boolean missingReturn = ie.getParameterListSize() == 1;
          if (!(ie instanceof NativeAPIEntry) ||
              ie.isFunction()                 ||
              (ie.getParameterListSize() != 2 && !missingReturn))
          {
             return null;
          }

          NativeAPIEntry calldef = (NativeAPIEntry) ie;
          String pmodes;

          Parameter pret = null;
          Parameter phwnd = null;

          if (missingReturn)
          {
             Parameter p0 = calldef.getParameter(0);

             if (p0.getMode().equalsIgnoreCase("INPUT"))
             {
                pmodes = "I";
                pret = null;
                phwnd = p0;
             }
             else
             {
                return null;
             }
          }
          else
          {
             Parameter p0 = calldef.getParameter(0);
             Parameter p1 = calldef.getParameter(1);
             if (p0.getMode().equalsIgnoreCase("INPUT") && p1.getMode().equalsIgnoreCase("RETURN"))
             {
                pmodes = "IO";
                pret = p1;
                phwnd = p0;
             }
             else if (p1.getMode().equalsIgnoreCase("INPUT") && p0.getMode().equalsIgnoreCase("RETURN"))
             {
                pmodes = "OI";
                pret = p0;
                phwnd = p1;
             }
             else
             {
                return null;
             }
          }

          // HWND data types are LONG, UNSIGNED-LONG or INT64
          switch (phwnd.getType())
          {
             case "LONG":
             case "UNSIGNED-LONG":
             case "INT64":
                break;
             default:
                return null;
          }
          // RETURN data types are BYTE, SHORT, UNSIGNED-SHORT, LONG, UNSIGNED-LONG or INT64
          if (!missingReturn)
          {
             switch (pret.getType())
             {
                case "BYTE":
                case "SHORT":
                case "UNSIGNED-SHORT":
                case "LONG":
                case "UNSIGNED-LONG":
                case "INT64":
                   break;
                default:
                   return null;
             }
          }

          return new NativeProcedureCaller(work.obtain(), phandle, libname, pname, iename)
          {
             @Override
             Object invokeImpl(String modes, Object... param) throws Exception
             {
                if (missingReturn)
                {
                   if (param.length == 1                       &&
                       (modes == null || pmodes.equals(modes)) &&
                       int64.class.isAssignableFrom(param[0].getClass()))
                   {
                      int64 hwnd = (int64) param[0];

                      LogicalTerminal.disableRedraw(new int64(hwnd));
                      return null;
                   }
                }
                else
                {
                   if (param.length == 2                                 &&
                       int64.class.isAssignableFrom(param[0].getClass()) &&
                       int64.class.isAssignableFrom(param[1].getClass()))
                   {
                      int64 hwnd = null;
                      int64 ret = null;

                      if (modes == null)
                      {
                         modes = pmodes;
                      }

                      if (modes.equals("IO"))
                      {
                         hwnd = (int64) param[0];
                         ret = (int64) param[1];
                      }
                      else if (modes.equals("OI"))
                      {
                         ret = (int64) param[0];
                         hwnd = (int64) param[1];
                      }

                      if (hwnd != null && ret != null)
                      {
                         LogicalTerminal.disableRedraw(new int64(hwnd), ret);
                         return null;
                      }
                   }
                }

                ErrorManager.recordOrThrowError(0, "Could not invoke DISABLE-REDRAW!");

                return null;
             }
          };
       }

       /**
        * Cleanup all pending resources initialized by the external program default constructor, as the call
        * failed.
        */
       public static void cleanupPending()
       {
          cleanupPending(work.obtain());
       }

       /**
        * Cleanup all pending resources initialized by the external program default constructor, as the call
        * failed.
        */
       private static void cleanupPending(WorkArea wa)
       {
          // cleanup all pending (to be registered) shared resources
          wa.tm.cleanupPending();
          SharedVariableManager.cleanupPending();
          LogicalTerminal.cleanupPending();
          wa.pm.cleanupPending();
          BufferManager.cleanupPending();
       }

       /**
        * Check the invocation result for a DYNAMIC-FUNCTION or FUNCTION call.
        *
        * @param    res
        *           The returned value.
        * @param    name
        *           The function's name.
        * @param    function
        *           Flag identifying the call as a function call.
        * @param    dynamicFunction
        *           Flag identifying the call as a DYNAMIC-FUNCTION call.
        *
        * @return   The prepared result.
        */
       private static BaseDataType checkInvokeResult(Object  res,
                                                     String  name,
                                                     boolean function,
                                                     boolean dynamicFunction)
       {
          BaseDataType result = unknown.UNKNOWN;

          if (res == null)
          {
             if (!function)
             {
                result = unknown.UNKNOWN;
             }
             else
             {
                final String msg = "Function call '%s' has returned a null value!";
                throw new NullPointerException(String.format(msg, name));
             }
          }
          else if (res instanceof BaseDataType)
          {
             result = (BaseDataType) res;
          }
          else
          {
             if (function)
             {
                final String spec =
                   "Incompatible return value by function %s: [%s] of type [%s]";
                String err = String.format(spec, name,
                                           result.toString(),
                                           result.getClass().toString());
                ErrorManager.displayError(err);

                ErrorManager.handleStopException(err);
             }
          }

          boolean isProxy = result != null && result.val() != result;
          if (dynamicFunction)
          {
             if (result != null && !isProxy)
             {
                BaseDataType proxy = BaseDataType.createProxy(result);

                if (result instanceof Text)
                {
                   // preserve case sensitivity
                   ((Text) proxy).setCaseSensitive(((Text) result).isCaseSensitive());
                }

                result = proxy;
             }
          }
          else
          {
             if (isProxy)
             {
                result = result.fallback();
             }
          }

          return result;
       }

       /**
        * Check the exception cause in the chain of the exception returned from a reflection call, if it contains
        * any FWD condition exceptions.
        *
        * @param    exc
        *           The exception to check.
        *
        * @return   The deferred exception, if the cause is not a {@link ConditionException}.
        *
        * @throws   ConditionException
        * @throws   ErrorConditionException
        * @throws   RetryUnwindException
        */
       private static Exception checkInvocationException(InvocationTargetException exc)
       {
          Exception deferred = null;

          // if the exception thrown was a P2J runtime type, we
          // must handle it normally, so extract the root cause
          // exception and rethrow it
          Throwable cause = exc.getCause();
          while (cause != null)
          {
             if (cause instanceof ConditionException)
             {
                if (cause instanceof InvalidParameterConditionException)
                {
                   // TODO: it is not entirely clear how safe it is to directly instantiate this
                   //       error; the InvalidParameterConditionException can be raised (we think)
                   //       only when there is some temp-table parameter error (see
                   //       TemporaryBuffer.handleTablesDoNotMatch); the location for the throw
                   //       suggests that the ERROR condition is raised regardless of NO-ERROR
                   //       mode

                   // re-wrap NumberedException
                   throw new ErrorConditionException(cause.getCause());
                }
                else
                {
                   throw (ConditionException) cause;
                }
             }
             if (cause instanceof RetryUnwindException)
             {
                throw (RetryUnwindException) cause;
             }

             // exception is a runtime problem that we don't handle here
             if (deferred == null)
             {
                deferred = exc;
             }

             cause = cause.getCause();
          }

          return deferred;
       }

       /**
        * Cache the invocation target for a callsite.
        * <p>
        * The details about this invocation will be saved into a {@link LastInvokeDetails} instance, and cached
        * in the {@link WorkArea#callSiteCache}, for the specified <code>thisCaller</code> (which can be either
        * a {@link WrappedResource} or an external program instance) and {@link InvokeConfig#getCallSite() call-site}.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    caller
        *           The caller for this invocation.
        * @param    cfg
        *           The invoke configuration.
        * @param    thisCaller
        *           The resource or referent performing this call, where the target will be cached.
        * @param    args
        *           The arguments for this call.
        */
       private static void cacheInvocation(WorkArea            wa,
                                           InternalEntryCaller caller,
                                           InvokeConfig        cfg,
                                           Object              thisCaller,
                                           Object[]            args)
       {
          String target = cfg.getTarget();
          String modes = cfg.getModes();
          String eventProcedureName = cfg.getEventProcedure() == null
                                         ? null
                                         : cfg.getEventProcedure().toStringMessage();
          Class<?>[] argumentTypes = null;
          Object eventProcedureContext = cfg.getEventProcedureContext() == null
                                            ? null
                                            : cfg.getEventProcedureContext().get();
          Object inHandle = cfg.getInHandle() == null ? null : cfg.getInHandle().get();
          boolean externalProcedure = caller.getPhandle() == null;
          Object instance = externalProcedure ? caller.getClassName() : caller.getCallerInstance();
          Method mthd = externalProcedure ? null : caller.mthd;

          if (args != null && args.length > 0)
          {
             argumentTypes = new Class[args.length];
             for (int i = 0; i < args.length; i++)
             {
                argumentTypes[i] = args[i].getClass();
             }
          }

          LastInvokeDetails invoke = new LastInvokeDetails((caller.ie == null ? null : caller.ie.pname),
                                                           target,
                                                           modes,
                                                           inHandle,
                                                           eventProcedureName,
                                                           eventProcedureContext,
                                                           instance,
                                                           mthd,
                                                           argumentTypes);

          Map<InvokeConfig, LastInvokeDetails> thisCallSite =
             externalProcedure ? wa.extProgcallSiteCache
                               : wa.callSiteCache.computeIfAbsent(thisCaller, (k) -> new IdentityHashMap<>());

          thisCallSite.put(cfg.getCallSite(), invoke);
       }

       /**
        * The cached details of an invocation callsite.
        */
       private static class LastInvokeDetails
       {
          /** The legacy name of the target. */
          private final String legacyName;

          /** The exact target name used at the call-site. */
          private final String target;

          /** The parameter modes. */
          private final String modes;

          /** The event procedure name being invoked, in case of an ASYNC call. */
          private final String eventProcedureName;

          /** The argument types for this call. */
          private final Class<?>[] argumentTypes;

          /** The event procedure context, in case of an ASYNC call. */
          private final WeakReference<Object> eventProcedureContext;

          /** The target handle for an internal procedure, specified at the call. */
          private final WeakReference<Object> inHandle;

          /**
           * The actual external program from where the {@link #target} was resolved and the {@link #method} is
           * being invoked.
           */
          private final WeakReference<Object> reference;

          /** The Java method to invoke. */
          private final Method method;

          /**
           * Create a new instance.
           *
           * @param    legacyName
           *           The legacy name of the target.
           * @param    target
           *           The exact target name used at the call-site.
           * @param    modes
           *           The parameter modes.
           * @param    inHandle
           *           The target handle for an internal procedure, specified at the call.
           * @param    eventProcedureName
           *           The event procedure name being invoked, in case of an ASYNC call.
           * @param    eventProcedureContext
           *           The event procedure context, in case of an ASYNC call.
           * @param    reference
           *           The actual external program from where the {@link #target} was resolved and the
           *           {@link #method} is being invoked.
           * @param    method
           *           The Java method to invoke.
           * @param    argumentTypes
           *           The argument types for this call.
           */
          public LastInvokeDetails(String     legacyName,
                                   String     target,
                                   String     modes,
                                   Object     inHandle,
                                   String     eventProcedureName,
                                   Object     eventProcedureContext,
                                   Object     reference,
                                   Method     method,
                                   Class<?>[] argumentTypes)
          {
             this.legacyName = legacyName;
             this.target = target;
             this.modes = modes;
             this.eventProcedureName = eventProcedureName;
             this.argumentTypes = argumentTypes;
             this.eventProcedureContext = (eventProcedureContext == null
                                             ? null
                                             : new WeakReference<>(eventProcedureContext));
             this.inHandle = (inHandle == null ? null : new WeakReference<>(inHandle));

             this.reference = (reference == null ? null : new WeakReference<>(reference));
             this.method = method;
          }

          /**
           * Check if this cached details are for an external program.
           *
           * @return   <code>true</code> if {@link #method} is <code>null</code>.
           */
          public boolean isExternalProgram()
          {
             return method == null;
          }

          /**
           * Check if this invocation matches a function call.
           *
           * @param    cfg
           *           The configuration to match against.
           *
           * @return   <code>true</code> if <code>cfg</code> is for a matching function call.
           *
           * @see #matches(InvokeConfig)
           */
          public boolean matchesFunction(InvokeConfig cfg)
          {
             if (!cfg.isFunction())
             {
                return false;
             }

             return matches(cfg);
          }

          /**
           * Check if this invocation matches a RUN call.
           * <p>
           * The configuration matches if and only if:
           * <ul>
           *    <li>{@link #matches(InvokeConfig)} is <code>true</code></li>
           *    <li>{@link InvokeConfig#getServerHandle()} is the SESSION handle, if specified.</li>
           *    <li>{@link InvokeConfig#getEventProcedure()} is the same as {@link #eventProcedureName},
           *        if specified.</li>
           *    <li>{@link InvokeConfig#getEventProcedureContext()} is the same as {@link #eventProcedureContext},
           *        if specified.</li>
           * </ul>
           *
           * @param    cfg
           *           The configuration to match against.
           *
           * @return   <code>true</code> if <code>cfg</code> is for a matchin RUN call.
           *
           * @see #matches(InvokeConfig)
           */
          public boolean matchesRun(InvokeConfig cfg)
          {
             if (cfg.isFunction() || cfg.isClass())
             {
                return false;
             }

             if (!matches(cfg))
             {
                return false;
             }

             if (cfg.getServerHandle() != null && !SessionUtils.asHandle().equals(cfg.getServerHandle()))
             {
                // not targeting SESSION
                return false;
             }

             if (eventProcedureName != null)
             {
                if (cfg.getEventProcedure() == null ||
                    !eventProcedureName.equalsIgnoreCase(cfg.getEventProcedure().toStringMessage()))
                {
                   // event procedure does not match
                   return false;
                }
             }
             else if (cfg.getEventProcedure() != null)
             {
                return false;
             }

             if (eventProcedureContext != null)
             {
                Object lastContext = eventProcedureContext.get();
                handle evtContext = cfg.getEventProcedureContext();
                if (evtContext != null && !evtContext._isValid())
                {
                   // the target is not valid, let it fail
                   return false;
                }
                Object cfgContext = (evtContext != null ? evtContext.get() : null);

                if (lastContext != cfgContext)
                {
                   // target has changed
                   return false;
                }
             }
             else if (cfg.getEventProcedureContext() != null)
             {
                return false;
             }

             return true;
          }

          /**
           * Check if this invocation matches the specified configuration.
           * <p>
           * The configuration matches if and only if:
           * <ul>
           *    <li>{@link #reference} is set and still valid.</li>
           *    <li>{@link InvokeConfig#getTarget()} is the same as {@link #target}.</li>
           *    <li>{@link InvokeConfig#getModes()} is the same as {@link #modes}, if specified.</li>
           *    <li>{@link InvokeConfig#getInHandle()} is the same as {@link #inHandle}, if specified.</li>
           *    <li>{@link InvokeConfig#getArguments()} types are the same as {@link #argumentTypes},
           *        if specified.
           *    </li>
           * </ul>
           *
           *
           * @param    cfg
           *           The configuration to match against.
           *
           * @return   <code>true</code> if <code>cfg</code> matches.
           */
          private boolean matches(InvokeConfig cfg)
          {
             if (!validReference())
             {
                return false;
             }

             if (cfg.getTarget() == null ||
                 cfg.getTarget().isEmpty() ||
                 !target.equalsIgnoreCase(cfg.getTarget()))
             {
                // target does not match
                return false;
             }

             if (!((cfg.getModes() == null && modes == null) ||
                 (cfg.getModes() != null && cfg.getModes().equals(modes))))
             {
                return false;
             }

             if (inHandle != null)
             {
                Object lastIn = inHandle.get();
                handle in = cfg.getInHandle();
                if (in != null && !in._isValid())
                {
                   // the target is not valid, let if fail
                   return false;
                }

                Object inRef = (in != null ? in.get() : null);
                if (lastIn != inRef)
                {
                   // target has changed
                   return false;
                }
             }
             else if (cfg.getInHandle() != null && !cfg.getInHandle()._isValid())
             {
                // if specified, IN handle must be valid
                return false;
             }

             Object[] args = cfg.getArguments();
             if (args != null && args.length == 0)
             {
                args = null;
             }

             if (!((args == null && argumentTypes == null) ||
                   (args != null && argumentTypes != null && args.length == argumentTypes.length)))
             {
                // arguments must be either not set or the same length, otherwise don't use cached target
                return false;
             }

             // check argument types
             if (args != null)
             {
                for (int i = 0; i < args.length; i++)
                {
                   Class<?> type = (args[i] == null ? null : args[i].getClass());
                   if (type != argumentTypes[i] || type == null || argumentTypes[i] == null)
                   {
                      // the signature does not match
                      return false;
                   }
                }
             }

             return true;
          }

          /**
           * Check if the {@link #reference} still exists.
           *
           * @return   See above.
           */
          private boolean validReference()
          {
             return reference == null || reference.get() != null;
          }
       }

       /**
        * Transform any {@link DataSetParameter} or {@link TableParameter} arguments at {@link #invokeLegacyMethod}
        * or {@link #initializeLegacyClass}, to their mode'ed versions, based on the INPUT, OUTPUT and
        * INPUT-OUTPUT modes.
        *
        * @param    modes
        *           The caller's modes.
        * @param    args
        *           The caller's arguments.
        */
       private static void prepareArgs(String modes, Object[] args)
       {
          if (args == null)
          {
             return;
          }

          for (int i = 0; i < args.length; i++)
          {
             Object arg = args[i];
             Class<?> argType = arg.getClass();
             if (argType == DataSetParameter.class)
             {
                DataSetParameter dsarg = (DataSetParameter) arg;
                char mode = (modes == null ? 'I' : modes.charAt(i));
                boolean isDsHandle = dsarg.getOriginalDataSet() == null;

                switch (mode)
                {
                   case 'I':
                   case 'i':
                      arg = isDsHandle ? new InputDataSetHandle(dsarg.getDatasetHandle(),
                                                                dsarg.getParameterOptions())
                                       : new InputDataSetParameter(dsarg.getDataset(),
                                                                   dsarg.getParameterOptions());
                      break;
                   case 'O':
                   case 'o':
                      arg = isDsHandle ? new OutputDataSetHandle(dsarg.getDatasetHandle(),
                                                                 dsarg.getParameterOptions())
                                       : new OutputDataSetParameter(dsarg.getDataset(),
                                                                    dsarg.getParameterOptions());
                      break;
                   case 'U':
                   case 'u':
                      arg = isDsHandle ? new InputOutputDataSetHandle(dsarg.getDatasetHandle(),
                                                                      dsarg.getParameterOptions())
                                       : new InputOutputDataSetParameter(dsarg.getDataset(),
                                                                         dsarg.getParameterOptions());
                      break;
                }
             }
             else if (argType == TableParameter.class)
             {
                char mode = (modes == null ? 'I' : modes.charAt(i));
                TableParameter tbarg = (TableParameter) arg;
                boolean isTbHandle = tbarg.getTableHandle().isUnknown();

                switch (mode)
                {
                   case 'I':
                   case 'i':
                      arg = isTbHandle ? new InputTableHandle(tbarg.getTableHandle(),
                                                                 tbarg.getParameterOptions())
                                       : new InputTableParameter(tbarg.getTable(), tbarg.getParameterOptions());
                      break;
                   case 'O':
                   case 'o':
                      arg = isTbHandle ? new OutputTableHandle(tbarg.getTableHandle(),
                                                                  tbarg.getParameterOptions())
                                       : new OutputTableParameter(tbarg.getTable(), tbarg.getParameterOptions());
                      break;
                   case 'U':
                   case 'u':
                      arg = isTbHandle ? new InputOutputTableHandle(tbarg.getTableHandle(),
                                                                       tbarg.getParameterOptions())
                                       : new InputOutputTableParameter(tbarg.getTable(),
                                                                       tbarg.getParameterOptions());
                      break;
                }
             }
             else if (arg instanceof BaseDataType && BaseDataType.isProxy((BaseDataType) arg))
             {
                arg = ((BaseDataType) arg).val();
             }

             args[i] = arg;
          }
       }

       /**
        * Stores global data relating to the state of the current context.
        */
       private static class WorkArea
       {
          /** Helper to use the ProcedureManager without any context local lookups. */
          private final ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();

          /** Helper to use the TransactionManager without any context local lookups. */
          private final TransactionManager.TransactionHelper tm = TransactionManager.getTransactionHelper();

          /** Helper to use the ErrorManager without any context local lookups. */
          private final ErrorManager.ErrorHelper em = ErrorManager.getErrorHelper();

          /** The BlockManager helper - must be initialized in 'obtain' to avoid recursive initialization. */
          private BlockManager.BlockManagerHelper bm = null;

          /** Stores the most recent RETURN-VALUE for this user's session. */
          private character returnValue = new character();

          /**
           * <code>true</code> if <code>RETURN-VALUE</code> was set during the call of the current root external
           * program. Used to determine if we need to update <code>RETURN-VALUE</code> after an appserver call.
           */
          private boolean returnValueSetInRootScope = false;

          /**
           * List of {@link Resolver} instances used for plain RUN statements.
           */
          private final List<Resolver> resolvers;

          /**
           * List of {@link Resolver} instances used for RUN IN handle,
           * DYNAMIC-FUNCTION(... IN handle) or FUNCTION ... IN handle cases.
           */
          private final ArrayList<Resolver> inHandleResolvers;

          /**
           * {@link InternalResolver} instance which searches the internal-entries
           * for a match.
           */
          private final Resolver internalResolver;

          /**
           * {@link InternalProcSuperResolver} instance which searches the
           * procedure's super-procedures for a match.
           */
          private final Resolver internalProcSuperResolver;

          /**
           * {@link SessionSuperResolver} instance which searches the sessions's
           * super-procedures for a match.
           */
          private final Resolver sessionSuperResolver;

          /**
           * {@link ExternalProgramResolver} instance which searches the external
           * program list for a match.
           */
          private final ExternalProgramResolver externalResolver;

          /**
           * Flag indicating if this call is an invocation from a remote, customer-specific
           * application.  When this flag is on, only external programs defined via the
           * {@link RemoteEntryPointResource} ACLs can be invoked.  This flag needs to be reset BEFORE
           * the remote API is invoked.
           */
          private boolean remoteInvoke = false;

          /** Flag indicating if last attempted invoke failed because of invalid arguments. */
          private boolean invalidArgs = false;

          /**
           * A cache of the targets, for each call-site emitted in the converted code, by their external program
           * instance.
           */
          private Map<Object, Map<InvokeConfig, LastInvokeDetails>> callSiteCache = new IdentityHashMap<>();

          /** A cache of the targets for external program calls, for each call-site.*/
          private Map<InvokeConfig, LastInvokeDetails> extProgcallSiteCache = new IdentityHashMap<>();

          /** The current invocation, used to cache the target when is not null. */
          private InvokeConfig currentInvoke = null;

          /** The current caller where to cache the invocation. */
          private Object currentCaller = null;

          /** Cached resolved procedure internal entries */
          private HashMap<InternalResolver.InternalEntryCacheKey, InternalEntry> cachedIEntries = new HashMap<>();

          private WorkArea()
          {
             // if PROPATH changes, then only external programs are affected - clear only that cache.
             EnvironmentOps.addSourcePathListener((path) -> extProgcallSiteCache.clear());

             internalResolver = new InternalResolver(this);
             internalProcSuperResolver = new InternalProcSuperResolver(this);
             sessionSuperResolver = new SessionSuperResolver(this);
             externalResolver = new ExternalProgramResolver(this);

             resolvers = new ArrayList<>();
             resolvers.add(internalResolver);
             resolvers.add(internalProcSuperResolver);
             resolvers.add(sessionSuperResolver);
             resolvers.add(externalResolver);

             inHandleResolvers = new ArrayList<>();
             inHandleResolvers.add(internalResolver);
             inHandleResolvers.add(internalProcSuperResolver);
             inHandleResolvers.add(sessionSuperResolver);
          }
       }

       /**
        * Simple container that stores and returns a context-local instance of
        * the global work area.
        */
       private static class ContextContainer
       extends ContextLocal<WorkArea>
       {
          /**
           * Obtains the context-local instance of the contained global work
           * area.
           *
           * @return   The work area associated with this context.
           */
          public WorkArea obtain()
          {
             WorkArea wa = this.get();
             if (wa.bm == null)
             {
                wa.bm = BlockManager.getHelper();
             }

             return wa;
          }

          /**
           * Initializes the work area, the first time it is requested within a
           * new context.
           *
           * @return   The newly instantiated work area.
           */
          protected synchronized WorkArea initialValue()
          {
             return new WorkArea();
          }
       }

       /**
        * Helper to expose APIs which have direct access to the context-local state.
        */
       static class ControlFlowOpsHelper
       {
          /** The {@link WorkArea} instance. */
          private final WorkArea wa;

          /**
           * Create a new instance and associate the given WorkArea instance.
           *
           * @param    wa
           *           The {@link WorkArea} instance.
           */
          public ControlFlowOpsHelper(WorkArea wa)
          {
             this.wa = wa;
          }

          /**
           * Sets the most recent RETURN-VALUE for the current user's session.
           *
           * @param    returnValue
           *           The RETURN-VALUE to set or <code>null</code> to assign the
           *           empty string.
           */
          public void setReturnValue(character returnValue)
          {
             if (returnValue == null)
             {
                wa.returnValue.assign(character.EMPTY_STRING);
             }
             else
             {
                wa.returnValue.assign(returnValue);
             }

             wa.returnValueSetInRootScope = wa.tm.isRootExternal();
          }

          /**
           * Sets the most recent RETURN-VALUE for the current user's session.
           *
           * @param    returnValue
           *           The RETURN-VALUE to set or <code>null</code> to assign the
           *           empty string.
           */
          public void setReturnValue(String returnValue)
          {
             wa.returnValue.assign(returnValue == null ? "" : returnValue);
             wa.returnValueSetInRootScope = wa.tm.isRootExternal();
          }
       }

       /**
        * Invoke the configured internal entry.
        */
       private static class InternalEntryCaller
       {
          /** Logger */
          private static final CentralLogger LOG = CentralLogger.get(InternalEntryCaller.class);

          /** Cache of methods by type, name, class, and signature */
          private static final Cache<CacheKey, CacheValue> cache = new LRUCache<>(8192);

          /** The external program instance where the internal entry's body is defined. */
          protected Object callerInstance;

          /** The java name of this internal entry. */
          protected String methodName;

          /** The legacy 4GL name of this internal entry. */
          protected String iename;

          /** The {@link WorkArea} instance. */
          protected WorkArea wa;

          /** The {@link Method} instance for this internal entry. */
          private Method mthd = null;

          /** The ReflectASM access point to this method. */
          private MethodAccess access = null;

          /** The method index in the MethodAccess instance. */
          private int index = -1;

          /** The handle associated with the {@link #callerInstance}. */
          private handle phandle;

          /** Worker which performs the {@link ProcedureManager.ProcedureHelper#pushCalleeInfo} call. */
          private Runnable pushWorker;

          /** Already resolved internal entry (used when invoking OO methods). */
          private InternalEntry ie = null;

          /**
           * Basic c'tor.
           *
           * @param    wa
           *           The context-local state in {@link WorkArea}.
           */
          public InternalEntryCaller(WorkArea wa)
          {
             this.wa = wa;
          }

          /**
           * Configure this instance to invoke the given internal entry.
           *
           * @param    wa
           *           The context-local state in {@link WorkArea}.
           * @param    phandle
           *           The procedure handle where the internal entry's body is
           *           defined.
           * @param    methodName
           *           The resolved java name for this internal entry.
           * @param    iename
           *           The legacy 4GL name of this internal entry.
           */
          public InternalEntryCaller(WorkArea wa, handle phandle, String methodName, String iename)
          {
             this(wa);

             this.phandle = new handle(phandle);
             this.callerInstance = phandle.get();
             this.methodName = methodName;
             this.iename = iename;
          }

          /**
           * Set the {@link #pushWorker}.
           *
           * @param    push
           *           The code which performs the {@link ProcedureManager.ProcedureHelper#pushCalleeInfo} call.
           */
          void setPushWorker(Runnable push)
          {
             this.pushWorker = push;
          }

          /**
           * Invoke the internal entry, using the {@link #mthd},
           * the {@link #callerInstance} and the passed arguments.
           * <p>
           * It performs an additional check, in case of remote invocation calls originating from
           * external, customer-specific application (when {@link WorkArea#remoteInvoke} flag is set):
           * access to the external program must be allowed via a {@link RemoteEntryPointResource} ACL.
           *
           * @param    modes
           *           An encoded string with the mode of each parameter specified as a
           *           character in the string.
           * @param    param
           *           The arguments for this Java method call.
           *
           * @return   The returned value
           *
           * @throws   Exception
           *           If any exceptions are encountered during method invocation.
           */
          final Object invoke(String modes, Object... param)
          throws Exception
          {
             WorkArea wa = work.obtain();
             if (wa.remoteInvoke)
             {
                wa.remoteInvoke = false;

                String pname = getExternalProgramName();

                SecurityManager sm = SecurityManager.getInstance();

                RemoteEntryPointResource repr =
                   (RemoteEntryPointResource) sm.getPluginInstance("remoteentrypoint");

                if (repr == null || !repr.isAllowed(pname))
                {
                   String msg = (repr == null) ?
                      String.format("Remote program '%s' cannot be accessed because the " +
                                       "RemoteEntryPointResource is NOT enabled.", pname) :
                      String.format("Access to remote program '%s' not allowed for user %s.",
                                    pname,
                                    sm.getUserId());

                   LOG.severe(msg);

                   throw new SilentUnwindException(new IllegalStateException(msg));
                }
             }

             return invokeImpl(modes, param);
          }

          /**
           * Invoke the internal entry, using the {@link #mthd},
           * the {@link #callerInstance} and the passed arguments.
           *
           * @param    modes
           *           An encoded string with the mode of each parameter specified as a
           *           character in the string.
           * @param    param
           *           The arguments for this Java method call.
           *
           * @return   The returned value
           *
           * @throws   Exception
           *           If any exceptions are encountered during method invocation.
           */
          Object invokeImpl(String modes, Object... param)
          throws Exception
          {
             InvokeConfig currentInvoke = wa.currentInvoke;
             Object currentCaller = wa.currentCaller;

             if (currentInvoke != null)
             {
                // the InternalEntryCaller.mthd will be affected by other calls, cache it now.
                cacheInvocation(wa, this, currentInvoke, currentCaller, param);
             }

             // the reference is no longer useful, reset it
             wa.currentInvoke = null;
             wa.currentCaller = null;

             boolean isStatic = Modifier.isStatic(mthd.getModifiers());
             Object instance = isStatic ? null : callerInstance;

             try
             {
                pushWorker.run();
                wa.bm.markPendingDynCall();

                if (access != null)
                {
                   return access.invoke(instance, index, param);
                }
                else
                {
                   mthd.setAccessible(true);
                   return mthd.invoke(instance, param);
                }
             }
             finally
             {
                wa.pm.popCalleeInfo();
             }
          }

          /**
           * Get the signature, as defined by the parameters.
           *
           * @param    param
           *           The arguments for this Java method call.
           *
           * @return   An array with the type of each parameter.
           */
          static Class<?>[] getSignature(Object... param)
          {
             Class<?>[] signature = new Class[param.length];

             for (int i = 0; i < signature.length; i++)
             {
                Class<?> type;
                Object p = param[i];
                if (p instanceof BufferReference)
                {
                   // the only provided support is for buffer objects.
                   Class<?> dmoBufIfc = RecordBuffer.getDMOBufInterfaceForObject(p);

                   if (dmoBufIfc == null)
                   {
                      throw new RuntimeException("Dynamic proxy not supported for parameter " + p);
                   }

                   type = dmoBufIfc;
                }
                else
                {
                   type = p == null ? Object.class : p.getClass();
                }

                if (type.isAnonymousClass())
                {
                   type = type.getSuperclass();
                }
                signature[i] = type;
             }

             return signature;
          }

          /**
           * Validate the given parameters against the method's real signature using the corresponding
           * parameter passing mode:
           * <ul>
           *    <li><strong>INPUT</strong>If the parameter's type is compatible with the real type
           *          (from the {@link #mthd method's} arguments), then cast it to it.
           *    <li><strong>OUTPUT</strong>If the real type is compatible with the parameter's type
           *          (from the {@link #mthd method's} arguments), the use a default value for the
           *          expected type. The compatibility assures the conversion will be possible at
           *          return time.
           *    <li><strong>INPUT-OUTPUT</strong>Both conversion must be available for this parameter
           *          to be validated. A cast at this moment will assure INPUT compatibility and a
           *          second cast when returning will also be needed.
           * </ul>
           * INPUT
           *
           * If all are valid, then {@link ArgValidationErrors#NO_ERROR} is returned. Else:
           * <ul>
           * <li>if the argument count does not match, then
           *     {@link ArgValidationErrors#INCORRECT_COUNT} is returned.</li>
           * <li>if the types of the arguments do not match, then
           *     {@link ArgValidationErrors#INCORRECT_TYPES} is returned.</li>
           * </ul>
           *
           * @param   function
           *          <code>true</code> if this is a function call.
           * @param   modes
           *          The types of parameters: INPUT(I) / OUTPUT (O) / INPUT-OUTPUT (U).
           * @param   extentInfo
           *          An output parameter array that provides the caller with additional information
           *          about the extent sizes when they don't match. The first item is the size of the
           *          argument (provided extent), the second is the parameter (expected extent).
           * @param   param
           *          The arguments for this Java method call.
           *
           * @return   One of the {@link ArgValidationErrors} constants.
           */
          ArgValidationErrors valid(boolean function, String modes, int[] extentInfo, Object... param)
          {
             return valid(this, function, modes, extentInfo, param);
          }

          /**
           * Validate the given parameters against the method's real signature using the corresponding
           * parameter passing mode:
           * <ul>
           *    <li><strong>INPUT</strong>If the parameter's type is compatible with the real type
           *          (from the {@link #mthd method's} arguments), then cast it to it.
           *    <li><strong>OUTPUT</strong>If the real type is compatible with the parameter's type
           *          (from the {@link #mthd method's} arguments), the use a default value for the
           *          expected type. The compatibility assures the conversion will be possible at
           *          return time.
           *    <li><strong>INPUT-OUTPUT</strong>Both conversion must be available for this parameter
           *          to be validated. A cast at this moment will assure INPUT compatibility and a
           *          second cast when returning will also be needed.
           * </ul>
           * INPUT
           *
           * If all are valid, then {@link ArgValidationErrors#NO_ERROR} is returned. Else:
           * <ul>
           * <li>if the argument count does not match, then
           *     {@link ArgValidationErrors#INCORRECT_COUNT} is returned.</li>
           * <li>if the types of the arguments do not match, then
           *     {@link ArgValidationErrors#INCORRECT_TYPES} is returned.</li>
           * </ul>
           *
           * @param   caller
           *          The {@link InternalEntryCaller} instance.
           * @param   function
           *          <code>true</code> if this is a function call.
           * @param   modes
           *          The types of parameters: INPUT(I) / OUTPUT (O) / INPUT-OUTPUT (U).
           * @param   extentInfo
           *          An output parameter array that provides the caller with additional information
           *          about the extent sizes when they don't match. The first item is the size of the
           *          argument (provided extent), the second is the parameter (expected extent).
           * @param   param
           *          The arguments for this Java method call.
           *
           * @return   One of the {@link ArgValidationErrors} constants.
           */
          private static ArgValidationErrors valid(InternalEntryCaller caller,
                                                   boolean             function,
                                                   String              modes,
                                                   int[]               extentInfo,
                                                   Object...           param)
          {
             // convert the parameters to BDT, if possible
             for (int i = 0; i < param.length; i++)
             {
                Object p = param[i];
                Object n = p;

                if (p instanceof Long)
                {
                   n = new int64((Long) p);
                }
                else if (p instanceof Double)
                {
                   n = new decimal((Double) p);
                }
                else if (p instanceof Float)
                {
                   n = new decimal((Float) p);
                }
                else if (p instanceof Number)
                {
                   n = new integer((Number) p);
                }
                else if (p instanceof Boolean)
                {
                   n = new logical((Boolean) p);
                }
                else if (p instanceof String)
                {
                   n = new character((String) p);
                }

                param[i] = n;
             }

             Class<?>[] signature = getSignature(param); // required signature
             Integer[] parametersExtent = new Integer[param.length];
             for (int i = 0; i < param.length; i++)
             {
                if (param[i] != null && param[i].getClass().isArray())
                {
                   parametersExtent[i] = Array.getLength(param[i]);
                }
             }

             Class<?> cls = caller.callerInstance.getClass();

             ArgValidationErrors rv;
             // a method may be set when invoking a legacy OO method - here we just need to prepare
             // and do a final validation of the arguments
             Method method = caller.mthd;

             // check the cache first; we not only cache successful lookups, but also remember failed
             // lookups, so we don't repeat them (it is quite expensive to fail to find a method, as
             // it involves two exceptions being thrown, a search through all methods of the class,
             // and a good bit of fuzzy matching logic)

             CacheKey key = new CacheKey(function, cls, caller.methodName, signature, parametersExtent);
             CacheValue value = getCache(key, method); // TODO: rename [value] with a more appropriate name
             if (method == null)
             {
                if (value != null)
                {
                   // only return immediately if we had an exact signature match, or if we failed to
                   // find a method at all previously for this set of criteria; for a fuzzy match, we
                   // still have parameter type massaging work to do below
                   if (value.exactMatch)
                   {
                      if (value.method != null)
                      {
                         caller.mthd = value.method;
                         caller.access = value.access;
                         caller.index = value.index;
                      }

                      return value.returnValue;
                   }
                }
                else
                {
                   try
                   {
                      method = cls.getMethod(caller.methodName, signature);
                      LegacySignature legacySignature = method.getAnnotation(LegacySignature.class);
                      LegacyParameter[] legacyParameters = legacySignature.parameters();

                      if (legacyParameters.length != param.length)
                      {
                         // cache failed result as well, so we don't need to repeat validation
                         rv = ArgValidationErrors.INCORRECT_COUNT;
                         putCache(key, null, rv, true);

                         return rv;
                      }

                      // check for the right extent parameters
                      for (int i = 0; i < param.length; i++)
                      {
                         if (parametersExtent[i] != null)
                         {
                            int expectedExt = legacyParameters[i].extent();
                            if (!parametersExtent[i].equals(expectedExt) &&
                                expectedExt != SourceNameMapper.DYNAMIC_EXTENT)
                            {
                               // cache failed result as well, so we don't need to repeat validation
                               rv = ArgValidationErrors.BAD_ARRAY_DIMENSION;
                               putCache(key, null, rv, true);

                               return rv;
                            }
                         }
                      }
                      rv = ArgValidationErrors.NO_ERROR;

                      value = putCache(key, method, rv, true);

                      caller.mthd = value.method;
                      caller.access = value.access;
                      caller.index = value.index;

                      return rv;
                   }
                   catch (Exception e)
                   {
                      method = null;
                   }

                   // fallback - check if the method is declared, but private
                   try
                   {
                      method = cls.getDeclaredMethod(caller.methodName, signature);
                      rv = ArgValidationErrors.NO_ERROR;

                      value = putCache(key, method, rv, true);

                      caller.mthd = value.method;
                      caller.access = value.access;
                      caller.index = value.index;

                      return rv;
                   }
                   catch (Exception e)
                   {
                      method = null;
                   }
                }
             }

             // TODO: add support for the longchar-to-char narrowing and char-to-longchar widening

             final Class<?> AEP = AbstractExtentParameter.class;
             final String extProgName = ProcedureManager.getAbsoluteName(caller.callerInstance);

             // go through all the methods (or the cached one only), and do some fuzzy parameter type
             // matching
             Method[] methods = method != null
                                  ? new Method[] { method }
                                  : (value != null)
                                        ? new Method[] { value.method }
                                        : cls.getDeclaredMethods();
             boolean correctNo = false;
             boolean hasParams = false;
             boolean cacheFailedResult = true;
             for (Method m : methods)
             {
                if (!m.getName().equals(caller.methodName))
                {
                   // if the name of the method does not match skip to next method
                   continue;
                }
                Class<?>[] mtypes = m.getParameterTypes();
                hasParams = (mtypes.length > 0);

                if (mtypes.length != param.length)
                {
                   // if parameter count does not match skip this method
                   continue;
                }
                if (modes == null)
                {
                   // if the call comes from ControlFlowOps.runSuper(), the modes are unknown
                   modes = SourceNameMapper.getParameterModes(extProgName, caller.ie, caller.iename, function);

                   // for builtin classes, there is no mapping (yet) - assume always INPUT
                   // TODO: annotations with parameter modes?
                   if (modes == null && caller.callerInstance instanceof BaseObject)
                   {
                      modes = "";
                      for (int i = 0; i < param.length; i++)
                      {
                         modes = modes + "I";
                      }
                   }
                }

                // save the "closest" method match in case of failure
                if (caller.mthd == null)
                {
                   caller.mthd = m;
                }

                correctNo = true;
                LegacySignature lsig = m.getAnnotation(LegacySignature.class);

                BiFunction<Class<?>, Class<?>, Constructor<?>> defaultCtor = (p1, p2) ->
                {
                   Constructor<?> ctor = Utils.findConstructor(p1, p2);
                   if (ctor == null)
                   {
                      throw new RuntimeException("Can't find " + p1 + " ctor for " + p2);
                   }

                   return ctor;
                };
                BiFunction<Class<?>, Class<?>, Constructor<?>> objectCtor = (p1, p2) ->
                {
                   try
                   {
                      return p1.getConstructor(Class.class, p2);
                   }
                   catch (NoSuchMethodException |
                          SecurityException e)
                   {
                      throw new RuntimeException(e);
                   }
                };
                Class<?>[] qualifiedType = new Class<?>[1];
                BiFunction<Constructor<?>, Object, BaseDataType> defaultInstance = (p1, p2) ->
                {
                   try
                   {
                      return (BaseDataType) p1.newInstance(p2);
                   }
                   catch (ReflectiveOperationException   |
                          IllegalArgumentException e)
                   {
                      throw new RuntimeException(e);
                   }
                };
                BiFunction<Constructor<?>, Object, BaseDataType> objectInstance = (p1, p2) ->
                {
                   try
                   {
                      return (BaseDataType) p1.newInstance(qualifiedType[0], p2);
                   }
                   catch (IllegalArgumentException |
                          ReflectiveOperationException e)
                     {
                        throw new RuntimeException(e);
                     }
                };

                // try to cast each parameter to the expected type. If it works, then use this method
                boolean ok = true;
                Object[] newPars = new Object[param.length];
                for (int i = 0; i < param.length; i++)
                {
                   newPars[i] = param[i];
                   Class<?> pcls = param[i].getClass();
                   if (pcls.isAnonymousClass())
                   {
                      pcls = pcls.getSuperclass();
                   }
                   Class<?> argType = signature[i];
                   Class<?> expectedType = mtypes[i];

                   qualifiedType[0] = null;
                   BiFunction<Class<?>, Class<?>, Constructor<?>> parCtor = defaultCtor;
                   BiFunction<Constructor<?>, Object, BaseDataType> instance = defaultInstance;
                   if (lsig != null)
                   {
                      LegacyParameter lpar = lsig.parameters()[i];
                      if (lpar.type().equals("JOBJECT") &&
                          lpar.qualified() != null      &&
                          !lpar.qualified().isEmpty())
                      {
                         try
                         {
                            qualifiedType[0] = Class.forName(lpar.qualified());
                            parCtor = objectCtor;
                            instance = objectInstance;
                         }
                         catch (ClassNotFoundException e)
                         {
                            // something went wrong...
                            LOG.log(Level.SEVERE, "Can't resolve qualified type " + lpar.qualified(), e);
                            ok = false;
                            break;
                         }
                      }

                      // check for wrong extent lengths
                      if (parametersExtent[i] != null)
                      {
                         int expectedExt = lpar.extent();
                         if (expectedExt != SourceNameMapper.DYNAMIC_EXTENT &&
                            !parametersExtent[i].equals(expectedExt))
                         {
                            // cache failed result as well, so we don't need to repeat validation
                            rv = ArgValidationErrors.BAD_ARRAY_DIMENSION;
                            putCache(key, null, rv, true);

                            return rv;
                         }
                      }
                   }

                   // check if passing a non-array to an array or an array to non-array.
                   // do not check arrays containing non-BDT components.
                   if (value == null &&
                       ((argType.isArray() != expectedType.isArray() &&
                         (!argType.isArray() ||
                          BaseDataType.class.isAssignableFrom(argType.getComponentType()))) ||
                        AEP.isAssignableFrom(argType) != AEP.isAssignableFrom(expectedType)))
                   {
                      boolean parExtent = argType.isArray() || AEP.isAssignableFrom(argType);

                      int expectedExtent = SourceNameMapper.getExtentForParam(extProgName,
                                                                              caller.ie,
                                                                              caller.iename,
                                                                              function,
                                                                              i);
                      int paramExtent = 0;
                      if (parExtent)
                      {
                         BaseDataType[] pvar = null;
                         if (AEP.isAssignableFrom(argType))
                         {
                            pvar = ((AbstractExtentParameter) newPars[i]).getVariableSafe();
                         }
                         else
                         {
                            pvar = (BaseDataType[]) newPars[i];
                         }

                         paramExtent = pvar.length;
                      }

                      if (expectedExtent == 0)
                      {
                         // expected param is dynamic-extent
                         rv = parExtent
                              ? ArgValidationErrors.DYN_ARRAY_FOR_NON_ARRAY
                              : ArgValidationErrors.NON_ARRAY_FOR_DYN_ARRAY;
                      }
                      else
                      {
                         // expected param is fixed-extent
                         rv = parExtent
                              ? ArgValidationErrors.ARRAY_FOR_NON_ARRAY
                              : ArgValidationErrors.NON_ARRAY_FOR_ARRAY;
                      }

                      extentInfo[1] = parExtent ? 0 : expectedExtent;
                      extentInfo[0] = parExtent ? paramExtent : 0;

                      // cache failed result as well, so we don't need to repeat validation
                      putCache(key, null, rv, true);

                      return rv;
                   }

                   // provide compatibility between resources and handles
                   if (WrappedResource.class.isAssignableFrom(argType) &&
                       handle.class.equals(expectedType))
                   {
                      newPars[i] = new handle((WrappedResource) param[i]);
                      // TODO: check the OUTPUT mode case
                      continue;
                   }

                   // provide compatibility between comhandles and handles
                   if (comhandle.class.isAssignableFrom(argType) &&
                       handle.class.equals(expectedType))
                   {
                      newPars[i] = new handle((comhandle) param[i]);
                      // TODO: check the OUTPUT mode case
                      continue;
                   }

                   if (!expectedType.isAssignableFrom(argType))
                   {
                      boolean wasPending = caller.wa.em.isPending();
                      caller.wa.em.setPending(false);
                      try
                      {
                         // if the param's type is unknown class, construct the unknown for the
                         // expectedType
                         if (unknown.class.equals(argType))
                         {
                            // for some data types (date, datetime, datetime-tz, raw) the
                            // unknown value is not created using default constructor
                            newPars[i] = BaseDataType.generateUnknown(expectedType);
                            // TODO: check the OUTPUT mode case
                         }
                         else
                         {
                            Class<?> toCompType;
                            Class<?> fromCompType;
                            boolean isArray = expectedType.isArray();
                            if (isArray)
                            {
                               // take into consideration the elements' type
                               toCompType = expectedType.getComponentType();
                               fromCompType = pcls.getComponentType();
                            }
                            else
                            {
                               toCompType = expectedType;
                               fromCompType = pcls;
                            }
                            Constructor<?> fwCtor;
                            Constructor<?> bwCtor = null;
                            // checking the parameter passing type: IN, OUT, IN-OUT
                            switch (modes.charAt(i))
                            {
                               case 'I':
                                  boolean i64Quirk = toCompType == integer.class && fromCompType == int64.class;

                                  if (i64Quirk)
                                  {
                                     Function<int64, integer> farg =
                                        (v) -> InputParameter.function(integer.class, v);
                                     Function<int64, integer> parg =
                                        (v) -> InputParameter.procedure(integer.class, v);

                                     Function<int64, integer> zarg = function ? farg : parg;

                                     if (isArray)
                                     {
                                        // copy the newPars[i] array through fwCtor morphism
                                        Object[] arrayArg = (Object[]) newPars[i];
                                        Object[] arrayParam = (Object[])
                                           Array.newInstance(toCompType, arrayArg.length);
                                        for (int k = 0; k < arrayArg.length; k++)
                                        {
                                           arrayParam[k] = zarg.apply((int64) arrayArg[i]);
                                        }
                                        newPars[i] = arrayParam;
                                     }
                                     else
                                     {
                                        newPars[i] = zarg.apply((int64) param[i]);
                                     }
                                  }
                                  else
                                  {
                                     try
                                     {
                                        fwCtor = parCtor.apply(toCompType, fromCompType);
                                     }
                                     catch (Throwable e)
                                     {
                                        // on failure try the 'generic' conversion constructor
                                        fwCtor = parCtor.apply(toCompType, BaseDataType.class);
                                     }
                                     if (isArray)
                                     {
                                        // copy the newPars[i] array through fwCtor morphism
                                        Object[] arrayArg = (Object[]) newPars[i];
                                        Object[] arrayParam = (Object[])
                                           Array.newInstance(toCompType, arrayArg.length);
                                        for (int k = 0; k < arrayArg.length; k++)
                                        {
                                           arrayParam[k] = instance.apply(fwCtor, arrayArg[i]);
                                        }
                                        newPars[i] = arrayParam;
                                     }
                                     else
                                     {
                                        newPars[i] = instance.apply(fwCtor, param[i]);
                                     }
                                  }
                                  break;
                               case 'O':
                                  // check existence of the return cast:
                                  if (toCompType == jobject.class || fromCompType == jobject.class)
                                  {
                                     bwCtor = fromCompType.getConstructor(BaseDataType.class);
                                  }
                                  else
                                  {
                                     bwCtor = fromCompType.getConstructor(toCompType);
                                  }
                                  if (qualifiedType[0] != null)
                                  {
                                     newPars[i] = new jobject(qualifiedType[0]);
                                  }
                                  else
                                  {
                                     // the output parameters aren't / don't need to be initiated
                                     newPars[i] = BaseDataType.generateDefault(toCompType);
                                  }
                                  break;
                               case 'U':
                                  // check existence of the return cast:
                                  if (fromCompType == jobject.class)
                                  {
                                     bwCtor = fromCompType.getConstructor(BaseDataType.class);
                                  }
                                  else
                                  {
                                     bwCtor = fromCompType.getConstructor(toCompType);
                                  }

                                  if (toCompType == jobject.class)
                                  {
                                     fwCtor = toCompType.getConstructor(Class.class, BaseDataType.class);
                                  }
                                  else
                                  {
                                     fwCtor = toCompType.getConstructor(fromCompType);
                                  }

                                  newPars[i] = instance.apply(fwCtor, param[i]);
                                  break;
                               case 'B':
                                  // check the DMO compatibility between argType and expectedType
                                  // 1. DMOs will match even when the table options are different (e.g. no-undo status)
                                  // 2. The table name is important when checking the DMO compatibility
                                  Class<? extends DataModelObject> argDmo = DmoMetadataManager.getDmoIface(argType);
                                  Class<? extends DataModelObject> expectedDmo = DmoMetadataManager.getDmoIface(expectedType);
                                  if (DmoMetadataManager.isSchemaMatch(argDmo, expectedDmo, true))
                                  {
                                     // create a proxy from the expectedType to the argument's instance
                                     newPars[i] = RecordBuffer.createProxy(expectedType, param[i]);
                                  }
                               break;
                            }
                            if (bwCtor != null)
                            {
                               // in the event the OUTPUT value must be converted back:
                               FieldAssigner.update(((BaseDataType) param[i]),
                                                    ((BaseDataType) newPars[i]),
                                                    bwCtor);
                            }
                         }
                         if (caller.wa.em.isPending())
                         {
                            // if any errors occurred during parameter matching
                            // (eg.: INTEGER overflow) procedure cannot be called
                            ok = false;
                            break;
                         }
                      }
                      catch (Throwable t)
                      {
                         // was not able to convert this param
                         ok = false;

                         // check if a condition was raised. if so, do not
                         // search for any other c'tors
                         boolean raisedCondition = false;
                         while (t != null)
                         {
                            if (t instanceof ConditionException)
                            {
                               raisedCondition = true;
                               cacheFailedResult = false;
                            }
                            t = t.getCause();
                         }

                         if (raisedCondition)
                         {
                            break;
                         }
                      }
                      finally
                      {
                         caller.wa.em.setPending(caller.wa.em.isPending() || wasPending);
                      }

                      // fallback, maybe there is a c'tor with a compatible type
                      if (!ok)
                      {
                         Constructor<?>[] ctors;
                         Constructor<?> bwCtor = null;
                         // checking the parameter passing type: IN, OUT, IN-OUT
                         switch (modes.charAt(i))
                         {
                            case 'I':
                               ctors = expectedType.getConstructors();
                               for (Constructor<?> ctor : ctors)
                               {
                                  Class<?>[] ctypes = ctor.getParameterTypes();
                                  if (ctypes.length == 1 && ctypes[0].isAssignableFrom(pcls))
                                  {
                                     try
                                     {
                                        newPars[i] = ctor.newInstance(param[i]);
                                        ok = true;// found my c'tor
                                        break;
                                     }
                                     catch (Exception e)
                                     {
                                        // ignore, ok stays false
                                     }
                                  }
                               }
                               break;

                            case 'O':
                               // check existence of the return cast:
                               ctors = pcls.getConstructors();
                               for (Constructor<?> ctor : ctors)
                               {
                                  Class<?>[] ctypes = ctor.getParameterTypes();
                                  if (ctypes.length == 1 &&
                                      ctypes[0].isAssignableFrom(expectedType))
                                  {
                                     try
                                     {
                                        bwCtor = ctor;
                                        newPars[i] = BaseDataType.generateDefault(expectedType);
                                        ok = true; // found my return cast c'tor
                                        break; // for each c'tor
                                     }
                                     catch (Exception e)
                                     {
                                        // ignore, ok stays false
                                     }
                                  }
                               }
                               break;

                            case 'U':
                               // check existence of the return cast:
                               ctors = pcls.getConstructors();
                               for (Constructor<?> ctor : ctors)
                               {
                                  Class<?>[] ctypes = ctor.getParameterTypes();
                                  if (ctypes.length == 1 &&
                                         ctypes[0].isAssignableFrom(expectedType))
                                  {
                                     try
                                     {
                                        bwCtor = ctor;
                                        ok = true; // found my return cast c'tor
                                        break; // for each c'tor
                                     }
                                     catch (Exception e)
                                     {
                                        // ignore, ok stays false
                                     }
                                  }
                               }
                               // but use as standard INPUT for now, if it exist
                               if (ok)
                               {
                                  ok = false;
                                  ctors = expectedType.getConstructors();
                                  for (Constructor<?> ctor : ctors)
                                  {
                                     Class<?>[] ctypes = ctor.getParameterTypes();
                                     if (ctypes.length == 1 && ctypes[0].isAssignableFrom(pcls))
                                     {
                                        try
                                        {
                                           newPars[i] = ctor.newInstance(param[i]);
                                           ok = true;// found my c'tor
                                           break;
                                        }
                                        catch (Exception e)
                                        {
                                           // ignore, ok stays false
                                        }
                                     }
                                  }
                               }
                               if (!ok)
                               {
                                  // if forward conversion fails, bwCtor is useless
                                  bwCtor = null;
                               }
                               break;
                         } // ~switch (mode)
                         if (bwCtor != null)
                         {
                            // in the event the OUTPUT value must be converted back:
                            FieldAssigner.update(((BaseDataType) param[i]),
                                                 ((BaseDataType) newPars[i]),
                                                 bwCtor);
                         }
                      }

                      // if still wasn't found, then exit.
                      if (!ok)
                      {
                         break;
                      }
                   }
                   else if (AEP.isAssignableFrom(expectedType) &&
                            AEP.isAssignableFrom(argType))
                   {
                      AbstractExtentParameter aep = (AbstractExtentParameter) newPars[i];
                      int argExt = aep.getVariableSafe().length;
                      int paramExt = SourceNameMapper.getExtentForParam(extProgName,
                                                                        caller.ie,
                                                                        caller.iename,
                                                                        function,
                                                                        i);

                      if (paramExt > 0 && paramExt != argExt)
                      {
                         // if paramExt is 0 it is dynamic extent so any extent is acceptable
                         extentInfo[0] = argExt;
                         extentInfo[1] = paramExt;
                         return ArgValidationErrors.BAD_ARRAY_DIMENSION; // cache it?
                      }

                      Class<?> argTypeE =
                         ((AbstractExtentParameter) param[i]).getVariableSafe().getClass().getComponentType();
                      Class<?> paramTypeE =
                         SourceNameMapper.getTypeForParam(extProgName, caller.ie, caller.iename, function, i);

                      if (argTypeE != paramTypeE)
                      {
                         Constructor<?> fwCtor = null;
                         Constructor<?> bwCtor = null;
                         try
                         {
                            // checking the parameter passing type: IN, OUT, IN-OUT
                            switch (modes.charAt(i))
                            {
                               case 'I':
                                  // this should not be normally reachable,
                                  // here we handle only wrapped EXTENT fields/variable
                                  throw new RuntimeException("Invalid execution path");
                               case 'U':
                                  try
                                  {
                                     fwCtor = paramTypeE.getConstructor(argTypeE);
                                  }
                                  catch (NoSuchMethodException e)
                                  {
                                     // on failure try the 'generic' conversion constructor
                                     fwCtor = paramTypeE.getConstructor(BaseDataType.class);
                                  }
                                  // no break, fall through!
                               case 'O':
                                  // check existence of the return cast:
                                  try
                                  {
                                     bwCtor = argTypeE.getConstructor(paramTypeE);
                                  }
                                  catch (NoSuchMethodException e)
                                  {
                                     // on failure try the 'generic' conversion constructor
                                     bwCtor = argTypeE.getConstructor(BaseDataType.class);
                                  }
                                  aep.setReturnConversion(argTypeE, paramTypeE, fwCtor, bwCtor);
                                  break;
                            }
                         }
                         catch (Throwable t)
                         {
                            // was not able to convert this param
                            ok = false;

                            // check if a condition was raised. if so, do not search for any
                            // other c'tors
                            boolean raisedCondition = false;
                            while (t != null)
                            {
                               if (t instanceof ConditionException)
                               {
                                  raisedCondition = true;
                               }
                               t = t.getCause();
                            }

                            if (raisedCondition)
                            {
                               break;
                            }
                         }
                      }
                      // otherwise should be no problem, the same class of array elements
                   }
                }

                if (ok)
                {
                   // was able to cast the parameters to expected types
                   System.arraycopy(newPars, 0, param, 0, param.length);
                   rv = ArgValidationErrors.NO_ERROR;

                   if (value == null)
                   {
                      value = putCache(key, m, rv, false);
                   }

                   caller.mthd = value.method;
                   caller.access = value.access;
                   caller.index = value.index;

                   return rv;
                }
             } // ~ for each method

             // we failed fuzzy parameter matching; determine what type of error to report
             rv = !correctNo
                  ? (!hasParams ? ArgValidationErrors.UNEXPECTED_PARAM
                                : ArgValidationErrors.INCORRECT_COUNT)
                  : ArgValidationErrors.INCORRECT_TYPES;

             //try to specialize the ArgValidationErrors.INCORRECT_TYPES by checking the extents
             if (rv == ArgValidationErrors.INCORRECT_TYPES)
             {
                method = caller.mthd;
                LegacySignature legacySignature = method.getAnnotation(LegacySignature.class);
                LegacyParameter[] legacyParameters = legacySignature.parameters();

                // check for the right extent parameters
                for (int i = 0; i < param.length; i++)
                {
                   if (parametersExtent[i] != null)
                   {
                      int expectedExt = legacyParameters[i].extent();
                      if (!parametersExtent[i].equals(expectedExt))
                      {
                         // cache failed result as well, so we don't need to repeat validation
                         rv = ArgValidationErrors.BAD_ARRAY_DIMENSION;
                         putCache(key, null, rv, true);
                         break;
                      }
                   }
                }
             }

             if (cacheFailedResult)
             {
                // cache failed result as well, so we don't need to repeat validation
                putCache(key, null, rv, true);
             }

             return rv;
          }

          /**
           * Get the java method name for this internal entry.
           *
           * @return   See above.
           */
          String getMethodName()
          {
             return methodName;
          }

          /**
           * Get the class name of the external program to which this internal
           * entry belongs.
           *
           * @return   See above.
           */
          String getClassName()
          {
             return callerInstance.getClass().getName();
          }

          /**
           * Get the external program instance where this internal entry is
           * defined.
           *
           * @return   See above.
           */
          Object getCallerInstance()
          {
             return callerInstance;
          }

          /**
           * Get the legacy name of this internal entry.
           *
           * @return   See above.
           */
          String getInternalEntryName()
          {
             return iename;
          }

          /**
           * Get the legacy name of the external program where the execution will take place.
           *
           * @return   See above.
           */
          String getExternalProgramName()
          {
             return ProcedureManager.getAbsoluteName(callerInstance);
          }

          /**
           * Get the string representation of the parameter modes defined for this
           * internal entry.
           *
           * @param    function
           *           <code>true</code> if this is a function call.
           *
           * @return   See above.
           */
          String getParameterModes(boolean function)
          {
             if (ie != null)
             {
                return ie.getParameterModes();
             }

             String pname = ProcedureManager.getAbsoluteName(callerInstance);
             String iename = getInternalEntryName();
             return SourceNameMapper.getParameterModes(pname, ie, iename, function);
          }

          /**
           * Get the parameters defined for this internal entry.
           *
           * @param    function
           *           <code>true</code> if this is a function call.
           *
           * @return   See above.
           */
          List<Parameter> getParameters(boolean function)
          {
             if (ie != null)
             {
                return ie.getParameterList();
             }

             String pname = ProcedureManager.getAbsoluteName(callerInstance);
             String iename = getInternalEntryName();
             return SourceNameMapper.getParameters(pname, ie, iename, function);
          }

          /**
           * Get the {@link #phandle} field.
           *
           * @return   See above.
           */
          handle getPhandle()
          {
             return phandle;
          }

          /**
           * Get a cached value, if any, for the given key.
           *
           * @param   key
           *          Cache key.
           * @param   mthd
           *          Method instance to validate against the cached value.  This is needed in OO dynamic invoke
           *          cases where the method resolution is not done via the runtime type, but via a type determined
           *          at conversion type.   May be <code>null</code>.
           *
           * @return  Cached value, or <code>null</code>.
           */
          private static CacheValue getCache(CacheKey key, Method mthd)
          {
             CacheValue value;

             synchronized (cache)
             {
                value = cache.get(key);

                // if the target method is known, the resolved method must be the same
                if (value != null        &&
                    mthd != null         &&
                    value.method != null &&
                    !value.method.equals(mthd))
                {
                   // this can happen in dynamic poly cases, where the lookup type is enforced from conversion
                   cache.remove(key);
                   value = null;
                }
             }

             return value;
          }

          /**
           * Cache the specified information under the given cache key.
           *
           * @param   key
           *          Cache key.
           * @param   method
           *          Method found matching a given name and signature, or {@code null} if no
           *          match was found.
           * @param   returnValue
           *          Enumerated return value for {@link #valid(boolean, String, int[], Object...)}
           *          method.
           * @param   exactMatch
           *          <code>true</code> to indicate the method signature stored in the key exactly
           *          matches that of the cached method; <code>false</code> to indicate one or more
           *          method arguments needs to be converted to a compatible type.
           *
           * @return  the created cache.
           */
          private static CacheValue putCache(CacheKey key,
                                      Method method,
                                      ArgValidationErrors returnValue,
                                      boolean exactMatch)
          {
             CacheValue value = new CacheValue(method, returnValue, exactMatch);

             synchronized (cache)
             {
                cache.put(key, value);
             }

             return value;
          }

          /**
           * A cache key which encapsulates the name of a method, its signature and containing class,
           * and whether that method represents a converted Progress function or a procedure.
           */
          private static class CacheKey
          {
             /** <code>true</code> if method represents a Progress function */
             final boolean function;

             /** Method's enclosing class */
             final Class<?> callerClass;

             /** Name of the method */
             final String methodName;

             /** Method's signature types */
             final Class<?>[] signature;

             /** The coresponding parameter's extent */
             final Integer[] paramExtent;

             /**
              * Constructor.
              *
              * @param   function
              *          <code>true</code> if method represents a Progress function.
              * @param   callerClass
              *          Method's enclosing class.
              * @param   methodName
              *          Name of the method.
              * @param   signature
              *          Method's signature types.
              * @param   paramExtent
              *          The extent of the parameters of the method.
              */
             CacheKey(boolean function,
                      Class<?> callerClass,
                      String methodName,
                      Class<?>[] signature,
                      Integer[] paramExtent)
             {
                this.function = function;
                this.callerClass = callerClass;
                this.methodName = methodName;
                this.signature = signature;
                this.paramExtent = paramExtent;
             }

             /**
              * Generate this object's hash code, based on its content, in a manner consistent with
              * {@link #equals(Object)}.
              *
              * @return  Hash code.
              */
             @Override
             public int hashCode()
             {
                int result = 17;

                result = 37 * result + (function ? 0 : 1);
                result = 37 * result + callerClass.hashCode();
                result = 37 * result + methodName.hashCode();
                result = 37 * result + Arrays.hashCode(signature);
                result = 37 * result + Arrays.hashCode(paramExtent);

                return result;
             }

             /**
              * Indicate whether this object is equivalent with the given object, based on both of
              * their type and content, in a manner consistent with {@link #hashCode()}.
              *
              * @return  <code>true</code> if this object is equivalent with the given object, else
              *          <code>false</code>.
              */
             @Override
             public boolean equals(Object o)
             {
                if (!(o instanceof CacheKey))
                {
                   return false;
                }

                CacheKey that = (CacheKey) o;

                if (this.function != that.function)
                {
                   return false;
                }

                if (!this.callerClass.equals(that.callerClass))
                {
                   return false;
                }

                if (!this.methodName.equals(that.methodName))
                {
                   return false;
                }

                int len = signature.length;
                if (len != that.signature.length)
                {
                   return false;
                }

                for (int i = 0; i < len; i++)
                {
                   if (!this.signature[i].equals(that.signature[i]))
                   {
                      return false;
                   }

                   // Objects.equals handles null references too
                   if (!Objects.equals(this.paramExtent[i], that.paramExtent[i]))
                   {
                      return false;
                   }
                }

                return true;
             }
          }

          /**
           * A data structure for the cache's values.
           */
          private static class CacheValue
          {
             /** Method, if any, else <code>null</code> if none was found */
             final Method method;

             /** The ReflectASM access point to this method. */
             final MethodAccess access;

             /** The method index in the MethodAccess instance. */
             final int index;

             /**
              * Enumerated return value for {@link
              * InternalEntryCaller#valid(boolean, String, int[], Object...)}.
              */
             final ArgValidationErrors returnValue;

             /** Flag indicating whether signature of caller's parameters exactly matches method */
             final boolean exactMatch;

             /**
              * Constructor.
              *
              * @param   method
              *          Method, if any, else <code>null</code> if none was found.
              * @param   returnValue
              *          Enumerated return value for {@link
              *          InternalEntryCaller#valid(boolean, String, int[], Object...)}.
              * @param   exactMatch
              *          Flag indicating whether signature of caller's parameters exactly matches
              *          method.
              */
             CacheValue(Method method, ArgValidationErrors returnValue, boolean exactMatch)
             {
                this.method = method;
                if (method != null)
                {
                   if (!Modifier.isPrivate(method.getModifiers()))
                   {
                      this.access = MethodAccess.get(method.getDeclaringClass());
                      this.index = access.getIndex(method.getName(), method.getParameterTypes());
                   }
                   else
                   {
                      this.access = null;
                      this.index = -1;
                   }
                }
                else
                {
                   this.access = null;
                   this.index = -1;
                }
                this.returnValue = returnValue;
                this.exactMatch = exactMatch;
             }
          }
       }

       /**
        * This caller is returned when the FUNCTION ... MAP TO can not be resolved. It contains the
        * function name from the last MAP TO clause.
        */
       private static class MapToNotFound
       extends InternalEntryCaller
       {
          public MapToNotFound(WorkArea wa, String iename)
          {
             super(wa);

             this.iename = iename;
          }
       }

       /**
        * Implements native procedure invocation (an API call to a shared library).
        */
       private static class NativeProcedureCaller
       extends InternalEntryCaller
       {
          /** The library name where this native procedure belongs. */
          private String libname = null;

          /** The legacy name of this native procedure. */
          private String pname = null;

          /**
           * Configure this caller with the given data.
           *
           * @param    wa
           *           The context-local state in {@link WorkArea}.
           * @param    phandle
           *           The procedure handle where this external entry is defined.
           * @param    libname
           *           The library name where this native procedure should be found.
           * @param    pname
           *           The legacy name of the external procedure from which this API is being called.
           * @param    iename
           *           The internal-entry name being called which is usually the same as the entry
           *           point name in the library except where an ordinal is being used.
           */
          public NativeProcedureCaller(WorkArea wa, handle phandle, String libname, String pname, String iename)
          {
             super(wa, phandle, null, iename);

             this.iename  = iename;
             this.libname = libname;
             this.pname   = pname;
          }

          /**
           * Call the native API with the arguments provided.
           *
           * @param    modes
           *           An encoded string with the mode of each parameter specified as a
           *           character in the string.
           * @param    args
           *           The array of procedure parameters.
           *
           * @return   Always <code>null</code> since procedures don't have a return value in the
           *           4GL.
           */
          @Override
          Object invokeImpl(String modes, Object... args)
          throws Exception
          {
             boolean failed = false;
             OutputParameterAssigner opa = TransactionManager.deregisterOutputParameterAssigner();

             try
             {
                Class<?>[] signature = getSignature(args);
                NativeInvoker.invoke(this.libname, this.pname, this.iename, signature, modes, args);
                failed = wa.em.isPending();
             }
             catch (Exception exc)
             {
                failed = true;
                throw exc;
             }
             finally
             {
                if (opa != null)
                {
                   if (failed)
                   {
                      opa.abort();
                   }
                   else
                   {
                      opa.processAssignments(pname, false);
                   }
                }
             }
             return null;
          }

          /**
           * For native API calls, the validation is deferred to the actual time of the call since
           * the 4GL does other parameter checking before the more traditional type and mode
           * checking.  Rather than place that logic here, the decision was made to keep all of
           * that processing together in {@link NativeInvoker#invoke}.
           *
           * @param   function
           *          <code>true</code> if this is a function call.
           * @param   modes
           *          The types of parameters: INPUT(I) / OUTPUT (O) / INPUT-OUTPUT (U).
           * @param   extentInfo
           *          An output parameter array that provides the caller with additional information
           *          about the extent sizes when they don't match. The first item is the size of the
           *          argument (provided extent), the second is the parameter (expected extent).
           * @param   param
           *          The arguments for this Java method call.
           *
           * @return  Always returns {@link ArgValidationErrors#DEFER_VALIDATION}, which disables
           *          validation processing at this time.
           */
          @Override
          ArgValidationErrors valid(boolean function, String modes, int[] extentInfo, Object... param)
          {
             return ArgValidationErrors.DEFER_VALIDATION;
          }

          /**
           * Get the java method name for this internal entry.
           *
           * @return   Always the same as the legacy native function name.  This is OK since
           *           this method is only used for error messages.
           */
          @Override
          String getMethodName()
          {
             return pname;
          }

          /**
           * Get the legacy name of the external program where the execution will take place.
           *
           * @return   See above.
           */
          @Override
          String getExternalProgramName()
          {
             return pname;
          }

          /**
           * Get the class name of the external program to which this internal entry belongs.
           *
           * @return   Always a nonsense class name.   This is OK since this method is only used
           *           for error messages.
           */
          @Override
          String getClassName()
          {
             return "NATIVE_LIBRARY_CALL";
          }
       }

       /**
        * Implements external program invocation.
        */
       private static class ExternalProcedureCaller
       extends InternalEntryCaller
       {
          /** Cache of classes for faster retrieval than {@code Class.forName} alone */
          private final static Map<String, Class<?>> classCache = new ConcurrentHashMap<>();

          /** A cache of constructor access objects for each class. */
          private final static Map<String, ConstructorAccess<?>> ctorCache = new ConcurrentHashMap<>();

          /** The class name associated with the external program. */
          private String className;

          /**
           * Associate this caller with the specified external program instance.
           *
           * @param    wa
           *           The context-local state in {@link WorkArea}.
           * @param    instance
           *           An instance of an external program.
           */
          public ExternalProcedureCaller(WorkArea wa, Object instance)
          {
             super(wa);

             this.methodName = "execute";
             this.className = instance.getClass().getName();

             this.callerInstance = instance;
          }

          /**
           * Configure this caller with the given data.
           *
           * @param    wa
           *           The context-local state in {@link WorkArea}.
           * @param    className
           *           The class name associated with the external program.
           * @param    persistent
           *           Flag indicating if this external program is being ran persistent.
           *
           * @throws   Exception
           *           If the class can't be resolved.
           */
          public ExternalProcedureCaller(WorkArea wa, String className, boolean persistent)
          throws Exception
          {
             super(wa);

             this.methodName = "execute";
             this.className = className;

             Class<?> cls = classCache.get(className);
             if (cls == null)
             {
                cls = Class.forName(className);
                classCache.putIfAbsent(className, cls);
             }

             wa.pm.setInstantingExternalProgramClass(cls);
             ConstructorAccess<?> ctor = ctorCache.get(className);
             if (ctor == null)
             {
                ctor = ConstructorAccess.get(cls);
                ctorCache.putIfAbsent(className, ctor);
             }

             // activate collection of all undoable vars defined in this procedure; this flag will be
             // unset only when the external program starts.
             if (persistent)
             {
                wa.tm.trackUndoables();
             }

             this.callerInstance = ctor.newInstance();
          }

          @Override
          String getClassName()
          {
             return className;
          }

          @Override
          String getInternalEntryName()
          {
             return "";
          }

          @Override
          String getParameterModes(boolean function)
          {
             String pname = ProcedureManager.getAbsoluteName(callerInstance);
             return SourceNameMapper.getParameterModes(pname);
          }

          @Override
          handle getPhandle()
          {
             // For external procedures, the phandle field is always null.
             return null;
          }
       }

       /**
        * Abstract class. Provides APIs to determine the correct target of a
        * function or procedure invocation statement.
        */
       private static abstract class Resolver
       {
          /** The {@link WorkArea} instance. */
          protected final WorkArea wa;

          /**
           * Create a new instance and associate the given WorkArea instance.
           *
           * @param    wa
           *           The {@link WorkArea} instance.
           */
          public Resolver(WorkArea wa)
          {
             this.wa = wa;
          }

          /**
           * Based on the given data, determine the procedure handle to which the
           * internal entry belongs and its java method name (or the external
           * program to be invoked).
           *
           * @param    phandle
           *           The procedure handle where this internal entry is defined.
           * @param    name
           *           The legacy name of the internal entry or external program.
           * @param    function
           *           <code>true</code> if need to search for functions.
           *           <code>false</code> if need to search for procedures.
           * @param    superCall
           *           <code>true</code> if this is a RUN SUPER or SUPER() call.
           * @param    exports
           *           The list of allowed external procedures to be invoked with a RUN ... ON SERVER
           *           statement.
           * @param    persistent
           *           Flag indicating if this external program is being ran persistent.
           *
           * @return   An {@link InternalEntryCaller caller} or null if not found.
           */
          abstract InternalEntryCaller resolve(handle    phandle,
                                               String    name,
                                               boolean   function,
                                               boolean   superCall,
                                               character exports,
                                               boolean   persistent);

          /**
           * Based on the given data, determine the procedure handle to which the
           * internal entry belongs and its java method name (or the external
           * program to be invoked).
           *
           * @param    phandle
           *           The procedure handle where this internal entry is defined.
           * @param    name
           *           The legacy name of the internal entry or external program.
           * @param    function
           *           <code>true</code> if need to search for functions.
           *           <code>false</code> if need to search for procedures.
           * @param    superCall
           *           <code>true</code> if this is a RUN SUPER or SUPER() call.
           * @param    exports
           *           The list of allowed external procedures to be invoked with a RUN ... ON SERVER
           *           statement.
           *
           * @return   An {@link InternalEntryCaller caller} or null if not found.
           */
          InternalEntryCaller resolve(handle    phandle,
                                      String    name,
                                      boolean   function,
                                      boolean   superCall,
                                      character exports)
          {
             return resolve(phandle, name, function, superCall, exports, false);
          }
       }

       /**
        * Search through the handle's defined internal-entries for a match.
        */
       private static class InternalResolver
       extends Resolver
       {
          public InternalResolver(WorkArea wa)
          {
             super(wa);
          }

          @Override
          public InternalEntryCaller resolve(handle    phandle,
                                             String    ieName,
                                             boolean   function,
                                             boolean   superCall,
                                             character exports,
                                             boolean   persistent)
          {
             String pname = ProcedureManager.getAbsoluteName(phandle.get());

             InternalEntryCacheKey key = new InternalEntryCacheKey(pname, ieName, function);
             InternalEntry ie = wa.cachedIEntries.get(key);
             if (ie == InternalEntry.NULL)
             {
                return null;
             }
             if (ie == null)
             {
                ie = SourceNameMapper.getInternalEntry(pname, ieName, function);
             }
             wa.cachedIEntries.put(key, ie == null ? InternalEntry.NULL : ie);

             if (ie == null)
             {
                return null;
             }

             if (ie.isPrivate())
             {
                // when resolving a 'IN SUPER' procedure/function or 'IN handle' function, then the
                // access mode for the procedure/function must be honored:
                // - if SOURCE-PROCEDURE is the same as the TARGET-PROCEDURE, any access mode can be
                //   used
                // - otherwise, only PUBLIC access mode can be used.

                // in a call context, before the invocation, SOURCE-PROCEDURE is always THIS-PROCEDURE
                // and TARGET-PROCEDURE is the 'phandle' (where the internal entry is defined)

                handle thisp = wa.pm.thisProcedure();
                if (!CompareOps.equals(thisp, phandle))
                {
                   return null;
                }
             }

             InternalEntryCaller caller = null;
             String libname = ie.getLibname();
             String mapTo = ie.getMapTo();

             if (libname != null)
             {
                // don't cache the result, native API call
                wa.currentInvoke = null;

                caller = overrideNativeCall(phandle, libname, pname, ieName, ie);
                if (caller == null)
                {
                   caller = new NativeProcedureCaller(wa, phandle, libname, pname, ieName);
                }
             }
             else if (mapTo != null)
             {
                // go recursively
                InternalEntryCaller res = resolve(phandle, mapTo, function, superCall, exports, false);

                if (res == null)
                {
                   // don't cache the result
                   wa.currentInvoke = null;
                   res = new MapToNotFound(wa, mapTo);
                }

                res.ie = ie;
                return res;
             }
             else if (!ie.isSuper())
             {
                handle hcaller = phandle;

                if (ie.isInHandle())
                {
                   // don't cache the result, we can't track when the IN expression changes
                   wa.currentInvoke = null;

                   hcaller = wa.pm.getHandleForFunction(phandle, ieName);
                }

                caller = new InternalEntryCaller(wa, hcaller, ie.jname, ieName);
                caller.ie = ie;
             }
             /*
             else if (!superCall)
             {
                // IN SUPER functions and procedures are used only when this
                // call is not from a RUN SUPER or SUPER call.
                Resolver r = work.obtain().internalProcSuperResolver;
                return r.resolve(phandle, ieName, function, superCall);
             }*/

             return caller;
          }

          /** Holds data of cached procedure internal entries */
          public static class InternalEntryCacheKey
          {
             /** Procedure name */
             public String procName;

             /** Internal entry name */
             public String entryName;

             /** Function flag */
             public boolean function;

             /** Hash code */
             public int hashCode;

             /**
              * Constructor.
              *
              * @param   procName
              *          Procedure name.
              * @param   entryName
              *          Internal entry name.
              * @param   function
              *          Function flag.
              */
             public InternalEntryCacheKey(String procName, String entryName, boolean function)
             {
                this.procName = procName;
                this.entryName = entryName;
                this.function = function;
                int result = 17;
                result = 37 * result + (function ? 5 : 3);
                result = 37 * result + Objects.hashCode(procName);
                result = 37 * result + Objects.hashCode(entryName);
                this.hashCode = result;
             }

             /**
              * Returns a hash code value for the object. This method is
              * supported for the benefit of hash tables such as those provided by
              * {@link HashMap}.
              *
              * @return a hash code value for this object.
              */
             @Override
             public int hashCode()
             {
                return hashCode;
             }

             /**
              * Indicates whether some other object is "equal to" this one.
              *
              * @param obj
              *    the reference object with which to compare.
              *
              * @return {@code true} if this object is the same as the obj
              *    argument; {@code false} otherwise.
              */
             @Override
             public boolean equals(Object obj)
             {
                InternalEntryCacheKey other = (InternalEntryCacheKey) obj;
                if (other == null)
                {
                   return false;
                }
                return other == this ||
                   other.function == function && Objects.equals(procName, other.procName) &&
                      Objects.equals(entryName, other.entryName);
             }
          }
       }

       /**
        * Search through the given super-procedure list for a match.
        */
       private static abstract class SuperResolver
       extends InternalResolver
       {
          public SuperResolver(WorkArea wa)
          {
             super(wa);
          }

          private final Set<Object> used = Collections.newSetFromMap(new IdentityHashMap<>());

          protected InternalEntryCaller resolveSuperProc(handle       phandle,
                                                         List<handle> superProcs,
                                                         String       ieName,
                                                         boolean      function,
                                                         boolean      superCall,
                                                         character    exports)
          {
             // search is done backwards (last set super-procedure is searched
             // first)
             for (handle sphandle : superProcs)
             {
                Object referent = sphandle.get();

                if (used.contains(referent))
                {
                   continue;
                }

                used.add(referent);
                try
                {
                   // when looking for an internal-entry, only the direct super-
                   // procedures are searched. no recursivity is done.
                   InternalEntryCaller caller = super.resolve(sphandle,
                                                              ieName,
                                                              function,
                                                              superCall,
                                                              exports,
                                                              false);
                   if (caller != null)
                   {
                      Object callerRef = caller.phandle.get();
                      if (callerRef != referent && (used.contains(callerRef) || callerRef == phandle.get()))
                      {
                         // we moved into another program, if this program is the source reference or one of the
                         // used super-procedure, do not allow it.
                         caller = null;
                      }
                   }

                   if (caller != null)
                   {
                      return caller;
                   }
                }
                finally
                {
                   used.remove(referent);
                }
             }

             return null;
          }
       }

       /**
        * Search through the super-procedures of the given procedure handle for
        * a match.
        */
       private static class InternalProcSuperResolver
       extends SuperResolver
       {
          public InternalProcSuperResolver(WorkArea wa)
          {
             super(wa);
          }

          @Override
          public InternalEntryCaller resolve(handle    phandle,
                                             String    ieName,
                                             boolean   function,
                                             boolean   superCall,
                                             character exports,
                                             boolean   persistent)
          {
             List<handle> superProcs = wa.pm.getSuperProcedures(phandle.get());

             return resolveSuperProc(phandle, superProcs, ieName, function, superCall, exports);
          }
       }

       /**
        * Search through the session's super-procedures of the handle for a match.
        */
       private static class SessionSuperResolver
       extends SuperResolver
       {
          public SessionSuperResolver(WorkArea wa)
          {
             super(wa);
          }

          @Override
          public InternalEntryCaller resolve(handle    phandle,
                                             String    ieName,
                                             boolean   function,
                                             boolean   superCall,
                                             character exports,
                                             boolean   persistent)
          {
             List<handle> superProcs = wa.pm.getSuperProcedures();

             return resolveSuperProc(phandle, superProcs, ieName, function, superCall, exports);
          }
       }

       /**
        * Search through the available external programs for a match.
        */
       private static class ExternalProgramResolver
       extends Resolver
       {
          /** The already resolved class name, from a previous cached call. */
          private String className = null;

          public ExternalProgramResolver(WorkArea wa)
          {
             super(wa);
          }

          @Override
          public InternalEntryCaller resolve(handle    phandle,
                                             String    name,
                                             boolean   function,
                                             boolean   superCall,
                                             character exports,
                                             boolean   persistent)
          {
             if (function)
             {
                // for functions, don't search external procedures
                return null;
             }

             if (exports != null      &&
                 !exports.isUnknown() &&
                 !TextOps.matchesList(exports, name).booleanValue())
             {
                // in case this is call is via an appserver, only the exported procedures can be used
                return null;
             }

             name = SourceNameMapper.normalizeLegacyName(name);

             String className = this.className;
             if (className == null)
             {
                className = SourceNameMapper.convertNameToClass(name);
             }
             else
             {
                // reset, no longer needed
                this.className = null;
             }

             InternalEntryCaller caller = null;
             if (className != null)
             {
                // is a registered external procedure
                try
                {
                   // set the 4GL ext prog being instantiated; this is required to expose the 4GL prog
                   // name to any errors generated during instantiation (i.e. shared vars error msgs
                   // need to know the procedure name where they are defined, if an error happens)
                   wa.pm.setInstantingExternalProgram(name);
                   caller = new ExternalProcedureCaller(wa, className, persistent);
                }
                catch (Throwable t)
                {
                   cleanupPending(wa);

                   Throwable cause = t;
                   while (cause != null)
                   {
                      // if the exception is a condition exception, let it propagate up the stack
                      if (cause instanceof ErrorConditionException)
                      {
                         // ERROR conditions raised during instantiation can be either logged
                         // (if NO-ERROR is in effect) or re-thrown.  Let the ErrorManager decide
                         // what to do; in any case, this returns null
                         ErrorConditionException ece = (ErrorConditionException) cause;
                         String msg = ece.getMessage();
                         int num = ece.getProgressErrorCode();

                         ErrorManager.recordOrThrowError(num, msg, false);
                         return null;
                      }
                      else if (cause instanceof ConditionException)
                      {
                         throw (ConditionException) cause;
                      }
                      if (cause instanceof RetryUnwindException)
                      {
                         throw (RetryUnwindException) cause;
                      }

                      cause = cause.getCause();
                   }

                   // if the exception was not propagated, log it, as something severe had happened.
                   LOG.log(Level.SEVERE, "Unable to resolve external program.", t);

                   return null;
                }
                finally
                {
                   wa.pm.setInstantingExternalProgram(null);
                   wa.pm.setInstantingExternalProgramClass(null);
                }
             }

             return caller;
          }
       }

       /**
        * An interface for which its implementation will resolve the arguments passed to a
        * <code>ControlFlowOps.invoke</code> API.
        */
       private static interface ArgumentResolver
       {
          /**
           * Resolve the arguments.
           *
           * @param    caller
           *           The resolved caller, to which the arguments will be passed.
           * @param    pname
           *           The caller's legacy name.
           * @param    function
           *           Flag indicating if this is a 4GL function call (when <code>true</code>).
           *
           * @return   An array with the resolved arguments.
           */
          public Object[] resolve(InternalEntryCaller caller, String pname, boolean function);
       }

       /**
        * This implementation resolves arguments for converted 4GL statements (RUN, function calls,
        * DYNAMIC-FUNCTION).  It just passes back the received array.
        */
       private static class ArrayArgumentResolver
       implements ArgumentResolver
       {
          /** The arguments used for this 4GL-style call. */
          private final Object[] args;

          /**
           * Create a new instance and save the arguments.
           *
           * @param    args
           *           The arguments for this 4GL-style call.
           */
          public ArrayArgumentResolver(Object[] args)
          {
             this.args = args;
          }

          /**
           * Resolve the arguments by returning back the {@link #args} received on instantiation.
           *
           * @param    caller
           *           The resolved caller, to which the arguments will be passed.
           * @param    pname
           *           The caller's legacy name.
           * @param    function
           *           Flag indicating if this is a 4GL function call (when <code>true</code>).
           *
           * @return   The initial {@link #args}.
           */
          @Override
          public Object[] resolve(InternalEntryCaller caller, String pname, boolean function)
          {
             Object[] res = new Object[args.length];
             for (int i = 0; i < args.length; i++)
             {
                if (args[i] instanceof BaseDataType)
                {
                   // the value may be a proxy; make sure to unwrap and work with the real value
                   res[i] = ((BaseDataType) args[i]).val();
                }
                else
                {
                   res[i] = args[i];
                }
             }
             return res;
          }
       }

       /**
        * This is a special implementation of resolving arguments received in a {@link Map}, where
        * the key is the legacy argument's name (case insensitive) and the value is a string-encoded
        * representation of this argument.
        * <p>
        * When resolving the arguments, they are matched against the caller's signature and an array
        * is constructed, so that the argument order matches the caller's signature.  If an argument
        * is not specified in the {@link #args map}, an unknown value is assumed for it.
        */
       private static class MapArgumentResolver
       implements ArgumentResolver
       {
          /**
           * A map with string representation of the 4GL supported types to their P2J implementation
           * class.
           */
          private static final Map<String, Class<? extends BaseDataType>> SUPPORTED_TYPES;

          static
          {
             Map<String, Class<? extends BaseDataType>> types = new HashMap<>();
             types.put("RAW", raw.class);
             types.put("DATE", date.class);
             types.put("DATETIME", datetime.class);
             types.put("DATETIMETZ", datetimetz.class);
             types.put("DATETIME-TZ", datetimetz.class);
             types.put("LOGICAL", logical.class);
             types.put("DECIMAL", decimal.class);
             types.put("INT64", int64.class);
             types.put("INTEGER", integer.class);
             types.put("RECID", recid.class);
             types.put("ROWID", rowid.class);
             types.put("CHARACTER", character.class);
             types.put("LONGCHAR", longchar.class);

             SUPPORTED_TYPES = Collections.unmodifiableMap(types);
          }

          /** The argument map. */
          private final Map<String, String> args;

          /** The {@link #args} converted/casted to their associated P2J type. */
          private final Map<String, BaseDataType> resolvedArgs = new HashMap<>();

          /** A set of 4GL legacy parameter names, which are marked as OUTPUT or INPUT-OUTPUT. */
          private Set<String> outputArguments = new HashSet<>();

          /**
           * Create a new instance.
           *
           * @param    args
           *           The map of arguments.
           */
          public MapArgumentResolver(Map<String, String> args)
          {
             this.args = new HashMap<>();
             for (String key : args.keySet())
             {
                this.args.put(key.toLowerCase(), args.get(key));
             }
          }

          /**
           * Resolve the arguments which will be passed to the caller.
           *
           * @param    caller
           *           The resolved caller, to which the arguments will be passed.
           * @param    name
           *           The caller's legacy name.
           * @param    function
           *           Flag indicating if this is a 4GL function call (when <code>true</code>).
           *
           * @return   The resolved arguments.
           *
           * @throws   ErrorConditionException
           *           If the arguments can't be matched against the caller's signature (by number,
           *           name or type).
           */
          @Override
          public Object[] resolve(InternalEntryCaller caller, String name, boolean function)
          {
             List<Parameter> params = caller.getParameters(function);
             if (params == null || params.isEmpty())
             {
                // no parameters, check if the arg list is empty!
                if (!args.isEmpty())
                {
                   // TODO: this has to do with the MSG_P2J_INVOKE incoming from the JS side, in
                   //       embedded mode; this is a FWD feature with no 4GL counterpart, so we can
                   //       change it to use recordOrThrowError(), as long as the remote JS side is
                   //       properly informed of the error; this code was originally adapted from
                   //       ControlFlowOps.validArguments
                   String msg = "Remote invoke passed parameters to %s, which did not expect any";
                   msg = String.format(msg, name);
                   throw new ErrorConditionException(1005, msg);
                }

                return new Object[0];
             }

             Object[] res = new Object[params.size()];
             Set<String> unused = new HashSet<>();
             unused.addAll(args.keySet());

             String modes = caller.getParameterModes(function);
             for (int i = 0; i < modes.length(); i++)
             {
                Parameter param = params.get(i);
                String pname = param.getLegacyName().toLowerCase();
                String ptype = param.getType();

                if (param.isExtent())
                {
                   // TODO: this is not safe for silent error mode, ErrorManager.recordOrThrowError()
                   //       should be used OR ErrorManager.throwError(NE, boolean) but generally we
                   //       should not be instantiating this exception directly
                   String msg = "Argument %s with type %s is defined EXTENT, which is not supported.";
                   throw new ErrorConditionException(5729, String.format(msg, pname, ptype));
                }

                if (modes.charAt(i) != 'I')
                {
                   outputArguments.add(pname);
                }

                Class<? extends BaseDataType> pclass = SUPPORTED_TYPES.get(ptype.toUpperCase());

                if (pclass == null)
                {
                   // TODO: this is not safe for silent error mode, ErrorManager.recordOrThrowError()
                   //       should be used OR ErrorManager.throwError(NE, boolean) but generally we
                   //       should not be instantiating this exception directly
                   String msg = "Argument %s has an unsupported type: %s";
                   throw new ErrorConditionException(5729, String.format(msg, pname, ptype));
                }

                // support only BDT sub-classes (check ptype against a list of BDT sub-classes)

                String sval = args.get(pname);
                BaseDataType aval = null;
                if (sval == null)
                {
                   // build a new instance, based on parameter's type
                   aval = BaseDataType.generateUnknown(pclass);
                }
                else
                {
                   Runnable error = () ->
                   {
                      // raise error, could not convert!
                      String msg = "Remote called routine %s %s has mismatched parameters";
                      msg = String.format(msg, caller.getInternalEntryName(), name);

                      // TODO: this is not safe for silent error mode, recordOrThrowError()
                      //       should be used OR throwError(NE, boolean) but generally we
                      //       should not be instantiating this exception directly
                      throw new ErrorConditionException(5729, msg);
                   };

                   try
                   {
                      // build a new instance using the BaseDataType(String) c'tor
                      aval = pclass.getConstructor(String.class).newInstance(sval);
                   }
                   catch (Exception expt)
                   {
                      error.run();
                   }

                   if (caller.wa.em.isPending())
                   {
                      error.run();
                   }
                }

                // this argument is used, save it
                unused.remove(pname);
                res[i] = aval;

                resolvedArgs.put(pname, aval);
             }

             if (!unused.isEmpty())
             {
                // TODO: this is not safe for silent error mode, ErrorManager.recordOrThrowError()
                //       should be used OR ErrorManager.throwError(NE, boolean) but generally we
                //       should not be instantiating this exception directly

                // some arguments were received which couldn't be matched - raise ERROR
                String msg = "Mismatched number of parameters passed to routine %s %s: %s";
                msg = String.format(msg, caller.getInternalEntryName(), name, unused.toString());
                throw new ErrorConditionException(3234, msg);
             }

             return res;
          }

          /**
           * Get a map with the OUTPUT or INPUT-OUTPUT arguments, with their state as after the caller
           * has been executed.  The keys will be the legacy 4GL names and the values will be a string
           * representation of each OUTPUT or INPUT-OUTPUT argument.
           *
           * @return   See above.
           */
          public Map<String, String> getOutputArguments()
          {
             Map<String, String> res = new HashMap<>();
             for (String key : resolvedArgs.keySet())
             {
                if (outputArguments.contains(key))
                {
                   res.put(key, resolvedArgs.get(key).toStringMessage());
                }
             }

             return res;
          }
       }

       /**
        * Helper class which also saves the {@link AsyncRequestImpl} instance, needed by the remote
        * request.
        */
       private static abstract class AsyncCall
       implements Runnable
       {
          /** The async request instance for this call. */
          public final AsyncRequestImpl asyncReq;

          /** The server used to execute this async call. */
          public final handle hServer;

          /** A handle variable where to save the web service. May be null. */
          public final handle portHandle;

          /** The target procedure name. */
          public final character pName;

          /** The handle to which the procedure belongs, if it is RUN ... IN handle statement. */
          public final handle pHandle;

          /** The handle to the asynchronous request. */
          public final handle asyncHandle;

          /** This represents the name of the internal procedure for the EVENT-PROCEDURE clause. */
          public final character eventProcName;

          /** The handle to a procedure in which context the EVENT-PROCEDURE is found. */
          public final handle inEventprocHandle;

          /**
           * This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON statement.
           */
          public final boolean transactionDistinct;

          /**
           *
           * @param    hServer
           *           The server used to execute this async call.
           * @param    portHandle
           *           A handle variable where to save the web service. May be null.
           * @param    pName
           *           The target procedure name.
           * @param    pHandle
           *           The handle to which the procedure belongs, if it is RUN ... IN handle
           *           statement.
           * @param    asyncHandle
           *           The handle to the asynchronous request.
           * @param    eventProcName
           *           This represents the name of the internal procedure for the EVENT-PROCEDURE
           *           clause, null if this clause is not specified.
           * @param    inEventprocHandle
           *           The handle to a procedure in which context the specified EVENT-PROCEDURE internal
           *           procedure is found, null if EVENT-PROCEDURE clause is not specified.
           * @param    transactionDistinct
           *           This marks if "TRANSACTION DISTINCT" option is specified with the RUN ... ON
           *           statement.
           * @param    modes
           *           A string representation of the modes of each parameter.
           * @param    args
           *           The procedure's arguments.
          */
          public AsyncCall(handle     hServer,
                           handle     portHandle,
                           character  pName,
                           handle     pHandle,
                           handle     asyncHandle,
                           character  eventProcName,
                           handle     inEventprocHandle,
                           boolean    transactionDistinct,
                           String     modes,
                           Object...  args)
          {
             this.hServer = new handle(hServer);
             this.portHandle = portHandle == null ? null : new handle(portHandle);
             this.pName = new character(pName);
             this.pHandle = pHandle == null ? null : new handle(pHandle);
             this.asyncHandle = asyncHandle == null ? null : new handle(asyncHandle);
             this.eventProcName = eventProcName == null ? null : new character(eventProcName);
             this.inEventprocHandle = inEventprocHandle == null ? null : new handle(inEventprocHandle);
             this.transactionDistinct = transactionDistinct;

             this.asyncReq = new AsyncRequestImpl(this.hServer,
                                                  this,
                                                  this.eventProcName,
                                                  this.inEventprocHandle,
                                                  this.pName,
                                                  this.pHandle,
                                                  modes,
                                                  args);
          }
       }
    }