# WidgetPool.java

    /*
    ** Module   : WidgetPool.java
    ** Abstract : widget pools management functions
    **
    ** Copyright (c) 2010-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ----------------------------------Description---------------------------------
    ** 001 SVL 20101004 Created initial version with no implementation.
    ** 002 CA  20130131 Moved to util package (as other non-widget resources can be pooled). Added
    **                  DELETE WIDGET-POOL support.
    ** 003 CA  20130918 Added runtime support for widget-pools.
    ** 004 CA  20140228 Delete only valid resources. Fixed merging of unnamed persistent pools to
    **                  previous scope.
    ** 005 OM  20141117 Fixed widget-pool management for persistent procedures.
    ** 006 CA  20150429 Fixed a mem-leak in deleteWidgetPools.
    ** 007 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
    ** 008 ECF 20171122 Minor optimizations.
    ** 009 CA  20190313 The persistent program's unnamed pools must be saved and pushed on the stack
    **                  whenever one of its internal entries get executed.
    **     CA  20190325 Implemented ProcedureHelper instead of direct usage of the static API.
    **                  This allows the elimination of context local usage in ProcedureManager.
    **     CA  20190326 Refactored to reduce the context-local usage.
    ** 010 CA  20190811 Optimized deleteNonPersistentPools, when there are no IDs in the set.
    ** 011 CA  20200505 Set the ProcedureManager and TransactionManager helper only for runtime.
    ** 012 CA  20201117 Fixed a leak with unnamedProcPools, which was not removing deleted referents.
    **     CA  20210310 Small performance improvement when processing unnamed/named pools on scope finish (avoid
    **                  the 'add' overhead if they are empty).
    ** 013 CA  20220120 Performance improvement for scope processing - don't build new collections until they are
    **                  needed.
    **     CA  20220201 A small performance improvement in scopeFinished.
    **     CA  20220409 Fixed a bug when invoking a procedure in an external program: upon exit of that internal
    **                  procedure, the external program's unnamed pools must survive (until it gets deleted).
    **     CA  20221010 Performance improvements - avoid the ProcedureData lookup, by keeping a parallel stack of
    **                  this data for THIS-PROCEDURE.  Refs #6826
    **     CA  20230110 Keep a single 'ids' in WorkArea which will be used in 'scopeFinished', to avoid creating
    **                  a new instance on each scope finished call.
    **     CA  20230110 Moved some other APIs to WidgetPoolHelper, to reduce ContextLocal.get calls.
    ** 014 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 015 CA  20230724 Further reduce context-local usage.
    ** 016 CA  20230817 HandleOps.delete executing for a DELETE WIDGET-POOL is not considered an explicit DELETE
    **                  OBJECT (which would prevent deleting a temp-table/buffer part of a dataset).
    ** 017 CA  20231031 Added 'BlockDefinition' parameter to 'scopeStart'.
    ** 018 CA  20231216 Small performance improvement.
    **     CA  20231221 Refactored the WidgetPool scopeable support to register for processing as needed.
    ** 019 CA  20231216 Fixed regression related to widget pools used in OO (the issue was with the implicit
    **                  widget pool for a class with USE-WIDGET-POOL, for which the instance gets deleted
    **                  implicitly when a block finishes).
    ** 020 CA  20240105 Fixed regression in H019 - running an internal entry in a persistent procedure which
    **                  has an unnamed pool must push WidgetPool scope for this internal entry.
    ** 021 DDF 20240222 Avoid NPE since the head of the wa.unnamedPoolScopes might be null.
    ** 022 ES  20250109 Changed resources in WidgetPoolData from HashSet to LinkedHashSet.
    */
    /*
    ** 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.util.*;
    import java.util.logging.*;

    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.util.ObjectOps.ObjectHelper;
    import com.goldencode.p2j.util.ProcedureManager.ProcedureData;
    import com.goldencode.p2j.util.logging.*;

    /**
     * Widget-pool management class.
     * <p>
     * A widget pool is a a mechanism to explicitly define the scope at which resources get an
     * "implicit" DELETE OBJECT when the control flow causes the program to exit that particular
     * scope. At its most basic, a widget pool is just a list of resources that get deleted, an
     * optional name, an optional persistent flag and an association with a block scope that exists at
     *  a "top-level" block (external proc, internal proc, user-defined function and trigger).
     * A block scope will only open and close at top-level blocks (external program, procedure,
     * function or trigger). Inner blocks don't cause any scoping behavior.
     * <p>
     * All pools are context-local, as they are private to the user's session.  No session-level
     * widget-pool cleanup is implemented at this time. The current implementation of all the
     * poolable resources does not require any session-level cleanup processing that is driven by
     * widget pools. Should the implementation of any one of the poolable resources be modified to
     * be dependent upon session-level widget-pool cleanup, then this feature will have to be added.
     * <p>
     * A pool will contain only resources which are dynamically created (and must be removed using
     * a DELETE statement), and only a sub-set of them can be pooled. From the list of CREATE
     * statements, only the following statements accept a named pool:
     * <ul>
     * <li>CREATE BROWSE</li>
     * <li>CREATE BUFFER</li>
     * <li>CREATE CALL</li>
     * <li>CREATE DATASET</li>
     * <li>CREATE DATA-SOURCE</li>
     * <li>CREATE QUERY</li>
     * <li>CREATE SAX-ATTRIBUTES</li>
     * <li>CREATE SAX-READER</li>
     * <li>CREATE SAX-WRITER</li>
     * <li>CREATE SOAP-HEADER</li>
     * <li>CREATE SOAP-HEADER-ENTRYREF</li>
     * <li>CREATE TEMP-TABLE handle</li>
     * <li><pre>CREATE { BUTTON     | COMBO-BOX | CONTROL-FRAME | DIALOG-BOX     |
     *         EDITOR     | FILL-IN   | FRAME         | IMAGE          |
     *         MENU-ITEM  | RADIO-SET | RECTANGLE     | SELECTION-LIST |
     *         MENU       | SLIDER    | SUB-MENU      | TEXT           |
     *         TOGGLE-BOX | WINDOW    | VALUE ( string-expression )
     *       }</pre></li>
     * <li>CREATE X-DOCUMENT</li>
     * <li>CREATE X-NODEREF</li>
     * </ul>
     * Even 4GL has a default persistent unnamed pool associated with the user's session (and always
     * stands on the bottom of the stack of unnamed pools), we do not implement this. For P2J, if
     * there is no explicitly created unnamed pool, it means the unnamed persistent pool is in effect,
     * and we will not add the resources to it.  As 4GL doesn't offer any mechanism of accessing a
     * pool's resources, the pool for a resource or the pool stack, plus the resources already exist
     * at most for the duration of the user's session, adding them to this pool will add unnecessary
     * overhead in P2J.  Thus, in P2J a resource may be:
     * <ul>
     * <li>unpooled (no pool will contain it), if no unnamed pool exists when the CREATE statement was
     *     executed, as there is no default unnamed pool. This is the equivalent of "part of the
     *     default unnamed persistent pool" in 4GL.</li>
     * <li>part of only one pool (named if IN WIDGET-POOL clause is present or unnamed, if no
     *     IN WIDGET-POOL clause is present and there is an unnamed pool).</li>
     * </ul>
     * <p>
     * The following rules describe how named and unnamed pools are implemented:
     * <p>
     * <h3>Unnamed Pools</h3>
     * Each created unnamed pool (non-persistent) is active for the current scope.  In the current
     * scope, unnamed pools are "stacked".  A chain of CREATE WIDGET-POOL statements will create a set
     * of unnamed pools and they will be deleted in the reversed order of the creation, in LIFO order:
     * <pre>
     * 1: DEF VAR h1 AS HANDLE.
     * 2: DEF VAR h2 AS HANDLE.
     *
     * 3: CREATE WIDGET-POOL. // U1
     * 4: CREATE BUTTON h1.
     *
     * 5: CREATE WIDGET-POOL. // U2
     * 6: CREATE BUTTION h2.
     *
     * 7: DELETE WIDGET-POOL. // U2
     * 8: DELETE WIDGET-POOL. // U1
     * </pre>
     * At step 3 and 5, an unnamed pool is created, identified by U1 and U2. At step 4, the resource
     * referenced by handle <code>h1</code> (R1) is added to unnamed pool U1 and at step 6, the
     * resource referenced by handle <code>h2</code> (R2) is added to unnamed pool U2 (the last created
     * one). Step 7 will delete unnamed pool U2 (and resource R2), while step 8 will delete unnamed
     * pool U1 (and resource R1).
     * <p>
     * Once a persistent unnamed pool is created, a DELETE WIDGET-POOL statement will not be able to
     * delete it, if the persistent pool is on top of the stack.  For this reason, a
     * CREATE WIDGET-POOL PERSISTENT statement will do nothing if the unnamed pool at the top of the
     * stack is persistent too.
     * <p>
     * When the current scope ends, all non-persistent unnamed pools will be deleted. But, if there
     * is an unnamed persistent pool on the top of the stack for scope S2, this one will be "leaked"
     * to the previous scope, S1.  The reason for this is that if scope S1 has an active unnamed
     * non-persistent pool, then this pool must be deleted when S1 ends.  There are no restriction
     * for S1 to already have its own persistent unnamed pool. Thus, when S2 ends and leaks the top
     * persistent unnamed pool to S1, will result in S1 to have more than one persistent pool. But
     * this will not alter how persistent pools behave for S1: non-persistent unnamed pools can still
     * be created on top of the persistent unnamed pool, and unnamed pool deletion will stop when the
     * first persistent unnamed pool is back on the top of the stack.
     * <pre>
     *  1: CREATE WIDGET-POOL. // U1
     *
     *  2: PROCEDURE proc0.
     *  3: CREATE WIDGET-POOL PERSISTENT. // U2
     *  4: // create resource R1
     *  5: DELETE WIDGET-POOL.
     *  6: END.
     *
     *  7: RUN proc0.
     *  8: DELETE WIDGET-POOL.
     *  9: CREATE WIDGET-POOL. // U3
     * 10: // create resource R2
     * 11: DELETE WIDGET-POOL.
     * 12: DELETE WIDGET-POOL.
     *
     * 13: CREATE WIDGET-POOL. // U4
     * 14: // create resource R3
     *
     * 15: CREATE WIDGET-POOL PERSISTENT. // U5
     * 16: // create resource R4
     * 17: DELETE WIDGET-POOL.
     * </pre>
     * <ul>
     * <li>step 1 creates an unnamed widget pool U1. The stack of unnamed pools for scope of the
     *     external program (S1) contains only U1.</li>
     * <li>step 7 executes <code>proc0</code> which creates unnamed pool U2. On step 5, the active
     *     pool contains resource R1, but will not be deleted. After <code>proc0</code> ends, pool U2
     *     is placed on top of the stack for scope S1, and the pool stack looks like [ U2 -&gt; U1 ].
     *     Resource R2 will not be deleted.</li>
     * <li>step 8 will try to delete the top of the stack, but U2 is persistent, so will be a no-op.
     *     </li>
     * <li>step 9 creats a new unnamed pool in scope S1, U3, and puts it on the top of the unnamed
     *     pool stack for scope S1. The stack for S1 will look like [ U3 -&gt; U2 -&gt; U1 ]. </li>
     * <li>step 10 will create resource R2 and place it in unnamed pool U3 (as this is on top of the
     *     stack for scope S1).</li>
     * <li>step 11 will delete the unnamed pool on the top of the stack for scope S1, which is U3 and
     *     is non-persistent. This will delete resource R2 too.</li>
     * <li>step 12 will try to delete the unnamed pool on the top of the stack for scope S1, which
     *     now is U2. But U2 is persistent, thus will not be deleted.</li>
     * <li>step 13 will create a new unnamed pool, U4, and place it on the top of stack for S1. The
     *     stack of unnamed pools for S1 will look like: [ U4 -&gt; U2 -&gt; U1 ]</li>
     * <li>step 14 will create resource R3 and place it in pool U4 (top of stack for S1).</li>
     * <li>step 15 will create a new persistent unnamed pool, U5, for scope S1. The stack for S1 will
     *     look like: [ U5 -&gt; U4 -&gt; U2 -&gt; U1 ].</li>
     * <li>step 16 will create resource R4 and place it in pool U5 (top of stack for S1).</li>
     * <li>step 17 will try to delete the top unnamed pool for S1, U5, but this is persistent, so it
     *     will not be deleted. After this, pools U4, U2 and U1 remain hidden. When S1 ends, only the
     *     non-persistent unnamed pools (U1 and U4) will be deleted. Pools U2 and U5 will survive, and
     *     pool U5 will be placed on the top of the stack for the previous scope, U0.</li>
     * </ul>
     * <p>
     * This unnamed pool property which doesn't allow a persistent unnamed pool to be deleted will
     * force it to exist for the duration of the user's session.
     *
     * <h3>Named Pools</h3>
     * At a certain time, no more than one named pool may exist with a given name. When a scope ends,
     * all non-persistent named pools will be deleted. A persistent pool will survive until someone
     * calls a DELETE WIDGET-POOL statement for that persistent pool; this means a persistent pool
     * will survive even after the external procedure which created it is finished or gets deleted.
     *
     * <h3>Resource management</h3>
     * When a resource (which can be pooled) is created, its IN WIDGET-POOL clause (if present) is
     * validated first. If a named pool is specified and it can't be found/isn't valid, then the
     * resource will not be created.
     * <p>
     * Adding a resource to an unnamed pool will search for the first active unnamed pool, either in
     * current scope or any previous scope. Once a resource is added to a named or unnamed pool,
     * it will survive until that pool gets deleted (or the resource is deleted).
     * <p>
     * When an external procedure is ran persistent, all resources will survive until the procedure is
     * deleted; when the procedure is deleted, it will delete all the resources in the non-persistent
     * pools.
     */
    public class WidgetPool
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(WidgetPool.class);

       /** A marker instance for an empty dequeu. */
       private static final Deque<Integer> EMPTY_DEQUE = new ArrayDeque<>();

       /** The widget pools are private per each context. */
       private static final ContextLocal<WorkArea> local = new ContextLocal<WorkArea>()
       {
          protected WorkArea initialValue()
          {
             return new WorkArea();
          };
       };

       /**
        * Delete an unnamed pool.
        */
       public static void delete()
       {
          delete((character) null);
       }

       /**
        * Delete a pool with the given name.
        *
        * @param    name
        *           The name of the newly created pool. Specify <code>null</code> is the pool is
        *           unnamed.
        */
       public static void delete(String name)
       {
          delete(name == null ? (character) null : new character(name));
       }

       /**
        * Delete a pool with the given name.
        *
        * @param    name
        *           The name of the newly created pool. Specify <code>null</code> is the pool is
        *           unnamed.
        */
       public static void delete(character name)
       {
          if (name != null && name.isUnknown())
          {
             final String errMsg = "Unable to evaluate widget pool name";
             ErrorManager.recordOrThrowError(3177, errMsg, false);
             return;
          }

          WorkArea wa = locate();
          // ensure the current top-level block has widget pool support
          BlockDefinition block = wa.tm.nearestTopLevel();
          if (!block.widgetPool)
          {
             wa.scopeStart(block);
          }

          boolean unnamed = (name == null);
          String sname = (unnamed ? null : name.toStringMessage());
          WidgetPoolData data = findActivePool(wa, sname, true);

          if (data == null)
          {
             if (!unnamed)
             {
                final String msg = "Attempt to delete widget-pool %s, but it doesn't exist";
                ErrorManager.recordOrThrowError(4117, String.format(msg, sname), false);
             }

             return;
          }

          deletePool(wa, data, false);
       }

       /**
        * Create an unnamed pool.
        */
       public static void create()
       {
          create((character) null);
       }

       /**
        * Create an unnamed pool.
        *
        * @param    persistent
        *           <code>true</code> if the pool is persistent.
        */
       public static void create(boolean persistent)
       {
          create((character) null, persistent);
       }

       /**
        * Create a pool with the given name.
        *
        * @param    name
        *           The name of the newly created pool. Specify <code>null</code> is the pool is
        *           unnamed.
        */
       public static void create(character name)
       {
          create(name, false);
       }

       /**
        * Create a pool with the given name.
        *
        * @param    name
        *           The name of the newly created pool. Specify <code>null</code> is the pool is
        *           unnamed.
        */
       public static void create(String name)
       {
          create(name == null ? (character) null : new character(name), false);
       }

       /**
        * Create a pool with the given name and the given "persistent" option.
        *
        * @param    name
        *           The name of the newly created pool. Specify <code>null</code> is the pool is
        *           unnamed.
        * @param    persistent
        *           <code>true</code> if the pool is persistent.
        */
       public static void create(String name, boolean persistent)
       {
          create(name == null ? (character) null : new character(name), persistent);
       }

       /**
        * Create a pool with the given name and the given "persistent" option.
        *
        * @param    name
        *           The name of the newly created pool. Specify <code>null</code> is  the pool is
        *           unnamed.
        * @param    persistent
        *           <code>true</code> if the pool is persistent.
        */
       public static void create(character name, boolean persistent)
       {
          if (name != null)
          {
             if (name.isUnknown())
             {
                final String errMsg = "Unable to evaluate widget pool name";
                ErrorManager.recordOrThrowError(3177, errMsg, false);
                return;
             }
             else if (name.toStringMessage().length() == 0)
             {
                final String errMsg = "Attempt to create a widget-pool with a zero-length name";
                ErrorManager.recordOrThrowError(4115, errMsg, false);
                return;
             }
          }

          boolean unnamed = (name == null);
          String sname = (unnamed ? null : name.toStringMessage());
          WorkArea wa = locate();

          // ensure the current top-level block has widget pool support
          BlockDefinition block = wa.tm.nearestTopLevel();
          if (!block.widgetPool)
          {
             wa.scopeStart(block);
          }

          WidgetPoolData data = findActivePool(wa, sname, true);

          if (data != null && !unnamed)
          {
             // error is shown only in case there is already a named pool
             final String msg = "Attempt to create widget-pool %s, but it already exists";
             ErrorManager.recordOrThrowError(4116, String.format(msg, sname), false);

             return;
          }

          // any existing unnamed pools will be hidden in this scope.
          data = newPool(wa, sname, persistent);
       }

       /**
        * Adds the specified resource to the pool. Although this is exposed for the public, do not
        * used outside of P2J code. Is made public because access to it is needed from any P2J
        * package.
        *
        * @param    resource
        *           The resource to be pooled.
        * @param    name
        *           The pool name, set to <code>null</code> if the unnamed pool is to be used.
        *
        * @throws   IllegalStateException
        *           The exception is throw if the call of this method targets a named pool (the
        *           <code>name</code> parameter is specified) and either the name is unknown or a pool
        *           with the specified name does not exist.
        *           <p>
        *           If the exception is thrown, there must be an error in the code which creates the
        *           resource. That code must validate the pool name (by calling
        *           {@link WidgetPool#validWidgetPool}) before any other resource-related validation
        *           and before the resource is created.
        */
       public static void addResource(WrappedResource resource, character name)
       {
          addResource(null, resource, name);
       }

       /**
        * Adds the specified resource to the pool. Although this is exposed for the public, do not
        * used outside of P2J code. Is made public because access to it is needed from any P2J
        * package.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    resource
        *           The resource to be pooled.
        * @param    name
        *           The pool name, set to <code>null</code> if the unnamed pool is to be used.
        *
        * @throws   IllegalStateException
        *           The exception is throw if the call of this method targets a named pool (the
        *           <code>name</code> parameter is specified) and either the name is unknown or a pool
        *           with the specified name does not exist.
        *           <p>
        *           If the exception is thrown, there must be an error in the code which creates the
        *           resource. That code must validate the pool name (by calling
        *           {@link WidgetPool#validWidgetPool}) before any other resource-related validation
        *           and before the resource is created.
        */
       public static void addResource(WorkArea wa, WrappedResource resource, character name)
       {
          if (wa == null)
          {
             wa = locate();
          }

          BlockDefinition blk = wa.tm.nearestTopLevel();
          if (!blk.widgetPool)
          {
             wa.scopeStart(blk);
          }

          boolean unnamed = (name == null);
          String sname = (unnamed ? null : name.toStringMessage());

          WidgetPoolData data = (name != null && name.isUnknown() ? null
                                                                  : findActivePool(wa, sname, false));
          if (data == null)
          {
             if (!unnamed)
             {
                final String msg =
                   "Could not determine named pool '%s'. You need to validate before adding the " +
                   "resource!";
                throw new IllegalStateException(String.format(msg, sname));
             }

             // no pool is active, return
             return;
          }

          data.addResource(resource);

          // save the resource, to be able to determine its pool later
          wa.poolForResource.put(resource, data.id);
       }

       /**
        * Determine if the specified <code>name</code> targets a known widget pool.
        * <p>
        * A missing unnamed pool will be an error.
        *
        * @param    name
        *           The pool name, set to <code>null</code> if the unnamed pool is to be used.
        *
        * @return   <code>true</code> if a pool was found with the specified name.
        */
       public static boolean validWidgetPool(character name)
       {
          // an unnamed widget-pool name is always valid.
          if (name == null)
          {
             return true;
          }

          if (name.isUnknown())
          {
             final String errMsg = "Unable to evaluate widget pool name";
             ErrorManager.recordOrThrowError(3177, errMsg, false);
             return false;
          }

          String sname = name.toStringMessage();

          WidgetPoolData data = findActivePool(locate(), sname, false);
          if (data == null)
          {
             // error is shown only if named pool is not found
             final String msg =
                "Trying to create a widget in widget-pool '%s', but pool does not exist";
             ErrorManager.recordOrThrowError(3178, String.format(msg, sname), false);

             return false;
          }

          return true;
       }

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

       /**
        * Locate the context's {@link WorkArea} instance.
        *
        * @return   See above.
        */
       private static WorkArea locate()
       {
          WorkArea wa = local.get();
          if (wa.tm == null)
          {
             wa.tm = TransactionManager.getTransactionHelper();
             wa.pm = ProcedureManager.getProcedureHelper();
             wa.oh = ObjectOps.getObjectHelper();
          }

          return wa;
       }

       /***
        * Create a new pool and register it in the appropriate maps/sets.
        *
        * @param    wa
        *           The context-local state in {@link WorkArea}.
        * @param    name
        *           The name of this pool. Is <code>null</code> if the pool is unnamed.
        * @param    persistent
        *           Flag identifying if this pool is persistent.
        *
        * @return   The created {@link WidgetPoolData definition} for the new pool.
        */
       private static WidgetPoolData newPool(WorkArea wa, String name, boolean persistent)
       {
          boolean unnamed = (name == null);

          if (unnamed && persistent)
          {
             Integer top = wa.unnamedPoolScopes.peek().peek();
             if (top != null && wa.persistentPools.contains(top))
             {
                // if the top pool is persistent, then do not create another one. even if 4GL might
                // create a different pool, there is no reason to do so, because we can't pop from
                // the stack a persistent unnamed pool.
                return wa.pools.get(top);
             }
          }

          int id = Utils.uniqueId();
          WidgetPoolData data = new WidgetPoolData(id, name);
          wa.pools.put(id, data);

          if (unnamed)
          {
             wa.unnamedPools.add(id);
          }
          else
          {
             wa.namedPools.put(data.key, id);
          }

          if (persistent)
          {
             wa.persistentPools.add(id);
          }

          if (unnamed)
          {
             Deque<Integer> ups = wa.unnamedPoolScopes.peek();
             if (ups == EMPTY_DEQUE)
             {
                ups = new ArrayDeque<>();
                wa.unnamedPoolScopes.pop();
                wa.unnamedPoolScopes.push(ups);
             }
             ups.push(id);
          }
          else
          {
             Set<Integer> nps = wa.namedPoolScopes.peek();
             if (nps == Collections.EMPTY_SET)
             {
                nps = new HashSet<>();
                wa.namedPoolScopes.pop();
                wa.namedPoolScopes.push(nps);
             }
             nps.add(id);
          }

          // collect the pools for each external program
          Object oThis = wa.pm._thisProcedure();

          Set<Integer> thisIds = wa.poolsPerExtProg.get(oThis);
          if (thisIds == null)
          {
             thisIds = new HashSet<Integer>();
             wa.poolsPerExtProg.put(oThis, thisIds);
          }
          thisIds.add(id);

          // save the procedure which created this pool
          wa.procForPool.put(id, oThis);

          return data;
       }

       /**
        * Filter the set of pool IDs and from it, delete only the non-persistent widget-pools.
        *
        * @param    wa
        *           The context-local state in {@link WorkArea}.
        * @param    ids
        *           A set of pool IDs.
        * @param    fromScopeFinished
        *           Flag indicating that this is called from {@link WorkArea#scopeFinished}.
        */
       private static void deleteNonPersistentPools(WorkArea wa, Set<Integer> ids, boolean fromScopeFinished)
       {
          if (ids.isEmpty())
          {
             return;
          }

          // determine the non-persistent pools
          Set<Integer> nonPersistent = new HashSet<>(ids);
          nonPersistent.removeAll(wa.persistentPools);

          // delete the non-persistent pools; persistent pools will survive, even after a procedure
          // ran non-persistent is finished (or deleted).
          for (int id : nonPersistent)
          {
             WidgetPoolData data = getPool(wa, id);
             deletePool(wa, data, fromScopeFinished);
          }
       }

       /**
        * Delete the specified pool. Note that an unnamed persistent pool will not be deleted.
        * <p>
        * When a pool is deleted, all its contained resources will be deleted too.
        *
        * @param    wa
        *           The context-local state in {@link WorkArea}.
        * @param    data
        *           The definition of the pool to be deleted.
        * @param    fromScopeFinished
        *           Flag indicating that this is called from {@link WorkArea#scopeFinished}.
        */
       private static void deletePool(WorkArea wa, WidgetPoolData data, boolean fromScopeFinished)
       {
          int id = data.id;

          boolean unnamed    = wa.unnamedPools.contains(id);
          boolean named      = wa.namedPools.containsKey(data.key);
          boolean persistent = wa.persistentPools.contains(id);

          if (unnamed && persistent)
          {
             // the unnamed persistent pool can't be deleted
             return;
          }

          // ensure the current top-level block has widget pool support
          boolean usePrev = wa.pm.inNotifications();
          fromScopeFinished = fromScopeFinished || (wa.oh.inScopeFinished() && wa.tm.nearestTopLevel().widgetPool);
          BlockDefinition block = usePrev ? wa.tm.prevTopLevel() : wa.tm.nearestTopLevel();
          if (!block.widgetPool)
          {
             Set<Integer> namedScope = null;
             Deque<Integer> unnamedScope = null;
             // if we are executing current block's WidgetPool.scopeFinished, we need to pop/push the block's scope
             // otherwise, even if we are in notifications, the current block's scope was either non-existent or
             // already popped
             if (usePrev && fromScopeFinished)
             {
                namedScope = wa.namedPoolScopes.pop();
                unnamedScope = wa.unnamedPoolScopes.pop();
             }
             wa.scopeStart(block);
             if (usePrev && fromScopeFinished)
             {
                wa.namedPoolScopes.push(namedScope);
                wa.unnamedPoolScopes.push(unnamedScope);
             }
          }

          // delete the resources; this will clean the resource-related maps
          data.deleteResources();

          // clean up the registry
          wa.pools.remove(id);
          if (unnamed)
          {
             wa.unnamedPools.remove(id);
          }
          else
          {
             wa.namedPools.remove(data.key);
          }
          wa.persistentPools.remove(id);
          // remove from the external program's set too.
          Object oThis = wa.procForPool.remove(id);
          Set<Integer> ids = wa.poolsPerExtProg.get(oThis);
          if (ids != null)
          {
             // ids may be null if the program which created this pool has been deleted.
             ids.remove(id);
          }

          // the other maps/sets
          if (unnamed)
          {
             Deque<Integer> ups = wa.unnamedPoolScopes.peek();
             if (ups != null && !ups.isEmpty())
             {
                ups.remove(id);
             }
          }
          else if (!wa.namedPoolScopes.isEmpty())
          {
             // this might be from a previous scope, so need to walk the stack
             for (Set<Integer> namedPools : wa.namedPoolScopes)
             {
                if (namedPools.remove(id))
                {
                   // terminate on first found
                   break;
                }
             }
          }
       }

       /**
        * Determine the active pool. If <code>name</code> is not <code>null</code>, than the active
        * named pool will be used.
        *
        * @param    wa
        *           The context-local state in {@link WorkArea}.
        * @param    name
        *           The pool name. May be <code>null</code> for an unnamed pool.
        * @param    currentScope
        *           <code>true</code> when search must be done only in current scope.
        * @return   <code>null</code> if no pool is found, else its
        *           {@link WidgetPoolData definition} is returned.
        */
       private static WidgetPoolData findActivePool(WorkArea wa, String name, boolean currentScope)
       {
          Integer id = null;
          boolean unnamed = (name == null);

          if (!unnamed)
          {
             // get the named pool
             String key = name.toLowerCase();
             id = wa.namedPools.get(key);
          }
          else if (wa.unnamedPools.size() > 0)
          {
             // if no registered unnamed pool, then no reason to search down the stack

             // get the unnamed pool for this scope; will be null if no unnamed in stack. always
             // check down the stack(s) until one is found
             for (Deque<Integer> stack : wa.unnamedPoolScopes)
             {
                if (!stack.isEmpty())
                {
                   id = stack.peek();
                   break;
                }

                if (currentScope)
                {
                   // exit on first scope, if needed
                   break;
                }
             }
          }

          return (id == null ? null : getPool(wa, id));
       }

       /**
        * Get the {@link WidgetPoolData pool} with the given ID.
        *
        * @param    wa
        *           Context-local work area.
        * @param    id
        *           The pool's ID.
        *
        * @return   See above.
        */
       private static WidgetPoolData getPool(WorkArea wa, int id)
       {
          return wa.pools.get(id);
       }

       /**
        * Helper to expose APIs which have direct access to the context-local state.
        */
       static class WidgetPoolHelper
       {
          /** The {@link WorkArea} instance. */
          private final WorkArea wa;

          /**
           * Create a new instance and associate the given WorkArea instance.
           *
           * @param    wa
           *           The {@link WorkArea} instance.
           */
          public WidgetPoolHelper(WorkArea wa)
          {
             this.wa = wa;
          }

          /**
           * Adds the specified resource to the pool. Although this is exposed for the public, do not
           * used outside of P2J code. Is made public because access to it is needed from any P2J
           * package.
           *
           * @param    resource
           *           The resource to be pooled.
           * @param    name
           *           The pool name, set to <code>null</code> if the unnamed pool is to be used.
           *
           * @throws   IllegalStateException
           *           The exception is throw if the call of this method targets a named pool (the
           *           <code>name</code> parameter is specified) and either the name is unknown or a pool
           *           with the specified name does not exist.
           *           <p>
           *           If the exception is thrown, there must be an error in the code which creates the
           *           resource. That code must validate the pool name (by calling
           *           {@link WidgetPool#validWidgetPool}) before any other resource-related validation
           *           and before the resource is created.
           */
          public void addResource(WrappedResource resource, character name)
          {
             WidgetPool.addResource(wa, resource, name);
          }

          /**
           * Checks if the given referent has an unnamed pool registered.
           *
           * @param    referent
           *           The referent to check.
           *
           * @return   <code>true</code> if there is at least one unnamed pool for this referent.
           */
          public boolean hasUnnamedPool(Object referent)
          {
             Deque<Integer> up = wa.unnamedProcPools.get(referent);

             return up != null && !up.isEmpty();
          }

          /**
           * Delegates the call to {@link WorkArea#scopeStart}
           *
           * @param    block
           *           The explicit block definition which required this notification.
           */
          public void scopeStart(BlockDefinition block)
          {
             wa.scopeStart(block);
          }

          /**
           * Delegates the call to {@link WorkArea#scopeFinished(Object, boolean, boolean)}
           */
          public void scopeFinished(Object oThis, boolean external, boolean persistent)
          {
             wa.scopeFinished(oThis, external, persistent);
          }

          /**
           * Remove the given resource from its pool, if it was pooled.
           *
           * @param    resource
           *           The resource to be removed.
           */
          public void removeResource(WrappedResource resource)
          {
             Integer poolId = wa.poolForResource.remove(resource);

             // nothing more to do if the resource wasn't pooled
             if (poolId == null)
             {
                return;
             }

             WidgetPoolData data = getPool(wa, poolId);
             if (data == null)
             {
                // this resource wasn't pooled, so nothing to do
                return;
             }

             data.removeResource(resource);
          }

          /**
           * Delete all the non-persistent widget-pools associated with the specified procedure.
           *
           * @param    proc
           *           The external procedure instance.
           */
          public void deleteWidgetPools(Object proc)
          {
             // an external procedure was deleted, this acts like it's scope has finished.
             Set<Integer> ids = wa.poolsPerExtProg.get(proc);
             if (ids != null)
             {
                deleteNonPersistentPools(wa, ids, false);

                // remove this from the map, is no longer needed
                wa.poolsPerExtProg.remove(proc);
             }
             wa.unnamedProcPools.remove(proc);
          }
       }

       /**
        * Container for the widget-pool data.
        */
       private static class WorkArea
       {
          /** Helper to use the TM without any context local lookups. */
          private TransactionManager.TransactionHelper tm = null;

          /** Helper to use the ProcedureManager without any context local lookups. */
          private ProcedureManager.ProcedureHelper pm = null;

          /** Helper to use the ObjectOps without any context local lookups. */
          private ObjectHelper oh = null;

          /** A registry of pools, per its ID. */
          private final Map<Integer, WidgetPoolData> pools = new HashMap<>();

          /** Pool names are global, so keep a map of them (key is always lowercased). */
          private final Map<String, Integer> namedPools = new HashMap<>();

          /** A set containing the ID of all unnamed pools. */
          private final Set<Integer> unnamedPools = new HashSet<>();

          /** A set containing the ID of all persistent pools. */
          private final Set<Integer> persistentPools = new HashSet<>();

          /** Identify the pool to which a resource was added (if was pooled). */
          private final Map<WrappedResource, Integer> poolForResource = new HashMap<>();

          /** Identify the external procedure used to create the pool. */
          private final Map<Integer, Object> procForPool = new HashMap<>();

          /** Collect all the created pools for an external program. */
          private final Map<Object, Set<Integer>> poolsPerExtProg = new IdentityHashMap<>();

          /** Keep the scopes of named pools. */
          private final Deque<Set<Integer>> namedPoolScopes = new ArrayDeque<>();

          /** Keep the scopes of unnamed pools (only one can be active at a certain time). */
          private final Deque<Deque<Integer>> unnamedPoolScopes = new ArrayDeque<>();

          /** A map of the unnamed pools active at the time the persistent pool has executed. */
          private final Map<Object, Deque<Integer>> unnamedProcPools = new IdentityHashMap<>();

          /** A cached set instance used by {@link #scopeFinished}. */
          private final Set<Integer> ids = new HashSet<>();

          /**
           * Notify a new scope has started; invoked by {@link ProcedureManager}'s scoping support when
           * a new scope is started.
           * <p>
           * Notification will be performed only on the start of a top-level block (external program,
           * procedure, function or trigger).
           * <p>
           * No default unnamed pool will be created. An unnamed pool must be explicitly created to be
           * used.
           *
           * @param    block
           *           The explicit block definition which required this notification.
           */
          public void scopeStart(BlockDefinition block)
          {
             if (block.type == BlockType.UNKNOWN)
             {
                // do nothing for the global block (like for appservers, where arguments are processed)
                return;
             }

             block.widgetPool = true;

             Deque<Integer> ups = EMPTY_DEQUE;

             if (block.type != BlockType.EXTERNAL_PROC)
             {
                // case 1: even if called from 'scopeFinished', still uses 'this-procedure', as when this is
                // called, ProcedureManager.scopeFinished already popped the currently processed 'this-procedure'.
                // so we target the previous scope 'naturally'

                // case 2: when implicit object deletion happens, this will be performed from within 'popScope',
                // but before ProcedureManager.scopeFinished happens.  So THIS-PROCEDURE is still on stack - in
                // such cases, we need to use 'prev-this-procedure'.

                ProcedureData pd = oh.inScopeFinished() ? pm.prevThisProcedureData() : pm.thisProcedureData();
                if (pd.isPersistent())
                {
                   Object referent = pd.getReferent();
                   if (unnamedProcPools.containsKey(referent))
                   {
                      ups = new ArrayDeque<>();
                      ups.addAll(unnamedProcPools.get(referent));
                   }
                }
             }

             namedPoolScopes.push(Collections.emptySet());
             unnamedPoolScopes.push(ups);
          }

          /**
           * Notify a scope has finished; invoked by {@link ProcedureManager}'s scoping support when a
           * scope is finished.
           * <p>
           * This will delete the created non-persistent pools, but only if this is not a notification
           * of an external procedure which was ran persistent.
           * <p>
           * Notification will be performed only on the end of a top-level block (external program,
           * procedure, function or trigger).
           *
           * @param    oThis
           *           Current THIS-PROCEDURE handle.
           * @param    external
           *           Flag indicating that this is an external procedure.
           * @param    persistent
           *           Flag indicating that this external procedure is ran persistent.
           */
          public void scopeFinished(Object oThis, boolean external, boolean persistent)
          {
             ids.clear();

             // compute the set of active pools
             Set<Integer> topNamed = namedPoolScopes.peek();
             if (!topNamed.isEmpty())
             {
                ids.addAll(topNamed);
             }
             Deque<Integer> topUnnamed = unnamedPoolScopes.peek();
             if (!topUnnamed.isEmpty())
             {
                ids.addAll(topUnnamed);
             }

             if (!external && unnamedProcPools.containsKey(oThis))
             {
                // the external program's pool get deleted when the extprog gets deleted
                Deque<Integer> oThisPools = unnamedProcPools.get(oThis);
                if (!oThisPools.isEmpty())
                {
                   ids.removeAll(oThisPools);
                   topUnnamed.removeAll(oThisPools);
                }
             }

             if (!(external && persistent))
             {
                deleteNonPersistentPools(this, ids, true);

                if (external)
                {
                   poolsPerExtProg.remove(oThis);
                   unnamedProcPools.remove(oThis);
                }
             }
             else
             {
                // if finishing an external program ran in persistent mode, let the pools survive;
                // they will be deleted when the external program gets deleted
             }

             // this must be done at this step - deleteNonPersistentPools is dependent on it
             namedPoolScopes.pop();
             Deque<Integer> unnamedIds = unnamedPoolScopes.pop();

             if (!persistent && !unnamedIds.isEmpty())
             {
                // we need to ensure the previous top-level block has widgetPool support
                // this needs to be done after we remove the current widget pool state for the current block
                BlockDefinition block = tm.prevTopLevel();
                if (block != null && !block.widgetPool)
                {
                   block.widgetPool = true;

                   Deque<Integer> ups = EMPTY_DEQUE;
                   ProcedureData prev = pm.prevThisProcedureData();
                   if (block.type != BlockType.EXTERNAL_PROC && prev.isPersistent())
                   {
                      Object referent = prev.getReferent();
                      if (unnamedProcPools.containsKey(referent))
                      {
                         ups = new ArrayDeque<>();
                         ups.addAll(unnamedProcPools.get(referent));
                      }
                   }

                   namedPoolScopes.push(Collections.emptySet());
                   unnamedPoolScopes.push(ups);
                }
             }

             // at the time of the pop, all the non-persistent pools have been removed. now, check if
             // a persistent unnamed pool is in effect. if so, "leak" it to the previous scope.
             // any pool created in a persistent procedure will not be leaked to a previous scope. The
             // persistent procedure's pools (named or unnamed, persistent or not) will be deleted when
             // the persistent procedure's handle gets deleted.
             if (!persistent && !unnamedPoolScopes.isEmpty() && !unnamedIds.isEmpty())
             {
                // what is left must be a persistent unnamed pool.
                int id = unnamedIds.pop();
                if (!unnamedIds.isEmpty())
                {
                   if (LOG.isLoggable(Level.SEVERE))
                   {
                      final String msg = "Scope [%d] should have no unnamed pools left!";
                      LOG.log(Level.SEVERE, String.format(msg, unnamedPoolScopes.size() + 1));
                   }
                }

                Deque<Integer> prevScope = unnamedPoolScopes.peek();
                int prev = prevScope.isEmpty() ? -1 : prevScope.peek();
                if (persistentPools.contains(prev))
                {
                   // merge all from this persistent pool to the prev pool
                   WidgetPoolData prevData = pools.get(prev);
                   WidgetPoolData currentData = pools.get(id);

                   for (WrappedResource res : currentData.resources)
                   {
                      prevData.addResource(res);
                      poolForResource.put(res, prev);
                   }

                   // remove this pool, as all its resources were leaked to the previous scope
                   persistentPools.remove(id);
                   pools.remove(id);
                   unnamedPools.remove(id);
                   Set<Integer> pools = poolsPerExtProg.get(oThis);
                   if (pools != null)
                   {
                      poolsPerExtProg.get(oThis).remove(id);
                   }
                   procForPool.remove(id);
                }
                else
                {
                   if (!pools.containsKey(id))
                   {
                      throw new RuntimeException("The WidgetPool " + id + " has already been deleted!");
                   }
                   // "leak" it to the prev scope
                   Deque<Integer> ups = unnamedPoolScopes.peek();
                   if (ups == EMPTY_DEQUE)
                   {
                      ups = new ArrayDeque<>();
                      unnamedPoolScopes.pop();
                      unnamedPoolScopes.push(ups);
                   }
                   ups.push(id);

                   // re-adjust the pool's creator as the one from the prev scope
                   Object referent = pm._thisProcedure();
                   Set<Integer> pools = poolsPerExtProg.get(referent);
                   if (pools == null)
                   {
                      pools = new HashSet<>();
                      poolsPerExtProg.put(referent, pools);
                   }
                   pools.add(id);

                   procForPool.put(id, referent);
                }
             }
             else if (persistent && external && !unnamedIds.isEmpty())
             {
                unnamedProcPools.put(oThis, unnamedIds);
             }
          }
       }

       /**
        * The data needed for a widget pool, named or unnamed.
        */
       private static class WidgetPoolData
       {
          /** Set of resources created in this widget pool. */
          private final Set<WrappedResource> resources = new LinkedHashSet<>();

          /** The ID of this widget pool. */
          private final int id;

          /** This pool's key (to identify it by name, if this is a named pool). */
          private final String key;

          /**
           * Create a new widget pool.
           *
           * @param    id
           *           The ID of this widget pool.
           * @param    name
           *           The name of this widget pool. <code>null</code> if unnamed.
           */
          public WidgetPoolData(int id, String name)
          {
             this.id = id;
             this.key = (name == null ? null : name.toLowerCase());
          }

          /**
           * Add a new resource to this widget pool.
           *
           * @param    resource
           *           The resource to be added.
           */
          public void addResource(WrappedResource resource)
          {
             resources.add(resource);
          }

          /**
           * Remove the resource from this widget pool.
           *
           * @param    resource
           *           The resource to be removed.
           */
          public void removeResource(WrappedResource resource)
          {
             resources.remove(resource);
          }

          /**
           * Delete all the resources from this pool.
           */
          public void deleteResources()
          {
             // use a copy, as the resource-related maps/sets get cleaned when the resource is deleted
             Set<WrappedResource> copy = new LinkedHashSet<WrappedResource>(resources);

             for (WrappedResource res : copy)
             {
                if (res.valid())
                {
                   HandleOps.delete(new handle(res), false);
                }
             }
          }
       }
    }