SharedVariableManager.java

/*
** Module   : SharedVariableManager.java
** Abstract : provides context-local scoped resource pools.
**
** Copyright (c) 2004-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------Description----------------------------
** 001 GES 20050729   @21924 Provides context-local scoped variable and stream pools.
** 002 GES 20050808   @22069 Moved ContextLocal inner class to a standalone class to promote
**                           reuse.
** 003 GES 20050818   @22163 Implemented the Scopeable interface to allow this class to be
**                           automatically notified of all scope start and end occurrences. This
**                           removes the need for client code to be instrumented to provide this
**                           service. Use of this class is now tied to the TransactionManager.
** 004 ECF 20050920   @22887 Implement ScopeableFactory interface. Allows generic access to
**                           singleton instance of this class from TransactionManager.
** 005 GES 20051004   @22930 Added support for shared buffers and error manager processing.
** 006 GES 20051101   @23163 Added support for shared frames.
** 007 GES 20060123   @24014 Converted from thread local to context local, moved initialization
**                           code to be based on the standard directory-based server
**                           initialization routines.
** 008 GES 20061010   @30262 Removed global option from frame support since this is not allowed
**                           in Progress.
** 009 ECF 20070328   @32652 Added shared temp-table support. Added context container for temp
**                           tables and add/lookup methods for temp tables.
** 010 SIY 20070711   @34430 Changed addFrame() to return existing frame if one is present in
**                           storage.
** 011 GES 20080822   @39520 Slight modification proposed by SVL to allow the currently
**                           registered global var of the same name to be returned as a result
**                           of the addVariable() method.
** 012 SIY 20080904   @39718 Inferred generics.
** 013 GES 20080918   @39857 Major rewrite to handle scopes differently. Now each resource
**                           supports some or all of the following: CURRENT, NEXT, GLOBAL. NEXT
**                           is the new feature that allows a resource add to wait until the next
**                           scope is opened before being added to the dictionary. Add'l
**                           signatures were added to allow a default scope that differs by
**                           resource type.
** 014 GES 20090422   @41914 Converted to standard string formatting.
** 015 CA  20131013          Added no-op scopeDeleted() method, required by the changes in
**                           Scopeable interface.
** 016 OM  20141203          Added double-initialization check for NEW SHARED GLOBAL EXTENT-s.
** 017 VIG 20141215          Added methods for shared menus support.
** 018 OM  20150903          Improved error handling in the case of missing shared temp-tables.
** 019 CA  20151027          When running an internal entry (procedure, function) of a persistent 
**                           procedure, all its shared data (variables, tables, etc) needs to be  
**                           pushed and popped, so that the internal entry (and any other external
**                           program ran from it) has access to it.
** 020 CA  20151222          Refactored registration of global shared resources: first, check 
**                           previous scopes if a resource with the same name exists; if so, use
**                           that; otherwise, create a new resource and register it.  
**                           Fixed error messages when the shared resource can no be found.
**                           When matching a shared resource with an existing one, check if it is
**                           the same (i.e. variables have the same data-type/extent/undo, buffers
**                           are for the same table, etc).
** 021 CA  20160420          Fixed H020 - defined global vars are checked by name in all scopes -
**                           if one is found (regardless if global or not), that is matched and
**                           returned, if matched.
** 022 CA  20160721          Shared vars are registered in the next scope, if they are not defined
**                           as NO-UNDO.
**     CA  20160728          Refactored the shared registries; the var-related registries are 
**                           exposed as copies for each external program, with the global scope
**                           shared among all programs. 
** 023 CA  20160812          Performance improvements for H022: use IdentityHashMap when using
**                           ext prog instances as keys, var ref count is kept via a mutable
**                           AtomicInteger instead of a referent set.
** 024 GES 20160914          Force case-insensitive mode for the scoped dictionaries.
** 025 GES 20171220          Added TODO comment.
** 026 CA  20171202          Fixed shared variable lookup - search in the entire stack of 
**                           procedures, not just in the context of THIS-PROCEDURE.
**                           Fixed a memory leak for cleaning up the shared state of a persistent
**                           procedure.
**     CA  20171219          Temp-table, buffer and menu dictionaries must be case-sensitive, as  
**                           the keys are java names, and not legacy names.
**     CA  20171220          The BUFFER, TEMP-TABLE and STREAM resources all behave the same as 
**                           the VARIABLE resources - the lookup is performed using the state of
**                           this-procedure and if not found, go back in the stack and look in 
**                           each procedure's state.
** 027 GES 20180102          Changed a comment.
** 028 EVL 20190128          Changed the approach to keep NO-UNDO option and check the variable
**                           against it.  Now all variables are storing in no-undo map with true
**                           or false value for option.
** 029 CA  20190122          Fixed shared object vars support.
**     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.
** 030 SVL 20190801          Added lookupTempTable(legacyName).
** 031 SVL 20200107          Support for shared queries.
** 032 CA  20200122          Javadoc fixes.
** 033 CA  20200930          Refactored to remove the singleton scopeable instance (which forced the scope 
**                           notifications to use context-local state).
**     CA  20201003          bufferCreator can be an IdentityHashMap, as the key is an external program 
**                           instance.
**     CA  20210508          Child shared buffers or temp-tables act like REFERENCE-ONLY, where they are bound
**                           to the master buffer/temp-table.
**     CA  20210626          Refactored shared resource lookup - in case of non-new shared lookup, the lookup
**                           will always be performed on the stack.  If there was a hit on the stack, then
**                           the initilized external program's registry will be updated with this shared 
**                           resource.
**                           Also, the lookup is always done on the stack first and on the global registry
**                           otherwise.  Previous point of keeping non-new shared resources in the registry
**                           ensures that the lookup will find the resource if there is a program on the stack
**                           referencing it.
**     CA  20211222          Fixed memory leak: shared frames and menus can't exist in a persistent procedure,
**                           so do not process them on scope start.  scopeDeleted() must ensure it is
**                           processing the external block of a non-persistent procedure.
**     CA  20220105          Fix for CA/20211222 - scopeDeleted() must explicitly check if the procedure is
**                           not persistent.
**     OM  20220115          APIs for queries accept a list of static BROWSE names for detecting errors.
**     CA  20220206          In scopeDeleted, use the ProcedureHelper instance to avoid context-local lookup.
**     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  20220704          Unreferenced non-NEW SHARED variables are marked as such, so there will be no 
**                           error raised if the lookup fails - this is required, as unreferenced shared 
**                           variables need to exist on the stack, if the lookup passed.  But if they are
**                           unreferenced, no error should be raised.
**     CA  20221010          Performance improvements - avoid the ProcedureData lookup, by keeping a parallel 
**                           stack of this data for THIS-PROCEDURE.  Refs #6826
** 034 CA  20230313          Return an actual BDT instance when a shared var is never referenced in the 
**                           current program, and also does not exist on the stack as 'new shared'.
** 035 RAA 20231009          Arrays with dynamic extent are now registered in addVariable.
** 036 CA  20230908          Fixed 'lookupVariableUnreferenced' to return an array in case of extent vars.
** 037 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'.
** 038 CA  20231129          Use method references and real singletons instead of lambdas, where possible.
** 039 EAB 20240611          Changed the parameter types used to call DmoMetadataManager.isSchemaMatch. 
*/

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

package com.goldencode.p2j.util;

import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.function.*;

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

/**
 * Provides context-local scoped variable, stream, buffer, frame and temp
 * table pools.
 * <p>
 * All references can be added to the top scope (closest enclosing scope) or
 * to the global scope.  Lookups return the topmost reference by the given
 * name or <code>null</code> if no reference by that name exists.  The backing
 * function is provided by the {@link ScopedSymbolDictionary} and this class
 * exposes the necessary helpers as static methods which de-reference the
 * pool that is localized to the context that is specific to the current
 * authenticated user.  Please see <code>SecurityManager</code> for more
 * details on this facility.
 * <p>
 * The resource dictionaries (pools) are separate since they require
 * different namespaces.  Where possible, the returned reference is of
 * a specific type, but at this time for all non-stream and non-temp table
 * resources, only <code>Object</code> references can be returned.
 * <p>
 * Buffers are persistent object representations of relational database
 * records.
 * <p>
 * Temp tables are actually buffers which point to temp tables, but this is
 * simply an implementation detail.  They represent shared temp table
 * definitions, as opposed to shared buffers.  They are managed separate from
 * regular buffers to ensure a separate namespace is available.
 * <p>
 * Frames are object representations of a screen buffer that is specific to
 * a given user-defined visual layout.
 * <p>
 * Implements the {@link Scopeable} interface to allow this class to be
 * automatically notified of all scope start and end occurrences. This removes
 * the need for client code to be instrumented to provide this service. The
 * use of this class is now tied to the {@link TransactionManager} class. In
 * particular, the singleton instance of this class is used in each context-
 * local list of {@link Scopeable} objects as a dispatcher. This bit
 * of nastiness is necessary since the Java Interface can't be implemented
 * on a static basis but instead requires an instance method implementation.
 * These instance methods then call the static counterparts which is where
 * the content-local versions of the resource pools are accessed.
 */
public class SharedVariableManager
implements Scopeable
{
   /** Context-local state related to shared variables. */
   private static ContextLocal<SharedVariableManager> local = new ContextLocal<SharedVariableManager>()
   {
      protected SharedVariableManager initialValue()
      {
         return new SharedVariableManager();
      };
   };
   
   /** Error used by {@link #lookupVariable}. */
   private static final Consumer<String> ERROR_VAR = (vname) ->
   {
      String pname = ProcedureManager.getInstantiatingExternalProgram();
      String msg = "%s Shared variable %s has not yet been created";
      msg = String.format(msg, pname, vname);

      errorHelper(392, msg);
   };
   
   /** Error used by {@link #lookupStream}. */
   private static final Consumer<String> ERROR_STREAM = (vname) ->
   {
      String pname = ProcedureManager.getInstantiatingExternalProgram();
      String msg = "%s Shared stream %s not defined yet";
      msg = String.format(msg, pname, vname);

      errorHelper(567, msg);
   };
   
   /** Error used by {@link #lookupQuery}. */
   private static final Consumer<String> ERROR_QUERY = (vname) -> 
   {
      errorHelper(3255, "Failure to find shared query " + vname);
   };
   
   /** Error used by {@link #lookupBuffer}. */
   private static final Consumer<String> ERROR_BUFFER = (vname) ->
   {
      String pname = ProcedureManager.getInstantiatingExternalProgram();
      String msg = "%s Unable to find shared buffer for %s";
      msg = String.format(msg, pname, vname);

      errorHelper(566, msg);
   };
   
   /** Error used by {@link #lookupTempTable}. */
   private static final Consumer<String> ERROR_TT = (vname) ->
   {
      String pname = ProcedureManager.getInstantiatingExternalProgram();
      String msg = "Unable to locate shared temp-table or work-table definition " + 
                   "for table %s in procedure %s";
      msg = String.format(msg, vname, pname);

      errorHelper(1429, msg);
   };

   /** Error used by {@link #lookupFrame}. */
   private static final Consumer<String> ERROR_FRAME = (vname) ->
   {
      String pname = ProcedureManager.getInstantiatingExternalProgram();
      String msg = "%s Shared frame %s not created yet";
      msg = String.format(msg, pname, vname);

      errorHelper(682, msg);
   };
   
   /** The instance state. */
   private final WorkArea wa = new WorkArea();
   
   /**
    * Adds a variable to the next scope that is opened.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    var
    *           The variable reference.
    *
    * @return   The <code>var</code> instance given if it is successfully
    *           added, otherwise the currently registered global instance
    *           with the same name.
    */
   public static <T> T addVariable(String name, T var)
   {
      return addVariable(ScopeLevel.NEXT, name, false, var, null);
   }
   
   /**
    * Adds a variable to the next scope that is opened.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    var
    *           The variable reference.
    * @param    init
    *           If non null, then var is assumed to be an extent variable, and this represents
    *           the default value for the variable's elements.
    *           
    * @return   The <code>var</code> instance given if it is successfully
    *           added, otherwise the currently registered global instance
    *           with the same name.
    */
   public static <T> T addVariable(String name, T var, Supplier<Object> init)
   {
      return addVariable(ScopeLevel.NEXT, name, false, var, init);
   }
   
   /**
    * Adds a variable to the specified scope.
    * <p>
    * If the next scope is specified, the instance will be placed in the list
    * of deferred instances that will be added the next time a scope is
    * opened.
    * <p>
    * If global is specified, the instance will be added to the bottom-most
    * scope on the stack.
    * <p>
    * If current is specified, the instance will be added to the scope that
    * is on the top of the stack. If there is only one scope on the stack,
    * the global and current scope will be the same scope.
    * <p>
    * Any attempt to add second global variable of the same name, will cause
    * the passed-in instance to be ignored (the currently registered instance
    * will not be replaced).  In addition, the currently registered instance
    * will be returned.
    *
    * @param    <T> 
    *           Variable type.
    * @param    scope
    *           Specifies the target scope.
    * @param    name
    *           The variable name.
    * @param    var
    *           The variable reference.
    *
    * @return   The <code>var</code> instance given if it is successfully
    *           added, otherwise the currently registered global instance
    *           with the same name.
    */
   public static <T> T addVariable(ScopeLevel scope, String name, T var)
   {
      return addVariable(scope, name, false, var, null);
   }
   
   
   /**
    * Adds a variable to the next scope that is opened.
    *
    * @param    <T> 
    *           Variable type.
    * @param    noUndo
    *           Flag indicating if the variable was defined as NO-UNDO.
    * @param    name
    *           The variable name.
    * @param    var
    *           The variable reference.
    *
    * @return   The <code>var</code> instance given if it is successfully
    *           added, otherwise the currently registered global instance
    *           with the same name.
    */
   public static <T> T addVariable(String name, boolean noUndo, T var)
   {
      return addVariable(ScopeLevel.NEXT, name, noUndo, var, null);
   }
   
   /**
    * Adds a variable to the next scope that is opened.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    noUndo
    *           Flag indicating if the variable was defined as NO-UNDO.
    * @param    var
    *           The variable reference.
    * @param    init
    *           If non null, then var is assumed to be an extent variable, and this represents
    *           the default value for the variable's elements.
    *           
    * @return   The <code>var</code> instance given if it is successfully
    *           added, otherwise the currently registered global instance
    *           with the same name.
    */
   public static <T> T addVariable(String name, boolean noUndo, T var, Supplier<Object> init)
   {
      return addVariable(ScopeLevel.NEXT, name, noUndo, var, init);
   }
   
   /**
    * Adds a variable to the specified scope.
    * <p>
    * If the next scope is specified, the instance will be placed in the list
    * of deferred instances that will be added the next time a scope is
    * opened.
    * <p>
    * If global is specified, the instance will be added to the bottom-most
    * scope on the stack.
    * <p>
    * If current is specified, the instance will be added to the scope that
    * is on the top of the stack. If there is only one scope on the stack,
    * the global and current scope will be the same scope.
    * <p>
    * Any attempt to add second global variable of the same name, will cause
    * the passed-in instance to be ignored (the currently registered instance
    * will not be replaced).  In addition, the currently registered instance
    * will be returned.
    *
    * @param    <T> 
    *           Variable type.
    * @param    scope
    *           Specifies the target scope.
    * @param    name
    *           The variable name.
    * @param    noUndo
    *           Flag indicating if the variable was defined as NO-UNDO.
    * @param    var
    *           The variable reference.
    *
    * @return   The <code>var</code> instance given if it is successfully
    *           added, otherwise the currently registered global instance
    *           with the same name.
    */
   public static <T> T addVariable(ScopeLevel scope, String name, boolean noUndo, T var)
   {
      return addVariable(scope, name, noUndo, var, null);
   }

   /**
    * Adds a variable to the specified scope.
    * <p>
    * If the next scope is specified, the instance will be placed in the list
    * of deferred instances that will be added the next time a scope is
    * opened.
    * <p>
    * If global is specified, the instance will be added to the bottom-most
    * scope on the stack.
    * <p>
    * If current is specified, the instance will be added to the scope that
    * is on the top of the stack. If there is only one scope on the stack,
    * the global and current scope will be the same scope.
    * <p>
    * Any attempt to add second global variable of the same name, will cause
    * the passed-in instance to be ignored (the currently registered instance
    * will not be replaced).  In addition, the currently registered instance
    * will be returned.
    *
    * @param    <T> 
    *           Variable type.
    * @param    scope
    *           Specifies the target scope.
    * @param    name
    *           The variable name.
    * @param    var
    *           The variable reference.
    * @param    init
    *           If non null, then var is assumed to be an extent variable, and this represents
    *           the default value for the variable's elements.
    *
    * @return   The <code>var</code> instance given if it is successfully
    *           added, otherwise the currently registered global instance
    *           with the same name.
    */
   public static <T> T addVariable(ScopeLevel scope, String name, T var, Supplier<Object> init)
   {
      return addVariable(scope, name, false, var, init);
   }
   
   /**
    * Adds a variable to the specified scope.
    * <p>
    * If the next scope is specified, the instance will be placed in the list
    * of deferred instances that will be added the next time a scope is
    * opened.
    * <p>
    * If global is specified, the instance will be added to the bottom-most
    * scope on the stack.
    * <p>
    * If current is specified, the instance will be added to the scope that
    * is on the top of the stack. If there is only one scope on the stack,
    * the global and current scope will be the same scope.
    * <p>
    * Any attempt to add second global variable of the same name, will cause
    * the passed-in instance to be ignored (the currently registered instance
    * will not be replaced).  In addition, the currently registered instance
    * will be returned.
    *
    * @param    <T> 
    *           Variable type.
    * @param    scope
    *           Specifies the target scope.
    * @param    name
    *           The variable name.
    * @param    noUndo
    *           Flag indicating if the variable was defined as NO-UNDO.
    * @param    var
    *           The variable reference.
    * @param    init
    *           If non null, then var is assumed to be an extent variable, and this represents
    *           the default value for the variable's elements.
    *
    * @return   The <code>var</code> instance given if it is successfully
    *           added, otherwise the currently registered global instance
    *           with the same name.
    */
   public static <T> T addVariable(ScopeLevel        scope, 
                                   String            name, 
                                   boolean           noUndo,
                                   T                 var, 
                                   Supplier<Object>  init)
   {
      WorkArea wa = local.get().wa;
      
      VarRegistry vr = wa.vars.getRegistry(scope, VarRegistry::new);
      
      SharedRegistry sr = vr.objects;
      boolean extent = var.getClass().isArray();
      boolean global = (scope == ScopeLevel.GLOBAL);
      
      if (global)
      {
         // when searching for a match, the search is not done only in the global scope. 
         // a DEFINE NEW SHARED can hide a previous DEFINE NEW GLOBAL SHARED, and a subsequent
         // DEFINE NEW GLOBAL SHARED will resolve to the DEFINE NEW SHARED var, not the global one
         VarRegistry reg = wa.vars.lookupRegistry(name);
         Object current = reg == null ? null : lookupWorker(reg.objects, name, null);

         // ignore if there is a previous shared definition (global or not)
         if (current != null)
         {
            checkSharedVariable(name, var, noUndo);
            if (current instanceof object)
            {
               checkObjectType(name, current, var);
            }
            
            return (T) current;
         }
      }

      addWorker(sr, scope, name, var);
      
      // the no-undo map now keeps the option value for every variable
      addWorker(vr.noUndoVars, scope, name, noUndo);
      
      if (var instanceof object[])
      {
         // extent shared vars are always initialized via TypeFactory; also, no implicit 
         // initialization can be set for the extent vars

         // save the original var, when is first defined.  this will help later on, when the
         // variable is reused via DEFINE NEW GLOBAL SHARED, to check if the extent matches
         addWorker(vr.extentVars, scope, name, var);
      }
      else if (extent)
      {
         BaseDataType[] bdvar = (BaseDataType[]) var;

         // assertion check - always pass an uninitialized array...
         for (int i = 0; i < bdvar.length; i++)
         {
            if (bdvar[i] != null)
            {
               throw new IllegalArgumentException(
                 "The array index " + i + " is already set for shared extsent var " + name + "!");
            }
         }
         
         // save the original var, when is first defined.  this will help later on, when the
         // variable is reused via DEFINE NEW GLOBAL SHARED, to check if the extent matches
         addWorker(vr.extentVars, scope, name, var);
         
         Object vinit = (init == null ? null : init.get());
         
         if (vinit == null)
         {
            BaseDataType.initializeDefaultExtent(bdvar);
         }
         else
         {
            if (vinit instanceof BaseDataType[])
            {
               BaseDataType[] bdinit = (BaseDataType[]) vinit;
               if (bdvar.length == 0)
               {
                  // resize it
                  bdvar = ArrayAssigner.resize(bdvar, bdinit.length);
                  var = (T) bdvar;
               }
               
               if (bdinit.length == bdvar.length)
               {
                  ArrayAssigner.assignMulti(bdvar, bdinit);
               }
               else if (bdinit.length < bdvar.length)
               {
                  for (int i = 0; i < bdinit.length; i++)
                  {
                     if (bdvar[i] == null)
                     {
                        bdvar[i] = bdinit[i].duplicate();
                     }
                     else
                     {
                        bdvar[i].assign(bdinit[i]);
                     }
                  }
                  
                  if (bdinit.length > 0)
                  {
                     ArrayAssigner.assignMulti(bdvar, bdinit.length, bdinit[bdinit.length - 1]);
                  }
               }
            }
            else if (init instanceof BaseDataType)
            {
               if (bdvar.length == 0)
               {
                  // resize it
                  bdvar = ArrayAssigner.resize(bdvar, 1);
                  var = (T) bdvar;
               }
               
               ArrayAssigner.assignMulti((BaseDataType[]) var, (BaseDataType) init);
            }
            else
            {
               throw new RuntimeException(
                  "Extent GLOBAL SHARED initializer must be a BaseDataType - received a " +
                  init.getClass()
               );
            }
         }
      }
      
      if (!noUndo)
      {
         if (extent)
         {
            TransactionManager.registerAt(scope, (LazyUndoable[]) var);
         }
         else
         {
            TransactionManager.registerAt(scope, (LazyUndoable) var);
         }
      }
      
      if (extent && Array.getLength(var) == 0)
      {
         ArrayAssigner.registerDynamicArray((BaseDataType[]) var);
      }
      
      return var;
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    *
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    *
    * @throws   ErrorConditionException
    *           When the variable cannot be found or it does not match the master.
    */
   public static <T> T lookupVariable(String name, Class<? extends BaseDataType> expected)
   throws ErrorConditionException
   {
      return lookupVariable(name, expected, null, null, false);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    type
    *           In case of {@link object} variables, this var's underlying type.
    *
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    *
    * @throws   ErrorConditionException
    *           When the variable cannot be found or it does not match the master.
    */
   public static <T> T lookupVariable(String                        name, 
                                      Class<? extends BaseDataType> expected, 
                                      Class<? extends _BaseObject_> type)
   throws ErrorConditionException
   {
      return lookupVariable(name, expected, type, null, false);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    noUndo
    *           Flag indicating if the variable was defined as NO-UNDO.
    *           
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    *
    * @throws   ErrorConditionException
    *           When the variable cannot be found or it does not match the master.
    */
   public static <T> T lookupVariable(String                        name, 
                                      Class<? extends BaseDataType> expected, 
                                      boolean                       noUndo)
   throws ErrorConditionException
   {
      return lookupVariable(name, expected, null, null, noUndo);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    type
    *           In case of {@link object} variables, this var's underlying type.
    * @param    noUndo
    *           Flag indicating if the variable was defined as NO-UNDO.
    *           
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    *
    * @throws   ErrorConditionException
    *           When the variable cannot be found or it does not match the master.
    */
   public static <T> T lookupVariable(String                        name, 
                                      Class<? extends BaseDataType> expected, 
                                      Class<? extends _BaseObject_> type,
                                      boolean                       noUndo)
   throws ErrorConditionException
   {
      return lookupVariable(name, expected, type, null, noUndo);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    extent
    *           The found var must be an extent var of the specified extent.
    *           
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    *
    * @throws   ErrorConditionException
    *           When the variable cannot be found or it does not match the master.
    */
   public static <T> T lookupVariable(String                        name, 
                                      Class<? extends BaseDataType> expected, 
                                      Integer                       extent)
   throws ErrorConditionException
   {
      return lookupVariable(name, expected, extent, false);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    type
    *           In case of {@link object} variables, this var's underlying type.
    * @param    extent
    *           The found var must be an extent var of the specified extent.
    *           
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    *
    * @throws   ErrorConditionException
    *           When the variable cannot be found or it does not match the master.
    */
   public static <T> T lookupVariable(String                        name, 
                                      Class<? extends BaseDataType> expected,
                                      Class<? extends _BaseObject_> type,
                                      Integer                       extent)
   throws ErrorConditionException
   {
      return lookupVariable(name, expected, type, extent, false);
   }
   
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    extent
    *           The found var must be an extent var of the specified extent.
    * @param    noUndo
    *           Flag indicating if the variable was defined as NO-UNDO.
    *           
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    *
    * @throws   ErrorConditionException
    *           When the variable cannot be found or it does not match the master.
    */
   public static <T> T lookupVariable(String                        name,
                                      Class<? extends BaseDataType> expected, 
                                      Integer                       extent, 
                                      boolean                       noUndo)
   throws ErrorConditionException
   {
      return lookupVariable(name, expected, null, extent, noUndo);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    type
    *           In case of {@link object} variables, this var's underlying type.
    * @param    extent
    *           The found var must be an extent var of the specified extent.
    * @param    noUndo
    *           Flag indicating if the variable was defined as NO-UNDO.
    *           
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    *
    * @throws   ErrorConditionException
    *           When the variable cannot be found or it does not match the master.
    */
   public static <T> T lookupVariable(String                        name,
                                      Class<? extends BaseDataType> expected,
                                      Class<? extends _BaseObject_> type,
                                      Integer                       extent, 
                                      boolean                       noUndo)
   throws ErrorConditionException
   {
      WorkArea wa = local.get().wa;
      
      Object found = lookupWorker(wa.vars, name, ERROR_VAR);
      if (found == null)
      {
         // fail early if it is not found
         return null;
      }

      Object var = (extent != null ? Array.newInstance(expected, Math.max(0,  extent))
                                   : type != null ? new object(type) 
                                                  : BaseDataType.generateDefault(expected));

      checkSharedVariable(name, var, noUndo);
      
      if (type != null)
      {
         checkObjectType(name, (object) found, var);
      }
      
      VarRegistry vr = wa.vars.getRegistry(ScopeLevel.NEXT, VarRegistry::new);
      SharedRegistry sr = vr.objects;
      addWorker(sr, ScopeLevel.NEXT, name, found);
      addWorker(vr.noUndoVars, ScopeLevel.NEXT, name, noUndo);
      if (found.getClass().isArray())
      {
         addWorker(vr.extentVars, ScopeLevel.NEXT, name, found);
      }
      
      return (T) found;
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    * <p>
    * An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
    * lookup passed, the shared variable must exist on the stack.
    * 
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    *
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    */
   public static <T> T lookupVariableUnreferenced(String name, Class<? extends BaseDataType> expected)
   {
      return lookupVariableUnreferenced(name, expected, null, null, false);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    * <p>
    * An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
    * lookup passed, the shared variable must exist on the stack.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    type
    *           In case of {@link object} variables, this var's underlying type.
    *
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    */
   public static <T> T lookupVariableUnreferenced(String                        name, 
                                                  Class<? extends BaseDataType> expected, 
                                                  Class<? extends _BaseObject_> type)
   {
      return lookupVariableUnreferenced(name, expected, type, null, false);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    * <p>
    * An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
    * lookup passed, the shared variable must exist on the stack.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    noUndo
    *           Flag indicating if the variable was defined as NO-UNDO.
    *           
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    */
   public static <T> T lookupVariableUnreferenced(String                        name, 
                                                  Class<? extends BaseDataType> expected, 
                                                  boolean                       noUndo)
   {
      return lookupVariableUnreferenced(name, expected, null, null, noUndo);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    * <p>
    * An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
    * lookup passed, the shared variable must exist on the stack.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    type
    *           In case of {@link object} variables, this var's underlying type.
    * @param    noUndo
    *           Flag indicating if the variable was defined as NO-UNDO.
    *           
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    */
   public static <T> T lookupVariableUnreferenced(String                        name, 
                                                  Class<? extends BaseDataType> expected, 
                                                  Class<? extends _BaseObject_> type,
                                                  boolean                       noUndo)
   {
      return lookupVariableUnreferenced(name, expected, type, null, noUndo);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    * <p>
    * An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
    * lookup passed, the shared variable must exist on the stack.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    extent
    *           The found var must be an extent var of the specified extent.
    *           
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    */
   public static <T> T lookupVariableUnreferenced(String                        name, 
                                                  Class<? extends BaseDataType> expected, 
                                                  Integer                       extent)
   {
      return lookupVariableUnreferenced(name, expected, extent, false);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    * <p>
    * An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
    * lookup passed, the shared variable must exist on the stack.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    type
    *           In case of {@link object} variables, this var's underlying type.
    * @param    extent
    *           The found var must be an extent var of the specified extent.
    *           
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    */
   public static <T> T lookupVariableUnreferenced(String                        name, 
                                                  Class<? extends BaseDataType> expected,
                                                  Class<? extends _BaseObject_> type,
                                                  Integer                       extent)
   {
      return lookupVariableUnreferenced(name, expected, type, extent, false);
   }
   
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    * <p>
    * An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
    * lookup passed, the shared variable must exist on the stack.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    extent
    *           The found var must be an extent var of the specified extent.
    * @param    noUndo
    *           Flag indicating if the variable was defined as NO-UNDO.
    *           
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    */
   public static <T> T lookupVariableUnreferenced(String                        name,
                                                  Class<? extends BaseDataType> expected, 
                                                  Integer                       extent, 
                                                  boolean                       noUndo)
   {
      return lookupVariableUnreferenced(name, expected, null, extent, noUndo);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first variable with the given 
    * name.
    * <p>
    * An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
    * lookup passed, the shared variable must exist on the stack.
    *
    * @param    <T> 
    *           Variable type.
    * @param    name
    *           The variable name.
    * @param    expected
    *           The defined type of the slave variable (must match the master var type).
    * @param    type
    *           In case of {@link object} variables, this var's underlying type.
    * @param    extent
    *           The found var must be an extent var of the specified extent.
    * @param    noUndo
    *           Flag indicating if the variable was defined as NO-UNDO.
    *           
    * @return   The variable reference associated with the name or <code>null</code> if not found.
    */
   public static <T> T lookupVariableUnreferenced(String                        name,
                                                  Class<? extends BaseDataType> expected,
                                                  Class<? extends _BaseObject_> type,
                                                  Integer                       extent, 
                                                  boolean                       noUndo)
   {
      Object[] res = new Object[1];
      ErrorManager.nestedSilent(() -> 
      {
         try
         {
            res[0] = lookupVariable(name, expected, type, extent, noUndo);
         }
         catch (ErrorConditionException err)
         {
            if (extent != null)
            {
               res[0] = Array.newInstance(expected, Math.max(0,  extent));
            }
            else
            {
               res[0] = BaseDataType.generateDefault(expected);
            }
            throw err;
         }
      });
      
      return (T) res[0];
   }
   
   /**
    * Adds a stream to the {@link ScopeLevel#CURRENT} scope.
    *
    * @param    name
    *           The stream name.
    * @param    stream
    *           The stream reference.
    */
   public static void addStream(String name, Stream stream)
   {
      addStream(ScopeLevel.CURRENT, name, stream);
   }
   
   /**
    * Adds a stream to the specified scope.
    * <p>
    * If the next scope is specified, the instance will be placed in the list
    * of deferred instances that will be added the next time a scope is opened.
    * <p>
    * If global is specified, the instance will be added to the bottom-most
    * scope on the stack.
    * <p>
    * If current is specified, the instance will be added to the scope that
    * is on the top of the stack. If there is only one scope on the stack,
    * the global and current scope will be the same scope.
    *
    * @param    scope 
    *           Specifies the target scope.
    * @param    name
    *           The stream name.
    * @param    stream
    *           The stream reference.
    */
   public static void addStream(ScopeLevel scope, String name, Stream stream)
   {
      WorkArea wa = local.get().wa;
      PersistentRegistry vr = wa.streams.getRegistry(scope, PersistentRegistry::new);
      
      addWorker(vr.objects, scope, name, stream);

      // as this is not reached via the scopeStart, we do it here (but only for NEW streams)
      AtomicInteger count = (AtomicInteger) wa.streams.refs.get(stream);
      if (count == null)
      {
         wa.streams.refs.put(stream, count = new AtomicInteger(1));
      }
      else
      {
         count.incrementAndGet();
      }
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first
    * stream with the given name.
    *
    * @param    name
    *           The stream name.
    *           
    * @return   The stream reference associated with the name or
    *           <code>null</code> if not found.
    *
    * @throws   ErrorConditionException
    *           When the stream cannot be found and silent error mode is
    *           disabled.
    */
   public static Stream lookupStream(String name)
   throws ErrorConditionException
   {
      WorkArea wa = local.get().wa;

      Stream found = (Stream) lookupWorker(wa.streams, name, ERROR_STREAM);
      
      addStream(ScopeLevel.NEXT, name, found);
      
      return found;
   }
   
   /**
    * Adds a buffer to the {@link ScopeLevel#CURRENT} scope.
    *
    * @param    name
    *           The buffer name.
    * @param    buffer
    *           The buffer reference.
    */
   public static <T> void addBuffer(String name, T buffer)
   {
      addBuffer(ScopeLevel.CURRENT, name, buffer);
   }
   
   /**
    * Creates and adds a query to the {@link ScopeLevel#NEXT} scope.
    *
    * @param   name
    *          The query name.
    * @param   scrolling
    *          Defines if the query should be scrolling.
    *
    * @deprecated Use the overloaded method with lookupQuery(String, boolean, String...) signature instead.
    *
    * @return  The {@code QueryWrapper} created using the current arguments.
    */
   @Deprecated
   public static QueryWrapper addQuery(String name, boolean scrolling)
   {
      return addQuery(ScopeLevel.NEXT, name, scrolling);
   }
   
   /**
    * Creates and adds a query to the {@link ScopeLevel#NEXT} scope.
    *
    * @param   name
    *          The query name.
    * @param   scrolling
    *          Defines if the query should be scrolling.
    * @param   browseNames
    *          The legacy name of any BROWSEs widgets which use this query.
    *
    * @return  The {@code QueryWrapper} created using the current arguments.
    */
   public static QueryWrapper addQuery(String name, boolean scrolling, String... browseNames)
   {
      return addQuery(ScopeLevel.NEXT, name, scrolling, browseNames);
   }
   
   /**
    * Creates and adds a query to the specified scope.
    * <p>
    * If the next scope is specified, the instance will be placed in the list
    * of deferred instances that will be added the next time a scope is opened.
    * <p>
    * If global is specified, the instance will be added to the bottom-most
    * scope on the stack.
    * <p>
    * If current is specified, the instance will be added to the scope that
    * is on the top of the stack. If there is only one scope on the stack,
    * the global and current scope will be the same scope.
    *
    * @param   scope
    *          Specifies the target scope.
    * @param   name
    *          The query name.
    * @param   scrolling
    *          Defines if the query should be scrolling.
    * @param   brwNames
    *          The legacy name of any BROWSEs widgets which use this query.
    *
    * @return  The {@code QueryWrapper} created using the current arguments.
    */
   public static QueryWrapper addQuery(ScopeLevel scope, String name, boolean scrolling, String... brwNames)
   {
      QueryWrapper query = new QueryWrapper(name, scrolling, brwNames);
      
      addQuery(scope, name, query);
      
      return query;
   }
   
   /**
    * Creates and adds a query to the specified scope.
    * <p>
    * If the next scope is specified, the instance will be placed in the list
    * of deferred instances that will be added the next time a scope is opened.
    * <p>
    * If global is specified, the instance will be added to the bottom-most
    * scope on the stack.
    * <p>
    * If current is specified, the instance will be added to the scope that
    * is on the top of the stack. If there is only one scope on the stack,
    * the global and current scope will be the same scope.
    *
    * @param    scope
    *           Specifies the target scope.
    * @param    name
    *           The query name.
    * @param    query
    *           The query instance
    */
   public static void addQuery(ScopeLevel scope, String name, QueryWrapper query)
   {
      WorkArea wa = local.get().wa;
      
      PersistentRegistry vr = wa.queries.getRegistry(scope, PersistentRegistry::new);
      addWorker(vr.objects, scope, name, query);
      
      AtomicInteger count = wa.queries.refs.get(query);
      if (count == null)
      {
         wa.queries.refs.put(query, new AtomicInteger(1));
      }
      else
      {
         count.incrementAndGet();
      }
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first query with the given name.
    *
    * @param   name
    *          The query name.
    *
    * @return  The query reference associated with the name or {@code null} if not found.
    *
    * @throws  ErrorConditionException
    *          When the query cannot be found.
    *
    * @deprecated Use the overloaded method with lookupQuery(String, String...) signature instead.
    */
   @Deprecated
   public static QueryWrapper lookupQuery(String name)
   throws ErrorConditionException
   {
      return lookupQuery(name, new String[] {});
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first query with the given name.
    *
    * @param   name
    *          The query name.
    *
    * @return  The query reference associated with the name or {@code null} if not found.
    *
    * @throws  ErrorConditionException
    *          When the query cannot be found.
    */
   public static QueryWrapper lookupQuery(String name, String... browses)
   throws ErrorConditionException
   {
      WorkArea wa = local.get().wa;
      QueryWrapper found = (QueryWrapper) lookupWorker(wa.queries, name, ERROR_QUERY);
      
      // TODO: shared queries can be defined in internal procedures... ???
      addQuery(ScopeLevel.NEXT, name, found);
      
      return found;
   }
   
   /**
    * Adds a buffer to the specified scope.
    * <p>
    * If the next scope is specified, the instance will be placed in the list
    * of deferred instances that will be added the next time a scope is opened.
    * <p>
    * If global is specified, the instance will be added to the bottom-most
    * scope on the stack.
    * <p>
    * If current is specified, the instance will be added to the scope that
    * is on the top of the stack. If there is only one scope on the stack,
    * the global and current scope will be the same scope.
    *
    * @param    <T>
    *           Buffer type.
    * @param    scope 
    *           Specifies the target scope.
    * @param    name
    *           The buffer name.
    * @param    buffer
    *           The buffer reference.
    */
   public static <T> void addBuffer(ScopeLevel scope, String name, T buffer)
   {
      WorkArea wa = local.get().wa;
      
      PersistentRegistry vr = wa.buffers.getRegistry(scope, BufferRegistry::new);
      addWorker(vr.objects, scope, name, buffer);
      
      // as this is not reached via the scopeStart, we do it here (but only for NEW buffers)
      wa.bufferCreator.put(buffer, wa.pm._thisProcedure());
      
      AtomicInteger count = (AtomicInteger) wa.buffers.refs.get(buffer);
      if (count == null)
      {
         wa.buffers.refs.put(buffer, count = new AtomicInteger(1));
      }
      else
      {
         count.incrementAndGet();
      }
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first buffer with the given name.
    *
    * @param   <T> 
    *          Buffer type.
    * @param   name
    *          The buffer name.
    * @param   ttname
    *          The temp-table name.
    * @param   expected
    *          The expected DMO type of the resolved buffer.
    *
    * @return  The buffer reference associated with the name or
    *          <code>null</code> if not found.
    *
    * @throws   ErrorConditionException
    *           When the buffer cannot be found or it does not match the master.
    */
   public static <T> T lookupBuffer(String name, String ttname, Class<?> expected)
   throws ErrorConditionException
   {
      WorkArea wa = local.get().wa;
      
      T found = (T) lookupWorker(wa.buffers, name, ERROR_BUFFER);
      
      if (found == null)
      {
         // fail early if it is not found
         return null;
      }
      boolean isTemp = found instanceof Temporary;

      RecordBuffer childBuffer = (RecordBuffer) ((BufferReference) found).buffer();
      Class<? extends DataModelObject> foundDmo = childBuffer.getDMOInterface();
      BooleanSupplier pred = () -> 
      {
         if (isTemp && !Temporary.class.isAssignableFrom(expected))
         {
            return false;
         }
         
         return DmoMetadataManager.isSchemaMatch(DmoMetadataManager.getDmoIface(expected), foundDmo);
      };
      
      Runnable error = () -> 
      {
         String msg = "Buffer %s for %s in %s does not match shared buffer defined NEW in %s";
         String pname = ProcedureManager.getInstantiatingExternalProgram();
         String tpname = "n/a"; 
         
         Object creator = wa.bufferCreator.get(found);
         if (creator != null)
         {
            // find the external program where the temp-table was originally defined...
            tpname = local.get().wa.pm.getRelativeName(creator);
         }

         msg = String.format(msg, name, ttname, pname, tpname);
         
         errorHelper(1763, msg);
      };
      
      if (!checkSharedResource(pred, error))
      {
         throw new IllegalStateException(
            "An exception should have been thrown by checkedSharedResource!");
      }
      
      T buffer = found;
      if (isTemp)
      {
         buffer = (T) TemporaryBuffer.useShared((Temporary) found, expected, name);
      }
      else
      {
         // we can safely return the master buffer if they are for a permanent table
      }
      
      addBuffer(ScopeLevel.NEXT, name, buffer);
      
      return found;
   }

   /**
    * Creates (via {@link TemporaryBuffer#define} and adds a temporary table buffer to the next 
    * scope - the instance will be placed in the list of deferred instances that will be added 
    * the next time a scope is opened.
    *
    * @param    <T> 
    *           Type of constructed instance.
    * @param    name
    *           The temporary table buffer name.
    * @param    dmoBufIface
    *           Interface which defines DMO's public API.  This must be a sub-interface of 
    *           {@link Temporary}.
    * @param    variable
    *           Name of the variable with which the record buffer will be associated in the 
    *           transaction manager.
    * @param    legacyName
    *           Legacy name of the temp-table.
    * @param    undoable
    *           <code>true</code> if changes made to records managed by this buffer can be undone;  
    *           <code>false</code> if such changes are committed immediately, such that they cannot
    *           be undone.
    * 
    * @return   An object which implements the specified interface, but which initially has no 
    *           backing data record.
    */
   public static <T extends TempTableBuffer> T addTempTable(String   name, 
                                                               Class<T> dmoBufIface,
                                                               String   variable,
                                                               String   legacyName,
                                                               boolean  undoable)
   {
      return addTempTable(ScopeLevel.NEXT, name, dmoBufIface, variable, legacyName, undoable);
   }

   /**
    * Creates (via {@link TemporaryBuffer#define} and adds a temporary table buffer to the next 
    * scope - the instance will be placed in the list of deferred instances that will be added 
    * the next time a scope is opened.
    *
    * @param    <T> 
    *           Type of constructed instance.
    * @param    name
    *           The temporary table buffer name.
    * @param    dmoBufIface
    *           Interface which defines DMO's public API.  This must be a sub-interface of 
    *           {@link Temporary}.
    * @param    variable
    *           Name of the variable with which the record buffer will be associated in the 
    *           transaction manager.
    * @param    legacyName
    *           Legacy name of the temp-table.
    * 
    * @return   An object which implements the specified interface, but which initially has no 
    *           backing data record.
    */
   public static <T extends TempTableBuffer> T addTempTable(String   name, 
                                                               Class<T> dmoBufIface,
                                                               String   variable,
                                                               String   legacyName)
   {
      return addTempTable(ScopeLevel.NEXT, name, dmoBufIface, variable, legacyName, true);
   }
   
   /**
    * Creates (via {@link TemporaryBuffer#define} and adds a temporary table buffer to the 
    * specified scope.
    * <p>
    * If the next scope is specified, the instance will be placed in the list of deferred 
    * instances that will be added the next time a scope is opened.
    * <p>
    * If global is specified, the instance will be added to the bottom-most scope on the stack.  
    * Also, first, existing temp-tables with the same name are searched - if one is found in one 
    * of the previous scopes, then that temp-table is returned only if its DMO interface is the 
    * same as or it implements the specified <code>dmoBufIface</code>
    * <p>
    * If current is specified, the instance will be added to the scope that is on the top of the 
    * stack. If there is only one scope on the stack, the global and current scope will be the 
    * same scope.
    *
    * @param    <T> 
    *           Type of constructed instance.
    * @param    scope 
    *           Specifies the target scope.
    * @param    name
    *           The temporary table buffer name.
    * @param    dmoBufIface
    *           Interface which defines DMO's public API.  This must be a sub-interface of 
    *           {@link Temporary}.
    * @param    variable
    *           Name of the variable with which the record buffer will be associated in the 
    *           transaction manager.
    * @param    legacyName
    *           Legacy name of the temp-table.
    * 
    * @return   An object which implements the specified interface, but which initially has no 
    *           backing data record.
    */
   public static <T extends TempTableBuffer> T addTempTable(ScopeLevel scope, 
                                                               String     name, 
                                                               Class<T>   dmoBufIface,
                                                               String     variable,
                                                               String     legacyName)
   {
      return addTempTable(scope, name, dmoBufIface, variable, legacyName, true);
   }
   
   /**
    * Creates (via {@link TemporaryBuffer#define} and adds a temporary table buffer to the 
    * specified scope.
    * <p>
    * If the next scope is specified, the instance will be placed in the list of deferred 
    * instances that will be added the next time a scope is opened.
    * <p>
    * If global is specified, the instance will be added to the bottom-most scope on the stack.
    * Also, first, existing temp-tables with the same name are searched - if one is found in one
    * of the previous scopes, then that temp-table is returned only if its DMO interface is the
    * same as or it implements the specified <code>dmoBufIface</code>
    * <p>
    * If current is specified, the instance will be added to the scope that is on the top of the 
    * stack. If there is only one scope on the stack, the global and current scope will be the 
    * same scope.
    *
    * @param    <T> 
    *           Type of constructed instance.
    * @param    scope 
    *           Specifies the target scope.
    * @param    name
    *           The temporary table buffer name.
    * @param    dmoBufIface
    *           Interface which defines DMO's public API.  This must be a sub-interface of 
    *           {@link Temporary}.
    * @param    variable
    *           Name of the variable with which the record buffer will be associated in the 
    *           transaction manager.
    * @param    legacyName
    *           Legacy name of the temp-table.
    * @param    undoable
    *           <code>true</code> if changes made to records managed by this buffer can be undone;  
    *           <code>false</code> if such changes are committed immediately, such that they cannot
    *           be undone.
    * 
    * @return   An object which implements the specified interface, but which initially has no 
    *           backing data record.
    */
   public static <T extends TempTableBuffer> T addTempTable(ScopeLevel scope,
                                                            String name,
                                                            Class<T> dmoBufIface,
                                                            String variable,
                                                            String legacyName,
                                                            boolean undoable)
   {
      WorkArea wa = local.get().wa;
      PersistentRegistry vr = wa.tempTables.getRegistry(scope, PersistentRegistry::new);
      SharedRegistry sr = vr.objects;
      boolean global = (scope == ScopeLevel.GLOBAL);
      T var = null;
      
      if (global)
      {
         Object found = sr.shared.lookupSymbol(name);
         
         // ignore repeated global definitions
         if (found != null)
         {
            checkSharedTempTable(found, name, dmoBufIface);

            // create a new buffer for this request (subsequent calls which re-use the temp-table
            // will create a separate slave buffer - do not reuse the master buffer)
            var = TemporaryBuffer.define((T) found, variable, legacyName, false);
         }
      }

      if (var == null)
      {
         var = TemporaryBuffer.define(dmoBufIface, variable, legacyName, global, undoable);
      }
      
      addWorker(sr, scope, name, var);
      PersistentRegistry ttNamesReg = wa.tempTableNames.getRegistry(scope, PersistentRegistry::new);
      addWorker(ttNamesReg.objects, scope, legacyName, name);

      return var;
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first temp
    * table with the given name.
    *
    * @param   <T> 
    *          Variable type.
    * @param   name
    *          The temp table buffer name.
    * @param   expected
    *          The expected DMO type of the resolved temp-table.
    *
    * @return  The temp table buffer reference associated with the name or
    *          <code>null</code> if not found.
    *
    * @throws   ErrorConditionException
    *           When the temp-table cannot be found or it does not match the master.
    */
   public static <T extends Temporary> T lookupTempTable(String name, Class<T> expected)
   throws ErrorConditionException
   {
      return lookupTempTable(name, expected, ERROR_TT);
   }

   /**
    * Searches all scopes from the top of the stack down for the first temp
    * table with the given legacy name.
    *
    * @param   legacyName
    *          Legacy name of the target temp table.
    *
    * @return  The temp table buffer reference associated with the name or
    *          <code>null</code> if not found.
    */
   public static <T extends Temporary> T lookupTempTable(String legacyName)
   throws ErrorConditionException
   {
      String variableName = (String) lookupWorker(local.get().wa.tempTableNames, legacyName, null);
      return lookupTempTable(variableName, null, null);
   }
   
   /**
    * Adds a frame to the {@link ScopeLevel#NEXT} scope.
    * <p>
    * The instance will be placed in the list of deferred instances that will
    * be added the next time a scope is opened.
    *
    * @param    name
    *           The frame name.
    * @param    frame
    *           The frame reference.
    *
    * @return   Previous instance with the same name or <code>null</code> if
    *           there were no such instance.  If there is a previous instance,
    *           it will be replaced with this new instance.
    */
   public static <T> T addFrame(String name, T frame)
   {
      return addFrame(ScopeLevel.NEXT, name, frame);
   }
   
   /**
    * Adds a frame to the specified scope.
    * <p>
    * If the next scope is specified, the instance will be placed in the list
    * of deferred instances that will be added the next time a scope is
    * opened.
    * <p>
    * If global is specified, the instance will be added to the next
    * scope on the stack, as there is no such thing as a global frame in
    * the Progress 4GL.
    * <p>
    * If current is specified, the instance will be added to the scope that
    * is on the top of the stack. If there is only one scope on the stack,
    * the global and current scope will be the same scope.
    *
    * @param    <T>
    *           Frame type.
    * @param    scope 
    *           Specifies the target scope.
    * @param    name
    *           The frame name.
    * @param    frame
    *           The frame reference.
    *
    * @return   Previous instance with the same name or <code>null</code> if
    *           there were no such instance.  If there is a previous instance,
    *           it will be replaced with this new instance.
    */
   public static <T> T addFrame(ScopeLevel scope, String name, T frame)
   {
      if (scope == ScopeLevel.GLOBAL)
      {
         scope = ScopeLevel.NEXT;
      }

      WorkArea wa = local.get().wa;
      
      SharedRegistry sr = wa.frames;
      T result = (T) sr.shared.lookupSymbol(name);
      
      addWorker(sr, scope, name, frame);
      
      return result;
   }
   
   /**
    * Adds a menu definition to the {@link ScopeLevel#NEXT} scope.
    * <p>
    * The instance will be placed in the list of deferred instances that will
    * be added the next time a scope is opened.
    *
    * @param    name
    *           The menu name.
    * @param    menu
    *           The menu definition reference.
    */
   public static <T> void addMenu(String name, T menu)
   {
      addMenu(ScopeLevel.NEXT, name, menu);
   }
   
   /**
    * Adds a menu to the specified scope.
    * <p>
    * If the next scope is specified, the instance will be placed in the list
    * of deferred instances that will be added the next time a scope is
    * opened.
    * <p>
    * If global is specified, the instance will be added to the next
    * scope on the stack, as GLOBAL is not a valid option for shared menus.
    * <p>
    * If current is specified, the instance will be added to the scope that
    * is on the top of the stack. If there is only one scope on the stack,
    * the global and current scope will be the same scope.
    *
    * @param    <T>
    *           Menu definition type.
    * @param    scope 
    *           Specifies the target scope. 
    * @param    name
    *           The menu name.
    * @param    menu
    *           The menu reference.
    */
   public static <T> void addMenu(ScopeLevel scope, String name, T menu)
   {
      if (scope == ScopeLevel.GLOBAL)
      {
         scope = ScopeLevel.NEXT;
      }
      
      WorkArea wa = local.get().wa;
      
      addWorker(wa.menus, scope, name, menu);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first frame with the given name.
    *
    * @param   <T> 
    *          Frame type.
    * @param   name
    *          The frame name.
    *
    * @return  The frame reference associated with the name or <code>null</code> if not found.
    *
    * @throws   ErrorConditionException
    *           When the frame cannot be found and silent error mode is disabled.
    */
   public static <T> T lookupFrame(String name)
   throws ErrorConditionException
   {
      WorkArea wa = local.get().wa;

      return (T) lookupWorker(wa.frames, name, ERROR_FRAME);
   }
   
   /**
    * Searches all scopes from the top of the stack down for the first menu with the given name.
    *
    * @param   <T> 
    *          menu definition type.
    * @param   name
    *          The menu name.
    *
    * @return  The menu definition reference associated with the name or <code>null</code> if not found.
    *
    * @throws   ErrorConditionException
    *           When the menu cannot be found and silent error mode is disabled.
    */
   public static <T> T lookupMenu(String name)
   throws ErrorConditionException
   {
      WorkArea wa = local.get().wa;
      
      return (T) lookupWorker(wa.menus, name, null);
   }
   
   /**
    * Provides the singleton instance of this class as a {@link Scopeable}.
    *
    * @return   The only instance of this class.
    */
   public Scopeable createScopeable()
   {
      return this;
   }
   
   /**
    * Provides a notification that a new scope is about to be entered.
    * 
    * @param    block
    *           The explicit block definition which required this notification.
    */
   @Override
   public void scopeStart(BlockDefinition block)
   {
     // shared resources can be defined only at the external program
      if (!block.external || wa.tm.isGlobalBlock())
      {
         return;
      }

      Object thisProc = wa.pm._thisProcedure();
      
      Consumer<SharedRegistry> c = (sr) ->
      {
         sr.shared.addScope(null);
         
         if (sr.pending != null)
         {
            Set<String> keys = sr.pending.keySet();
            
            for (String next : keys)
            {
               Object resource = sr.pending.get(next);

               sr.shared.addSymbol(false, next, resource);
            }
            
            sr.pending = null;
         }
      };

      if (!wa.pm.isThisProcedurePersistent())
      {
         // menu and frames can't be in a persistent procedure
         for (SharedRegistry sr : wa.resources)
         {
            c.accept(sr);
         }
      }
      
      for (PersistentShares pshares : wa.persistentResources)
      {
         PersistentRegistry pending = pshares.pending;
         if (pending == null)
         {
            // nothing registered in the current ran program
            continue;
         }
         
         pshares.pending = null;
         
         // save the registry at THIS-PROCEDURE
         pshares.persistentDictionaries.put(thisProc, pending);
         
         pending.accept(c);
         
         SharedRegistry sr = pending.objects;
         // exclude the global scope
         for (int scope = 0; scope <  sr.shared.size() - 1; scope++)
         {
            Map<String, Object> dict = sr.shared.getDictionaryAtScope(scope, false);
            if (dict == null)
            {
               continue;
            }
            
            for (Object o : dict.values())
            {
               AtomicInteger count = (AtomicInteger) pshares.refs.get(o);
               if (count == null)
               {
                  pshares.refs.put(o, count = new AtomicInteger(1));
               }
               else
               {
                  count.incrementAndGet();
               }
            }
         }
      }
   }   
   
   /**
    * Provides a notification that a scope is about to be exited.
    * <p>
    * This is a no-op for the shared variable manager.
    */
   public void scopeFinished()
   {
      // no-op
   }
   
   /**
    * Provides notification that the external procedure scope has been deleted.
    */
   @Override
   public void scopeDeleted()
   {
      Object referent = wa.pm.getProcessedProcedure();

      if (wa.tm.isExternalBlock()            &&
          !wa.tm.isGlobalBlock()             &&
          referent == wa.pm._thisProcedure() &&
          !wa.pm._isPersistentAndNotPendingDelete(referent))
      {
         for (SharedRegistry sr : wa.resources)
         {
            // menu and frames can't be in a persistent procedures, so 'scopeDeleted' will be called when
            // the external program ends.  but scopeDeleted gets called when the external program ends, and
            // this-procedure was persistent but already deleted within the external program - so we need to
            // check if the referent is not persistent.
            sr.shared.deleteScope();
         }
      }

      if (referent == null              ||
          wa.pm._isPersistentAndNotPendingDelete(referent) ||
          (referent == wa.pm._thisProcedure() && !wa.tm.isExternalBlock()))
      {
         return;
      }

      // this needs to be executed only when the procedure gets deleted.
      if (referent != null)
      {
         for (PersistentShares pshare : wa.persistentResources)
         {
            PersistentRegistry reg = 
               (PersistentRegistry) pshare.persistentDictionaries.remove(referent);
            
            if (reg == null)
            {
               continue;
            }
            
            // for all objects exposed via the shared registry of the deleted procedure, decrement
            // their ref count when the ref count reaches zero, all these must be removed from the 
            // undoable list!
            IdentityHashMap<LazyUndoable, Object> removed = new IdentityHashMap<>();

            SharedRegistry sr = reg.objects;
            // exclude the global scope
            for (int scope = 0; scope <  sr.shared.size() - 1; scope++)
            {
               Map<String, Object> dict = sr.shared.getDictionaryAtScope(scope, false);
               if (dict == null)
               {
                  continue;
               }
               
               for (Object o : dict.values())
               {
                  AtomicInteger refs = (AtomicInteger) pshare.refs.get(o);
                  if (refs == null)
                  {
                     continue;
                  }

                  int count = refs.decrementAndGet();
                  
                  if (count == 0)
                  {
                     pshare.refs.remove(o);
      
                     reg.collectUndoables(removed, o);
                  }
               }
            }
            
            if (!removed.isEmpty())
            {
               TransactionManager.deregister(null, removed);
            }
         }
      }
   }

   /**
    * Get the {@link ScopeId} for the instance.
    * 
    * @return   {@link ScopeId#SHARED_VARIABLE_MANAGER}.
    */
   @Override
   public ScopeId getScopeId()
   {
      return ScopeId.SHARED_VARIABLE_MANAGER;
   }
   
   /**
    * Cleanup the {@link WorkArea#bufferCreator} for the specific buffer instance.
    * 
    * @param    buffer
    *           The buffer instance.
    */
   public static void cleanupBuffer(BufferReference buffer)
   {
      WorkArea wa = local.get().wa;
      
      wa.bufferCreator.remove(buffer);
   }

   /**
    * Update the references for the specified array, as a result of a resize.
    * 
    * @param    array
    *           The previous instance.
    * @param    copy
    *           The resized instance.
    */
   static <T extends BaseDataType> void updateExtentVar(T[] array, T[] copy)
   {
      WorkArea wa = local.get().wa;
      
      for (VarRegistry v : wa.vars.persistentDictionaries.values())
      {
         if (v == null)
         {
            continue;
         }
         
         SharedRegistry sr = v.objects;
         int globalScope = sr.shared.size() - 1;

         // search all scopes and update the variable; do not update the extentVars dictionary - this
         // holds the original variable, to preserve its original extent, at the master definition
         for (int scope = 0; scope < sr.shared.size(); scope++)
         {
            Map<String, Object> dict = sr.shared.getDictionaryAtScope(scope, false);
            if (dict == null)
            {
               continue;
            }
            
            if (dict.values().contains(array))
            {
               // find its key (we don' know it here...)
               String foundKey = null;
               for (String key : dict.keySet())
               {
                  Object val = dict.get(key);
                  if (val == array)
                  {
                     foundKey = key;
                     break;
                  }
               }
               
               dict.put(foundKey, copy);
               
               if (scope == globalScope)
               {
                  for (LazyUndoable undoable : copy)
                  {
                     undoable.setGlobal(true);
                  }
               }
               
               // break on first found, as only one reference must exist
               break;
            }
         }
      }
   }
   
   /**
    * Cleanup the pending shared resources, as external program instantiation failed.
    */
   static void cleanupPending()
   {
      WorkArea wa = local.get().wa;
      
      // TODO: is there any resource-specific cleanup which should be done? 
      // i.e. discard temp-tables?

      for (SharedRegistry sr : wa.resources)
      {
         // FRAME: ??
         // MENU: ??

         sr.pending = null;
      }
      
      for (PersistentShares pshare : wa.persistentResources)
      {
         // VARIABLE: nothing to do, just clean
         // STREAM: nothing to do, just clean
         // BUFFER: ??
         // TEMP-TABLE: ??

         pshare.pending = null;
      }
   }
   
   /**
    * Core add worker that defers adding (using a pending list in the work area) if this is a 
    * <code>ScopeLevel.NEXT</code> request, otherwise the instance will be mapped into the current
    * or global scope immediately.
    *
    * @param    sr
    *           The {@link SharedRegistry} container where to add the var.
    * @param    scope
    *           The scope to target.
    * @param    name
    *           The symbolic name for the shared instance.
    * @param    var
    *           The instance to be shared.
    */
   private static void addWorker(SharedRegistry sr, ScopeLevel scope, String name, Object var)
   {
      SharedVariableManager mgr = local.get();
      mgr.wa.tm.registerPendingScopeable(mgr);
      
      if (scope == ScopeLevel.NEXT)
      {
         if (sr.pending == null)
         {
            sr.pending = new HashMap<>();
         }
         
         // we don't have to worry about the global flag as these can't be global, just save off
         // the case-insensitive name and instance
         sr.pending.put(sr.shared.isCaseSensitive() ? name : name.toLowerCase(), var);
      }
      else
      {
         sr.shared.addSymbol((scope == ScopeLevel.GLOBAL), name, var);
      }
   }
   
   /**
    * Core lookup worker that returns the first instance found by the
    * given name.  The order of lookup is:
    * <ol>
    *   <li> pending list
    *   <li> each of the scopes starting at the current scope and moving
    *        through the stack to the global scope (if it exists)
    * </ol>
    *
    * @param    sr
    *           The {@link SharedRegistry} container where to add the var.
    * @param    name
    *           The symbolic name for the shared instance.
    * @param    error
    *           If no instance is found, use this to construct and throw the ERROR condition.
    *           
    * @return   The found resource, from the given context-local registry.
    */
   private static Object lookupWorker(SharedRegistry sr, String name, Consumer<String> error)
   throws ErrorConditionException
   {
      Object result = null;
      
      if (sr.pending != null)
      {
         // names are always case-insensitive
         result = sr.pending.get(sr.shared.isCaseSensitive() ? name : name.toLowerCase());
      }
      
      if (result == null)
      {
         result = sr.shared.lookupSymbol(name);
      }
      
      if (result == null && error != null)
      {
         error.accept(name);
      }
      
      return result;
   }

   /**
    * Core lookup worker that returns the first instance found by the given name.  This is used
    * for resources which can survive the persistent scope.  The current procedure's state is
    * walked from the top of the stack down, with each scope performing this search:
    * <ol>
    *   <li> pending list
    *   <li> each of the scopes starting at the current scope and moving
    *        through the stack to the global scope (if it exists)
    * </ol>
    *
    * @param    pshare
    *           The {@link PersistentShares} container where to add the object.
    * @param    name
    *           The symbolic name for the shared instance.
    * @param    error
    *           If no instance is found, use this to construct and throw the ERROR condition.
    *           
    * @return   The found resource, from the given context-local registry.
    */
   private static Object lookupWorker(PersistentShares<?> pshare, String name, Consumer<String> error)
   {
      PersistentRegistry reg = pshare.lookupRegistry(name);
      
      if (reg == null)
      {
         if (error != null)
         {
            error.accept(name);
         }
         return null;
      }

      return lookupWorker(reg.objects, name, error);
   }
   
   /**
    * Simple helper to construct a {@link ErrorConditionException} and throw it, when needed.
    *
    * @param    num
    *           The error number.
    * @param    msg
    *           The error message.
    *
    * @throws   ErrorConditionException
    *           Always, this will be caught by ControlFlowOps and decide to log or re-throw 
    *           the error.
    */
   private static void errorHelper(int num, String msg)
   throws ErrorConditionException
   {
      // this is used when there is a problem resolving a shared resource; we explicitly throw
      // here because we assume this will be caught by ControlFlowOps$ExternalProgramResolver.resolve
      // which catches all errors and dispatches them via ErrorManager.recordOrThrowError; it is
      // safe to generate this exception directly
      ErrorConditionException ece = new ErrorConditionException(num, msg);
      throw ece;
   }
   
   /**
    * Check if the shared temp-table DMO class matches the found master temp-table.
    * 
    * @param    found
    *           The found master temp-table.
    * @param    name
    *           The shared temp-table name.
    * @param    expected
    *           The expected temp-table to be defined by the found master.
    */
   private static void checkSharedTempTable(Object                     found, 
                                            String                     name, 
                                            Class<? extends Temporary> expected)
   {
      TemporaryBuffer childBuffer = found == null ? null : (TemporaryBuffer) ((BufferReference) found).buffer();
      Class<? extends DataModelObject> foundDmo = found == null ? null : childBuffer.getDMOInterface();
      BooleanSupplier pred = () -> found != null && DmoMetadataManager.isSchemaMatch(expected, foundDmo);
            
      Runnable error = () ->
      {
         String msg = 
            "In procedure %s, shared temp-table %s has a conflict in field, index or undo status";
         String pname = ProcedureManager.getInstantiatingExternalProgram();
         msg = String.format(msg, pname, name);
         
         errorHelper(2075, msg);
      };
      
      if (!checkSharedResource(pred, error))
      {
         throw new IllegalStateException(
            "An exception should have been thrown by checkedSharedResource!");
      }
   }
 
   /**
    * Check if the found legacy 4GL object is compatible with the expected one.
    * <p>
    * Regardless of extent vars or scalar vars, the 4GL class specified at the definition must
    * match exactly.
    * 
    * @param    name
    *           The shared variables name.
    * @param    found
    *           The found legacy 4GL object.
    * @param    expected
    *           An instance describing the expected object.
    */
   @SuppressWarnings("unchecked")
   private static void checkObjectType(String name,
                                       Object found,
                                       Object expected)
   {
      Class<? extends _BaseObject_> expectedType = null;
      Class<? extends _BaseObject_> foundType = null;

      if (found instanceof object)
      {
         foundType = ((object<? extends _BaseObject_>) found).type();
         expectedType = ((object<? extends _BaseObject_>) expected).type();
      }
      else if (found instanceof object[])
      {
         object<? extends _BaseObject_>[] fref = (object<? extends _BaseObject_>[]) found;
         object<? extends _BaseObject_>[] eref = (object<? extends _BaseObject_>[]) expected;
         
         foundType = fref.length == 0 ? ArrayAssigner.getObjectType(fref)
                                      : fref[0].type();
         expectedType = eref.length == 0 ? ArrayAssigner.getObjectType(eref)
                                         : eref[0].type();
      }

      if (expectedType != foundType)
      {
         // the object's underlying type must match exactly
         
         String msg = "Classes don't match for shared variable %s in %s";
         String pname = ProcedureManager.getInstantiatingExternalProgram();
         msg = String.format(msg, name, pname);
         errorHelper(13450, msg);
      }
   }
   
   /**
    * Check if the defined shared variable matches the existing master variable.
    * 
    * @param    name
    *           The shared variables name.
    * @param    var
    *           The shared var instance (which is attempting to be defined).
    * @param    noUndo
    *           <code>true</code> if this variable has its NO-UNDO option set.
    */
   private static void checkSharedVariable(String  name, Object  var, boolean noUndo)
   {
      BooleanSupplier pred = () -> (variablesMatch(name, var, noUndo));
      
      Runnable error = () ->
      {
         String msg = "%s Conflict in extent, datatype, or undo status for shared %s";
         String pname = ProcedureManager.getInstantiatingExternalProgram();
         msg = String.format(msg, pname, name);
         
         errorHelper(391, msg);
      };
      
      if (!checkSharedResource(pred, error))
      {
         throw new IllegalStateException(
            "An exception should have been thrown by checkedSharedResource!");
      }
   }
   
   /**
    * Check if the candidate slave shared variable matches an existing master variable.
    * 
    * @param    name
    *           The shared var name.
    * @param    var
    *           The slave variable which needs to match the master one.
    * @param    noUndo
    *           Flag indicating if the slave var was defined as NO-UNDO.
    */
   private static boolean variablesMatch(String name, Object var, boolean noUndo)
   {
      WorkArea wa = local.get().wa;

      VarRegistry v = wa.vars.lookupRegistry(name);
      SharedRegistry sr = (var.getClass().isArray() ? v.extentVars : v.objects);

      Object shared = sr.shared.lookupSymbol(name);
      
      if (shared == null)
      {
         return false;
      }

      // check if variable matches the registered one
      boolean canAssign = BaseDataType.sameType(shared, var);
      
      if (canAssign)
      {
         // check no-undo
         SharedRegistry noUndoWa = v.noUndoVars;
         Object sharedNoUndoVar = noUndoWa.shared.lookupSymbol(name);
         
         boolean sharedNoUndo = (sharedNoUndoVar != null &&
                                 ((Boolean)sharedNoUndoVar).booleanValue());
         
         if (sharedNoUndo != noUndo)
         {
            canAssign = false;
         }
      }

      return canAssign;
   }

   /**
    * Check if the specified match condition is <code>true</code>.  If it is not, it will execute
    * the specified error code.
    * 
    * @param    pred
    *           The predicate to evaluate if the resource matches the found master.
    * @param    error
    *           The error code to execute.
    * 
    * @return   <code>true</code> if the resource matches the master.
    */
   private static boolean checkSharedResource(BooleanSupplier pred, Runnable error)
   {
      if (!pred.getAsBoolean())
      {
         error.run();
         return false;
      }
      
      return true;
   }

   /**
    * Searches all scopes from the top of the stack down for the first temp
    * table with the given name.
    *
    * @param   <T>
    *          Variable type.
    * @param   name
    *          The temp table buffer name.
    * @param   expected
    *          The expected DMO type of the resolved temp-table.
    * @param   error
    *          Error action to b executed if no instance is found.
    *
    * @return  The temp table buffer reference associated with the name or
    *          <code>null</code> if not found.
    */
   private static <T extends Temporary> T lookupTempTable(String name,
                                                          Class<T> expected,
                                                          Consumer<String> error)
   throws ErrorConditionException
   {
      WorkArea wa = local.get().wa;

      T found =  (T) lookupWorker(wa.tempTables, name, error);

      if (found == null)
      {
         // fail early if it is not found
         return null;
      }

      if (expected != null)
      {
         checkSharedTempTable(found, name, expected);
      }

      PersistentRegistry vr = wa.tempTables.getRegistry(ScopeLevel.NEXT, PersistentRegistry::new);
      SharedRegistry sr = vr.objects;
      addWorker(sr, ScopeLevel.NEXT, name, found);
      PersistentRegistry ttNamesReg = wa.tempTableNames.getRegistry(ScopeLevel.NEXT, PersistentRegistry::new);
      addWorker(ttNamesReg.objects, ScopeLevel.NEXT, name, name);
      
      return found;
   }

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

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

      /** Map with each buffer's external program, where it was defined. */
      private Map<Object, Object> bufferCreator = new IdentityHashMap<>();
      
      private PersistentShares<VarRegistry> vars = new PersistentShares<>(new VarRegistry());
      
      /** Pool of streams which is context-local. Initializes upon first use. */ 
      private PersistentShares<PersistentRegistry> streams = 
         new PersistentShares<>(new PersistentRegistry());
      
      /** Pool of buffers which is context-local. Initializes upon first use. */ 
      private PersistentShares<BufferRegistry> buffers = 
         new PersistentShares<>(new BufferRegistry());

      /** Pool of queries which is context-local. Initializes upon first use. */
      private PersistentShares<PersistentRegistry> queries =
            new PersistentShares<>(new PersistentRegistry());
      
      /** Temp-table pool which is context-local. Initializes upon first use. */ 
      private PersistentShares<PersistentRegistry> tempTables = 
         new PersistentShares<>(new PersistentRegistry(true));

      /**
       * Temp-table names pool which is context-local. Initializes upon first use. Contains
       * legacy name -&gt; variable name (same as converted name) mapping for {@link #tempTables}.
       */
      private PersistentShares<PersistentRegistry> tempTableNames =
         new PersistentShares<>(new PersistentRegistry());
      
      /** Pool of frames which is context-local. Initializes upon first use. */ 
      private SharedRegistry frames = new SharedRegistry();
      
      /** Pool of menus which is context-local. Initializes upon first use. */ 
      private SharedRegistry menus = new SharedRegistry(true);

      /** All the shared data. */
      private SharedRegistry[] resources = 
      {
         frames,
         menus
      };
      
      /** All the shared data which survives with the persistent procedure. */
      private PersistentShares<?>[] persistentResources = 
      {
         vars,
         streams,
         buffers,
         tempTables,
         tempTableNames,
         queries
      };
   }

   /**
    * Container for the registries related to shared variables.
    */
   private static class VarRegistry
   extends PersistentRegistry
   {
      /** Pool of extent vars which is context-local (with the state at their definition). */ 
      private final SharedRegistry extentVars = new SharedRegistry();
      
      /** Pool of NO-UNDO vars which is context-local (with the state at their definition). */ 
      private final SharedRegistry noUndoVars = new SharedRegistry();
      
      /** All the shared data. */
      private SharedRegistry[] resources = 
      {
         objects,
         extentVars,
         noUndoVars
      };

      /**
       * Clone this instance.
       * 
       * @return   A copy of this instance.
       */
      @Override
      public VarRegistry clone()
      {
         VarRegistry var = new VarRegistry();
         
         for (int i = 0; i < resources.length; i++)
         {
            var.resources[i].shared.copyAll(resources[i].shared, true);
         }
         
         return var;
      }
      
      /**
       * Accept the specified consumer over all resource registries.
       * 
       * @param    c
       *           The operation to perform.
       */
      @Override
      public void accept(Consumer<SharedRegistry> c)
      {
         for (SharedRegistry sr : resources)
         {
            c.accept(sr);
         }
      }

      /**
       * Collect the undoables for this resource in the specified map.
       * 
       * @param    removed
       *           The map where to add the undoables.
       * @param    o
       *           The original object.
       */
      @Override
      public void collectUndoables(IdentityHashMap<LazyUndoable, Object> removed, Object o)
      {
         if (o instanceof BaseDataType)
         {
            removed.put((BaseDataType) o, null);
         }
         else
         {
            for (BaseDataType v : (BaseDataType[]) o)
            {
               removed.put(v, null);
            }
         }
      }
   }
   
   /**
    * A registry for BUFFER resources.
    */
   private static class BufferRegistry
   extends PersistentRegistry
   {
      /**
       * Default c'tor.  Initialize the dictionaries as case-sensitive.
       */
      public BufferRegistry()
      {
         super(true);
      }

      /**
       * Collect the undoables for this resource in the specified map.
       * 
       * @param    removed
       *           The map where to add the undoables.
       * @param    o
       *           The original object.
       */
      @Override
      public void collectUndoables(IdentityHashMap<LazyUndoable, Object> removed, Object o)
      {
         LazyUndoable undoable = ((BufferReference) o).buffer().getUndoable();
         
         if (undoable != null)
         {
            removed.put(undoable, null);
         }
      }
      
      /**
       * Clone this instance.
       * 
       * @return   A copy of this instance.
       */
      @Override
      protected PersistentRegistry clone()
      {
         PersistentRegistry reg = new BufferRegistry();
         reg.objects.shared.copyAll(objects.shared, true);

         return reg;
      }
   }
   
   /**
    * A registry for resources which exist with the persistent procedure.
    */
   private static class PersistentRegistry
   implements Cloneable
   {   
      /** Object pool which is context-local. Initializes upon first use. */ 
      protected final SharedRegistry objects;
   
      /**
       * Default c'tor.  Initialize the dictionaries as case-insensitive.
       */
      public PersistentRegistry()
      {
         this(false);
      }

      /**
       * Initialize the dictionaries with the provided case-sensitivity.
       * 
       * @param    caseSensitive
       *           Flag indicating if the dictionary keys are case-sensitive or not.
       */
      public PersistentRegistry(boolean caseSensitive)
      {
         this.objects = new SharedRegistry(caseSensitive);
      }
      
      /**
       * Accept the specified consumer over all resource registries.
       * 
       * @param    c
       *           The operation to perform.
       */
      public void accept(Consumer<SharedRegistry> c)
      {
         c.accept(objects);
      }

      /**
       * Collect the undoables for this resource in the specified map.  By default is a no-op.
       * 
       * @param    removed
       *           The map where to add the undoables.
       * @param    o
       *           The original object.
       */
      public void collectUndoables(IdentityHashMap<LazyUndoable, Object> removed, Object o)
      {
         // no-op
      }
      
      /**
       * Clone this instance.
       * 
       * @return   A copy of this instance.
       */
      @Override
      protected PersistentRegistry clone()
      {
         PersistentRegistry reg = new PersistentRegistry(objects.shared.isCaseSensitive());
         reg.objects.shared.copyAll(objects.shared, true);

         return reg;
      }
   }

   /**
    * Common behavior for all shared resources which are in sync with the persistent procedure 
    * state.
    */
   private static class PersistentShares<T extends PersistentRegistry>
   {
      /** The persistent dictionaries, saved per each external program. */
      private final Map<Object, T> persistentDictionaries = new IdentityHashMap<>();
      
      /**
       * Track references for all shared objects, in all registries (not defined/used) - when
       * the reference count reaches zero, they will be removed from the transaction-related
       * registries.
       */
      private final IdentityHashMap<Object, AtomicInteger> refs = new IdentityHashMap<>();

      /** The objects being defined in the currently instantiating external program. */
      private PersistentRegistry pending = null;

      /** Global pool which is context-local. */ 
      private final T globals;
      
      /**
       * Initialize this container with the specified global registry, which will be used as a 
       * seed for all other scopes.
       */
      private PersistentShares(T globals)
      {
         this.globals = globals;
      }
      
      /**
       * Resolve the registry: if the scope is {@link ScopeLevel#NEXT}, return the {@link #globals} registry.
       * If the scope is {@link ScopeLevel#NEXT}, return the {@link #pending} registry.
       * <p>
       * Otherwise, return the registry associated with the current {@link ProcedureManager#thisProcedure()}.
       * 
       * @param    scope
       *           The scope for which the registry is needed.
       * @param    newRegistry
       *           A function to initiliaze the {@link #pending} or this-procedure's registry, if needed.
       *           
       * @return   The {@link #pending} registry.
       */
      public T getRegistry(ScopeLevel scope, Supplier<T> newRegistry)
      {
         if (scope == ScopeLevel.GLOBAL)
         {
            return this.globals;
         }
         
         if (scope == ScopeLevel.NEXT)
         {
            if (this.pending == null)
            {
               this.pending = newRegistry.get();
            }
            
            return (T) this.pending;
         }

         WorkArea wa = local.get().wa;
         Object referent = wa.pm._thisProcedure();
         T reg = persistentDictionaries.computeIfAbsent(referent, (o) -> newRegistry.get());
         
         return reg;
      }
      
      /**
       * Lookup a {@link PersistentRegistry} which contains a shared variable with the given name; 
       * this starts from the top of the stack and searches in the context of each 
       * {@link #persistentDictionaries referent} for the given variable name.
       * 
       * @param    name
       *           The shared object name.
       *           
       * @return   The found {@link PersistentRegistry} or <code>null</code> if it can't be found.
       */
      public T lookupRegistry(String name)
      {
         Function<Object, Object> search = (referent) ->
         {
            T vreg = persistentDictionaries.get(referent);
            if (vreg == null)
            {
               return null;
            }
            
            return (lookupWorker(vreg.objects, name, null) != null) ? vreg : null;
         };
         
         // search previous scopes...
         T reg = (T) ProcedureManager.searchInStack(search);
         
         if (reg == null)
         {
            // default to global lookup
            reg = globals;
         }
         
         return reg;
      }
   }
   
   /** 
    * Stores the registry for a specified shared resource.
    */
   private static class SharedRegistry
   {
      /** 
       * Instances mapped by case-insensitive name and organized by scope.  Use this version of
       * the constructor so that a global scope is added during construction. 
       */
      private final ScopedSymbolDictionary<Object> shared;
      
      /** Instances that must be added to the NEXT scope. */
      private Map<String, Object> pending = null;

      /**
       * Initialize the registry with a case-insensitive {@link #shared dictionary}.
       */
      private SharedRegistry()
      {
         this(false);
      }

      /**
       * Initialize the registry.
       * 
       * @param   caseSensitive
       *          Flag indicating if the {@link #shared dictionary} is case sensitive or not; a
       *          registry which has as keys java names must always be case-sensitive.  If it is
       *          using legacy names, then it must be case-insensitive.
       */
      private SharedRegistry(boolean caseSensitive)
      {
         shared = new ScopedSymbolDictionary<Object>(null, caseSensitive);
      }
   }
}