ObjectOps.java

/*
** Module   : ObjectOps.java
** Abstract : Defines APIs to implement 4GL object-specific operations.
**
** Copyright (c) 2018-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 CA  20181213 Created initial version.
**     OM  20181217 newInstance() returns the expected generic type.
** 002 OM  20190129 Remove reference after the destructors are invoked.
**     CA  20190219 Runtime implementaiton.
**     CA  20190326 Implemented ProcedureHelper and TransactionHelper instead of direct usage of
**                  the static API.  This allows the elimination of context local usage in 
**                  ProcedureManager and TransactionManager.
** 003 CA  20190418 Added no-arg DYNAMIC-NEW support.
** 004 CA  20190710 Fixed issues related to working with legacy OE classes for which FWD provides 
**                  support.  Do not delete a legacy instance if this is supposed to be returned 
**                  by a function call (this delete check is postponed in the parent scope).
**     CA  20190719 Plain object instance does not do reference counting.
**     CA  20190729 When loading a legacy class, backup and restore the output parameter assigners.
**     CA  20190995 Added cast(BDT) version.
** 005 AIL 20191120 Made the scope methods aware of DATABASE TRIGGER Block Type.
**     CA  20191112 Fixed instantiation of Progress.Lang.Class (via GetClass) - this must be a 
**                  unique instance for each invocation, and lifetime must match any other legacy
**                  4GL instance.
** 006 CA  20200116 Javadoc fixes.
** 007 CA  20200304 A 4GL object instance must delay its delete if is still on stack.
** 008 CA  20200330 More fixes related to OE-compatible error management, in constructor case
**                  and 'implicit' 4GL ERROR conditions.
** 009 CA  20200427 Replaced isValid().booleanValue() calls with _isValid().
**     CA  20200429 Added stubs for DYNAMIC-ENUM.
**     CA  20200503 Added stubs for GET-CLASS().
**     GES 20200506 Implemented DYNAMIC-ENUM().
**     GES 20200511 Bypassed life cycle management for legacy enums.
**     ME  20200521 Move getLegacyClass from LegacyClass, add class hierarchy registration.
**     GES 20200528 Fixed enum error handling.
**     GES 20200529 Added safety code to avoid life cycle processing for enums.
**     GES 20200603 Reworked legacy name/class mapping to use static data in a thread-safe manner.
** 010 ME  20200810 Added registerClassWorker() to getClassInstance().
**     CA  20201003 Replaced Guava identity HashSet with Collections.newSetFromMap(IdentityHashMap).
**     ME  20201009 Let 'invoke' return plain Object as it can be anything (including array).
**     CA  20201014 Reverted ME/20201009 - 'invoke' will return BDT, and the fix will either emit a 
**                  'invokeExtent' for calls where it is known for the lvalue to be an extent, or we add 
**                  BDT(Object) constructors for all sub-classes.
**     ME  20201127 Throw error instead of showing it if cast fails.
**     ME  20210128 Added cast/dynamic cast for object arrays.
**     CA  20210215 An INPUT parameter at the property SETTER must be registered as pending, so its reference
**                  can be tracked. 
**     CA  20210310 Performance improvement in hasDestructor - cache the result of this method.
**     CA  20210322 asResource(LegacyEnum) will always return an ObjectResource instance (and a resource ID
**                  computed), so temp-table fields can refer enums. 
**     ME  20210330 Added convenient increment/decrement methods for object references.
**     CA  20210804 Fixed a problem with caching available destructors - 'clsWithDtor' must have as key the 
**                  object's defining class paired with the destructor name (which may be from a super-class).
**     HC  20211011 Implemented i18n support.
**     CA  20211214 The legacy class instances are always singleton for the current context.
**     CA  20220106 THIS-OBJECT from a static method will return the legacy class object - this is required as
**                  in some cases (like 'clsEvt:subscribe(static-method)') FWD emits THIS-OBJECT as first
**                  argument and the parser does not resolve the static method name.
**     CA  20220120 Fixed some cases of pending assignment.  Some performance improvements.
**     CA  20220203 A small performance improvement for scopeFinished.
**     CA  20220208 Objects deserialized from a remote appserver call can be automatically registered as known
**                  instances.  Fixed a 'pendingAssign' bug when the root block exits. 
**     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  20221006 Improved context-local lookup. Refs #6824
**     TJD 20220504 Java 11 compatibility minor changes
**     CA  20221123 If an 'object' instance is assigned in the FINALLY block and also referenced by the RETURN
**                  statement, then do not remove it from 'pendingAssign'.
**     EVL 20221209 Adding BaseDataType as valid object type for dynamicCast() method call.
**     CA  20220426 Added stubs for DYNAMIC-PROPERTY statement and function (setter and getter). 
**     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.
**                  An unknown value returned by 'new' or 'cast' must be typed with the legacy class name.
**     CA  20220524 Fixed destructor execution.
**     CA  20220526 Added DYNAMIC-PROPERTY getter and setter implementation, not all argument validation is 
**                  implemented.
**     CA  20220531 Added a version of newDynamicInstance which emits for NEW with POLY arguments.
**     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  20220602 Added basic support for transport of object instances over appserver call.
**     CA  20220609 Fixed a regression in destructor processing.
**     CA  20220617 Dynamic invocation uses the runtime reference type, and not the definition type, in any
**                  error message.
**     CA  20230110 Added a ObjectHelper to provide direct access to context-local state.
**     CA  20230116 Improvement from context-local state resolution.
** 011 CA  20230321 When a legacy class is first loaded, force its static constructor to execute.
** 012 CA  20230321 Better fix for H011 - avoid logging an error message for 'executeStaticConstructor'.
** 013 CA  20230615 Small optimization - avoid using obj.ref() and use instead obj.ref, if the 'obj' instance
**                  is known to be valid.
** 014 CA  20230731 An instance can be assigned during its initialization (constructor calls) - do not save it
**                  as 'pendingAssign' in such cases.
** 015 CA  20230724 Resolve and cache the 'execute methods' at SourceNameMapper$LegacyClass.executeMethods.
** 016 CA  20230817 Removed ObjectOps.register, as this registration is handled by runtime.
** 017 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  20231007 Arguments for direct method calls which can't be emitted as direct java calls  (as they 
**                  are considered 'dynamic poly') must be wrapped in a 'polyArg' to check the type.
** 018 CA  20231026 An object instance being returned must be tracked on the caller block, as the returned
**                  value may or may not be assigned, and the callee may return a freshly created instance 
**                  which will never gets discarded if is never assigned again.
**                  A persistent procedure with a pending delete is no longer persistent, so scopeDeleted 
**                  needs to be executed.
**     CA  20231031 Added 'BlockDefinition' parameter to 'scopeStart', required when a top-level scopeable is  
**                  lazy registered while in an inner block.
** 019 CA  20231026 Static poly calls do not require the string representation of the class.
** 020 CA  20231208 POLY OO method invocations must use the var's declared type and not the runtime type.
** 021 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.
** 022 CA  20231221 ObjectOps scopeable support is registered only when: a var is defined, a new instance is
**                  being created, or a function/method returns 'object'.
** 023 CA  20231216 Fixed regression related to widget pools used in OO (the issue was with the implicit 
**                  widget pool for a class with USE-WIDGET-POOL, for which the instance gets deleted 
**                  implicitly when a block finishes). 
** 024 CA  20240105 The 'inScopeFinished' flag must be reset/restored while executing destructors.
** 025 CA  20240324 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
**                  Added a flag to allow tracking the objects valid state without a map lookup.  This flag is
**                  set to false when the instance gets deleted.
** 026 CA  20240410 Track the instantiating class, when creating a new instance, so the scopeables know that 
**                  they need to be registered as pending or not.
** 027 CA  20240520 Fixed validation of the DYNAMIC-PROPERTY arguments for statement and function.
** 028 ES  20240711 Call cleanupPending in case of StopConditionExeption is thrown by a stop-after timer.
** 029 AL2 20240717 Clear the newly constructed object from the objects collection if the constructor failed.
** 030 DDF 20240305 Do not delete the resource when the initialization fails due to a constructor
**                  that could not be found.
**     DDF 20240311 Refactored newInstance() and newInstanceInternal() to include an additional parameter
**                  used for avoiding executing the constructor method during initialization.
** 031 VVT 20240826 SourceNameMapper.convertJavaProg() renamed. See #8613-16.
** 032 CA  20250422 The reference in TYPE-OF can be POLY, added overloads.
** 033 PBB 20250509 Added support for DYNAMIC-INVOKE when the result is or might be an extent.
** 034 PBB 20250522 Added DYNAMIC-PROPERTY support mostly related to returning extents.
** 035 ES  20250527 Return new ObjectResource if the reference object is not tracked.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.util;

import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.security.*;

import java.lang.reflect.*;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.*;

import org.reflections.Reflections;

/**
 * Defines APIs to manage 4GL-style objects (instantiation, deletion, cast, etc).
 */
public class ObjectOps
{
   /** DYNAMIC-ENUM() error message specification. */
   private static final String DE_SPEC = "Could not evaluate '%s' parameter for DYNAMIC-ENUM";
   
   /** Legacy names for each loaded class. */
   private static final Map<Class<? extends _BaseObject_>, String> legacyNames = new IdentityHashMap<>();
   
   /** Map of legacy names converted classes. */
   private static final Map<String, Class<? extends _BaseObject_>> name2cls = new HashMap<>();
   
   /** Controls access to the static class/name lookup data. */
   private static final Object lock = new Object();
      
   /** Context-local data. */
   private static final ContextLocal<WorkArea> local = new ContextLocal<WorkArea>()
   {
      protected WorkArea initialValue()
      {
         return new WorkArea();
      }
   };
   
   static
   {
      // register all classes from 'oo sdk' implementation  
      Reflections reflections = new Reflections("com.goldencode.p2j.oo");
      Set<Class<? extends _BaseObject_>> legacyClasses = reflections.getSubTypesOf(_BaseObject_.class);
      
      for (Class<? extends _BaseObject_> cls : legacyClasses) {
         registerClass(cls, false);
      }
   }

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

   /**
    * Implementation of the DYNAMIC-PROPERTY statement (setter).
    *
    * @param    ref
    *           The object reference to which the property belongs.
    * @param    prop
    *           The legacy property name.
    * @param    val
    *           The property value.
    */
   public static void setDynamicProperty(object<? extends _BaseObject_> ref, character prop, Object val)
   {
      //fail fast

      if (ref.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
         return;
      }

      if (prop.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid property name passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16581}, new String[]{errMsg}, false, false, false);
         return;
      }

      setDynamicProperty(ref, prop.toStringMessage(), val);
   }
   
   /**
    * Implementation of the DYNAMIC-PROPERTY statement (setter).
    * 
    * @param    ref
    *           The object reference to which the property belongs.
    * @param    prop
    *           The legacy property name.
    * @param    val
    *           The property value.
    */
   public static void setDynamicProperty(object<? extends _BaseObject_> ref, String prop, Object val)
   {
      //fail fast
      if (ref.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
         return;
      }

      if (!validDynamicPropertyArgs(ref, prop, null, true))
      {
         return;
      }

      PropertyReference pref = new PropertyReference(ref, prop);
      if (val == null)
      {
         val = new unknown();
      }
      
      if (val.getClass().isArray())
      {
         pref.setExtent((BaseDataType[]) val);
      }
      else if (val instanceof BaseDataType)
      {
         pref.set((BaseDataType) val);
      }
      else
      {
         //try to convert the value to a BDT
         try
         {
            BaseDataType btdVal = convertFromJavaToBTD(val);
            pref.set(btdVal);
         }
         catch (Exception e)
         {
            UnimplementedFeature.missing(
                     "DYNAMIC-PROPERTY statement is not implemented for " + val.getClass());
         }
      }
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY statement (setter).
    *
    * @param    ref
    *           The object reference to which the property belongs.
    * @param    prop
    *           The legacy property name (an extent property).
    * @param    idx
    *           The index in the extent property.
    * @param    val
    *           The property value.
    */
   public static void setDynamicProperty(object<? extends _BaseObject_> ref,
                                         character                      prop,
                                         NumberType                     idx,
                                         Object                         val)
   {
      //fail fast

      if (ref.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
         return;
      }

      if (prop.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid property name passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16581}, new String[]{errMsg}, false, false, false);
         return;
      }

      setDynamicProperty(ref, prop.toStringMessage(), idx, val);
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY statement (setter).
    * 
    * @param    ref
    *           The object reference to which the property belongs.
    * @param    prop
    *           The legacy property name (an extent property).
    * @param    idx
    *           The index in the extent property.
    * @param    val
    *           The property value.
    */
   public static void setDynamicProperty(object<? extends _BaseObject_> ref, 
                                         String                         prop,
                                         NumberType                     idx, 
                                         Object                         val)
   {
      //fail fast
      if (ref.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
         return;
      }

      if (!validDynamicPropertyArgs(ref, prop, idx, true))
      {
         return;
      }

      PropertyReference pref = new PropertyReference(ref, prop, idx);
      if (val == null)
      {
         val = new unknown();
      }
      
      if (val.getClass().isArray())
      {
         pref.setExtent((BaseDataType[]) val);
      }
      else if (val instanceof BaseDataType)
      {
         pref.set((BaseDataType) val);
      }
      else
      {
         //try to convert the value to a BDT
         try
         {
            BaseDataType btdVal = convertFromJavaToBTD(val);
            pref.set(btdVal);
         }
         catch (Exception e)
         {
            UnimplementedFeature.missing(
                     "DYNAMIC-PROPERTY statement is not implemented for " + val.getClass());
         }
      }
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY statement (setter).
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    * @param    val
    *           The property value.
    */
   public static void setDynamicProperty(character className, character prop, Object val)
   {
      // fail fast

      if (className.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
         return;
      }

      if (prop.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid property name passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16581}, new String[]{errMsg}, false, false, false);
         return;
      }


      setDynamicProperty(className.toStringMessage(), prop.toStringMessage(), val);
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY statement (setter).
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    * @param    val
    *           The property value.
    */
   public static void setDynamicProperty(character className, String prop, Object val)
   {
      // fail fast
      if (className.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
         return;
      }

      setDynamicProperty(className.toStringMessage(), prop, val);
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY statement (setter).
    * 
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    * @param    val
    *           The property value.
    */
   public static void setDynamicProperty(String className, character prop, Object val)
   {
      //fail fast
      if (prop.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid property name passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16581}, new String[]{errMsg}, false, false, false);
         return;
      }

      setDynamicProperty(className, prop.toStringMessage(), val);
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY statement (setter).
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    * @param    val
    *           The property value.
    */
   public static void setDynamicProperty(String className, String prop, Object val)
   {
      if (!validDynamicPropertyArgs(className, prop, null, true))
      {
         return;
      }

      PropertyReference pref = new PropertyReference(className, prop);
      if (val == null)
      {
         val = new unknown();
      }
      
      if (val.getClass().isArray())
      {
         pref.setExtent((BaseDataType[]) val);
      }
      else if (val instanceof BaseDataType)
      {
         pref.set((BaseDataType) val);
      }
      else
      {
         //try to convert the value to a BDT
         try
         {
            BaseDataType btdVal = convertFromJavaToBTD(val);
            pref.set(btdVal);
         }
         catch (Exception e)
         {
            UnimplementedFeature.missing(
                     "DYNAMIC-PROPERTY statement is not implemented for " + val.getClass());
         }
      }
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY statement (setter).
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name (an extent property).
    * @param    idx
    *           The index in the extent property.
    * @param    val
    *           The property value.
    */
   public static void setDynamicProperty(character className, character prop, NumberType idx, Object val)
   {
      //fail fast

      if (className.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
         return;
      }

      if (prop.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid property name passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16581}, new String[]{errMsg}, false, false, false);
         return;
      }

      setDynamicProperty(className.toStringMessage(), prop.toStringMessage(), idx, val);
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY statement (setter).
    * 
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name (an extent property).
    * @param    idx
    *           The index in the extent property.
    * @param    val
    *           The property value.
    */
   public static void setDynamicProperty(String className, character prop, NumberType idx, Object val)
   {
      //fail fast
      if (prop.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid property name passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16581}, new String[]{errMsg}, false, false, false);
         return;
      }

      setDynamicProperty(className, prop.toStringMessage(), idx, val);
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY statement (setter).
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name (an extent property).
    * @param    idx
    *           The index in the extent property.
    * @param    val
    *           The property value.
    */
   public static void setDynamicProperty(character className, String prop, NumberType idx, Object val)
   {
      //fail fast
      if (className.isUnknown())
      {
         // This error doesn't end in dot in progress
         String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
         return;
      }

      setDynamicProperty(className.toStringMessage(), prop, idx, val);
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY statement (setter).
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name (an extent property).
    * @param    idx
    *           The index in the extent property.
    * @param    val
    *           The property value.
    */
   public static void setDynamicProperty(String className, String prop, NumberType idx, Object val)
   {
      if (!validDynamicPropertyArgs(className, prop, idx, true))
      {
         return;
      }

      PropertyReference pref = new PropertyReference(className, prop, idx);
      if (val == null)
      {
         val = new unknown();
      }
      
      if (val.getClass().isArray())
      {
         pref.setExtent((BaseDataType[]) val);
      }
      else if (val instanceof BaseDataType)
      {
         pref.set((BaseDataType) val);
      }
      else
      {
         //try to convert the value to a BDT
         try
         {
            BaseDataType btdVal = convertFromJavaToBTD(val);
            pref.set(btdVal);
         }
         catch (Exception e)
         {
            UnimplementedFeature.missing(
                     "DYNAMIC-PROPERTY statement is not implemented for " + val.getClass());
         }
      }
   }

   /**
    * Converts the value from java native type to progress base data type equivalent.
    *
    * @param    value
    *           The value to be converted to BDT.
    *           
    * @return   The progress data type equivalent of the given value.
    */
   private static BaseDataType convertFromJavaToBTD(Object value)
   {
      Class<?> jtype = value.getClass();
      if (jtype == Integer.class || jtype == Short.class || jtype == Byte.class ||
               jtype == int.class || jtype == short.class || jtype == byte.class)
      {
         return new integer(((Number) value).intValue());
      }
      else if (jtype == Long.class || jtype == long.class)
      {
         return new int64(((Number) value).longValue());
      }
      else if (jtype == Double.class || jtype == Float.class ||
               jtype == double.class || jtype == float.class)
      {
         return new decimal(((Number) value).doubleValue());
      }
      else if (jtype == BigDecimal.class)
      {
         return new decimal((BigDecimal) value);
      }
      else if (jtype == Boolean.class || jtype == boolean.class)
      {
         return new logical(((Boolean) value).booleanValue());
      }
      else if (jtype == String.class)
      {
         return new character((String) value);
      }
      else if (jtype == byte[].class)
      {
         return new raw((byte[]) value);
      }
      else if (jtype == Date.class)
      {
         return new date((Date) value);
      }
      else if (jtype == Timestamp.class)
      {
         return new datetime((Timestamp) value);
      }

      ErrorManager.recordOrThrowError(-1, "Can not convert value " + value.toString() +
               " of type " +jtype + " from Java!");

      return unknown.UNKNOWN;
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter).
    * 
    * @param    ref
    *           The object reference to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *           
    * @return   The property value.
    */
   public static BaseDataType getDynamicProperty(object<? extends _BaseObject_> ref, character prop)
   {
      return getDynamicProperty(ref, prop.toStringMessage());
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
    * inferred from the surrounding context that the return type should be an <code>EXTENT</code>.
    *
    * @param    ref
    *           The object reference to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The extent property value.
    */
   public static BaseDataType[] getDynamicPropExtent(object<? extends _BaseObject_>   ref,
                                                     character                        prop)
   {
      return getDynamicPropExtent(ref, prop.toStringMessage());
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
    * inferred from the surrounding context that the return type can either be an
    * <code>EXTENT</code> or a scalar value.
    *
    * @param    ref
    *           The object reference to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The property value.
    */
   public static Object getDynamicPropPoly(object<? extends _BaseObject_> ref, character prop)
   {
      return getDynamicPropPoly(ref, prop.toStringMessage());
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter).
    *
    * @param    ref
    *           The object reference to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The property value.
    */
   public static BaseDataType getDynamicProperty(object<? extends _BaseObject_> ref, String prop)
   {
      if (!validDynamicPropertyArgs(ref, prop, null, false))
      {
         return new unknown();
      }

      PropertyReference pref = new PropertyReference(ref, prop);

      return pref.get();
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
    * inferred from the surrounding context that the return type should be an <code>EXTENT</code>.
    *
    * @param    ref
    *           The object reference to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The extent property value.
    */
   public static BaseDataType[] getDynamicPropExtent(object<? extends _BaseObject_> ref, String prop)
   {
      if (!validDynamicPropertyArgs(ref, prop, null, false))
      {
         //TODO: Check what happens in progress if the execution reaches this place
         throw new RuntimeException();
      }

      PropertyReference pref = new PropertyReference(ref, prop);

      return pref.getExtent();
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
    * inferred from the surrounding context that the return type can either be an
    * <code>EXTENT</code> or a scalar value.
    *
    * @param    ref
    *           The object reference to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The property value.
    */
   public static Object getDynamicPropPoly(object<? extends _BaseObject_> ref, String prop)
   {
      if (!validDynamicPropertyArgs(ref, prop, null, false))
      {
         //TODO: Check what happens in progress if the execution reaches this place
         throw new RuntimeException();
      }

      PropertyReference pref = new PropertyReference(ref, prop);

      if (pref.getPropertyExtent() != null)
      {
         // This seems to be a 4GL error but strangely, when trying to assign to a variable
         // declared as extent 1, when DYNAMIC-PROPERTY is supposed to return an extent of 1,
         // progress throws this error. This behavior is currently intentionally omitted.

         Object[] result;
         result = pref.getExtent();
         if (result.getClass().getComponentType() == object.class)
         {
            object<?>[] castedResult = (object<?>[]) result;
            boolean foundInst = false;
            for (int i = 0; i < Array.getLength(result); i++)
            {
               if (castedResult[i]._isValid())
               {
                  foundInst = true;
                  break;
               }
            }

            if (foundInst)
            {
               return result;
            }
            else
            {
               BaseDataType[] res = new BaseDataType[Array.getLength(result)];
               Arrays.fill(res, unknown.UNKNOWN);
               return res;
            }
         }

         return result;
      }
      else
      {
         Object result;
         result = pref.get();
         if (result.getClass() == object.class && ((object<?>) result).ref == null)
         {
            return unknown.UNKNOWN;
         }
         else
         {
            return result;
         }
      }
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter).
    * 
    * @param    ref
    *           The object reference to which the property belongs.
    * @param    prop
    *           The legacy property name (an extent property).
    * @param    idx
    *           The index in the extent property.
    *           
    * @return   The property value.
    */
   public static BaseDataType getDynamicProperty(object<? extends _BaseObject_> ref, 
                                                 character                      prop, 
                                                 NumberType                     idx)
   {
      if (!validDynamicPropertyArgs(ref, prop, idx, false))
      {
         return new unknown();
      }
      
      PropertyReference pref = new PropertyReference(ref, prop.toStringMessage(), idx);

      return pref.get();
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter).
    *
    * @param    ref
    *           The object reference to which the property belongs.
    * @param    prop
    *           The legacy property name (an extent property).
    * @param    idx
    *           The index in the extent property.
    *
    * @return   The property value.
    */
   public static BaseDataType getDynamicProperty(object<? extends _BaseObject_> ref,
                                                 String                         prop,
                                                 NumberType                     idx)
   {
      if (!validDynamicPropertyArgs(ref, prop, idx, false))
      {
         return new unknown();
      }

      PropertyReference pref = new PropertyReference(ref, prop, idx);

      return pref.get();
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter).
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The property value.
    */
   public static BaseDataType getDynamicProperty(character className, character prop)
   {
      return getDynamicProperty(className.toStringMessage(), prop.toStringMessage());
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
    * inferred from the surrounding context that the return type should be an <code>EXTENT</code>.
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The extent property value.
    */
   public static BaseDataType[] getDynamicPropExtent(character className, character prop)
   {
      return getDynamicPropExtent(className.toStringMessage(), prop.toStringMessage());
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
    * inferred from the surrounding context that the return type can either be an
    * <code>EXTENT</code> or a scalar value.
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The property value.
    */
   public static Object getDynamicPropPoly(character className, character prop)
   {
      return getDynamicPropPoly(className.toStringMessage(), prop.toStringMessage());
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter).
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The property value.
    */
   public static BaseDataType getDynamicProperty(String className, character prop)
   {
      return getDynamicProperty(className, prop.toStringMessage());
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
    * inferred from the surrounding context that the return type should be an <code>EXTENT</code>.
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The extent property value.
    */
   public static BaseDataType[] getDynamicPropExtent(String className, character prop)
   {
      return getDynamicPropExtent(className, prop.toStringMessage());
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
    * inferred from the surrounding context that the return type can either be an
    * <code>EXTENT</code> or a scalar value.
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The property value.
    */
   public static Object getDynamicPropPoly(String className, character prop)
   {
      return getDynamicPropPoly(className, prop.toStringMessage());
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter).
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The property value.
    */
   public static BaseDataType getDynamicProperty(character className, String prop)
   {
      return getDynamicProperty(className.toStringMessage(), prop);
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
    * inferred from the surrounding context that the return type should be an <code>EXTENT</code>.
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The extent property value.
    */
   public static BaseDataType[] getDynamicPropExtent(character className, String prop)
   {
      return getDynamicPropExtent(className.toStringMessage(), prop);
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
    * inferred from the surrounding context that the return type can either be an
    * <code>EXTENT</code> or a scalar value.
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The property value.
    */
   public static Object getDynamicPropPoly(character className, String prop)
   {
      return getDynamicPropPoly(className.toStringMessage(), prop);
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter).
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The property value.
    */
   public static BaseDataType getDynamicProperty(String className, String prop)
   {
      if (!validDynamicPropertyArgs(className, prop, null, false))
      {
         return new unknown();
      }

      PropertyReference pref = new PropertyReference(className, prop);

      return pref.get();
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
    * inferred from the surrounding context that the return type should be an <code>EXTENT</code>.
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The extent property value.
    */
   public static BaseDataType[] getDynamicPropExtent(String className, String prop)
   {
      if (!validDynamicPropertyArgs(className, prop, null, false))
      {
         //TODO: Check what happens in progress if the execution reaches this place
         throw new RuntimeException();
      }

      PropertyReference pref = new PropertyReference(className, prop);

      return pref.getExtent();
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
    * inferred from the surrounding context that the return type can either be an
    * <code>EXTENT</code> or a scalar value.
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name.
    *
    * @return   The property value.
    */
   public static Object getDynamicPropPoly(String className, String prop)
   {
      if (!validDynamicPropertyArgs(className, prop, null, false))
      {
         //TODO: Check what happens in progress if the execution reaches this place
         throw new RuntimeException();
      }

      PropertyReference pref = new PropertyReference(className, prop);

      if (pref.getPropertyExtent() != null)
      {
         // This seems to be a 4GL error but strangely, when trying to assign to a variable
         // declared as extent 1, when DYNAMIC-PROPERTY is supposed to return an extent of 1,
         // progress throws this error. This behavior is currently intentionally omitted.

         Object[] result;
         result = pref.getExtent();
         if (result.getClass().getComponentType() == object.class && Array.getLength(result) != 0)
         {
            object<?>[] castedResult = (object<?>[]) result;
            boolean foundInst = false;
            for (int i = 0; i < Array.getLength(result); i++)
            {
               if (castedResult[i]._isValid())
               {
                  foundInst = true;
                  break;
               }
            }

            if (foundInst)
            {
               return result;
            }
            else
            {
               BaseDataType[] res = new BaseDataType[Array.getLength(result)];
               Arrays.fill(res, unknown.UNKNOWN);
               return res;
            }
         }

         return result;
      }
      else
      {
         Object result = pref.get();
         if (result.getClass() == object.class && ((object<?>) result).ref == null)
         {
            return unknown.UNKNOWN;
         }
         else
         {
            return result;
         }
      }

   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter).
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name (an extent property).
    * @param    idx
    *           The index in the extent property.
    *
    * @return   The property value.
    */
   public static BaseDataType getDynamicProperty(character className, character prop, NumberType idx)
   {
      return getDynamicProperty(className.toStringMessage(), prop.toStringMessage(), idx);
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter).
    * 
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name (an extent property).
    * @param    idx
    *           The index in the extent property.
    *           
    * @return   The property value.
    */
   public static BaseDataType getDynamicProperty(String className, character prop, NumberType idx)
   {
      return getDynamicProperty(className, prop.toStringMessage(), idx);
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter).
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name (an extent property).
    * @param    idx
    *           The index in the extent property.
    *
    * @return   The property value.
    */
   public static BaseDataType getDynamicProperty(character className, String prop, NumberType idx)
   {
      return getDynamicProperty(className.toStringMessage(), prop, idx);
   }

   /**
    * Implementation of the DYNAMIC-PROPERTY function (getter).
    *
    * @param    className
    *           The legacy class to which the property belongs.
    * @param    prop
    *           The legacy property name (an extent property).
    * @param    idx
    *           The index in the extent property.
    *
    * @return   The property value.
    */
   public static BaseDataType getDynamicProperty(String className, String prop, NumberType idx)
   {
      if (!validDynamicPropertyArgs(className, prop, idx, false))
      {
         return new unknown();
      }

      PropertyReference pref = new PropertyReference(className, prop, idx);

      return pref.get();
   }
   
   /**
    * Wrapper for an argument resulting from a {@link #invokePoly} or {@link #invokeStandalonePoly} call; in
    * this case, the direct Java call must enforce the type at compile time, and the runtime will check if the
    * returned value is compatible with this type.
    * 
    * @param    <T>
    *           The expected type of this call.
    * @param    type
    *           The expected type of this call.
    * @param    res
    *           The result to check.
    *           
    * @return   The returned value.  An ERROR condition will be raised if the types are incompatible.
    */
   public static <T extends _BaseObject_> object<T> polyObjectArg(Class<T> type, Object res)
   {
      Class<?> resType = null;
      
      if (res instanceof object)
      {
         object<?> obj = (object<?>) res;
         if (obj._isValid())
         {
            resType = obj.ref.getClass();
         }
      }
      
      if (resType != null && !type.isAssignableFrom(resType))
      {
         // WARNING: the error message here deviates from the standard error message shown bellow.  This is
         // because 'polyArg' is emitted for direct Java calls, and we want to avoid the overhead of using
         // the dynamic APIs to execute this call.
         
         // Routine <program> <caller> sent called routine <target> <target-class> mismatched parameters. (2570)
         // Error attempting to push run time parameters onto the stack. (984)
         
         ErrorManager.recordOrThrowError(new int[] { 2570, 984 }, 
                                         new String[] 
                                         {
                                            "Routine sent called routine mismatched parameters", 
                                            "Error attempting to push run time parameters onto the stack"
                                         },
                                         false);
         return new object<>(type);
      }
      
      return (object) res;
   }
      
   /**
    * Wrapper for an argument resulting from a {@link #invokePoly} or {@link #invokeStandalonePoly} call; in
    * this case, the direct Java call must enforce the type at compile time, and the runtime will check if the
    * returned value is compatible with this type.
    * 
    * @param    <T>
    *           The expected type of this call.
    * @param    type
    *           The expected type of this call.
    * @param    res
    *           The result to check.
    *           
    * @return   The returned value.  An ERROR condition will be raised if the types are incompatible.
    */
   public static <T> T polyArg(Class<T> type, Object res)
   {
      Class<?> resType = res == null ? null : res.getClass();
      
      if (resType != null && !type.isAssignableFrom(resType))
      {
         // WARNING: the error message here deviates from the standard error message shown bellow.  This is
         // because 'polyArg' is emitted for direct Java calls, and we want to avoid the overhead of using
         // the dynamic APIs to execute this call.
         
         // Routine <program> <caller> sent called routine <target> <target-class> mismatched parameters. (2570)
         // Error attempting to push run time parameters onto the stack. (984)
         
         ErrorManager.recordOrThrowError(new int[] { 2570, 984 }, 
                                         new String[] 
                                         {
                                            "Routine sent called routine mismatched parameters", 
                                            "Error attempting to push run time parameters onto the stack"
                                         },
                                         false);
         return (T) BaseDataType.generateUnknown(type);
      }
      
      return (T) res;
   }
   
   /**
    * Perform a dynamic method call, on which the return type and the lookup are enforced via types emitted 
    * during conversion.
    * <p>
    * For the return type, if is not compatible, FWD will raise a 'Caller compilation ... 12882' ERROR; this
    * is earlier than OE does, but for chained calls, Java will abend if the correct type is not on stack when
    * it tries to invoke the next method, so we need to enforce it early.
    * <p>
    * For the method lookup, it will be limited to the target type emitted during conversion, and not the 
    * runtime type.
    *  
    * @param    <T>
    *           The expected return type.
    * @param    retType
    *           The expected return type.  The actual runtime returned type must be the same or a sub-class
    *           of this type.
    * @param    targetType
    *           The target type where the method lookup will be performed.
    * @param    mthdName
    *           The target method name.
    * @param    modes
    *           The argument modes.
    * @param    args
    *           The arguments.
    *           
    * @return   Always a {@link object} instance with the specified type.
    */
   public static <T extends _BaseObject_> object<T> invokePoly(Class<T>                      retType, 
                                                               Class<? extends _BaseObject_> targetType, 
                                                               String                        mthdName, 
                                                               String                        modes, 
                                                               Object...                     args)
   {
      if (!loadClass(local.get(), targetType))
      {
         return new object<T>(retType);
      }

      return (object<T>) invokePolyWorker(false, retType, targetType, targetType, mthdName, modes, args);
   }
   
   /**
    * Perform a dynamic method call, on which the method lookup is enforced via a type emitted during 
    * conversion.
    * <p>
    * For the method lookup, it will be limited to the target type emitted during conversion, and not the 
    * runtime type.
    *  
    * @param    targetType
    *           The target type where the method lookup will be performed.
    * @param    mthdName
    *           The target method name.
    * @param    modes
    *           The argument modes.
    * @param    args
    *           The arguments.
    *           
    * @return   Any {@link BaseDataType} type, as this is not emitted as lvalue for chained OO calls.
    */
   public static BaseDataType invokePoly(Class<? extends _BaseObject_> targetType, 
                                         String                        mthdName, 
                                         String                        modes, 
                                         Object...                     args)
   {
      if (!loadClass(local.get(), targetType))
      {
         return unknown.UNKNOWN;
      }

      return invokePolyWorker(false, null, targetType, targetType, mthdName, modes, args);
   }
   
   /**
    * Perform a dynamic method call, on which the method lookup is enforced via a type emitted during 
    * conversion.
    * <p>
    * For the method lookup, it will be limited to the target type emitted during conversion, and not the 
    * runtime type.
    *  
    * @param    targetType
    *           The target type where the method lookup will be performed.
    * @param    mthdName
    *           The target method name.
    * @param    modes
    *           The argument modes.
    * @param    args
    *           The arguments.
    */
   public static void invokeStandalonePoly(Class<? extends _BaseObject_> targetType, 
                                           String                        mthdName, 
                                           String                        modes, 
                                           Object...                     args)
   {
      if (!loadClass(local.get(), targetType))
      {
         return;
      }

      invokePolyWorker(true, null, targetType, targetType, mthdName, modes, args);
   }
   
   /**
    * Perform a dynamic method call, on which the return type and the lookup are enforced via types emitted 
    * during conversion.
    * <p>
    * For the return type, if is not compatible, FWD will raise a 'Caller compilation ... 12882' ERROR; this
    * is earlier than OE does, but for chained calls, Java will abend if the correct type is not on stack when
    * it tries to invoke the next method, so we need to enforce it early.
    * <p>
    * For the method lookup, it will be limited to the target type emitted during conversion, and not the 
    * runtime type.
    *  
    * @param    <T>
    *           The expected return type.
    * @param    retType
    *           The expected return type.  The actual runtime returned type must be the same or a sub-class
    *           of this type.
    * @param    ref
    *           The reference on which the invocation will be performed.
    * @param    targetType
    *           The target type where the method lookup will be performed.
    * @param    mthdName
    *           The target method name.
    * @param    modes
    *           The argument modes.
    * @param    args
    *           The arguments.
    *           
    * @return   Always a {@link object} instance with the specified type.
    */
   public static <T extends _BaseObject_> object<T> invokePoly(Class<T>                       retType, 
                                                               object<? extends _BaseObject_> ref, 
                                                               Class<? extends _BaseObject_>  targetType, 
                                                               String                         mthdName, 
                                                               String                         modes, 
                                                               Object...                      args)
   {
      _BaseObject_ theRef = ref.ref();
      if (theRef == null)
      {
         return new object<T>(retType);
      }

      return (object<T>) invokePolyWorker(false, retType, theRef, targetType, mthdName, modes, args);
   }
   
   /**
    * Perform a dynamic method call, on which the method lookup is enforced via a type emitted during 
    * conversion.
    * <p>
    * For the method lookup, it will be limited to the target type emitted during conversion, and not the 
    * runtime type.
    *  
    * @param    ref
    *           The reference on which the invocation will be performed.
    * @param    targetType
    *           The target type where the method lookup will be performed.
    * @param    mthdName
    *           The target method name.
    * @param    modes
    *           The argument modes.
    * @param    args
    *           The arguments.
    *           
    * @return   Any {@link BaseDataType} type, as this is not emitted as lvalue for chained OO calls.
    */
   public static BaseDataType invokePoly(object<? extends _BaseObject_> ref, 
                                         Class<? extends _BaseObject_>  targetType,
                                         String                         mthdName,
                                         String                         modes,
                                         Object...                      args)
   {
      _BaseObject_ theRef = ref.ref();
      if (theRef == null)
      {
         return unknown.UNKNOWN;
      }

      return invokePolyWorker(false, null, theRef, targetType, mthdName, modes, args);
   }
   
   /**
    * Perform a dynamic method call, on which the method lookup is enforced via a type emitted during 
    * conversion.
    * <p>
    * For the method lookup, it will be limited to the target type emitted during conversion, and not the 
    * runtime type.
    *  
    * @param    ref
    *           The reference on which the invocation will be performed.
    * @param    targetType
    *           The target type where the method lookup will be performed.
    * @param    mthdName
    *           The target method name.
    * @param    modes
    *           The argument modes.
    * @param    args
    *           The arguments.
    */
   public static void invokeStandalonePoly(object<? extends _BaseObject_> ref, 
                                           Class<? extends _BaseObject_>  targetType, 
                                           String                         mthdName, 
                                           String                         modes, 
                                           Object...                      args)
   {
      _BaseObject_ theRef = ref.ref();
      if (theRef == null)
      {
         return;
      }

      invokePolyWorker(true, null, theRef, targetType, mthdName, modes, args);
   }
   
   /**
    * Perform a dynamic method call, on which the return type and the lookup are enforced via types emitted 
    * during conversion.
    * <p>
    * For the return type, if is not compatible, FWD will raise a 'Caller compilation ... 12882' ERROR; this
    * is earlier than OE does, but for chained calls, Java will abend if the correct type is not on stack when
    * it tries to invoke the next method, so we need to enforce it early.
    * <p>
    * For the method lookup, it will be limited to the target type emitted during conversion, and not the 
    * runtime type.

    * @param    voidReturn
    *           Flag indicating if the method is expected to return a value or not.
    * @param    retType
    *           When set, the expected return type.  The actual runtime returned type must be the same or a 
    *           sub-class of this type.
    * @param    referent
    *           The reference on which the invocation will be performed.  This is either a sub-type of
    *           {@link _BaseObject_} (for static calls), or a {@link _BaseObject_} instance, otherwise.
    * @param    targetType
    *           The target type where the method lookup will be performed.
    * @param    mthdName
    *           The target method name.
    * @param    modes
    *           The argument modes.
    * @param    args
    *           The arguments.
    * 
    * @return   The method result.
    */
   private static BaseDataType invokePolyWorker(boolean                       voidReturn,
                                                Class<? extends _BaseObject_> retType,
                                                Object                        referent, 
                                                Class<? extends _BaseObject_> targetType, 
                                                String                        mthdName,
                                                String                        modes,
                                                Object...                     args)
   {
      boolean isStatic = referent instanceof Class;
      if (isStatic)
      {
         referent = ObjectOps.getStaticInstance((Class<? extends _BaseObject_>) referent);
      }
      
      String clsName = getLegacyName(targetType);
      InternalEntry ie = ControlFlowOps.resolveLegacyEntry(true, targetType, clsName, mthdName, isStatic, modes, args);
      if (ie == null && !isStatic)
      {
         isStatic = true;
         referent = ObjectOps.getStaticInstance(targetType);
                  
         // allow a static method to be invoked from an instance context
         ie = ControlFlowOps.resolveLegacyEntry(true, targetType, clsName, mthdName, isStatic, null, args);
      }
      
      if (ie == null)
      {
         WorkArea wa = local.get();
         String callingProcedure = SessionUtils._isRemote() ? "(Remote client)" : wa.pm.getStackEntry(0);
         String msg = "Routine %s sent called routine %s %s mismatched parameters";
         msg = String.format(msg, callingProcedure, mthdName, clsName);
         ErrorManager.recordOrThrowError(2570, msg, false);
         return unknown.UNKNOWN;
      }
      
      
      Object ret = ControlFlowOps.invokeLegacyMethod(ie, voidReturn, referent, clsName, mthdName, null, args);
      
      // OpenEdge is falling into an 'unstable' duck-typing mode when the return value of the runtime-resolved
      // method and the compile-resolved method are not a match.
      // in FWD, we do a 'best-of': the conversion-time resolved return type must be an exact match or a 
      // super-type for the runtime-returned method's value.
      if (retType != null)
      {
         Class<?> mthdRet = null;
         if (ret instanceof object)
         {
            if (((object) ret)._isValid())
            {
               mthdRet = ((object) ret).ref.getClass();
            }
            else
            {
               // unknown, assume the same
               mthdRet = retType;
            }
         }
         
         if (mthdRet == null || !retType.isAssignableFrom(mthdRet))
         {
            // the exact error message is this:
            // Could not access element '<method-name>' of class '<qualified-class-name>' using object of type 
            // '<qualified-actual-return-type>' - caller compilation is out of sync with class compilation. (12882)
            
            // this exact error is raised when the next chained call is being performed, and not when the 
            // 'mismatched' return type is determined in FWD - in OpenEdge terms, this is a programming error 
            // and needs to be fixed in the 4GL code.
            
            // in FWD terms, to solve this, would mean that right-side chained call needs to be treated as 
            // 'poly', too, which just complicates the generated Java code.
            
            // the error message is not the same as 4GL, because at this point there is no knowledge about
            // what method will be called; we enforced just the type.
            String msg = "Caller compilation is out of sync with class compilation - return type is " +  
                         retType + " while the expected type is " + ie.getMethod().getReturnType();
            ErrorManager.recordOrThrowError(12882, msg);
            return retType == null ? unknown.UNKNOWN : new object<>(retType);
         }
      }

      return (BaseDataType) ret;
   }

   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   public static BaseDataType invoke(String clsName, String mthdName)
   {
      return invoke(clsName, mthdName, null);
   }

   /**
    * Handles the invocation of the specified static method in the specified class. It is
    * generated if inferred from the surrounding context that the result should be an
    * <code>EXTENT</code>.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static BaseDataType[] invokeExtent(String clsName, String mthdName)
   {
      return invokeExtent(clsName, mthdName, null);
   }

   /**
    * Handles the invocation of the specified method in the specified class. It is generated
    * if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
    * or a scalar value.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    *
    * @return    The EXTENT or scalar result of the specified method.
    */
   public static Object dynamicInvoke(String clsName, String mthdName)
   {
      return dynamicInvoke(clsName, mthdName, null);
   }
   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   public static BaseDataType invoke(character clsName, String mthdName)
   {
      return invoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(), 
                    mthdName,
                    null);
   }

   /**
    * Handles the invocation of the specified static method in the specified class. It is
    * generated if inferred from the surrounding context that the result should be an
    * <code>EXTENT</code>.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static BaseDataType[] invokeExtent(character clsName, String mthdName)
   {
      return invokeExtent(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
                                 mthdName,
                                 null);
   }

   /**
     * Handles the invocation of the specified method in the specified class. It is generated
     * if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
     * or a scalar value.
     *
     * @param    clsName
     *           The legacy class name.
     * @param    mthdName
     *           The static method name.
     *
     * @return   The EXTENT or scalar result of the specified method.
     */
   public static Object dynamicInvoke(character clsName, String mthdName)
   {
      return dynamicInvoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
                           mthdName,
                           null);
   }
   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   public static BaseDataType invoke(String clsName, character mthdName)
   {
      return invoke(clsName, 
                    mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(), 
                    null);
   }

   /**
    * Handles the invocation of the specified static method in the specified class. It is
    * generated if inferred from the surrounding context that the result should be an
    * <code>EXTENT</code>.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static BaseDataType[] invokeExtent(String clsName, character mthdName)
   {
      return invokeExtent(clsName,
                                 mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                                 null);
   }

   /**
    * Handles the invocation of the specified method in the specified class. It is generated
    * if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
    * or a scalar value.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    *
    * @return    The EXTENT or scalar result of the specified method.
    */
   public static Object dynamicInvoke(String clsName, character mthdName)
   {
      return dynamicInvoke(clsName,
                           mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                           null);
   }
   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   public static BaseDataType invoke(character clsName, character mthdName)
   {
      return invoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(), 
                    mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                    null);
   }

   /**
    * Handles the invocation of the specified static method in the specified class. It is
    * generated if inferred from the surrounding context that the result should be an
    * <code>EXTENT</code>.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static BaseDataType[] invokeExtent(character clsName, character mthdName)
   {
      return invokeExtent(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
                                 mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                                 null);
   }

   /**
    * Handles the invocation of the specified method in the specified class. It is generated
    * if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
    * or a scalar value.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    *
    * @return    The EXTENT or scalar result of the specified method.
    */
   public static Object dynamicInvoke(character clsName, character mthdName)
   {
      return dynamicInvoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
                           mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                           null);
   }
   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   public static BaseDataType invoke(character clsName, 
                                     String    mthdName, 
                                     String    modes, 
                                     Object... args)
   {
      return invoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(), 
                    mthdName,
                    modes,
                    args);
   }

   /**
    * Handles the invocation of the specified static method in the specified class. It is
    * generated if inferred from the surrounding context that the result should be an
    * <code>EXTENT</code>.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static BaseDataType[] invokeExtent(character clsName,
                                             String    mthdName,
                                             String    modes,
                                             Object... args)
   {
      return invokeExtent(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
                                 mthdName,
                                 modes,
                                 args);
   }

   /**
    * Handles the invocation of the specified method in the specified class. It is generated
    * if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
    * or a scalar value.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *
    * @return    The EXTENT or scalar result of the specified method.
    */
   public static Object dynamicInvoke(character clsName,
                                      String    mthdName,
                                      String    modes,
                                      Object... args)
   {
      return dynamicInvoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
                          mthdName,
                          modes,
                          args);
   }
   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   public static BaseDataType invoke(String    clsName, 
                                     character mthdName, 
                                     String    modes, 
                                     Object... args)
   {
      return invoke(clsName, 
                    mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(), 
                    modes, 
                    args);
   }

   /**
    * Handles the invocation of the specified static method in the specified class. It is
    * generated if inferred from the surrounding context that the result should be an
    * <code>EXTENT</code>.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static BaseDataType[] invokeExtent(String    clsName,
                                             character mthdName,
                                             String    modes,
                                             Object... args)
   {
      return invokeExtent(clsName,
                                 mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                                 modes,
                                 args);
   }

   /**
    * Handles the invocation of the specified method in the specified class. It is generated
    * if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
    * or a scalar value.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *
    * @return    The EXTENT or scalar result of the specified method.
    */
   public static Object dynamicInvoke(String    clsName,
                                      character mthdName,
                                      String    modes,
                                      Object... args)
   {
      return dynamicInvoke(clsName,
                           mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                           modes,
                           args);
   }
   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   public static BaseDataType invoke(character clsName,
                                     character mthdName,
                                     String    modes, 
                                     Object... args)
   {
      return invoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(), 
                    mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                    modes,
                    args);
   }

   /**
    * Handles the invocation of the specified static method in the specified class. It is
    * generated if inferred from the surrounding context that the result should be an
    * <code>EXTENT</code>.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static BaseDataType[] invokeExtent(character clsName,
                                             character mthdName,
                                             String    modes,
                                             Object... args)
   {
      return invokeExtent(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
                                 mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                                 modes,
                                 args);
   }

   /**
    * Handles the invocation of the specified method in the specified class. It is generated
    * if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
    * or a scalar value.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *
    * @return    The EXTENT or scalar result of the specified method.
    */
   public static Object dynamicInvoke(character clsName,
                                      character mthdName,
                                      String    modes,
                                      Object... args)
   {
      return dynamicInvoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
                           mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                           modes,
                           args);
   }

   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   public static BaseDataType invoke(String clsName, String mthdName, String modes, Object... args)
   {
      Object result = invoke(false, clsName, mthdName, modes, args);

      //Check for a non valid return type
      if (result.getClass().isArray())
      {
         ErrorManager.recordOrThrowError(11428);
      }
      return (BaseDataType) result;
   }

   /**
    * Handles the invocation of the specified static method in the specified class. It is
    * generated if inferred from the surrounding context that the result should be an
    * <code>EXTENT</code>.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static BaseDataType[] invokeExtent(String    clsName,
                                             String    mthdName,
                                             String    modes,
                                             Object... args)
   {
      Object result = invoke(false, clsName, mthdName, modes, args);

      //Check for a non valid return type
      if (!result.getClass().isArray())
      {
         ErrorManager.recordOrThrowError(11428);
      }
      return (BaseDataType[]) result;
   }

   /**
    * Handles the invocation of the specified method in the specified class. It is generated
    * if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
    * or a scalar value.
    *
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *
    * @return    The EXTENT or scalar result of the specified method.
    */
   public static Object dynamicInvoke(String    clsName,
                                      String    mthdName,
                                      String    modes,
                                      Object... args)
   {
      return invoke(false, clsName, mthdName, modes, args);
   }

   /**
    * Invoke the specified instance method in the specified object.
    * 
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The method name.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   public static BaseDataType invoke(object<? extends _BaseObject_> ref, character mthdName)
   {
      return invoke(ref, mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(), null);
   }

   /**
    * Handles the invocation of the specified static method in the specified class. It is
    * generated if inferred from the surrounding context that the result should be an
    * <code>EXTENT</code>.
    *
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The method name.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static BaseDataType[] invokeExtent(object<? extends _BaseObject_> ref,
                                             character                      mthdName)
   {
      return invokeExtent(ref,
                                 mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                          null);
   }

   /**
    * Handles the invocation of the specified method in the specified class. It is generated
    * if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
    * or a scalar value.
    *
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The method name.
    *
    * @return    The EXTENT or scalar result of the specified method.
    */
   public static Object dynamicInvoke(object<? extends _BaseObject_> ref, character mthdName)
   {
      return dynamicInvoke(ref,
                           mthdName.isUnknown() ?
                                    (String) null :
                                    mthdName.toStringMessage(), null);
   }
   
   /**
    * Invoke the specified instance method in the specified object.
    * 
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The method name.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   public static BaseDataType invoke(object<? extends _BaseObject_> ref, String mthdName)
   {
      return invoke(ref, mthdName, null);
   }

   /**
    * Handles the invocation of the specified static method in the specified class. It is
    * generated if inferred from the surrounding context that the result should be an
    * <code>EXTENT</code>.
    *
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The method name.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static BaseDataType[] invokeExtent(object<? extends _BaseObject_> ref,
                                             String                         mthdName)
   {
      return invokeExtent(ref, mthdName, null);
   }

   /**
    * Handles the invocation of the specified method in the specified class. It is generated
    * if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
    * or a scalar value.
    *
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The method name.
    *
    * @return    The EXTENT or scalar result of the specified method.
    */
   public static Object dynamicInvoke(object<? extends _BaseObject_> ref, String mthdName)
   {
      return dynamicInvoke(ref, mthdName, null);
   }

   /**
    * Invoke the specified instance method in the specified object.
    * 
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   public static BaseDataType invoke(object<? extends _BaseObject_> ref, 
                                     character                      mthdName, 
                                     String                         modes, 
                                     Object...                      args)
   {
      return invoke(ref,
                    mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                    modes, 
                    args);
   }

   /**
    * Handles the invocation of the specified static method in the specified class. It is
    * generated if inferred from the surrounding context that the result should be an
    * <code>EXTENT</code>.
    *
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static BaseDataType[] invokeExtent(object<? extends _BaseObject_> ref,
                                             character                      mthdName,
                                             String                         modes,
                                             Object...                      args)
   {
      return invokeExtent(ref,
                                 mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                                 modes,
                                 args);
   }

   /**
    * Handles the invocation of the specified method in the specified class. It is generated
    * if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
    * or a scalar value.
    *
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static Object dynamicInvoke(object<? extends _BaseObject_> ref,
                                      character                      mthdName,
                                      String                         modes,
                                      Object...                      args)
   {
      return dynamicInvoke(ref,
                           mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
                           modes,
                           args);
   }
   
   /**
    * Invoke the specified instance method in the specified object.
    * 
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   public static BaseDataType invoke(object<? extends _BaseObject_> ref, 
                                     String                         mthdName, 
                                     String                         modes, 
                                     Object...                      args)
   {
      Object result = invoke(false, ref, mthdName, modes, args);

      // Check the result for an EXTENT type
      if (result.getClass().isArray())
      {
         ErrorManager.recordOrThrowError(11428);
      }
      return (BaseDataType) result;
   }

   /**
    * Handles the invocation of the specified static method in the specified class. It is
    * generated if inferred from the surrounding context that the result should be an
    * <code>EXTENT</code>.
    *
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static BaseDataType[] invokeExtent(object<? extends _BaseObject_> ref,
                                             String                         mthdName,
                                             String                         modes,
                                             Object...                      args)
   {
      Object result = invoke(false, ref, mthdName, modes, args);

      //Check for a non valid return type
      if (!result.getClass().isArray())
      {
         ErrorManager.recordOrThrowError(11428);
      }
      return (BaseDataType[]) result;
   }

   /**
    * Handles the invocation of the specified method in the specified class. It is generated
    * if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
    * or a scalar value.
    *
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *
    * @return    The EXTENT result of the specified method.
    */
   public static Object dynamicInvoke(object<? extends _BaseObject_> ref,
                                      String                         mthdName,
                                      String                         modes,
                                      Object...                      args)
   {
      return invoke(false, ref, mthdName, modes, args);
   }
   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    */
   public static void invokeStandalone(String clsName, String mthdName)
   {
      invoke(true, clsName, mthdName, null);
   }
   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    */
   public static void invokeStandalone(character clsName, String mthdName)
   {
      invoke(true, 
             clsName.isUnknown() ? (String) null : clsName.toStringMessage(), 
             mthdName,
             null);
   }
   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    */
   public static void invokeStandalone(String clsName, character mthdName)
   {
      invoke(true,
             clsName, 
             mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(), 
             null);
   }
   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    */
   public static void invokeStandalone(character clsName, character mthdName)
   {
      invoke(true,
             clsName.isUnknown() ? (String) null : clsName.toStringMessage(), 
             mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
             null);
   }
   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    */
   public static void invokeStandalone(character clsName, 
                                       String    mthdName, 
                                       String    modes, 
                                       Object... args)
   {
      invoke(true,
             clsName.isUnknown() ? (String) null : clsName.toStringMessage(), 
             mthdName,
             modes,
             args);
   }
   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    */
   public static void invokeStandalone(String    clsName, 
                                       character mthdName, 
                                       String    modes, 
                                       Object... args)
   {
      invoke(true,
             clsName, 
             mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(), 
             modes, 
             args);
   }
   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    */
   public static void invokeStandalone(character clsName,
                                       character mthdName,
                                       String    modes, 
                                       Object... args)
   {
      invoke(true,
             clsName.isUnknown() ? (String) null : clsName.toStringMessage(), 
             mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
             modes,
             args);
   }

   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    */
   public static void invokeStandalone(String    clsName, 
                                       String    mthdName,
                                       String    modes, 
                                       Object... args)
   {
      invoke(true, clsName, mthdName, modes, args);
   }

   /**
    * Invoke the specified instance method in the specified object.
    * 
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The static method name.
    */
   public static void invokeStandalone(object<? extends _BaseObject_> ref, character mthdName)
   {
      invoke(true, ref, mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(), null);
   }
   
   /**
    * Invoke the specified instance method in the specified object.
    * 
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The static method name.
    */
   public static void invokeStandalone(object<? extends _BaseObject_> ref, String mthdName)
   {
      invoke(true, ref, mthdName, null);
   }

   /**
    * Invoke the specified instance method in the specified object.
    * 
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    */
   public static void invokeStandalone(object<? extends _BaseObject_> ref, 
                                       character                      mthdName, 
                                       String                         modes, 
                                       Object...                      args)
   {
      invoke(true,
             ref,
             mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
             modes, 
             args);
   }
   
   /**
    * Invoke the specified instance method in the specified object.
    * 
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    */
   public static void invokeStandalone(object<? extends _BaseObject_> ref, 
                                       String                         mthdName, 
                                       String                         modes, 
                                       Object...                      args)
   {
      invoke(true, ref, mthdName, modes, args);
   }

   /**
    * Deletes the given object.
    *
    * @param   o
    *          The object to be deleted.
    */
   public static void delete(object<?> o)
   {
      if (o.isUnknown() || !isValid(o.ref))
      {
         ErrorManager.recordOrThrowError(5425, 
                                         "Invalid or inappropriate handle value given to " +
                                         "DELETE OBJECT or DELETE PROCEDURE statement", 
                                         false);
         return;
      }
      
      // non-tracked instances cannot be deleted, this is a NOP
      if (!o.ref.isTracked())
      {
         return;
      }

      boolean deleted = false;
      
      try
      {
         deleted = delete(o.ref);
      }
      finally
      {
         if (deleted)
         {
            o.setUnknown();
         }
      }
   }

   /**
    * Create a new dynamic object.
    * 
    * @param    legacyClsName
    *           The legacy class name.  This may be qualified or unqualified.
    *           
    * @return   A new created object wrapped in a {@link object} instance.
    */
   public static <T extends _BaseObject_> object<T> newDynamicInstance(String legacyClsName)
   {
      return newDynamicInstance(legacyClsName, null);
   }
   
   /**
    * Create a new dynamic object; this API is emitted for a NEW converted to DYNAMIC-NEW, as the arguments
    * are POLY.
    * 
    * @param    cls
    *           The legacy class.
    * @param    modes
    *           The argument's modes.
    * @param    args
    *           The arguments.
    *           
    * @return   A new created object wrapped in a {@link object} instance.
    */
   public static <T extends _BaseObject_> object<T> newDynamicInstance(Class<T>  cls,
                                                                       String    modes,
                                                                       Object... args)
   {
      // enums cannot be instantiated
      if (LegacyEnum.failEnumInstantiation(cls))
      {
         return new object<T>();
      }
      
      String legacyClsName = getLegacyName(cls);
      return newInstanceInternal(cls, legacyClsName, modes, args);
   }
   
   /**
    * Create a new dynamic object.
    * 
    * @param    legacyClsName
    *           The legacy class name.  This may be qualified or unqualified.
    * @param    modes
    *           The argument's modes.
    * @param    args
    *           The arguments.
    *           
    * @return   A new created object wrapped in a {@link object} instance.
    */
   public static <T extends _BaseObject_> object<T> newDynamicInstance(String    legacyClsName,
                                                                       String    modes,
                                                                       Object... args)
   {
      Class<T> cls = resolveClass(legacyClsName);
      
      if (cls == null)
      {
         ErrorManager.recordOrThrowError(14284, 
                                         "DYNAMIC-NEW could not find class " + legacyClsName, 
                                         false);
         return new object<T>();
      }
      
      // enums cannot be instantiated
      if (LegacyEnum.failEnumInstantiation(cls))
      {
         return new object<T>();
      }
      
      //TODO: Fix the case for throwing the error 13844 and not 15310
      return newInstanceInternal(cls, legacyClsName, modes, args);
   }

   /**
    * Create a new dynamic object.
    * 
    * @param    legacyClsName
    *           The legacy class name.  This may be qualified or unqualified.
    *           
    * @return   A new created object wrapped in a {@link object} instance.
    */
   public static <T extends _BaseObject_> object<T> newDynamicInstance(character legacyClsName)
   {
      return newDynamicInstance(legacyClsName.toStringMessage(), null);
   }

   /**
    * Create a new dynamic object.
    * 
    * @param    legacyClsName
    *           The legacy class name.  This may be qualified or unqualified.
    * @param    modes
    *           The argument's modes.
    * @param    args
    *           The arguments.
    *           
    * @return   A new created object wrapped in a {@link object} instance.
    */
   public static <T extends _BaseObject_> object<T> newDynamicInstance(character legacyClsName,
                                                                       String    modes,
                                                                       Object... args)
   {
      return newDynamicInstance(legacyClsName.toStringMessage(), modes, args);
   }
   
   /**
    * Create a new object.
    * 
    * @param    cls
    *           The object's class.
    *           
    * @return   A new created object wrapped in a {@link object} instance.
    */
   public static <T extends _BaseObject_> object<T> newInstance(Class<T>  cls)
   {
      return newInstance(cls, null);
   }
   
   /**
    * Create a new object.
    * 
    * @param    cls
    *           The object's class.
    * @param    bypassConstructor
    *           Avoid executing the constructor method during initialization if <code>true</code>.
    * @param    args
    *           The arguments.
    *           
    * @return   A new created object wrapped in a {@link object} instance.
    */
   public static <T extends _BaseObject_> object<T> newInstance(Class<T>   cls,
                                                                boolean    bypassConstructor,
                                                                Object...  args)
   {
      String name = getLegacyName(cls);
      
      return newInstanceInternal(cls, name, null, bypassConstructor, args);
   }
   
   /**
    * Create a new object.
    * 
    * @param    cls
    *           The object's class.
    * @param    modes
    *           The argument modes.
    * @param    args
    *           The arguments.
    *           
    * @return   A new created object wrapped in a {@link object} instance.
    */
   public static <T extends _BaseObject_> object<T> newInstance(Class<T>  cls,
                                                                String    modes,
                                                                Object... args)
   {
      String name = getLegacyName(cls);
      
      return newInstanceInternal(cls, name, modes, false, args);
   }
   
   /**
    * Create a new object.
    * 
    * @param    cls
    *           The object's class.
    * @param    name
    *           The legacy class name.  This may be qualified or unqualified.
    * @param    modes
    *           The argument modes.
    * @param    args
    *           The arguments.
    *           
    * @return   A new created object wrapped in a {@link object} instance.
    */
   public static <T extends _BaseObject_> object<T> newInstanceInternal(Class<T>  cls, 
                                                                        String    name, 
                                                                        String    modes,
                                                                        Object... args)
   {
      return newInstanceInternal(cls, name, modes, false, args);
   }

   /**
    * Create a new object.
    * 
    * @param    cls
    *           The object's class.
    * @param    name
    *           The legacy class name.  This may be qualified or unqualified.
    * @param    modes
    *           The argument modes.
    * @param    bypassConstructor
    *           Avoid executing the constructor method during initialization if <code>true</code>.
    * @param    args
    *           The arguments.
    *           
    * @return   A new created object wrapped in a {@link object} instance.
    */
   public static <T extends _BaseObject_> object<T> newInstanceInternal(Class<T>  cls, 
                                                                        String    name, 
                                                                        String    modes,
                                                                        boolean   bypassConstructor,
                                                                        Object... args)
   {
      if (cls == _BaseObject_.class)
      {
         cls = (Class<T>) BaseObject.class;
      }

      // must not be an interface, enum or an abstract class

      if (LegacyEnum.class.isAssignableFrom(cls))
      {
         String cname = ObjectOps.getLegacyName(cls);
         
         // in the 4GL, this is a compile error but this provides runtime safety
         String msg = String.format("Cannot use NEW statement with '%s' because it is an enum",
                                    cname);
         
         ErrorManager.recordOrThrowError(17970, msg, false);
         
         return new object<T>(cls);
      }
      if (Modifier.isAbstract(cls.getModifiers()))
      {
         ErrorManager.recordOrThrowError(15107, 
                                         "You cannot create an instance of " + name + 
                                         " because it is an abstract class",
                                         false);
         
         return new object<T>(cls);
      }
      if (cls.isInterface())
      {
         ErrorManager.recordOrThrowError(12879, 
                                         name + " is an interface and cannot be used as a " +
                                         "procedure name for RUN or the type name for a NEW " +
                                         "statement", 
                                         false);
         
         return new object<T>(cls);
      }

      ObjectResource res = null;
      WorkArea wa = local.get();
      wa.tm.registerTopLevelScopeable(wa);
      
      try
      {
         //  before creating the resource, load the static constructor(s)
         if (!loadClass(wa, cls))
         {
            return new object<T>(cls);
         }
         
         String oldName = wa.pm.getInstantiatingExternalProgram();
         Class<?> oldCls = wa.pm.getInstantiatingExternalProgramClass();
         T ref = null;
         try
         {
            wa.pm.setInstantingExternalProgram(name);
            wa.pm.setInstantingExternalProgramClass(cls);
            ref = cls.getDeclaredConstructor().newInstance();
         }
         finally
         {
            wa.pm.setInstantingExternalProgram(oldName);
            wa.pm.setInstantingExternalProgramClass(oldCls);
         }

         res = newInstanceInternal(wa, ref, cls, name, modes, bypassConstructor, args);
         
         if (res == null)
         {
            String errMsg = "Ambiguous runtime method call. Could not resolve " +
                         cls.getSimpleName() + " reference";
            ErrorManager.recordOrThrowError(13844, errMsg, false);
            return null;
         }
         
         return new object<T>(ref);
      }
      catch (ReflectiveOperationException    |
             IllegalArgumentException        e)
      {
         ControlFlowOps.cleanupPending();
         if (res != null)
         {
            res.delete();
         }

         // TODO: log this?
         return new object<T>(cls);
      }
      catch (RuntimeException exe)
      {
         ControlFlowOps.cleanupPending();
         throw exe;
      }
   }
   
   /**
    * Create a new object.
    * 
    * @param    wa
    *           The context-local state.
    * @param    ref
    *           The Java reference for this {@link _BaseObject_} instance.
    * @param    cls
    *           The object's class.
    * @param    name
    *           The legacy class name.  This may be qualified or unqualified.
    * @param    modes
    *           The argument modes.
    * @param    args
    *           The arguments.
    *           
    * @return   A new created object wrapped in a {@link object} instance.
    */
   private static <T extends _BaseObject_> ObjectResource newInstanceInternal(WorkArea  wa,
                                                                              T         ref, 
                                                                              Class<T>  cls, 
                                                                              String    name, 
                                                                              String    modes,
                                                                              Object... args)
   {
      return newInstanceInternal(wa, ref, cls, name, modes, false, args);
   }
   
   /**
    * Create a new object.
    * 
    * @param    wa
    *           The context-local state.
    * @param    ref
    *           The Java reference for this {@link _BaseObject_} instance.
    * @param    cls
    *           The object's class.
    * @param    name
    *           The legacy class name.  This may be qualified or unqualified.
    * @param    modes
    *           The argument modes.
    * @param    bypassConstructor
    *           Avoid executing the constructor method during initialization if <code>true</code>.
    * @param    args
    *           The arguments.
    *           
    * @return   A new created object wrapped in a {@link object} instance.
    */
   private static <T extends _BaseObject_> ObjectResource newInstanceInternal(WorkArea  wa,
                                                                              T         ref, 
                                                                              Class<T>  cls, 
                                                                              String    name, 
                                                                              String    modes,
                                                                              boolean   bypassConstructor,
                                                                              Object... args)
   {
      // enums can't be instantiated
      if (LegacyEnum.class.isAssignableFrom(cls))
      {
         String cname = ObjectOps.getLegacyName(cls);
         
         // in the 4GL, this is a compile error but this provides runtime safety
         String msg = String.format("Cannot use NEW statement with '%s' because it is an enum",
                                    cname);
         
         ErrorManager.recordOrThrowError(17970, msg, false);
         
         return null;
      }

      // create a synthetic resource tracking this object; this is performed before anything 
      // else (static or instance constructor) gets executed
      ObjectResource res = new ObjectResource(ref);
      List<InternalEntry> dtors = SourceNameMapper.getDestructors(cls);
      if (dtors != null)
      {
         for (InternalEntry dtor : dtors)
         {
            res.pushDestructor(dtor);
         }
      }

      // this is saved regardless if instantiation passes or not - it will be cleared on delete
      wa.objects.put(ref, res);
      ref.__setValidInternal__(true);

      Method[] executes = SourceNameMapper.getExecuteMethods(cls);

      // if an error was throw from within a c'tor (or UNDO, THROW with ROUTINE-LEVEL ON ERROR UNDO, THROW.)
      // then the initialization failed and call the destructors for all initialized classes
      
      pendingInitialize(wa, ref);
      
      boolean ok = false;
      boolean err = false;
      try
      {
         ok = ControlFlowOps.initializeLegacyObject(ref, name, executes, modes, bypassConstructor, args);
      }
      
      // allow Progress compatible exceptions to flow through
      catch (ConditionException | RetryUnwindException ce)
      {
         err = true;
         throw ce;
      }
      
      finally
      {
         endInitialization(wa, ref);
         
         if (!ok)
         {
            // couldn't initialize... delete the resource
            res.delete();
            
            if (!res.valid())
            {
               wa.objects.remove(ref);
               ref.__setValidInternal__(false);
            }
            
            if (!err)
            {
               return null;
            }
         }
      }
      
      if (!isValid(ref))
      {
         // the object was deleted by the constructor - don't do anything else
         return null;
      }
      
      // 4GL deletes the NEW statement's reference immediately (if was not saved by other
      // means). we delay this check until the block exits.
      pendingAssign(wa, ref, true);
      
      return res;
   }
   
   /**
    * Check if the given class is the same or a super-class of the given object.
    * 
    * @param    o
    *           The object instance.
    * @param    cls
    *           The type to check.
    *           
    * @return   <code>true</code> if the given class is the same or a super-class of the object's 
    *           type.
    */
   public static logical typeOf(BaseDataType o, Class<?> cls)
   {
      if (o.isUnknown())
      {
         return logical.UNKNOWN;
      }
      
      if (!(o instanceof object))
      {
         return logical.FALSE;
      }
      
      return typeOf((object) o, cls);
   }
   
   /**
    * Check if the given class is the same or a super-class of the given object.
    * 
    * @param    o
    *           The object instance.
    * @param    cls
    *           The type to check.
    *           
    * @return   <code>true</code> if the given class is the same or a super-class of the object's 
    *           type.
    */
   public static logical typeOf(object<?> o, Class<?> cls)
   {
      if (o.isUnknown())
      {
         // unknown value is returned
         return logical.UNKNOWN;
      }
      
      Object ref = o.ref;
      
      return logical.of(cls != null && ref != null && cls.isInstance(ref));
   }
   
   /**
    * Check if the given class is the same or a super-class of the given object.
    * 
    * @param    o
    *           The object instance.
    * @param    cls
    *           The type to check.
    *           
    * @return   <code>true</code> if the given class is the same or a super-class of the object's 
    *           type.
    */
   public static logical typeOf(BaseDataType o, String cls)
   {
      if (o.isUnknown())
      {
         return logical.UNKNOWN;
      }
      
      if (!(o instanceof object))
      {
         return logical.FALSE;
      }
      
      return typeOf((object) o, cls);
   }
   
   /**
    * Check if the given class is the same or a super-class of the given object.
    * 
    * @param    o
    *           The object instance.
    * @param    cls
    *           The type to check.
    *           
    * @return   <code>true</code> if the given class is the same or a super-class of the object's 
    *           type.
    */
   public static logical typeOf(object<?> o, String cls)
   {
      if (o.isUnknown())
      {
         // unknown value is returned
         return logical.UNKNOWN;
      }
      
      Class<? extends _BaseObject_> lcls = resolveClass(cls);
      if (lcls == null)
      {
         return logical.UNKNOWN;
      }
      
      return typeOf(o, lcls);
   }

   /**
    * Cast this object to the specified type.
    * 
    * @param    val
    *           The object instance.
    * @param    cls
    *           The type to check.
    *           
    * @return   An object of the new type.  If the value is not an {@link object}, then unknown
    *           is returned.
    */
   public static <T extends _BaseObject_> object<T> cast(BaseDataType val, Class<T> cls)
   {
      if (!(val instanceof object))
      {
         // if the value is not an object, then return unkown. 4GL doesn't show any error.
         return new object();
      }
      
      return cast((object) val, cls);
   }
   
   /**
    * Cast this object to the specified type.
    * 
    * @param    o
    *           The object instance.
    * @param    cls
    *           The type to check.
    *           
    * @return   A new {@link object} instance, if the cast is possible.
    */
   public static <T extends _BaseObject_> object<T> cast(object<?> o, Class<T> cls)
   {
      if (o.isUnknown())
      {
         // no error is thrown
         return new object<T>(cls);
      }
      
      if (!cls.isInstance(o.ref))
      {
         String t1 = getLegacyName((Class<? extends _BaseObject_>) o.ref.getClass());
         String t2 = getLegacyName(cls);
         
         // at least for enums this is a warning, the error-status:error is false
         ErrorManager.recordOrThrowError(12869, "Invalid cast from " + t1 + " " + t2, false, false);
         
         // unknown object is returned
         return new object<T>(cls);
      }
      
      return new object<T>((T) o.ref);
   }
   
   /**
    * Cast this object array to the specified type.
    * 
    * @param    o
    *           The object array instance.
    * @param    cls
    *           The type to check.
    *           
    * @return   A new {@link object} array instance, if the cast is possible.
    */
   public static <T extends _BaseObject_> object<T>[] cast(object<?>[] o, Class<T> cls)
   {
      object<T>[] objArr = TypeFactory.objectExtent(o.length, cls, false);
      
      for (int i = 0; i < objArr.length; i++)
      {
         objArr[i].assign(cast(o[i], cls));
      }
      
      return objArr;
   }

   /**
    * Find and return the specified enum.
    * 
    * @param    cname
    *           The enum class name.
    * @param    value
    *           The enum value.
    *           
    * @return   The enum instance.
    */
   public static object<? extends LegacyEnum> dynamicEnum(String cname, int64 value)
   {
      return dynamicEnum(new character(cname), value);
   }

   /**
    * Find and return the specified enum.
    * 
    * @param    cname
    *           The enum class name.
    * @param    value
    *           The enum value.
    *           
    * @return   The enum instance.
    */
   public static <T extends LegacyEnum> object<T> dynamicEnum(character cname, int64 value)
   {
      Class<T> cls = LegacyEnum.findClass(cname,
                                          ObjectOps::generateInvalidTypeDE,
                                          LegacyEnum::generateFailedToFindEnumType);
      
      if (cls == null)
      {
         return new object<T>();
      }
      
      return LegacyEnum.lookupWorker(cls, value, ObjectOps::generateInvalidValueDE); 

   }

   /**
    * Find and return the specified enum.
    * 
    * @param    cname
    *           The enum class name.
    * @param    name
    *           The enum name.
    *           
    * @return   The enum instance.
    */
   public static object<? extends LegacyEnum> dynamicEnum(String cname, character name)
   {
      return dynamicEnum(new character(cname), name);
   }

   /**
    * Find and return the specified enum.
    * 
    * @param    cname
    *           The enum class name.
    * @param    name
    *           The enum name.
    *           
    * @return   The enum instance.
    */
   public static <T extends LegacyEnum> object<T> dynamicEnum(character cname, character name)
   {
      Class<T> cls = LegacyEnum.findClass(cname,
                                          ObjectOps::generateInvalidTypeDE,
                                          LegacyEnum::generateFailedToFindEnumType);
      
      if (cls == null)
      {
         return new object<T>();
      }
      
      return LegacyEnum.lookupWorker(cls, name, ObjectOps::generateInvalidValueDE); 
   }

   /**
    * Find and return the specified enum.
    * 
    * @param    cname
    *           The enum class name.
    * @param    value
    *           The enum value.
    *           
    * @return   The enum instance.
    */
   public static object<? extends LegacyEnum> dynamicEnum(String cname, long value)
   {
      return dynamicEnum(new character(cname), new int64(value));
   }

   /**
    * Find and return the specified enum.
    * 
    * @param    cname
    *           The enum class name.
    * @param    value
    *           The enum value.
    *           
    * @return   The enum instance.
    */
   public static object<? extends LegacyEnum> dynamicEnum(character cname, long value)
   {
      return dynamicEnum(cname, new int64(value));
   }

   /**
    * Find and return the specified enum.
    * 
    * @param    cname
    *           The enum class name.
    * @param    name
    *           The enum name.
    *           
    * @return   The enum instance.
    */
   public static object<? extends LegacyEnum> dynamicEnum(String cname, String name)
   {
      return dynamicEnum(new character(cname), new character(name));
   }

   /**
    * Find and return the specified enum.
    * 
    * @param    cname
    *           The enum class name.
    * @param    name
    *           The enum name.
    *           
    * @return   The enum instance.
    */
   public static object<? extends LegacyEnum> dynamicEnum(character cname, String name)
   {
      return dynamicEnum(cname, new character(name));
   }

   /**
    * Cast this object to the specified type, resolved dynamically.
    * 
    * @param    o
    *           The object instance.
    * @param    legacyClsName
    *           The legacy class name.  This may be qualified or unqualified.
    *           
    * @return   A new {@link object} instance, if the cast is possible.  
    */
   public static object<? extends _BaseObject_> dynamicCast(object<?> o, String legacyClsName)
   {
      Class<? extends _BaseObject_> cls = resolveClass(legacyClsName);
      
      if (cls == null)
      {
         ErrorManager.recordOrThrowError(12900, 
                                        "CAST to '" + legacyClsName + "' not allowed. " + 
                                        "CAST target must be a user-defined type",
                                        false,
                                        false);
         return new object<>();
      }

      return cast(o, cls);
   }

   /**
    * Cast this object to the specified type, resolved dynamically.
    * 
    * @param    o
    *           The object instance.
    * @param    legacyClsName
    *           The legacy class name.  This may be qualified or unqualified.
    *           
    * @return   A new {@link object} instance, if the cast is possible.  
    */
   public static object<? extends _BaseObject_> dynamicCast(object<?> o, character legacyClsName)
   {
      if (legacyClsName.isUnknown() || legacyClsName.toStringMessage().isEmpty())
      {
         ErrorManager.recordOrThrowError(12900, 
                                        "CAST to 'UNKNOWN' not allowed. " + 
                                        "CAST target must be a user-defined type",
                                        false,
                                        false);

         return new object<>();
      }

      return dynamicCast(o, legacyClsName.toStringMessage());
   }

   /**
    * Cast this object to the specified type, resolved dynamically.
    * 
    * @param    o
    *           The object instance.
    * @param    legacyClsName
    *           The legacy class name.  This may be qualified or unqualified.
    *           
    * @return   A new {@link object} instance, if the cast is possible.  
    */
   public static object<? extends _BaseObject_> dynamicCast(BaseDataType o, String legacyClsName)
   {
      Class<? extends _BaseObject_> cls = resolveClass(legacyClsName);
      
      if (cls == null)
      {
         ErrorManager.recordOrThrowError(12900, 
                                        "CAST to '" + legacyClsName + "' not allowed. " + 
                                        "CAST target must be a user-defined type",
                                        false,
                                        false);
         return new object<>();
      }

      return cast(o, cls);
   }

   /**
    * Cast this object to the specified type, resolved dynamically.
    * 
    * @param    o
    *           The object instance.
    * @param    legacyClsName
    *           The legacy class name.  This may be qualified or unqualified.
    *           
    * @return   A new {@link object} instance, if the cast is possible.  
    */
   public static object<? extends _BaseObject_> dynamicCast(BaseDataType o, character legacyClsName)
   {
      if (legacyClsName.isUnknown() || legacyClsName.toStringMessage().isEmpty())
      {
         ErrorManager.recordOrThrowError(12900, 
                                        "CAST to 'UNKNOWN' not allowed. " + 
                                        "CAST target must be a user-defined type",
                                        false,
                                        false);

         return new object<>();
      }

      return dynamicCast(o, legacyClsName.toStringMessage());
   }
   
   /**
    * Cast this object array to the specified type, resolved dynamically.
    * 
    * @param    o
    *           The object array instance.
    * @param    legacyClsName
    *           The legacy class name.  This may be qualified or unqualified.
    *           
    * @return   A new {@link object} array instance, if the cast is possible.  
    */
   public static object<? extends _BaseObject_>[] dynamicCast(object<?>[] o, character legacyClsName)
   {
      if (legacyClsName.isUnknown() || legacyClsName.toStringMessage().isEmpty())
      {
         ErrorManager.recordOrThrowError(12900, 
                                        "CAST to 'UNKNOWN' not allowed. " + 
                                        "CAST target must be a user-defined type", 
                                        false,
                                        false);
      }

      return dynamicCast(o, legacyClsName.toStringMessage());
   }
   
   /**
    * Cast this object array to the specified type, resolved dynamically.
    * 
    * @param    o
    *           The object array instance.
    * @param    legacyClsName
    *           The legacy class name.  This may be qualified or unqualified.
    *           
    * @return   A new {@link object} array instance, if the cast is possible.  
    */
   public static object<? extends _BaseObject_>[] dynamicCast(object<?>[] o, String legacyClsName)
   {
      Class<? extends _BaseObject_> cls = resolveClass(legacyClsName);
      
      if (cls == null)
      {
         ErrorManager.recordOrThrowError(12900, 
                                        "CAST to '" + legacyClsName + "' not allowed. " + 
                                        "CAST target must be a user-defined type",
                                        false,
                                        false);
      }
      
      object<_BaseObject_>[] objArr = TypeFactory.objectExtent(o.length, _BaseObject_.class, false);

      int numErr = 0;
      int[] errNums = new int[o.length];
      String[] errMsgs = new String[o.length];

      for (int i = 0; i < objArr.length; i++)
      {
         final int idx = i;

         if (ErrorManager
                  .silent(() -> objArr[idx].assign(cast(o[idx], cls))))
         {
            errNums[numErr] = ErrorManager.getErrorNumber(1).intValue();
            errMsgs[numErr] = ErrorManager.getErrorText(1).getValue();
            numErr++;
         }
      }

      if (numErr > 0)
         ErrorManager.recordOrThrowError(Arrays.copyOf(errNums, numErr), Arrays.copyOf(errMsgs, numErr), false, false);
      
      return objArr;
   }
   
   /**
    * Cast this object array to the specified type, resolved dynamically.
    * 
    * @param    o
    *           The object array instance.
    * @param    legacyClsName
    *           The legacy class name.  This may be qualified or unqualified.
    *           
    * @return   A new {@link object} array instance, if the cast is possible.  
    */
   public static object<? extends _BaseObject_>[] dynamicCast(BaseDataType[] o, character legacyClsName)
   {
      if (legacyClsName.isUnknown() || legacyClsName.toStringMessage().isEmpty())
      {
         ErrorManager.recordOrThrowError(12900, 
                                        "CAST to 'UNKNOWN' not allowed. " + 
                                        "CAST target must be a user-defined type", 
                                        false,
                                        false);
      }

      return dynamicCast(o, legacyClsName.toStringMessage());
   }
   
   /**
    * Cast this object array to the specified type, resolved dynamically.
    * 
    * @param    o
    *           The object array instance.
    * @param    legacyClsName
    *           The legacy class name.  This may be qualified or unqualified.
    *           
    * @return   A new {@link object} array instance, if the cast is possible.  
    */
   public static object<? extends _BaseObject_>[] dynamicCast(BaseDataType[] o, String legacyClsName)
   {
      Class<? extends _BaseObject_> cls = resolveClass(legacyClsName);
      
      if (cls == null)
      {
         ErrorManager.recordOrThrowError(12900, 
                                        "CAST to '" + legacyClsName + "' not allowed. " + 
                                        "CAST target must be a user-defined type",
                                        false,
                                        false);
      }
      
      object<_BaseObject_>[] objArr = TypeFactory.objectExtent(o.length, _BaseObject_.class, false);

      int numErr = 0;
      int[] errNums = new int[o.length];
      String[] errMsgs = new String[o.length];

      for (int i = 0; i < objArr.length; i++)
      {
         final int idx = i;

         if (ErrorManager
                  .silent(() -> objArr[idx].assign(cast(o[idx], cls))))
         {
            errNums[numErr] = ErrorManager.getErrorNumber(1).intValue();
            errMsgs[numErr] = ErrorManager.getErrorText(1).getValue();
            numErr++;
         }
      }

      if (numErr > 0)
         ErrorManager.recordOrThrowError(Arrays.copyOf(errNums, numErr), Arrays.copyOf(errMsgs, numErr), false, false);
      
      return objArr;
   }
   
   /**
    * Get the THIS-OBJECT system reference.
    * 
    * @return   See above.
    */
   public static object<? extends _BaseObject_> thisObject()
   {
      object<? extends _BaseObject_> o = new object<>();
      
      WorkArea wa = local.get();
      Object ref = wa.pm._thisProcedure();
      
      if (ref instanceof _BaseObject_)
      {
         o.set((_BaseObject_) ref);
      }
      else if (ref.getClass() == Object.class)
      {
         Class<? extends _BaseObject_> cls = wa.referents2static.get(ref);
         o.set(getClassInstance(cls));
      }
      
      return o;
   }

   /**
    * Check if this object is valid.
    * 
    * @param    ref
    *           The legacy object.
    *           
    * @return   <code>true</code> if this object is valid.
    */
   public static boolean isValid(_BaseObject_ ref)
   {
      return ref != null && (!ref.isTracked() || ref.__isValidInternal__());
   }

   /**
    * Check if the given instance is serializable.
    * 
    * @param    ref
    *           The instance to check.
    *           
    * @return   See above.
    */
   public static boolean isSerializable(_BaseObject_ ref)
   {
      // TODO: check the annotation at the type, and not 'instanceof Serializable'
      // TODO: see RemoteInvocationResult.setError, the 'send' flag needs to be adjusted once this is complete.
      return false;
   }
   
   /**
    * Convert this object to its associated pseudo-resource.
    * 
    * @param    ref
    *           The legacy object.
    *           
    * @return   The pseudo-resource.
    */
   public static ObjectResource asResource(_BaseObject_ ref)
   {
      return asResource(local.get(), ref);
   }
   
   /**
    * Convert this object to its associated pseudo-resource.
    * 
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    ref
    *           The legacy object.
    *           
    * @return   The pseudo-resource.
    */
   public static ObjectResource asResource(WorkArea wa, _BaseObject_ ref)
   {
      if (ref instanceof LegacyEnum || !ref.isTracked())
      {
         return new ObjectResource(ref);
      }
      else
      {
         return wa.objects.get(ref);
      }
   }

   /**
    * Load the given legacy class.  This will initialize it by recursively execute the static
    * c'tor for all super-classes (if defined).
    * 
    * @param    cls
    *           The class to initialize.
    */
   public static void load(Class<? extends _BaseObject_> cls)
   {
      loadClass(local.get(), cls);
   }

   /**
    * Increment the reference counter for this instance.
    * 
    * @param    ref
    *           The legacy instance.
    */
   public static void increment(_BaseObject_ ref)
   {
      increment(local.get(), ref);
   }
   
   /**
    * Increment the reference counter for this object instance.
    * 
    * @param    obj
    *           The legacy object instance.
    */
   public static void increment(object<? extends _BaseObject_> obj)
   {
      if (obj != null && obj._isValid())
         increment(obj.ref);
   }
   
   /**
    * Decrement the reference counter for this instance.
    * 
    * @param    ref
    *           The legacy instance.
    */
   public static void decrement(_BaseObject_ ref)
   {
      decrement(local.get(), ref);
   }
   
   /**
    * Decrement the reference counter for this object instance.
    * 
    * @param    obj
    *           The legacy object instance.
    */
   public static void decrement(object<? extends _BaseObject_> obj)
   {
      if (obj != null && obj._isValid())
         decrement(obj.ref);
   }
   
   /**
    * Decrement the reference counter for this instance.
    * 
    * @param    wa
    *           The context-local state.
    * @param    ref
    *           The legacy instance.
    */
   public static void decrement(WorkArea wa, _BaseObject_ ref)
   {
      // non-tracked instances cannot be reference counted, this is a NOP
      if (ref != null && !ref.isTracked())
      {
         return;
      }
      
      if (isValid(ref))
      {
         wa.objects.get(ref).decrement();
      }
      else
      {
         // TODO: throw exception?
      }
   }

   /**
    * Increment the reference counter for this instance.
    * 
    * @param    wa
    *           The context-local state.
    * @param    ref
    *           The legacy instance.
    */
   static void increment(WorkArea wa, _BaseObject_ ref)
   {
      // non-tracked instances cannot be reference counted, this is a NOP
      if (ref != null && !ref.isTracked())
      {
         return;
      }
      
      if (isValid(ref))
      {
         wa.objects.get(ref).increment();
      }
      else
      {
         // TODO: throw exception?
      }
   }
   
   /**
    * Register this instance as a local resource.  This is used when the instance was created from a remote
    * side, and send via serialization.
    * 
    * @param    ref
    *           The instance to register.
    */
   static void registerObject(_BaseObject_ ref)
   {
      WorkArea wa = local.get();
      wa.objects.put(ref, new ObjectResource(ref));
   }
   
   /**
    * When an INPUT object variable is initialized in the top-level block via {@link TypeFactory#object},
    * it needs to be registered as 'pending'.  This will defer its registration with the 
    * {@link WorkArea#definitions} until the top-level block starts.
    * 
    * @param    objects
    *           The list of {@link object} definitions.
    */
   static void registerPending(Object... objects)
   {
      WorkArea wa = local.get();
      
      for (Object obj : objects)
      {
         wa.pendingObjects.add(obj);
      }
      
      wa.tm.registerPendingScopeable(wa);
   }

   /**
    * Mark this instance as a pending assignment.
    * 
    * @param    ref
    *           The legacy instance.
    */
   static void pendingAssign(_BaseObject_ ref)
   {
      pendingAssign(local.get(), ref, true);
   }
   
   /**
    * Mark this instance as a pending assignment.
    * 
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    ref
    *           The legacy instance.
    * @param    register
    *           Flag indicating if the scopeable support must be registered.
    */
   static void pendingAssign(WorkArea wa, _BaseObject_ ref, boolean register)
   {
      if (!isValid(ref))
      {
         return;
      }
      
      // pending initialization or the instance was already assigned
      if (!ref.isTracked() || isInitializing(wa, ref) || asResource(wa, ref).getReferences() > 0)
         return;
      
      if (register)
      {
         wa.tm.registerBlockScopeable(wa);
      }
      
      Set<_BaseObject_> pending = wa.pendingAssigned.peek();
      if (pending == Collections.EMPTY_SET)
      {
         wa.pendingAssigned.pop();
         wa.pendingAssigned.push(pending = Collections.newSetFromMap(new IdentityHashMap<>()));
      }
      pending.add(ref);
   }
   
   /**
    * Mark this instance as a pending initialization.
    * 
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    ref
    *           The legacy instance.
    */
   private static void pendingInitialize(WorkArea wa, _BaseObject_ ref)
   {
      List<Object> savePending = wa.pendingObjects.isEmpty() ? null : new ArrayList<>(wa.pendingObjects);
      try
      {
         // clear and restore the pending objects, as 'registerBlockScopeable' may register current block for
         // scope notifications, but the 'pending' objects are not ours.
         wa.pendingObjects.clear();
         wa.tm.registerBlockScopeable(wa);
         
         Set<_BaseObject_> pending = wa.pendingInitialized.peek();
         if (pending == Collections.EMPTY_SET)
         {
            wa.pendingInitialized.pop();
            wa.pendingInitialized.push(pending = Collections.newSetFromMap(new IdentityHashMap<>()));
         }
         pending.add(ref);
      }
      finally
      {
         wa.pendingObjects.clear();
         if (savePending != null)
         {
            wa.pendingObjects.addAll(savePending);
         }
      }
   }
   
   /**
    * Remove this instance from the pending initializations, as it was initialized.
    * 
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    ref
    *           The legacy instance.
    */
   static void endInitialization(WorkArea wa, _BaseObject_ ref)
   {
      for (Set<_BaseObject_> pending : wa.pendingInitialized)
      {
         if (pending.remove(ref))
         {
            break;
         }
      }
   }
   
   /**
    * Return true if this instance is pending initialization.
    * 
    * @param    wa
    *           The {@link WorkArea} instance.
    * @param    ref
    *           The legacy instance.
    */
   static boolean isInitializing(WorkArea wa, _BaseObject_ ref)
   {
      if (wa == null)
      {
         wa = local.get();
      }
      
      for (Set<_BaseObject_> pending : wa.pendingInitialized)
      {
         if (pending.contains(ref))
         {
            return true;
         }
      }
      
      return false;
   }
   
   /**
    * Check if this instance is a legacy object or a pseudo-instance associated with a static
    * class.
    * 
    * @param    ref
    *           The legacy object.
    * 
    * @return   See above.
    */
   static boolean isLegacyObject(Object ref)
   {
      return ref != null && (ref instanceof _BaseObject_ || ref.getClass() == Object.class);
   }
   
   /**
    * Deletes the given object.
    *
    * @param   ref
    *          The object to be deleted.
    * 
    * @return  <code>true</code> if the delete was possible (the instance is not on stack).
    */
   static boolean delete(_BaseObject_ ref)
   {
      return delete(local.get(), ref);
   }

   /**
    * Deletes the given object.
    *
    * @param   wa
    *          The context-local state.
    * @param   ref
    *          The object to be deleted.
    * 
    * @return  <code>true</code> if the delete was possible (the instance is not on stack).
    */
   static boolean delete(WorkArea wa, _BaseObject_ ref)
   {
      if (ref instanceof LegacyEnum)
         return false;
      
      for (Set<_BaseObject_> pending : wa.pendingAssigned)
      {
         if (pending.contains(ref))
         {
            // this reference may be assigned - let it survive and will be cleaned up on the next
            // block exit
            return false;
         }
      }
      
      ObjectResource res = wa.objects.get(ref);
      if (res.isPendingDelete())
      {
         return false;
      }

      // allow the destructors to be called before actually removing the object
      res.delete();
      if (!res.valid())
      {
         wa.objects.remove(ref);
         ref.__setValidInternal__(false);
      }
      
      return !res.valid();
    }
   
   /**
    * Checks whether an object has any destructors pushed on stack ready to be executed when the
    * lifetime of the object ends.
    *
    * @param   ref
    *          The reference to the object to be tested.
    * 
    * @return  {@code true} if when the object is deleted at least one destructor will be
    *          executed. 
    */
   public static boolean hasDestructors(_BaseObject_ ref)
   {
      // non-tracked instances cannot have destructors
      if (ref != null && !ref.isTracked())
      {
         return false;
      }
      
      return isValid(ref) && local.get().objects.get(ref).hasActiveDestructors();
   }
   
   /**
    * When resizing an legacy object array with dynamic extent,  we must re-register the array 
    * reference.
    * 
    * @param    oldArray
    *           The old array.
    * @param    newArray
    *           The new array.
    */
   static void reRegister(BaseDataType[] oldArray, BaseDataType[] newArray)
   {
      WorkArea work = local.get();
      
      // go through all scopes
      for (Set<Object> scopeArrays : work.definitions)
      {
         if (scopeArrays.remove(oldArray))
         {
             // oldArray exists in the scope, replace with newArray
             scopeArrays.add(newArray);
             return;
         }
      }

      for (Set<Object> scopeArrays : work.extProgDefinitions.values())
      {
         if (scopeArrays.remove(oldArray))
         {
             // oldArray exists in the scope, replace with newArray
             scopeArrays.add(newArray);
             return;
         }
      }
   }

   /**
    * Get the {@link LegacyClass} mapped in FWD by the specified converted class.
    * 
    * @param    cls
    *           The converted class.
    *           
    * @return   A {@link LegacyClass} instance.
    */
   public static object<? extends LegacyClass> getLegacyClass(Class<? extends _BaseObject_> cls)
   {
      return new object<>(getClassInstance(cls));
   }

   /**
    * Get the {@link LegacyClass} mapped in FWD by the specified converted class name.
    * 
    * @param    typeName
    *           The converted class name.
    *           
    * @return   A {@link LegacyClass} instance.
    */
   public static object<? extends LegacyClass> getLegacyClass(String typeName)
   {
      Class<? extends _BaseObject_> cls = ObjectOps.resolveClass(typeName);
      
      if (cls == null)
      {
         ErrorManager.recordOrThrowError(15287, 
                                         "Could not dynamically find class'" + typeName + "'", 
                                         false, false);
         return new object<>();
      }
      
      registerClassWorker(typeName, cls);
      
      return getLegacyClass(cls);
   }

   /**
    * Get the {@link LegacyClass} mapped in FWD by the specified converted class name.
    * 
    * @param    cls
    *           The converted class name.
    *           
    * @return   A {@link LegacyClass} instance.
    */
   public static object<? extends LegacyClass> getLegacyClass(character cls)
   {
      return getLegacyClass(cls.isUnknown() ? "" : cls.getValue());
   }
   
   /**
    * Resolve the legacy name for this class.
    * 
    * @param    cls
    *           The class.
    *           
    * @return   The legacy name.
    */
   public static String getLegacyName(Class<? extends _BaseObject_> cls)
   {
      return legacyNameWorker(cls);
   }
   
   /**
    * Given a legacy class, get the pseudo-instance associated with the static members.
    * 
    * @param    cls
    *           The legacy class.
    * 
    * @return   The pseudo-instance for this class.
    */
   public static Object getStaticInstance(Class<? extends _BaseObject_> cls)
   {
      // enums are not tracked and have no static legacy state
      if (LegacyEnum.class.isAssignableFrom(cls))
      {
         // TODO: is there a better return value, this seems unsafe
         return null;
      }
      
      return local.get().staticReferents.get(cls);
   }
   
   /**
    * Given a static class pseudo-instance, get its associated legacy class.
    * 
    * @param    instance
    *           The pseudo-instance for a static class.
    * 
    * @return   The resolved legacy class.
    */
   public static Class<? extends _BaseObject_> getStaticClass(Object instance)
   {
      // enums are not tracked and have no static legacy state
      if (instance != null && LegacyEnum.class.isAssignableFrom(instance.getClass()))
      {
         // TODO: is there a better return value, this seems unsafe
         return null;
      }
      
      return local.get().referents2static.get(instance);
   }
   
   /**
    * Emulate an implicit static constructor.
    * 
    * @param    cls
    *           The legacy class for which the constructor will be executed.
    */
   static void defaultStaticConstructor(Class<?> cls)
   {
      // for enums, this is a NOP
      if (LegacyEnum.class.isAssignableFrom(cls))
      {
         return;
      }
      
      BlockManager.externalProcedure(cls, new Block());
   }

   /**
    * Load the given legacy class.  This will initialize it by recursively execute the static
    * c'tor for all super-classes (if defined).
    * 
    * @param    wa
    *           The context-local state.
    * @param    cls
    *           The class to initialize.
    *           
    * @return   <code>true</code> if the initialization completed.
    */
   static boolean loadClass(WorkArea wa, Class<? extends _BaseObject_> cls)
   {
      // enums are not tracked, legacy lifecycle processing is bypassed
      if (LegacyEnum.class.isAssignableFrom(cls))
      {
         return true;
      }
      
      // before trying to load this class, we need to backup (and restore) any state specified
      // for the invocation at the variables.  This includes the output parameter assigner
      OutputParameterAssigner opa = wa.tm.deregisterOutputParameterAssigner();
      try
      {
         return loadClassInt(wa, cls);
      }
      finally
      {
         if (opa != null)
         {
            wa.tm.registerOutputParameterAssigner(opa);
         }
      }
   }
   
   /**
    * Perform recursive loading of all classes, up the hierarchy.
    * 
    * @param    wa
    *           The context-local state.
    * @param    cls
    *           The class to initialize.
    *           
    * @return   <code>true</code> if the initialization completed.
    */
   private static boolean loadClassInt(WorkArea wa, Class<? extends _BaseObject_> cls)
   {
      // enums are not tracked, legacy lifecycle processing is bypassed (we especially need to
      // avoid access to staticReferents and referents2static
      if (LegacyEnum.class.isAssignableFrom(cls))
      {
         return true;
      }
      
      // TODO: if first static c'tor fails, and second loading attempt doesn't, does 4GL allow
      // the class to load?
      
      if (wa.staticReferents.containsKey(cls) || wa.pendingLoadedClasses.contains(cls))
      {
         //  prevent recursive loading, if the static c'tor accesses static members
         return true;
      }
      
      wa.pendingLoadedClasses.add(cls);
      try
      {
         boolean parentLoaded = true;
         if (cls != BaseObject.class)
         {
            parentLoaded = loadClassInt(wa, (Class<? extends _BaseObject_>) cls.getSuperclass());
         }
         
         if (!parentLoaded)
         {
            return false;
         }
   
         Object referent = new Object();
         wa.staticReferents.put(cls, referent);
         wa.referents2static.put(referent, cls);
         String name = getLegacyName(cls);

         // first load of a class causes its language to be applied, for good
         TranslationManager.getInstance().applyLanguage(cls);

         // execute the static c'tor
         if (!ControlFlowOps.initializeLegacyClass(cls, referent, name))
         {
            new ExternalProgramWrapper(referent).delete();
            wa.staticReferents.remove(cls);
            
            ErrorManager.recordOrThrowError(14631, 
                                            "Static instance failed to load. " +
                                            "Cannot reference class " + name,
                                            false, false);
            return false;
         }
         
         return true;
      }
      finally
      {
         wa.pendingLoadedClasses.remove(cls);
      }
   }

   /**
    * Resolve the associated converted class name for this legacy class.
    * 
    * @param    cname
    *           The fully qualified legacy class name.
    *           
    * @return   The found {@link Class} or <code>null</code> if it can't be found.
    */
   public static <T extends _BaseObject_> Class<T> resolveClass(String cname)
   {
      if (cname == null)
      {
         return null;
      }
      
      Class<T> type = (Class<T>) lookupClassWorker(cname);
      
      if (type != null)
      {
         return type;
      }
      
      String clsName = SourceNameMapper.convertOOToClass(cname);
      
      if (clsName == null)
      {
         return null;
      }
      
      try 
      {
         return (Class<T>) Class.forName(clsName);
      }
      catch (ClassNotFoundException ex)
      {
         return null;
      }
   }
   
   /**
    * Create a mapping between a given legacy class name and the converted Java class instance. 
    *  
    * @param    cname
    *           The fully qualified legacy class name.
    * @param    cls
    *           The converted type.
    */
   public static void registerClass(String cname, Class<? extends _BaseObject_> cls)
   {
      registerClassWorker(cname, cls);
   }
   
   /**
    * Create a mapping between a given legacy class name and the converted Java class instance. In this
    * the legacy class name will be looked up.
    * 
    * Complete class hierarchy is registered or not depending on the hierarchy flag.
    *  
    * @param    cls
    *           The converted type.
    * @param    hierarchy
    *           Flag to load the complete class hierarchy
    */
   public static void registerClass(Class<? extends _BaseObject_> cls, boolean hierarchy)
   {
      String clsName = legacyNameWorker(cls);

      if (clsName != null)
      {
         if (hierarchy)
         {
            if (cls.getSuperclass() != null && BaseObject.class.isAssignableFrom(cls.getSuperclass()))
            {
               registerClass((Class<? extends _BaseObject_>) cls.getSuperclass(), hierarchy);
            }

            for (Class i : cls.getInterfaces())
            {
               if (_BaseObject_.class.isAssignableFrom(i))
               {
                  registerClass(i, hierarchy);
               }
            }
         }
      }
   }
   
   /**
    * Resolve the {@link LegacyClass} of super class or interface of the class, register it as needed.
    * 
    * @param    superName
    *           name of assignable super class or interface
    * @param    cls
    *           The converted type.
    */
   public static Class<? extends _BaseObject_> getAssignableClass(String superName,
            Class<? extends _BaseObject_> cls)
   {
      String clsName = getLegacyName(cls);
      Class<? extends _BaseObject_> superCls = null;

      if (clsName != null && clsName.equalsIgnoreCase(superName))
      {
         registerClassWorker(superName, cls);
         return cls;
      }

      if (cls.getSuperclass() != null && BaseObject.class.isAssignableFrom(cls.getSuperclass()))
      {
         superCls = getAssignableClass(superName,
                  (Class<? extends _BaseObject_>) cls.getSuperclass());
      }

      if (superCls == null)
      {
         for (Class i : cls.getInterfaces())
         {
            if (_BaseObject_.class.isAssignableFrom(i))
            {
               superCls = getAssignableClass(superName, i);

               if (superCls != null)
                  break;
            }
         }

      }

      return superCls;

   }
   
   /**
    * Get the {@link LegacyClass} instance for the given type.
    * 
    * @param    cls
    *           The converted type.
    *           
    * @return   See above.
    */
   public static LegacyClass getClassInstance(Class<? extends _BaseObject_> cls)
   {
      WorkArea wa = local.get();
      LegacyClass lc = wa.legacyClasses.get(cls);
      
      if (lc == null)
      {
         executeStaticConstructor(cls);

         LegacyResource lr = cls.getAnnotation(LegacyResource.class);
         String clsName = (lr != null ? lr.resource() : SourceNameMapper.getLegacyClassName(cls.getName()));
         lc = new LegacyClass(clsName, cls);
   
         registerClassWorker(clsName, cls);
         loadClass(wa, LegacyClass.class);
         newInstanceInternal(wa, lc, LegacyClass.class, "Progress.Lang.Class", null);
         
         wa.legacyClasses.put(cls, lc);
      }
      
      return lc;
   }
   
   /**
    * Force executing the static constructor now.  If this is a legacy enum class, and all access is done
    * via reflection, then the 'enum' static fields do not get initialized via reflection access.
    * 
    * @param    cls
    *           The class to check.
    */
   private static void executeStaticConstructor(Class<? extends _BaseObject_> cls)
   {
      if (LegacyEnum.class.isAssignableFrom(cls))
      {
         // force loading of the static initializer
         ErrorManager.nestedSilent(() ->
         {
            try
            {
               Method m = cls.getDeclaredMethod("getEnum", int64.class);
               // just force execution of the method, we don't want to retreive any enum
               m.invoke(null, new int64()); 
            }
            catch (Throwable t)
            {
               // ignore, this also catches the ErrorConditionException from the method call
            }
         });
      }
   }
   
   /**
    * Return the converted Java class instance for the given legacy class name. 
    *  
    * @param    cname
    *           The fully qualified legacy class name.
    *
    * @return   The converted Java class.
    */
   private static Class<? extends _BaseObject_> lookupClassWorker(String cname)
   {
      Class<? extends _BaseObject_> cls = null;
      
      synchronized (lock)
      {
         cls = name2cls.get(cname.toLowerCase());
      }
      
      return cls;
   }
   
   /**
    * Create a mapping between a given legacy class name and the converted Java class instance. 
    *  
    * @param    cname
    *           The fully qualified legacy class name.
    * @param    cls
    *           The converted type.
    */
   private static void registerClassWorker(String cname, Class<? extends _BaseObject_> cls)
   {
      if (cname == null || cls == null)
      {
         // something is wrong, this cannot work
         return;
      }
      
      synchronized (lock)
      {
         if (legacyNames.putIfAbsent(cls, cname) == null)
         {
            name2cls.putIfAbsent(cname.toLowerCase(), cls);
         }
      }
   }
   
   /**
    * Resolve the legacy name for this class.
    * 
    * @param    cls
    *           The class.
    *           
    * @return   The legacy name.
    */
   private static String legacyNameWorker(Class<? extends _BaseObject_> cls)
   {
      if (cls == _BaseObject_.class)
      {
         cls = BaseObject.class;
      }
      
      String name = null;
      
      synchronized (lock)
      {
         name = legacyNames.get(cls);
         
         if (name == null)
         {
            if (cls.isAnnotationPresent(LegacyResource.class))
            {
               name = cls.getAnnotation(LegacyResource.class).resource();
            }
            else
            {
               name = SourceNameMapper.getLegacyClassName(cls.getName());
            }
            
            registerClassWorker(name, cls);
         }
      }
      
      return name;
   }

   
   /**
    * Invoke the specified static method in the specified class.
    * 
    * @param     voidReturn
    *            Flag indicating that the method can return void.
    * @param     clsName
    *            The legacy class name.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   private static Object invoke(boolean   voidReturn,
                                String    clsName, 
                                String    mthdName, 
                                String    modes, 
                                Object... args)
   {
      if (mthdName == null)
      {
         ErrorManager.recordOrThrowError(15300, 
                                         "Could not evaluate method name parameter for Invoke",
                                         false);
         return new unknown();
      }

      Class<? extends _BaseObject_> cls = resolveClass(clsName);
      if (cls == null)
      {
         ErrorManager.recordOrThrowError(15285, 
                                         "A valid class instance or static class name is " +
                                         "required for dynamically invoking a method or " +
                                         "accessing a property",
                                         false);
         
         return new unknown();
      }

      if (!loadClass(local.get(), cls))
      {
         return new unknown();
      }
      
      return ControlFlowOps.invokeLegacyMethod(voidReturn, 
                                               cls,
                                               clsName,
                                               mthdName,
                                               modes,
                                               args);
   }

   /**
    * Invoke the specified instance method in the specified object.
    * 
    * @param     voidReturn
    *            Flag indicating that the method can return void.
    * @param     ref
    *            The legacy object reference.
    * @param     mthdName
    *            The static method name.
    * @param     modes
    *            The argument modes.
    * @param     args
    *            The arguments.
    *            
    * @return    Any returned value, or unknown if the method is void.
    */
   private static Object invoke(boolean                        voidReturn,
                                object<? extends _BaseObject_> ref, 
                                String                         mthdName, 
                                String                         modes, 
                                Object...                      args)
   {
      if (mthdName == null)
      {
         ErrorManager.recordOrThrowError(15300, 
                                         "Could not evaluate method name parameter for Invoke",
                                         false);
         return new unknown();
      }
      
      if (ref.isUnknown())
      {
         ErrorManager.recordOrThrowError(15285, 
                                         "A valid class instance or static class name is " +
                                         "required for dynamically invoking a method or " +
                                         "accessing a property",
                                         false);

         return new unknown();
      }
      

      // TODO: invoking methods from builtin classes in some cases raises a
      // "Dynamic access to certain built-in methods and properties is not permitted (17008)"
      
      String clsName = getLegacyName(ref.ref.getClass());
      return ControlFlowOps.invokeLegacyMethod(voidReturn, 
                                               ref.ref, 
                                               clsName, 
                                               mthdName,
                                               modes,
                                               args);
   }

   /**
    * Generate the invalid type error for ToObject.
    */
   private static void generateInvalidTypeDE()
   {
      ErrorManager.recordOrThrowError(18007, String.format(DE_SPEC, "enum-type-name"));
   }
   
   /** 
    * Generate the invalid value error for ToObject.
    *
    * @param    cls
    *           The enum for which the value is invalid.
    */                              
   private static void generateInvalidValueDE(Class<?> cls)
   {
      ErrorManager.recordOrThrowError(18007, String.format(DE_SPEC, "member-name-or-value"));
   }
      
   /**
    * Validate the reference, property and index for a DYNAMIC-PROPERTY getter or setter.
    * 
    * @param    ref
    *           The reference to validate against.
    * @param    prop
    *           The property name.
    * @param    idx
    *           The index, non-null only for indexed DYNAMIC-PROPERTY cases.
    * @param    setter
    *           Flag indicating if this is a setter or getter call.
    *           
    * @return   <code>true</code> if the arguments are valid.
    */
   private static boolean validDynamicPropertyArgs(object<? extends _BaseObject_> ref, 
                                                   character                      prop, 
                                                   NumberType                     idx,
                                                   boolean                        setter)
   {
      if (!ref._isValid())
      {
         ErrorManager.recordOrThrowError(new int[] { 16580 }, 
                                         new String[] { "Invalid object reference passed to DYNAMIC-PROPERTY" }, 
                                         false, false, false);
         return false;
      }
      
      Class<? extends _BaseObject_> cls = ref.ref.getClass();
      
      return validDynamicPropertyArgs(cls, prop.toStringMessage(), idx, setter);
   }

   /**
    * Validate the reference, property and index for a DYNAMIC-PROPERTY getter or setter.
    *
    * @param    ref
    *           The reference to validate against.
    * @param    prop
    *           The property name.
    * @param    idx
    *           The index, non-null only for indexed DYNAMIC-PROPERTY cases.
    * @param    setter
    *           Flag indicating if this is a setter or getter call.
    *
    * @return   <code>true</code> if the arguments are valid.
    */
   private static boolean validDynamicPropertyArgs(object<? extends _BaseObject_> ref,
                                                   String                         prop,
                                                   NumberType                     idx,
                                                   boolean                        setter)
   {
      if (!ref._isValid())
      {
         ErrorManager.recordOrThrowError(new int[] { 16580 },
                  new String[] { "Invalid object reference passed to DYNAMIC-PROPERTY" },
                  false, false, false);
         return false;
      }

      Class<? extends _BaseObject_> cls = ref.ref.getClass();

      return validDynamicPropertyArgs(cls, prop, idx, setter);
   }
   
   /**
    * Validate the class name, property and index for a DYNAMIC-PROPERTY getter or setter.
    * 
    * @param    className
    *           The class where the property belongs.
    * @param    prop
    *           The property name.
    * @param    idx
    *           The index, non-null only for indexed DYNAMIC-PROPERTY cases.
    * @param    setter
    *           Flag indicating if this is a setter or getter call.
    *           
    * @return   <code>true</code> if the arguments are valid.
    */
   private static boolean validDynamicPropertyArgs(String     className, 
                                                   character  prop, 
                                                   NumberType idx, 
                                                   boolean    setter)
   {
      Class<? extends _BaseObject_> cls = ObjectOps.resolveClass(className);
      
      if (cls == null)
      {
         ErrorManager.recordOrThrowError(15287, 
                                         "Could not dynamically find class'" + className + "'", 
                                         false, false);
         return false;
      }
      
      return validDynamicPropertyArgs(cls, prop.toStringMessage(), idx, setter);
   }

   /**
    * Validate the class name, property and index for a DYNAMIC-PROPERTY getter or setter.
    *
    * @param    className
    *           The class where the property belongs.
    * @param    prop
    *           The property name.
    * @param    idx
    *           The index, non-null only for indexed DYNAMIC-PROPERTY cases.
    * @param    setter
    *           Flag indicating if this is a setter or getter call.
    *
    * @return   <code>true</code> if the arguments are valid.
    */
   private static boolean validDynamicPropertyArgs(String     className,
                                                   String     prop,
                                                   NumberType idx,
                                                   boolean    setter)
   {
      Class<? extends _BaseObject_> cls = ObjectOps.resolveClass(className);

      if (cls == null)
      {
         ErrorManager.recordOrThrowError(15287,
                  "Could not dynamically find class'" + className + "'",
                  false, false);
         return false;
      }

      return validDynamicPropertyArgs(cls, prop, idx, setter);
   }

   /**
    * Validate the property and index for a DYNAMIC-PROPERTY getter or setter.
    * 
    * @param    cls
    *           The class where the property belongs.
    * @param    prop
    *           The property name.
    * @param    idx
    *           The index, non-null only for indexed DYNAMIC-PROPERTY cases.
    * @param    setter
    *           Flag indicating if this is a setter or getter call.
    *           
    * @return   <code>true</code> if the arguments are valid.
    */
   private static boolean validDynamicPropertyArgs(Class<? extends _BaseObject_>  cls, 
                                                   character                      prop,
                                                   NumberType                     idx,
                                                   boolean                        setter)
   {
      return validDynamicPropertyArgs(cls, prop.toStringMessage(), idx, setter);
   }

   /**
    * Validate the property and index for a DYNAMIC-PROPERTY getter or setter.
    *
    * @param    cls
    *           The class where the property belongs.
    * @param    prop
    *           The property name.
    * @param    idx
    *           The index, non-null only for indexed DYNAMIC-PROPERTY cases.
    * @param    setter
    *           Flag indicating if this is a setter or getter call.
    *
    * @return   <code>true</code> if the arguments are valid.
    */
   private static boolean validDynamicPropertyArgs(Class<? extends _BaseObject_>  cls,
                                                   String                         prop,
                                                   NumberType                     idx,
                                                   boolean                        setter)
   {
      String msg;

      if (prop.isEmpty())
      {
         msg = "Invalid property name passed to DYNAMIC-PROPERTY";
         ErrorManager.recordOrThrowError(new int[] { 16581 }, new String[] { msg }, false, false, false);
         return false;
      }

      String propName = prop;

      InternalEntry agetter = SourceNameMapper.findLegacyAccessor(cls, propName, true, false, idx != null);
      InternalEntry asetter = SourceNameMapper.findLegacyAccessor(cls, propName, false, false, idx != null);
      if (asetter == null && agetter == null)
      {
         InternalEntry agetterbulk = idx == null 
                                       ? null 
                                       : SourceNameMapper.findLegacyAccessor(cls, propName, true, true, false);
         InternalEntry asetterbulk = idx == null 
                                       ? null 
                                       : SourceNameMapper.findLegacyAccessor(cls, propName, false, true, false);
         if (agetterbulk != null || asetterbulk != null)
         {
            if (setter)
            {
               asetter = asetterbulk;
               idx = null;
            }
            else
            {
               msg = "Invalid runtime use of indeterminate extent.  It must be set to a fixed dimension";
               ErrorManager.recordOrThrowError(11387, msg, false);
               return false;
            }
         }
         else
         {
            msg = "Could not find property '" + propName + "' in class '" + getLegacyName(cls) + "'";
            ErrorManager.recordOrThrowError(16548, msg, false);
            return false;
         }
      }
      
      if (!setter && agetter == null)
      {
         msg = "Property '" + propName + "' in Class '" + 
               getLegacyName((Class<? extends _BaseObject_>) asetter.getMethod().getDeclaringClass()) + 
               "' does not allow read access";
         ErrorManager.recordOrThrowError(13822, msg, false);
         return false;
      }
      
      if (setter && asetter == null)
      {
         msg = "Property '" + propName + "' in Class '" + 
                getLegacyName((Class<? extends _BaseObject_>) agetter.getMethod().getDeclaringClass()) +  
                "' does not allow write access";
         ErrorManager.recordOrThrowError(13851, msg, false);
         return false;
      }
      
      // check modifier
      Method mthd = setter ? asetter.getMethod() : agetter.getMethod();
      int mods = mthd.getModifiers();
      if (!Modifier.isPublic(mods))
      {
         WorkArea wa = local.get();
         Class thisProcClass = wa.pm._thisProcedure().getClass();
         
         if (Modifier.isPrivate(mods))
         {
            // same class
            if (thisProcClass != mthd.getDeclaringClass())
            {
               if (setter)
               {
                  msg = "Property " + propName + " has a PRIVATE SET accessor so " + 
                        "can be set only from within the class where it's defined";
                  ErrorManager.recordOrThrowError(13835, msg, false);
               }
               else
               {
                  msg = "Property " + propName + " has a PRIVATE GET accessor so " + 
                        "can be read only from within the class where it's defined";
                  ErrorManager.recordOrThrowError(13833, msg, false);
               }
               return false;
            }
         }
         else if (Modifier.isProtected(mods))
         {
            // same class or the accessor is from a super-class
            if (!mthd.getDeclaringClass().isAssignableFrom(thisProcClass))
            {
               if (setter)
               {
                  msg = "Property " + propName + " has a PROTECTED SET accessor so can be set only from " + 
                        "within the class hierarchy where it's defined";
                  ErrorManager.recordOrThrowError(13836, msg, false);
               }
               else
               {
                  msg = "Property " + propName + " has a PROTECTED GET accessor so can be read only from " +
                        "within the class hierarchy where it's defined";
                  ErrorManager.recordOrThrowError(13834, msg, false);
               }
               return false;
            }
            
         }
      }
      
      if (idx != null)
      {
         if (idx.isUnknown())
         {
            if (setter)
            {
               msg = "Array subscript 0 is out of range";
               ErrorManager.recordOrThrowError(26, msg);
            }
            else
            {
               msg = "Invalid array index passed to DYNAMIC-PROPERTY";
               ErrorManager.recordOrThrowError(new int[] { 16582 }, new String[] { msg }, false, false, false);
            }
            return false;
         }
         
         Integer extent = SourceNameMapper.findLegacyPropertyExtent(cls, propName);
         if (extent == null)
         {
            if (setter)
            {
               msg = "** Array subscript " + idx.intValue() + " is out of range";
               ErrorManager.recordOrThrowError(26, msg, false);
            }
            else
            {
               msg = "Invalid runtime use of indeterminate extent.  It must be set to a fixed dimension";
               ErrorManager.recordOrThrowError(11387, msg, false);
            }
            return false;
         }
         else if (idx.intValue() > extent || idx.intValue() < 1)
         {
            if (setter)
            {
               msg = "** Array subscript " + idx.intValue() + " is out of range";
               ErrorManager.recordOrThrowError(26, msg, false);
            }
            else
            {
               msg = "Array subscript " + idx.intValue() + " is out of range.  " + 
                     "The indeterminate extent is fixed to a dimension of " + extent;
               ErrorManager.recordOrThrowError(11388, msg, false);
            }
            return false;
         }
      }
      
      // TODO: yes Built-in class Progress.Lang.Object, property Next-Sibling is not settable. (16939)
      
      return true;
   }
   
   /**
    * Helper to expose APIs which have direct access to the context-local state.
    */
   public static class ObjectHelper
   {
      /** 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 ObjectHelper(WorkArea wa)
      {
         this.wa = wa;
      }
      
      /**
       * Register the {@link WorkArea} scopeable as pending, to be added to the next block.
       */
      public void registerPendingScopeable()
      {
         wa.tm.registerPendingScopeable(wa);
      }
      
      /**
       * Get the state of the {@link WorkArea#inScopeFinished} flag.
       * 
       * @return   <code>true</code> if we are in {@link WorkArea#scopeDeleted()} or 
       *           {@link WorkArea#scopeFinished()}.
       */
      public boolean inScopeFinished()
      {
         return wa.inScopeFinished;
      }
      
      /**
       * Set the state of the {@link WorkArea#inScopeFinished} flag.
       * 
       * @param    state
       *           The flag's value.
       */ 
      public void setInScopeFinished(boolean state)
      {
         wa.inScopeFinished = state;
      }
      
      /**
       * Register the {@link WorkArea} scopeable in this current block.
       */
      public void registerScopeable()
      {
         wa.tm.registerTopLevelScopeable(wa);
      }
      
      /**
       * Register this instance as if it was defined in the current block, so the reference can be tracked
       * again - this is useful for 'zero counter' reference returned from a function call, which, if not
       * assigned to some other var, will never get deleted.
       * 
       * @param    obj
       *           The object to register.
       */
      public void registerObject(ObjectVar<?> obj)
      {
         BlockType bt = wa.tm.getNearestTopLevelType();
         
         Set<Object> defs;
         if (bt == BlockType.EXTERNAL_PROC)
         {
            Object referent = wa.pm._thisProcedure();
            defs = wa.extProgDefinitions.get(referent);
            if (defs == null)
            {
               defs = Collections.newSetFromMap(new IdentityHashMap<>());
               wa.extProgDefinitions.put(referent, defs);
            }
         }
         else
         {
            defs = wa.definitions.peek();
            if (defs == Collections.EMPTY_SET)
            {
               defs = Collections.newSetFromMap(new IdentityHashMap<>());
               wa.definitions.pop();
               wa.definitions.push(defs);
            }
         }
         
         defs.add(obj);
      }
      
      /**
       * Mark this instance as a pending assignment.
       * 
       * @param    ref
       *           The legacy instance.
       */
      public void pendingAssign(_BaseObject_ ref)
      {
         ObjectOps.pendingAssign(wa, ref, true);
      }

      /**
       * Load the given legacy class.  This will initialize it by recursively execute the static
       * c'tor for all super-classes (if defined).
       * 
       * @param    cls
       *           The class to initialize.
       */
      public void load(Class<? extends _BaseObject_> cls)
      {
         ObjectOps.loadClass(wa, cls);
      }

      /**
       * Deletes the given object.
       *
       * @param   ref
       *          The object to be deleted.
       * 
       * @return  <code>true</code> if the delete was possible (the instance is not on stack).
       */
      public boolean delete(_BaseObject_ ref)
      {
         return ObjectOps.delete(wa, ref);
      }

      /**
       * Deregister the specified instance.
       * 
       * @param    ref
       *           The instance to deregister.
       */
      public void deregisterObject(_BaseObject_ ref)
      {
         wa.objects.remove(ref);
         ref.__setValidInternal__(false);
      }
      
      /**
       * Decrement the reference counter for this instance.
       * 
       * @param    ref
       *           The legacy instance.
       */
      public void decrement(_BaseObject_ ref)
      {
         ObjectOps.decrement(wa, ref);
      }

      /**
       * Increment the reference counter for this instance.
       * 
       * @param    ref
       *           The legacy instance.
       */
      public void increment(_BaseObject_ ref)
      {
         ObjectOps.increment(wa, ref);
      }

      /**
       * Check if the reference is marked as {@link WorkArea#pendingAssigned}.
       * 
       * @param    ref
       *           The reference to check.
       *           
       * @return   See above.
       */
      public boolean isPendingAssigned(_BaseObject_ ref)
      {
         return !wa.pendingAssigned.isEmpty() && wa.pendingAssigned.peek().contains(ref);
      }

      /**
       * Check if this reference is saved to be returned by a function or method call.
       * 
       * @param    ref
       *           The legacy instance.
       *           
       * @return   <code>true</code> if the instance is pending to be returned by a function or method call.
       */
      public boolean instanceReturned(_BaseObject_ ref)
      {
         return BlockManager.checkFunctionReturn((ret) ->
         {
            if (ret instanceof object)
            {
               object o = (object) ret;
               if (ref == o.ref)
               {
                  return true;
               }
            }
            
            return false;
         });
      }

      /**
       * Remove this instance from the pending assignments, as it was assigned.
       * 
       * @param    ref
       *           The legacy instance.
       */
      public void instanceAssigned(_BaseObject_ ref)
      {
         if (instanceReturned(ref))
         {
            return;
         }
         
         for (Set<_BaseObject_> pending : wa.pendingAssigned)
         {
            if (pending.remove(ref))
            {
               break;
            }
         }
      }

      /**
       * Return true if this instance is pending initialization.
       * 
       * @param    ref
       *           The legacy instance.
       */
      public boolean isInitializing(_BaseObject_ ref)
      {
         return ObjectOps.isInitializing(wa, ref);
      }

      /**
       * Check if this object is valid.
       * 
       * @param    ref
       *           The legacy object.
       *           
       * @return   <code>true</code> if this object is valid.
       */
      public boolean isValid(_BaseObject_ ref)
      {
         return ObjectOps.isValid(ref);
      }
   }
   
   /**
    * Context-local data.
    */
   private static class WorkArea
   implements Scopeable
   {
      /** Helper to use the ProcedureManager without any context local lookups. */
      private final ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();

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

      /** The set of classes pending to be loaded. */
      private Set<Class<? extends _BaseObject_>> pendingLoadedClasses = Collections.newSetFromMap(new IdentityHashMap<>());

      /** Map of legacy objects to their pseudo-resource. */
      private Map<_BaseObject_, ObjectResource> objects = new IdentityHashMap<>();
      
      /** Pending object definitions which need to be registered in the next scope. */
      private Set<Object> pendingObjects = Collections.newSetFromMap(new IdentityHashMap<>()); 

      /** Variable definitions which are top-level block members. */
      private Deque<Set<Object>> definitions = new ArrayDeque<>();
      
      /** Map of variable definitions which are class members. */
      private Map<Object, Set<Object>> extProgDefinitions = new IdentityHashMap<>();
      
      /**
       * Bogus referents associated with a static class.  These are required to be able to track
       * this as a 'persistent procedure' which will never get deleted.
       */
      private Map<Class<? extends _BaseObject_>, Object> staticReferents = new IdentityHashMap<>();
      
      /** Bogus referents associated with a static class. */
      private Map<Object, Class<? extends _BaseObject_>> referents2static = new IdentityHashMap<>();
      
      /**
       * The set of newly created objects which wait to be assigned.  These will be deleted on
       * the next block's scope finish, if they are not yet assigned.
       */
      private Deque<Set<_BaseObject_>> pendingAssigned = new ArrayDeque<>();
      
      /**
       * The set of newly created objects which wait to be initialized.  These will be deleted
       * when the object initialization completes.
       */
      private Deque<Set<_BaseObject_>> pendingInitialized = new ArrayDeque<>();
      
      /** The map of instantiated {@link LegacyClass} objects. */
      private Map<Class<? extends _BaseObject_>, LegacyClass> legacyClasses = new IdentityHashMap<>();
      
      /** Flag indicating if we are processing {@link #scopeFinished()} or {@link #scopeDeleted()}. */
      private boolean inScopeFinished = false;
      
      /**
       * Provides a notification that a new scope is about to be entered.
       * <p>
       * Used to create new collections where to register the variable definitions for that block.
       * 
       * @param    block
       *           The explicit block definition which required this notification.
       */
      @Override
      public void scopeStart(BlockDefinition block)
      {
         BlockType bt = block.type;
         
         if (bt.equals(BlockType.INTERNAL_PROC) ||
             bt.equals(BlockType.FUNCTION)      ||
             bt.equals(BlockType.TRIGGER)       ||
             bt.equals(BlockType.DATABASETRIGGER))
         {
            if (!pendingObjects.isEmpty())
            {
               Set<Object> refs = Collections.newSetFromMap(new IdentityHashMap<>());
               refs.addAll(pendingObjects);
               definitions.push(refs);
            }
            else
            {
               definitions.push(Collections.emptySet());
            }
            
            pendingObjects.clear();
         }
         else if (bt.equals(BlockType.EXTERNAL_PROC))
         {
            if (!pendingObjects.isEmpty())
            {
               Object referent = pm._thisProcedure();

               Set<Object> extProgDefs = extProgDefinitions.get(referent);

               if (extProgDefs == null)
               {
                  extProgDefs = Collections.newSetFromMap(new IdentityHashMap<>());
                  extProgDefinitions.put(referent, extProgDefs);
               }
               
               extProgDefs.addAll(pendingObjects);
               
               pendingObjects.clear();
            }
         }
         
         pendingAssigned.push(Collections.emptySet());
         pendingInitialized.push(Collections.emptySet());
      }

      /**
       * Provides a notification that a scope is about to be exited.
       * <p>
       * All variable definitions defined in this block will have the reference counter decremented
       * for the wrapped object.
       */
      @Override
      public void scopeFinished()
      {
         boolean prevScopeFinished = inScopeFinished;
         
         inScopeFinished = true;
         try
         {
            BlockType bt = tm.getBlockType();
   
            if (bt.equals(BlockType.INTERNAL_PROC)  ||
                 bt.equals(BlockType.FUNCTION)      ||
                 bt.equals(BlockType.TRIGGER)       ||
                 bt.equals(BlockType.DATABASETRIGGER))
            {
               Set<Object> defs = definitions.pop();
               decrement(defs);
            }
            
            if (!pendingInitialized.isEmpty())
               pendingInitialized.pop();
               
            // this is done AFTER any decrement is performed - because in case of functions returning
            // an object, that will be marked as 'pending assigned' and the object will not be
            // deleted (but the reference will be decremented).
            if (!pendingAssigned.isEmpty())
            {
               Set<_BaseObject_> pending = pendingAssigned.peek();
               if (pending != null && !pending.isEmpty())
               {
                  // 4GL deletes an object immediately if it was not assigned - we track them here,
                  // and if when this scope is exit their reference counter is 0, we delete them.
                  Object funcRet = BlockManager.getFunctionReturn();
                  Set<_BaseObject_> funcRetVals = null;
                  if (funcRet != null && 
                      (funcRet instanceof object || 
                       (funcRet.getClass().isArray() && 
                        object.class.isAssignableFrom(funcRet.getClass().getComponentType()))))
                  {
                     funcRetVals = Collections.newSetFromMap(new IdentityHashMap<>());
                     if (funcRet instanceof object)
                     {
                        object<?> obj = (object<?>) funcRet;
                        
                        if (obj._isValid() && obj.isTracked())
                        {
                           funcRetVals.add(obj.ref);
                        }
                     }
                     else
                     {
                        for (int i = 0; i < Array.getLength(funcRet); i++)
                        {
                           object<?> obj = (object<?>) Array.get(funcRet, i);
                           
                           if (obj != null && obj._isValid() && obj.isTracked())
                           {
                              funcRetVals.add(obj.ref);
                           }
                        }
                     }
                  }
                  
                  for (_BaseObject_ obj : pending)
                  {
                     if (!obj.isTracked())
                     {
                        continue;
                     }
                     
                     ObjectResource res = objects.get(obj);
                     
                     if (res.getReferences() == 0)
                     {
                        if (funcRetVals == null || !funcRetVals.contains(obj))
                        {
                           res.deleteSilent();
                        }
                        else
                        {
                           // move this one level down
                           if (pendingAssigned.size() > 1)
                           {
                              pendingAssigned.pop();
                              pendingAssign(this, obj, false);
                              pendingAssigned.push(pending);
                           }
                        }
                     }
                  }
               }
               pendingAssigned.pop();
            }
         }
         finally
         {
            inScopeFinished = prevScopeFinished;
         }
      }

      /**
       * Provides a notification that an external scope is about to be deleted.
       * <p>
       * All variables defined at this external program will have the reference counter decremented.
       */
      @Override
      public void scopeDeleted()
      {
         boolean prevScopeFinished = inScopeFinished;
         inScopeFinished = true;
         
         try
         {
            Object referent = pm.getProcessedProcedure();
            if (referent == null           || 
                pm._isPersistentAndNotPendingDelete(referent) || 
                (referent == pm._thisProcedure() && !tm.isExternalBlock()))
            {
               // if no referent or procedure is still persistent, do not clean up yet
               return;
            }
   
            Set<Object> defs = extProgDefinitions.remove(referent);
            if (defs != null)
            {
               decrement(defs);
            }
         }
         finally
         {
            inScopeFinished = prevScopeFinished;
         }
      }

      /**
       * Get the {@link ScopeId} for the instance.
       * 
       * @return   {@link ScopeId#OBJECT_OPS}.
       */
      @Override
      public ScopeId getScopeId()
      {
         return ScopeId.OBJECT_OPS;
      }
      
      /**
       * Decrement the references for all this objects.
       * 
       * @param    defs
       *           Scalar or array object variables.
       */
      private void decrement(Set<Object> defs)
      {
         for (Object o : defs)
         {
            if (o instanceof object)
            {
               object<? extends _BaseObject_> obj = (object<?>) o;
               if (!obj.isUnknown())
               {
                  obj.decrement();
               }
            }
            else if (o instanceof object[])
            {
               object<? extends _BaseObject_>[] objs = (object<?>[]) o;
               for (object<? extends _BaseObject_> obj : objs)
               {
                  if (!obj.isUnknown())
                  {
                     obj.decrement();
                  }
               }
               
               if (objs.length == 0)
               {
                  // this was never resized, so remove its type
                  ArrayAssigner.removeObjectType((object[]) objs);
               }
            }
            else
            {
               // TODO: error out?
            }
         }
      }
   }
}