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