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);
      }
   }   
}