ArrayAssigner.java

/*
** Module   : ArrayAssigner.java
** Abstract : automates the assignment of each element of a BDT array
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 GES 20051104   @23229 Provides a simple helper to automate the assignment of each element
**                           of a BaseDataType array based on an array input.
** 002 GES 20060410   @25444 All elements can be set to the same scalar value (new signature).
** 003 GES 20060412   @25486 Added a setUnknown() method.
** 004 GES 20060425   @25707 Added a new assign() signature.
** 005 ECF 20060428   @25851 Added safety code. If target array element is null, duplicate
**                           corresponding source to initialize it.
** 006 GES 20080911   @39793 Added signatures to allow primitives to be assigned from.
** 007 GES 20090625   @42956 Fix and helpers to avoid ArrayIndexOutOfBounds. This implements a
**                           new delegation model to hide all array variable dereferencing (for
**                           assignment or simple access). The helpers can thus translate or
**                           avoid Java level AIOOB exceptions and behave the way that the 4GL
**                           requires.
** 008 CA  20130209          Added support for the EXTENT statement and output EXTENT parameters.
** 009 CA  20130315          Added missing assignSingle and assignMulti APIs, for to the cases
**                           when the rvalue is a resource, as in some cases the :HANDLE attribute
**                           conversion emits the WrappedResource instance instead of handle.
** 010 OM  20130418          Switched to long/int64 to support 64-bit wide indexes and values. 
** 011 CA  20131013          Added no-op scopeDeleted() method, required by the changes in 
**                           Scopeable interface.
** 012 GES 20140116          Made isDynamicArray() public.
** 013 HC  20140324          Fixed extent-to-extent assignment.
** 014 HC  20140413          Fixed extent parameter initialization.
** 015 HC  20140430          Fixed decimal precision for extent variables. 
** 016 HC  20140613          Improved extent parameter support - removed some of 
**                           the code being emitted and introduced multiple wrapper classes 
**                           depending on the parameter type and the l-value type (variable vs.
**                           field).
** 017 CA  20151214          When resizing a shared extent variable, update the reference in the
**                           SharedVariableManager registries.
** 018 OM  20160706          Small fix in subscriptWorker() method.
** 019 CA  20160728          Fixed undoable support for dynamic-extent arrays: in this case, when
**                           the array has its extent set, all the elements must register for undo
**                           depending on the scope where the array was defined.
** 020 CA  20160812          Performance improvements for H019: use IdentityHashMap when using
**                           ext prog instances or BDT[] as keys, pass the tx level when 
**                           registering dynamic extent arrays.
** 021 GES 20171207          Removed explicit bypass on pending error to match new silent error 
**                           mode.
** 022 CA  20190122          Added runtime for object extent variables.
**     CA  20190306          Do not delete the scope of a persistent procedure.
**     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.
** 023 CA  20190508          Added support for CASE-SENSITIVE extent var option.
** 024 CA  20190812          Fixed 'isObject' - check if the class is object or a subclass.
** 025 CA  20201003          Replaced Guava identity HashSet with Collections.newSetFromMap(IdentityHashMap).
**     ME  20201009          Add new 'assignMulti' method with Object input to work with Class.invoke.
**         20201019          Save the object type reference on resize back to indeterminate array.
**         20210128          Register as dynamic when resize back to indeterminate array.
**     CA  20210203          Fixed registration of extent variables (defined in a procedure/function) and 
**                           extent parameters - they need to be registered as 'pending' and registered when
**                           the top-level scope is processed.
**     CA  20210223          If a dynamic array parameter is resized by the top-level block code, then the
**                           reference in the parameter instance must be updated.
**     CA  20210322          Fixed dynamic arrays used in persistent programs or OO instances.
**     CA  20210609          Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly 
**                           initialized at the method's execution, and not at the caller's arguments.
**     CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the 
**                           registration is now specific to each type of scopeable.  For each case, the block
**                           will be registered for scope support (for that particular scopeable) only when
**                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
**                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
**                           processing all the scopeables for each and every block.
**     CA  20221010          Performance improvements - avoid the ProcedureData lookup, by keeping a parallel 
**                           stack of this data for THIS-PROCEDURE.  Refs #6826
**     CA  20220524          Fixed issues with dynamic array registration.
**     CA  20220601          Added 'assignMultiPoly', emitted when r-value is '::', DYNAMIC-INVOKE or 
**                           DYNAMIC-FUNCTION.
**     OM  20220604          Improved dereferenciation support.
**     CA  20220609          When resizing a dynamic array back to zero, release or legacy object instances.
**                           reRegister must process the persistent procedure's dynamic arrays, too.
**     CA  20220630          A small performance improvement.
**     CA  20221010          Performance improvements - avoid the ProcedureData lookup, by keeping a parallel 
**                           stack of this data for THIS-PROCEDURE.  Refs #6826
**     CA  20230110          Cache the helper for ProcedureManager, ObjectOps and others (as needed), to reduce  
**                           context-local lookup.
** 026 EVL 20230301          Fix for invalid array index exception in assignSingleWorker().  Protection added.
** 027 CA  20230712          Added support to resize a 'jobject' array.
** 028 CA  20231026          A persistent procedure with a pending delete is no longer persistent, so 
**                           scopeDeleted needs to be executed.
**     CA  20231031          Added 'BlockDefinition' parameter to 'scopeStart'.
** 029 CA  20231129          Improvements related to the management of various dequeues and collections (use 
**                           empty set/map instead of creating a new instance, where possible).
** 030 CA  20240226          'copyOf' must create an array of ObjectVar if component type is 'object'.
** 031 DDF 20241001          Fix OO instance leak when a call is made with an input, extent, object argument.
** 032 CA  20240911          Added method to resize a dynamic extent field via deserialization of a legacy 
**                           object.
** 033 ICP 20250123          Used integer, int64 and logical constants to leverage caches instances.
** 034 PBB 20250522          Added note explaining a weird case that seems to be a bug in 4gl.
*/

/*
** 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.util.*;

/**
 * Provides a simple helper to automate the assignment of each element of a 
 * {@link BaseDataType} array based on 2 array inputs.
 */
public class ArrayAssigner
{
   /** Context-local data container. */
   private static ContextLocal<WorkArea> local = new ContextLocal<WorkArea>()
   {
      protected WorkArea initialValue()
      {
         WorkArea wa = new WorkArea();
         return wa;
      };
   };

   /**
    * Assigns (mutates the content of) each matching element of a target
    * array based on the corresponding element of the source array.  The
    * source and target arrays must be assignable to each other (must be
    * assignment-compatible). The copy operation will stop as soon as the end
    * of either array is encountered. If either array is longer than the other,
    * then the copy will only occur for the overlapping portion. The copy
    * always starts at element 0 and finishes at the lesser of source length or
    * target length.
    * <p>
    * In case the assignment target represents an indeterminate extent variable
    * and the source is a determinate extent or a fixed indeterminate extent
    * the target is reallocated to the size of the source extent. The allocated 
    * reference is returned as the result from the function.
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    source
    *           The array from which to assign. Must not be <code>null</code>.
    *           
    * @return   The reference to the target array or a reference to a newly
    *           allocated array representing the assignment target.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static BaseDataType[] assignMultiPoly(BaseDataType[] target, Object source)
   {
      if (source instanceof BaseDataType)
      {
         assignMulti(target, (BaseDataType) source);
         return target;
      }
      else if (source != null && source.getClass().isArray())
      {
         return assignMulti(target, ((BaseDataType[]) source));
      }
      else
      {
         throw new IllegalArgumentException("Source type " + (source == null ? "null" : source.getClass()) + 
                                            " is not supported!");
      }
   }

   /**
    * Assigns (mutates the content of) each matching element of a target
    * array based on the corresponding element of the source array.  The
    * source and target arrays must be assignable to each other (must be
    * assignment-compatible). The copy operation will stop as soon as the end
    * of either array is encountered. If either array is longer than the other,
    * then the copy will only occur for the overlapping portion. The copy
    * always starts at element 0 and finishes at the lesser of source length or
    * target length.
    * <p>
    * In case the assignment target represents an indeterminate extent variable
    * and the source is a determinate extent or a fixed indeterminate extent
    * the target is reallocated to the size of the source extent. The allocated 
    * reference is returned as the result from the function.
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    source
    *           The array from which to assign. Must not be <code>null</code>.
    *           
    * @return   The reference to the target array or a reference to a newly
    *           allocated array representing the assignment target.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static BaseDataType[] assignMulti(BaseDataType[] target, BaseDataType[] source) 
   throws ErrorConditionException,
          NullPointerException
   {
      // 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.

      boolean dynamicTarget = isDynamicArray(target);

      if (dynamicTarget     && 
          target.length > 0 && 
          (source.length == 0 ||
           source.length != target.length))
      {
         final String err = "Indeterminate extent is already fixed to a dimension of %d";
         String errMsg = String.format(err, target.length);
         ErrorManager.recordOrThrowError(13738, errMsg, false);
         
         return target;
      }
      if (!dynamicTarget)
      {
         if (source.length == 0)
         {
            final String errMsg = "Uninitialized array used as source of assignment";
            ErrorManager.recordOrThrowError(14906, errMsg, false);
            
            return target;
         }
         else if (source.length != target.length)
         {
            final String errMsg = 
               "Whole-array assignment target and source must have the same extent unless " +
               "the target is indeterminate";
            ErrorManager.recordOrThrowError(14905, errMsg, false);
            
            return target;
         }
      }
      
      if (target.length == 0 && source.length > 0)
      {
         target = resize(target, source.length);
      }

      for (int i = 0; (i < target.length && i < source.length); i++)
      {
         if (target[i] == null)
         {
            target[i] = source[i].duplicate();
         }
         else
         {
            (target[i]).assign(source[i]);
         }
      }
      
      return target;
   }
   
   /**
    * Assigns (mutates the content of) each matching element of a target
    * array based on the corresponding element of the source array.  
    * 
    * This is used for <code>invoke</code> method of Class or Call interface.
    * 
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    source
    *           The source array from which to assign. Must be an array of <code>BaseDataType</code>.
    *           
    * @return   The reference to the target array or a reference to a newly
    *           allocated array representing the assignment target.
    *
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    *           ErrorConditionException
    *           If the source is not an array of <code>BaseDataType</code>.
    */
   public static BaseDataType[] assignMulti(BaseDataType[] target, Object source) 
   throws ErrorConditionException,
          NullPointerException
   {
      if (source.getClass().isArray() && BaseDataType.class.isAssignableFrom(source.getClass().getComponentType())) {
         return assignMulti(target, (BaseDataType[]) source);
      } else {
         throw new ErrorConditionException("Parameter is a non-array parameter");
      }
   }
   
   /**
    * Assigns (mutates the content of) all elements of a target array from
    * the same source value.  The source and target types must be
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    source
    *           The value from which to assign.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static void assignMulti(BaseDataType[] target, long source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignMulti(target, int64.of(source));
   }   
   
   /**
    * Assigns (mutates the content of) all elements of a target array from
    * the same source value.  The source and target types must be
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    source
    *           The value from which to assign.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static void assignMulti(BaseDataType[] target, double source) 
   throws ErrorConditionException,
          NullPointerException
   {
      assignMulti(target, new decimal(source));
   }   
   
   /**
    * Assigns (mutates the content of) all elements of a target array from
    * the same source value.  The source and target types must be
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    source
    *           The value from which to assign.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static void assignMulti(BaseDataType[] target, String source) 
   throws ErrorConditionException,
          NullPointerException
   {
      assignMulti(target, new character(source));
   }   
   
   /**
    * Assigns (mutates the content of) all elements of a target array from
    * the same source value.  The source and target types must be
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    source
    *           The value from which to assign.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static void assignMulti(BaseDataType[] target, boolean source) 
   throws ErrorConditionException,
          NullPointerException
   {
      assignMulti(target, logical.of(source));
   }   
   
   /**
    * Assigns (mutates the content of) all elements of a target array from
    * the same source value.  The source and target types must be
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static void assignMulti(BaseDataType[] target, BaseDataType source) 
   throws ErrorConditionException,
          NullPointerException
   {
      if (target.length == 0 && TransactionManager.blockInitialized())
      {
         // TODO: for some reason, this error message doesn't end in a dot in 4GL
         String msg = "Invalid assignment to an unfixed indeterminate extent";
         ErrorManager.recordOrThrowError(11389, msg, false);
         
         return;
      }
      
      for (int i = 0; i < target.length; i++)
      {
         if (target[i] == null)
         {
            target[i] = source.duplicate();
         }
         else
         {
            (target[i]).assign(source);
         }
      }
   }   
   
   /**
    * Assigns (mutates the content of) all elements of a target array from
    * the same source value.  The source and target types must be
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static void assignMulti(BaseDataType[] target, WrappedResource source) 
   throws ErrorConditionException,
          NullPointerException
   {
      if (target.length == 0 && TransactionManager.blockInitialized())
      {
         // TODO: for some reason, this error message doesn't end in a dot in 4GL
         String msg = "Invalid assignment to an unfixed indeterminate extent";
         ErrorManager.recordOrThrowError(11389, msg, false);
         
         return;
      }
      
      for (int i = 0; i < target.length; i++)
      {
         if (target[i] == null)
         {
            target[i] = new handle(source);
         }
         else
         {
            (target[i]).assign(new handle(source));
         }
      }
   }   
   
   /**
    * Assigns (mutates the content of) all elements of a target array starting
    * at the given index through the end of the array, using the same source
    * value.  The source and target types must be assignable to each other 
    * (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    idx
    *           Starting index (0-based) for the assignments.
    * @param    source
    *           The value from which to assign.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static void assignMulti(BaseDataType[] target, int idx, long source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignMulti(target, idx, int64.of(source));
   }
   
   /**
    * Assigns (mutates the content of) all elements of a target array starting
    * at the given index through the end of the array, using the same source
    * value.  The source and target types must be assignable to each other 
    * (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    idx
    *           Starting index (0-based) for the assignments.
    * @param    source
    *           The value from which to assign.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static void assignMulti(BaseDataType[] target, int idx, double source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignMulti(target, idx, new decimal(source));
   }
   
   /**
    * Assigns (mutates the content of) all elements of a target array starting
    * at the given index through the end of the array, using the same source
    * value.  The source and target types must be assignable to each other 
    * (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    idx
    *           Starting index (0-based) for the assignments.
    * @param    source
    *           The value from which to assign.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static void assignMulti(BaseDataType[] target, int idx, String source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignMulti(target, idx, new character(source));
   }
   
   /**
    * Assigns (mutates the content of) all elements of a target array starting
    * at the given index through the end of the array, using the same source
    * value.  The source and target types must be assignable to each other 
    * (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    idx
    *           Starting index (0-based) for the assignments.
    * @param    source
    *           The value from which to assign.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static void assignMulti(BaseDataType[] target, int idx, boolean source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignMulti(target, idx, logical.of(source));
   }
   
   /**           
    * Assigns (mutates the content of) all elements of a target array starting
    * at the given index through the end of the array, using the same source
    * value.  The source and target types must be assignable to each other 
    * (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    idx
    *           Starting index (0-based) for the assignments.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static void assignMulti(BaseDataType[] target, int idx, BaseDataType source)
   throws ErrorConditionException,
          NullPointerException
   {
      for (int i = idx; i < target.length; i++)
      {
         if (target[i] == null)
         {
            target[i] = source.duplicate();
         }
         else
         {
            (target[i]).assign(source);
         }
      }
   }   
   
   /**           
    * Assigns (mutates the content of) all elements of a target array starting
    * at the given index through the end of the array, using the same source
    * value.  The source and target types must be assignable to each other 
    * (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    idx
    *           Starting index (0-based) for the assignments.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure of an individual assignment.
    * @throws   NullPointerException
    *           If either the source or the target arrays are 
    *           <code>null</code>.
    */
   public static void assignMulti(BaseDataType[]  target, int idx, WrappedResource source) 
   throws ErrorConditionException,
          NullPointerException
   {
      for (int i = idx; i < target.length; i++)
      {
         if (target[i] == null)
         {
            target[i] = new handle(source);
         }
         else
         {
            (target[i]).assign(new handle(source));
         }
      }
   }   
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, long index, long source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignSingle(target, int64.of(index), int64.of(source));
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, long index, double source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignSingle(target, int64.of(index), new decimal(source));
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, long index, boolean source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignSingle(target, int64.of(index), new logical(source));
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, long index, String source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignSingle(target, int64.of(index), new character(source));
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, long index, BaseDataType source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignSingle(target, int64.of(index), source);
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, long index, WrappedResource source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignSingle(target, int64.of(index), new handle(source));
   }
      
   /**
    * Assigns (or mutates the content of) the indexed element of a target array to the given source value.
    * The source and target types must be assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by 1 and then used if it is in
    *           bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, long index, Object source)
   throws ErrorConditionException,
          NullPointerException
   {
      if (source instanceof BaseDataType)
      {
         assignSingleWorker(target, int64.of(index), (BaseDataType) source);
      }
      else if (source != null && source.getClass().isArray())
      {
         ErrorManager.recordOrThrowError(14905);
         // Whole-array assignment target and source must have the same extent unless the target is indeterminate. (14905)
      }
      else
      {
         throw new IllegalArgumentException(
               "Source type " + (source == null ? "null" : source.getClass()) + " is not supported!");
      }
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, NumberType index, long source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignSingle(target, index, int64.of(source));
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, NumberType index, double source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignSingle(target, index, new decimal(source));
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, NumberType index, boolean source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignSingle(target, index, logical.of(source));
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, NumberType index, String source) 
   throws ErrorConditionException,
          NullPointerException
   {
      assignSingle(target, index, new character(source));
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, NumberType index, BaseDataType source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignSingleWorker(target, index, source);
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, NumberType index, WrappedResource source)
   throws ErrorConditionException,
          NullPointerException
   {
      assignSingleWorker(target, index, new handle(source));
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingle(BaseDataType[] target, NumberType index, Object source)
   throws ErrorConditionException,
          NullPointerException
   {
      if (source instanceof BaseDataType)
      {
         assignSingleWorker(target, index, (BaseDataType) source);
      }
      else if (source != null && source.getClass().isArray())
      {
         ErrorManager.recordOrThrowError(14905);
         // Whole-array assignment target and source must have the same extent unless the target is indeterminate. (14905)
      }
      else
      {
         throw new IllegalArgumentException(
               "Source type " + (source == null ? "null" : source.getClass()) + " is not supported!");
      }
   }
   
   /**
    * Dereference the given array using the given 1-based index.
    *
    * @param    array
    *           The array to dereference.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    */
   public static <B extends BaseDataType> B subscript(B[] array, long index)
   {
      int idx = subscriptHelper(array, index);
      return subscriptWorker(array, idx);
   }
   
   /**
    * Dereference the given array using the given 1-based index.
    *
    * @param    array
    *           The array to dereference.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    */
   public static <B extends BaseDataType> B subscript(B[] array, NumberType index)
   {
      int idx = subscriptHelper(array, index, false);
      return subscriptWorker(array, idx);
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the unknown value.
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingleUnknown(BaseDataType[] target, long index)
   throws ErrorConditionException,
          NullPointerException
   {
      assignSingleUnknown(target, int64.of(index));
   }
   
   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the unknown value.
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   public static void assignSingleUnknown(BaseDataType[] target, NumberType index)
   throws ErrorConditionException,
          NullPointerException
   {
      Class<? extends BaseDataType> cls = 
         (Class<? extends BaseDataType>) target.getClass().getComponentType();

      assignSingleWorker(target, index, BaseDataType.generateUnknown(cls));
   }
   
   /**                     
    * Sets all elements of a target array to the <code>unknown value</code>.
    *
    * @param    target
    *           The array to be modified. Must not be <code>null</code>.
    */
   public static void assignUnknown(BaseDataType[] target)
   {
      if (target.length == 0 && TransactionManager.blockInitialized())
      {
         // TODO: for some reason, this error message doesn't end in a dot in 4GL
         String msg = "Invalid assignment to an unfixed indeterminate extent";
         ErrorManager.recordOrThrowError(11389, msg, false);
         
         return;
      }
      
      for (int i = 0; i < target.length; i++)
      {
         target[i].setUnknown();
      }
   }
   
   /**
    * Resize this Java-style array.  In case the new size is greater than the array's size, the new elements
    * will be initialized to a {@link jobject} with the {@link Object} type.
    * <p>
    * WARNING: {@link jobject} arrays are not compatible with EXTENT - they are not tracked by FWD runtime.
    * 
    * @param    <T>
    *           The jobject element type.
    * @param    array
    *           The array to be resized.
    * @param    newSize
    *           The new size.
    *           
    * @return   The new array reference.
    */
   public static <T> jobject<T>[] resize(jobject<T>[] array, int newSize)
   {
      jobject<T>[] res = new jobject[newSize];
      System.arraycopy(array, 0, res, 0, array.length);
      for (int i = array.length; i < newSize; i++)
      {
         res[i] = new jobject<T>(Object.class);
      }
      return res;
   }
   
   /**
    * Implement the EXTENT statement. This resizes the given array to the new size and returns
    * the new instance.
    * <p>
    * If the given array is not of size zero, then an error is raised.  Else, all the elements
    * of the array will be set to default value.
    * 
    * @param    array
    *           The dynamic-extent array to be resized.
    * @param    newSize
    *           The array's new size.
    * 
    * @return   The resized array.
    */
   public static <T extends BaseDataType> T[] resize(T[] array, int newSize)
   {
      return resize(array, integer.of(newSize), false);
   }
   
   /**
    * Implement the EXTENT statement. This resizes the given array to the new size and returns
    * the new instance.
    * <p>
    * If the given array is not of size zero, then an error is raised.  Else, all the elements
    * of the array will be set to default value.
    * 
    * @param    array
    *           The dynamic-extent array to be resized.
    * @param    newSize
    *           The array's new size.
    * 
    * @return   The resized array.
    */
   public static <T extends BaseDataType> T[] resize(T[] array, int64 newSize)
   {
      return resize(array, newSize, false);
   }

   /**
    * Implement the EXTENT statement. This resizes the given array to the new size and returns
    * the new instance.
    * <p>
    * If the given array is not of size zero, then an error is raised.  Else, all the elements
    * of the array will be set to unknown or default value depending on the <code>initToUnknown</code> 
    * parameter.
    * 
    * @param    array
    *           The dynamic-extent array to be resized.
    * @param    newSize
    *           The array's new size.
    * @param    initToUnknown
    *           Initialization flag, see above for more info.
    * 
    * @return   The resized array.
    */
   public static <T extends BaseDataType> T[] resize(T[] array, int newSize, boolean initToUnknown)
   {
      return resize(array, integer.of(newSize), initToUnknown);
   }

   /**
    * Implement the EXTENT statement. This resizes the given array to the new size and returns
    * the new instance.
    * <p>
    * If the given array is not of size zero (and the newSize is not unknown), then an error is 
    * raised. Else, the array will be resized and all the elements will be set to unknown or 
    * the default value depending on the <code>initToUnknown</code> parameter.
    * <p>
    * The new extent value must be from the [1..28000] interval.
    * 
    * @param    array
    *           The dynamic-extent array to be resized. Must not be null. If the array was not
    *           resized yet, must be 0-length.
    * @param    newSize
    *           The array's new size.
    * @param    initToUnknown
    *           Initialization flag, see above for more info.
    * 
    * @return   The resized array.
    */
   @SuppressWarnings("unchecked")
   public static <T extends BaseDataType> T[] resize(T[] array, int64 newSize, boolean initToUnknown)
   {
      return resize(array, newSize, initToUnknown, true);
   }
   
   /**
    * Implement the EXTENT statement. This resizes the given array to the new size and returns
    * the new instance.
    * <p>
    * If the given array is not of size zero (and the newSize is not unknown), then an error is 
    * raised. Else, the array will be resized and all the elements will be set to unknown or 
    * the default value depending on the <code>initToUnknown</code> parameter.
    * <p>
    * The new extent value must be from the [1..28000] interval.
    * 
    * @param    array
    *           The dynamic-extent array to be resized. Must not be null. If the array was not
    *           resized yet, must be 0-length.
    * @param    newSize
    *           The array's new size.
    * @param    initToUnknown
    *           Initialization flag, see above for more info.
    * @param    isVar
    *           Flag indicating to create {@link ObjectVar} elements instead of {@link object}.
    *           
    * @return   The resized array.
    */
   @SuppressWarnings("unchecked")
   static <T extends BaseDataType> T[] resize(T[] array, int64 newSize, boolean initToUnknown, boolean isVar)
   {
      long size = newSize.isUnknown() ? 0 : newSize.longValue();
      if (!newSize.isUnknown())
      {
         if (array.length != 0)
         {
            final String msg = "Indeterminate extent is already fixed to a dimension of %d";
            ErrorManager.recordOrThrowError(13738, String.format(msg, array.length), false);
            return array;
         }
   
         if (size <= 0 || size > 28000)
         {
            //size < 1:  Invalid value specified for the EXTENT statement. (13737)
            //size > 28000: Array extent may not be greater than 28000 (3350)
            final String msg = "Indeterminate extent dimension %d is out of range 1 to 28000";
            ErrorManager.recordOrThrowError(11385, String.format(msg, size), false);
            return array;
         }
      }

      WorkArea wa = local.get();
      
      Class<? extends BaseDataType> cls = 
         (Class<? extends BaseDataType>) array.getClass().getComponentType();
      T[] copy = (T[]) Array.newInstance(cls, (int) size);
      
      boolean isObject = object.class.isAssignableFrom(array.getClass().getComponentType());
      // if this object array was resized, is no longer needed to keep the type
      Class<? extends _BaseObject_> objType = isObject ? wa.dynamicArrayType.remove(array) : null;
      
      if (isObject && size == 0) {
         // reset back to indeterminate size, save the type, and release all references
         if (objType == null && array.length > 0)
         {
            objType = (Class<? extends _BaseObject_>) ((object) array[0]).type();
         }
         
         if (objType != null)
            wa.dynamicArrayType.put((object<? extends _BaseObject_>[]) copy, objType);
         
         for (object<? extends _BaseObject_> ref : (object[]) array)
         {
            ref.decrement();
         }
      }
      
      for (int i = 0; i < copy.length; i++)
      {
         if (isObject)
         {
            copy[i] = isVar ? (T) new ObjectVar<>(objType) : (T) BaseDataType.generateUnknown(cls);
         }
         else
         {
            if (initToUnknown)
            {
               copy[i] = (T) BaseDataType.generateUnknown(cls);
            }
            else
            {
               copy[i] = (T) BaseDataType.generateDefault(cls);
            }
         }
      }
      
      // re-register it - replace the old array with the new one
      wa.reRegister(array, copy, wa);
      if (isObject)
      {
         ObjectOps.reRegister(array, copy);
      }
      
      // preserve precision of the source array
      copyDecimalPrecision(array, copy);
      copyCaseSensitive(array, copy);
      
      // if this array belongs to an extent parameter, update it
      AbstractExtentParameter<BaseDataType> extentParam = wa.getExtentParameter(array);
      if (extentParam != null)
      {
         extentParam.setParameter(copy);
      }
      
      // if shared, update the shared reference (global or local)
      SharedVariableManager.updateExtentVar(array, copy);
      
      // if this is an undoable extent var, we need to register all instances as undoable!
      Object referent = wa.pm._thisProcedure();
      Deque<Map<BaseDataType[], Integer>> undoableExts = wa.dynamicUndoableArrays.get(referent);
      Iterator<Map<BaseDataType[], Integer>> iter = undoableExts.iterator();
      while (iter.hasNext())
      {
         Map<BaseDataType[], Integer> undoables = iter.next();
         
         Integer transLevel = undoables.remove(array);

         if (transLevel != null)
         {
            // if this is the ext program scope and the proc is ran persistent
            boolean persistent = !iter.hasNext() && wa.pm.isThisProcedurePersistent();
            
            //  the entry must be registered at the scope where the extent was defined
            wa.tm.registerAt(transLevel, persistent, copy);
            
            break;
         }
      }
      
      return copy;
   }

   /**
    * Get a copy of the given array.
    * 
    * @param    array
    *           The array for which a copy is needed.
    * 
    * @return   The copy.
    */
   public static <T extends BaseDataType> T[] copyOf(T[] array)
   {
      return copyOf(array, true);
   }
   
   /**
    * Get a copy of the given array.
    * 
    * @param    array
    *           The array for which a copy is needed.
    * @param    pending
    *           Flag indicating if the copy is pending (as the call is made before the top-level block starts).
    *           
    * @return   The copy.
    */
   public static <T extends BaseDataType> T[] copyOf(T[] array, boolean pending)
   {
      Class<? extends BaseDataType> cls = 
               (Class<? extends BaseDataType>) array.getClass().getComponentType();
      boolean isObject = (cls == object.class);
      
      T[] copy = (T[]) Array.newInstance(cls, array.length);
      for (int i = 0; i < copy.length; i++)
      {
         copy[i] = isObject ? (T) new ObjectVar() : (T) BaseDataType.generateUnknown(cls);
         copy[i].assign(array[i]);
      }
      
      WorkArea wa = local.get();
      if (wa.isRegistered(array))
      {
         // if the original array is registered as a dynamic array
         // register the copy as well.
         // for example, when assigning the output parameter back to its 
         // original reference, a copy is made
         wa.register(copy, pending);
      }
      
      if (isObject && pending)
      {
         ObjectOps.registerPending((Object) copy);
      }
      
      // preserve precision of the source array
      copyDecimalPrecision(array, copy);
      copyCaseSensitive(array, copy);
      
      return copy;
   }

   /**
    * Get the length of the given dynamic-extent variable.
    * 
    * @param    array
    *           The dynamic-extent variable.
    *           
    * @return   If the array's length is zero, return unknown. Else, the actual size.
    */
   public static integer lengthOf(BaseDataType[] array)
   {
      return array.length == 0 ? integer.UNKNOWN : integer.of(array.length);
   }

   /**
    * Register the given array as a dynamic-array.
    * <p>
    * As this is called always before the top-level block is actually executed (is invoked during parameter
    * processing at the caller), the dynamic array is registered as 'pending', and saved to be processed by
    * the next top-level block.
    * 
    * @param    array
    *           The dynamic-array to register.
    */
   public static void registerDynamicArray(BaseDataType[] array)
   {
      registerDynamicArray(array, true);
   }

   /**
    * Register the given array as a dynamic-array.
    * 
    * @param    array
    *           The dynamic-array to register.
    * @param    pending
    *           Flag indicating if the copy is pending (as the call is made before the top-level block starts).
    */
   public static void registerDynamicArray(BaseDataType[] array, boolean pending)
   {
      local.get().register(array, pending);
   }

   /**
    * Register the set of given arrays as dynamic extent.
    * 
    * @param    arrays
    *           The set of dynamic arrays to register.
    * @param    transLevel
    *           The transaction nesting level where this instance was created.
    */
   public static void registerDynamicUndoableArray(Set<BaseDataType[]> arrays, int transLevel)
   {
      local.get().registerDynamicUndoable(arrays, transLevel);
   }
   
   /**
    * This method returns true for any real dynamic-extent array (regardless if it had its
    * size set).
    * 
    * @param    array
    *           The array to be checked.
    *           
    * @return   See above.
    */
   public static boolean isDynamicArray(BaseDataType[] array)
   {
      return local.get().isRegistered(array);
   }
   
   /**
    * Sets decimal precision for the array argument.
    * 
    * @param     array
    *            The array to be set with the supplied precision.
    * @param     precision
    *            Decimal precision to be set in the supplied array.
    */
   public static void setDecimalPrecision(decimal[] array, int precision)
   {
      if (array.length == 0)
      {
         // for dynamic extents, precision is stored in the context
         WorkArea wa = local.get();
         wa.setDecimalPrecision(array, precision);
      }
      else
      {
         // precision is stored in the components for non-dynamic extents
         for (decimal d : array)
         {
            d.setPrecision(precision);
         }
      }
   }
   
   /**
    * Sets case sensitivity for the array argument.
    * 
    * @param     array
    *            The array to be set with the supplied case sensitivity.
    * @param     caseSens
    *            Case sensitivity to be set in the supplied array.
    */
   public static void setCaseSensitive(Text[] array, boolean caseSens)
   {
      if (array.length == 0)
      {
         // for dynamic extents, precision is stored in the context
         WorkArea wa = local.get();
         wa.setCaseSensitive(array, caseSens);
      }
      else
      {
         // precision is stored in the components for non-dynamic extents
         for (Text txt : array)
         {
            txt.setCaseSensitive(caseSens);
         }
      }
   }
   
   /**
    * Returns decimal precision of the supplied array. If the array represents a
    * dynamic-extent, the precision must have been previously set, otherwise <code>null</code> is
    * returned.
    * 
    * @param    array
    *           Array representing an extent variable.
    * 
    * @return   The decimal precision value or <code>null</code>, see above.      
    */
   public static Integer getDecimalPrecision(decimal[] array)
   {
      if (array.length == 0)
      {
         // for dynamic extents, precision is stored in the context
         WorkArea wa = local.get();
         return wa.getDecimalPrecision(array);
      }
      else 
      {
         // precision is stored in the components for non-dynamic extents
         return array[0].getPrecision();
      }
   }
   
   /**
    * Returns case sensitivity of the supplied array. If the array represents a
    * dynamic-extent, the case sensitivity must have been previously set, otherwise 
    * <code>null</code> is returned.
    * 
    * @param    array
    *           Array representing an extent variable.
    * 
    * @return   The case sensitivity value or <code>null</code>, see above.      
    */
   public static Boolean getCaseSensitive(Text[] array)
   {
      if (array.length == 0)
      {
         // for dynamic extents, precision is stored in the context
         WorkArea wa = local.get();
         return wa.getCaseSensitive(array);
      }
      else 
      {
         // precision is stored in the components for non-dynamic extents
         return array[0].isCaseSensitive();
      }
   }

   /**
    * Save the legacy 4GL class for the specified dynamic extent array.
    * 
    * @param    ref
    *           The dynamic array reference.
    * @param    cls
    *           The legacy 4GL class.
    */
   static <T extends _BaseObject_> void setObjectType(object<T>[] ref, Class<T> cls)
   {
      local.get().dynamicArrayType.put(ref, cls);
   }

   /**
    * Delete the saved the legacy 4GL class for the specified dynamic extent array.
    * 
    * @param    ref
    *           The dynamic array reference.
    */
   static <T extends _BaseObject_> void removeObjectType(object<T>[] ref)
   {
      local.get().dynamicArrayType.remove(ref);
   }

   /**
    * Get the legacy 4GL class for the specified dynamic extent array.
    * 
    * @param    ref
    *           The dynamic array reference.
    *           
    * @return   The legacy 4GL class for this array's objects.
    */
   static Class<? extends _BaseObject_> getObjectType(object<? extends _BaseObject_>[] ref)
   {
      return local.get().dynamicArrayType.get(ref);
   }

   /**
    * Registers an unfixed indeterminate extent parameter instance.
    * The registration is required to keep the extent parameter instance
    * up-to-date in case its parameter reference changes (for example
    * when the parameter value is resized by the EXTENT statement).
    * 
    * @param   extentParameter
    *          An instance representing an input or input-output extent parameter. 
    * 
    * @throws  IllegalArgumentException
    *          When the passed in instance does not represent an unfixed 
    *          indeterminate extent.
    */
   static <T extends BaseDataType> void registerExtentParameter(
         AbstractExtentParameter<T> extentParameter)
   {
      T[] param = extentParameter.getParameter();
      if (param.length > 0)
      {
         throw new IllegalArgumentException(
               "Unfixed indeterminate extent for extentParameter argument is expected.");
      }
      
      WorkArea wa = local.get();
      wa.registerExtentParameter(extentParameter);
   }

   /**
    * Register the {@link WorkArea} instance as pending scopeable, to be added to the next block.
    */
   static void registerPendingScopeable()
   {
      WorkArea wa = local.get();
      wa.tm.registerPendingScopeable(wa);
   }
   /**
    * Method use to resize a dynamic extent field on object deserialization.  The size must never be zero.
    * 
    * @param    array
    *           The dynamic-extent array to be resized. Must not be null, and its size zero.
    * @param    newSize
    *           The array's new size, must not be zero.
    * @param    ref
    *           The instance to which the array belongs.
    *           
    * @return   The resized array.
    */
   @SuppressWarnings("unchecked")
   static BaseDataType[] resizeField(BaseDataType[] array, int newSize, _BaseObject_ ref)
   {
      WorkArea wa = local.get();
      
      Class<? extends BaseDataType> cls = (Class<? extends BaseDataType>) array.getClass().getComponentType();
      BaseDataType[] copy = (BaseDataType[]) Array.newInstance(cls, newSize);
      
      boolean isObject = object.class.isAssignableFrom(array.getClass().getComponentType());
      // if this object array was resized, is no longer needed to keep the type
      Class<? extends _BaseObject_> objType = isObject ? wa.dynamicArrayType.remove(array) : null;
      
      for (int i = 0; i < copy.length; i++)
      {
         if (isObject)
         {
            copy[i] = new ObjectVar<>(objType);
         }
         else
         {
            copy[i] = BaseDataType.generateUnknown(cls);
         }
      }
      
      // re-register it - replace the old array with the new one
      wa.reRegister(array, copy, wa);
      if (isObject)
      {
         ObjectOps.reRegister(array, copy);
      }
      
      // preserve precision of the source array
      copyDecimalPrecision(array, copy);
      copyCaseSensitive(array, copy);
      
      // if this is an undoable extent var, we need to register all instances as undoable!
      Deque<Map<BaseDataType[], Integer>> undoableExts = wa.dynamicUndoableArrays.get(ref);
      Iterator<Map<BaseDataType[], Integer>> iter = undoableExts.iterator();
      while (iter.hasNext())
      {
         Map<BaseDataType[], Integer> undoables = iter.next();
         
         Integer transLevel = undoables.remove(array);

         if (transLevel != null)
         {
            // the entry must be registered at the scope where the extent was defined
            wa.tm.registerAt(transLevel, true, ref, copy);
            
            break;
         }
      }
      
      return copy;
   }
   
   /**
    * Dereference the given array using the given 0-based index.
    *
    * @param    array
    *           The array to dereference.
    * @param    idx
    *           The 0-based index into the array. No bounds checking is done.
    */
   private static <B extends BaseDataType> B subscriptWorker(B[] array, int idx)
   {
      if (idx == -2)
      {
         // this case means the index is unknown, so we need to return unknown
         return (B) BaseDataType.generateUnknown(array.getClass().getComponentType());
      }
      
      if (idx == -1)
      {
         // if an invalid index was passed and [subscriptHelper()] returned -1 then the 
         // [ErrorManager.isPendingError()] is already set to true anyway.
         // However, if [ErrorManager.isPendingError()] was set because ANY OTHER reason and [idx]
         // is valid, then we need to return the correct dereference, array[idx].
         Class<?> cls = array.getClass().getComponentType();
         
         // silently return unknown value (safest thing)
         return (B) BaseDataType.generateUnknown(cls);
      }
      
      return array[idx];
   }
   
   /**
    * Generate a valid 0-based integral subscript from the 1-based numeric 
    * value given. Bounds checking is done based on the target array length.
    *
    * @param    array
    *           The array to dereference.
    * @param    _idx
    *           The 1-based index to be checked and modified.
    * @param    assign
    *           <code>true</code> if the extent var in on the left-side of the assign.
    *
    * @return   The 0-based integral subscript, -2 if the index is the
    *           <code>unknown value</code> or -1 if the index is out of bounds
    *           in silent error mode.
    *
    * @throws   ErrorConditionException
    *           If the index is out of bounds.
    */
   private static int subscriptHelper(BaseDataType[] array, NumberType _idx, boolean assign)
   throws ErrorConditionException
   { 
      if (_idx.isUnknown())
      {
         // no error is generated if the index is unknown, but unknown needs to be returned
         return -2;
      }

      // 4GL lets the the 33 MSB overflow, computing the array index using last 31 bits
      int idx = (int)(_idx.longValue() & 0x7FFFFFFFL);
      if (idx < 0)
      {
         String spec   = "Array subscript %d is out of range";
         String errmsg = String.format(spec, idx);
         
         ErrorManager.recordOrThrowError(26, errmsg);
         return idx - 1;
      }

      int len = array.length;
      if (len == 0)
      {
         // dynamic-extent, not initialized
         String msg = "Invalid runtime use of indeterminate extent.  " +
                      "It must be set to a fixed dimension";
         
         // TODO: if we are in message mode, it seems like the error is shown, but not thrown
         ErrorManager.recordOrThrowError(11387, msg, false);

         // we need to return -1
         return -1;
      }
      else if (idx <= 0 || idx - 1 >= len)
      {
         // error out!
         if (isDynamicArray(array) && (!assign || idx - 1 >= len))
         {
            String spec = 
               "Array subscript %d is out of range.  The indeterminate extent is fixed to a " +
               "dimension of %d";
            String errmsg = String.format(spec, idx, len);
            
            ErrorManager.recordOrThrowError(11388, errmsg, false);
         }
         else
         {
            String spec   = "Array subscript %d is out of range";
            String errmsg = String.format(spec, idx);
            
            ErrorManager.recordOrThrowError(26, errmsg);
         }

         // we need to return -1
         return -1;
      }
      
      return idx - 1;
   }
   
   /**
    * Generate a valid 0-based integral subscript from the 1-based numeric 
    * value given. Bounds checking is done based on the target array length.
    *
    * @param    array
    *           The array to dereference.
    * @param    idx
    *           The 1-based index to be checked and modified.
    *
    * @return   The 0-based integral subscript or -1 if the index is out
    *           of bounds in silent error mode.
    *
    * @throws   ErrorConditionException
    *           If the index is out of bounds.
    */
   private static int subscriptHelper(BaseDataType[] array, long idx)
   throws ErrorConditionException
   {
      return subscriptHelper(array, int64.of(idx), false);
   }

   /**
    * Assigns (or mutates the content of) the indexed element of a target array
    * to the given source value.  The source and target types must be 
    * assignable to each other (must be assignment-compatible).
    *
    * @param    target
    *           The array to be assigned. Must not be <code>null</code>.
    * @param    index
    *           The 1-based index into the array. This will be decremented by
    *           1 and then used if it is in bounds.
    * @param    source
    *           The value from which to assign. Must not be <code>null</code>.
    *
    * @throws   ErrorConditionException
    *           On any failure in the assignment or array index out of bounds.
    * @throws   NullPointerException
    *           If either the source or the target arrays are <code>null</code>.
    */
   private static void assignSingleWorker(BaseDataType[] target,
                                          NumberType     index,
                                          BaseDataType   source)
   {

      boolean unknown = index.isUnknown();
      boolean dynamic = isDynamicArray(target);
      if (!unknown && target.length == 0)
      {
         // dynamic-extent, not initialized, and tried to assign an element
         if (index.intValue() <= 0)
         {
            String spec   = "Array subscript %d is out of range";
            String errmsg = String.format(spec, index.intValue());
            
            ErrorManager.recordOrThrowError(26, errmsg);
         }
         else
         {
            // TODO: for some reason, this error message doesn't end in a dot in 4GL
            String msg = "Invalid assignment to an unfixed indeterminate extent";
            ErrorManager.recordOrThrowError(11389, msg, false);
         }
         return;
      }
      
      int idx = -1;
      
      try
      {
         idx = subscriptHelper(target, index, true);
         if (idx == -2)
         {
            // in case the index is unknown, show only this message
            ErrorManager.recordOrThrowError(26, "Array subscript 0 is out of range");
            return;
         }
      }
      
      catch (ErrorConditionException ece)
      {
         // re-throw the error if in dynamic mode or if the index is less than zero
         if (dynamic || (!index.isUnknown() && index.intValue() < 0))
            throw ece;
         
         // eat the first error, we will rethrow shortly
      }
      
      if (idx < 0)
      {
         if (!dynamic)
         {
            // only non-dynamic arrays show this error (double space before Field is intented)
            ErrorManager.recordOrThrowError(142, "Unable to update  Field");
         }
         
         // silently return
         return;
      }
      
      if (target[idx] == null)
      {
         target[idx] = source.duplicate();
      }
      else
      {
         (target[idx]).assign(source);
      }
   }
   
   /**
    * Copies decimal precision from <code>source</code> to <code>destination</code> if 
    * decimal array is passed in. For non-decimal arrays, this method is effectively a no-op.
    * 
    * @param    source
    *           An array instance, the source of decimal precision.
    * @param    destination
    *           An array instance, the destination for decimal precision.
    * 
    * @throws   IllegalArgumentException
    *           When component types of the source and destination array are not equal.
    */
   private static void copyDecimalPrecision(BaseDataType[] source, BaseDataType[] destination)
   {
      Class<?> sourceComponentClass = source.getClass().getComponentType();
      Class<?> destinationComponentClass = destination.getClass().getComponentType();
      
      if (!sourceComponentClass.equals(destinationComponentClass))
      {
         throw new IllegalArgumentException("Source and destination component types must be equal.");
      }
      
      // only for decimal arrays
      if (decimal.class.isAssignableFrom(source.getClass().getComponentType()))
      {
         // copy the precision
         Integer precision = getDecimalPrecision((decimal[]) source);
         if (precision != null)
         {
            setDecimalPrecision((decimal[]) destination, precision);
         }
      }
   }
   
   /**
    * Copies case sensitivity from <code>source</code> to <code>destination</code> if 
    * text array is passed in. For non-text arrays, this method is effectively a no-op.
    * 
    * @param    source
    *           An array instance, the source of text case-sensitivity.
    * @param    destination
    *           An array instance, the destination for case-sensitivity.
    * 
    * @throws   IllegalArgumentException
    *           When component types of the source and destination array are not equal.
    */
   private static void copyCaseSensitive(BaseDataType[] source, BaseDataType[] destination)
   {
      Class<?> sourceComponentClass = source.getClass().getComponentType();
      Class<?> destinationComponentClass = destination.getClass().getComponentType();
      
      if (!sourceComponentClass.equals(destinationComponentClass))
      {
         throw new IllegalArgumentException("Source and destination component types must be equal.");
      }
      
      // only for Text arrays
      if (Text.class.isAssignableFrom(source.getClass().getComponentType()))
      {
         // copy the case sensitivity
         Boolean caseSens = getCaseSensitive((Text[]) source);
         if (caseSens != null)
         {
            setCaseSensitive((Text[]) destination, caseSens);
         }
      }
   }

   /** 
    * Stores global data relating to the state of the current context.
    */
   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 TransactionManager.TransactionHelper tm = TransactionManager.getTransactionHelper();

      /** Stack of dynamic-extent arrays, per each scope.*/
      private Deque<Set<BaseDataType[]>> dynamicArrays = new ArrayDeque<Set<BaseDataType[]>>();
      
      /** The dynamic arrays declared before the next top-level block will start, to be registered with it. */
      private Set<BaseDataType[]> pendingDynamicArrays = Collections.newSetFromMap(new IdentityHashMap<>());
      
      /** Stack of undoable dynamic-extent arrays, per each scope and each external program.*/
      private Map<Object, Deque<Map<BaseDataType[], Integer>>> dynamicUndoableArrays = new IdentityHashMap<>();

      /** Map of decimal dynamic array precisions. */
      private Map<BaseDataType[], Integer> dynamicArrayPrecisions = new IdentityHashMap<>();

      /** Map of text dynamic array case-sensitive. */
      private Map<BaseDataType[], Boolean> dynamicArrayCaseSens = new IdentityHashMap<>();

      /** The extent arguments to be passed to the next top-level block, to be registered with it. */
      private Map<BaseDataType[], AbstractExtentParameter<?>> pendingExtentParameters = new IdentityHashMap<>();
      
      /** Map of extent parameter instances */
      private Deque<Map<BaseDataType[], AbstractExtentParameter<?>>> extentParameters = new ArrayDeque<>();
      
      /** 
       * Mapping of the dynamic extent underlying 4GL class type - required when setting the
       * size of this array. 
       */
      private Map<object<? extends _BaseObject_>[], Class<? extends _BaseObject_>> 
      dynamicArrayType = new IdentityHashMap<>();
      
      /** Map of dynamic arrays created in a persistent program. */
      private Map<Object, Set<BaseDataType[]>> persistentProcDynArrays = new IdentityHashMap<>();
      
      public WorkArea()
      {
         // add a scope for the 'global' block where all the persistent procedure arrays are kept
         dynamicArrays.push(Collections.newSetFromMap(new IdentityHashMap<>()));
      }
      
      @Override
      public void scopeStart(BlockDefinition block)
      {
         if (!pendingDynamicArrays.isEmpty())
         {
            Set<BaseDataType[]> arrs = Collections.newSetFromMap(new IdentityHashMap<>());
            dynamicArrays.push(arrs);
            arrs.addAll(pendingDynamicArrays);
            pendingDynamicArrays.clear();
         }
         else
         {
            dynamicArrays.push(Collections.emptySet());
         }
         if (!pendingExtentParameters.isEmpty())
         {
            Map<BaseDataType[], AbstractExtentParameter<?>> exts = new IdentityHashMap<>();
            extentParameters.push(exts);
            exts.putAll(pendingExtentParameters);
            pendingExtentParameters.clear();
         }
         else
         {
            extentParameters.push(Collections.emptyMap());
         }
         Object referent = pm._thisProcedure();
         Deque<Map<BaseDataType[], Integer>> undoableExtStack = dynamicUndoableArrays.get(referent);
         if (undoableExtStack == null)
         {
            dynamicUndoableArrays.put(referent, undoableExtStack = new ArrayDeque<>());
         }
         undoableExtStack.push(Collections.emptyMap());
      }

      @Override
      public void scopeDeleted()
      {
         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;
         }

         dynamicUndoableArrays.remove(referent);
         
         Set<BaseDataType[]> refs = persistentProcDynArrays.remove(referent);
         if (refs != null && !refs.isEmpty())
         {
            // cleanup after this persistent program - remove them from the global block and other collections
            dynamicArrays.getLast().removeAll(refs);
            dynamicArrayPrecisions.keySet().removeAll(refs);
            dynamicArrayCaseSens.keySet().removeAll(refs);
            dynamicArrayType.keySet().removeAll(refs);
         }
      }

      @Override
      public void scopeFinished()
      {
         Object referent = pm._thisProcedure();

         if (!dynamicArrays.isEmpty())
         {
            // remove scope-registered arrays
            Set<BaseDataType[]> arrays = dynamicArrays.pop();
          
            if (!arrays.isEmpty())
            {
               if (pm._isPersistentAndNotPendingDelete(referent) && tm.isExternalBlock())
               {
                  // all dynamic arrays in a persistent program must be saved in the global block
                  dynamicArrays.getLast().addAll(arrays);
                  persistentProcDynArrays.put(referent, arrays);
               }
               else
               {
                  // remove precisions and output extent param registrations for the arrays being removed
                  for (BaseDataType[] array : arrays)
                  {
                     dynamicArrayPrecisions.remove(array);
                     dynamicArrayCaseSens.remove(array);
                     dynamicArrayType.remove(array);
                  }
               }
            }
         }
         
         if (!extentParameters.isEmpty())
         {
            extentParameters.pop();
         }
         
         Deque<Map<BaseDataType[], Integer>> undoableExtStack = dynamicUndoableArrays.get(referent);
         if (undoableExtStack != null && undoableExtStack.size() > 1)
         {
            // the top-most scope (for the ext program) remains saved until the external program
            // gets deleted
            undoableExtStack.pop();
         }
      }
      
      /**
       * Get the {@link ScopeId} for the instance.
       * 
       * @return   {@link ScopeId#ARRAY_ASSIGNER}.
       */
      @Override
      public ScopeId getScopeId()
      {
         return ScopeId.ARRAY_ASSIGNER;
      }
      
      /**
       * Returns <code>true</code> if the supplied array argument has been
       * previously registered, <code>false</code> otherwise.
       * 
       * @param    array
       *           A valid array instance.
       *        
       * @return   See above.
       */
      public boolean isRegistered(BaseDataType[] array)
      {
          for (Set<BaseDataType[]> scopeArrays : dynamicArrays)
          {
             if (scopeArrays.contains(array))
             {
                return true;
             }
          }
          
          return false;
      }
      
      /**
       * Register the given <code>array</code> as a dynamic-extent array. If the array is already
       * registered the method doesn't register it again and simply returns.
       * <p>
       * The registration of this array will be done in the current block - the API is executed only by 
       * legacy APIs which are executed within the top-level block.
       * 
       * @param    array
       *           The dynamic-extent array reference, must not be <code>null</code>.
       */
      public void register(BaseDataType[] array)
      {
         register(array, false);
      }
      
      /**
       * Register the given <code>array</code> as a dynamic-extent array. If the array is already
       * registered the method doesn't register it again and simply returns.
       * 
       * @param    array
       *           The dynamic-extent array reference, must not be <code>null</code>.
       * @param    pending
       *           Flag indicating if the copy is pending (as the call is made before the top-level block starts).
       */
      public void register(BaseDataType[] array, boolean pending)
      {
         // register only to one scope
         if (!isRegistered(array))
         {
            if (pending)
            {
               tm.registerPendingScopeable(this);
               
               pendingDynamicArrays.add(array);
            }
            else
            {
               Set<BaseDataType[]> arrs = dynamicArrays.peek();
               if (arrs == Collections.EMPTY_SET)
               {
                  dynamicArrays.pop();
                  dynamicArrays.push(arrs = Collections.newSetFromMap(new IdentityHashMap<>()));
               }
               arrs.add(array);
            }
         }
      }
      
      /**
       * Register the set of given arrays as dynamic extent.
       * 
       * @param    arrays
       *           The set of dynamic arrays to register.
       * @param    transLevel
       *           The transaction nesting level where this instance was created.
       */
      public void registerDynamicUndoable(Set<BaseDataType[]> arrays, int transLevel)
      {
         tm.registerBlockScopeable(this);
         
         Object referent = pm._thisProcedure();
         Deque<Map<BaseDataType[], Integer>> undoableExts = dynamicUndoableArrays.get(referent);
         Map<BaseDataType[], Integer> refs = undoableExts.peek();
         if (refs == (Map) Collections.emptyMap())
         {
            refs = new IdentityHashMap<>();
            undoableExts.pop();
            undoableExts.push(refs);
         }
         for (BaseDataType[] array : arrays)
         {
            refs.put(array, transLevel);
         }
      }

      /**
       * Deregisters <code>oldArray</code> and registers <code>newArray</code> into the scope
       * previously occupied by <code>oldArray</code>.
       * 
       * @param    oldArray
       *           A valid array instance.
       * @param    newArray
       *           A valid array instance.
       * @param    wa
       *           The {@link WorkArea} instance.
       */
      public void reRegister(BaseDataType[] oldArray, BaseDataType[] newArray, WorkArea wa)
      {
         // go through all scopes
         for (Set<BaseDataType[]> scopeArrays : dynamicArrays)
         {
            if (scopeArrays.remove(oldArray))
            {
                // oldArray exists in the scope, replace with newArray
                scopeArrays.add(newArray);
                
                // arrays are registered only in the first-level scope
                break;
            }
         }
         
         if (AbstractParameter.isExtentParameter(oldArray))
         {
            AbstractParameter.replaceParameter(oldArray, newArray);
         }
         
         for (Set<BaseDataType[]> thisRefs : wa.persistentProcDynArrays.values())
         {
            if (thisRefs != null && thisRefs.remove(oldArray))
            {
               thisRefs.add(newArray);
            }
         }
      }
      
      /**
       * Retrieve the decimal precision of the supplied array or <code>null</code> if precision
       * was not previously set for the array argument.
       * 
       * @param    array
       *           Array representing an extent variable.
       * 
       * @return   The decimal precision value or <code>null</code>, see above.
       */
      public Integer getDecimalPrecision(decimal[] array)
      {
         return dynamicArrayPrecisions.get(array);
      }
      
      /**
       * Retrieve the case sensitivity of the supplied array or <code>null</code> if case sens
       * was not previously set for the array argument.
       * 
       * @param    array
       *           Array representing an extent variable.
       * 
       * @return   The case sensitivity value or <code>null</code>, see above.
       */
      public Boolean getCaseSensitive(Text[] array)
      {
         return dynamicArrayCaseSens.get(array);
      }
      
      /**
       * Sets decimal precision for the array argument.
       * 
       * @param    array
       *           The array to be set with the supplied precision.
       * @param    precision
       *           Decimal precision to be set for the supplied array.
       */
      public void setDecimalPrecision(decimal[] array, int precision)
      {
         // make sure the array is registered, so it's properly cleaned up
         // on scope unrolling
         // it's actually a valid case precision being set before registration
         register(array);
         dynamicArrayPrecisions.put(array, precision);
      }
      
      /**
       * Sets case sensitivity for the array argument.
       * 
       * @param    array
       *           The array to be set with the supplied case sensitivity.
       * @param    caseSens
       *           Case sensitivity to be set for the supplied array.
       */
      public void setCaseSensitive(Text[] array, boolean caseSens)
      {
         // make sure the array is registered, so it's properly cleaned up
         // on scope unrolling
         // it's actually a valid case precision being set before registration
         register(array);
         dynamicArrayCaseSens.put(array, caseSens);
      }
      
      /**
       * Registers an unfixed indeterminate extent parameter instance into 
       * the current scope. The registered instance will be automatically unregistered 
       * when the scope is finished. The method will use the return value of
       * {@link AbstractExtentParameter#getParameter()} as the registration key.
       * To get the registered instance back, use that reference in the call to
       * {@link #getExtentParameter(BaseDataType[])}. 
       *  
       * @param    extentParameter
       *           An extent parameter instance. 
       */
      public <T extends BaseDataType> void registerExtentParameter(AbstractExtentParameter<T> extentParameter)
      {
         T[] parameter = extentParameter.getParameter();
         pendingExtentParameters.put(parameter, extentParameter);
      }
      
      /**
       * Returns a previously registered extent parameter instance, 
       * see {@link #registerExtentParameter(AbstractExtentParameter)} for more info.
       * 
       * @param    array
       *           A valid dynamic array instance.
       * 
       * @return   An extent parameter instance.
       */
      public <T extends BaseDataType> AbstractExtentParameter<T> getExtentParameter(BaseDataType[] array)
      {
         // go through all scopes
         for (Map<BaseDataType[], AbstractExtentParameter<?>> scopeParams : extentParameters)
         {
            @SuppressWarnings("unchecked")
            AbstractExtentParameter<T> extentParam = (AbstractExtentParameter<T>) scopeParams.get(array);
            if (extentParam != null)
            {
               return extentParam;
            }
         }
         
         return null;
      }
   }
}