# TransactionManager.java

    /*
    ** Module   : TransactionManager.java
    ** Abstract : transaction/block properties helper methods
    **
    ** Copyright (c) 2005-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
    ** 001 GES 20050809   @22096 Created initial version, supporting
    **                           transaction/block properties with full
    **                           support for nested blocks, undo and retry.
    ** 002 GES 20050825   @22242 Added global finish processing.
    ** 003 ECF 20050920   @22888 Various changes to support database
    **                           transaction processing. Added registration
    **                           and use of ScopeableFactory interface.
    **                           Reversed order of Commitable notifications.
    **                           Delayed notification of opening scope until
    **                           after transaction (if any) is begun.
    ** 004 GES 20051005   @22999 Added master commit and master finish
    **                           notifications that are guaranteed to occur
    **                           after all other individual commits/finishes.
    ** 005 ECF 20051007   @23005 More changes to support database transaction
    **                           processing. Changed isTransaction()'s access
    **                           to public. Removed unused, private variant of
    **                           isFullTransaction() and added public, no-arg
    **                           variant of this method.
    ** 006 GES 20051007   @23007 Added rollback notification for the updated
    **                           Commitable interface AND added master
    **                           rollback support.
    ** 007 ECF 20051104   @23234 Changes to support iterate notifications.
    **                           Finalizables are now notified at each call to
    **                           the iterate() method.
    ** 008 ECF 20051104   @23261 Update to honorError() method. Added code to
    **                           report to stderr the error messages embedded
    **                           in an exception chain, in the case where the
    **                           error is not rethrown to a higher block.
    ** 009 ECF 20051108   @23277 Add deferred commit/rollback error handling.
    **                           This defers throwing an exception thrown by
    **                           a commit/rollback notification callback and
    **                           caught during processCommit/Rollback until it
    **                           is safe to do so. Logging added as well.
    ** 010 ECF 20051108   @23397 Added isTopLevelBlock() method. Fixed
    **                           popScope(), which was popping its scope too
    **                           early. Use peek instead, then pop at end of
    **                           method.
    ** 011 ECF 20051202   @23596 Allow multiple master Commitables and
    **                           Finalizables to be registered. Also extended
    **                           deferred error handling to Finalizable
    **                           notification processing.
    ** 012 GES 20060123   @24019 Moved to context-local storage and fixed a
    **                           couple of deferredError bugs.
    ** 013 GES 20060128   @24114 Added isGlobalBlock().
    ** 014 GES 20060130   @24142 Added processing quit state flag for global
    **                           exiting.
    ** 015 ECF 20060130   @24225 Added registerTopLevelFinalizable() method.
    **                           Also added logging to report push/pop scope
    **                           events.
    ** 016 GES 20060202   @24228 Added support for differentiating external
    **                           blocks (external procs) from other top-level
    **                           blocks (any proc/func/trigger).
    ** 017 GES 20060203   @24245 Changed method name makeBackup() to
    **                           blockSetup().  This is done because this
    **                           hook now has responsibilities to handle
    **                           iterate() to compensate for usage of explicit
    **                           continue (which would bypass the iterate()
    **                           call).
    ** 018 GES 20060204   @24278 Added batch notification support.
    ** 019 GES 20060207   @24329 Check interrupted status in strategic hooks
    **                           and throw a StopConditionException in the
    **                           case where the thread has its interrupted
    **                           status set.
    ** 020 ECF 20060206   @24344 Prevent infinite recursion loop in
    **                           ContextContainer.obtain. Was caused when
    **                           ScopeableFactory.createScopeable called into
    **                           TransactionManager while ContextContainer was
    **                           still initializing WorkArea.
    ** 021 GES 20060302   @24856 Added removeFinalizable() to facilitate
    **                           cleanup of resources before finished().
    ** 022 ECF 20060309   @24997 Modified auto-remove processing for master
    **                           commitables and finalizables. Auto-remove
    **                           processing was too aggressive;  it must occur
    **                           only for popScope for a full transaction.
    ** 023 GES 20060314   @25060 Moved to common logging infrastructure and
    **                           added major debug logging.
    ** 024 GES 20060322   @25193 New rollback() signature.
    ** 025 ECF 20060502   @25947 Added registerNearestLoopFinalizable()
    **                           method. Finds nearest enclosing block which
    **                           defines a loop and registers a Finalizable to
    **                           it.
    ** 026 GES 20060510   @26056 New state to externally query if an iteration
    **                           is being called from the top or the bottom of
    **                           the block.
    ** 027 GES 20060517   @26191 Added retry notification processing.
    ** 028 GES 20060518   @26274 Use an identity based map for undoables to
    **                           solve problem where two keys with the same
    **                           value can't be separately stored in the map.
    ** 029 GES 20060529   @26649 Iterate processing must trip processing
    **                           rollback if there is a pending rollback
    **                           since we may have gotten here via a NEXT
    **                           and otherwise the rollback may not occur.
    **                           Deferred rollback is also triggered before
    **                           getting to the labeled block in the case
    **                           where the block associated with the full
    **                           transaction is enclosed (more deeply nested)
    **                           in the labeled block.  The rollback is then
    **                           triggered at the full transaction level.
    ** 030 GES 20060608   @27024 Added a method to abort further processing
    **                           in a block in triggering a RETRY as part of
    **                           an UNDO statement (as opposed to processing
    **                           a RETRY from within a catch block).
    ** 031 GES 20060609   @27055 Converted deferred error processing to catch
    **                           and process Exception rather than the more
    **                           specific ErrorConditionException. This is
    **                           required whenever we have some unexpected
    **                           exception (an abnormal end).  Otherwise the
    **                           deferred error handling did not trigger and
    **                           the state of the TM is corrupted. This had
    **                           bad effects as the finally blocks called
    **                           popScope() up the stack.
    ** 032 ECF 20060610   @27089 Add processing of master transaction retries.
    **                           Finalizables registered for master
    **                           transaction processing were not receiving
    **                           retry notifications.
    ** 033 GES 20060626   @27565 Support silent error mode in the calling
    **                           method from triggerErrorInCaller().
    ** 034 GES 20060804   @28426 Modified block processing to ensure that
    **                           the Finalizable.iterate() notifications only
    **                           occur from the top of the block.
    ** 035 GES 20060809   @28514 Added a new concept of a lightweight block
    **                           which allows the differentiation between
    **                           a Progress DO block and a REPEAT/FOR block
    **                           (or really any other block types in
    **                           Progress).  There are new transaction level
    **                           constants and helpers to test and query
    **                           these levels.
    ** 036 GES 20060908   @29363 Retry must honor pending rollbacks for the
    **                           current block.
    ** 037 GES 20060921   @29832 Fix to force a default retry at the same
    **                           level as any pending rollbacks if the block
    **                           is not specified (if it is null). Previously
    **                           only the current block was retried even when
    **                           an enclosing block was the level of the
    **                           rollback.  Also fixed the fact that loop
    **                           protection disabling must propagate up to
    **                           enclosing blocks during popScope.
    ** 038 ECF 20061002   @30069 Fix to rollback(). Only defer rollback if
    **                           current scope is not a subtransaction or a
    **                           transaction. Also prevent unnecessary retry
    **                           processing from popScope().
    ** 039 GES 20061004   @30133 Support for nested errors (an error generated
    **                           during processing of another error, while
    **                           nested error mode is enabled).
    ** 040 GES 20061009   @30219 Honor null return from createScopeable() to
    **                           allow specific factories to decide NOT to
    **                           register an object for a given context.
    ** 041 GES 20061010   @30263 Added registerNextExternal() and deferred
    **                           external procedure block registration for
    **                           use by objects being constructed in an
    **                           initializer/constructor but needing to be
    **                           registered in the next external proc's block.
    ** 042 ECF 20061011   @30354 Fixed methods processFinalizables() and
    **                           processRetry(). Master finalizables were not
    **                           being processed in the case where no
    **                           non-master finalizables were registered in
    **                           the same scope.
    ** 043 GES 20061116   @31215 Converted all internal processing from using
    **                           block labels to identify blocks, into using
    **                           an index into the stack of blocks.  This is
    **                           a unique value whereas the label can be
    **                           duplicated in nested blocks.  In particular,
    **                           there was a case where the full transaction
    **                           flag was being cleared because a nested
    **                           block had the same label as the one that was
    **                           marked as the full transaction.
    ** 044 ECF 20061116   @31240 Fixed sprintf spec in WorkArea. Was using %s
    **                           where %i is required, raising an exception in
    **                           the Format class.
    ** 045 GES 20070409   @32860 When deferred errors have a condition as a
    **                           cause, unpack that cause and throw that
    **                           instead of the wrapper.  This negates the
    **                           effect of runtime components that are not
    **                           Progress aware, which wrap normal conditions
    **                           in more generic exceptions.  Since the 4GL
    **                           compatible runtime knows how to deal with
    **                           conditions, the behavior will be correct.
    ** 046 NVS 20070515   @33490 Fixed ArrayIndexOutOfBoundsException in the
    **                           toString() method.
    ** 047 GES 20071213   @36341 Disallow multiple finalizable registrations
    **                           for the same object in the same block.
    ** 048 GES 20071213   @36352 Added deferError support to scope
    **                           notification processing.
    ** 049 ECF 20071214   @36377 Added protection for iteration of certain
    **                           WorkArea resources. To prevent a concurrent
    **                           modification exception, updates to the work
    **                           area's scopeList, deferredFinalizables,
    **                           masterCommit, and masterFinish collections,
    **                           and to the current block's finalizables and
    **                           commitables collections, are disallowed while
    **                           the respective resource is being iterated.
    **                           A warning is logged with a stack trace. This
    **                           prevents catastrophic failure, but also
    **                           signals a programming error that should be
    **                           fixed.
    ** 050 GES 20071218   @36428 Exposed the current finalizable operation
    **                           in process.
    ** 051 GES 20071218   @36439 Added abnormalEnd() to notify the TM about
    **                           any unexpected failure, allowing any database
    **                           state to be rolled back (keeping integrity
    **                           of the database).
    ** 052 CA  20080207   @37027 Added constants for different types of blocks.
    **                           Added a new pushScope(...) method which
    **                           receives the type of block being entered -
    **                           only editing blocks are currently supported.
    **                           Added a method to retrieve the type of block
    **                           which is currently active.
    ** 053 ECF 20080218   @37118 Fixed processRollback(). Rollback processing
    **                           must occur before undo processing. Otherwise,
    **                           we might be performing rollback processing on
    **                           the wrong records in RecordBuffer.
    ** 054 ECF 20080221   @37142 Added protection code to WorkArea.toString().
    **                           Also rolled back #052 (@37118), which caused
    **                           regressions.
    ** 055 ECF 20080430   @38182 Fixed processRollback(). Reinstated #053
    **                           (@37118). SVL's related fix to RecordBuffer
    **                           corrects the regressions reported in #054
    **                           (@37142).
    ** 056 GES 20080505   @38210 Moved undoables to be an array for better
    **                           performance.
    ** 057 GES 20080612   @38756 Uncaught condition exceptions should UNDO
    **                           the full transaction level as they pass
    **                           through, unless the condition is QUIT (which
    **                           has the behavior of committing).
    ** 058 GES 20080619   @38888 Massive update of infinite loop protection
    **                           to fix deviations from Progress behavior.
    **                           This includes ILP for NEXT (honorNext()),
    **                           PAUSE support for sometimes disabling ILP
    **                           (depending on runtime context), special
    **                           behavior for RETRY caused by an ENDKEY,
    **                           a "stutter" mode for RETRY conversion et al.
    **                           Fixed some "holes" in exception processing
    **                           that would allow an UNDO, RETRY cause inside
    **                           a transaction but which is targeted at a
    **                           block that is outside the transaction, to
    **                           avoid rolling back at the full transaction
    **                           level. Added the ability to deregister a
    **                           list of undoables for the current scope (and
    **                           nested scopes).  This is needed to allow
    **                           no-undo parameters to be properly supported.
    ** 059 GES 20080624   @38916 Limiting registration of undoables to a
    **                           single instance is being removed because
    **                           it forces a compareTo which is potentially
    **                           dangerous since some undoables may have
    **                           errors when compared to each other (in
    **                           unexpected ways).  It is also expensive so
    **                           it is good to avoid it for that reason.
    ** 060 GES 20080625   @38960 Moved to the BlockType enum.
    ** 061 GES 20080627   @38992 Fix for honorNext().
    ** 062 ECF 20080630   @39031 Added registerNext(). Defers registration of
    **                           an Undoable until the next block scope opens.
    ** 063 CA  20080714   @39102 Fixed deregister() memory leak: the
    **                           backupUndoList was keeping the state of the
    **                           undoables after the entry of the current
    **                           block, not before.
    ** 064 GES 20080811   @39381 New features for UNDO and RETRY processing.
    ** 065 CA  20080826   @39565 Added processRollbackPending - notify the
    **                           commitables that a rollback is pending to be
    **                           executed by a parent block.
    ** 066 CA  20080815   @39448 Added honorStop(). This method will decide if
    **                           the processing of a STOP condition will be
    **                           vetoed at the current scope.
    ** 067 GES 20080818   @39858 Added some register() signatures to provide
    **                           simpler defaults. Removed the commit feature
    **                           of register() since it wasn't used anywhere.
    ** 068 GES 20080930   @39976 Reworked undo registration to allow a cleaner
    **                           interface to the caller. Now it allows
    **                           varargs to minimize the number of calls.
    ** 069 GES 20081003   @40036 Added _isRetry().
    ** 070 SIY 20081002   @40015 Inferred generics. Added helper method
    **                           notifyCondition().
    ** 071 SVL 20081024   @40219 Added findNearestExternal() function.
    ** 072 SIY 20081111   @40411 Call notifyCondition() if real problem is
    **                           detected in abnormalEnd().
    ** 073 CA  20081127   @40708 Added 'block iterated' support - method
    **                           hasParentIteratedBlock will check if any
    **                           of the current blocks iterated at least
    **                           once.
    ** 074 GES 20081126   @40742 Added helpers to access the properties of
    **                           a block and to manage the number of
    **                           interactions the current block has had.
    ** 075 GES 20081210   @40866 Added support for controlling the rollup
    **                           flag for the current block. Honor the rollup
    **                           flag for the containing block when exiting
    **                           the current block.
    ** 076 GES 20090115   @41131 Only wrap abend exceptions once. Modified
    **                           abnormalEnd() signature to avoid duplicate
    **                           notifications. In places where we re-throw
    **                           a condition exception, we now ensure that
    **                           abnormalEnd() is used to allow the rollback
    **                           processing to happen.
    ** 077 GES 20090422   @41917 Converted to standard string formatting.
    ** 078 ECF 20090714   @43189 Reworked deferred error processing. Deferred
    **                           error is now a Throwable. Fixed abnormal end
    **                           processing such that an exception thrown during
    **                           notification or rollback does not mask the root
    **                           cause of the abnormal end.
    ** 079 GES 20090729   @43442 Added processValidate() method to call validate()
    **                           on registered Commitable instances.
    ** 080 CA  20090731   @43472 Added code for off-end listener registration.
    **                           When a FOR or FOR EACH block is initialized, all
    **                           its associated queries must have their off-end
    **                           listeners registered, to allow block termination
    **                           when the query goes off-end.
    ** 081 CA  20090804   @43506 Added getOffEndListeners - returns the topmost
    **                           set of query off-end listeners and eventually
    **                           clears that set.
    ** 082 ECF 20090816   @43655 Added methods to register and deregister an
    **                           OutputParameterAssigner to/from the current
    **                           block.
    ** 083 CA  20090821   @43716 When deferring a STOP condition caused by thread
    **                           interruption, it will not be logged as SEVERE
    **                           (to avoid bogus stacktraces in server log).
    ** 084 ECF 20090826   @43785 Rolled back #008 (@23261). It is unclear why this
    **                           was added and it outputs directly to stderr,
    **                           seemingly unnecessarily.
    ** 085 GES 20090831   @43804 Save and restore error mgr silent mode flag
    **                           across top-level block boundaries. Removed
    **                           previous stacking support for the error mgr,
    **                           which was over-stacking things.
    ** 086 GES 20090915   @43898 At the end of retry processing, honor deferred
    **                           errors and any pending stop condition.
    ** 087 CA  20090917   @43931 Fix abnormal connection end. If the TM was
    **                           notified to abort processing, the next
    **                           honorStopCondition call will throw a
    **                           SilentUnwindException. So, no one can interfere
    **                           with the shutdown.
    ** 088 CA  20090922   @43979 In deferError, log SilentUnwindException's using
    **                           FINER instead of SEVERE level.
    ** 089 GES 20090924   @44011 Added a context-specific headless flag.
    ** 090 SVL 20091117   @44398 Added support for the Resettable objects.
    ** 091 CA  20091106   @44320 Protect the rollback processing against any
    **                           thread interruption incoming from a CTRL-C or a
    **                           client abend, by marking it as a critical
    **                           section.
    ** 092 CA  20091202   @44470 Added methods for retrieving the current nesting
    **                           level and the level of the block which is started
    **                           a full transaction.
    ** 093 GES 20100330   @44786 Added setHeadless() to allow per-context control
    **                           over headless mode. This can also be done via the
    **                           directory, but this interface allows a dynamic
    **                           approach.
    ** 094 ECF 20121114          Fix for Java 7: modified pushScope to deal with
    **                           varargs in overloaded register method.
    ** 095 ECF 20130121          Fix for Java 7: Added registerGlobal to
    **                           disambiguate from overloaded register methods.
    ** 096 CA  20130126          Save the Block instance in a block definition
    **                           field (BlockDefinition.block).
    **                           Added nearestExecutingBlock.
    ** 097 CA  20130209          Added dynamic EXTENT and EXTENT parameter support.
    ** 098 SVL 20131028          Added registerFinalizableAt, registerCommitAt, isTransactionAt.
    ** 099 CA  20131206          Added delayed processing of code which normally would be executed
    **                           by a finalize or scopeFinished implementation. This processing will
    **                           call deleted() or scopeDeleted() implementations, immediately if the
    **                           current THIS-PROCEDURE is not persistent or when the procedure gets
    **                           explicitly deleted, otherwise.
    ** 100 SVL 20140106          Added "silent" parameter to deferError function, changed function
    **                           access modifier.
    ** 101 CA  20140109          Do not cache context-local vars, as they may get reset (in case of a
    **                           state-reset appserver Agent).
    ** 102 CA  20140224          Finalizable and scope processing needs to be aware of persistent
    **                           procedures marked for "delete-on-exit".
    ** 103 SVL 20140221          findNearestTopLevel was made public.
    ** 104 CA  20140301          Procedure's scope is notified first for start and last for finish.
    ** 105 SVL 20140414          Modified isGlobalBlock in order to support agent contexts.
    ** 106 CA  20140513          Added a weight for the context-local var, to ensure predetermined
    **                           order during context reset.
    ** 107 ECF 20140813          Changed access of processValidate method to package private.
    ** 108 SVL 20150115          Added _isHeadless which gets "headless" attribute without initiating
    **                           any contexts in TM, LT etc.
    ** 109 SVL 20150116          Fixed _isHeadless for conversion case.
    ** 110 OM  20150506          Added public method nearestTopLevelType(). Upgraded code to Java7.
    ** 111 ECF 20150510          Added deregisterGlobalFinalizable to enable explicit removal of a
    **                           finalizable from the global list.
    ** 112 ECF 20150801          Added support for compound query optimization.
    ** 113 OM  20160126          Do not create unneeded context local on abortProcessing(). Server
    **                           context is automatically headless. isHeadless() made public.
    **     ECF 20160128          Minor performance improvement: avoid context-local lookup when
    **                           possible in isGlobalBlock.
    ** 114 ECF 20160206          Store the exception, if any, which triggered a rollback, and log that
    **                           exception and the current one, if available, when we detect the
    **                           illegal state of triggering a rollback when one already is pending.
    **                           Changed exception type from UnsupportedOperationException to
    **                           IllegalStateException in this case.
    ** 115 OM  20160223          Added cleanupDeferredFinalizables() method.
    ** 116 IAS 20160408          Use synthetic key as context key.
    ** 117 ECF 20160517          Prevent duplicate Undoable instances from being added to the global
    **                           scope.
    ** 118 GES 20160523          Minor code cleanup.
    ** 119 CA  20160627          Reworked undoable support - the undoables register themselves with
    **                           all blocks up the stack, until either 1. the tx block which created
    **                           it is reached or 2. the last tx block where it was saved is reached.
    **                           Refactored context-local resolution in the TM APIs, so that the
    **                           WorkArea instance is resolved only once and then re-used.
    **     CA  20160721          Reworked variable/parameter definitions and initialization to use
    **                           UndoableFactory and TypeFactory APIs (thus TM.register for undoables
    **                           is no longer required in converted code).
    **     CA  20160728          Fixed undo support for dynamic extent vars.  Fixed undo support for
    **                           variables defined in external programs ran persistent.
    ** 120 CA  20160812          Performance improvements for H119: use IdentityHashMap when using
    **                           ext prog instances as keys, pass the tx level when registering
    **                           dynamic extent arrays.
    ** 121 ECF 20160827          Additional refactoring to avoid resolving WorkArea more often than
    **                           necessary.
    ** 122 CA  20170427          _isHeadless() will always return true for virtual sessions.
    ** 123 CA  20171030          Added isSuppressError.
    **     ECF 20171030          Implemented transaction metadata.
    ** 124 ECF 20180201          Removed query optimization call from stopOffEndRegistration.
    ** 125 GES 20180702          Do not propagate ILP and interactions state past a top level block.
    ** 126 CA  20181112          Added support for the TRANSACTION resource.
    ** 127 CA  20190219          Added getBlock(levels).
    **     GES 20190324          Refactored to create a TransactionHelper inner class which contains
    **                           instance method versions of the external static TM interface.  That
    **                           legacy API has its implementation moved into private static workers
    **                           which take a WorkArea as the first parameter. There is an accessor
    **                           to get a new instance of the TransactionHelper which contains an
    **                           instance of the WorkArea.  This allows all instance method API usage
    **                           in TransactionHelper to directly use the private static workers
    **                           without any context local lookup.  Since the original API is intact,
    **                           code can be moved to this new approach over time.
    **     CA  20190325          Implemented ProcedureHelper instead of direct usage of the static
    **                           API.  This allows the elimination of context local usage in
    **                           ProcedureManager.
    ** 128 ECF 20190714          Added support for aggressive buffer flushing mode.
    ** 129 CA  20190710          Made deregisterOutputParameterAssigner public.
    **     CA  20190722          Changed offEndQueries type to HashSet.
    **     HC  20190724          Changed abend log level.
    **     CA  20190728          Sort the finalizables by their weight, ascending.  Required by the
    **                           output table/dataset cases, which needs to be performed before the
    **                           buffer's scope is closed.
    **     OM  20190731          Overloaded triggerErrorInCaller to accept numeric error code.
    ** 130 AIL 20191119          Added more scenarios in which an error should be honored. Made aware
    **                           of DATABASE TRIGGER DataType.
    ** 131 CA  20200312          Fixes for UNDO, THROW and RETURN ERROR, in case when a OO-like
    **                           error condition needs to be generated.
    **     CA  20200324          More fixes related to OO-like error processing via AppError.
    ** 132 GES 20200316          Added invocation of Finalizable.entry() in blockSetup() (only the
    **                           first time it is executed for a block or loop).
    **     ECF 20200419          Change to BufferManager.endTxPost method signature.
    ** 133 AIL 20200518          Added utilities for working with blocks: getBlockAtDepth and countBlocks.
    **     AIL 20200616          Added more operations on the block stack.
    **     AIL 20200622          Removed getStack method.
    **                           Updated copyStack method.
    **     GES 20200730          Deleted setHeadless() which was dead code. Eliminated ErrorManager context-
    **                           local lookups.
    **     CA  20200927          Avoid context-local lookups by relying on the helper instance.
    **     CA  20201003          Use an identity HashSet where possible.
    **     CA  20201015          Replaced java.util.Stack with a non-synchronized custom implementation.
    **     OM  20201125          Javadoc update.
    **     CA  20210310          Small performance improvement related to offend query/listener processing.
    **     ECF 20210504          Adjusted for API changes in BufferManager.
    **     CA  20210629          Database triggers may get invoked while processing finalizable for a external
    **                           program delete, so the processing procedure must be saved/restored and not set
    **                           to null.
    **     GES 20210809          Moved FINALLY block execution to occur after commitables/finalizables in
    **                           iterateWorker(), processRetry() or popScope(). If a finally block must be
    **                           processed, it must be explicitly enabled during each block iteration and
    **                           after execution here, it is cleared.  If the finally block is not enabled
    **                           using TransactionHelper.setupFinallyBlock(), then no finally processing will
    **                           occur during that iteration. During execution of the finally block (in
    **                           processFinallyBlock(), at the full transaction level we clear the transaction
    **                           level and nested transaction level state and then restore it after the finally
    **                           block executes.  This duplicates the behavior where in the 4GL, the finally
    **                           block does not report it is in a transaction when executed at the full
    **                           transaction level.
    **     CA  20210906          'processFinallyBlock' must be enclosed in a try/finally block, so any conditions
    **                           raised from it will ensure the parent block is processed correctly.
    **     OM  20211111          Added transaction support for finally blocks.
    **     OM  20211201          Improved transaction support for finally blocks.
    **     OM  20211203          Ignore ReturnUnwindException when processing the finally block of a proc/func.
    **     CA  20211222          Fixed memory leak: when a procedure gets deleted, all its undoables must be
    **                           deregistered; stale procedures can be registered with this block, when its FINALLY
    **                           code is executed, so ensure DeferredDeletablesManager gets executed after the
    **                           FINALLY code completes, as, the block's finalizables are already processed before
    **                           FINALLY executes.
    **     ECF 20220103          Do not propagate aggressive FOR flushing to nested blocks.
    **     CA  20220114          Added SESSION:CLIENT-DISCONNECTED FWD extension attribute.
    **                           Added SESSION:TERMINATION-HOOK, a hook to be executed when the global block is
    **                           being terminated, to allow application-specific cleanup.  The cleanup code must
    **                           not perform any client-side calls, for i.e. file, socket access, UI, etc; any
    **                           such code can be avoided via the CLIENT-DISCONNECTED flag.
    **     CA  20220120          Improved performance for finalizables, keep them already sorted by their weight.
    **                           ConditionListener's are kept in their own list.
    **     CA  20220128          TERMINATION-HOOK can not be set for remote sessions (appserver).
    **     CA  20220210          Removed Commitable.rollbackPending and its notifications, as there is no actual
    **                           implementation of this method.
    **     CA  20220310          Backed out a change in CA/20220210 - in popScope(), the global scope is finalized
    **                           only when the root block is popped (this includes appserver agents).
    **     RFB 20220712          Added isRootExternal method which is similar to isRootNestingLevel, but it uses
    **                           the nearest external block nesting instead of the root. Ref #6578.
    **     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  20220906          Extracted all BufferManager state which requires notification for
    **                           'isImportantBlock' to an external TxWrapper class (including the private class
    **                           here), to allow the scopeable BufferManager state related only to buffers (and
    **                           not transaction related) to be registered for scope notifications in a lazy
    **                           manner, when buffers are accessed/used/opened.
    **     CA  20221006          Added JMX instrumentation for process commit, rollback and validate. Refs #6814
    **     CA  20221006          Lazy initialization for commitables and conditionListeners. 'finalizables' can be
    **                           a set instead of a list, as their order is not important.  Refs #6824
    **     CA  20221010          Performance improvements - avoid the ProcedureData lookup, by keeping a parallel
    **                           stack of this data for THIS-PROCEDURE.  Refs #6826
    **     CA  20220602          TRANSACTION-MODE AUTO is opened at the agent's call scope, not the root scope.
    **     CA  20220630          Fixed getBlockAtDepth - if 'blockDepth' is 0, return global block.
    **     CA  20220918          Performance improvement - do not save procUndoables if there are no undoables at
    **                           that external program.
    **     CA  20221010          Performance improvements - avoid the ProcedureData lookup, by keeping a parallel
    **                           stack of this data for THIS-PROCEDURE.  Refs #6826
    **     SVL 20230108          Improved performance by replacing some "for-each" loops with indexed "for" loops.
    **     CA  20230110          A small improvement for 'addFinalizable'.
    **     CA  20230116          Having a package-private inner-class field accessed from another inner-class
    **                           avoids a synthetic getter method emitted by javac.
    **     HC  20230118          Eliminated some of the uses of String.toUpperCase and/or
    **                           String.toLowerCase for performance.
    ** 134 CA  20230223          Avoid listIterator usage, as a plain for loop can be used, in 'nearestTopLevel'.
    ** 135 SVL 20230317          offEndQueries is now a stack: fixed the set of off-end queries being wrong when
    **                           a database trigger is invoked because of buffer flush in FOR cycle init block.
    ** 136 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    ** 137 CA  20230615          The CATCH block must be executed just before the FINALLY block, in popScope.
    **                           Refactored masterFinish/masterCommit to default to null, and also moved the
    **                           management of this for CATCH and FINALLY in a common block (for popScope).
    ** 138 CA  20230607          TRANSACTION-MODE AUTO must preserve the tx in the root appserver block - this is
    **                           required because in modes other than State-free, requests can be executed on the
    **                           same agent who had TRANSACTION-MODE AUTO, and this must see as active the tx
    **                           initially set.
    ** 139 GBB 20230616          Remove log for exceptions caused by ApplicationRequestedStop.
    ** 140 CA  20230713          Fixed a regression in H137 - a RETURN from a CATCH block was leaking to previous
    **                           block, and terminating it early.
    **                           Small optimization, use TM.getBlock() which returns the top of the stack, instead
    **                           of TM.getBlock(0).
    ** 141 CA  20230712          Fixed 'isRootExternal' when the target on the appserver is an internal entry for
    **                           a persistent program.
    ** 142 GBB 20230825          SecurityManager context & session methods calls updated.
    ** 143 CA  20231026          Do not pin the Block instance to BlockDefinition, as it is never read.
    **     CA  20231031          Added 'scopeStart(BlockDefinition)', required when a top-level scopeable is lazy
    **                           registered while in an inner block.
    ** 144 CA  20231214          Fixed a 'pushScope' API which was passing UNKNOWN as the block type instead of
    **                           the parameter.
    ** 145 CA  20231216          Avoid iterators when processing BlockDefinition.finalizables.
    **     CA  20231221          Refactored the WidgetPool scopeable support to register for processing as needed.
    ** 146 HC  20240222          Enabled JMX on FWD Client.
    ** 147 CA  20240320          Avoid BDTs within internal FWD runtime.  Replaced iterators with Java 'for',
    **                           where it applies.
    ** 148 AL2 20240322          Fixed typo when deregistering undoables from all tx blocks.
    ** 149 DDF 20240404          Reverted change made when deregistering undoables.
    ** 150 SP  20240422          Added processUndoables(boolean currentBlock).
    ** 151 DDF 20240509          Call deleted() when deregistering a Finalizable from masterFinish collection,
    **                           but only for a transaction initiated by TRANSACTION-MODE AUTO.
    **     DDF 20240509          Replace usage of autoRemove() with autoRemoveAndDelete().
    ** 152 SB  20240523          Added attemptRollback. Refs #8728.
    ** 153 CA  20240609          Added new overloads of 'triggerErrorInCaller'.
    ** 154 SVL 20240621          Pass error code to the client side in triggerErrorInCaller.
    ** 155 CA  20240707          If there is a pending error when executing a FINALLY block, execute this in its
    **                           own 'nestedSilent' mode, as a FINALLY block can not override errors from the main
    **                           block.
    ** 156 ES  20240711          Call cleanupPending in case of StopConditionExeption is thrown by a stop-after timer.
    ** 157 CA  20240809          Avoid iterator usage, for performance improvement.
    ** 158 SP  20240801          Extracted processCommit, processRollback and processValidate lambdas
    **                           to processCommitImpl, processRollbackImpl and processValidateImpl methods.
    **                           Skip using JMX timers when JMX_DEBUG flag is not set.
    ** 159 AS  20240924          Use triggeredInCaller attribute when creating an
    **                           ErrorConditionException in triggerErrorInCaller().
    ** 160 TJD 20230410          Check if rollback is already done with hadRollback function
    **     TJD 20230824          Mark full transaction block as rolledBack on deferred rollback to allow proper
    **                           restore of record locks
    ** 161 ES  20241009          Setting undoThrow flag in case a rollback of a block happend.
    ** 162 CA  20240911          When resize a dynamic extent field via deserialization, undoables must be
    **                           registered in the deserialized instance, as this instance is not on stack.
    ** 163 OM  20241128          Multi-tenant runtime support: selected the proper persistence context, eventually
    **                           based on [sharedDb] parameter.
    ** 164 CA  20250127          If a persistent procedure's delete is delayed, then register the BufferManager
    **                           scopeable, as this will be needed when the current scope ends, to delete the
    **                           procedure's resources.
    ** 165 GBB 20250403          Added getScopeLabel. BlockManager conditions to be enum constants.
    ** 166 ES  20250411          Combined execution logic for finally and catch blocks in processTerminationBlock method.
    ** 167 ES  20240221          Switch to throwing the StopConditionException through ErrorManager.
    **     ES  20250228          Cleaning up the stopAfterTImer when a Progress.Lang.Stop condition was
    **                           thrown.
    **     ES  20250228          Register the stopAfterTimer for the WorkArea of the TM.
    */

    /*
    ** 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.util.logging.*;
    import com.goldencode.p2j.jmx.*;
    import com.goldencode.p2j.net.*;
    import com.goldencode.p2j.oo.lang.Stop;
    import com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.persist.meta.*;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.security.SecurityManager;
    import com.goldencode.p2j.ui.LogicalTerminal;
    import com.goldencode.util.*;
    import com.goldencode.util.Stack;

    import static com.goldencode.p2j.util.BlockType.*;

    /**
     * Progress compatible transaction/block properties static utility methods
     * which operate upon a context-local scoped dictionary of blocks.  This
     * class provides the following features:
     * <p>
     * <ul>
     *    <li> Maintains state for each block, handling the proper nesting of
     *         blocks.
     *    <li> Tracks the start and end of transactions and nested
     *         sub-transactions.
     *    <li> Provides support for sending notifications of block start/end to
     *         registered instances of the {@link Scopeable} interface.
     *    <li> Provides a rollback service within a transaction or sub-transaction
     *         triggered by known error conditions or by explicit request.  See
     *         {@link Undoable} for externally driven rollback and see the
     *         {@link Commitable} for rollback notifications and custom rollback
     *         support.  See {@link #registerNext}, {@link #registerCurrent} and
     *         {@link #registerCommit}.
     *    <li> Provides a commit service triggered at known successful completion
     *         of a transaction or sub-transaction.  See {@link Commitable} and
     *         {@link #registerCommit} for commit notifications.
     *    <li> Provides a cleanup service for a callback on any exit from a given
     *         scope (whether that exit was normal or abnormal).  See the
     *         {@link Finalizable} interface and {@link #registerFinalizable}.
     *    <li> Provides an iteration service triggered by client code invocation
     *         of the iterate() method.  See the {@link Finalizable} interface
     *         and {@link #registerFinalizable}.
     *    <li> Provides a retry callback triggered by client code invocation
     *         of the needsRetry() method.  See the {@link Finalizable} interface
     *         and {@link #registerFinalizable}.
     *    <li> Provides a session-level cleanup service which is called after the
     *         exit of the top-most scope. This global finish processing occurs
     *         when the last scope is popped from the stack of scopes.  For this
     *         reason, the current implementation assumes that the session is
     *         over at the moment that the top-most user-defined scope exits.
     *         See {@link #registerFinalizable} and note the use of the
     *         <code>global</code> option.
     *    <li> Maintains state regarding retry logic to allow blocks or loops to
     *         be retried as a result of error conditions being raised.
     *    <li> Maintains state and processing of retry and error stack unwinding.
     *    <li> Provides a session-level batch start and stop service.
     *    <li> At every block start, stop and iteration the current thread's
     *         <code>interrupted status</code> is checked and if the thread
     *         has been previously interrupted, a {@link StopConditionException}
     *         will be thrown.  See {@link #honorStopCondition}.
     * </ul>
     * <p>
     * The following is the mapping of Progress language features to the
     * corresponding feature in this class:
     * <p>
     * <pre>
     * Progress Name    Type                Method
     * ---------------  --------------      -----------------------------------
     * transaction      function            {@link #isTransactionActive}
     * retry            function            {@link #isRetry}
     * </pre>
     * <p>
     * This class minimizes the amount and complexity of client code by using
     * lists of objects to notify of specific events.  The following is a
     * summary of the event processing:
     * <p>
     * <pre>
     * Interface     Registration Method
     * ------------  ---------------------------
     * Undoable      {@link #registerNext} {@link #registerCurrent}
     * Commitable    {@link #registerCommit}
     * Finalizable   {@link #registerFinalizable}
     * Scopeable     {@link #registerScopeable}
     * BatchListener {@link #registerBatchListener}
     *
     * Interface     Notify Method(s)     Event
     * ------------  -------------------  ------------------------
     * Undoable      {@link #pushScope}           {@link Undoable#deepCopy}
     *               {@link #blockSetup}          {@link Undoable#deepCopy}
     *               {@link #rollback}            {@link Undoable#assign}
     *               {@link #popScope}            {@link Undoable#assign}
     * Commitable    {@link #blockSetup}          {@link Commitable#commit}
     *               {@link #popScope}            {@link Commitable#commit}
     *               {@link #rollback}            {@link Commitable#rollback}
     *               {@link #popScope}            {@link Commitable#rollback}
     * Finalizable   {@link #popScope}            {@link Finalizable#finished}
     *               {@link #blockSetup}          {@link Finalizable#iterate}
     * Scopeable     {@link #pushScope}           {@link Scopeable#scopeStart}
     *               {@link #popScope}            {@link Scopeable#scopeFinished}
     * BatchListener {@link #batchStart}          {@link BatchListener#batchNotify}
     *               {@link #batchStop}           {@link BatchListener#batchNotify}
     * </pre>
     *
     * @author    GES
     */
    public final class TransactionManager
    {

       /** The block won't start a transaction or sub-transaction. */
       public static final int NO_TRANSACTION = 0;

       /** The block will start a sub-transaction if a transaction is active. */
       public static final int SUB_TRANSACTION = 1;

       /** The block will start a transaction or sub-transaction. */
       public static final int TRANSACTION = 2;

       /** No <code>Finalizable</code> processing is currently occurring. */
       public static final int OP_NONE = 0;

       /** <code>entry</code> processing is currently occurring. */
       public static final int OP_ENTRY = 1;

       /** <code>iterate</code> processing is currently occurring. */
       public static final int OP_ITERATE = 2;

       /** <code>retry</code> processing is currently occurring. */
       public static final int OP_RETRY = 3;

       /** <code>finished</code> processing is currently occurring. */
       public static final int OP_FINISHED = 4;

       /** Indicates that the block has no properties. */
       public static final int PROP_NONE = 0x00;

       /** ERROR property. */
       public static final int PROP_ERROR = 0x01;

       /** ENDKEY property. */
       public static final int PROP_ENDKEY = 0x02;

       /** STOP property. */
       public static final int PROP_STOP = 0x04;

       /** QUIT property. */
       public static final int PROP_QUIT = 0x08;

       /**
        * The index of the block where the automatic transaction will be opened (when a
        * TRANSACTION-MODE AUTOMATIC language statement is in effect).
        */
       private static final int APPSERVER_ROOT_BLOCK_INDEX = 0;

       /** Instrumentation for {@link #processCommit}. */
       private static final NanoTimer COMMIT = NanoTimer.getInstance(FwdServerJMX.TimeStat.TMCommit);

       /** Instrumentation for {@link #processRollback}. */
       private static final NanoTimer ROLLBACK = NanoTimer.getInstance(FwdServerJMX.TimeStat.TMRollback);

       /** Instrumentation for {@link #processValidate}. */
       private static final NanoTimer VALIDATE = NanoTimer.getInstance(FwdServerJMX.TimeStat.TMValidate);

       /** Logger (this is JVM-wide rather than being context-local). */
       private static final CentralLogger LOG = CentralLogger.get(TransactionManager.class.getName());

       /** Context-local global data area for transaction state. */
       private static final ContextContainer workArea = new ContextContainer();

       /**
        * Flags whether the {@code _Trans} metadata table is active. If the flag is active the active
        * transactions for all users can be visualized using the {@code _Trans} SVT. Otherwise only
        * the transactions for current user can be queried using {@code DBTASKID()} ABL function.
        */
       private static final Map<String, TransactionTableUpdater> updaters = new CaseInsensitiveHashMap<>();

       /** Transaction information for each database. Access to its content must be synchronized. */
       private static final Map<String, DbTransactions> transactionData = new CaseInsensitiveHashMap<>();

       /**
        * Initialize a metaschema database by initializing a transaction listener and storing it for later use.
        * <p>
        * Called only at server startup.
        *
        * @param   primaryDB
        *          Primary database for which metaschema information is being managed.
        */
       public static void initializeMeta(Database primaryDB)
       {
          String dbId = primaryDB.getId();

          // sync may not be mandatory here because the method is called during the initialization
          synchronized (transactionData)
          {
             if (transactionData.containsKey(dbId))
             {
                if (LOG.isLoggable(Level.WARNING))
                {
                   LOG.log(Level.WARNING, "Multiple registration of '" + dbId + "' database.");
                }
                return;
             }

             transactionData.put(dbId, new DbTransactions());

             if (MetadataManager.inUse(MetadataManager._TRANS) && TransactionTableUpdater.isEnabled())
             {
                updaters.put(dbId, new TransactionTableUpdater());
             }
          }
       }

       /**
        * Generate a string representation of the given transaction level.
        *
        * @param    level
        *           The transaction level to be decoded.  Must be one of
        *           <code>NO_TRANSACTION</code>, <code>SUB_TRANSACTION</code> or
        *           <code>TRANSACTION</code>.
        *
        * @return   The human readable string.
        */
       public static String decodeLevel(int level)
       {
          String txt = "UNKNOWN";

          switch (level)
          {
             case NO_TRANSACTION:
                txt = "NO_TRANSACTION";
                break;
             case SUB_TRANSACTION:
                txt = "SUB_TRANSACTION";
                break;
             case TRANSACTION:
                txt = "TRANSACTION";
                break;
          }

          return txt;
       }

       /**
        * Generate a string representation of the block properties of a block.
        *
        * @param    props
        *           The block properties to decode.
        *
        * @return   The human readable string.
        */
       public static String decodeProperties(int props)
       {
          StringBuilder sb = new StringBuilder();

          if ((props & PROP_ERROR) != 0)
          {
             sb.append(BlockManager.Condition.ERROR.name());
          }

          if ((props & PROP_ENDKEY) != 0)
          {
             if (sb.length() > 0)
             {
                sb.append(", ");
             }

             sb.append(BlockManager.Condition.ENDKEY.name());
          }

          if ((props & PROP_STOP) != 0)
          {
             if (sb.length() > 0)
             {
                sb.append(", ");
             }

             sb.append(BlockManager.Condition.STOP.name());
          }

          if ((props & PROP_QUIT) != 0)
          {
             if (sb.length() > 0)
             {
                sb.append(", ");
             }

             sb.append(BlockManager.Condition.QUIT.name());
          }

          return sb.toString();
       }

       /**
        * Reports on the current <code>Finalizable</code> operation being
        * executed.
        *
        * @return   One of the following:
        *           <ul>
        *              <li> <code>OP_NONE</code>
        *              <li> <code>OP_ITERATE</code>
        *              <li> <code>OP_RETRY</code>
        *              <li> <code>OP_FINISHED</code>
        *           </ul>
        */
       public static int currentOperation()
       {
          return workArea.obtain().op;
       }

       /**
        * Adds the given object into the list of objects for the current block
        * which require commit notification.  This form of registration is
        * useful for objects whose natural lifetime are scoped to a different
        * block than the block in which they must be committed.  For example,
        * if a global or shared resource needs to be committed in a specific
        * scope, then this can be used in cooperation with the normal
        * {@link #registerCurrent} processing to control the commit points.
        * <p>
        * This notification will ONLY occur when there is an active transaction.
        * <p>
        * Notifications occur in LIFO order;  thus, commitables which depend upon
        * others being committed first must be registered before the objects upon
        * which they are dependent.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed.
        *
        * @param    target
        *           The object reference to be committed on success.  This
        *           cannot be <code>null</code>.
        */
       public static void registerCommit(Commitable target)
       {
          WorkArea wa = workArea.obtain();
          registerCommitAt(wa, wa.blocks.size() - 1, target);
       }

       /**
        * Adds the given object into the list of objects which require commit notification for the
        * block at the specified depth  This form of registration is useful for objects whose natural
        * lifetime are scoped to a different block than the block in which they must be committed.
        * For example, if a global or shared resource needs to be committed in a specific scope,
        * then this can be used in cooperation with the normal {@link #registerCurrent} processing to
        * control the commit points.
        * <p>
        * This notification will ONLY occur when there is an active transaction.
        * <p>
        * Notifications occur in LIFO order;  thus, commitables which depend upon
        * others being committed first must be registered before the objects upon
        * which they are dependent.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed.
        *
        * @param    blockDepth
        *           Zero-based depth of the target block, starting from the outermost block.
        * @param    target
        *           The object reference to be committed on success.  This cannot be
        *           <code>null</code>.
        */
       public static void registerCommitAt(int blockDepth, Commitable target)
       {
          registerCommitAt(workArea.obtain(), blockDepth, target);
       }

       /**
        * Adds the given object as the transaction level reference which will get
        * a single, "master" commit call after all other commits have been
        * processed at the full transaction level.  This will never be called
        * for sub-transaction commits, nor will it be called if the transaction
        * is abnormally exited.
        * <p>
        * This notification will ONLY occur when there is an active transaction
        * and it only will be called at the exit of the block that corresponds
        * to the full transaction.
        * <p>
        * If the full transaction block is also an iterating block, this
        * notification will occur for each iteration of the block.
        *
        * @param    target
        *           The object reference to be committed on success.  This
        *           can be <code>null</code> if one intends to clear the current
        *           reference.
        * @param    remove
        *           If <code>true</code> automatically remove this registration
        *           after the full transaction scope has ended (whether or not
        *           the commit occurs).  If <code>false</code>, this reference
        *           will remain globally until replaced.
        */
       public static void registerTransactionCommit(Commitable target, boolean remove)
       {
          WorkArea wa = workArea.obtain();
          if (wa.masterCommit == null)
          {
             wa.masterCommit = new LinkedHashMap<>();
          }
          wa.masterCommit.put(target, remove ? Boolean.TRUE : Boolean.FALSE);
       }

       /**
        * Register the given object as a handler which can veto the processing of
        * STOP condition processing at the current scope.  Handlers are invoked
        * in FIFO order.  Any handler can issue a veto, which forces the STOP
        * condition to be handled at a higher scope, if at all.
        *
        * @param   h
        *          The stop condition veto handler.
        */
       public static void registerStopVetoHandler(StopConditionVetoHandler h)
       {
          WorkArea wa = workArea.obtain();
          wa.stopHandlers.add(h);
       }

       /**
        * Registers the instance for scope notifications at the current block.
        * <p>
        * This will call {@link Scopeable#scopeStart} to notify for the scope processing.
        *
        * @param    target
        *           The scopeable instance.
        */
       public static void registerBlockScopeable(Scopeable target)
       {
          registerBlockScopeable(workArea.obtain(), target);
       }

       /**
        * Register the target scopeable as pending, to be added to the next block.
        *
        * @param    target
        *           The scopeable instance.
        */
       public static void registerPendingScopeable(Scopeable target)
       {
          registerPendingScopeable(workArea.obtain(), target);
       }

       /**
        * Register the given <code>Resettable</code> object in order to reset it
        * at specific points of block processing.
        *
        * @param resettable
        *        <code>Resettable</code> to be registered.
        */
       public static void registerResettable(Resettable resettable)
       {
          WorkArea wa = workArea.obtain();
          wa.resettables.add(resettable);
       }

       /**
        * Adds the given object into the list of objects for the specified block
        * which require iterate/retry/finish notification.  This form of
        * registration is useful for objects whose natural lifetime is scoped to
        * a block and which need a hook for cleaning up resources or handling
        * other block termination processing.
        * <p>
        * This notification will occur whether or not a transaction is active.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed (after the {@link Finalizable#finished} method is called)
        * in the case of a non-global scope OR at the moment that the last
        * scope has been removed in the case of the global scope.
        * <p>
        * The given object will only be added if it is not already in the list.
        * <p>
        * Finalizables for a scope are processed in the same order as they were
        * registered.
        *
        * @param    target
        *           The object reference to be notified on any exit from the
        *           block specified.  This cannot be <code>null</code>.
        * @param    global
        *           Specifies the target scope. <code>false</code> specifies the
        *           current scope, and <code>true</code> specifies the global
        *           scope.
        */
       public static void registerFinalizable(Finalizable target, boolean global)
       {
          WorkArea wa = workArea.obtain();
          registerFinalizableAt(wa, (global ? 0 : wa.blocks.size() - 1), target);
       }

       /**
        * Remove the given finalizable from the global list of finalizables.
        *
        * @param    target
        *           Finalizable to be removed from the global list.
        */
       public static void deregisterGlobalFinalizable(Finalizable target)
       {
          deregisterGlobalFinalizable(workArea.obtain(), target);
       }

       /**
        * Adds the given object into the list of objects for the specified block
        * which require iterate/retry/finish notification.  This form of
        * registration is useful for objects whose natural lifetime is scoped to
        * a block and which need a hook for cleaning up resources or handling
        * other block termination processing.
        * <p>
        * This notification will occur whether or not a transaction is active.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed (after the {@link Finalizable#finished} method is called)
        * in the case of a non-global scope OR at the moment that the last
        * scope has been removed in the case of the global scope.
        * <p>
        * The given object will only be added if it is not already in the list.
        * <p>
        * Finalizables for a scope are processed in the same order as they were
        * registered.
        *
        * @param    blockDepth
        *           zero-based depth of target block, starting from the outermost
        *           block. <code>0</code> indicates the global block.
        * @param    target
        *           The object reference to be notified on any exit from the
        *           block specified.  This cannot be <code>null</code>.
        */
       public static void registerFinalizableAt(int blockDepth, Finalizable target)
       {
          registerFinalizableAt(workArea.obtain(), blockDepth, target);
       }

       /**
        * Adds the given object into the list of objects which require
        * iterate/retry/finish notifications for the next block that is opened
        * which is a top-level block that corresponds to a Progress external
        * procedure.
        * <p>
        * This form of registration is useful for objects whose natural lifetime
        * is scoped to the top-level block and which need a hook for cleaning up
        * resources or handling other block termination processing.  In
        * particular, it is necessary for objects that are constructed during
        * a class' constructor/initializer (before the external procedure block
        * has opened).  Such objects cannot simply register since their
        * construction is not actually enclosed by the block.
        * <p>
        * This notification will occur whether or not a transaction is active.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed (after the {@link Finalizable#finished} method is called).
        * <p>
        * The given object will only be added if it is not already in the list.
        *
        * @param    target
        *           The object reference to be notified on any exit from the
        *           top-level external block.  This cannot be <code>null</code>.
        *
        * @throws   IllegalStateException
        *           if the top-level block cannot be identified.
        */
       public static void registerNextExternal(Finalizable target)
       {
          registerNextExternal(workArea.obtain(), target);
       }

       /**
        * Allow finalizables to do their cleanup ({@link Finalizable#initFailure()}) now since the
        * scoped they were designed was aborted and there won't be other chance to call their
        * {@link Finalizable#finished()}.
        * <p>
        * After iteration the list is deleted.
        * <p>
        * Also, cleanup all other pending state being collected while the external program was
        * instantiating.
        */
       public static void cleanupPending()
       {
          cleanupPending(workArea.obtain());
       }

       /**
        * Adds the given object into the list of objects which require
        * iterate/retry/finish notifications for the nearest enclosing
        * top-level block.  A top-level block is generated for Progress external
        * procedures, internal procedures, triggers and functions.  An additional
        * parameter allows registration to be specified for the specific block
        * which corresponds with an external procedure block scope in Progress.
        * <p>
        * This form of registration is useful for objects whose natural lifetime
        * is scoped to the top-level block and which need a hook for cleaning up
        * resources or handling other block termination processing.
        * <p>
        * This notification will occur whether or not a transaction is active.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed (after the {@link Finalizable#finished} method is called).
        * <p>
        * The given object will only be added if it is not already in the list.
        * <p>
        * Finalizables for a scope are processed in the same order as they were
        * registered.
        *
        * @param    target
        *           The object reference to be notified on any exit from the
        *           top-level block.  This cannot be <code>null</code>.
        * @param    external
        *           If <code>true</code>, register with the nearest enclosing
        *           external block (external procedure), otherwise register with
        *           the nearest enclosing top-level block (which may be but is
        *           not guaranteed to be the external block).
        *
        * @throws   IllegalStateException
        *           if the top-level block cannot be identified.
        */
       public static void registerTopLevelFinalizable(Finalizable target, boolean external)
       {
          registerTopLevelFinalizable(workArea.obtain(), target, external);
       }

       /**
        * Adds the given object into the list of objects which require
        * iterate/retry/finish notification for the nearest enclosing, looping
        * block.
        * <p>
        * This form of registration is useful for objects whose natural lifetime
        * is scoped to the nearest enclosing, looping block and which need a hook
        * for cleaning up resources or handling other block termination
        * processing.
        * <p>
        * This notification will occur whether or not a transaction is active.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed (after the {@link Finalizable#finished} method is called).
        * <p>
        * The given object will only be added if it is not already in the list.
        *
        * @param    target
        *           The object reference to be notified on any exit from or
        *           iteration of the target block.  This cannot be
        *           <code>null</code>.
        *
        * @throws   IllegalStateException
        *           if no enclosing, looping block can be identified.
        */
       public static void registerNearestLoopFinalizable(Finalizable target)
       {
          registerNearestLoopFinalizable(workArea.obtain(), target);
       }

       /**
        * Removes the given object from the list of objects which require
        * iterate/retry/finish notifications.  This will find the first instance
        * of the given reference by walking up each block from the current to the
        * global block. The first instance found will be removed and the method
        * will then return. If the same instance is registered in multiple scopes,
        * it would take multiple calls to remove all instances.
        * <p>
        * After this call, no notifications will be received.
        *
        * @param    target
        *           The object reference to be removed.
        *
        * @return   <code>true</code> if the target instance was found and
        *           removed.
        */
       public static boolean removeFinalizable(Finalizable target)
       {
          if (target == null)
          {
             // nothing to do
             return false;
          }

          return removeFinalizable(workArea.obtain(), target);
       }

       /**
        * Adds the given object as the transaction level reference which will get
        * a "master" finish call after all other finish notifications have been
        * processed at the full transaction level.  This will never be called for
        * sub-transaction finish notification.
        * <p>
        * This notification will ONLY occur when there is an active transaction
        * and it only will be called at the exit of the block that corresponds
        * to the full transaction.
        * <p>
        * Multiple targets may be registered by invoking this method multiple
        * times.  However, the same object registered multiple times will receive
        * the notification only once per full transaction block exit.  If multple
        * targets are registered, they will be called in the order in which they
        * were registered.
        *
        * @param    target
        *           The object reference to be finished at full transaction block
        *           end.
        * @param    remove
        *           If <code>true</code> automatically remove this registration
        *           after the full transaction scope has ended.  If
        *           <code>false</code>, this reference will remain globally until
        *           replaced.
        */
       public static void registerTransactionFinish(Finalizable target, boolean remove)
       {
          WorkArea wa = workArea.obtain();
          if (wa.masterFinish == null)
          {
             wa.masterFinish = new LinkedHashMap<>();
          }
          wa.masterFinish.put(target, remove);
       }

       /**
        * Adds the given object into the batch notifier list.  This is a global
        * list (it isn't associated with any block) that is triggered externally
        * by a call to {@link #batchStart} and {@link #batchStop}.  Every object
        * in the list will be notified in the order in which they were added to
        * the list.
        *
        * @param    target
        *           The object reference to be notified of any batch start and
        *           stop.
        */
       public static void registerBatchListener(BatchListener target)
       {
          workArea.obtain().batchListeners.add(target);
       }

       /**
        * Reports whether the current block was just rolled back or a containing
        * block has a pending rollback. If there is no current transaction open,
        * then there cannot be a pending or current rollback.
        *
        * @return   <code>true</code> if there was or will be a rollback.
        */
       public static boolean hadRollback()
       {
          return hadRollback(workArea.obtain());
       }

       /**
        * Trigger a rollback for all registered <code>Undoable</code> objects
        * inside the current active transaction at the scope defined by the
        * current block.  All objects that are rolled back will have their
        * values assigned based on the values backed up at the start of the
        * specified scope.  A flow control change MUST always immediately follow
        * this method otherwise undefined results can occur.
        * <p>
        * This caller-driven control flow is required to properly duplicate the
        * semantics of Progress 4GL condition processing which always generates
        * an implicit or explicit flow control action after every UNDO. Since the
        * flow of control change can reference a different scope than that of
        * the rollback, this can't be handled by re-throwing the exception but
        * must instead be separately driven by a deferred rollback and a caller
        * driven flow control statement such as break, continue or return. The
        * key limitation that makes this possible is the fact that the UNDO
        * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
        * the scope to which flow of control will be changed.  This means that
        * one can rollback at a more deeply nested scope but change flow of
        * control to resume processing at a different (less deeply nested) scope.
        * The reverse is not possible.
        */
       public static void rollback()
       {
          rollback(null);
       }

       /**
        * Trigger a rollback for all registered <code>Undoable</code> objects
        * inside the current active transaction at the scope defined
        * explicitly by the given label or implicitly if the label is
        * <code>null</code>.  All objects that are rolled back will have their
        * values assigned based on the values backed up at the start of the
        * specified scope.  If the current scope is not the target scope, the
        * given exception will be stored, the rollback will be deferred and
        * the rollback will be processed as the scopes naturally unwind.  This
        * unwinding must be generated by a change in flow of control by the
        * calling code.  This flow control change MUST always immediately follow
        * this method otherwise undefined results can occur.
        * <p>
        * This caller-driven control flow is required to properly duplicate the
        * semantics of Progress 4GL condition processing which always generates
        * an implicit or explicit flow control action after every UNDO. Since the
        * flow of control change can reference a different scope than that of
        * the rollback, this can't be handled by re-throwing the exception but
        * must instead be separately driven by a deferred rollback and a caller
        * driven flow control statement such as break, continue or return. The
        * key limitation that makes this possible is the fact that the UNDO
        * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
        * the scope to which flow of control will be changed.  This means that
        * one can rollback at a more deeply nested scope but change flow of
        * control to resume processing at a different (less deeply nested) scope.
        * The reverse is not possible.
        *
        * @param    label
        *           Specifies the name of the scope at which to rollback or
        *           <code>null</code> if the scope should be implicitly
        *           determined.
        */
       public static void rollback(String label)
       {
          rollback(label, null);
       }

       /**
        * Trigger a rollback for all registered <code>Undoable</code> objects
        * inside the current active transaction at the scope defined
        * explicitly by the given label or implicitly if the label is
        * <code>null</code>.  All objects that are rolled back will have their
        * values assigned based on the values backed up at the start of the
        * specified scope.  If the current scope is not the target scope, the
        * given exception will be stored, the rollback will be deferred and
        * the rollback will be processed as the scopes naturally unwind.  This
        * unwinding must be generated by a change in flow of control by the
        * calling code.  This flow control change MUST always immediately follow
        * this method otherwise undefined results can occur.
        * <p>
        * This caller-driven control flow is required to properly duplicate the
        * semantics of Progress 4GL condition processing which always generates
        * an implicit or explicit flow control action after every UNDO. Since the
        * flow of control change can reference a different scope than that of
        * the rollback, this can't be handled by re-throwing the exception but
        * must instead be separately driven by a deferred rollback and a caller
        * driven flow control statement such as break, continue or return. The
        * key limitation that makes this possible is the fact that the UNDO
        * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
        * the scope to which flow of control will be changed.  This means that
        * one can rollback at a more deeply nested scope but change flow of
        * control to resume processing at a different (less deeply nested) scope.
        * The reverse is not possible.
        *
        * @param    label
        *           Specifies the name of the scope at which to rollback or
        *           <code>null</code> if the scope should be implicitly
        *           determined.
        * @param    cause
        *           The exceptional cause, if any, of the rollback.
        */
       public static void rollback(String label, Throwable cause)
       {
          rollback(workArea.obtain(), label, cause);
       }

       /**
        * Trigger a rollback for all registered <code>Undoable</code> objects
        * inside the current active transaction at the nearest top level scope.
        * All objects that are rolled back will have their values assigned based
        * on the values backed up at the start of the specified scope.  If the
        * current scope is not the target scope, the given exception will be
        * stored, the rollback will be deferred and the rollback will be processed
        * as the scopes naturally unwind.  This unwinding must be generated by a
        * change in flow of control by the calling code.  This flow control change
        * MUST always immediately follow this method otherwise undefined results
        * can occur.
        * <p>
        * This caller-driven control flow is required to properly duplicate the
        * semantics of Progress 4GL condition processing which always generates
        * an implicit or explicit flow control action after every UNDO. Since the
        * flow of control change can reference a different scope than that of
        * the rollback, this can't be handled by re-throwing the exception but
        * must instead be separately driven by a deferred rollback and a caller
        * driven flow control statement such as break, continue or return. The
        * key limitation that makes this possible is the fact that the UNDO
        * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
        * the scope to which flow of control will be changed.  This means that
        * one can rollback at a more deeply nested scope but change flow of
        * control to resume processing at a different (less deeply nested) scope.
        * The reverse is not possible.
        */
       public static void rollbackTopLevel()
       {
          rollbackTopLevel(workArea.obtain());
       }

       /**
        * Initializes a new scope, the associated block definition data
        * structures.  A backup of objects that can be rolled back is deferred
        * until after the block opens (see {@link #blockSetup} and
        * {@link #makeBackup}).
        * <p>
        * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
        * the core data structures are initialized but no rollback support will
        * be provided.
        * <p>
        * This method will honor any pending thread interruption by throwing
        * a {@link StopConditionException} using {@link #honorStopCondition}.
        * <p>
        * This will create a block of <code>BlockType.UNKNOWN</code> and block
        * properties <code>PROP_NONE</code>.
        *
        * @param    label
        *           The text label associated with the block.  This usually
        *           should correspond with the label used in the source file
        *           to identify the block for purposes of the break and continue
        *           statements.
        * @param    level
        *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
        *           {@link #TRANSACTION}.  This code defines the level of
        *           rollback and transaction support which should be provided.
        * @param    external
        *           <code>true</code> if this block is associated with the
        *           external procedure (in Progress terms). This controls the
        *           cleanup of certain resources such as temp-tables, frames
        *           and streams.
        * @param    topLevel
        *           <code>true</code> if this block is a method.
        * @param    loop
        *           Defines if this block is a loop or not.
        * @param    next
        *           <code>true</code> if this infinite loop protection should
        *           convert a retry into a next (continue), otherwise the retry
        *           will be converted into a leave (break).
        *
        * @throws   StopConditionException
        *           If the current thread's was previously interrupted.
        */
       public static void pushScope(String    label,
                                    int       level,
                                    boolean   external,
                                    boolean   topLevel,
                                    boolean   loop,
                                    boolean   next)
       {
          pushScope(label,
                    level,
                    PROP_NONE,
                    external,
                    topLevel,
                    loop,
                    next,
                    UNKNOWN);
       }

       /**
        * Returns the label of the current block scope.
        *
        * @return   See above.
        */
       public static String getScopeLabel()
       {
          Stack<BlockDefinition> blocks = workArea.obtain().blocks;
          return blocks.size() > 0 ? blocks.peek().label : null;
       }

       /**
        * Initializes a new scope, the associated block definition data
        * structures.  A backup of objects that can be rolled back is deferred
        * until after the block opens (see {@link #blockSetup} and
        * {@link #makeBackup}).
        * <p>
        * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
        * the core data structures are initialized but no rollback support will
        * be provided.
        * <p>
        * This method will honor any pending thread interruption by throwing
        * a {@link StopConditionException} using {@link #honorStopCondition}.
        * <p>
        * This will create a block with block properties <code>PROP_NONE</code>.
        *
        * @param    label
        *           The text label associated with the block.  This usually
        *           should correspond with the label used in the source file
        *           to identify the block for purposes of the break and continue
        *           statements.
        * @param    level
        *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
        *           {@link #TRANSACTION}.  This code defines the level of
        *           rollback and transaction support which should be provided.
        * @param    external
        *           <code>true</code> if this block is associated with the
        *           external procedure (in Progress terms). This controls the
        *           cleanup of certain resources such as temp-tables, frames
        *           and streams.
        * @param    topLevel
        *           <code>true</code> if this block is a method.
        * @param    loop
        *           Defines if this block is a loop or not.
        * @param    next
        *           <code>true</code> if this infinite loop protection should
        *           convert a retry into a next (continue), otherwise the retry
        *           will be converted into a leave (break).
        * @param    blockType
        *           Specifies this block's type.
        *
        * @throws   StopConditionException
        *           If the current thread's was previously interrupted.
        */
       public static void pushScope(String    label,
                                    int       level,
                                    boolean   external,
                                    boolean   topLevel,
                                    boolean   loop,
                                    boolean   next,
                                    BlockType blockType)
       {
          pushScope(label,
                    level,
                    PROP_NONE,
                    external,
                    topLevel,
                    loop,
                    next,
                    blockType);
       }

       /**
        * Initializes a new scope, the associated block definition data
        * structures.  A backup of objects that can be rolled back is deferred
        * until after the block opens (see {@link #blockSetup} and
        * {@link #makeBackup}).
        * <p>
        * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
        * the core data structures are initialized but no rollback support will
        * be provided.
        * <p>
        * This method will honor any pending thread interruption by throwing
        * a {@link StopConditionException} using {@link #honorStopCondition}
        *
        * @param    label
        *           The text label associated with the block.  This usually
        *           should correspond with the label used in the source file
        *           to identify the block for purposes of the break and continue
        *           statements.
        * @param    level
        *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
        *           {@link #TRANSACTION}.  This code defines the level of
        *           rollback and transaction support which should be provided.
        * @param    props
        *           A bitfield storing block properties which will be a
        *           combination of {@link #PROP_NONE}, {@link #PROP_ERROR},
        *           {@link #PROP_ENDKEY}, {@link #PROP_STOP} or
        *           {@link #PROP_QUIT}.
        * @param    external
        *           <code>true</code> if this block is associated with the
        *           external procedure (in Progress terms). This controls the
        *           cleanup of certain resources such as temp-tables, frames
        *           and streams.
        * @param    topLevel
        *           <code>true</code> if this block is a method.
        * @param    loop
        *           Defines if this block is a loop or not.
        * @param    next
        *           <code>true</code> if this infinite loop protection should
        *           convert a retry into a next (continue), otherwise the retry
        *           will be converted into a leave (break).
        * @param    blockType
        *           Specifies this block's type.
        *
        * @throws   StopConditionException
        *           If the current thread's was previously interrupted.
        */
       public static void pushScope(String    label,
                                    int       level,
                                    int       props,
                                    boolean   external,
                                    boolean   topLevel,
                                    boolean   loop,
                                    boolean   next,
                                    BlockType blockType)
       {
          WorkArea wa = workArea.obtain();
          pushScope(wa,
                   label,
                   level,
                   props,
                   external,
                   topLevel,
                   loop,
                   next,
                   false,
                   blockType);
       }

       /**
        * Remove the current block from the stack of scopes, process any pending
        * rollbacks (if the current block is the proper target), notify any
        * objects needing commits (if the block finished successfully) and handle
        * finish processing for the block.  This method must be called from a
        * <code>finally</code> block such that this is guaranteed to be called
        * no matter how the flow of control exits the associated block (exception,
        * break, return or the flow of control naturally passing through the
        * end of the block).
        * <p>
        * Rollback and commit processing only occur when there is an active
        * transaction.  Otherwise this processing is bypassed.  Finish processing
        * is always called.
        * <p>
        * This method implements global finish processing (as the last action
        * before returning) when the last scope is popped from the stack of
        * scopes.  For this reason, the current implementation assumes that the
        * session is over at the moment that the top-most user-defined scope
        * exits.
        * <p>
        * This method will honor any pending thread interruption by throwing
        * a {@link StopConditionException} using {@link #honorStopCondition}
        *
        * @throws   StopConditionException
        *           If the current thread's was previously interrupted.
        */
       public static void popScope()
       throws StopConditionException
       {
          popScope(workArea.obtain());
       }

       /**
        * Notify {@link ConditionListener} instances about condition.
        *
        * @param   condition
        *          The condition the listeners will be notified.
        */
       public static void notifyCondition(BlockManager.Condition condition)
       {
          // do not pass null down
          if (condition == null)
          {
             return;
          }

          notifyCondition(workArea.obtain(), condition);
       }

       /**
        * Notify {@link ConditionListener} instances about exception condition.
        *
        * @param   condition
        *          The instance of {@link ConditionException}.
        */
       public static void notifyCondition(ConditionException condition)
       {
          notifyCondition(BlockManager.exceptionToCondition(condition));
       }

       /**
        * Check if the latest block has initialized.
        *
        * @return   See above.
        */
       public static boolean blockInitialized()
       {
          BlockDefinition blk = workArea.obtain().blocks.peek();

          return blk.init;
       }

       /**
        * Get a helper to access certain context-local state required for undoable support.
        */
       public static UndoableHelper getUndoableHelper()
       {
          return new UndoableHelper(workArea.obtain());
       }

       /**
        * Get a helper to access certain context-local state required for transaction support.
        */
       public static TransactionHelper getTransactionHelper()
       {
          return new TransactionHelper(workArea.obtain());
       }

       /**
        * Provides a callback entry point for the top of the loop.  This location
        * is required to handle the backup of undo variables via
        * {@link #makeBackup}.  On the second and subsequent executions of this
        * method for a given block, this method handles <code>iterate</code>
        * notifications.
        * <p>
        * It is important to note that <code>continue</code> can be called from
        * nested blocks at arbitrary depths.  This method can handle such nesting
        * at any level.
        * <p>
        * This method will honor any pending thread interruption by throwing
        * a {@link StopConditionException} using {@link #honorStopCondition}
        *
        * @throws   StopConditionException
        *           If the current thread's was previously interrupted.
        */
       public static void blockSetup()
       throws StopConditionException
       {
          blockSetup(workArea.obtain());
       }

       /**
        * Creates or updates the backup set for all objects needing rollback
        * support in the current block, if a transaction is active.  This is
        * split up from {@link #pushScope} and {@link #blockSetup} due to the
        * semantics of Progress in which the backup set is created or refreshed
        * after the loop control variables have been modified.
        */
       public static void makeBackup()
       {
          backupWorker(workArea.obtain(), null);
       }

       /**
        * Reports if there is a current transaction which is active.
        *
        * @return    <code>true</code> if a transaction is active.
        */
       public static logical isTransactionActive()
       {
          return new logical(isTransaction());
       }

       /**
        * Reports if the current iteration of this block or loop is a retry.
        *
        * @return    <code>true</code> if this is a retry of this block or loop
        *            (as opposed to the first time through this iteration).
        */
       public static boolean _isRetry()
       {
          return _isRetry(workArea.obtain());
       }

       /**
        * Reports if the current iteration of this block or loop is a retry.
        *
        * @return    <code>true</code> if this is a retry of this block or loop
        *            (as opposed to the first time through this iteration).
        */
       public static logical isRetry()
       {
          return new logical(_isRetry());
       }

       /**
        * Records the fact that the pending RETRY is being caused by an ENDKEY
        * condition. This enables "downstream" retry processing to implement the
        * "stuttering" quirk and some behavior in honoring PAUSE as a way to
        * disable infinite loop protection.
        */
       public static void markEndkeyRetry()
       {
          markEndkeyRetry(workArea.obtain());
       }

       /**
        * Disables infinite loop protection for the remainder of the current
        * iteration of the innermost block.  This should be called from any
        * runtime code that blocks for user input AND from the {@link #isRetry}
        * method.
        *
        * @param    blk
        *           The block on which to operator or <code>null</code> to
        *           operate on the current (innermost) block.
        */
       public static void disableLoopProtection(BlockDefinition blk)
       {
          if (blk == null)
          {
             // peek at the current block def on the top of the stack
             blk = workArea.obtain().blocks.peek();
          }

          blk.loopProtection = false;
       }

       /**
        * This checks if any of the enclosing loops has iterated at least once.
        *
        * @return   <code>true</code> if any of the enclosing loops has iterated
        *           at least once.
        */
       public static boolean hasParentIteratedBlock()
       {
          return hasParentIteratedBlock(workArea.obtain());
       }

       /**
        * Determines if a NEXT operation with the given target block should
        * be honored or if the NEXT should be converted to a LEAVE. This is
        * a special form of infinite loop protection.
        * <p>
        * A NEXT is converted into a LEAVE when all of the following conditions
        * are true:
        * <p>
        * <ul>
        *   <li> The NEXT is associated with the UNDO, NEXT language statement or
        *        with an ON phrase that has UNDO, NEXT as the result of the given
        *        condition (e.g. ON ENDKEY UNDO, NEXT). It is important to note
        *        that Progress does not provide infinite loop protection for the
        *        NEXT language statement, so there is no conversion to LEAVE in
        *        that case.
        *   <li> The target block (whether implicitly determined by the lack of a
        *        label or explicitly determined by the presence of a label) must
        *        also have been the target block for the UNDO operation. It is
        *        possible for the NEXT to be targetted at a more enclosing block
        *        than the UNDO, in which case there is no conversion to LEAVE.
        *   <li> The target block type must be REPEAT, REPEAT WHILE or DO WHILE.
        *        These are the only loop types that normally convert RETRY to
        *        LEAVE instead of NEXT.  Likewise, these are the only loop types
        *        which honor infinite loop protection for NEXT by converting it
        *        to a LEAVE.
        *   <li> Infinite loop protection must not have been disabled for the
        *        target block.  In this case, any form of PAUSE is honored as
        *        one of the mechanisms to disable infinite loop protection.
        * </ul>
        * <p>
        * Since NEXT is only possible on a looping block, if the target block
        * is not a looping block then this method will return <code>false</code>.
        *
        * @param    label
        *           The label of the target block for the NEXT action.
        *
        * @return   <code>true</code> for NEXT, <code>false</code> for LEAVE.
        */
       public static boolean honorNext(String label)
       {
          return honorNext(workArea.obtain(), label);
       }

       /**
        * Determines if the currently executing block should be retried.  This
        * is where infinite loop protection is implemented if it is enabled.
        *
        * @return   <code>true</code> if the block (whether or not it is a loop)
        *           should be retried.
        */
       public static boolean needsRetry()
       {
          return needsRetry(workArea.obtain());
       }

       /**
        * Accesses the state of whether there is a pending break that must be
        * honored.  This would have been generated by the infinite loop
        * protection.
        *
        * @return   <code>true</code> if a break is pending for this block.
        */
       public static boolean isBreakPending()
       {
          return isBreakPending(workArea.obtain());
       }

       /**
        * Force an error to be generated such that it will be caught in the
        * caller of this method.  The stack will be unwound and normal error
        * processing bypassed until this method returns.  This unwinding is
        * dependent upon catch blocks being implemented with a corresponding call
        * to {@link #honorError}.
        * <p>
        * In the case where silent error mode is enabled in the calling method,
        * the "normal" stack unwinding approach cannot be used because the
        * exception cannot be thrown. Instead, the error manager will be updated
        * with the error data and this method will silently return.  It is
        * critical that in such cases the calling code must immediately execute a
        * <code>return</code> statement which will maintain the proper Progress
        * behavior.  The caller can check the error manager to determine that an
        * error occurred.
        * <p>
        * Detecting the silent error mode in the caller requires that the top
        * level block be found.  The silent error flag of the caller is cached
        * there.
        *
        * @param    text
        *           The text to use as the error message in the exception.
        *
        * @throws   ErrorConditionException
        */
       public static void triggerErrorInCaller(String text)
       throws ErrorConditionException
       {
          triggerErrorInCaller(workArea.obtain(), null, text);
       }
       /**
        * Force an error to be generated such that it will be caught in the caller of this method. The
        * stack will be unwound and normal error processing bypassed until this method returns. This
        * unwinding is dependent upon catch blocks being implemented with a corresponding call to
        * {@link #honorError}.
        * <p>
        * In the case where silent error mode is enabled in the calling method, the "normal" stack
        * unwinding approach cannot be used because the exception cannot be thrown. Instead, the error
        * manager will be updated with the error data and this method will silently return. It is
        * critical that in such cases the calling code must immediately execute a {@code return}
        * statement which will maintain the proper Progress behavior. The caller can check the error
        * manager to determine that an error occurred.
        * <p>
        * Detecting the silent error mode in the caller requires that the top level block be found.
        * The silent error flag of the caller is cached there.
        * <p>
        * NOTE: The error message will NOT be printed if the calling procedure is the 'main' one.
        *
        * @param   errNum
        *          The identifier of the error.
        * @param   text
        *          The text to use as the error message in the exception.
        *
        * @throws  ErrorConditionException
        */
       public static void triggerErrorInCaller(int errNum, String text)
       throws ErrorConditionException
       {
          triggerErrorInCaller(workArea.obtain(), errNum, text);
       }

       /**
        * Force an error to be generated such that it will be caught in the caller of this method. The
        * stack will be unwound and normal error processing bypassed until this method returns. This
        * unwinding is dependent upon catch blocks being implemented with a corresponding call to
        * {@link #honorError}.
        * <p>
        * In the case where silent error mode is enabled in the calling method, the "normal" stack
        * unwinding approach cannot be used because the exception cannot be thrown. Instead, the error
        * manager will be updated with the error data and this method will silently return. It is
        * critical that in such cases the calling code must immediately execute a {@code return}
        * statement which will maintain the proper Progress behavior. The caller can check the error
        * manager to determine that an error occurred.
        * <p>
        * Detecting the silent error mode in the caller requires that the top level block be found.
        * The silent error flag of the caller is cached there.
        * <p>
        * NOTE: The error message will NOT be printed if the calling procedure is the 'main' one.
        *
        * @param   errNum
        *          The identifier of the error.
        * @param   text
        *          The text to use as the error message in the exception.
        * @param   errFlag
        *          Flag indicating the ERROR-ERROR:ERROR is set.
        * @param   prefix
        *          Prefix or not with two asterics when building the error message text.
        *
        * @throws  ErrorConditionException
        */
       public static void triggerErrorInCaller(int errNum, String text, boolean errFlag, boolean prefix)
       throws ErrorConditionException
       {
          triggerErrorInCaller(workArea.obtain(), errNum, text, errFlag, prefix);
       }

       /**
        * Check if this block's caller requires errors to be suppressed.
        *
        * @return   {@link BlockDefinition#suppressError} for {@link #nearestTopLevel} block.
        */
       public static boolean isSuppressError()
       {
          return isSuppressError(workArea.obtain());
       }

       /**
        * Force an error to be generated such that it will be caught in the
        * caller of this method.  The stack will be unwound and normal error
        * processing bypassed until this method returns.  This unwinding is
        * dependent upon catch blocks being implemented with a corresponding call
        * to {@link #honorError}.
        * <p>
        * In the case where silent error mode is enabled in the calling method,
        * the "normal" stack unwinding approach cannot be used because the
        * exception cannot be thrown. Instead, the error manager will be updated
        * with the error data and this method will silently return.  It is
        * critical that in such cases the calling code must immediately execute a
        * <code>return</code> statement which will maintain the proper Progress
        * behavior.  The caller can check the error manager to determine that an
        * error occurred.
        * <p>
        * Detecting the silent error mode in the caller requires that the top
        * level block be found.  The silent error flag of the caller is cached
        * there.
        *
        * @throws   ErrorConditionException
        */
       public static void triggerErrorInCaller()
       throws ErrorConditionException
       {
          triggerErrorInCaller("");
       }

       /**
        * Force a retry of the current block (if there is no pending rollback)
        * or retry the same block as the pending rollback, in either case
        * ensure that the flow of control will not naturally continue.
        * <p>
        * A {@link RetryUnwindException} will be thrown to unwind the stack
        * to the given scope, even if the scope is the current block.  This
        * unwinding is dependent upon catch blocks being implemented with a
        * corresponding call to {@link #honorRetry}.
        * <p>
        * <b>This method will NEVER naturally return since an exception is ALWAYS
        * thrown.</b>  This method should generally not be called from within a
        * catch block if the intention is to retry the current block since it
        * would be outside of the block's associated <code>try</code>.  In that
        * case, only an enclosing block could be retried.
        *
        * @throws   RetryUnwindException
        *           This method always generates this exception to ensure that
        *           natural processing will not continue.
        */
       public static void forceRetry()
       throws RetryUnwindException
       {
          forceRetry(null);
       }

       /**
        * Force a retry of the current block or the scope named by the given
        * label, ensuring that the flow of control will not naturally continue.
        * A {@link RetryUnwindException} will be thrown to unwind the stack
        * to the given scope, even if the scope is the current block.  This
        * unwinding is dependent upon catch blocks being implemented with a
        * corresponding call to {@link #honorRetry}.
        * <p>
        * <b>This method will NEVER naturally return since an exception is ALWAYS
        * thrown.</b>  This method should generally not be called from within a
        * catch block if the intention is to retry the current block since it
        * would be outside of the block's associated <code>try</code>.  In that
        * case, only an enclosing block could be retried.
        *
        * @param    label
        *           The name of the scope to retry. Specify <code>null</code> to
        *           retry the scope that has a pending rollback OR to retry
        *           the current scope if there is no pending rollback.
        *
        * @throws   RetryUnwindException
        *           This method always generates this exception to ensure that
        *           natural processing will not continue.
        */
       public static void forceRetry(String label)
       throws RetryUnwindException
       {
          forceRetry(workArea.obtain(), label);
       }

       /**
        * Force a retry of the current block (if there is no pending rollback)
        * or the block targeted for a pending rollback.  If a containing scope is
        * the target of the retry, then a {@link RetryUnwindException} will be
        * thrown to unwind the stack to the given scope.  This unwinding is
        * dependent upon catch blocks being implemented with a corresponding call
        * to {@link #honorRetry}.
        * <p>
        * If the current block is not the target, the retry is deferred by
        * marking the target block and then unwinding the stack.  Otherwise,
        * this method will return naturally.  This method is only appropriate
        * to be called from within <code>catch</code> blocks since a natural
        * return within a <code>catch</code> block will fall through to allow
        * the retry loop to engage.
        *
        * @throws   RetryUnwindException
        */
       public static void triggerRetry()
       throws RetryUnwindException
       {
          triggerRetry(null);
       }

       /**
        * Force a retry of the current block or the scope named by the given
        * label.  If a containing scope is the target of the retry, then
        * a {@link RetryUnwindException} will be thrown to unwind the stack
        * to the given scope.  This unwinding is dependent upon catch blocks
        * being implemented with a corresponding call to {@link #honorRetry}.
        * <p>
        * If the current block is not the target, the retry is deferred by
        * marking the target block and then unwinding the stack.  Otherwise,
        * this method will return naturally.  This method is only appropriate
        * to be called from within <code>catch</code> blocks since a natural
        * return within a <code>catch</code> block will fall through to allow
        * the retry loop to engage.
        *
        * @param    label
        *           The name of the scope to retry. Specify <code>null</code> to
        *           retry the scope of that has a pending rollback OR to retry
        *           the current scope if there is no pending rollback.
        *
        * @throws   RetryUnwindException
        */
       public static void triggerRetry(String label)
       throws RetryUnwindException
       {
          triggerRetry(workArea.obtain(), label);
       }

       /**
        * Dispatch the given exception to all registered stop condition handlers
        * to see if any requires that the STOP condition be vetoed at the
        * current scope.  If any of the registered handlers indicate that the STOP
        * condition should not be honored at this level, the exception is
        * rethrown, presumably to be honored at a higher scope, or not at all.  In
        * the latter case, the program running in the current context will abort.
        *
        * @param   exc
        *          The exception to be checked.
        *
        * @throws  StopConditionException
        *          if the STOP condition is not honored at this scope.
        */
       public static void honorStop(StopConditionException exc)
       throws StopConditionException
       {
          honorStop(workArea.obtain(), exc);
       }

       /**
        * Check if there is a pending retry of the current block and if so then
        * enable retry.  This is the "last half" of the processing to unwind the
        * stack up to a containing scope that is the target of a retry.  The
        * "first half" of the unwinding is implemented by throwing a
        * {@link RetryUnwindException}.  This unwinding is dependent upon catch
        * blocks being implemented to call this method.
        * <p>
        * If the current block is not the target for the pending retry, the
        * exception is re-thrown.
        *
        * @param    rue
        *           The exception to honor or ignore.
        *
        * @throws   RetryUnwindException
        */
       public static void honorRetry(RetryUnwindException rue)
       throws RetryUnwindException
       {
          honorRetry(workArea.obtain(), rue);
       }

       /**
        * Check if the current error must unwind all the way to the caller of
        * the top-level block in this method (if the ignore flag is set).  If
        * this is the case then the error is re-thrown.  Before this occurs, if
        * the current block is the top-level block of this method, then the
        * ignore flag will be cleared.  If the ignore flag is <code>false</code>
        * then this method returns silently.
        * <p>
        * This code also honors any nested error that was generated while another
        * error was being processed AND while nested error mode was enabled.  Any
        * such stored error will be re-thrown here.
        *
        * @param    err
        *           The exception to honor or ignore.
        *
        * @throws   ErrorConditionException
        */
       public static void honorError(ErrorConditionException err)
       throws ErrorConditionException
       {
          honorError(workArea.obtain(), err);
       }

       /**
        * Notifies the transaction manager that an unexpected exception or error
        * has occurred and that any transaction must be aborted. This will
        * maintain the state of the system properly, including rolling back any
        * open transaction. Once the state is properly maintained, the triggering
        * <code>Throwable</code> will be re-thrown as a
        * <code>RuntimeException</code>.
        * <p>
        * If the exception is of type <code>ConditionException</code> or
        * <code>RetryUnwindException</code>, this method will do nothing since
        * these are used for normal flow of control purposes and are used in
        * close coordination with the transaction manager's state.  They need
        * no other special behavior to be safe.  This method is designed to
        * handle exceptions (like <code>NullPointerException</code>) which are
        * not valid in a converted 4GL program.
        *
        * @param    thr
        *           The unexpected exception or error.
        *
        * @throws   RuntimeException
        */
       public static void abnormalEnd(Throwable thr)
       throws RuntimeException
       {
          abnormalEnd(thr, true);
       }

       /**
        * Notifies the transaction manager that an unexpected exception or error
        * has occurred and that any transaction must be aborted. This will
        * maintain the state of the system properly, including rolling back any
        * open transaction. Once the state is properly maintained, the triggering
        * <code>Throwable</code> will be re-thrown as a
        * <code>RuntimeException</code>.
        * <p>
        * If the exception is of type <code>ConditionException</code> or
        * <code>RetryUnwindException</code>, this method will do nothing since
        * these are used for normal flow of control purposes and are used in
        * close coordination with the transaction manager's state.  They need
        * no other special behavior to be safe.  This method is designed to
        * handle exceptions (like <code>NullPointerException</code>) which are
        * not valid in a converted 4GL program.
        *
        * @param    thr
        *           The unexpected exception or error.
        * @param    needsNotify
        *           <code>true</code> if condition notification must be executed
        *           (only happens if the <code>thr</code> is a condition).
        *
        * @throws   RuntimeException
        */
       public static void abnormalEnd(Throwable thr, boolean needsNotify)
       throws RuntimeException
       {
          abnormalEnd(workArea.obtain(), thr, needsNotify);
       }

       /**
        * Reports if the given transaction level represents the case where the
        * block has no effect on transaction processing.
        *
        * @param    lvl
        *           The transaction level to test.
        *
        * @return   <code>true</code> if the level is <code>NO_TRANSACTION</code>.
        */
       public static boolean isNoTransactionLevel(int lvl)
       {
          return (lvl == NO_TRANSACTION);
       }

       /**
        * Reports if the given transaction level represents the case where the
        * block has creates a sub-transaction when a transaction is active.
        *
        * @param    lvl
        *           The transaction level to test.
        *
        * @return   <code>true</code> if the level is
        *           <code>SUB_TRANSACTION</code>.
        */
       public static boolean isSubTransactionLevel(int lvl)
       {
          return (lvl == SUB_TRANSACTION);
       }

       /**
        * Reports if the given transaction level represents the case where the
        * block has creates a new transaction if a transaction is not active or
        * creates a sub-transaction when a transaction is active.
        *
        * @param    lvl
        *           The transaction level to test.
        *
        * @return   <code>true</code> if the level is <code>TRANSACTION</code>.
        */
       public static boolean isTransactionLevel(int lvl)
       {
          return (lvl == TRANSACTION);
       }

       /**
        * Reports the transaction level of the current block.
        *
        * @return   The transaction level.
        */
       public static int currentTransactionLevel()
       {
          BlockDefinition blk = workArea.obtain().blocks.peek();
          return blk.level;
       }

       /**
        * Reports if the current scope defines the outermost transaction block
        * (which is the scope at which final commit processing must occur).
        *
        * @return   <code>true</code> if the current scope defines the top-level
        *           transaction.
        */
       public static boolean isFullTransaction()
       {
          return workArea.obtain().isFullTransaction();
       }

       /**
        * Reports if there is a current transaction which is active.
        *
        * @return    <code>true</code> if a transaction is active.
        */
       public static boolean isTransaction()
       {
          return workArea.obtain().isTransaction();
       }

       /**
        * Obtain the current transaction id for a database (DBTASKID function).
        * <p>
        * The {@code DBTASKID} function is related to _trans VST. However, the relation is not 1:1.
        * If, in a transaction, any record from a database is involved (updated OR {@code FIND},
        * regardless of the lock level - and this includes the {@code _Trans} VST, too) then the task
        * (transaction) id is incremented and the new value is returned by {@code DBTASKID} function.
        * Otherwise (all records of the database are not accessed in any way OR just read access to
        * buffers loaded before the transaction started) the {@code _Trans._Trans-Num} of the database
        * is NOT incremented and {@code DBTASKID} function returns {@code ?} (unknown value).
        * <p>
        * In other words, even if the transaction block is already being executed, {@code DBTASKID}
        * function (so this method) returns unknown value for a database, until at least a field is
        * updated or a buffer is loaded (with {@code FIND} or {@code for}).
        *
        * @param   db
        *          The key name of the database.
        *
        * @return  The the current transaction id of the database or {@code null} if the database is
        *          unknown / not connected or if the transaction id has not been set yet.
        */
       public static Integer getTransactionId(String db)
       {
          // no need to check whether there is a transaction; the direct map lookup is much faster
          DbTransactions dbData;

          // this might not be necessary. The [transactionData] is only populated during dbs init
          synchronized (transactionData)
          {
             dbData = transactionData.get(db);
             if (dbData == null)
             {
                return null; // no known data about this database
             }
          }

          synchronized (dbData)
          {
             SecurityManager sm = SecurityManager.getInstance();
             if (sm == null)
             {
                return null; // a bit strange, should not happen
             }

             Integer userId = sm.sessionSm.getSessionId();
             if (userId == null)
             {
                return null; // a bit strange, should not happen
             }

             return dbData.transactions.get(userId); // may be null if a transaction is not active
          }
       }

       /**
        * Flushes all known content of {@code _Trans} metadata table to database. This is done before
        * every access to {@code _Trans} table to make sure the fresh data is accessible by the SQL.
        *
        * @param   p
        *          The persistence to be used.
        */
       public static void flushTransMetaData(Persistence p)
       {
          if (!TransactionTableUpdater.isEnabled())
          {
             return; // the _Trans metadata table is not active
          }

          DbTransactions dbData;
          TransactionTableUpdater updater;
          synchronized (transactionData)
          {
             if (updaters.isEmpty())
             {
                return; // quick out
             }

             String dbName = p.getDatabase(Persistence.META_CTX).getId();
             updater = updaters.get(dbName);
             if (updater == null)
             {
                return; // the _Trans metadata table is not enabled for this database
             }

             dbData = transactionData.get(dbName);
             if (dbData == null || !dbData.isDirty)
             {
                // no known data about this database. We can safely return here, there was no
                // transaction activity for this database.
                return;
             }
          }

          synchronized (dbData)
          {
             updater.flush(p, dbData.transactions);

             // mark database as clean (states for all transactions were flushed)
             dbData.isDirty = false;
          }
       }

       /**
        * Reports if there is a current active transaction at the specified block depth.
        *
        * @param     blockDepth
        *            Zero-based block depth starting from the outermost block at which transaction
        *            status is checked.
        *
        * @return    <code>true</code> if a transaction is active at the specified block depth.
        */
       public static boolean isTransactionAt(int blockDepth)
       {
          int trans = workArea.obtain().transLevel;
          return trans != -1 && trans <= blockDepth;
       }

       /**
        * Reports if the current block is a top-level block (this corresponds
        * with a method-level block).
        *
        * @return  <code>true</code> if top-level block, else <code>false</code>.
        */
       public static boolean isTopLevelBlock()
       {
          return isTopLevelBlock(workArea.obtain());
       }

       /**
        * Reports if the current block is a external block (this corresponds
        * with a method-level block that was derived from a Progress external
        * procedure).  This will be <code>false</code> for blocks derived from
        * internal procedures, functions and triggers.
        *
        * @return   <code>true</code> if it is an external block, else
        *           <code>false</code>.
        */
       public static boolean isExternalBlock()
       {
          BlockDefinition blk = workArea.obtain().blocks.peek();
          return blk.external;
       }

       /**
        * Reports if the current block is a global scoped block. For normal contexts:
        * <code>true</code> if the current block is the only block on the stack of block scopes; for
        * appserver agent contexts: <code>true</code> if the current block is one of two outermost
        * blocks ("appserver-agent" and "startup") on the stack of block scopes.
        *
        * @return   See above.
        */
       public static boolean isGlobalBlock()
       {
          WorkArea wa = workArea.obtain();
          return isGlobalBlock(wa);
       }

       /**
        * Reports if the current block has the ERROR property.
        *
        * @return   <code>true</code> if the current block has the
        *           <code>PROP_ERROR</code> bit set in its properties bitmap.
        */
       public static boolean hasErrorProp()
       {
          return hasProperty(workArea.obtain(), PROP_ERROR);
       }

       /**
        * Reports if the current block has the ENDKEY property.
        *
        * @return   <code>true</code> if the current block has the
        *           <code>PROP_ENDKEY</code> bit set in its properties bitmap.
        */
       public static boolean hasEndkeyProp()
       {
          return hasProperty(workArea.obtain(), PROP_ENDKEY);
       }

       /**
        * Reports if the current block has the STOP property.
        *
        * @return   <code>true</code> if the current block has the
        *           <code>PROP_STOP</code> bit set in its properties bitmap.
        */
       public static boolean hasStopProp()
       {
          return hasProperty(workArea.obtain(), PROP_STOP);
       }

       /**
        * Reports if the current block has the QUIT property.
        *
        * @return   <code>true</code> if the current block has the
        *           <code>PROP_QUIT</code> bit set in its properties bitmap.
        */
       public static boolean hasQuitProp()
       {
          return hasProperty(workArea.obtain(), PROP_QUIT);
       }

       /**
        * During global finish processing, this reports if the exit was caused
        * by a <code>QuitConditionException</code>.
        *
        * @return   <code>true</code> if this exit is due to a quit condition.
        */
       public static boolean isProcessingQuit()
       {
          return workArea.obtain().processingQuit;
       }

       /**
        * During global finish processing, this sets the flag for an exit that
        * was caused by a <code>QuitConditionException</code>.
        *
        * @param    quit
        *           <code>true</code> if this exit is due to a quit condition.
        */
       public static void setProcessingQuit(boolean quit)
       {
          workArea.obtain().processingQuit = quit;
       }

       /**
        * Report if this context should be treated as headless. A headless
        * session is one that has no user interaction.
        *
        * @return   <code>true</code> if this session is headless.
        */
       public static boolean isHeadless()
       {
          return workArea.obtain().headless;
       }

       /**
        * Increases the number (by 1) of interactive language statements
        * (statements which took user input except for PAUSE) that have executed
        * in the current block since the most recent start scope, iterate or
        * retry.
        */
       public static void incrementInteractions()
       {
          BlockDefinition blk = workArea.obtain().blocks.peek();
          blk.interactions++;
       }

       /**
        * Resets the number of interactive language statements (statements which
        * took user input except for PAUSE) that have executed in the current
        * block to 0.
        */
       public static void resetInteractions()
       {
          BlockDefinition blk = workArea.obtain().blocks.peek();
          blk.interactions = 0;
       }

       /**
        * Gets the number of interactive language statements (statements which
        * took user input except for PAUSE) that have executed in the current
        * block  since the most recent start scope, iterate or retry.
        *
        * @return   The number of interactions in the current block.
        */
       public static int getInteractions()
       {
          BlockDefinition blk = workArea.obtain().blocks.peek();
          return blk.interactions;
       }

       /**
        * Get the rollup value for the current block. If <code>true</code>,
        * then any contained blocks that exit will cause the current (containing)
        * block's interactions counter to be increased by the contained block's
        * interactions counter. Otherwise, no aggregation will occur.
        *
        * @return   The current block's rollup value.
        */
       public static boolean isRollup()
       {
          BlockDefinition blk = workArea.obtain().blocks.peek();
          return blk.rollup;
       }

       /**
        * Get the rollup value for the enclosing block. If <code>true</code>,
        * then the current block's exit will cause the enclosing (containing)
        * block's interactions counter to be increased by the current block's
        * interactions counter. Otherwise, no aggregation will occur.
        *
        * @return   The current block's rollup value.
        */
       public static boolean isEnclosingRollup()
       {
          return isEnclosingRollup(workArea.obtain());
       }

       /**
        * Set the new rollup value for the current block. If <code>true</code>,
        * then any contained blocks that exit will cause the current (containing)
        * block's interactions counter to be increased by the contained block's
        * interactions counter. Otherwise, no aggregation will occur.
        *
        * @param    rollup
        *           The new rollup value.
        */
       public static void setRollup(boolean rollup)
       {
          BlockDefinition blk = workArea.obtain().blocks.peek();
          blk.rollup = rollup;
       }

       /**
        * Notify all registered {@link BatchListener} objects that a batch is
        * starting.
        */
       public static void batchStart()
       {
          processBatchNotifications(workArea.obtain(), true);
       }

       /**
        * Notify all registered {@link BatchListener} objects that a batch is
        * stopping.
        */
       public static void batchStop()
       {
          processBatchNotifications(workArea.obtain(), false);
       }

       /**
        * Tests the interrupted status of the current thread using
        * <code>Thread.interrupted</code>, if <code>true</code> then a
        * {@link StopConditionException} will be thrown. The interrupted status
        * will be <code>true</code> if a call to <code>Thread.interrupt</code>
        * was previously made.
        * <p>
        * The thread's interrupted status is cleared by this method before the
        * <code>StopConditionException</code> is thrown.
        *
        * @throws   StopConditionException
        *           If the current thread's was previously interrupted.
        * @throws   SilentUnwindException
        *           If the connection ended prematurely end the current thread
        *           needs to stop.
        */
       public static void honorStopCondition()
       throws StopConditionException,
              SilentUnwindException
       {
          workArea.obtain().honorStopCondition();
       }

       /**
        * Method called when the processing needs to be terminated. This will set a flag which will
        * indicate that the processing should be ended by raising a {@link
        * LeaveUnwindException error}, on the next {@link #honorStopCondition()} call.
        */
       public static void abortProcessing()
       {
          if (workArea.get(false) == null)
          {
             // if the context does not exist, do not create it at this time
             return;
          }

          WorkArea wa = workArea.obtain();
          wa.abortProcessing = true;
       }

       /**
        * Enable/disable nested error mode. This mode allows for storing an error
        * which is generated during the processing of a different error, so that
        * during {@link #honorError} this can be re-thrown.  This duplicates some
        * obscure behavior seen in Progress.
        *
        * @param    mode
        *           <code>true</code> to enable, <code>false</code> to disable.
        */
       public static void setNestedMode(boolean mode)
       {
          workArea.obtain().nested = mode;
       }

       /**
        * Reports on the enable/disable state of nested error mode. This mode
        * allows for storing an error which is generated during the processing of
        * a different error, so that during {@link #honorError} this can be
        * re-thrown.
        *
        * @return   <code>true</code> if nested error mode is enabled.
        */
       public static boolean isNestedMode()
       {
          return workArea.obtain().nested;
       }

       /**
        * Returns the current block type.
        *
        * @return   The type of the current block.
        */
       public static BlockType getBlockType()
       {
          BlockDefinition blk = workArea.obtain().blocks.peek();

          return blk.type;
       }

       /**
        * Returns the index of the full transaction block, or -1 if none exists.
        *
        * @return   The index of the full transaction block, or -1 if none exists.
        */
       public static int getFullTransactionBlock()
       {
          return getFullTransactionBlock(workArea.obtain());
       }

       /**
        * Returns the current nesting level.
        *
        * @return   the current nesting level.
        */
       public static int getNestingLevel()
       {
          return workArea.obtain().blocks.size();
       }

       /**
        * Check if this block is the block associated with the root external program,
        * in the 4GL legacy stack.
        *
        * @return   See above.
        */
       public static boolean isRootNestingLevel()
       {
          int nestingLevel = getNestingLevel();
          WorkArea wa = workArea.obtain();
          return nestingLevel == (wa.isRemote() ? 3 : 2);
       }

       /**
        * Check if this block is the block associated with the root external program,
        * in the 4GL legacy stack using the nearest external block.
        *
        * @return   See above.
        */
       public static boolean isRootExternal()
       {
          int nestingLevel = findNearestExternal();
          WorkArea wa = workArea.obtain();
          // in case of appserver calls, the root can be an internal entry or an external program.
          // in internal entry case, 'nestingLevel' will be resolved to 2.
          return nestingLevel <= (wa.isRemote() ? 3 : 2);
       }

       /**
        * Store the given exception for re-throwing during {@link #honorError} if
        * nested error mode is enabled.  Note that only 1 error can ever be nested
        * and the last one set via this method will be the one that is honored.
        *
        * @param    err
        *           The error to store.
        */
       public static void saveNestedError(ConditionException err)
       {
          WorkArea wa = workArea.obtain();

          if (wa.nested)
          {
             wa.nestedError = err;
          }
       }

       /**
        * Notifies this class that an interactive pause has occurred. This may
        * affect the processing of infinite loop protection depending on the
        * type of block being processed.  This state is set for the remainder of
        * the current iteration of the innermost block.
        */
       public static void hadPause()
       {
          // peek at the current block def on the top of the stack
          BlockDefinition blk = workArea.obtain().blocks.peek();
          blk.hadPause = true;
       }

       /**
        * Search the stack of scopes and return the level of the first external
        * block found.
        *
        * @return   The stack level of the nearest enclosing external block or
        *           -1 if there is no block marked as an external block. Level
        *           number is 1-based starting from the outermost scope.
        */
       public static int findNearestExternal()
       {
          return findNearestExternal(workArea.obtain());
       }

       /**
        * This method is called before the initialization of a FOR or FOR EACH
        * block. It will clear the {@link WorkArea#offEndQueries} set and will
        * set the {@link WorkArea#offEndRegistration} flag to ON. While this flag
        * is ON, all created queries will be collected in the
        * {@link WorkArea#offEndQueries} set.
        * <p>
        * After the block initialization is finished, the
        * {@link #stopOffEndRegistration()} will be called.
        */
       public static void startOffEndRegistration()
       {
          WorkArea wa = workArea.obtain();

          wa.offEndRegistration = true;
          wa.offEndQueries.push(new HashSet<>());
       }

       /**
        * Pause off-end queries registration started by {@link #startOffEndRegistration()}.
        */
       public static void pauseOffEndRegistration()
       {
          WorkArea wa = workArea.obtain();
          wa.savedOffEndRegistration = wa.offEndRegistration;
          wa.offEndRegistration = false;
       }

       /**
        * Resume off-end queries registration started by {@link #startOffEndRegistration()} and paused by
        * {@link #pauseOffEndRegistration()}.
        */
       public static void resumeOffEndRegistration()
       {
          WorkArea wa = workArea.obtain();
          wa.offEndRegistration = wa.savedOffEndRegistration;
          wa.savedOffEndRegistration = false;
       }

       /**
        * This method is called after the initialization of a FOR or FOR EACH
        * block is finished. It will save all the {@link QueryOffEndListener}s
        * associated with the registered {@link WorkArea#offEndQueries queries}.
        */
       public static void stopOffEndRegistration()
       {
          stopOffEndRegistration(workArea.obtain());
       }

       /**
        * Register the passed query for query off-end events. The passed queries
        * are all the queries created in the init method for a FOR or FOR EACH
        * block.
        * <p>
        * These queries will be able to end the loop when the last record is
        * reached, by overriding any user-defined END-KEY action. The action
        * wich is executed is the default UNDO, LEAVE action for END-KEY.
        *
        * @param  query
        *         The query which needs to be registered.
        */
       public static void registerOffEndQuery(P2JQuery query)
       {
          registerOffEndQuery(workArea.obtain(), query);
       }

       /**
        * Check if the given listener is registered for query off-end events and
        * can override the default END-KEY action (which is UNDO, LEAVE), when
        * the current block is FOR or FOR EACH.
        *
        * @param   listener
        *          A listener to be checked if is registered for off-end events.
        *
        * @return  <code>true</code> if the listener is registered for query
        *          off-end event.
        */
       public static boolean isOffEndListener(QueryOffEndListener listener)
       {
          return isOffEndListener(workArea.obtain(), listener);
       }

       /**
        * Iterate all registered off-end listeners and call the
        * {@link QueryOffEndListener#initialize() initialize} or
        * {@link QueryOffEndListener#finish() finish} method, depending on the
        * flag.
        *
        * @param   init
        *          <code>true</code> if the listeners are initialized; else, the
        *          {@link QueryOffEndListener#finish()} method is called.
        */
       public static void processOffEndListeners(boolean init)
       {
          processOffEndListeners(workArea.obtain(), init);
       }

       /**
        * Get the topmost set of off-end {@link QueryOffEndListener listeners}.
        *
        * @param   clear
        *          <code>true</code> if the top-most off-end listeners set
        *          should be cleared.
        */
       public static Set<QueryOffEndListener> getOffEndListeners(boolean clear)
       {
          return getOffEndListeners(workArea.obtain(), clear);
       }

       /**
        * Register the single output parameter assigner for the current block.
        * This object will be deregistered by the {@link BlockManager} when it is
        * time to process assign-backs from function/procedure output parameters
        * to the database record fields with which they are associated.
        *
        * @param   opa
        *          An output parameter assigner.
        *
        * @throws  IllegalStateException
        *          if a second output parameter assigner is registered for a block
        *          before the current one is deregistered.  There can be only one.
        */
       public static void registerOutputParameterAssigner(OutputParameterAssigner opa)
       {
          registerOutputParameterAssigner(workArea.obtain(), opa);
       }

       /**
        * Defer handling of an exception thrown by a commit/rollback notification
        * callback on a registered {@link Commitable}, so that iteration or pop
        * scope processing can be continued without interruption.  This ensures
        * that all <code>Commitable</code>s registered after the errant one still
        * receive their commit/rollback notifications, and that (in the case of
        * popping a scope), registered {@link Scopeable}s still receive their
        * scope finished notifications.
        * <p>
        * If no error is already pending, <code>exc</code> is stored, to be thrown
        * upon conclusion of processing the current block scope or iteration.  If
        * an error is already pending, <code>exc</code> is discarded.  In either
        * case, <code>exc</code> is logged if error-level logging is enabled.
        *
        * @param   action
        *          A brief description of the action occurring at the time the
        *          error was encountered, such as "commit" or "master rollback".
        * @param   thr
        *          The error caught during processing of a commit or rollback
        *          notification.
        *
        * @see     WorkArea#handleDeferredError
        */
       public static void deferError(String action, Throwable thr)
       {
          deferError(action, thr, false);
       }

       /**
        * Defer handling of an exception thrown by a commit/rollback notification
        * callback on a registered {@link Commitable}, so that iteration or pop
        * scope processing can be continued without interruption.  This ensures
        * that all <code>Commitable</code>s registered after the errant one still
        * receive their commit/rollback notifications, and that (in the case of
        * popping a scope), registered {@link Scopeable}s still receive their
        * scope finished notifications.
        * <p>
        * If no error is already pending, <code>exc</code> is stored, to be thrown
        * upon conclusion of processing the current block scope or iteration.  If
        * an error is already pending, <code>exc</code> is discarded.  In either
        * case, <code>exc</code> is logged if error-level logging is enabled.
        *
        * @param   action
        *          A brief description of the action occurring at the time the
        *          error was encountered, such as "commit" or "master rollback".
        * @param   thr
        *          The error caught during processing of a commit or rollback
        *          notification.
        * @param   silent
        *          If <code>true</code> then exception is not logged.
        *
        * @see     WorkArea#handleDeferredError
        */
       public static void deferError(String action, Throwable thr, boolean silent)
       {
          deferError(workArea.obtain(), action, thr, silent);
       }

       /**
        * Search the stack of scopes and return the level of the first top level
        * block found.
        *
        * @return   The stack level of the nearest enclosing top level block or
        *           -1 if there is no block marked as a top level block.
        */
       public static int findNearestTopLevel()
       {
          return findNearestTopLevel(workArea.obtain());
       }

       /**
        * Search the stack of scopes and return the type of first top level block found.
        *
        * @return   The nearest enclosing top level block or {@link BlockType#UNKNOWN} if
        *           there is no block marked as a top level block.
        */
       public static BlockType getNearestTopLevelType()
       {
          return getNearestTopLevelType(workArea.obtain());
       }

       /**
        * Worker for reporting if this context should be treated as headless. A headless session is
        * one that has no user interaction.
        *
        * <p>
        * To determine if the current context is headless the method uses the following algorithm:
        * <ul>
        *    <li>check the {@link SecurityManager#isServerAccount()}. Server sessions are implicit
        *          headless;
        *    <li>check the context token named "headless". If it exists, its value tells whether the
        *          context is for a headless session or not; otherwise the directory is checked;
        *    <li>check the directory. If there is a {@code boolean} value set to {@code true} for a
        *          given context's account in one of the following paths, then the context is
        *          headless. This allows the user interface client to be an optional part of any
        *          account. In other words, process accounts can be run in a "headless" mode with no
        *          requirement for any user interface. These are the paths (in order in which they
        *          are searched):
        *          <ol>
        *             <li>{@code /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/headless};
        *             <li>{@code /server/&lt;serverID&gt;/runtime/default/headless};
        *             <li>{@code /server/default/runtime/&lt;account_or_group&gt;/headless};
        *             <li>{@code /server/default/runtime/default/headless};
        *          </ol>
        *    <li>otherwise the context is not headless.
        * </ul>
        *
        * @return   <code>true</code> if this session is headless.
        */
       public static boolean _isHeadless()
       {
          SecurityManager sm = SecurityManager.getInstance();
          if (sm != null && sm.isServerAccount())
          {
             // in server context we are also in headless mode
             return true;
          }

          SessionManager sessMgr = SessionManager.get();

          if (sessMgr != null && sessMgr.isVirtualSession())
          {
             return true;
          }

          Boolean headless = (sm != null) ? (Boolean) sm.contextSm.getToken(ContextKey.HEADLESS_KEY) : null;

          // second: check directly in the context
          if (headless != null && headless)
          {
             return true;
          }
          // third: if headless not set in context, check in the directory
          else if (headless == null && Utils.getDirectoryNodeBoolean(null, "headless", false))
          {
             return true;
          }

          return false;
       }

       /**
        * Register the specified targets as undoables.  This will mark the object as undoable and save
        * the current tx level, regardless if a tx is active or not.
        *
        * @param    undoTargets
        *           List of object references to be registered.  This cannot be
        *           <code>null</code>.
        */
       public static void registerCurrent(LazyUndoable... undoTargets)
       {
          registerCurrent(workArea.obtain(), undoTargets);
       }

       /**
        * Worker that notifies all objects in the commit list that they must
        * commit (persist any state).
        * <p>
        * On the last iteration of a loop or block which ends in success (in
        * which the flow of control reaches the natural end of the block), both
        * will be called in succession.  However, other forms of exit which are
        * still considered successful (i.e. return and break which are not the
        * result of condition processing) will only execute <code>popScope</code>
        * and <code>iterate</code> will be bypassed.
        * <p>
        * It is important to note that the list of commitables in blk is
        * iterated in reverse order from that in which they are registered with
        * the <code>TransactionManager</code>.  This is done to ensure that
        * database transactions (which are registered before their corresponding
        * buffers) are always committed after all buffers are committed.
        *
        * @throws   ErrorConditionException
        *           If validation fails.
        */
       static void processValidate()
       throws ErrorConditionException
       {
          workArea.obtain().processValidate();
       }

       /**
        * Get the BlockDefinition instance of the nearest block of one of the
        * following types:
        * <ul>
        *    <li>{@link BlockType#EXTERNAL_PROC external procedure}</li>
        *    <li>{@link BlockType#INTERNAL_PROC internal procedure}</li>
        *    <li>{@link BlockType#FUNCTION user-defined function}</li>
        *    <li>{@link BlockType#DATABASETRIGGER trigger}</li>
        *    <li>{@link BlockType#TRIGGER trigger}</li>
        *    <li>{@link BlockType#EDITING editing}</li>
        * </ul>
        *
        * @return   See above.
        */
       static BlockDefinition nearestExecutingBlock()
       {
          return nearestExecutingBlock(workArea.obtain());
       }

       /**
        * Get the executing block at the specified level.
        *
        * @param    levels
        *           Zero-based depth of the target block, starting from the innermost block.
        *
        * @return   The found block, or <code>null</code> if the index is outside of the block depth.
        */
       static BlockDefinition getBlock(int levels)
       {
          return getBlock(null, levels);
       }

       /**
        * Get the top {@link BlockDefinition}.
        *
        * @return   See above.
        */
       static BlockDefinition getBlock()
       {
          return workArea.obtain().blocks.peek();
       }

       /**
        * Get the executing block at the specified level.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    levels
        *           Zero-based depth of the target block, starting from the innermost block.
        *
        * @return   The found block, or <code>null</code> if the index is outside of the block depth.
        */
       static BlockDefinition getBlock(WorkArea wa, int levels)
       {
          if (wa == null)
          {
             wa = workArea.obtain();
          }

          if (levels >= wa.blocks.size())
          {
             return null;
          }

          return wa.blocks.get(wa.blocks.size() - 1 - levels);
       }

       /**
        * Get the executing block at the specified level.
        *
        * @param    blockDepth
        *           Zero-based depth of the target block, starting from the outermost block; 0 is the
        *           global scope.
        *
        * @return   The found block, or <code>null</code> if the index is outside of the block depth.
        */
       static BlockDefinition getBlockAtDepth(int blockDepth)
       {
          return getBlockAtDepth(null, blockDepth);
       }

       /**
        * Get the executing block at the specified level.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    blockDepth
        *           Zero-based depth of the target block, starting from the outermost block; 0 is the
        *           global scope.
        *
        * @return   The found block, or <code>null</code> if the index is outside of the block depth.
        */
       static BlockDefinition getBlockAtDepth(WorkArea wa, int blockDepth)
       {
          if (wa == null)
          {
             wa = workArea.obtain();
          }

          if (blockDepth == 0)
          {
             return wa.globalBlock;
          }

          if (blockDepth >= wa.blocks.size())
          {
             return null;
          }

          return wa.blocks.get(blockDepth);
       }

       /*
        * Deregister the output parameter assigner for the current block, if any.
        * It is expected that only the {@link BlockManager} will invoke this
        * method.
        *
        * @return  Output parameter assigner for this block, or <code>null</code>
        *          if none was registered.
        */
       public static OutputParameterAssigner deregisterOutputParameterAssigner()
       {
          return deregisterOutputParameterAssigner(workArea.obtain());
       }

       /**
        * Adds the given object into the list of objects which require undo
        * notifications for the next block that is opened.
        * <p>
        * This form of registration is useful for objects whose natural lifetime
        * is scoped to the top-level block.  In particular, it is necessary for
        * objects that are constructed during a class' constructor/initializer
        * (before the external procedure block has opened).  Such objects cannot
        * simply register since their construction is not actually enclosed by the
        * block.
        * <p>
        * When the deferred registration occurs, it will NOT be global and will
        * NOT be added to registered Commitables.
        * <p>
        * This notification will occur whether or not a transaction is active.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed.
        * <p>
        * The given object will only be added if it is not already in the list.
        *
        * @param    target
        *           The object references to be registered.  This cannot be
        *           <code>null</code>.
        */
       static void registerNext(LazyUndoable... target)
       {
          registerAt(ScopeLevel.NEXT, target);
       }

       /**
        * Adds the given object into the list of objects which require undo
        * notifications for the next block that is opened.
        * <p>
        * This form of registration is useful for objects whose natural lifetime
        * is scoped to the top-level block.  In particular, it is necessary for
        * objects that are constructed during a class' constructor/initializer
        * (before the external procedure block has opened).  Such objects cannot
        * simply register since their construction is not actually enclosed by the
        * block.
        * <p>
        * When the deferred registration occurs, it will NOT be global and will
        * NOT be added to registered Commitables.
        * <p>
        * This notification will occur whether or not a transaction is active.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed.
        * <p>
        * The given object will only be added if it is not already in the list.
        *
        * @param    scope
        *           The scope where to register the undoable targets.
        * @param    target
        *           The object references to be registered.  This cannot be
        *           <code>null</code>.
        */
       static void registerAt(ScopeLevel scope, LazyUndoable... target)
       {
          registerAt(workArea.obtain(), scope, target);
       }

       /**
        * Adds the object into a scoped dictionary of objects requiring UNDO support. This registry
        * is used to backup the objects at the beginning of transactions or sub-transactions.
        * <p>
        * These registrations naturally are removed when the associated scope is removed. However,
        * the global scope is long-lived. Special care is necessary to prevent the same instance of
        * global shared variables being added multiple times.
        *
        * @param    transLevel
        *           The transaction level to set for this undoable.
        * @param    persistent
        *           Flag indicating if the undoable is defined in a persistent procedure
        * @param    undoTargets
        *           The object references to be rolled back on failure.  This
        *           cannot be <code>null</code>.
        */
       static void registerAt(int transLevel, boolean persistent, LazyUndoable... undoTargets)
       {
          registerAt(workArea.obtain(), transLevel, persistent, undoTargets);
       }

       /**
        * Deregister the specified undoables from all tx blocks.
        *
        * @param    wa
        *           The {@link WorkArea} instance; if <code>null</code>, it will be resolved.
        * @param    undoables
        *           The undoables to be deregistered.
        */
       static void deregister(WorkArea wa, IdentityHashMap<LazyUndoable, Object> undoables)
       {
          if (wa == null)
          {
             wa = workArea.obtain();
          }

          if (wa.transLevel == -1)
          {
             return;
          }

          Stack<BlockDefinition> blocks = wa.blocks;
          for (int i = 0; i < blocks.size(); i++)
          {
             BlockDefinition block = blocks.get(i);
             if (block.undoables != null)
             {
                Iterator<UndoablePair> iter = block.undoables.iterator();
                while (iter.hasNext())
                {
                   UndoablePair pair = iter.next();

                   if (undoables.containsKey(pair.src))
                   {
                      iter.remove();
                   }
                }
             }

             if (block.full)
             {
                break;
             }
          }
       }

       /**
        * Get the current transaction depth.
        *
        * @return   The {@link WorkArea#txNestingLevel} field.
        */
       static int getTransDepth()
       {
          return workArea.obtain().txNestingLevel;
       }

       /**
        * Enable tracking of all the undaobles defined in the next instantiating external program.
        */
       static void trackUndoables()
       {
          WorkArea wa = workArea.obtain();

          wa.trackUndoables = true;
          wa.trackedUndoables = new IdentityHashMap<>();
       }

       /**
        * Untrack (by removing from all the tx blocks) all the undoables defined and registered for
        * the specified external program.
        *
        * @param    referent
        *           The external program instance.
        */
       static void untrackUndoables(Object referent)
       {
          untrackUndoables(workArea.obtain(), referent);
       }

       /**
        * Rollback the transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
        *
        * @param    chained
        *           Flag indicating if a new transaction will be started.
        */
       static void rollbackTx(boolean chained)
       {
          rollbackTx(workArea.obtain(), chained);
       }

       /**
        * Commit the transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
        *
        * @param    chained
        *           Flag indicating if a new transaction will be started.
        */
       static void commitTx(boolean chained)
       {
          commitTx(workArea.obtain(), chained);
       }

       /**
        * Begin an explicit transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
        * <p>
        * This explicitly marks this tx as being open by the
        * {@link TransactionResource#getTxInitProcedure() TRANS-INIT-PROCEDURE}.
        */
       static void beginTx()
       {
          beginTx(null, null);
       }

       /**
        * End an explicit transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
        */
       static void endTx()
       {
          endTx(null, null);
       }

       /**
        * Logic required when the tx initiated by TRANSACTION-MODE AUTO gets committed or rolled back.
        */
       static void notifyMasterFinish()
       {
          WorkArea wa = workArea.get();

          wa.notifyMasterFinish(false);
          wa.autoRemoveMasterCommit();
          wa.autoRemoveMasterFinish(true);
       }

       /**
        * Remove the specified target from the list of finalizables.
        *
        * @param    finishers
        *           The list of finalizables.
        * @param    target
        *           The target to remove.
        *
        * @return   <code>true</code> if the target was removed.
        */
       private static boolean removeFinalizable(Set<Finalizable>[] finishers, Finalizable target)
       {
          if (finishers == null)
          {
             return false;
          }

          Set<Finalizable> l = finishers[target.weight().getIndex()];

          return l != null && l.remove(target);
       }

       /**
        * Add the given target to the list of finalizables.
        *
        * @param    block
        *           The target block.
        * @param    target
        *           The target to add.
        */
       private static void addFinalizable(BlockDefinition block, Finalizable target)
       {
          int idx = target.weight().getIndex();
          Set<Finalizable> finishers = block.finalizables[idx];
          if (finishers == null)
          {
             finishers = Collections.newSetFromMap(new IdentityHashMap<>(8));
             block.finalizables[idx] = finishers;
          }

          // add the object to the list of finalizable targets if it isn't
          // already in the list (avoids being called twice for the same event)
          if (finishers.add(target) && target instanceof ConditionListener)
          {
             if (block.conditionListeners == null)
             {
                block.conditionListeners = new ArrayList<>(2);
             }

             block.conditionListeners.add((ConditionListener) target);
          }
       }

       /**
        * Register StopAfterTimer to a ContextLocal WorkArea.
        *
        * @param    wa
        *           WorkArea
        * @param    stopAfterTimer
        *           StopAfterTimer to register.
        */
       private static void registerStopAfterTimer(WorkArea wa, StopAfterTimer stopAfterTimer)
       {
          wa.stopAfterTimer = stopAfterTimer;
       }

       /**
        * Adds the given object into the list of objects which require commit notification for the
        * block at the specified depth  This form of registration is useful for objects whose natural
        * lifetime are scoped to a different block than the block in which they must be committed.
        * For example, if a global or shared resource needs to be committed in a specific scope,
        * then this can be used in cooperation with the normal {@link #registerCurrent} processing to
        * control the commit points.
        * <p>
        * This notification will ONLY occur when there is an active transaction.
        * <p>
        * Notifications occur in LIFO order;  thus, commitables which depend upon
        * others being committed first must be registered before the objects upon
        * which they are dependent.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    blockDepth
        *           Zero-based depth of the target block, starting from the outermost block.
        * @param    target
        *           The object reference to be committed on success.  This cannot be
        *           <code>null</code>.
        */
       private static void registerCommitAt(WorkArea wa, int blockDepth, Commitable target)
       {
          BlockDefinition blk = wa.blocks.get(blockDepth);

          // prevent ConcurrentModificationException
          if (blk.commitables != null && wa.nowIterating == blk.commitables)
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                log(Level.WARNING,
                    "registerCommit",
                    "Illegal attempt to register Commitable",
                    new Throwable());
             }

             return;
          }

          if (blk.commitables == null)
          {
             blk.commitables = new ArrayList<>();
          }

          // add the object to the list of commit targets
          blk.commitables.add(target);
       }

       /**
        * Registers the instance for scope notifications at the current block.
        * <p>
        * This will call {@link Scopeable#scopeStart} to notify for the scope processing.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    target
        *           The scopeable instance.
        */
       private static void registerBlockScopeable(WorkArea wa, Scopeable target)
       {
          BlockDefinition block = wa.blocks.peek();
          registerScopeable(wa, block, target, true);
       }

       /**
        * Register the target scopeable as pending, to be added to the next block.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    target
        *           The scopeable instance.
        */
       private static void registerPendingScopeable(WorkArea wa, Scopeable target)
       {
          if (wa.nowIterating != null && wa.nowIterating == wa.blocks.peek().scopeList)
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                log(Level.WARNING,
                    "registerPendingScopeable",
                    "Illegal attempt to register Scopeable",
                    new Throwable());
             }

             return;
          }

          if (wa.pendingScopeables == null)
          {
             wa.pendingScopeables = new Scopeable[ScopeId.values().length];
          }
          wa.pendingScopeables[target.getScopeId().ordinal()] = target;
       }

       /**
        * Registers the instance for scope notifications at the current block.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    block
        *           The target block.
        * @param    target
        *           The scopeable instance.
        * @param    start
        *           Flag indicating if {@link Scopeable#scopeStart} should be executed.
        */
       private static void registerScopeable(WorkArea wa, BlockDefinition block, Scopeable target, boolean start)
       {
          int id = target.getScopeId().ordinal();

          if (wa.nowIterating != null && wa.nowIterating == block.scopeList && block.scopeList[id] == null)
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                log(Level.WARNING,
                    "registerScopeable",
                    "Illegal attempt to register Scopeable",
                    new Throwable());
             }

             return;
          }

          if (block.scopeList == null)
          {
             block.scopeList = new Scopeable[ScopeId.values().length];
          }

          if (block.scopeList[id] == null)
          {
             block.scopeList[id] = target;
             if (start)
             {
                // process the scope start
                target.scopeStart(block);
             }
          }
       }

       /**
        * Remove the given finalizable from the global list of finalizables.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    target
        *           Finalizable to be removed from the global list.
        */
       private static void deregisterGlobalFinalizable(WorkArea wa, Finalizable target)
       {
          // prevent ConcurrentModificationException
          if (wa.nowIterating == wa.globalBlock.finalizables)
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                log(Level.WARNING,
                    "deregisterGlobalFinalizable",
                    "Illegal attempt to deregister Finalizable",
                    new Throwable());
             }

             return;
          }

          removeFinalizable(wa.globalBlock.finalizables, target);
       }

       /**
        * Adds the given object into the list of objects for the specified block
        * which require iterate/retry/finish notification.  This form of
        * registration is useful for objects whose natural lifetime is scoped to
        * a block and which need a hook for cleaning up resources or handling
        * other block termination processing.
        * <p>
        * This notification will occur whether or not a transaction is active.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed (after the {@link Finalizable#finished} method is called)
        * in the case of a non-global scope OR at the moment that the last
        * scope has been removed in the case of the global scope.
        * <p>
        * The given object will only be added if it is not already in the list.
        * <p>
        * Finalizables for a scope are processed in the same order as they were
        * registered.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    blockDepth
        *           zero-based depth of target block, starting from the outermost
        *           block. <code>0</code> indicates the global block.
        * @param    target
        *           The object reference to be notified on any exit from the
        *           block specified.  This cannot be <code>null</code>.
        */
       private static void registerFinalizableAt(WorkArea wa, int blockDepth, Finalizable target)
       {
          BlockDefinition targetBlock;

          if (blockDepth == 0)
          {
             // 0 indicates globalBlock rather than block with the index 0 (startup block).
             targetBlock = wa.globalBlock;
          }
          else
          {
             BlockDefinition blk = wa.blocks.get(blockDepth);
             targetBlock = blk;
          }

          // prevent ConcurrentModificationException
          if (wa.nowIterating == targetBlock.finalizables)
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                log(Level.WARNING,
                    "registerFinalizable",
                    "Illegal attempt to register Finalizable",
                    new Throwable());
             }

             return;
          }

          addFinalizable(targetBlock, target);
       }

       /**
        * Adds the given object into the list of objects which require
        * iterate/retry/finish notifications for the next block that is opened
        * which is a top-level block that corresponds to a Progress external
        * procedure.
        * <p>
        * This form of registration is useful for objects whose natural lifetime
        * is scoped to the top-level block and which need a hook for cleaning up
        * resources or handling other block termination processing.  In
        * particular, it is necessary for objects that are constructed during
        * a class' constructor/initializer (before the external procedure block
        * has opened).  Such objects cannot simply register since their
        * construction is not actually enclosed by the block.
        * <p>
        * This notification will occur whether or not a transaction is active.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed (after the {@link Finalizable#finished} method is called).
        * <p>
        * The given object will only be added if it is not already in the list.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    target
        *           The object reference to be notified on any exit from the
        *           top-level external block.  This cannot be <code>null</code>.
        *
        * @throws   IllegalStateException
        *           if the top-level block cannot be identified.
        */
       private static void registerNextExternal(WorkArea wa, Finalizable target)
       {
          if (wa.deferredExtFinalizables == null)
          {
             wa.deferredExtFinalizables = new ArrayList<>();
          }

          // prevent ConcurrentModificationException
          if (wa.nowIterating == wa.deferredExtFinalizables)
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                log(Level.WARNING,
                    "registerNextExternal",
                    "Illegal attempt to register Finalizable",
                    new Throwable());
             }

             return;
          }

          // add the object to the list of finalizable targets if it isn't
          // already in the list (avoids being called twice for the same event)
          if (!wa.deferredExtFinalizables.contains(target))
          {
             wa.deferredExtFinalizables.add(target);
          }
       }

       /**
        * Allow finalizables to do their cleanup ({@link Finalizable#initFailure()}) now since the
        * scoped they were designed was aborted and there won't be other chance to call their
        * {@link Finalizable#finished()}.
        * <p>
        * After iteration the list is deleted.
        * <p>
        * Also, cleanup all other pending state being collected while the external program was
        * instantiating.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        */
       private static void cleanupPending(WorkArea wa)
       {
          if (wa.deferredExtFinalizables != null)
          {
             wa.deferredExtFinalizables.forEach(Finalizable::initFailure);
             wa.deferredExtFinalizables = null;
          }

          wa.deferredUndoables = null;
          wa.trackedUndoables = null;
          wa.trackUndoables = false;
          wa.deferredDynExt = null;
          wa.pendingScopeables = null;
       }

       /**
        * Adds the given object into the list of objects which require
        * iterate/retry/finish notifications for the nearest enclosing
        * top-level block.  A top-level block is generated for Progress external
        * procedures, internal procedures, triggers and functions.  An additional
        * parameter allows registration to be specified for the specific block
        * which corresponds with an external procedure block scope in Progress.
        * <p>
        * This form of registration is useful for objects whose natural lifetime
        * is scoped to the top-level block and which need a hook for cleaning up
        * resources or handling other block termination processing.
        * <p>
        * This notification will occur whether or not a transaction is active.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed (after the {@link Finalizable#finished} method is called).
        * <p>
        * The given object will only be added if it is not already in the list.
        * <p>
        * Finalizables for a scope are processed in the same order as they were
        * registered.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    target
        *           The object reference to be notified on any exit from the
        *           top-level block.  This cannot be <code>null</code>.
        * @param    external
        *           If <code>true</code>, register with the nearest enclosing
        *           external block (external procedure), otherwise register with
        *           the nearest enclosing top-level block (which may be but is
        *           not guaranteed to be the external block).
        *
        * @throws   IllegalStateException
        *           if the top-level block cannot be identified.
        */
       private static void registerTopLevelFinalizable(WorkArea    wa,
                                                       Finalizable target,
                                                       boolean     external)
       {
          BlockDefinition               targetBlock = null;
          Stack<BlockDefinition>        blocks      = wa.blocks;
          ListIterator<BlockDefinition> iter        = blocks.listIterator(blocks.size());

          // walk backwards up the stack of block definitions
          while (iter.hasPrevious())
          {
             BlockDefinition blk = iter.previous();

             // if we are looking for an external block and found one OR
             // if we are NOT looking for an external block and found any top
             // level block
             if ((external && blk.external) || (!external && blk.topLevel))
             {
                // done!
                targetBlock = blk;
                break;
             }
          }

          if (targetBlock == null)
          {
             String errmsg = (external ? "external" : "top-level");
             throw new IllegalStateException("Unable to find " + errmsg + " block");
          }

          // prevent ConcurrentModificationException
          if (wa.nowIterating == targetBlock.finalizables)
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                log(Level.WARNING,
                    "registerTopLevelFinalizable",
                    "Illegal attempt to register Finalizable",
                    new Throwable());
             }

             return;
          }

          addFinalizable(targetBlock, target);
       }

       /**
        * Adds the given object into the list of objects which require
        * iterate/retry/finish notification for the nearest enclosing, looping
        * block.
        * <p>
        * This form of registration is useful for objects whose natural lifetime
        * is scoped to the nearest enclosing, looping block and which need a hook
        * for cleaning up resources or handling other block termination
        * processing.
        * <p>
        * This notification will occur whether or not a transaction is active.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed (after the {@link Finalizable#finished} method is called).
        * <p>
        * The given object will only be added if it is not already in the list.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    target
        *           The object reference to be notified on any exit from or
        *           iteration of the target block.  This cannot be <code>null</code>.
        *
        * @throws   IllegalStateException
        *           if no enclosing, looping block can be identified.
        */
       private static void registerNearestLoopFinalizable(WorkArea wa, Finalizable target)
       {
          // walk backwards up the stack of block definitions
          Stack<BlockDefinition> blocks = wa.blocks;
          ListIterator<BlockDefinition> iter = blocks.listIterator(blocks.size());
          while (iter.hasPrevious())
          {
             BlockDefinition blk = iter.previous();
             if (blk.loop)
             {
                // prevent ConcurrentModificationException
                if (wa.nowIterating == blk.finalizables)
                {
                   if (LOG.isLoggable(Level.WARNING))
                   {
                      log(Level.WARNING,
                          "registerNearestLoopFinalizable",
                          "Illegal attempt to register Finalizable",
                          new Throwable());
                   }

                   return;
                }

                addFinalizable(blk, target);
                return;
             }
          }

          throw new IllegalStateException("Unable to find nearest enclosing, looping block");
       }

       /**
        * Removes the given object from the list of objects which require
        * iterate/retry/finish notifications.  This will find the first instance
        * of the given reference by walking up each block from the current to the
        * global block. The first instance found will be removed and the method
        * will then return. If the same instance is registered in multiple scopes,
        * it would take multiple calls to remove all instances.
        * <p>
        * After this call, no notifications will be received.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    target
        *           The object reference to be removed. Must not be {@code null}.
        *
        * @return   <code>true</code> if the target instance was found and
        *           removed.
        */
       private static boolean removeFinalizable(WorkArea wa, Finalizable target)
       {
          Stack<BlockDefinition>        blocks = wa.blocks;
          ListIterator<BlockDefinition> iter   = blocks.listIterator(blocks.size());
          boolean                       found  = false;

          // walk the stack of block definitions from the current back "up"
          while (iter.hasPrevious() && !found)
          {
             BlockDefinition blk = iter.previous();
             if (wa.nowIterating != blk.finalizables)
             {
                found = removeFinalizable(blk.finalizables, target);
             }
             else
             {
                // prevent ConcurrentModificationException
                if (LOG.isLoggable(Level.WARNING))
                {
                   log(Level.WARNING,
                       "removeFinalizable",
                       "Illegal attempt to deregister Finalizable",
                       new Throwable());
                }

                return false;
             }
          }

          // if still not found, try the global block
          if (!found)
          {
             if (wa.nowIterating != wa.globalBlock.finalizables)
             {
                found = removeFinalizable(wa.globalBlock.finalizables, target);
             }
             else
             {
                // prevent ConcurrentModificationException
                if (LOG.isLoggable(Level.WARNING))
                {
                   log(Level.WARNING,
                       "removeFinalizable",
                       "Illegal attempt to deregister global Finalizable",
                       new Throwable());
                }

                return false;
             }
          }

          return found;
       }

       /**
        * Reports whether the current block was just rolled back or a containing
        * block has a pending rollback. If there is no current transaction open,
        * then there cannot be a pending or current rollback.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        *
        * @return   <code>true</code> if there was or will be a rollback.
        */
       private static boolean hadRollback(WorkArea wa)
       {
          // if there is no transaction, there can't be a pending rollback or a
          // current rollback
          if (wa.transLevel != -1)
          {
             // check if a containing block has a pending rollback
             if (wa.rollbackPending)
             {
                return true;
             }

             BlockDefinition blk = wa.blocks.peek();

             // check if the current block was rolled back
             if (blk.rolledBack)
             {
                return true;
             }
          }

          return false;
       }

       /**
        * Trigger a rollback for all registered <code>Undoable</code> objects
        * inside the current active transaction at the scope defined
        * explicitly by the given label or implicitly if the label is
        * <code>null</code>.  All objects that are rolled back will have their
        * values assigned based on the values backed up at the start of the
        * specified scope.  If the current scope is not the target scope, the
        * given exception will be stored, the rollback will be deferred and
        * the rollback will be processed as the scopes naturally unwind.  This
        * unwinding must be generated by a change in flow of control by the
        * calling code.  This flow control change MUST always immediately follow
        * this method otherwise undefined results can occur.
        * <p>
        * This caller-driven control flow is required to properly duplicate the
        * semantics of Progress 4GL condition processing which always generates
        * an implicit or explicit flow control action after every UNDO. Since the
        * flow of control change can reference a different scope than that of
        * the rollback, this can't be handled by re-throwing the exception but
        * must instead be separately driven by a deferred rollback and a caller
        * driven flow control statement such as break, continue or return. The
        * key limitation that makes this possible is the fact that the UNDO
        * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
        * the scope to which flow of control will be changed.  This means that
        * one can rollback at a more deeply nested scope but change flow of
        * control to resume processing at a different (less deeply nested) scope.
        * The reverse is not possible.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    label
        *           Specifies the name of the scope at which to rollback or
        *           <code>null</code> if the scope should be implicitly
        *           determined.
        * @param    cause
        *           The exceptional cause, if any, of the rollback.
        */
       private static void rollback(WorkArea wa, String label, Throwable cause)
       {
          if (LOG.isLoggable(Level.FINE))
          {
             log(Level.FINE, "rollback", "rollback_label = " + label);
          }

          // find the target block and call our worker
          int level = findStackLevel(wa, label);
          rollbackWorker(wa, level, cause);
       }

       /**
        * Trigger a rollback for all registered <code>Undoable</code> objects
        * inside the current active transaction at the nearest top level scope.
        * All objects that are rolled back will have their values assigned based
        * on the values backed up at the start of the specified scope.  If the
        * current scope is not the target scope, the given exception will be
        * stored, the rollback will be deferred and the rollback will be processed
        * as the scopes naturally unwind.  This unwinding must be generated by a
        * change in flow of control by the calling code.  This flow control change
        * MUST always immediately follow this method otherwise undefined results
        * can occur.
        * <p>
        * This caller-driven control flow is required to properly duplicate the
        * semantics of Progress 4GL condition processing which always generates
        * an implicit or explicit flow control action after every UNDO. Since the
        * flow of control change can reference a different scope than that of
        * the rollback, this can't be handled by re-throwing the exception but
        * must instead be separately driven by a deferred rollback and a caller
        * driven flow control statement such as break, continue or return. The
        * key limitation that makes this possible is the fact that the UNDO
        * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
        * the scope to which flow of control will be changed.  This means that
        * one can rollback at a more deeply nested scope but change flow of
        * control to resume processing at a different (less deeply nested) scope.
        * The reverse is not possible.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        */
       private static void rollbackTopLevel(WorkArea wa)
       {
          if (LOG.isLoggable(Level.FINE))
          {
             log(Level.FINE, "rollback nearest top level block", null);
          }

          // find the target block and call the worker
          int level = findNearestTopLevel(wa);
          rollbackWorker(wa, level, null);
       }

       /**
        * Trigger a rollback for all registered <code>Undoable</code> objects
        * inside the current active transaction at the scope defined by the
        * specified stack level.  All objects that are rolled back will have their
        * values assigned based on the values backed up at the start of the
        * specified scope.  If the current scope is not the target scope, the
        * given exception will be stored, the rollback will be deferred and
        * the rollback will be processed as the scopes naturally unwind.  This
        * unwinding must be generated by a change in flow of control by the
        * calling code.  This flow control change MUST always immediately follow
        * this method otherwise undefined results can occur.
        * <p>
        * This caller-driven control flow is required to properly duplicate the
        * semantics of Progress 4GL condition processing which always generates
        * an implicit or explicit flow control action after every UNDO. Since the
        * flow of control change can reference a different scope than that of
        * the rollback, this can't be handled by re-throwing the exception but
        * must instead be separately driven by a deferred rollback and a caller
        * driven flow control statement such as break, continue or return. The
        * key limitation that makes this possible is the fact that the UNDO
        * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
        * the scope to which flow of control will be changed.  This means that
        * one can rollback at a more deeply nested scope but change flow of
        * control to resume processing at a different (less deeply nested) scope.
        * The reverse is not possible.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    level
        *           Specifies the stack level of the scope at which to rollback or
        *           -1 if the current scope should be the target.
        * @param    cause
        *           The exceptional cause, if any, of the rollback.
        */
       private static void rollbackWorker(WorkArea wa, int level, Throwable cause)
       {
          BlockDefinition blk = null;

          if (level == -1)
          {
             blk = wa.blocks.peek();
          }
          else
          {
             blk = wa.blocks.elementAt(level);
          }

          // mark that block as being the UNDO target
          blk.isUndoLevel = true;

          // rollback an only occur inside an active transaction
          if (wa.isTransaction())
          {
             // is there a pending rollback?
             if (wa.isRollbackPending())
             {
                // this should not happen!

                String errmsg = "There is already a pending rollback.";

                if (wa.rollbackPendingCause != null)
                {
                   LOG.log(Level.SEVERE, errmsg + " Pending cause:", wa.rollbackPendingCause);
                }
                else
                {
                   LOG.severe(errmsg + " Pending cause unavailable.");
                }

                if (cause != null)
                {
                   LOG.log(Level.SEVERE, errmsg + " Current cause:", cause);
                }
                else
                {
                   LOG.severe(errmsg + " Current cause unavailable.");
                }

                throw new IllegalStateException(errmsg);
             }
             else
             {
                // determine if an explicit label refers to this scope OR if
                // a lack of a label implicitly refers to this scope
                if (rollbackCurrentScope(wa, level))
                {
                   // peek at the current block def on the top of the stack
                   if (level != -1)
                   {
                      blk = wa.blocks.peek();
                   }

                   // remember that we rolled back so on exit there is no commit
                   blk.rolledBack = true;
                   blk.undoThrow = true;

                   // we will immediately roll back, which allows other subsequent
                   // processing like retry to work properly
                   processRollback(wa, blk);
                }
                else
                {
                   // the undo is destined for an enclosing scope, defer it; this
                   // must be done even if the enclosing target scope is outside
                   // of the transaction since otherwise, when popScope() exits
                   // the transaction it will not know that there is a pending
                   // rollback and the full transaction will be committed... that
                   // would be a bad thing
                   wa.rollbackScope   = level;
                   wa.rollbackPending = true;
                }

                processResettables(wa, true);
             }
          }
          else
          {
             // no transaction but we still need to remember that this occurred
             // so that any following retry will know the target block for an
             // implicit retry (a null label in triggerRetry/forceRetry) - note
             // that we may be setting the target scope to null right here if the
             // label parm to this method is null, that is OK
             wa.retryScope = level;

             processResettables(wa, false);
          }
       }

       /**
        * Initializes a new scope, the associated block definition data
        * structures.  A backup of objects that can be rolled back is deferred
        * until after the block opens (see {@link #blockSetup} and
        * {@link #makeBackup}).
        * <p>
        * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
        * the core data structures are initialized but no rollback support will
        * be provided.
        * <p>
        * This method will honor any pending thread interruption by throwing
        * a {@link StopConditionException} using {@link #honorStopCondition}
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    label
        *           The text label associated with the block.  This usually
        *           should correspond with the label used in the source file
        *           to identify the block for purposes of the break and continue
        *           statements.
        * @param    level
        *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
        *           {@link #TRANSACTION}.  This code defines the level of
        *           rollback and transaction support which should be provided.
        * @param    props
        *           A bitfield storing block properties which will be a
        *           combination of {@link #PROP_NONE}, {@link #PROP_ERROR},
        *           {@link #PROP_ENDKEY}, {@link #PROP_STOP} or
        *           {@link #PROP_QUIT}.
        * @param    external
        *           <code>true</code> if this block is associated with the
        *           external procedure (in Progress terms). This controls the
        *           cleanup of certain resources such as temp-tables, frames
        *           and streams.
        * @param    topLevel
        *           <code>true</code> if this block is a method.
        * @param    loop
        *           Defines if this block is a loop or not.
        * @param    next
        *           <code>true</code> if this infinite loop protection should
        *           convert a retry into a next (continue), otherwise the retry
        *           will be converted into a leave (break).
        * @param    databaseTrigger
        *           <code>true</code> if this scope should define a
        *           database trigger block
        * @param    blockType
        *           Specifies this block's type.
        *
        * @throws   StopConditionException
        *           If the current thread's was previously interrupted.
        */
       private static void pushScope(WorkArea  wa,
                                     String    label,
                                     int       level,
                                     int       props,
                                     boolean   external,
                                     boolean   topLevel,
                                     boolean   loop,
                                     boolean   next,
                                     boolean   databaseTrigger,
                                     BlockType blockType)
       {
          if (LOG.isLoggable(Level.FINE))
          {
             String spec = "new block: label = %s; type = %s; level = %s; " +
                           "external = %b; top_level = %b; loop = %b; "     +
                           "next_or_leave = %s; properties = '%s';";

             Object[] parms = new Object[]
             {
                label,
                blockType.toString(),
                decodeLevel(level),
                external,
                topLevel,
                loop,
                (next ? "next" : "leave"),
                decodeProperties(props)
             };

             log(Level.FINE, "pushScope", String.format(spec, parms));
          }

          try
          {
             // check the interrupted status and throw the stop condition if needed
             wa.honorStopCondition();
          }
          catch (RuntimeException exe)
          {
             ControlFlowOps.cleanupPending();
             throw exe;
          }

          // remember if this scope starts a new transaction rather than a
          // sub-transaction or no transaction
          boolean full = false;

          // start a new transaction if needed and one is not already active
          if (isTransactionLevel(level) && !wa.isTransaction())
          {
             full = true;
          }

          // determine if we have to mark this block for FOR block aggressive buffer flushing: if the
          // new block is any type of FOR block, turn on the forFlushing property

          boolean forFlushing = false;

          if (!external)
          {
             // any FOR block enables forFlushing state
             switch (blockType)
             {
                case FOR_LOOP:
                case FOR_LOOP_WHILE:
                case FOR_LOOP_TO:
                case FOR_LOOP_TO_WHILE:
                case FOR_BLOCK:
                case FOR_BLOCK_WHILE:
                case FOR_BLOCK_TO:
                case FOR_BLOCK_TO_WHILE:
                   forFlushing = true;
                default:
                   break;
             }
          }

          // create the block definition
          BlockDefinition blk = new BlockDefinition(label,
                                                    level,
                                                    props,
                                                    full,
                                                    external,
                                                    topLevel,
                                                    loop,
                                                    next,
                                                    forFlushing,
                                                    databaseTrigger,
                                                    blockType);

          if ((props & PROP_ENDKEY) != 0 && wa.lt != null && wa.isInteractive()) // wa.lt is NULL for headless mode
          {
             // LT must not be added for agents or batch mode
             // this is registered directly at the block, to avoid using the 'pending'

             // WARNING: LT has dependencies on top-level blocks (including external); currently, such blocks have
             // by default the ENDKEY property - if this registration changes, attention is needed to register
             // LT scopeable for these blocks.
             registerScopeable(wa, blk, wa.lt, false);
          }

          // add our block
          wa.blocks.push(blk);

          // add a new query off-end listeners set to the stack
          wa.offEndListeners.push(Collections.emptySet());

          // mark our global state as having an active transaction if needed
          if (full)
          {
             // this must be after we have added the latest block to our stack
             // in order to properly have a 0-based index
             wa.setTransactionLevel(wa.blocks.size() - 1);
          }

          if (wa.processUndoables())
          {
             wa.txNestingLevel++;
             blk.txNestingLevel = wa.txNestingLevel;
          }

          // the error manager needs a special notification (it can't be a
          // scopeable because it is used on both the client and the server)
          if (topLevel)
          {
             // remember if silent mode was enabled or not
             blk.suppressError = wa.em.isSilent();

             // disable silent mode if it was enabled because silent mode on a
             // RUN statement or for a function call is something that only
             // suppresses errors during the dispatching itself and NOT during the
             // execution of the called code
             if (blk.suppressError)
             {
                wa.em.setSilent(false);
             }
          }

          if (wa.bm != null)
          {
             if (wa.bm.isImportantBlockTransition())
             {
                BufferManager.registerTxScopeable(wa.bm);
             }

             if (isGlobalBlock(wa) || blk.external)
             {
                // the global block must be added, for the scoped dictionary's global block.
                // all external blocks must register also
                BufferManager.registerPendingScopeable(wa.bm);
             }
          }

          if (blk.external)
          {
             ArrayAssigner.registerPendingScopeable();
          }

          // notify Scopeables that scope has opened
          processScopeNotifications(wa, blk, true);

          // process deferred external procedure block registration requests
          if (external && wa.deferredExtFinalizables != null)
          {
             try
             {
                wa.nowIterating = wa.deferredExtFinalizables;
                ArrayList<Finalizable> deferredExtFinalizables = wa.deferredExtFinalizables;
                for (int i = 0; i < deferredExtFinalizables.size(); i++)
                {
                   Finalizable target = deferredExtFinalizables.get(i);
                   registerTopLevelFinalizable(target, true);
                }

                // clear the list
                wa.deferredExtFinalizables = null;
             }
             finally
             {
                wa.nowIterating = null;
             }
          }

          // process deferred block registration requests
          if (wa.deferredUndoables != null)
          {
             try
             {
                boolean persistent = (blk.external && wa.trackUndoables);
                wa.nowIterating = wa.deferredUndoables;
                for (LazyUndoable target : wa.deferredUndoables.keySet())
                {
                   registerUndo(target, wa.txNestingLevel, persistent);
                }

                // clear the list
                wa.deferredUndoables = null;
             }
             finally
             {
                wa.nowIterating = null;
             }
          }

          if (wa.deferredDynExt != null)
          {
             try
             {
                // uninitialized dynamic extent vars
                ArrayAssigner.registerDynamicUndoableArray(wa.deferredDynExt, wa.txNestingLevel);
             }
             finally
             {
                wa.deferredDynExt = null;
             }
          }

          if (blk.external && wa.trackUndoables)
          {
             // if this is the scope for an external program and undoables are being tracked
             // (i.e. the program is ran persistent), then save all the defined undoables, so that
             // they can be removed from all tx blocks, when the persistent proc gets deleted.
             Object referent = wa.pm._thisProcedure();
             if (wa.trackedUndoables != null && wa.trackedUndoables.isEmpty())
             {
                wa.procUndoables.put(referent, wa.trackedUndoables);
             }

             wa.trackUndoables = false;
             wa.trackedUndoables = null;
          }
       }

       /**
        * Remove the current block from the stack of scopes, process any pending
        * rollbacks (if the current block is the proper target), notify any
        * objects needing commits (if the block finished successfully) and handle
        * finish processing for the block.  This method must be called from a
        * <code>finally</code> block such that this is guaranteed to be called
        * no matter how the flow of control exits the associated block (exception,
        * break, return or the flow of control naturally passing through the
        * end of the block).
        * <p>
        * Rollback and commit processing only occur when there is an active
        * transaction.  Otherwise this processing is bypassed.  Finish processing
        * is always called.
        * <p>
        * This method implements global finish processing (as the last action
        * before returning) when the last scope is popped from the stack of
        * scopes.  For this reason, the current implementation assumes that the
        * session is over at the moment that the top-most user-defined scope
        * exits.
        * <p>
        * This method will honor any pending thread interruption by throwing
        * a {@link StopConditionException} using {@link #honorStopCondition}
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        *
        * @throws   StopConditionException
        *           If the current thread's was previously interrupted.
        */
       private static void popScope(WorkArea wa)
       throws StopConditionException
       {
          if (LOG.isLoggable(Level.FINE))
          {
             log(Level.FINE, "popScope", null);
          }

          // get the top block def on the stack
          BlockDefinition blk = wa.blocks.peek();

          if (blk.topLevel                                                                       &&
              (blk.scopeList == null || blk.scopeList[ScopeId.BUFFER_MANAGER.ordinal()] == null) &&
              wa.pm._isDelayedDelete(wa.pm._thisProcedure()))
          {
              BufferManager.registerScopeable(wa.bm);
          }

          try
          {
             if (wa.processUndoables())
             {
                wa.txNestingLevel--;
             }

             // if we have no more scopes in the stack of scopes, set flag for global termination
             // this global termination is the same for both appserver agents and normal clients - the state kept
             // in the global block needs to be finalized only when the actual client gets terminated.
             boolean globalTerm = (wa.blocks.size() == 1);

             // transaction related processing
             if (wa.isTransaction())
             {
                // is there a pending rollback?
                if (wa.isRollbackPending())
                {
                   // determine if a pending rollback refers to this scope
                   if (rollbackCurrentScope(wa, wa.getRollbackScope()))
                   {
                      processRollback(wa, blk);
                      // temporary mark block as rolled back to allow locks downgrade
                      blk.rolledBack = true;
                   }

                   // previously, if 'rollbackCurrentScope' was false, a 'processRollbackPending' method was being
                   // called, which was executing the 'rollbackPending' for each Commitable.  But as there was
                   // no real implementation of this method anywhere in FWD, this was removed.
                }
                else
                {
                   // we only commit if we haven't rolled back in this scope
                   if (!blk.rolledBack)
                   {
                      // OK, this is a case where we need to COMMIT!
                      processCommit(wa, blk);
                   }
                }
             }

             // prevent downstream code from retrying
             if (wa.retryScope != -1 && wa.retryScope == (wa.blocks.size() - 1))
             {
                wa.retryScope = -1;
             }

             // handle cleanup notifications
             processFinalizables(wa, blk, false);

             try
             {
                try
                {
                   try
                   {
                      if (blk.possibleCatch != null)
                      {
                         try
                         {
                            executeBlockTermination(wa, blk,
                                                    () -> processTerminationBlock(wa, blk, blk.possibleCatch), false);
                         }
                         finally
                         {
                            blk.possibleCatch = null;
                         }
                      }
                   }
                   finally
                   {
                      if (blk.finallyBlock != null)
                      {
                         try
                         {
                            // FINALLY block support (this MUST be AFTER commit/rollback and finalizables are
                            // processed but BEFORE the scope notifications)
                            executeBlockTermination(wa, blk,
                                     () -> processTerminationBlock(wa, blk, blk.finallyBlock), true);
                         }
                         finally
                         {
                            blk.finallyBlock = null;
                         }
                      }

                      wa.honorStopAfterCondition(blk);
                   }
                }
                finally
                {
                   if (!wa.isRemote() && isGlobalBlock(wa))
                   {
                      // this will be called after the termination of the root top-level block for standalone
                      // clients or appserver agents (the root top-level block is always a child of the global
                      // block, so this termination hook can be considered a FINALLY block for the global block).

                      // FWD ensures that this code is executed after all normal application logic has executed
                      // (including any FINALLY block processing for the root top-level block).  As this is
                      // considered a FINALLY code for the global block, we need to execute in the same terms as
                      // normal FINALLY blocks (AFTER commit/rollback and finalizables are processed
                      // but BEFORE the scope notifications)
                      processTerminationHook(wa);
                   }
                }
             }
             finally
             {
                // now we are done with the scope
                processScopeNotifications(wa, blk, false);

                // the error manager needs a special notification (it can't be a
                // scopeable because it is used on both the client and the server)
                if (blk.topLevel && blk.suppressError)
                {
                   // restore silent mode
                   wa.em.setSilent(true);
                }

                if (blk.undoables != null)
                {
                   ArrayList<UndoablePair> undoables = blk.undoables;
                   for (int i = 0; i < undoables.size(); i++)
                   {
                      UndoablePair target = undoables.get(i);
                      target.src.popBlock();
                   }
                }

                // top level blocks must clear our transaction flag
                if (wa.isTransactionBlock())
                {
                   wa.setTransactionLevel(-1);
                }

                // global termination occurs as the last thing after the last scope is removed
                if (globalTerm)
                {
                   processFinalizables(wa, wa.globalBlock, false);
                }

                // auto-remove processing for master commitables and finalizables
                if (wa.isFullTransaction())
                {
                   wa.autoRemoveMasterCommit();
                   wa.autoRemoveMasterFinish();
                }
             }
          }
          finally
          {
             // pop the top block definition
             wa.blocks.pop();

             // pop the query off-end listeners stack
             wa.offEndListeners.pop();

             if (blk != null)
             {
                // propagate any disabling of loop protection in the inner block back to any
                // enclosing inner block BUT NOT past the top level block
                if (!blk.topLevel && wa.blocks.size() > 0)
                {
                   // save off the exiting block's loop protection values
                   boolean prot   = blk.loopProtection;
                   boolean pause  = blk.hadPause;
                   boolean endkey = blk.endkeyRetry;

                   BlockDefinition enclosing = null;

                   // did the inner block have loop protection disabled
                   if (!prot)
                   {
                      enclosing = wa.blocks.peek();
                      enclosing.loopProtection = false;
                   }

                   // did the inner block have a pause occur
                   if (pause)
                   {
                      if (enclosing == null)
                      {
                         enclosing = wa.blocks.peek();
                      }

                      enclosing.hadPause = true;
                   }

                   if (endkey)
                   {
                      if (enclosing == null)
                      {
                         enclosing = wa.blocks.peek();
                      }

                      enclosing.endkeyRetry = true;
                   }

                   if (blk.interactions > 0)
                   {
                      if (enclosing == null)
                      {
                         enclosing = wa.blocks.peek();
                      }

                      if (enclosing.rollup)
                      {
                         enclosing.interactions += blk.interactions;
                      }
                   }
                }
             }
          }

          wa.handleDeferredError();
       }

       /**
        * Execute legacy block termination blocks like CATCH and FINALLY.
        * <p>
        * This will save and restore the {@link WorkArea#masterCommit} and {@link WorkArea#masterFinish} maps.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    blk
        *           The parent block definition.
        * @param    code
        *           The code to execute.
        * @param    isFinally
        *           Flag indicating a FINALLY block is executing.
        */
       private static void executeBlockTermination(WorkArea wa,
                                                   BlockDefinition blk,
                                                   Runnable code,
                                                   boolean isFinally)
       {
          Map<Object, Boolean> savedMasterCommit = null;
          Map<Object, Boolean> savedMasterFinish = null;

          // temporarily suspend/hide master commitables and finalizables while the code is being executed
          if (wa.masterCommit != null && !wa.masterCommit.isEmpty())
          {
             savedMasterCommit = wa.masterCommit;
             wa.masterCommit = null;
          }

          if (wa.masterFinish != null && !wa.masterFinish.isEmpty())
          {
             savedMasterFinish = wa.masterFinish;
             wa.masterFinish = null;
          }

          boolean nestedSilent = (isFinally && blk.wasReturnError);
          try
          {
             if (nestedSilent)
             {
                ErrorManager.nestedSilent(code);
             }
             else
             {
                code.run();
             }
          }
          catch (ReturnUnwindException rue)
          {
             switch (blk.type)
             {
                case FUNCTION:
                case EXTERNAL_PROC:
                case INTERNAL_PROC:
                case DATABASETRIGGER:
                   break;      // ignore it, returning anyway
                default:
                   throw rue;  // rethrow in order to return from the top-level routine (function/procedure)
             }
          }
          finally
          {
             // restore the master commitables and finalizables, if they were stacked and hidden
             if (savedMasterCommit != null)
             {
                wa.masterCommit = savedMasterCommit;
                savedMasterCommit = null;
             }
             if (savedMasterFinish != null)
             {
                wa.masterFinish = savedMasterFinish;
                savedMasterFinish = null;
             }
          }

       }

       /**
        * When the {@link #isGlobalBlock() the root top-level block} terminates, invoke any hook specified by
        * the {@link CommonSession#isClientDisconnected() SESSION:CLIENT-DISCONNECTED attribute}.
        *
        * @param    wa
        *           The context local state.
        */
       private static void processTerminationHook(WorkArea wa)
       {
          character hook = SessionUtils.getTerminationHook();
          if (hook.isUnknown() || hook.toJavaType().trim().isEmpty())
          {
             return;
          }

          ErrorManager.silent(() ->
          {
             String target = hook.toJavaType();
             try
             {
                int colon = target.indexOf(':');
                if (colon > 0 && target.lastIndexOf(':') == colon)
                {
                   // the com.goldencode.Foo:cleanup syntax, a static method call
                   String cls = target.substring(0, colon);
                   String mthd = target.substring(colon + 1);
                   ObjectOps.invokeStandalone(cls, mthd);
                }
                else
                {
                   // an external program
                   ControlFlowOps.invokeExternalProcedure(hook, false, null, false, null, null);
                }
             }
             catch (Throwable t)
             {
                // ignore any error, but log it
                if (LOG.isLoggable(Level.SEVERE))
                {
                   LOG.log(Level.SEVERE, "Abnormal end when processing termination hook '" + target + "':", t);
                }
             }
          });
       }

       /**
        * Execute the FINALLY block associated with the block definition, if any.
        * <p>
        * The FINALLY block will always be executed for the associated block, even if the stack unwinds because
        * of an abend.  WARNING: it is important to not change this behavior, FWD must allow the FINALLY code to
        * execute even if in cases of abends.  It is the application code's responsibility to ensure that the
        * code being executed is server-side only, maybe protected via the
        * {@link CommonSession#isClientDisconnected() SESSION:CLIENT-DISCONNECTED attribute}.
        *
        * @param    wa
        *           The context local state.
        * @param    blk
        *           The block definition to process.
        */
       private static void processTerminationBlock(WorkArea wa, BlockDefinition blk, Runnable code)
       {
          int transLevel         = -1;
          int workTxNestingLevel = -1;
          int blkTxNestingLevel  = -1;

          if (code != null)
          {
             // clear them, DeferredDeletablesManager can get registered again in the FINALLY block; but restore
             // them once that finishes.
             Set<Finalizable>[] prev = blk.finalizables;
             blk.finalizables = new Set[WeightFactor.WEIGHTS_ARRAY_SIZE];

             try
             {
                if (blk.full)
                {
                   transLevel         = wa.transLevel;
                   workTxNestingLevel = wa.txNestingLevel;
                   blkTxNestingLevel  = blk.txNestingLevel;

                   wa.transLevel      = -1;
                   wa.txNestingLevel  = -1;
                   blk.txNestingLevel = -1;
                }

                code.run();
             }
             catch (ReturnUnwindException rue)
             {
                switch (blk.type)
                {
                   case FUNCTION:
                   case EXTERNAL_PROC:
                   case INTERNAL_PROC:
                   case DATABASETRIGGER:
                      break;      // ignore it, returning anyway
                   default:
                      throw rue;  // rethrow in order to return from the top-level routine (function/procedure)
                }
             }
             finally
             {
                if (blk.full)
                {
                   wa.transLevel      = transLevel;
                   wa.txNestingLevel  = workTxNestingLevel;
                   blk.txNestingLevel = blkTxNestingLevel;
                }

                // execute again block's finalizables, if new ones were registered
                processFinalizables(wa, blk, false);

                // add back the previous ones
                blk.finalizables = prev;
             }
          }
       }

       /**
        * Notify {@link ConditionListener} instances about exception condition.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param   condition
        *          The instance of {@link ConditionException}.
        */
       private static void notifyCondition(WorkArea wa, ConditionException condition)
       {
          notifyCondition(wa, BlockManager.exceptionToCondition(condition));
       }

       /**
        * Notify {@link ConditionListener} instances about condition.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    condition
        *           The condition the listeners will be notified.
        */
       private static void notifyCondition(WorkArea wa, BlockManager.Condition condition)
       {
          BlockDefinition blk = wa.blocks.peek();

          if (blk.conditionListeners != null)
          {
             ArrayList<ConditionListener> conditionListeners = blk.conditionListeners;
             for (int i = 0; i < conditionListeners.size(); i++)
             {
                ConditionListener listener = conditionListeners.get(i);
                try
                {
                   listener.condition(condition);
                }
                catch (Throwable thr)
                {
                   deferError(wa, "notifyCondition", thr, false);
                }
             }
          }
       }

       /**
        * Provides a callback entry point for the top of the loop.  This location
        * is required to handle the backup of undo variables via
        * {@link #makeBackup}.  On the second and subsequent executions of this
        * method for a given block, this method handles <code>iterate</code>
        * notifications.
        * <p>
        * It is important to note that <code>continue</code> can be called from
        * nested blocks at arbitrary depths.  This method can handle such nesting
        * at any level.
        * <p>
        * This method will honor any pending thread interruption by throwing
        * a {@link StopConditionException} using {@link #honorStopCondition}
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        *
        * @throws   StopConditionException
        *           If the current thread's was previously interrupted.
        */
       private static void blockSetup(WorkArea wa)
       throws StopConditionException
       {
          if (LOG.isLoggable(Level.FINE))
          {
             log(Level.FINE, "blockSetup", null);
          }

          // read the block def at the top of the stack
          BlockDefinition blk = wa.blocks.peek();

          if (blk.init)
          {
             // second or subsequent time through
             iterateWorker(wa);
             blk.iterated = true;

             processResettables(wa, true);
          }
          else
          {
             // first time through
             processEntry(wa, blk);

             blk.init = true;
          }

          // now trigger the undoable backups
          backupWorker(wa, blk);
       }

       /**
        * Reports if the current iteration of this block or loop is a retry.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        *
        * @return    <code>true</code> if this is a retry of this block or loop
        *            (as opposed to the first time through this iteration).
        */
       private static boolean _isRetry(WorkArea wa)
       {
          // peek at the current block def on the top of the stack
          BlockDefinition blk = wa.blocks.peek();

          disableLoopProtection(blk);

          return blk.wasRetried;
       }

       /**
        * Records the fact that the pending RETRY is being caused by an ENDKEY
        * condition. This enables "downstream" retry processing to implement the
        * "stuttering" quirk and some behavior in honoring PAUSE as a way to
        * disable infinite loop protection.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        */
       private static void markEndkeyRetry(WorkArea wa)
       {
          // peek at the current block def on the top of the stack
          BlockDefinition blk = wa.blocks.peek();
          blk.endkeyRetry = true;
       }

       /**
        * This checks if any of the enclosing loops has iterated at least once.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        *
        * @return   <code>true</code> if any of the enclosing loops has iterated
        *           at least once.
        */
       private static boolean hasParentIteratedBlock(WorkArea wa)
       {
          for (BlockDefinition blk : wa.blocks)
          {
             if (blk.loop && blk.iterated)
             {
                return true;
             }
          }

          return false;
       }

       /**
        * Determines if a NEXT operation with the given target block should
        * be honored or if the NEXT should be converted to a LEAVE. This is
        * a special form of infinite loop protection.
        * <p>
        * A NEXT is converted into a LEAVE when all of the following conditions
        * are true:
        * <p>
        * <ul>
        *   <li> The NEXT is associated with the UNDO, NEXT language statement or
        *        with an ON phrase that has UNDO, NEXT as the result of the given
        *        condition (e.g. ON ENDKEY UNDO, NEXT). It is important to note
        *        that Progress does not provide infinite loop protection for the
        *        NEXT language statement, so there is no conversion to LEAVE in
        *        that case.
        *   <li> The target block (whether implicitly determined by the lack of a
        *        label or explicitly determined by the presence of a label) must
        *        also have been the target block for the UNDO operation. It is
        *        possible for the NEXT to be targetted at a more enclosing block
        *        than the UNDO, in which case there is no conversion to LEAVE.
        *   <li> The target block type must be REPEAT, REPEAT WHILE or DO WHILE.
        *        These are the only loop types that normally convert RETRY to
        *        LEAVE instead of NEXT.  Likewise, these are the only loop types
        *        which honor infinite loop protection for NEXT by converting it
        *        to a LEAVE.
        *   <li> Infinite loop protection must not have been disabled for the
        *        target block.  In this case, any form of PAUSE is honored as
        *        one of the mechanisms to disable infinite loop protection.
        * </ul>
        * <p>
        * Since NEXT is only possible on a looping block, if the target block
        * is not a looping block then this method will return <code>false</code>.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    label
        *           The label of the target block for the NEXT action.
        *
        * @return   <code>true</code> for NEXT, <code>false</code> for LEAVE.
        */
       private static boolean honorNext(WorkArea wa, String label)
       {
          BlockDefinition blk = null;
          int             lvl = -1;

          // check if the NEXT is targetted at the current block
          if (label == null || label.length() == 0)
          {
             // get the current block
             lvl = wa.blocks.size() - 1;
             blk = wa.blocks.peek();
          }
          else
          {
             // lookup the block target by its label
             lvl = findStackLevel(wa, label);

             if (lvl >= 0)
                blk = wa.blocks.elementAt(lvl);
          }

          // protect from NPE, but that should not happen unless a bogus label is passed
          if (blk != null)
          {
             // any non-looping block type always converts NEXT to LEAVE
             if (blk.type == DO                  ||
                 blk.type == FINALLY             ||
                 blk.type == FOR_BLOCK           ||
                 blk.type == FOR_BLOCK_WHILE     ||
                 blk.type == FOR_BLOCK_TO        ||
                 blk.type == FOR_BLOCK_TO_WHILE  ||
                 blk.type == EXTERNAL_PROC       ||
                 blk.type == INTERNAL_PROC       ||
                 blk.type == FUNCTION            ||
                 blk.type == TRIGGER             ||
                 blk.type == DATABASETRIGGER)
             {
                return false;
             }

             // make sure the target block is one of the looping blocks that can
             // cause NEXT to convert to LEAVE
             if (blk.type == DO_WHILE ||
                 blk.type == REPEAT   ||
                 blk.type == REPEAT_WHILE)
             {
                // conversion only happens if there is an active UNDO operation
                // targetted at the same block
                if (blk.isUndoLevel)
                {
                   boolean disable = false;

                   // this next algorithm is needed because normally, the popping
                   // of each block off the stack handles the "rollup" of the
                   // ILP state, but this method is being called before that
                   // rollup can occur so we simulate it here to allow us to come
                   // to the same conclusion

                   // walk up the stack to find if ILP or PAUSE has occurred
                   // anywhere between the current block and the target block,
                   // since disabling infinite loop protection or any PAUSE
                   // causes the conversion to be avoided
                   for (int i = wa.blocks.size() - 1; i >= lvl; i--)
                   {
                      BlockDefinition next = wa.blocks.elementAt(i);

                      if (!next.loopProtection || next.hadPause)
                      {
                         disable = true;
                         break;
                      }
                   }

                   // if ILP is not disabled
                   if (!disable)
                   {
                      // convert to LEAVE
                      return false;
                   }
                }
             }
          }

          // honor the NEXT, don't convert to LEAVE
          return true;
       }

       /**
        * Determines if the currently executing block should be retried.  This
        * is where infinite loop protection is implemented if it is enabled.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        *
        * @return   <code>true</code> if the block (whether or not it is a loop)
        *           should be retried.
        */
       private static boolean needsRetry(WorkArea wa)
       {
          // peek at the current block def on the top of the stack
          BlockDefinition blk = wa.blocks.peek();

          boolean noPause = true;

          // a PAUSE sometimes acts to disable infinite loop protection
          if (blk.hadPause)
          {
             // depending solely on the current block type
             if (blk.type == DO_WHILE ||
                 blk.type == REPEAT   ||
                 blk.type == REPEAT_WHILE)
             {
                noPause = false;
             }

             // also can happen in other block types IF this is a retry caused
             // by the raising of an ENDKEY condition
             if (blk.endkeyRetry)
             {
                if (blk.type == DO                  ||
                    blk.type == FINALLY             ||
                    blk.type == DO_TO               ||
                    blk.type == REPEAT_TO           ||
                    blk.type == FOR_BLOCK           ||
                    blk.type == FOR_BLOCK_WHILE     ||
                    blk.type == FOR_BLOCK_TO        ||
                    blk.type == FOR_BLOCK_TO_WHILE  ||
                    blk.type == FOR_LOOP            ||
                    blk.type == FOR_LOOP_WHILE      ||
                    blk.type == FOR_LOOP_TO         ||
                    blk.type == FOR_LOOP_TO_WHILE)
                {
                   noPause = false;
                }
             }
          }

          boolean bypass = false;

          // in certain block types when a RETRY is caused by an ENDKEY, the
          // normal infinite loop protection mechanism is bypassed depending on
          // an internal counter; this causes a kind of "stutter" effect where
          // a RETRY on some iterations will be a real RETRY and other iterations
          // it will be converted to a NEXT
          if (blk.endkeyRetry && blk.loopProtection && noPause)
          {
             // looping blocks that modify state on each pass are the only
             // blocks that get this stuttering behavior
             if (blk.type == DO_TO               ||
                 blk.type == REPEAT_TO           ||
                 blk.type == FOR_LOOP            ||
                 blk.type == FOR_LOOP_WHILE      ||
                 blk.type == FOR_LOOP_TO         ||
                 blk.type == FOR_LOOP_TO_WHILE)
             {
                // the ilpCount will start at -1 and then approach 1 (so the
                // first 2 passes through here will convert to NEXT), then on
                // the 3rd pass the ilpCount will back down to 0 (allowing the
                // RETRY to occur normally), then the 4th pass will increment
                // the ilpCount to 1 (converts to NEXT), then 5th pass will
                // back down to 0 (RETRY), 6th pass (NEXT), 7th pass (RETRY)...
                // causing a "stuttering" effect

                // maintain the "stutter" counter
                if (blk.ilpCount < 1)
                {
                   // allow the retry to be converted to NEXT (all these block
                   // types should have blk.next == true)
                   blk.ilpCount++;
                }
                else
                {
                   // cause a "stutter" (allow the RETRY to proceed as if ILP
                   // was disabled)
                   blk.ilpCount--;
                   bypass = true;
                }
             }
          }

          // honor infinite loop protection (noPause is the way that PAUSE
          // disables infinite loop protection)
          if (blk.retry && blk.loopProtection && noPause && !bypass)
          {
             // set flag to convert RETRY into LEAVE
             if (!blk.next)
             {
                blk.pendingBreak = true;
             }

             // unless the special flag is set above, this is equivalent to NEXT
             return false;
          }

          boolean result = blk.retry;

          // is this a retry?
          if (result)
          {
             // reset our retry state as if this was a fresh pass at the block
             // except the wasRetried will be true this time
             blk.retry          = false;
             blk.loopProtection = true;
             blk.hadPause       = false;
             blk.endkeyRetry    = false;
             blk.pendingBreak   = false;
             blk.wasRetried     = true;
             blk.rolledBack     = false;
             blk.isUndoLevel    = false;

             // this is the last point before the retry pass of the loop actually
             // occurs, notify the finalizables that a retry is occurring
             processRetry(wa, blk);
          }

          return result;
       }

       /**
        * Accesses the state of whether there is a pending break that must be
        * honored.  This would have been generated by the infinite loop
        * protection.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        *
        * @return   <code>true</code> if a break is pending for this block.
        */
       private static boolean isBreakPending(WorkArea wa)
       {
          // peek at the current block def on the top of the stack
          BlockDefinition blk = wa.blocks.peek();

          return blk.pendingBreak;
       }

       /**
        * Force an error to be generated such that it will be caught in the
        * caller of this method.  The stack will be unwound and normal error
        * processing bypassed until this method returns.  This unwinding is
        * dependent upon catch blocks being implemented with a corresponding call
        * to {@link #honorError}.
        * <p>
        * In the case where silent error mode is enabled in the calling method,
        * the "normal" stack unwinding approach cannot be used because the
        * exception cannot be thrown. Instead, the error manager will be updated
        * with the error data and this method will silently return.  It is
        * critical that in such cases the calling code must immediately execute a
        * <code>return</code> statement which will maintain the proper Progress
        * behavior.  The caller can check the error manager to determine that an
        * error occurred.
        * <p>
        * Detecting the silent error mode in the caller requires that the top
        * level block be found.  The silent error flag of the caller is cached
        * there.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    errNum
        *           The identifier of the error.  When not-null, the error message will be registered.
        * @param    text
        *           The text to use as the error message in the exception.
        *
        * @throws   ErrorConditionException
        */
       private static void triggerErrorInCaller(WorkArea wa, Integer errNum, String text)
       throws ErrorConditionException
       {
          triggerErrorInCaller(wa, errNum, text, true, true);
       }

       /**
        * Force an error to be generated such that it will be caught in the
        * caller of this method.  The stack will be unwound and normal error
        * processing bypassed until this method returns.  This unwinding is
        * dependent upon catch blocks being implemented with a corresponding call
        * to {@link #honorError}.
        * <p>
        * In the case where silent error mode is enabled in the calling method,
        * the "normal" stack unwinding approach cannot be used because the
        * exception cannot be thrown. Instead, the error manager will be updated
        * with the error data and this method will silently return.  It is
        * critical that in such cases the calling code must immediately execute a
        * <code>return</code> statement which will maintain the proper Progress
        * behavior.  The caller can check the error manager to determine that an
        * error occurred.
        * <p>
        * Detecting the silent error mode in the caller requires that the top
        * level block be found.  The silent error flag of the caller is cached
        * there.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    errNum
        *           The identifier of the error.  When not-null, the error message will be registered.
        * @param    text
        *           The text to use as the error message in the exception.
        * @param    errFlag
        *           Flag indicating the ERROR-ERROR:ERROR is set.
        * @param    prefix
        *           Prefix or not with two asterics when building the error message text.
        *
        * @throws   ErrorConditionException
        */
       private static void triggerErrorInCaller(WorkArea wa,
                                                Integer  errNum,
                                                String   text,
                                                boolean  errFlag,
                                                boolean  prefix)
       throws ErrorConditionException
       {
          BlockDefinition blk = nearestTopLevel(wa);

          if (blk != null && blk.suppressError)
          {
             if (errNum != null)
             {
                wa.em.addError(errNum, text, errFlag, prefix);
             }

             wa.em.setPending(true);
          }
          else
          {
             // turn on the ignore flag
             wa.ignoreError = true;

             // throw the error
              if (errNum != null)
              {
                  throw new ErrorConditionException(errNum, text, true);
              }
              else
              {
                  throw new ErrorConditionException(-1, text, true);
              }
          }
       }

       /**
        * Force an error to be generated such that it will be caught in the
        * caller of this method.  The stack will be unwound and normal error
        * processing bypassed until this method returns.  This unwinding is
        * dependent upon catch blocks being implemented with a corresponding call
        * to {@link #honorError}.
        * <p>
        * In the case where silent error mode is enabled in the calling method,
        * the "normal" stack unwinding approach cannot be used because the
        * exception cannot be thrown. Instead, the error manager will be updated
        * with the error data and this method will silently return.  It is
        * critical that in such cases the calling code must immediately execute a
        * <code>return</code> statement which will maintain the proper Progress
        * behavior.  The caller can check the error manager to determine that an
        * error occurred.
        * <p>
        * Detecting the silent error mode in the caller requires that the top
        * level block be found.  The silent error flag of the caller is cached
        * there.
        *
        * @param    lex
        *           The legacy OO-like error to throw.
        *
        * @throws   ErrorConditionException
        */
       private static void triggerErrorInCaller(WorkArea wa, LegacyErrorException lex)
       throws ErrorConditionException
       {
          BlockDefinition blk = nearestTopLevel(wa);

          if (blk != null && blk.suppressError)
          {
             wa.em.setPending(true);
          }

          // throw the error
          throw lex;
       }

       /**
        * Check if this block's caller requires errors to be suppressed.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        *
        * @return   {@link BlockDefinition#suppressError} for {@link #nearestTopLevel} block.
        *
        */
       private static boolean isSuppressError(WorkArea wa)
       {
          BlockDefinition blk = nearestTopLevel(wa);

          return (blk != null && blk.suppressError);
       }

       /**
        * Force a retry of the current block or the scope named by the given
        * label, ensuring that the flow of control will not naturally continue.
        * A {@link RetryUnwindException} will be thrown to unwind the stack
        * to the given scope, even if the scope is the current block.  This
        * unwinding is dependent upon catch blocks being implemented with a
        * corresponding call to {@link #honorRetry}.
        * <p>
        * <b>This method will NEVER naturally return since an exception is ALWAYS
        * thrown.</b>  This method should generally not be called from within a
        * catch block if the intention is to retry the current block since it
        * would be outside of the block's associated <code>try</code>.  In that
        * case, only an enclosing block could be retried.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    label
        *           The name of the scope to retry. Specify <code>null</code> to
        *           retry the scope that has a pending rollback OR to retry
        *           the current scope if there is no pending rollback.
        *
        * @throws   RetryUnwindException
        *           This method always generates this exception to ensure that
        *           natural processing will not continue.
        */
       private static void forceRetry(WorkArea wa, String label)
       throws RetryUnwindException
       {
          int level = findStackLevel(wa, label);

          if (level == -1)
          {
             if (wa.rollbackPending)
             {
                // force retry at the same level as the pending rollback
                level = wa.rollbackScope;
             }
             else
             {
                if (!wa.isTransaction())
                {
                   // in a non-transaction block we still may have an implicit
                   // target specified during rollback but it would have been
                   // stored in the retryScope var (it may have been set to -1
                   // too, that is OK)
                   level = wa.retryScope;
                }
             }
          }

          // is the current block the target?
          if (level == -1)
          {
             // pretend that we are doing a "normal" unwind
             level = wa.blocks.size() - 1;
          }

          // record the scope for the pending retry
          wa.retryScope = level;

          // abort current processing and jump to end of current block OR unwind
          // the stack to the specified block
          throw new RetryUnwindException("Unwinding to stack level " + level);
       }

       /**
        * Force a retry of the current block or the scope named by the given
        * label.  If a containing scope is the target of the retry, then
        * a {@link RetryUnwindException} will be thrown to unwind the stack
        * to the given scope.  This unwinding is dependent upon catch blocks
        * being implemented with a corresponding call to {@link #honorRetry}.
        * <p>
        * If the current block is not the target, the retry is deferred by
        * marking the target block and then unwinding the stack.  Otherwise,
        * this method will return naturally.  This method is only appropriate
        * to be called from within <code>catch</code> blocks since a natural
        * return within a <code>catch</code> block will fall through to allow
        * the retry loop to engage.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    label
        *           The name of the scope to retry. Specify <code>null</code> to
        *           retry the scope of that has a pending rollback OR to retry
        *           the current scope if there is no pending rollback.
        *
        * @throws   RetryUnwindException
        */
       private static void triggerRetry(WorkArea wa, String label)
       throws RetryUnwindException
       {
          // TODO: if this is a routine block, then check if this retry needs to convert to ERROR
          // TODO: exclude destructors and UI triggers

          int level = findStackLevel(wa, label);

          if (level == -1)
          {
             if (wa.rollbackPending)
             {
                // force retry at the same level as the pending rollback
                level = wa.rollbackScope;
             }
             else
             {
                if (!wa.isTransaction())
                {
                   // in a non-transaction block we still may have an implicit
                   // target specified during rollback but it would have been
                   // stored in the retryScope var (it may have been set to -1
                   // too, that is OK)
                   level = wa.retryScope;
                }
             }
          }

          // peek at the current block def on the top of the stack
          BlockDefinition blk = wa.blocks.peek();

          if (level == -1 || level == (wa.blocks.size() - 1))
          {
             blk.retry = true;
          }
          else
          {
             // record the scope for the pending retry
             wa.retryScope = level;

             // unwind the stack to that point
             throw new RetryUnwindException("Unwinding to stack level " + level);
          }
       }

       /**
        * Detect if the nearest top level block is a database trigger block
        * @param wa
        *        Context local work area. Must not be {@code null}.
        * @return
        *        True if the nearest top level block is a database trigger, false otherwise
        */
       public static boolean isTopLevelDatabaseTrigger(WorkArea wa)
       {
          BlockDefinition blk = nearestTopLevel(wa);
          return blk.databaseTrigger;
       }

       /**
        * Dispatch the given exception to all registered stop condition handlers
        * to see if any requires that the STOP condition be vetoed at the
        * current scope.  If any of the registered handlers indicate that the STOP
        * condition should not be honored at this level, the exception is
        * rethrown, presumably to be honored at a higher scope, or not at all.  In
        * the latter case, the program running in the current context will abort.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param   exc
        *          The exception to be checked.
        *
        * @throws  StopConditionException
        *          if the STOP condition is not honored at this scope.
        */
       private static void honorStop(WorkArea wa, StopConditionException exc)
       throws StopConditionException
       {
          for (StopConditionVetoHandler handler : wa.stopHandlers)
          {
             if (handler.vetoStop(exc))
             {
                // re-throw the exception but first it will maintain the rollback
                // state/processing
                abnormalEnd(wa, exc, false);
             }
          }

          // No handlers want to veto the STOP at this scope - allow the STOP
          // to be executed
       }

       /**
        * Check if there is a pending retry of the current block and if so then
        * enable retry.  This is the "last half" of the processing to unwind the
        * stack up to a containing scope that is the target of a retry.  The
        * "first half" of the unwinding is implemented by throwing a
        * {@link RetryUnwindException}.  This unwinding is dependent upon catch
        * blocks being implemented to call this method.
        * <p>
        * If the current block is not the target for the pending retry, the
        * exception is re-thrown.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    rue
        *           The exception to honor or ignore.
        *
        * @throws   RetryUnwindException
        */
       private static void honorRetry(WorkArea wa, RetryUnwindException rue)
       throws RetryUnwindException
       {
          if (wa.retryScope == (wa.blocks.size() - 1))
          {
             // peek at the current block def on the top of the stack
             BlockDefinition blk = wa.blocks.peek();

             blk.retry = true;
             wa.retryScope = -1;
          }
          else
          {
             // we cannot propagate the exception past the full transaction
             // level without causing a rollback(), unless there is a rollback
             // already pending in which case it will be naturally be handled
             // by popScope() when the full transaction exits, so don't force a
             // rollback again (calling this more than once must not be done
             // while there is a pending rollback); note that this is only safety
             // code since "well formed" converted 4GL code should never cause
             // this to occur since the UNDO and RETRY must always be targeted
             // at the same block AND the UNDO (a call to rollback()) should
             // always be made before the RETRY is triggered
             if (wa.isFullTransaction() && !hadRollback())
             {
                rollback(null, rue);
             }

             // we aren't there yet, re-throw the exception
             throw rue;
          }
       }

       /**
        * Check if the current error must unwind all the way to the caller of
        * the top-level block in this method (if the ignore flag is set).  If
        * this is the case then the error is re-thrown.  Before this occurs, if
        * the current block is the top-level block of this method, then the
        * ignore flag will be cleared.  If the ignore flag is <code>false</code>
        * then this method returns silently.
        * <p>
        * This code also honors any nested error that was generated while another
        * error was being processed AND while nested error mode was enabled.  Any
        * such stored error will be re-thrown here.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    err
        *           The exception to honor or ignore.
        *
        * @throws   ErrorConditionException
        */
       private static void honorError(WorkArea wa, ErrorConditionException err)
       throws ErrorConditionException
       {

          if (wa.ignoreError ||
              isTopLevelDatabaseTrigger(wa))
          {
             // peek at the current block def on the top of the stack
             BlockDefinition blk = wa.blocks.peek();

             if (blk.topLevel)
             {
                // the next catch block will handle this error (it will be in the caller)
                wa.ignoreError = false;

                // this bypasses the commit
                blk.rolledBack = true;
             }

             // TODO: should this call abnormalEnd() instead so that the rollback
             // state/processing is maintained/handled?

             // continue to unwind
             throw err;
          }

          // nested error support
          if (wa.nestedError != null)
          {
             ConditionException ece = wa.nestedError;

             // reset our nested error
             wa.nestedError = null;

             // TODO: should this call abnormalEnd() instead so that the rollback
             // state/processing is maintained/handled?
             throw ece;
          }
       }

       /**
        * Notifies the transaction manager that an unexpected exception or error
        * has occurred and that any transaction must be aborted. This will
        * maintain the state of the system properly, including rolling back any
        * open transaction. Once the state is properly maintained, the triggering
        * <code>Throwable</code> will be re-thrown as a
        * <code>RuntimeException</code>.
        * <p>
        * If the exception is of type <code>ConditionException</code> or
        * <code>RetryUnwindException</code>, this method will do nothing since
        * these are used for normal flow of control purposes and are used in
        * close coordination with the transaction manager's state.  They need
        * no other special behavior to be safe.  This method is designed to
        * handle exceptions (like <code>NullPointerException</code>) which are
        * not valid in a converted 4GL program.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    thr
        *           The unexpected exception or error.
        * @param    needsNotify
        *           <code>true</code> if condition notification must be executed
        *           (only happens if the <code>thr</code> is a condition).
        *
        * @throws   RuntimeException
        */
       private static void abnormalEnd(WorkArea wa, Throwable thr, boolean needsNotify)
       throws RuntimeException
       {
          if (thr instanceof ConditionException)
          {
             if (needsNotify)
             {
                notifyCondition(wa, (ConditionException) thr);
             }

             // we cannot propagate the exception past the full transaction
             // level without causing a rollback() except for QUIT which has
             // the unusual behavior of committing the transaction UNLESS one
             // specifies an ON QUIT phrase inside the transaction block or a
             // nested block which encloses the raised condition (note that in
             // that case this method will never be called so it will work fine)
             if (wa.isFullTransaction() && !(thr instanceof QuitConditionException))
             {
                // if a rollback is already pending, then it will be naturally
                // handled by popScope() when the full transaction exits, so
                // don't force a rollback again (calling this more than once
                // must not be done while there is a pending rollback)
                if (!hadRollback())
                {
                   rollback(wa, null, thr);
                }
             }

             ConditionException ce = (ConditionException) thr;
             throw ce;
          }
          else if (thr instanceof RetryUnwindException)
          {
             // this should never really get called since there should always
             // be an explicit catch block for this BEFORE any Throwable catch
             // block, but for safety purposes...
             RetryUnwindException rue = (RetryUnwindException) thr;
             honorRetry(wa, rue);
          }
          else
          {
             SessionUtils.clientDisconnected();

             if (!isCausedBy(thr, ApplicationRequestedStop.class))
             {
                LOG.severe("Abnormal end; original error:", thr);
             }

             boolean storeDeferred = (wa.deferredError == null);
             try
             {
                if (storeDeferred)
                {
                   // if there is no existing deferred error, store the current
                   // error, ensuring errors thrown in this try block do not mask
                   // the cause of the abnormal end, but are still logged by
                   // deferError()
                   wa.deferredError = thr;
                }
                notifyCondition(wa, BlockManager.Condition.UNKNOWN);
                rollback(wa, null, thr);
             }
             finally
             {
                if (storeDeferred)
                {
                   // reset deferred error to its previous state
                   wa.deferredError = null;
                }
             }

             if (thr instanceof RuntimeException)
             {
                // the exception is already the right type so we don't have to
                // wrap it again (this avoids unnecessarily long chained log
                // entries
                throw (RuntimeException) thr;
             }
             else
             {
                // wrap the exception in a type that will propagate all the
                // way "out" as an abend
                throw new RuntimeException(thr);
             }
          }
       }

       /**
        * Returns if a Throwable is caused by a Throwable of the class to check against.
        *
        * @param    thr
        *           The Throwable to check.
        * @param    clazz
        *           The Throwable class to check against.
        *
        * @return   <code>true</code> if the Throwable is caused a Throwable of the provided class,
        *           <code>false</code> otherwise.
        */
       private static <T extends Throwable> boolean isCausedBy(Throwable thr, Class<T> clazz)
       {
          if (thr == null)
          {
             return false;
          }
          if (thr.getClass().equals(clazz))
          {
             return true;
          }
          return isCausedBy(thr.getCause(), clazz);
       }

       /**
        * Reports if the current block is a top-level block (this corresponds
        * with a method-level block).
        *
        * @param    wa
        *           Context local work area.
        *
        * @return  <code>true</code> if top-level block, else <code>false</code>.
        */
       private static boolean isTopLevelBlock(WorkArea wa)
       {
          BlockDefinition blk = wa.blocks.peek();
          return blk.topLevel;
       }

       /**
        * Reports if the current block is a global scoped block. For normal contexts:
        * <code>true</code> if the current block is the only block on the stack of block scopes; for
        * appserver agent contexts: <code>true</code> if the current block is one of two outermost
        * blocks ("appserver-agent" and "startup") on the stack of block scopes.
        *
        * @param    wa
        *           Context local work area.
        *
        * @return   See above.
        */
       private static boolean isGlobalBlock(WorkArea wa)
       {
          int size = wa.blocks.size();
          return size == 1 || (size == 2 && wa.isRemote());
       }

       /**
        * Search the stack of scopes and return the level of the first top level block found.
        *
        * @param    wa
        *           Context local work area.
        *
        * @return   The stack level of the nearest enclosing top level block or
        *           -1 if there is no block marked as a top level block.
        */
       private static int findNearestTopLevel(WorkArea wa)
       {
          // walk backwards up the stack
          for (int i = wa.blocks.size() - 1; i >= 0; i--)
          {
             BlockDefinition blk = wa.blocks.elementAt(i);

             if (blk.topLevel)
             {
                return i;
             }
          }

          return -1;
       }

       /**
        * Get the rollup value for the enclosing block. If <code>true</code>,
        * then the current block's exit will cause the enclosing (containing)
        * block's interactions counter to be increased by the current block's
        * interactions counter. Otherwise, no aggregation will occur.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        *
        * @return   The current block's rollup value.
        */
       private static boolean isEnclosingRollup(WorkArea wa)
       {
          int parent = wa.blocks.size() - 2;

          // by default, the rollup value is true
          if (parent < 0)
             return true;

          BlockDefinition blk = wa.blocks.elementAt(parent);

          return blk.rollup;
       }

       /**
        * Resets all registered <code>Resettable</code> objects or makes them
        * clear their lists of changes that should be reset.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    clearOnly
        *           If <code>true</code> then clear the lists of changes that should
        *           be reset, otherwise perform reset of these changes.
        */
       private static void processResettables(WorkArea wa, boolean clearOnly)
       {
          for (int i = 0; i < wa.resettables.size(); i++)
          {
             Resettable resettable = wa.resettables.get(i);

             resettable.resetState(clearOnly);
          }
       }

       /**
        * Adds the object into a scoped dictionary of objects requiring UNDO support. This registry
        * is used to backup the objects at the beginning of transactions or sub-transactions.
        * <p>
        * These registrations naturally are removed when the associated scope is removed. However,
        * the global scope is long-lived. Special care is necessary to prevent the same instance of
        * global shared variables being added multiple times.
        *
        * @param    undoTarget
        *           The object reference to be rolled back on failure.  This
        *           cannot be <code>null</code>.
        * @param    transLevel
        *           The transaction level to set for this undoable.
        * @param    persistent
        *           Flag indicating if the undoable is defined in a persistent procedure
        */
       private static void registerUndo(LazyUndoable undoTarget, int transLevel, boolean persistent)
       {
          if (undoTarget == null)
          {
             return;
          }

          // if we are starting a persistent procedure, then the undoables must register all
          // the way up the stack, and not just until the defining scope is reached: this is
          // because an undo can be trigger by a previous block, outside of the persistent
          // procedure, after the persistent procedure has finished its external scope

          // the transLevel is subtracted 1 because we register in the current tx block
          undoTarget.markUndoable(persistent ? -1 : transLevel - 1);
       }

       /**
        * Search the stack of scopes and return the type of first top level block found.
        *
        * @param    wa
        *           Context local work area.
        *
        * @return   The nearest enclosing top level block or {@link BlockType#UNKNOWN} if
        *           there is no block marked as a top level block.
        */
       private static BlockType getNearestTopLevelType(WorkArea wa)
       {
          BlockDefinition top = nearestTopLevel(wa);
          return (top == null) ? UNKNOWN : top.type;
       }

       /**
        * Search the stack of scopes and return the first top level block found.
        *
        * @param    wa
        *           Context local work area.
        *
        * @return   The nearest enclosing top level block or <code>null</code> if
        *           there is no block marked as a top level block.
        */
       private static BlockDefinition nearestTopLevel(WorkArea wa)
       {
          Stack<BlockDefinition>        blocks = wa.blocks;

          for (int idx = blocks.size() - 1; idx >= 0; idx--)
          {
             BlockDefinition blk = blocks.get(idx);

             if (blk.topLevel)
             {
                // done!
                return blk;
             }
          }
          return null;
       }

       /**
        * Returns the index of the full transaction block, or -1 if none exists.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        *
        * @return   The index of the full transaction block, or -1 if none exists.
        */
       private static int getFullTransactionBlock(WorkArea wa)
       {
          Stack<BlockDefinition> blocks = wa.blocks;

          for (int i = 0; i < blocks.size(); i++)
          {
             if (blocks.get(i).full)
             {
                return i;
             }
          }

          return -1;
       }

       /**
        * Search the stack of scopes and return the level of the first external
        * block found.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        *
        * @return   The stack level of the nearest enclosing external block or
        *           -1 if there is no block marked as an external block. Level
        *           number is 1-based starting from the outermost scope.
        */
       private static int findNearestExternal(WorkArea wa)
       {
          ListIterator<BlockDefinition> iter =
             wa.blocks.listIterator(wa.blocks.size());

          int level = wa.blocks.size() + 1;

          // walk backwards up the stack of block definitions
          while (iter.hasPrevious())
          {
             BlockDefinition blk = iter.previous();
             level--;

             if (blk.external)
             {
                return level;
             }
          }

          return -1;
       }

       /**
        * This method is called after the initialization of a FOR or FOR EACH
        * block is finished. It will save all the {@link QueryOffEndListener}s
        * associated with the registered {@link WorkArea#offEndQueries queries}.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        */
       private static void stopOffEndRegistration(WorkArea wa)
       {
          Set<QueryOffEndListener> listeners = wa.offEndListeners.peek();

          Set<P2JQuery> queries = wa.offEndQueries.pop();
          if (!queries.isEmpty())
          {
             if (listeners == Collections.EMPTY_SET)
             {
                listeners = Collections.newSetFromMap(new IdentityHashMap<>());
                wa.offEndListeners.pop();
                wa.offEndListeners.push(listeners);
             }

             for (P2JQuery query : queries)
             {
                ArrayList<QueryOffEndListener> ql = query.getOffEndListeners();
                for (int i = 0; i < ql.size(); i++)
                {
                   listeners.add(ql.get(i));
                }
             }
          }

          wa.offEndRegistration = false;
       }

       /**
        * Register the passed query for query off-end events. The passed queries
        * are all the queries created in the init method for a FOR or FOR EACH
        * block.
        * <p>
        * These queries will be able to end the loop when the last record is
        * reached, by overriding any user-defined END-KEY action. The action
        * wich is executed is the default UNDO, LEAVE action for END-KEY.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    query
        *           The query which needs to be registered.
        */
       private static void registerOffEndQuery(WorkArea wa, P2JQuery query)
       {
          if (wa.offEndRegistration)
          {
             wa.offEndQueries.peek().add(query);
          }
       }

       /**
        * Check if the given listener is registered for query off-end events and
        * can override the default END-KEY action (which is UNDO, LEAVE), when
        * the current block is FOR or FOR EACH.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    listener
        *           A listener to be checked if is registered for off-end events.
        *
        * @return  <code>true</code> if the listener is registered for query
        *          off-end event.
        */
       private static boolean isOffEndListener(WorkArea wa, QueryOffEndListener listener)
       {
          Set<QueryOffEndListener> listeners = wa.offEndListeners.peek();

          return listeners.contains(listener);
       }

       /**
        * Iterate all registered off-end listeners and call the
        * {@link QueryOffEndListener#initialize() initialize} or
        * {@link QueryOffEndListener#finish() finish} method, depending on the
        * flag.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    init
        *           <code>true</code> if the listeners are initialized; else, the
        *           {@link QueryOffEndListener#finish()} method is called.
        */
       private static void processOffEndListeners(WorkArea wa, boolean init)
       {
          Set<QueryOffEndListener> listeners = wa.offEndListeners.peek();

          for (QueryOffEndListener qoel : listeners)
          {
             if (init)
             {
                qoel.initialize();
             }
             else
             {
                qoel.finish();
             }
          }
       }

       /**
        * Get the topmost set of off-end {@link QueryOffEndListener listeners}.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    clear
        *           <code>true</code> if the top-most off-end listeners set
        *           should be cleared.
        */
       private static Set<QueryOffEndListener> getOffEndListeners(WorkArea wa, boolean clear)
       {
          Set<QueryOffEndListener> top = wa.offEndListeners.peek();
          try
          {
             if (!top.isEmpty())
             {
                Set<QueryOffEndListener> res = Collections.newSetFromMap(new IdentityHashMap<>());
                res.addAll(top);
                return res;
             }
             else
             {
                return Collections.emptySet();
             }
          }
          finally
          {
             if (clear)
             {
                top.clear();
             }
          }
       }

       /**
        * Register the single output parameter assigner for the current block.
        * This object will be deregistered by the {@link BlockManager} when it is
        * time to process assign-backs from function/procedure output parameters
        * to the database record fields with which they are associated.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    opa
        *           An output parameter assigner.
        *
        * @throws  IllegalStateException
        *          if a second output parameter assigner is registered for a block
        *          before the current one is deregistered.  There can be only one.
        */
       private static void registerOutputParameterAssigner(WorkArea wa, OutputParameterAssigner opa)
       {
          // peek at the current block def on the top of the stack
          BlockDefinition blk = wa.blocks.peek();

          if (blk.outputParmAssigner != null)
          {
             throw new IllegalStateException(
                "Illegal attempt to register output parameter assigner");
          }

          blk.outputParmAssigner = opa;
       }

       /**
        * Defer handling of an exception thrown by a commit/rollback notification
        * callback on a registered {@link Commitable}, so that iteration or pop
        * scope processing can be continued without interruption.  This ensures
        * that all <code>Commitable</code>s registered after the errant one still
        * receive their commit/rollback notifications, and that (in the case of
        * popping a scope), registered {@link Scopeable}s still receive their
        * scope finished notifications.
        * <p>
        * If no error is already pending, <code>exc</code> is stored, to be thrown
        * upon conclusion of processing the current block scope or iteration.  If
        * an error is already pending, <code>exc</code> is discarded.  In either
        * case, <code>exc</code> is logged if error-level logging is enabled.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param   action
        *          A brief description of the action occurring at the time the
        *          error was encountered, such as "commit" or "master rollback".
        * @param   thr
        *          The error caught during processing of a commit or rollback
        *          notification.
        * @param   silent
        *          If <code>true</code> then exception is not logged.
        *
        * @see     WorkArea#handleDeferredError
        */
       private static void deferError(WorkArea wa, String action, Throwable thr, boolean silent)
       {
          // when intermixing Progress-specific and non-Progress aware runtime
          // layers, it is possible for ConditionException causes to be hidden
          // within other exception types; this processing follows the causal
          // chain and un-hides any ConditionException that was obscured
          Throwable cause       = thr;
          Throwable condition   = thr;
          boolean   interrupted = false;
          boolean   silentUnwind = false;

          while (cause != null)
          {
             if (cause instanceof ConditionException)
             {
                condition = cause;
             }

             if (cause instanceof InterruptedException)
             {
                interrupted = true;
             }

             if (cause instanceof SilentUnwindException)
             {
                silentUnwind = true;
             }

             cause = cause.getCause();
          }

          if (!silent)
          {
             // if the condition is a STOP condition generated by a thread
             // interruption via CTRL+C, then we deferr the error, but change the
             // log level from SEVERE to FINE
             Level logLvl = silentUnwind ||
                   (condition instanceof StopConditionException &&
                         interrupted) ? Level.FINER
                   : Level.SEVERE;

             if (LOG.isLoggable(logLvl))
             {
                String msg = "Error processing " + action + "; ";
                if (wa.deferredError == null)
                {
                   msg += "deferring throw until block/iteration completes";
                }
                else
                {
                   msg += "deferred error already pending; new error discarded";
                }

                log(logLvl, "deferError", msg, thr);
             }
          }

          if (wa.deferredError == null)
          {
             // save the error
             wa.deferredError = condition;
          }
       }

       /**
        * Notify all registered {@link BatchListener} objects that a batch is
        * either starting or stopping.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    start
        *           <code>true</code> if this is the start of a batch, otherwise
        *           <code>false</code>.
        */
       private static void processBatchNotifications(WorkArea wa, boolean start)
       {
          if (LOG.isLoggable(Level.FINE))
          {
             log(Level.FINE,
                 "processBatchNotifications",
                 "start_batch = " + Boolean.toString(start));
          }

          for (BatchListener listener : wa.batchListeners)
          {
             listener.batchNotify(start);
          }
       }

       /**
        * Allows a loop iteration event to occur in the transaction manager,
        * which triggers commit processing before the next iteration of the
        * loop begins.
        * <p>
        * This hook is called at the top of the loop, just after the loop test
        * has been passed.  If the loop test fails, then this will not be called.
        * This code will be called inside the block itself, as the first thing
        * that is done.
        * <p>
        * Note that this is only called on the second and subsequent iterations
        * of the block, no matter how the iteration occurred (via natural control
        * flow through the bottom of an iterating block OR via a Java
        * <code>continue</code> statement that caused a jump to the top of the
        * loop).  Clearing the retry logic and rollback logic is important
        * whether or not this is a loop since non-loops can be retried (via
        * condition processing) or iterated via <code>continue</code>.
        * <p>
        * All registered {@link Finalizable Finalizables} are notified of the
        * iteration via their {@link Finalizable#iterate} callback method.
        * <p>
        * If the block is not a loop, commit/rollback processing and
        * <code>Finalizable</code> processing will NOT be executed.
        * <p>
        * This must never be called outside of a bracketed pair of
        * {@link #pushScope} and {@link #popScope} calls.
        * <p>
        * This method will honor any pending thread interruption by throwing
        * a {@link StopConditionException} using {@link #honorStopCondition}
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        *
        * @throws   StopConditionException
        *           If the current thread's was previously interrupted.
        */
       private static void iterateWorker(WorkArea wa)
       throws StopConditionException
       {
          if (LOG.isLoggable(Level.FINE))
          {
             log(Level.FINE, "iterateWorker", null);
          }

          // peek at the block def on the top of the stack
          BlockDefinition blk = wa.blocks.peek();

          // transaction related processing
          if (blk.loop && wa.isTransaction())
          {
             // is there a pending rollback or have we rolled back in this scope?
             if (wa.isRollbackPending())
             {
                // determine if a pending rollback refers to this scope
                if (rollbackCurrentScope(wa, wa.getRollbackScope()))
                {
                   processRollback(wa, blk);
                }
             }
             else if (!blk.rolledBack)
             {
                // OK, this is a case where we need to COMMIT!
                processCommit(wa, blk);
             }
          }

          // reset our retry/commit logic
          blk.committed      = false;
          blk.retry          = false;
          blk.loopProtection = true;
          blk.hadPause       = false;
          blk.endkeyRetry    = false;
          blk.pendingBreak   = false;
          blk.wasRetried     = false;
          blk.rolledBack     = false;
          blk.isUndoLevel    = false;
          blk.ilpCount       = 0;

          // notify listeners of the iteration
          if (blk.loop)
          {
             processFinalizables(wa, blk, true);
          }

          try
          {
             // FINALLY block support (this MUST be AFTER commit/rollback and finalizables are processed); at
             // this location a full transaction still appears to be open in FWD but in the 4GL will report
             // TRANSACTION == false for the full-transaction level (sub-transaction levels still show being
             // inside the transaction); this means we must force a save/clear/restore of the transaction state
             // for the duration of the FINALLY block execution
             try
             {
                processTerminationBlock(wa, blk, blk.finallyBlock);
             }
             finally
             {
                blk.finallyBlock = null;
             }
          }
          finally
          {
             wa.handleDeferredError();
          }
       }

       /**
        * Creates or updates the backup set for all objects needing rollback
        * support in the current block, if a transaction is active.  This is
        * split up from {@link #pushScope} and {@link #blockSetup} due to the
        * semantics of Progress in which the backup set is created or refreshed
        * after the loop control variables have been modified.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    blk
        *           The block for which a backup must be made or <code>null</code>
        *           to use the current block on top of the stack.
        */
       private static void backupWorker(WorkArea wa, BlockDefinition blk)
       {
          // read the block def at the top of the stack
          if (blk == null)
          {
             blk = wa.blocks.peek();
          }

          // transactions and sub-transactions trigger backup processing (blk.undoables will be
          // null when a transaction is not active OR when in a NO_TRANSACTION block OR when in
          // a transaction and no changes have been registered)
          if (blk.undoables != null)
          {
             // this is an iteration, update the backup set for the next pass
             updateBackupSet(blk.undoables);
          }
       }

       /**
        * Determine if the current scope represents a transaction or
        * sub-transaction and if so, whether the given level explicitly refers
        * to this scope OR if the given level is -1, then this implicitly refers
        * to this scope.
        * <p>
        * Even if the level is not -1 (it must refer to an enclosing block), if
        * the given block is a full transaction block (the block at which the
        * transaction is scoped), then the rollback must occur at this point
        * instead of deferring the rollback until the transaction is no longer
        * active.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    level
        *           The stack level of the scope to be rolled back.
        *
        * @return   <code>true</code> if the above conditions hold.
        */
       private static boolean rollbackCurrentScope(WorkArea wa, int level)
       {
          // peek at the current block def on the top of the stack
          BlockDefinition blk = wa.blocks.peek();

          return !isNoTransactionLevel(blk.level) &&
                 (level == -1 || level == (wa.blocks.size() - 1) || blk.full);
       }

       /**
        * Find the nearest enclosing block associated with the given label.
        *
        * @param    label
        *           The block label to search for.
        *
        * @return   The block found or <code>null</code> if there is no enclosing
        *           block with that label.
        */
       @SuppressWarnings("unused")
       private static BlockDefinition findBlock(String label)
       {
          if (label != null)
          {
             WorkArea wa = workArea.obtain();

             int sz = wa.blocks.size();

             // walk backwards up the stack
             for (int i = sz - 1; i >= 0; i--)
             {
                BlockDefinition blk = wa.blocks.elementAt(i);

                if (blk.label.equals(label))
                {
                   return blk;
                }
             }
          }

          return null;
       }

       /**
        * Find the stack level of the nearest enclosing block associated with the
        * given label.
        *
        * @param    wa
        *           Context local work area.
        * @param    label
        *           The block label to search for.
        *
        * @return   The 0-based stack level for the block that was found or -1 if
        *           there is no enclosing block with that label.
        */
       private static int findStackLevel(WorkArea wa, String label)
       {
          if (label != null)
          {
             int sz = wa.blocks.size();

             // walk backwards up the stack
             for (int i = sz - 1; i >= 0; i--)
             {
                BlockDefinition blk = wa.blocks.elementAt(i);

                if (blk.label.equals(label))
                {
                   return i;
                }
             }
          }

          return -1;
       }

       /**
        * Worker that actually implements rollback by copying the backed up
        * values into the original reference.  Once all externally driven
        * rollback processing (the {@link Undoable} interface) is done,
        * notifications are provided to all registered {@link Commitable}
        * instances in the current block's commitable list.
        * <p>
        * If the block is also the full transaction block, the last thing
        * done is a single master rollback call to the instance registered
        * for the {@link #registerTransactionCommit}.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    blk
        *           The block to be rolled back.
        */
       private static void processRollback(WorkArea wa, BlockDefinition blk)
       {
          if (blk.commitables == null)
          {
             return;
          }

          if (FwdServerJMX.JMX_DEBUG)
          {
             ROLLBACK.timer(() -> processRollbackImpl(wa, blk));
          }
          else
          {
             processRollbackImpl(wa, blk);
          }
       }

       /**
        * Worker that actually implements rollback by copying the backed up
        * values into the original reference.  Once all externally driven
        * rollback processing (the {@link Undoable} interface) is done,
        * notifications are provided to all registered {@link Commitable}
        * instances in the current block's commitable list.
        * <p>
        * If the block is also the full transaction block, the last thing
        * done is a single master rollback call to the instance registered
        * for the {@link #registerTransactionCommit}.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    blk
        *           The block to be rolled back.
        */
       private static void processRollbackImpl(WorkArea wa, BlockDefinition blk)
       {
          if (LOG.isLoggable(Level.FINE))
          {
             log(Level.FINE, "processRollback", null);
          }

          // mark the start of the critical section
          CriticalSectionManager.beginSection();

          try
          {
             try
             {
                wa.nowIterating = blk.commitables;

                // notify commitables
                ArrayList<Commitable> commitables = blk.commitables;
                for (int i = 0; i < commitables.size(); i++)
                {
                   Commitable target = commitables.get(i);
                   try
                   {
                      target.rollback(blk.full);
                   }
                   catch (Throwable thr)
                   {
                      deferError(wa, "rollback", thr, false);
                   }
                }
             }
             finally
             {
                wa.nowIterating = null;
             }

             // process the master rollback
             if (blk.full)
             {
                wa.notifyMasterCommit(true);
             }

             // mark the work area to note that rollback has been completed, which
             // clears the deferred rollback state;  subsequent calls to the
             // isRollbackPending() method will return false.
             wa.rollbackScope   = -1;
             wa.rollbackPending = false;
             wa.rollbackPendingCause = null;

             // process the externally driven undo
             if (blk.undoables != null)
             {
                ArrayList<UndoablePair> undoables = blk.undoables;
                for (int i = 0; i < undoables.size(); i++)
                {
                   UndoablePair next = undoables.get(i);
                   next.src.rollback(next.copy);
                }
             }
          }
          finally
          {
             // stop the critical section
             CriticalSectionManager.endSection();
          }
       }

       /**
        * Worker that notifies all objects in the commit list that they must
        * commit (persist any state).
        * <p>
        * On the last iteration of a loop or block which ends in success (in
        * which the flow of control reaches the natural end of the block), both
        * will be called in succession.  However, other forms of exit which are
        * still considered successful (i.e. return and break which are not the
        * result of condition processing) will only execute <code>popScope</code>
        * and <code>iterate</code> will be bypassed.
        * <p>
        * It is important to note that the list of commitables in blk is
        * iterated in reverse order from that in which they are registered with
        * the <code>TransactionManager</code>.  This is done to ensure that
        * database transactions (which are registered before their corresponding
        * buffers) are always committed after all buffers are committed.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    blk
        *           The scope to be committed.
        */
       private static void processCommit(WorkArea wa, BlockDefinition blk)
       {
          if (blk.commitables == null)
          {
             return;
          }

          if (FwdServerJMX.JMX_DEBUG)
          {
             COMMIT.timer(() -> processCommitImpl(wa, blk));
          }
          else
          {
             processCommitImpl(wa, blk);
          }
       }

       /**
        * Worker that notifies all objects in the commit list that they must
        * commit (persist any state).
        * <p>
        * On the last iteration of a loop or block which ends in success (in
        * which the flow of control reaches the natural end of the block), both
        * will be called in succession.  However, other forms of exit which are
        * still considered successful (i.e. return and break which are not the
        * result of condition processing) will only execute <code>popScope</code>
        * and <code>iterate</code> will be bypassed.
        * <p>
        * It is important to note that the list of commitables in blk is
        * iterated in reverse order from that in which they are registered with
        * the <code>TransactionManager</code>.  This is done to ensure that
        * database transactions (which are registered before their corresponding
        * buffers) are always committed after all buffers are committed.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    blk
        *           The scope to be committed.
        */
       private static void processCommitImpl(WorkArea wa, BlockDefinition blk)
       {
          if (LOG.isLoggable(Level.FINE))
          {
             log(Level.FINE, "processCommit", null);
          }

          if (blk.committed || !wa.processUndoables(blk))
          {
             // already done or nothing to do, just return
             return;
          }

          try
          {
             wa.nowIterating = blk.commitables;

             ArrayList<Commitable> commitables = blk.commitables;
             for (int i = 0; i < commitables.size(); i++)
             {
                Commitable target = commitables.get(i);
                try
                {
                   target.commit(blk.full);
                }
                catch (Throwable thr)
                {
                   deferError(wa, "commit", thr, false);
                }
             }
          }
          finally
          {
             wa.nowIterating = null;
          }

          // process the master commit
          if (blk.full)
          {
             wa.notifyMasterCommit(false);
          }

          // remember that we have already handled the commit
          blk.committed = true;
       }

       /**
        * Worker that notifies all objects in the finalize list that the block
        * is being exited or iterated.
        *
        * @param    wa
        *           Context-local work area.
        * @param    blk
        *           The scope to be finalized.
        * @param    iterate
        *           <code>true</code> to call each target's <code>iterate</code>
        *           method or <code>false</code> to call <code>finished</code>.
        */
       private static void processFinalizables(WorkArea        wa,
                                               BlockDefinition blk,
                                               boolean         iterate)
       {
          if (LOG.isLoggable(Level.FINE))
          {
             log(Level.FINE,
                 "processFinalizables",
                 "iterate = " + Boolean.toString(iterate));
          }

          Object prevReferent = wa.pm.getProcessedProcedure();
          Set<Finalizable>[] fini = blk.finalizables;

          if (fini != null)
          {
             Object referent = null;
             boolean delayed = false;
             // can not call for start, as the procedure is not yet initialized
             if (!iterate && isTopLevelBlock(wa) && !isGlobalBlock(wa))
             {
                referent = wa.pm._thisProcedure();
                wa.pm.setProcessedProcedure(referent);

                if (getNearestTopLevelType(wa) == EXTERNAL_PROC)
                {
                   delayed = wa.pm.isThisProcedurePersistent() && !wa.pm._isDelayedDelete(referent);
                }
             }

             try
             {
                wa.nowIterating = fini;
                wa.op = iterate ? OP_ITERATE : OP_FINISHED;

                for (int i = 0; i < fini.length; i++)
                {
                   Set<Finalizable> l = fini[i];
                   if (l == null)
                   {
                      continue;
                   }

                   for (Finalizable target : l)
                   {
                      try
                      {
                         if (iterate)
                         {
                            target.iterate();
                         }
                         else
                         {
                            target.finished();

                            if (delayed)
                            {
                               wa.pm.addFinalizable(referent, target);
                            }
                            else
                            {
                               target.deleted();
                            }
                         }
                      }
                      catch (Throwable thr)
                      {
                         deferError(wa, (iterate ? "iterate" : "finished"), thr, false);
                      }
                   }
                }
             }

             finally
             {
                wa.nowIterating = null;
                wa.pm.setProcessedProcedure(prevReferent);
                wa.op = OP_NONE;
             }
          }

          // process the master finish/iterate
          if (blk.full)
          {
             wa.notifyMasterFinish(iterate);
          }
       }

       /**
        * Worker that notifies all objects in the finalize list that the block is being entered
        * (the {@code Finalizable.entry()} method will be called.  No persistent changes to the
        * transaction or block state will occur.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    blk
        *           The scope to be retried.
        */
       private static void processEntry(WorkArea wa, BlockDefinition blk)
       {
          if (LOG.isLoggable(Level.FINE))
          {
             log(Level.FINE, "processEntry", null);
          }

          Set<Finalizable>[] fini = blk.finalizables;
          if (fini != null)
          {
             try
             {
                wa.nowIterating = fini;
                wa.op = OP_ENTRY;

                for (int i = 0; i < fini.length; i++)
                {
                   Set<Finalizable> l = fini[i];

                   if (l == null)
                   {
                      continue;
                   }

                   for (Finalizable target : l)
                   {
                      try
                      {
                         target.entry();
                      }
                      catch (Throwable thr)
                      {
                         deferError(wa, "entry", thr, false);
                      }
                   }
                }
             }

             finally
             {
                wa.nowIterating = null;
                wa.op = OP_NONE;
             }
          }
       }

       /**
        * Worker that notifies all objects in the finalize list that the block
        * is being retried.  In addition, any pending rollback for this block
        * is honored here.
        *
        * @param    wa
        *           The {@link WorkArea} instance.
        * @param    blk
        *           The scope to be retried.
        */
       private static void processRetry(WorkArea wa, BlockDefinition blk)
       {
          if (LOG.isLoggable(Level.FINE))
          {
             log(Level.FINE, "processRetry", null);
          }

          // transaction related processing
          if (wa.isTransaction())
          {
             // is there a pending rollback or have we rolled back in this scope?
             if (wa.isRollbackPending())
             {
                // determine if a pending rollback refers to this scope
                if (rollbackCurrentScope(wa, wa.getRollbackScope()))
                {
                   // note that it is good that this method does NOT set the
                   // blk.rolledBack flag as otherwise a commit could not occur
                   // at the end of this retry iteration
                   processRollback(wa, blk);
                }
             }
          }

          Set<Finalizable>[] fini = blk.finalizables;
          if (fini != null)
          {
             try
             {
                wa.nowIterating = fini;
                wa.op = OP_RETRY;

                for (int i = 0; i < fini.length; i++)
                {
                   Set<Finalizable> l = fini[i];

                   if (l == null)
                   {
                      continue;
                   }

                   for (Finalizable target : l)
                   {
                      try
                      {
                         target.retry();
                      }
                      catch (Throwable thr)
                      {
                         deferError(wa, "retry", thr, false);
                      }
                   }
                }
             }

             finally
             {
                wa.nowIterating = null;
                wa.op = OP_NONE;
             }
          }

          // process the master retry
          if (blk.full)
          {
             wa.notifyMasterRetry();
          }

          try
          {
             // FINALLY block support (this MUST be AFTER commit/rollback and finalizables are processed); at
             // this location a full transaction still appears to be open in FWD but in the 4GL will report
             // TRANSACTION == false for the full-transaction level (sub-transaction levels still show being
             // inside the transaction); this means we must force a save/clear/restore of the transaction state
             // for the duration of the FINALLY block execution
             try
             {
                processTerminationBlock(wa, blk, blk.finallyBlock);
             }
             finally
             {
                blk.finallyBlock = null;
             }
          }
          finally
          {
             wa.handleDeferredError();
          }
       }

       /**
        * Get the BlockDefinition instance of the nearest block of one of the
        * following types:
        * <ul>
        *    <li>{@link BlockType#EXTERNAL_PROC external procedure}</li>
        *    <li>{@link BlockType#INTERNAL_PROC internal procedure}</li>
        *    <li>{@link BlockType#FUNCTION user-defined function}</li>
        *    <li>{@link BlockType#TRIGGER trigger}</li>
        *    <li>{@link BlockType#DATABASETRIGGER trigger}</li>
        *    <li>{@link BlockType#EDITING editing}</li>
        * </ul>
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        *
        * @return   See above.
        */
       private static BlockDefinition nearestExecutingBlock(WorkArea wa)
       {
          Stack<BlockDefinition>        blocks = wa.blocks;
          ListIterator<BlockDefinition> iter   = blocks.listIterator(blocks.size());

          // walk backwards up the stack of block definitions
          while (iter.hasPrevious())
          {
             BlockDefinition blk = iter.previous();

             if (blk.type == EXTERNAL_PROC ||
                 blk.type == INTERNAL_PROC ||
                 blk.type == FUNCTION      ||
                 blk.type == TRIGGER       ||
                 blk.type == EDITING       ||
                 blk.type == DATABASETRIGGER)
             {
                // done!
                return blk;
             }
          }

          return null;
       }

       /**
        * Deregister the output parameter assigner for the current block, if any.
        * It is expected that only the {@link BlockManager} will invoke this
        * method.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        *
        * @return  Output parameter assigner for this block, or <code>null</code>
        *          if none was registered.
        */
       private static OutputParameterAssigner deregisterOutputParameterAssigner(WorkArea wa)
       {
          // peek at the current block def on the top of the stack
          BlockDefinition blk = wa.blocks.peek();

          OutputParameterAssigner opa = blk.outputParmAssigner;

          blk.outputParmAssigner = null;

          return opa;
       }

       /**
        * Adds the given object into the list of objects which require undo
        * notifications for the next block that is opened.
        * <p>
        * This form of registration is useful for objects whose natural lifetime
        * is scoped to the top-level block.  In particular, it is necessary for
        * objects that are constructed during a class' constructor/initializer
        * (before the external procedure block has opened).  Such objects cannot
        * simply register since their construction is not actually enclosed by the
        * block.
        * <p>
        * When the deferred registration occurs, it will NOT be global and will
        * NOT be added to registered Commitables.
        * <p>
        * This notification will occur whether or not a transaction is active.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed.
        * <p>
        * The given object will only be added if it is not already in the list.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    target
        *           The object references to be registered.  This cannot be
        *           <code>null</code>.
        */
       private static void registerNext(WorkArea wa, LazyUndoable... target)
       {
          registerAt(wa, ScopeLevel.NEXT, target);
       }

       /**
        * Adds the given object into the list of objects which require undo
        * notifications for the next block that is opened.
        * <p>
        * This form of registration is useful for objects whose natural lifetime
        * is scoped to the top-level block.  In particular, it is necessary for
        * objects that are constructed during a class' constructor/initializer
        * (before the external procedure block has opened).  Such objects cannot
        * simply register since their construction is not actually enclosed by the
        * block.
        * <p>
        * When the deferred registration occurs, it will NOT be global and will
        * NOT be added to registered Commitables.
        * <p>
        * This notification will occur whether or not a transaction is active.
        * <p>
        * These registrations naturally are removed when the associated scope
        * is removed.
        * <p>
        * The given object will only be added if it is not already in the list.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    scope
        *           The scope where to register the undoable targets.
        * @param    target
        *           The object references to be registered.  This cannot be
        *           <code>null</code>.
        */
       private static void registerAt(WorkArea wa, ScopeLevel scope, LazyUndoable... target)
       {
          if (wa.deferredUndoables == null)
          {
             wa.deferredUndoables = new IdentityHashMap<>();
          }

          // prevent ConcurrentModificationException
          if (wa.nowIterating == wa.deferredUndoables)
          {
             if (LOG.isLoggable(Level.WARNING))
             {
                log(Level.WARNING,
                    "registerNext",
                    "Illegal attempt to register Undoable",
                    new Throwable());
             }

             return;
          }

          if (target.length == 0)
          {
             if (wa.deferredDynExt == null)
             {
                wa.deferredDynExt = Collections.newSetFromMap(new IdentityHashMap<>());
             }

             wa.deferredDynExt.add((BaseDataType[]) target);
          }
          else
          {
             boolean global = (scope == ScopeLevel.GLOBAL);

             for (LazyUndoable u : target)
             {
                wa.deferredUndoables.put(u, null);

                if (wa.trackUndoables)
                {
                   if (global)
                   {
                      u.setGlobal(true);
                   }
                   else
                   {
                      wa.trackedUndoables.put(u, null);
                   }
                }
             }
          }
       }

       /**
        * Register the specified targets as undoables.  This will mark the object as undoable and save
        * the current tx level, regardless if a tx is active or not.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    undoTargets
        *           List of object references to be registered.  This cannot be
        *           <code>null</code>.
        */
       private static void registerCurrent(WorkArea wa, LazyUndoable... undoTargets)
       {
          boolean persistent = wa.blocks.peek().external && wa.pm.isThisProcedurePersistent();

          registerAt(wa, wa.txNestingLevel, persistent, undoTargets);
       }

       /**
        * Adds the object into a scoped dictionary of objects requiring UNDO support. This registry
        * is used to backup the objects at the beginning of transactions or sub-transactions.
        * <p>
        * These registrations naturally are removed when the associated scope is removed. However,
        * the global scope is long-lived. Special care is necessary to prevent the same instance of
        * global shared variables being added multiple times.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    transLevel
        *           The transaction level to set for this undoable.
        * @param    persistent
        *           Flag indicating if the undoable is defined in a persistent procedure
        * @param    undoTargets
        *           The object references to be rolled back on failure.  This
        *           cannot be <code>null</code>.
        */
       private static void registerAt(WorkArea        wa,
                                      int             transLevel,
                                      boolean         persistent,
                                      LazyUndoable... undoTargets)
       {
          registerAt(wa, transLevel, persistent, null, undoTargets);
       }

       /**
        * Adds the object into a scoped dictionary of objects requiring UNDO support. This registry
        * is used to backup the objects at the beginning of transactions or sub-transactions.
        * <p>
        * These registrations naturally are removed when the associated scope is removed. However,
        * the global scope is long-lived. Special care is necessary to prevent the same instance of
        * global shared variables being added multiple times.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    transLevel
        *           The transaction level to set for this undoable.
        * @param    persistent
        *           Flag indicating if the undoable is defined in a persistent procedure
        * @param    When not-null, persistent must be true and the referent represents the instance where to look
        *           for undoables.  Otherwise, THIS-PROCEDURE will be used.
        * @param    undoTargets
        *           The object references to be rolled back on failure.  This
        *           cannot be <code>null</code>.
        */
       private static void registerAt(WorkArea        wa,
                                      int             transLevel,
                                      boolean         persistent,
                                      Object          referent,
                                      LazyUndoable... undoTargets)
       {
          for (LazyUndoable undoTarget : undoTargets)
          {
             registerUndo(undoTarget, transLevel, persistent);
          }

          if (persistent)
          {
             if (referent == null)
             {
                referent = wa.pm._thisProcedure();
             }
             IdentityHashMap<LazyUndoable, Object> pundoables = wa.procUndoables.get(referent);
             if (pundoables == null)
             {
                wa.procUndoables.put(referent, pundoables = new IdentityHashMap<>());
             }

             // if this is an external block and this is a persistent procedure, then collect these
             for (LazyUndoable target : undoTargets)
             {
                pundoables.put(target, null);
             }
          }
       }

       /**
        * Enable tracking of all the undaobles defined in the next instantiating external program.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        */
       private static void trackUndoables(WorkArea wa)
       {
          wa.trackUndoables = true;
          wa.trackedUndoables = new IdentityHashMap<>();
       }

       /**
        * Untrack (by removing from all the tx blocks) all the undoables defined and registered for
        * the specified external program.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    referent
        *           The external program instance.
        */
       private static void untrackUndoables(WorkArea wa, Object referent)
       {
          IdentityHashMap<LazyUndoable, Object> pundoables = wa.procUndoables.remove(referent);

          if (pundoables == null)
          {
             return;
          }

          // this is called when a persistent procedure gets deleted - once that happens, there is nothing left
          // in the procedure's state which can be undoable

          deregister(wa, pundoables);
       }

       /**
        * Rollback the transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    chained
        *           Flag indicating if a new transaction will be started.
        */
       private static void rollbackTx(WorkArea wa, boolean chained)
       {
          BlockDefinition blk = wa.blocks.get(APPSERVER_ROOT_BLOCK_INDEX);

          try
          {
             processRollback(wa, blk);
          }
          finally
          {
             endTx(wa, blk);

             if (chained)
             {
                beginTx(wa, blk);
             }
          }
       }

       /**
        * Commit the transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
        *
        * @param    wa
        *           Context local work area. Must not be {@code null}.
        * @param    chained
        *           Flag indicating if a new transaction will be started.
        */
       private static void commitTx(WorkArea wa, boolean chained)
       {
          BlockDefinition blk = wa.blocks.get(APPSERVER_ROOT_BLOCK_INDEX);

          try
          {
             processCommit(wa, blk);
          }
          finally
          {
             endTx(wa, blk);

             if (chained)
             {
                beginTx(wa, blk);
             }
          }
       }

       /**
        * Begin an explicit transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
        * <p>
        * This explicitly marks this tx as being open by the
        * {@link TransactionResource#getTxInitProcedure() TRANS-INIT-PROCEDURE}.
        *
        * @param    wa
        *           The current {@link WorkArea} instance.  When <code>null</code>, resolve it.
        * @param    blk
        *           The {@link #APPSERVER_ROOT_BLOCK_INDEX root block}.  When <code>null</code>,
        *           resolve it.
        */
       private static void beginTx(WorkArea wa, BlockDefinition blk)
       {
          if (wa == null)
          {
             wa = workArea.obtain();
          }
          if (blk == null)
          {
             blk = wa.blocks.get(APPSERVER_ROOT_BLOCK_INDEX);
          }

          TransactionImpl.setOpenByTxInitProc(true);

          blk.committed = false;
          blk.rolledBack = false;
          blk.full = true;
          blk.level = TRANSACTION;

          // there is only one level (the root one)
          wa.setTransactionLevel(0);

          // this is set only for the appserver target's block scope, but nothing else is done
          wa.txNestingLevel = wa.blocks.peek().level > 0 ? 1 : 0;
          blk.txNestingLevel = wa.txNestingLevel;

          // we need to notify the BufferManager that a tx is active
          BufferManager.get().beginTx(true, true, APPSERVER_ROOT_BLOCK_INDEX + 1);
       }

       /**
        * End an explicit transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
        *
        * @param    wa
        *           The current {@link WorkArea} instance.  When <code>null</code>, resolve it.
        * @param    blk
        *           The {@link #APPSERVER_ROOT_BLOCK_INDEX root block}.  When <code>null</code>,
        *           resolve it.
        */
       private static void endTx(WorkArea wa, BlockDefinition blk)
       {
          if (wa == null)
          {
             wa = workArea.obtain();
          }
          if (blk == null)
          {
             blk = wa.blocks.get(APPSERVER_ROOT_BLOCK_INDEX);
          }

          try
          {
             BufferManager bufMan = BufferManager.get();
             bufMan.endTx(true, true);
             bufMan.endTxPost();
          }
          finally
          {
             blk.full = false;
             blk.level = NO_TRANSACTION;
             blk.txNestingLevel = -1;

             wa.setTransactionLevel(-1);
             wa.txNestingLevel = -1;

             TransactionImpl.setOpenByTxInitProc(false);
          }
       }

       /**
        * Worker that notifies all objects in the scopeable list that a block
        * scope is being started or ended.
        *
        * @param    wa
        *           Context-local work area.
        * @param    blk
        *           The scope to be finalized.
        * @param    start
        *           If <code>true</code> generate a start scope notification,
        *           otherwise generate an end scope notification.
        */
       private static void processScopeNotifications(WorkArea wa, BlockDefinition blk, boolean start)
       {
          if (LOG.isLoggable(Level.FINE))
          {
             log(Level.FINE, "processScopeNotifications", "start = " + start);
          }

          if (wa.pendingScopeables != null)
          {
             for (int i = 0; i < wa.pendingScopeables.length; i++)
             {
                Scopeable target = wa.pendingScopeables[i];
                if (target != null)
                {
                   registerScopeable(wa, blk, target, false);
                }
             }
             wa.pendingScopeables = null;
          }

          // check scopeables inherited from previous block
          BlockDefinition prev = getBlock(wa, 1);
          int accumMgrId = ScopeId.ACCUMULATOR_MANAGER.ordinal();
          if (!blk.topLevel                                               &&
              prev != null                                                &&
              prev.scopeList != null                                      &&
              prev.scopeList[accumMgrId] != null                          &&
              (blk.scopeList == null || blk.scopeList[accumMgrId] == null))
          {
             registerScopeable(wa, blk, AccumulatorManager.getInstance(), false);
          }

          Object prevReferent = wa.pm.getProcessedProcedure();

          try
          {
             if (!(blk.topLevel || blk.scopeList != null))
             {
                // nothing to notify
                return;
             }

             Object referent;
             boolean delayed;
             // can not call for start, as the procedure is not yet initialized
             if (!start && blk.topLevel && !isGlobalBlock(wa))
             {
                referent = wa.pm._thisProcedure();
                wa.pm.setProcessedProcedure(referent);

                delayed = getNearestTopLevelType(wa) == EXTERNAL_PROC &&
                          wa.pm.isThisProcedurePersistent()           &&
                          !wa.pm._isDelayedDelete(referent);
             }
             else
             {
                delayed = false;
                referent = null;
             }

             if (start && blk.topLevel)
             {
                // procedure scopes need to be opened first
                wa.procScopeable.scopeStart(blk);
             }

             if (blk.scopeList != null)
             {
                wa.nowIterating = blk.scopeList;
                for (int i = 0; i < blk.scopeList.length; i++)
                {
                   Scopeable target = blk.scopeList[i];
                   if (target == null)
                   {
                      continue;
                   }

                   try
                   {
                      if (start)
                      {
                         target.scopeStart(blk);
                      }
                      else
                      {
                         target.scopeFinished();

                         if (delayed)
                         {
                            wa.pm.addScopeable(referent, target);
                         }
                         else
                         {
                            target.scopeDeleted();
                         }
                      }
                   }

                   catch (Throwable thr)
                   {
                      deferError(wa, (start ? "scopeStart" : "scopeFinished"), thr, false);
                   }
                }
             }

             if (!start && blk.topLevel)
             {
                // procedure scopes need to be finished last
                wa.procScopeable.scopeFinished();
             }
          }

          finally
          {
             wa.nowIterating = null;
             wa.pm.setProcessedProcedure(prevReferent);
          }
       }

       /**
        * Walk through the current backup set and refresh the stored value for
        * each one.  This processing corresponds to the end of a sub-transaction
        * (see {@link #blockSetup}).
        */
       private static void updateBackupSet(List<UndoablePair> backup)
       {
          for (UndoablePair undoPair : backup)
          {
             if (undoPair.src.changed())
             {
                undoPair.copy = (LazyUndoable) undoPair.src.deepCopy();
             }
          }
       }

       /**
        * Reports if the current block has the given property.
        *
        * @param    wa
        *           Context local work area.  Must not be {@code null}.
        * @param    prop
        *           The property bit flag to test.  Should be one of the constants
        *           <code>PROP_ERROR, PROP_ENDKEY, PROP_STOP or PROP_QUIT</code>.
        *
        * @return   <code>true</code> if the current block has the given
        *           property bit set in its properties bitmap.
        */
       private static boolean hasProperty(WorkArea wa, int prop)
       {
          BlockDefinition blk = wa.blocks.peek();
          return (blk.props & prop) != 0;
       }

       /**
        * Write a formatted log entry with the error text and some state based
        * on the current block.
        *
        * @param    lvl
        *           The logging level.
        * @param    location
        *           The event or call site from which the log entry was
        *           generated.
        * @param    txt
        *           The text to insert into the log.  May be <code>null</code>.
        */
       private static void log(Level           lvl,
                               String          location,
                               String          txt)
       {
          log(lvl, location, txt, null);
       }

       /**
        * Write a formatted log entry with the error text and some state based
        * on the current block.
        *
        * @param    lvl
        *           The logging level.
        * @param    location
        *           The event or call site from which the log entry was
        *           generated.
        * @param    txt
        *           The text to insert into the log.  May be <code>null</code>.
        * @param    trw
        *           The throwable that caused this logging to occur or
        *           <code>null</code> if no exception is present.
        */
       private static void log(Level     lvl,
                               String    location,
                               String    txt,
                               Throwable trw)
       {
          if (LOG.isLoggable(lvl))
          {
             WorkArea        wa  = workArea.obtain();
             BlockDefinition blk = null;

             if (wa.depth() > 0)
             {
                blk = wa.blocks.peek();
             }

             String err = CentralLogger.generate("%s %s %s",
                                                 wa.toString(),
                                                 (blk == null ? "" : blk.toString()),
                                                 (txt == null ? "" : txt));

             if (trw == null)
             {
                LOG.logp(lvl, "TransactionManager." + location, "", err);
             }
             else
             {
                LOG.logp(lvl, "TransactionManager." + location, "", err, trw);
             }
          }
       }

       /**
        * Helper to expose transaction state in a way that avoids context local lookups.
        */
       public static class TransactionHelper
       {
          /** Quick access to context-local error helper. */
          public final ErrorManager.ErrorHelper errHlp;

          /** 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 TransactionHelper(WorkArea wa)
          {
             this.wa     = wa;
             this.errHlp = wa.em;
          }

          /**
           * Get the current {@link WorkArea#nowIterating} instance.
           *
           * @return   See above.
           */
          public Object getNowIterating()
          {
             return wa.nowIterating;
          }

          /**
           * Set the current {@link WorkArea#nowIterating} instance.
           *
           * @param    nowIterating
           *           The instance (saved via {@link #getNowIterating()} or <code>null</code>) to restore.
           */
          public void setNowIterating(Object nowIterating)
          {
             wa.nowIterating = nowIterating;
          }

          /**
           * Get the {@link WorkArea#pendingScopeables} list.
           *
           * @return   See above.
           */
          public Scopeable[] getPendingScopeables()
          {
             return wa.pendingScopeables == null
                      ? null
                      : Arrays.copyOf(wa.pendingScopeables, wa.pendingScopeables.length);
          }

          /**
           * Set the {@link WorkArea#pendingScopeables}.
           *
           * @param    scopeables
           *           The pending scopeables.
           */
          public void setPendingScopeables(Scopeable[] scopeables)
          {
             if (scopeables == null)
             {
                wa.pendingScopeables = null;
                return;
             }

             if (wa.pendingScopeables == null)
             {
                wa.pendingScopeables = new Scopeable[ScopeId.values().length];
             }
             for (int i = 0; i < scopeables.length; i++)
             {
                wa.pendingScopeables[i] = scopeables[i];
             }
          }

          /**
           * Check whether the transaction id should be set for a database.
           *
           * @param   db
           *          the logical name of the database (The {@code getId()} of the {@code Database}).
           */
          public void checkTransaction(String db)
          {
             if (!wa.isTransaction())
             {
                // no transaction active? return unknown value
                return;
             }

             SecurityManager sm = SecurityManager.getInstance();
             if (sm == null)
             {
                if (LOG.isLoggable(Level.WARNING))
                {
                   LOG.log(Level.WARNING, "Failed to obtain the session Id");
                }
                return;
             }
             Integer userId = sm.sessionSm.getSessionId();
             if (userId == null)
             {
                if (LOG.isLoggable(Level.WARNING))
                {
                   LOG.log(Level.WARNING, "Failed to obtain the session Id");
                }
                return;
             }

             DbTransactions dbData;
             synchronized (transactionData)
             {
                dbData = transactionData.get(db);
                if (dbData == null)
                {
                   if (LOG.isLoggable(Level.WARNING))
                   {
                      LOG.log(Level.WARNING,
                              "Database " + db +
                              " was not found in the list of active databases. Adding it now.");
                   }

                   // first access to this database?
                   dbData = new DbTransactions();
                   transactionData.put(db, dbData);
                   if (TransactionTableUpdater.isEnabled())
                   {
                      updaters.put(db, new TransactionTableUpdater());
                   }
                }
             }

             synchronized (dbData)
             {
                // if transaction for this used has not been set, increment and add now
                if (!dbData.transactions.containsKey(userId))
                {
                   int tId = ++dbData.lastTransactionId;
                   if (tId < 0) // wrapping around from 1 , not Integer.MIN_VALUE
                   {
                      tId = 1;
                   }
                   dbData.transactions.put(userId, tId);
                   dbData.isDirty = true;
                }
             }
          }

          /**
           * Reports on the current <code>Finalizable</code> operation being
           * executed.
           *
           * @return   One of the following:
           *           <ul>
           *              <li> <code>OP_NONE</code>
           *              <li> <code>OP_ITERATE</code>
           *              <li> <code>OP_RETRY</code>
           *              <li> <code>OP_FINISHED</code>
           *           </ul>
           */
          public int currentOperation()
          {
             return wa.op;
          }

          /**
           * Register StopAfterTimer to a ContextLocal WorkArea.
           *
           * @param    stopAfterTimer
           *           StopAfterTimer to register.
           */
          public void registerStopAfterTimer(StopAfterTimer stopAfterTimer)
          {
             TransactionManager.registerStopAfterTimer(wa, stopAfterTimer);
          }

          /**
           * Adds the given object into the list of objects for the current block
           * which require commit notification.  This form of registration is
           * useful for objects whose natural lifetime are scoped to a different
           * block than the block in which they must be committed.  For example,
           * if a global or shared resource needs to be committed in a specific
           * scope, then this can be used in cooperation with the normal
           * {@link #registerCurrent} processing to control the commit points.
           * <p>
           * This notification will ONLY occur when there is an active transaction.
           * <p>
           * Notifications occur in LIFO order;  thus, commitables which depend upon
           * others being committed first must be registered before the objects upon
           * which they are dependent.
           * <p>
           * These registrations naturally are removed when the associated scope
           * is removed.
           *
           * @param    target
           *           The object reference to be committed on success.  This
           *           cannot be <code>null</code>.
           */
          public void registerCommit(Commitable target)
          {
             TransactionManager.registerCommitAt(wa, wa.blocks.size() - 1, target);
          }

          /**
           * Adds the given object into the list of objects which require commit notification for the
           * block at the specified depth  This form of registration is useful for objects whose natural
           * lifetime are scoped to a different block than the block in which they must be committed.
           * For example, if a global or shared resource needs to be committed in a specific scope,
           * then this can be used in cooperation with the normal {@link #registerCurrent} processing to
           * control the commit points.
           * <p>
           * This notification will ONLY occur when there is an active transaction.
           * <p>
           * Notifications occur in LIFO order;  thus, commitables which depend upon
           * others being committed first must be registered before the objects upon
           * which they are dependent.
           * <p>
           * These registrations naturally are removed when the associated scope
           * is removed.
           *
           * @param    blockDepth
           *           Zero-based depth of the target block, starting from the outermost block.
           * @param    target
           *           The object reference to be committed on success.  This cannot be
           *           <code>null</code>.
           */
          public void registerCommitAt(int blockDepth, Commitable target)
          {
             TransactionManager.registerCommitAt(wa, blockDepth, target);
          }

          /**
           * Adds the given object as the transaction level reference which will get
           * a single, "master" commit call after all other commits have been
           * processed at the full transaction level.  This will never be called
           * for sub-transaction commits, nor will it be called if the transaction
           * is abnormally exited.
           * <p>
           * This notification will ONLY occur when there is an active transaction
           * and it only will be called at the exit of the block that corresponds
           * to the full transaction.
           * <p>
           * If the full transaction block is also an iterating block, this
           * notification will occur for each iteration of the block.
           *
           * @param    target
           *           The object reference to be committed on success.  This
           *           can be <code>null</code> if one intends to clear the current
           *           reference.
           * @param    remove
           *           If <code>true</code> automatically remove this registration
           *           after the full transaction scope has ended (whether or not
           *           the commit occurs).  If <code>false</code>, this reference
           *           will remain globally until replaced.
           */
          public void registerTransactionCommit(Commitable target, boolean remove)
          {
             if (wa.masterCommit == null)
             {
                wa.masterCommit = new LinkedHashMap<>();
             }
             wa.masterCommit.put(target, remove ? Boolean.TRUE : Boolean.FALSE);
          }

          /**
           * Register the given object as a handler which can veto the processing of
           * STOP condition processing at the current scope.  Handlers are invoked
           * in FIFO order.  Any handler can issue a veto, which forces the STOP
           * condition to be handled at a higher scope, if at all.
           *
           * @param   h
           *          The stop condition veto handler.
           */
          public void registerStopVetoHandler(StopConditionVetoHandler h)
          {
             wa.stopHandlers.add(h);
          }

          /**
           * Registers the instance for scope notifications at the current block.
           * <p>
           * This will call {@link Scopeable#scopeStart} to notify for the scope processing.
           *
           * @param    target
           *           The scopeable instance.
           */
          public void registerBlockScopeable(Scopeable target)
          {
             TransactionManager.registerBlockScopeable(wa, target);
          }

          /**
           * Register the target scopeable as pending, to be added to the next block.
           *
           * @param    target
           *           The scopeable instance.
           */
          public void registerPendingScopeable(Scopeable target)
          {
             TransactionManager.registerPendingScopeable(wa, target);
          }

          /**
           * Registers the instance for scope notifications at the nearest {@link #findNearestTopLevel() top level}
           * block.
           * <p>
           * WARNING: this must be called only for instances which require notifications only for
           * top-level blocks, as this will registered the instance at the nearest top-level block and call
           * {@link Scopeable#scopeStart} to emulate as if it was called when the top-level block started.
           *
           * @param    target
           *           The scopeable instance.
           */
          public void registerTopLevelScopeable(Scopeable target)
          {
             BlockDefinition block = TransactionManager.nearestTopLevel(wa);
             registerScopeable(wa, block, target, true);
          }

          /**
           * Search the stack of scopes and return the first top level block found.
           *
           * @return   The nearest enclosing top level block or <code>null</code> if there is no block marked as a
           *           top level block.
           */
          public BlockDefinition nearestTopLevel()
          {
             return TransactionManager.nearestTopLevel(wa);
          }

          /**
           * Search the stack of scopes and return the previous top level block.
           *
           * @return   The previous enclosing top level block or <code>null</code> if there is no block marked as
           *           a top level block after the nearest top-level block.
           */
          public BlockDefinition prevTopLevel()
          {
             Stack<BlockDefinition> blocks = wa.blocks;
             boolean found = false;
             for (int idx = blocks.size() - 1; idx >= 0; idx--)
             {
                BlockDefinition blk = blocks.get(idx);

                if (blk.topLevel)
                {
                   if (found)
                   {
                      return blk;
                   }

                   found = true;
                }
             }

             return null;

          }

          /**
           * Register the given <code>Resettable</code> object in order to reset it
           * at specific points of block processing.
           *
           * @param resettable
           *        <code>Resettable</code> to be registered.
           */
          public void registerResettable(Resettable resettable)
          {
             wa.resettables.add(resettable);
          }

          /**
           * Adds the given object into the list of objects for the specified block
           * which require iterate/retry/finish notification.  This form of
           * registration is useful for objects whose natural lifetime is scoped to
           * a block and which need a hook for cleaning up resources or handling
           * other block termination processing.
           * <p>
           * This notification will occur whether or not a transaction is active.
           * <p>
           * These registrations naturally are removed when the associated scope
           * is removed (after the {@link Finalizable#finished} method is called)
           * in the case of a non-global scope OR at the moment that the last
           * scope has been removed in the case of the global scope.
           * <p>
           * The given object will only be added if it is not already in the list.
           * <p>
           * Finalizables for a scope are processed in the same order as they were
           * registered.
           *
           * @param    target
           *           The object reference to be notified on any exit from the
           *           block specified.  This cannot be <code>null</code>.
           * @param    global
           *           Specifies the target scope. <code>false</code> specifies the
           *           current scope, and <code>true</code> specifies the global
           *           scope.
           */
          public void registerFinalizable(Finalizable target, boolean global)
          {
             int blockDepth = global ? 0 : wa.blocks.size() - 1;
             TransactionManager.registerFinalizableAt(wa, blockDepth, target);
          }

          /**
           * Remove the given finalizable from the global list of finalizables.
           *
           * @param    target
           *           Finalizable to be removed from the global list.
           */
          public void deregisterGlobalFinalizable(Finalizable target)
          {
             TransactionManager.deregisterGlobalFinalizable(wa, target);
          }

          /**
           * Adds the given object into the list of objects for the specified block
           * which require iterate/retry/finish notification.  This form of
           * registration is useful for objects whose natural lifetime is scoped to
           * a block and which need a hook for cleaning up resources or handling
           * other block termination processing.
           * <p>
           * This notification will occur whether or not a transaction is active.
           * <p>
           * These registrations naturally are removed when the associated scope
           * is removed (after the {@link Finalizable#finished} method is called)
           * in the case of a non-global scope OR at the moment that the last
           * scope has been removed in the case of the global scope.
           * <p>
           * The given object will only be added if it is not already in the list.
           * <p>
           * Finalizables for a scope are processed in the same order as they were
           * registered.
           *
           * @param    blockDepth
           *           zero-based depth of target block, starting from the outermost
           *           block. <code>0</code> indicates the global block.
           * @param    target
           *           The object reference to be notified on any exit from the
           *           block specified.  This cannot be <code>null</code>.
           */
          public void registerFinalizableAt(int blockDepth, Finalizable target)
          {
             TransactionManager.registerFinalizableAt(wa, blockDepth, target);
          }

          /**
           * Adds the given object into the list of objects which require
           * iterate/retry/finish notifications for the next block that is opened
           * which is a top-level block that corresponds to a Progress external
           * procedure.
           * <p>
           * This form of registration is useful for objects whose natural lifetime
           * is scoped to the top-level block and which need a hook for cleaning up
           * resources or handling other block termination processing.  In
           * particular, it is necessary for objects that are constructed during
           * a class' constructor/initializer (before the external procedure block
           * has opened).  Such objects cannot simply register since their
           * construction is not actually enclosed by the block.
           * <p>
           * This notification will occur whether or not a transaction is active.
           * <p>
           * These registrations naturally are removed when the associated scope
           * is removed (after the {@link Finalizable#finished} method is called).
           * <p>
           * The given object will only be added if it is not already in the list.
           *
           * @param    target
           *           The object reference to be notified on any exit from the
           *           top-level external block.  This cannot be <code>null</code>.
           *
           * @throws   IllegalStateException
           *           if the top-level block cannot be identified.
           */
          public void registerNextExternal(Finalizable target)
          {
             TransactionManager.registerNextExternal(wa, target);
          }

          /**
           * Allow finalizables to do their cleanup ({@link Finalizable#initFailure()}) now since the
           * scoped they were designed was aborted and there won't be other chance to call their
           * {@link Finalizable#finished()}.
           * <p>
           * After iteration the list is deleted.
           * <p>
           * Also, cleanup all other pending state being collected while the external program was
           * instantiating.
           */
          public void cleanupPending()
          {
             TransactionManager.cleanupPending(wa);
          }

          /**
           * Adds the given object into the list of objects which require
           * iterate/retry/finish notifications for the nearest enclosing
           * top-level block.  A top-level block is generated for Progress external
           * procedures, internal procedures, triggers and functions.  An additional
           * parameter allows registration to be specified for the specific block
           * which corresponds with an external procedure block scope in Progress.
           * <p>
           * This form of registration is useful for objects whose natural lifetime
           * is scoped to the top-level block and which need a hook for cleaning up
           * resources or handling other block termination processing.
           * <p>
           * This notification will occur whether or not a transaction is active.
           * <p>
           * These registrations naturally are removed when the associated scope
           * is removed (after the {@link Finalizable#finished} method is called).
           * <p>
           * The given object will only be added if it is not already in the list.
           * <p>
           * Finalizables for a scope are processed in the same order as they were
           * registered.
           *
           * @param    target
           *           The object reference to be notified on any exit from the
           *           top-level block.  This cannot be <code>null</code>.
           * @param    external
           *           If <code>true</code>, register with the nearest enclosing
           *           external block (external procedure), otherwise register with
           *           the nearest enclosing top-level block (which may be but is
           *           not guaranteed to be the external block).
           *
           * @throws   IllegalStateException
           *           if the top-level block cannot be identified.
           */
          public void registerTopLevelFinalizable(Finalizable target, boolean external)
          {
             TransactionManager.registerTopLevelFinalizable(wa, target, external);
          }

          /**
           * Adds the given object into the list of objects which require
           * iterate/retry/finish notification for the nearest enclosing, looping
           * block.
           * <p>
           * This form of registration is useful for objects whose natural lifetime
           * is scoped to the nearest enclosing, looping block and which need a hook
           * for cleaning up resources or handling other block termination
           * processing.
           * <p>
           * This notification will occur whether or not a transaction is active.
           * <p>
           * These registrations naturally are removed when the associated scope
           * is removed (after the {@link Finalizable#finished} method is called).
           * <p>
           * The given object will only be added if it is not already in the list.
           *
           * @param    target
           *           The object reference to be notified on any exit from or
           *           iteration of the target block.  This cannot be
           *           <code>null</code>.
           *
           * @throws   IllegalStateException
           *           if no enclosing, looping block can be identified.
           */
          public void registerNearestLoopFinalizable(Finalizable target)
          {
             TransactionManager.registerNearestLoopFinalizable(wa, target);
          }

          /**
           * Removes the given object from the list of objects which require
           * iterate/retry/finish notifications.  This will find the first instance
           * of the given reference by walking up each block from the current to the
           * global block. The first instance found will be removed and the method
           * will then return. If the same instance is registered in multiple scopes,
           * it would take multiple calls to remove all instances.
           * <p>
           * After this call, no notifications will be received.
           *
           * @param    target
           *           The object reference to be removed.
           *
           * @return   <code>true</code> if the target instance was found and
           *           removed.
           */
          public boolean removeFinalizable(Finalizable target)
          {
             if (target == null)
             {
                // nothing to do
                return false;
             }

             return TransactionManager.removeFinalizable(wa, target);
          }

          /**
           * Adds the given object as the transaction level reference which will get
           * a "master" finish call after all other finish notifications have been
           * processed at the full transaction level.  This will never be called for
           * sub-transaction finish notification.
           * <p>
           * This notification will ONLY occur when there is an active transaction
           * and it only will be called at the exit of the block that corresponds
           * to the full transaction.
           * <p>
           * Multiple targets may be registered by invoking this method multiple
           * times.  However, the same object registered multiple times will receive
           * the notification only once per full transaction block exit.  If multple
           * targets are registered, they will be called in the order in which they
           * were registered.
           *
           * @param    target
           *           The object reference to be finished at full transaction block
           *           end.
           * @param    remove
           *           If <code>true</code> automatically remove this registration
           *           after the full transaction scope has ended.  If
           *           <code>false</code>, this reference will remain globally until
           *           replaced.
           */
          public void registerTransactionFinish(Finalizable target, boolean remove)
          {
             if (wa.masterFinish == null)
             {
                wa.masterFinish = new LinkedHashMap<>();
             }
             wa.masterFinish.put(target, remove);
          }

          /**
           * Adds the given object into the batch notifier list.  This is a global
           * list (it isn't associated with any block) that is triggered externally
           * by a call to {@link #batchStart} and {@link #batchStop}.  Every object
           * in the list will be notified in the order in which they were added to
           * the list.
           *
           * @param    target
           *           The object reference to be notified of any batch start and
           *           stop.
           */
          public void registerBatchListener(BatchListener target)
          {
             wa.batchListeners.add(target);
          }

          /**
           * Reports whether the current block was just rolled back or a containing
           * block has a pending rollback. If there is no current transaction open,
           * then there cannot be a pending or current rollback.
           *
           * @return   <code>true</code> if there was or will be a rollback.
           */
          public boolean hadRollback()
          {
             return TransactionManager.hadRollback(wa);
          }

          /**
           * Trigger a rollback for all registered <code>Undoable</code> objects
           * inside the current active transaction at the scope defined by the
           * current block.  All objects that are rolled back will have their
           * values assigned based on the values backed up at the start of the
           * specified scope.  A flow control change MUST always immediately follow
           * this method otherwise undefined results can occur.
           * <p>
           * This caller-driven control flow is required to properly duplicate the
           * semantics of Progress 4GL condition processing which always generates
           * an implicit or explicit flow control action after every UNDO. Since the
           * flow of control change can reference a different scope than that of
           * the rollback, this can't be handled by re-throwing the exception but
           * must instead be separately driven by a deferred rollback and a caller
           * driven flow control statement such as break, continue or return. The
           * key limitation that makes this possible is the fact that the UNDO
           * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
           * the scope to which flow of control will be changed.  This means that
           * one can rollback at a more deeply nested scope but change flow of
           * control to resume processing at a different (less deeply nested) scope.
           * The reverse is not possible.
           */
          public void rollback()
          {
             rollback(null);
          }

          /**
           * Trigger a rollback for all registered <code>Undoable</code> objects
           * inside the current active transaction at the scope defined
           * explicitly by the given label or implicitly if the label is
           * <code>null</code>.  All objects that are rolled back will have their
           * values assigned based on the values backed up at the start of the
           * specified scope.  If the current scope is not the target scope, the
           * given exception will be stored, the rollback will be deferred and
           * the rollback will be processed as the scopes naturally unwind.  This
           * unwinding must be generated by a change in flow of control by the
           * calling code.  This flow control change MUST always immediately follow
           * this method otherwise undefined results can occur.
           * <p>
           * This caller-driven control flow is required to properly duplicate the
           * semantics of Progress 4GL condition processing which always generates
           * an implicit or explicit flow control action after every UNDO. Since the
           * flow of control change can reference a different scope than that of
           * the rollback, this can't be handled by re-throwing the exception but
           * must instead be separately driven by a deferred rollback and a caller
           * driven flow control statement such as break, continue or return. The
           * key limitation that makes this possible is the fact that the UNDO
           * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
           * the scope to which flow of control will be changed.  This means that
           * one can rollback at a more deeply nested scope but change flow of
           * control to resume processing at a different (less deeply nested) scope.
           * The reverse is not possible.
           *
           * @param    label
           *           Specifies the name of the scope at which to rollback or
           *           <code>null</code> if the scope should be implicitly
           *           determined.
           */
          public void rollback(String label)
          {
             rollback(label, null);
          }

          /**
           * Trigger a rollback for all registered <code>Undoable</code> objects
           * inside the current active transaction at the scope defined
           * explicitly by the given label or implicitly if the label is
           * <code>null</code>.  All objects that are rolled back will have their
           * values assigned based on the values backed up at the start of the
           * specified scope.  If the current scope is not the target scope, the
           * given exception will be stored, the rollback will be deferred and
           * the rollback will be processed as the scopes naturally unwind.  This
           * unwinding must be generated by a change in flow of control by the
           * calling code.  This flow control change MUST always immediately follow
           * this method otherwise undefined results can occur.
           * <p>
           * This caller-driven control flow is required to properly duplicate the
           * semantics of Progress 4GL condition processing which always generates
           * an implicit or explicit flow control action after every UNDO. Since the
           * flow of control change can reference a different scope than that of
           * the rollback, this can't be handled by re-throwing the exception but
           * must instead be separately driven by a deferred rollback and a caller
           * driven flow control statement such as break, continue or return. The
           * key limitation that makes this possible is the fact that the UNDO
           * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
           * the scope to which flow of control will be changed.  This means that
           * one can rollback at a more deeply nested scope but change flow of
           * control to resume processing at a different (less deeply nested) scope.
           * The reverse is not possible.
           *
           * @param    label
           *           Specifies the name of the scope at which to rollback or
           *           <code>null</code> if the scope should be implicitly
           *           determined.
           * @param    cause
           *           The exceptional cause, if any, of the rollback.
           */
          public void rollback(String label, Throwable cause)
          {
             TransactionManager.rollback(wa, label, cause);
          }

          /**
           * Trigger a rollback for all registered <code>Undoable</code> objects
           * inside the current active transaction at the nearest top level scope.
           * All objects that are rolled back will have their values assigned based
           * on the values backed up at the start of the specified scope.  If the
           * current scope is not the target scope, the given exception will be
           * stored, the rollback will be deferred and the rollback will be processed
           * as the scopes naturally unwind.  This unwinding must be generated by a
           * change in flow of control by the calling code.  This flow control change
           * MUST always immediately follow this method otherwise undefined results
           * can occur.
           * <p>
           * This caller-driven control flow is required to properly duplicate the
           * semantics of Progress 4GL condition processing which always generates
           * an implicit or explicit flow control action after every UNDO. Since the
           * flow of control change can reference a different scope than that of
           * the rollback, this can't be handled by re-throwing the exception but
           * must instead be separately driven by a deferred rollback and a caller
           * driven flow control statement such as break, continue or return. The
           * key limitation that makes this possible is the fact that the UNDO
           * (rollback) scope MUST ALWAYS BE equivalent to or more specific than
           * the scope to which flow of control will be changed.  This means that
           * one can rollback at a more deeply nested scope but change flow of
           * control to resume processing at a different (less deeply nested) scope.
           * The reverse is not possible.
           */
          public void rollbackTopLevel()
          {
             TransactionManager.rollbackTopLevel(wa);
          }

          /**
           * Initializes a new scope, the associated block definition data
           * structures.  A backup of objects that can be rolled back is deferred
           * until after the block opens (see {@link #blockSetup} and
           * {@link #makeBackup}).
           * <p>
           * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
           * the core data structures are initialized but no rollback support will
           * be provided.
           * <p>
           * This method will honor any pending thread interruption by throwing
           * a {@link StopConditionException} using {@link #honorStopCondition}.
           * <p>
           * This will create a block of <code>BlockType.UNKNOWN</code> and block
           * properties <code>PROP_NONE</code>.
           *
           * @param    label
           *           The text label associated with the block.  This usually
           *           should correspond with the label used in the source file
           *           to identify the block for purposes of the break and continue
           *           statements.
           * @param    level
           *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
           *           {@link #TRANSACTION}.  This code defines the level of
           *           rollback and transaction support which should be provided.
           * @param    external
           *           <code>true</code> if this block is associated with the
           *           external procedure (in Progress terms). This controls the
           *           cleanup of certain resources such as temp-tables, frames
           *           and streams.
           * @param    topLevel
           *           <code>true</code> if this block is a method.
           * @param    loop
           *           Defines if this block is a loop or not.
           * @param    next
           *           <code>true</code> if this infinite loop protection should
           *           convert a retry into a next (continue), otherwise the retry
           *           will be converted into a leave (break).
           *
           * @throws   StopConditionException
           *           If the current thread's was previously interrupted.
           */
          public void pushScope(String    label,
                                int       level,
                                boolean   external,
                                boolean   topLevel,
                                boolean   loop,
                                boolean   next)
          {
             pushScope(label, level, PROP_NONE, external, topLevel, loop, next, UNKNOWN);
          }

          /**
           * Initializes a new scope, the associated block definition data
           * structures.  A backup of objects that can be rolled back is deferred
           * until after the block opens (see {@link #blockSetup} and
           * {@link #makeBackup}).
           * <p>
           * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
           * the core data structures are initialized but no rollback support will
           * be provided.
           * <p>
           * This method will honor any pending thread interruption by throwing
           * a {@link StopConditionException} using {@link #honorStopCondition}.
           * <p>
           * This will create a block with block properties <code>PROP_NONE</code>.
           *
           * @param    label
           *           The text label associated with the block.  This usually
           *           should correspond with the label used in the source file
           *           to identify the block for purposes of the break and continue
           *           statements.
           * @param    level
           *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
           *           {@link #TRANSACTION}.  This code defines the level of
           *           rollback and transaction support which should be provided.
           * @param    external
           *           <code>true</code> if this block is associated with the
           *           external procedure (in Progress terms). This controls the
           *           cleanup of certain resources such as temp-tables, frames
           *           and streams.
           * @param    topLevel
           *           <code>true</code> if this block is a method.
           * @param    loop
           *           Defines if this block is a loop or not.
           * @param    next
           *           <code>true</code> if this infinite loop protection should
           *           convert a retry into a next (continue), otherwise the retry
           *           will be converted into a leave (break).
           * @param    blockType
           *           Specifies this block's type.
           *
           * @throws   StopConditionException
           *           If the current thread's was previously interrupted.
           */
          public void pushScope(String    label,
                                int       level,
                                boolean   external,
                                boolean   topLevel,
                                boolean   loop,
                                boolean   next,
                                BlockType blockType)
          {
             pushScope(label, level, PROP_NONE, external, topLevel, loop, next, UNKNOWN);
          }

          /**
           * Initializes a new scope, the associated block definition data
           * structures.  A backup of objects that can be rolled back is deferred
           * until after the block opens (see {@link #blockSetup} and
           * {@link #makeBackup}).
           * <p>
           * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
           * the core data structures are initialized but no rollback support will
           * be provided.
           * <p>
           * This method will honor any pending thread interruption by throwing
           * a {@link StopConditionException} using {@link #honorStopCondition}
           *
           * @param    label
           *           The text label associated with the block.  This usually
           *           should correspond with the label used in the source file
           *           to identify the block for purposes of the break and continue
           *           statements.
           * @param    level
           *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
           *           {@link #TRANSACTION}.  This code defines the level of
           *           rollback and transaction support which should be provided.
           * @param    props
           *           A bitfield storing block properties which will be a
           *           combination of {@link #PROP_NONE}, {@link #PROP_ERROR},
           *           {@link #PROP_ENDKEY}, {@link #PROP_STOP} or
           *           {@link #PROP_QUIT}.
           * @param    external
           *           <code>true</code> if this block is associated with the
           *           external procedure (in Progress terms). This controls the
           *           cleanup of certain resources such as temp-tables, frames
           *           and streams.
           * @param    topLevel
           *           <code>true</code> if this block is a method.
           * @param    loop
           *           Defines if this block is a loop or not.
           * @param    next
           *           <code>true</code> if this infinite loop protection should
           *           convert a retry into a next (continue), otherwise the retry
           *           will be converted into a leave (break).
           * @param    blockType
           *           Specifies this block's type.
           *
           * @throws   StopConditionException
           *           If the current thread's was previously interrupted.
           */
          public void pushScope(String    label,
                                int       level,
                                int       props,
                                boolean   external,
                                boolean   topLevel,
                                boolean   loop,
                                boolean   next,
                                BlockType blockType)
          {
             pushScope(label, level, props, external, topLevel, loop, next, false, blockType);
          }

          /**
           * Initializes a new scope, the associated block definition data
           * structures.  A backup of objects that can be rolled back is deferred
           * until after the block opens (see {@link #blockSetup} and
           * {@link #makeBackup}).
           * <p>
           * In the case of a block which is defined as <code>NO_TRANSACTION</code>,
           * the core data structures are initialized but no rollback support will
           * be provided.
           * <p>
           * This method will honor any pending thread interruption by throwing
           * a {@link StopConditionException} using {@link #honorStopCondition}
           *
           * @param    label
           *           The text label associated with the block.  This usually
           *           should correspond with the label used in the source file
           *           to identify the block for purposes of the break and continue
           *           statements.
           * @param    level
           *           One of {@link #NO_TRANSACTION}, {@link #SUB_TRANSACTION} or
           *           {@link #TRANSACTION}.  This code defines the level of
           *           rollback and transaction support which should be provided.
           * @param    props
           *           A bitfield storing block properties which will be a
           *           combination of {@link #PROP_NONE}, {@link #PROP_ERROR},
           *           {@link #PROP_ENDKEY}, {@link #PROP_STOP} or
           *           {@link #PROP_QUIT}.
           * @param    external
           *           <code>true</code> if this block is associated with the
           *           external procedure (in Progress terms). This controls the
           *           cleanup of certain resources such as temp-tables, frames
           *           and streams.
           * @param    topLevel
           *           <code>true</code> if this block is a method.
           * @param    loop
           *           Defines if this block is a loop or not.
           * @param    next
           *           <code>true</code> if this infinite loop protection should
           *           convert a retry into a next (continue), otherwise the retry
           *           will be converted into a leave (break).
           * @param    databaseTrigger
           *           <code>true</code> if this scope should create a definition
           *           of a database trigger block.
           * @param    blockType
           *           Specifies this block's type.
           *
           * @throws   StopConditionException
           *           If the current thread's was previously interrupted.
           */
          public void pushScope(String    label,
                                int       level,
                                int       props,
                                boolean   external,
                                boolean   topLevel,
                                boolean   loop,
                                boolean   next,
                                boolean   databaseTrigger,
                                BlockType blockType)
          {
             TransactionManager.pushScope(wa,
                                          label,
                                          level,
                                          props,
                                          external,
                                          topLevel,
                                          loop,
                                          next,
                                          databaseTrigger,
                                          blockType);
          }

          /**
           * Remove the current block from the stack of scopes, process any pending
           * rollbacks (if the current block is the proper target), notify any
           * objects needing commits (if the block finished successfully) and handle
           * finish processing for the block.  This method must be called from a
           * <code>finally</code> block such that this is guaranteed to be called
           * no matter how the flow of control exits the associated block (exception,
           * break, return or the flow of control naturally passing through the
           * end of the block).
           * <p>
           * Rollback and commit processing only occur when there is an active
           * transaction.  Otherwise this processing is bypassed.  Finish processing
           * is always called.
           * <p>
           * This method implements global finish processing (as the last action
           * before returning) when the last scope is popped from the stack of
           * scopes.  For this reason, the current implementation assumes that the
           * session is over at the moment that the top-most user-defined scope
           * exits.
           * <p>
           * This method will honor any pending thread interruption by throwing
           * a {@link StopConditionException} using {@link #honorStopCondition}
           *
           * @throws   StopConditionException
           *           If the current thread's was previously interrupted.
           */
          public void popScope()
          throws StopConditionException
          {
             TransactionManager.popScope(wa);
          }

          /**
           * Notify {@link ConditionListener} instances about condition.
           *
           * @param   condition
           *          The condition the listeners will be notified.
           */
          public void notifyCondition(BlockManager.Condition condition)
          {
             // do not pass null down
             if (condition == null)
             {
                return;
             }

             TransactionManager.notifyCondition(wa, condition);
          }

          /**
           * Notify {@link ConditionListener} instances about exception condition.
           *
           * @param   condition
           *          The instance of {@link ConditionException}.
           */
          public void notifyCondition(ConditionException condition)
          {
             TransactionManager.notifyCondition(wa, BlockManager.exceptionToCondition(condition));
          }

          /**
           * Check if the latest block has initialized.
           *
           * @return   See above.
           */
          public boolean blockInitialized()
          {
             BlockDefinition blk = wa.blocks.peek();

             return blk.init;
          }

          /**
           * Provides a callback entry point for the top of the loop.  This location
           * is required to handle the backup of undo variables via
           * {@link #makeBackup}.  On the second and subsequent executions of this
           * method for a given block, this method handles <code>iterate</code>
           * notifications.
           * <p>
           * It is important to note that <code>continue</code> can be called from
           * nested blocks at arbitrary depths.  This method can handle such nesting
           * at any level.
           * <p>
           * This method will honor any pending thread interruption by throwing
           * a {@link StopConditionException} using {@link #honorStopCondition}
           *
           * @throws   StopConditionException
           *           If the current thread's was previously interrupted.
           */
          public void blockSetup()
          throws StopConditionException
          {
             TransactionManager.blockSetup(wa);
          }

          /**
           * Save the given runnable as the FINALLY block for the next iterate, retry or finished. This
           * will be cleared after it is executed, so it must be setup each iteration of the block.
           *
           * @param    fini
           *           The FINALLY block code to execute.
           */
          public void setupFinallyBlock(Runnable fini)
          {
             BlockDefinition blk = wa.blocks.peek();

             blk.finallyBlock = fini;
          }

          /**
           * Save the given runnable as the CATCH block for the next iterate, retry or finished. This will be
           * be cleared after it is executed, so it must be setup each iteration of the block.
           *
           * @param    possibleCatch
           *           The CATCH block code to execute.
           */
          public void setupCatchProcessing(Runnable possibleCatch)
          {
             BlockDefinition blk = wa.blocks.peek();
             blk.possibleCatch = possibleCatch;
          }

          /**
           * Save the given LegacyStopException. This will be cleared after it is executed,
           * so it must be setup each iteration of the block.
           *
           * @param    stopCondition
           *           LegacyStopException from stop-after to propagate.
           */
          public void setupStopAfterProccessing(LegacyStopException stopCondition)
          {
             wa.stopAfterException = stopCondition;
          }

          /**
           * Creates or updates the backup set for all objects needing rollback
           * support in the current block, if a transaction is active.  This is
           * split up from {@link #pushScope} and {@link #blockSetup} due to the
           * semantics of Progress in which the backup set is created or refreshed
           * after the loop control variables have been modified.
           */
          public void makeBackup()
          {
             TransactionManager.backupWorker(wa, null);
          }

          /**
           * Reports if there is a current transaction which is active.
           *
           * @return    <code>true</code> if a transaction is active.
           */
          public logical isTransactionActive()
          {
             return new logical(wa.isTransaction());
          }

          /**
           * Reports if the current iteration of this block or loop is a retry.
           *
           * @return    <code>true</code> if this is a retry of this block or loop
           *            (as opposed to the first time through this iteration).
           */
          public boolean _isRetry()
          {
             return TransactionManager._isRetry(wa);
          }

          /**
           * Reports if the current iteration of this block or loop is a retry.
           *
           * @return    <code>true</code> if this is a retry of this block or loop
           *            (as opposed to the first time through this iteration).
           */
          public logical isRetry()
          {
             return new logical(_isRetry());
          }

          /**
           * Records the fact that the pending RETRY is being caused by an ENDKEY
           * condition. This enables "downstream" retry processing to implement the
           * "stuttering" quirk and some behavior in honoring PAUSE as a way to
           * disable infinite loop protection.
           */
          public void markEndkeyRetry()
          {
             TransactionManager.markEndkeyRetry(wa);
          }

          /**
           * This checks if any of the enclosing loops has iterated at least once.
           *
           * @return   <code>true</code> if any of the enclosing loops has iterated
           *           at least once.
           */
          public boolean hasParentIteratedBlock()
          {
             return TransactionManager.hasParentIteratedBlock(wa);
          }

          /**
           * Determines if a NEXT operation with the given target block should
           * be honored or if the NEXT should be converted to a LEAVE. This is
           * a special form of infinite loop protection.
           * <p>
           * A NEXT is converted into a LEAVE when all of the following conditions
           * are true:
           * <p>
           * <ul>
           *   <li> The NEXT is associated with the UNDO, NEXT language statement or
           *        with an ON phrase that has UNDO, NEXT as the result of the given
           *        condition (e.g. ON ENDKEY UNDO, NEXT). It is important to note
           *        that Progress does not provide infinite loop protection for the
           *        NEXT language statement, so there is no conversion to LEAVE in
           *        that case.
           *   <li> The target block (whether implicitly determined by the lack of a
           *        label or explicitly determined by the presence of a label) must
           *        also have been the target block for the UNDO operation. It is
           *        possible for the NEXT to be targetted at a more enclosing block
           *        than the UNDO, in which case there is no conversion to LEAVE.
           *   <li> The target block type must be REPEAT, REPEAT WHILE or DO WHILE.
           *        These are the only loop types that normally convert RETRY to
           *        LEAVE instead of NEXT.  Likewise, these are the only loop types
           *        which honor infinite loop protection for NEXT by converting it
           *        to a LEAVE.
           *   <li> Infinite loop protection must not have been disabled for the
           *        target block.  In this case, any form of PAUSE is honored as
           *        one of the mechanisms to disable infinite loop protection.
           * </ul>
           * <p>
           * Since NEXT is only possible on a looping block, if the target block
           * is not a looping block then this method will return <code>false</code>.
           *
           * @param    label
           *           The label of the target block for the NEXT action.
           *
           * @return   <code>true</code> for NEXT, <code>false</code> for LEAVE.
           */
          public boolean honorNext(String label)
          {
             return TransactionManager.honorNext(wa, label);
          }

          /**
           * Determines if the currently executing block should be retried.  This
           * is where infinite loop protection is implemented if it is enabled.
           *
           * @return   <code>true</code> if the block (whether or not it is a loop)
           *           should be retried.
           */
          public boolean needsRetry()
          {
             return TransactionManager.needsRetry(wa);
          }

          /**
           * Accesses the state of whether there is a pending break that must be
           * honored.  This would have been generated by the infinite loop
           * protection.
           *
           * @return   <code>true</code> if a break is pending for this block.
           */
          public boolean isBreakPending()
          {
             return TransactionManager.isBreakPending(wa);
          }

          /**
           * Force an error to be generated such that it will be caught in the
           * caller of this method.  The stack will be unwound and normal error
           * processing bypassed until this method returns.  This unwinding is
           * dependent upon catch blocks being implemented with a corresponding call
           * to {@link #honorError}.
           * <p>
           * In the case where silent error mode is enabled in the calling method,
           * the "normal" stack unwinding approach cannot be used because the
           * exception cannot be thrown. Instead, the error manager will be updated
           * with the error data and this method will silently return.  It is
           * critical that in such cases the calling code must immediately execute a
           * <code>return</code> statement which will maintain the proper Progress
           * behavior.  The caller can check the error manager to determine that an
           * error occurred.
           * <p>
           * Detecting the silent error mode in the caller requires that the top
           * level block be found.  The silent error flag of the caller is cached
           * there.
           *
           * @param    text
           *           The text to use as the error message in the exception.
           *
           * @throws   ErrorConditionException
           */
          public void triggerErrorInCaller(String text)
          throws ErrorConditionException
          {
             TransactionManager.triggerErrorInCaller(wa, null, text);
          }

          /**
           * Check if this block's caller requires errors to be suppressed.
           *
           * @return   {@link BlockDefinition#suppressError} for {@link #nearestTopLevel} block.
           */
          public boolean isSuppressError()
          {
             return TransactionManager.isSuppressError(wa);
          }

          /**
           * Force an error to be generated such that it will be caught in the
           * caller of this method.  The stack will be unwound and normal error
           * processing bypassed until this method returns.  This unwinding is
           * dependent upon catch blocks being implemented with a corresponding call
           * to {@link #honorError}.
           * <p>
           * In the case where silent error mode is enabled in the calling method,
           * the "normal" stack unwinding approach cannot be used because the
           * exception cannot be thrown. Instead, the error manager will be updated
           * with the error data and this method will silently return.  It is
           * critical that in such cases the calling code must immediately execute a
           * <code>return</code> statement which will maintain the proper Progress
           * behavior.  The caller can check the error manager to determine that an
           * error occurred.
           * <p>
           * Detecting the silent error mode in the caller requires that the top
           * level block be found.  The silent error flag of the caller is cached
           * there.
           *
           * @throws   ErrorConditionException
           */
          public void triggerErrorInCaller()
          throws ErrorConditionException
          {
             TransactionManager.triggerErrorInCaller(wa, null, "");
          }

          /**
           * Force an error to be generated such that it will be caught in the
           * caller of this method.  The stack will be unwound and normal error
           * processing bypassed until this method returns.  This unwinding is
           * dependent upon catch blocks being implemented with a corresponding call
           * to {@link #honorError}.
           * <p>
           * In the case where silent error mode is enabled in the calling method,
           * the "normal" stack unwinding approach cannot be used because the
           * exception cannot be thrown. Instead, the error manager will be updated
           * with the error data and this method will silently return.  It is
           * critical that in such cases the calling code must immediately execute a
           * <code>return</code> statement which will maintain the proper Progress
           * behavior.  The caller can check the error manager to determine that an
           * error occurred.
           * <p>
           * Detecting the silent error mode in the caller requires that the top
           * level block be found.  The silent error flag of the caller is cached
           * there.
           *
           * @param    lex
           *           The legacy OO-like error to throw.
           *
           * @throws   ErrorConditionException
           */
          public void triggerErrorInCaller(LegacyErrorException lex)
          throws ErrorConditionException
          {
             TransactionManager.triggerErrorInCaller(wa, lex);
          }

          /**
           * Force a retry of the current block (if there is no pending rollback)
           * or retry the same block as the pending rollback, in either case
           * ensure that the flow of control will not naturally continue.
           * <p>
           * A {@link RetryUnwindException} will be thrown to unwind the stack
           * to the given scope, even if the scope is the current block.  This
           * unwinding is dependent upon catch blocks being implemented with a
           * corresponding call to {@link #honorRetry}.
           * <p>
           * <b>This method will NEVER naturally return since an exception is ALWAYS
           * thrown.</b>  This method should generally not be called from within a
           * catch block if the intention is to retry the current block since it
           * would be outside of the block's associated <code>try</code>.  In that
           * case, only an enclosing block could be retried.
           *
           * @throws   RetryUnwindException
           *           This method always generates this exception to ensure that
           *           natural processing will not continue.
           */
          public void forceRetry()
          throws RetryUnwindException
          {
             TransactionManager.forceRetry(wa, null);
          }

          /**
           * Force a retry of the current block or the scope named by the given
           * label, ensuring that the flow of control will not naturally continue.
           * A {@link RetryUnwindException} will be thrown to unwind the stack
           * to the given scope, even if the scope is the current block.  This
           * unwinding is dependent upon catch blocks being implemented with a
           * corresponding call to {@link #honorRetry}.
           * <p>
           * <b>This method will NEVER naturally return since an exception is ALWAYS
           * thrown.</b>  This method should generally not be called from within a
           * catch block if the intention is to retry the current block since it
           * would be outside of the block's associated <code>try</code>.  In that
           * case, only an enclosing block could be retried.
           *
           * @param    label
           *           The name of the scope to retry. Specify <code>null</code> to
           *           retry the scope that has a pending rollback OR to retry
           *           the current scope if there is no pending rollback.
           *
           * @throws   RetryUnwindException
           *           This method always generates this exception to ensure that
           *           natural processing will not continue.
           */
          public void forceRetry(String label)
          throws RetryUnwindException
          {
             TransactionManager.forceRetry(wa, label);
          }

          /**
           * Force a retry of the current block (if there is no pending rollback)
           * or the block targeted for a pending rollback.  If a containing scope is
           * the target of the retry, then a {@link RetryUnwindException} will be
           * thrown to unwind the stack to the given scope.  This unwinding is
           * dependent upon catch blocks being implemented with a corresponding call
           * to {@link #honorRetry}.
           * <p>
           * If the current block is not the target, the retry is deferred by
           * marking the target block and then unwinding the stack.  Otherwise,
           * this method will return naturally.  This method is only appropriate
           * to be called from within <code>catch</code> blocks since a natural
           * return within a <code>catch</code> block will fall through to allow
           * the retry loop to engage.
           *
           * @throws   RetryUnwindException
           */
          public void triggerRetry()
          throws RetryUnwindException
          {
             TransactionManager.triggerRetry(wa, null);
          }

          /**
           * Force a retry of the current block or the scope named by the given
           * label.  If a containing scope is the target of the retry, then
           * a {@link RetryUnwindException} will be thrown to unwind the stack
           * to the given scope.  This unwinding is dependent upon catch blocks
           * being implemented with a corresponding call to {@link #honorRetry}.
           * <p>
           * If the current block is not the target, the retry is deferred by
           * marking the target block and then unwinding the stack.  Otherwise,
           * this method will return naturally.  This method is only appropriate
           * to be called from within <code>catch</code> blocks since a natural
           * return within a <code>catch</code> block will fall through to allow
           * the retry loop to engage.
           *
           * @param    label
           *           The name of the scope to retry. Specify <code>null</code> to
           *           retry the scope of that has a pending rollback OR to retry
           *           the current scope if there is no pending rollback.
           *
           * @throws   RetryUnwindException
           */
          public void triggerRetry(String label)
          throws RetryUnwindException
          {
             TransactionManager.triggerRetry(wa, label);
          }

          /**
           * Dispatch the given exception to all registered stop condition handlers
           * to see if any requires that the STOP condition be vetoed at the
           * current scope.  If any of the registered handlers indicate that the STOP
           * condition should not be honored at this level, the exception is
           * rethrown, presumably to be honored at a higher scope, or not at all.  In
           * the latter case, the program running in the current context will abort.
           *
           * @param   exc
           *          The exception to be checked.
           *
           * @throws  StopConditionException
           *          if the STOP condition is not honored at this scope.
           */
          public void honorStop(StopConditionException exc)
          throws StopConditionException
          {
             TransactionManager.honorStop(wa, exc);
          }

          /**
           * Check if there is a pending retry of the current block and if so then
           * enable retry.  This is the "last half" of the processing to unwind the
           * stack up to a containing scope that is the target of a retry.  The
           * "first half" of the unwinding is implemented by throwing a
           * {@link RetryUnwindException}.  This unwinding is dependent upon catch
           * blocks being implemented to call this method.
           * <p>
           * If the current block is not the target for the pending retry, the
           * exception is re-thrown.
           *
           * @param    rue
           *           The exception to honor or ignore.
           *
           * @throws   RetryUnwindException
           */
          public void honorRetry(RetryUnwindException rue)
          throws RetryUnwindException
          {
             TransactionManager.honorRetry(wa, rue);
          }

          /**
           * Check if the current error must unwind all the way to the caller of
           * the top-level block in this method (if the ignore flag is set).  If
           * this is the case then the error is re-thrown.  Before this occurs, if
           * the current block is the top-level block of this method, then the
           * ignore flag will be cleared.  If the ignore flag is <code>false</code>
           * then this method returns silently.
           * <p>
           * This code also honors any nested error that was generated while another
           * error was being processed AND while nested error mode was enabled.  Any
           * such stored error will be re-thrown here.
           *
           * @param    err
           *           The exception to honor or ignore.
           *
           * @throws   ErrorConditionException
           */
          public void honorError(ErrorConditionException err)
          throws ErrorConditionException
          {
             TransactionManager.honorError(wa, err);
          }

          /**
           * Notifies the transaction manager that an unexpected exception or error
           * has occurred and that any transaction must be aborted. This will
           * maintain the state of the system properly, including rolling back any
           * open transaction. Once the state is properly maintained, the triggering
           * <code>Throwable</code> will be re-thrown as a
           * <code>RuntimeException</code>.
           * <p>
           * If the exception is of type <code>ConditionException</code> or
           * <code>RetryUnwindException</code>, this method will do nothing since
           * these are used for normal flow of control purposes and are used in
           * close coordination with the transaction manager's state.  They need
           * no other special behavior to be safe.  This method is designed to
           * handle exceptions (like <code>NullPointerException</code>) which are
           * not valid in a converted 4GL program.
           *
           * @param    thr
           *           The unexpected exception or error.
           *
           * @throws   RuntimeException
           */
          public void abnormalEnd(Throwable thr)
          throws RuntimeException
          {
             TransactionManager.abnormalEnd(wa, thr, true);
          }

          /**
           * Notifies the transaction manager that an unexpected exception or error
           * has occurred and that any transaction must be aborted. This will
           * maintain the state of the system properly, including rolling back any
           * open transaction. Once the state is properly maintained, the triggering
           * <code>Throwable</code> will be re-thrown as a
           * <code>RuntimeException</code>.
           * <p>
           * If the exception is of type <code>ConditionException</code> or
           * <code>RetryUnwindException</code>, this method will do nothing since
           * these are used for normal flow of control purposes and are used in
           * close coordination with the transaction manager's state.  They need
           * no other special behavior to be safe.  This method is designed to
           * handle exceptions (like <code>NullPointerException</code>) which are
           * not valid in a converted 4GL program.
           *
           * @param    thr
           *           The unexpected exception or error.
           * @param    needsNotify
           *           <code>true</code> if condition notification must be executed
           *           (only happens if the <code>thr</code> is a condition).
           *
           * @throws   RuntimeException
           */
          public void abnormalEnd(Throwable thr, boolean needsNotify)
          throws RuntimeException
          {
             TransactionManager.abnormalEnd(wa, thr, needsNotify);
          }

          /**
           * Reports the transaction level of the current block.
           *
           * @return   The transaction level.
           */
          public int currentTransactionLevel()
          {
             BlockDefinition blk = wa.blocks.peek();
             return blk.level;
          }

          /**
           * Reports if the current scope defines the outermost transaction block
           * (which is the scope at which final commit processing must occur).
           *
           * @return   <code>true</code> if the current scope defines the top-level
           *           transaction.
           */
          public boolean isFullTransaction()
          {
             return wa.isFullTransaction();
          }

          /**
           * Reports if there is a current transaction which is active.
           *
           * @return    <code>true</code> if a transaction is active.
           */
          public boolean isTransaction()
          {
             return wa.transLevel != -1;
          }

          /**
           * Reports if there is a current active transaction at the specified block depth.
           *
           * @param     blockDepth
           *            Zero-based block depth starting from the outermost block at which transaction
           *            status is checked.
           *
           * @return    <code>true</code> if a transaction is active at the specified block depth.
           */
          public boolean isTransactionAt(int blockDepth)
          {
             int trans = wa.transLevel;
             return trans != -1 && trans <= blockDepth;
          }

          /**
           * Reports if the current block is a top-level block (this corresponds
           * with a method-level block).
           *
           * @return  <code>true</code> if top-level block, else <code>false</code>.
           */
          public boolean isTopLevelBlock()
          {
             return TransactionManager.isTopLevelBlock(wa);
          }

          /**
           * Reports if the current block is a external block (this corresponds
           * with a method-level block that was derived from a Progress external
           * procedure).  This will be <code>false</code> for blocks derived from
           * internal procedures, functions and triggers.
           *
           * @return   <code>true</code> if it is an external block, else
           *           <code>false</code>.
           */
          public boolean isExternalBlock()
          {
             BlockDefinition blk = wa.blocks.peek();
             return blk.external;
          }

          /**
           * Reports if the current block is a global scoped block. For normal contexts:
           * <code>true</code> if the current block is the only block on the stack of block scopes; for
           * appserver agent contexts: <code>true</code> if the current block is one of two outermost
           * blocks ("appserver-agent" and "startup") on the stack of block scopes.
           *
           * @return   See above.
           */
          public boolean isGlobalBlock()
          {
             int size = wa.blocks.size();
             return size == 1 || (size == 2 && wa.isRemote());
          }

          /**
           * Check if this session is an appserver session or not.
           *
           * @return   The {@link WorkArea#remote} state.
           */
          public boolean isRemote()
          {
             return wa.isRemote();
          }

          /**
           * Reports if the current block has the ERROR property.
           *
           * @return   <code>true</code> if the current block has the
           *           <code>PROP_ERROR</code> bit set in its properties bitmap.
           */
          public boolean hasErrorProp()
          {
             return TransactionManager.hasProperty(wa, PROP_ERROR);
          }

          /**
           * Reports if the current block has the ENDKEY property.
           *
           * @return   <code>true</code> if the current block has the
           *           <code>PROP_ENDKEY</code> bit set in its properties bitmap.
           */
          public boolean hasEndkeyProp()
          {
             return TransactionManager.hasProperty(wa, PROP_ENDKEY);
          }

          /**
           * Reports if the current block has the STOP property.
           *
           * @return   <code>true</code> if the current block has the
           *           <code>PROP_STOP</code> bit set in its properties bitmap.
           */
          public boolean hasStopProp()
          {
             return TransactionManager.hasProperty(wa, PROP_STOP);
          }

          /**
           * Reports if the current block has the QUIT property.
           *
           * @return   <code>true</code> if the current block has the
           *           <code>PROP_QUIT</code> bit set in its properties bitmap.
           */
          public boolean hasQuitProp()
          {
             return TransactionManager.hasProperty(wa, PROP_QUIT);
          }

          /**
           * During global finish processing, this reports if the exit was caused
           * by a <code>QuitConditionException</code>.
           *
           * @return   <code>true</code> if this exit is due to a quit condition.
           */
          public boolean isProcessingQuit()
          {
             return wa.processingQuit;
          }

          /**
           * During global finish processing, this sets the flag for an exit that
           * was caused by a <code>QuitConditionException</code>.
           *
           * @param    quit
           *           <code>true</code> if this exit is due to a quit condition.
           */
          public void setProcessingQuit(boolean quit)
          {
             wa.processingQuit = quit;
          }

          /**
           * Report if this context should be treated as headless. A headless
           * session is one that has no user interaction.
           *
           * @return   <code>true</code> if this session is headless.
           */
          public boolean isHeadless()
          {
             return wa.headless;
          }

          /**
           * Increases the number (by 1) of interactive language statements
           * (statements which took user input except for PAUSE) that have executed
           * in the current block since the most recent start scope, iterate or
           * retry.
           */
          public void incrementInteractions()
          {
             BlockDefinition blk = wa.blocks.peek();
             blk.interactions++;
          }

          /**
           * Resets the number of interactive language statements (statements which
           * took user input except for PAUSE) that have executed in the current
           * block to 0.
           */
          public void resetInteractions()
          {
             BlockDefinition blk = wa.blocks.peek();
             blk.interactions = 0;
          }

          /**
           * Gets the number of interactive language statements (statements which
           * took user input except for PAUSE) that have executed in the current
           * block  since the most recent start scope, iterate or retry.
           *
           * @return   The number of interactions in the current block.
           */
          public int getInteractions()
          {
             BlockDefinition blk = wa.blocks.peek();
             return blk.interactions;
          }

          /**
           * Get the rollup value for the current block. If <code>true</code>,
           * then any contained blocks that exit will cause the current (containing)
           * block's interactions counter to be increased by the contained block's
           * interactions counter. Otherwise, no aggregation will occur.
           *
           * @return   The current block's rollup value.
           */
          public boolean isRollup()
          {
             BlockDefinition blk = wa.blocks.peek();
             return blk.rollup;
          }

          /**
           * Get the rollup value for the enclosing block. If <code>true</code>,
           * then the current block's exit will cause the enclosing (containing)
           * block's interactions counter to be increased by the current block's
           * interactions counter. Otherwise, no aggregation will occur.
           *
           * @return   The current block's rollup value.
           */
          public boolean isEnclosingRollup()
          {
             return TransactionManager.isEnclosingRollup(wa);
          }

          /**
           * Set the new rollup value for the current block. If <code>true</code>,
           * then any contained blocks that exit will cause the current (containing)
           * block's interactions counter to be increased by the contained block's
           * interactions counter. Otherwise, no aggregation will occur.
           *
           * @param    rollup
           *           The new rollup value.
           */
          public void setRollup(boolean rollup)
          {
             BlockDefinition blk = wa.blocks.peek();
             blk.rollup = rollup;
          }

          /**
           * Notify all registered {@link BatchListener} objects that a batch is
           * starting.
           */
          public void batchStart()
          {
             TransactionManager.processBatchNotifications(wa, true);
          }

          /**
           * Notify all registered {@link BatchListener} objects that a batch is
           * stopping.
           */
          public void batchStop()
          {
             TransactionManager.processBatchNotifications(wa, false);
          }

          /**
           * Tests the interrupted status of the current thread using
           * <code>Thread.interrupted</code>, if <code>true</code> then a
           * {@link StopConditionException} will be thrown. The interrupted status
           * will be <code>true</code> if a call to <code>Thread.interrupt</code>
           * was previously made.
           * <p>
           * The thread's interrupted status is cleared by this method before the
           * <code>StopConditionException</code> is thrown.
           *
           * @throws   StopConditionException
           *           If the current thread's was previously interrupted.
           * @throws   SilentUnwindException
           *           If the connection ended prematurely end the current thread
           *           needs to stop.
           */
          public void honorStopCondition()
          throws StopConditionException,
                 SilentUnwindException
          {
             wa.honorStopCondition();
          }

          /**
           * Method called when the processing needs to be terminated. This will set a flag which will
           * indicate that the processing should be ended by raising a {@link
           * LeaveUnwindException error}, on the next {@link #honorStopCondition()} call.
           */
          public void abortProcessing()
          {
             if (workArea.get(false) == null)
             {
                // if the context does not exist, do not create it at this time
                return;
             }

             wa.abortProcessing = true;
          }

          /**
           * Enable/disable nested error mode. This mode allows for storing an error
           * which is generated during the processing of a different error, so that
           * during {@link #honorError} this can be re-thrown.  This duplicates some
           * obscure behavior seen in Progress.
           *
           * @param    mode
           *           <code>true</code> to enable, <code>false</code> to disable.
           */
          public void setNestedMode(boolean mode)
          {
             wa.nested = mode;
          }

          /**
           * Reports on the enable/disable state of nested error mode. This mode
           * allows for storing an error which is generated during the processing of
           * a different error, so that during {@link #honorError} this can be
           * re-thrown.
           *
           * @return   <code>true</code> if nested error mode is enabled.
           */
          public boolean isNestedMode()
          {
             return wa.nested;
          }

          /**
           * Returns the current block type.
           *
           * @return   The type of the current block.
           */
          public BlockType getBlockType()
          {
             BlockDefinition blk = wa.blocks.peek();

             return blk.type;
          }

          /**
           * Get the top {@link BlockDefinition}.
           *
           * @return   See above.
           */
          public BlockDefinition getBlock()
          {
             return wa.blocks.peek();
          }

          /**
           * Returns the index of the full transaction block, or -1 if none exists.
           *
           * @return   The index of the full transaction block, or -1 if none exists.
           */
          public int getFullTransactionBlock()
          {
             return TransactionManager.getFullTransactionBlock(wa);
          }

          /**
           * Returns the current nesting level.
           *
           * @return   the current nesting level.
           */
          public int getNestingLevel()
          {
             return wa.blocks.size();
          }

          /**
           * Check if this is block is the block associated with the root external program,
           * in the 4GL legacy stack.
           *
           * @return   See above.
           */
          public boolean isRootNestingLevel()
          {
             int nestingLevel = getNestingLevel();

             return nestingLevel == (wa.isRemote() ? 3 : 2);
          }

          /**
           * Check if this is block is the block associated with the root external program,
           * in the 4GL legacy stack using the nearest external block.
           *
           * @return   See above.
           */
          public boolean isRootExternal()
          {
             return TransactionManager.isRootExternal();
          }

          /**
           * Store the given exception for re-throwing during {@link #honorError} if
           * nested error mode is enabled.  Note that only 1 error can ever be nested
           * and the last one set via this method will be the one that is honored.
           *
           * @param    err
           *           The error to store.
           */
          public void saveNestedError(ConditionException err)
          {
             if (wa.nested)
             {
                wa.nestedError = err;
             }
          }

          /**
           * Notifies this class that an interactive pause has occurred. This may
           * affect the processing of infinite loop protection depending on the
           * type of block being processed.  This state is set for the remainder of
           * the current iteration of the innermost block.
           */
          public void hadPause()
          {
             // peek at the current block def on the top of the stack
             BlockDefinition blk = wa.blocks.peek();
             blk.hadPause = true;
          }

          /**
           * Search the stack of scopes and return the level of the first external
           * block found.
           *
           * @return   The stack level of the nearest enclosing external block or
           *           -1 if there is no block marked as an external block. Level
           *           number is 1-based starting from the outermost scope.
           */
          public int findNearestExternal()
          {
             return TransactionManager.findNearestExternal(wa);
          }

          /**
           * This method is called before the initialization of a FOR or FOR EACH
           * block. It will clear the {@link WorkArea#offEndQueries} set and will
           * set the {@link WorkArea#offEndRegistration} flag to ON. While this flag
           * is ON, all created queries will be collected in the
           * {@link WorkArea#offEndQueries} set.
           * <p>
           * After the block initialization is finished, the
           * {@link #stopOffEndRegistration()} will be called.
           */
          public void startOffEndRegistration()
          {
             wa.offEndRegistration = true;
             wa.offEndQueries.push(new HashSet<>());
          }

          /**
           * This method is called after the initialization of a FOR or FOR EACH
           * block is finished. It will save all the {@link QueryOffEndListener}s
           * associated with the registered {@link WorkArea#offEndQueries queries}.
           */
          public void stopOffEndRegistration()
          {
             TransactionManager.stopOffEndRegistration(wa);
          }

          /**
           * Register the passed query for query off-end events. The passed queries
           * are all the queries created in the init method for a FOR or FOR EACH
           * block.
           * <p>
           * These queries will be able to end the loop when the last record is
           * reached, by overriding any user-defined END-KEY action. The action
           * wich is executed is the default UNDO, LEAVE action for END-KEY.
           *
           * @param  query
           *         The query which needs to be registered.
           */
          public void registerOffEndQuery(P2JQuery query)
          {
             TransactionManager.registerOffEndQuery(wa, query);
          }

          /**
           * Check if the given listener is registered for query off-end events and
           * can override the default END-KEY action (which is UNDO, LEAVE), when
           * the current block is FOR or FOR EACH.
           *
           * @param   listener
           *          A listener to be checked if is registered for off-end events.
           *
           * @return  <code>true</code> if the listener is registered for query
           *          off-end event.
           */
          public boolean isOffEndListener(QueryOffEndListener listener)
          {
             return TransactionManager.isOffEndListener(wa, listener);
          }

          /**
           * Iterate all registered off-end listeners and call the
           * {@link QueryOffEndListener#initialize() initialize} or
           * {@link QueryOffEndListener#finish() finish} method, depending on the
           * flag.
           *
           * @param   init
           *          <code>true</code> if the listeners are initialized; else, the
           *          {@link QueryOffEndListener#finish()} method is called.
           */
          public void processOffEndListeners(boolean init)
          {
             TransactionManager.processOffEndListeners(wa, init);
          }

          /**
           * Get the topmost set of off-end {@link QueryOffEndListener listeners}.
           *
           * @param   clear
           *          <code>true</code> if the top-most off-end listeners set
           *          should be cleared.
           */
          public Set<QueryOffEndListener> getOffEndListeners(boolean clear)
          {
             return TransactionManager.getOffEndListeners(wa, clear);
          }

          /**
           * Register the single output parameter assigner for the current block.
           * This object will be deregistered by the {@link BlockManager} when it is
           * time to process assign-backs from function/procedure output parameters
           * to the database record fields with which they are associated.
           *
           * @param   opa
           *          An output parameter assigner.
           *
           * @throws  IllegalStateException
           *          if a second output parameter assigner is registered for a block
           *          before the current one is deregistered.  There can be only one.
           */
          public void registerOutputParameterAssigner(OutputParameterAssigner opa)
          {
             TransactionManager.registerOutputParameterAssigner(wa, opa);
          }

          /**
           * Defer handling of an exception thrown by a commit/rollback notification
           * callback on a registered {@link Commitable}, so that iteration or pop
           * scope processing can be continued without interruption.  This ensures
           * that all <code>Commitable</code>s registered after the errant one still
           * receive their commit/rollback notifications, and that (in the case of
           * popping a scope), registered {@link Scopeable}s still receive their
           * scope finished notifications.
           * <p>
           * If no error is already pending, <code>exc</code> is stored, to be thrown
           * upon conclusion of processing the current block scope or iteration.  If
           * an error is already pending, <code>exc</code> is discarded.  In either
           * case, <code>exc</code> is logged if error-level logging is enabled.
           *
           * @param   action
           *          A brief description of the action occurring at the time the
           *          error was encountered, such as "commit" or "master rollback".
           * @param   thr
           *          The error caught during processing of a commit or rollback
           *          notification.
           *
           * @see     WorkArea#handleDeferredError
           */
          public void deferError(String action, Throwable thr)
          {
             TransactionManager.deferError(wa, action, thr, false);
          }

          /**
           * Defer handling of an exception thrown by a commit/rollback notification
           * callback on a registered {@link Commitable}, so that iteration or pop
           * scope processing can be continued without interruption.  This ensures
           * that all <code>Commitable</code>s registered after the errant one still
           * receive their commit/rollback notifications, and that (in the case of
           * popping a scope), registered {@link Scopeable}s still receive their
           * scope finished notifications.
           * <p>
           * If no error is already pending, <code>exc</code> is stored, to be thrown
           * upon conclusion of processing the current block scope or iteration.  If
           * an error is already pending, <code>exc</code> is discarded.  In either
           * case, <code>exc</code> is logged if error-level logging is enabled.
           *
           * @param   action
           *          A brief description of the action occurring at the time the
           *          error was encountered, such as "commit" or "master rollback".
           * @param   thr
           *          The error caught during processing of a commit or rollback
           *          notification.
           * @param   silent
           *          If <code>true</code> then exception is not logged.
           *
           * @see     WorkArea#handleDeferredError
           */
          public void deferError(String action, Throwable thr, boolean silent)
          {
             TransactionManager.deferError(wa, action, thr, silent);
          }

          /**
           * Search the stack of scopes and return the level of the first top level
           * block found.
           *
           * @return   The stack level of the nearest enclosing top level block or
           *           -1 if there is no block marked as a top level block.
           */
          public int findNearestTopLevel()
          {
             return TransactionManager.findNearestTopLevel(wa);
          }

          /**
           * Search the stack of scopes and return the type of first top level block found.
           *
           * @return   The nearest enclosing top level block or {@link BlockType#UNKNOWN} if
           *           there is no block marked as a top level block.
           */
          public BlockType getNearestTopLevelType()
          {
             return TransactionManager.getNearestTopLevelType(wa);
          }

          /**
           * Attempt to initiate a rollback. A rollback is initiated if the transaction level
           * of the current scope is a SUB-TRANSACTION or TRANSACTION and if there is no pending
           * rollback. If a rollback is already pending a rollback will be initiated when the scope
           * is being popped. See {@link #popScope()}.
           */
          public void attemptRollback()
          {
             if (currentTransactionLevel() != NO_TRANSACTION && !wa.isRollbackPending())
             {
                rollback();
             }
          }

          /**
           * Worker that notifies all objects in the commit list that they must
           * commit (persist any state).
           * <p>
           * On the last iteration of a loop or block which ends in success (in
           * which the flow of control reaches the natural end of the block), both
           * will be called in succession.  However, other forms of exit which are
           * still considered successful (i.e. return and break which are not the
           * result of condition processing) will only execute <code>popScope</code>
           * and <code>iterate</code> will be bypassed.
           * <p>
           * It is important to note that the list of commitables in blk is
           * iterated in reverse order from that in which they are registered with
           * the <code>TransactionManager</code>.  This is done to ensure that
           * database transactions (which are registered before their corresponding
           * buffers) are always committed after all buffers are committed.
           *
           * @throws   ErrorConditionException
           *           If validation fails.
           */
          void processValidate()
          throws ErrorConditionException
          {
             wa.processValidate();
          }

          /**
           * Get the BlockDefinition instance of the nearest block of one of the
           * following types:
           * <ul>
           *    <li>{@link BlockType#EXTERNAL_PROC external procedure}</li>
           *    <li>{@link BlockType#INTERNAL_PROC internal procedure}</li>
           *    <li>{@link BlockType#FUNCTION user-defined function}</li>
           *    <li>{@link BlockType#TRIGGER trigger}</li>
           *    <li>{@link BlockType#DATABASETRIGGER trigger}</li>
           *    <li>{@link BlockType#EDITING editing}</li>
           * </ul>
           *
           * @return   See above.
           */
          BlockDefinition nearestExecutingBlock()
          {
             return TransactionManager.nearestExecutingBlock(wa);
          }

          /**
           * Get the executing block at the specified level.
           *
           * @param    levels
           *           Zero-based depth of the target block, starting from the innermost block.
           *
           * @return   The found block, or <code>null</code> if the index is outside of the block depth.
           */
          BlockDefinition getBlock(int levels)
          {
             return TransactionManager.getBlock(wa, levels);
          }

          /**
           * Deregister the output parameter assigner for the current block, if any.
           * It is expected that only the {@link BlockManager} will invoke this
           * method.
           *
           * @return  Output parameter assigner for this block, or <code>null</code>
           *          if none was registered.
           */
          OutputParameterAssigner deregisterOutputParameterAssigner()
          {
             return TransactionManager.deregisterOutputParameterAssigner(wa);
          }

          /**
           * Adds the given object into the list of objects which require undo
           * notifications for the next block that is opened.
           * <p>
           * This form of registration is useful for objects whose natural lifetime
           * is scoped to the top-level block.  In particular, it is necessary for
           * objects that are constructed during a class' constructor/initializer
           * (before the external procedure block has opened).  Such objects cannot
           * simply register since their construction is not actually enclosed by the
           * block.
           * <p>
           * When the deferred registration occurs, it will NOT be global and will
           * NOT be added to registered Commitables.
           * <p>
           * This notification will occur whether or not a transaction is active.
           * <p>
           * These registrations naturally are removed when the associated scope
           * is removed.
           * <p>
           * The given object will only be added if it is not already in the list.
           *
           * @param    target
           *           The object references to be registered.  This cannot be
           *           <code>null</code>.
           */
          void registerNext(LazyUndoable... target)
          {
             TransactionManager.registerAt(wa, ScopeLevel.NEXT, target);
          }

          /**
           * Adds the given object into the list of objects which require undo
           * notifications for the next block that is opened.
           * <p>
           * This form of registration is useful for objects whose natural lifetime
           * is scoped to the top-level block.  In particular, it is necessary for
           * objects that are constructed during a class' constructor/initializer
           * (before the external procedure block has opened).  Such objects cannot
           * simply register since their construction is not actually enclosed by the
           * block.
           * <p>
           * When the deferred registration occurs, it will NOT be global and will
           * NOT be added to registered Commitables.
           * <p>
           * This notification will occur whether or not a transaction is active.
           * <p>
           * These registrations naturally are removed when the associated scope
           * is removed.
           * <p>
           * The given object will only be added if it is not already in the list.
           *
           * @param    scope
           *           The scope where to register the undoable targets.
           * @param    target
           *           The object references to be registered.  This cannot be
           *           <code>null</code>.
           */
          void registerAt(ScopeLevel scope, LazyUndoable... target)
          {
             TransactionManager.registerAt(wa, scope, target);
          }

          /**
           * Register the specified targets as undoables.  This will mark the object as undoable and save
           * the current tx level, regardless if a tx is active or not.
           *
           * @param    undoTargets
           *           List of object references to be registered.  This cannot be
           *           <code>null</code>.
           */
          public void registerCurrent(LazyUndoable... undoTargets)
          {
             TransactionManager.registerCurrent(wa, undoTargets);
          }

          /**
           * Adds the object into a scoped dictionary of objects requiring UNDO support. This registry
           * is used to backup the objects at the beginning of transactions or sub-transactions.
           * <p>
           * These registrations naturally are removed when the associated scope is removed. However,
           * the global scope is long-lived. Special care is necessary to prevent the same instance of
           * global shared variables being added multiple times.
           *
           * @param    transLevel
           *           The transaction level to set for this undoable.
           * @param    persistent
           *           Flag indicating if the undoable is defined in a persistent procedure
           * @param    When not-null, persistent must be true and the referent represents the instance where to
           *           look for undoables.  Otherwise, THIS-PROCEDURE will be used.
           * @param    undoTargets
           *           The object references to be rolled back on failure.  This
           *           cannot be <code>null</code>.
           */
          void registerAt(int transLevel, boolean persistent, Object referent, LazyUndoable... undoTargets)
          {
             TransactionManager.registerAt(wa, transLevel, persistent, referent, undoTargets);
          }

          /**
           * Adds the object into a scoped dictionary of objects requiring UNDO support. This registry
           * is used to backup the objects at the beginning of transactions or sub-transactions.
           * <p>
           * These registrations naturally are removed when the associated scope is removed. However,
           * the global scope is long-lived. Special care is necessary to prevent the same instance of
           * global shared variables being added multiple times.
           *
           * @param    transLevel
           *           The transaction level to set for this undoable.
           * @param    persistent
           *           Flag indicating if the undoable is defined in a persistent procedure
           * @param    undoTargets
           *           The object references to be rolled back on failure.  This
           *           cannot be <code>null</code>.
           */
          void registerAt(int transLevel, boolean persistent, LazyUndoable... undoTargets)
          {
             TransactionManager.registerAt(wa, transLevel, persistent, undoTargets);
          }

          /**
           * Enable tracking of all the undaobles defined in the next instantiating external program.
           */
          void trackUndoables()
          {
             wa.trackUndoables = true;
             wa.trackedUndoables = new IdentityHashMap<>();
          }

          /**
           * Untrack (by removing from all the tx blocks) all the undoables defined and registered for
           * the specified external program.
           *
           * @param    referent
           *           The external program instance.
           */
          void untrackUndoables(Object referent)
          {
             TransactionManager.untrackUndoables(wa, referent);
          }

          /**
           * Rollback the transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
           *
           * @param    chained
           *           Flag indicating if a new transaction will be started.
           */
          void rollbackTx(boolean chained)
          {
             TransactionManager.rollbackTx(wa, chained);
          }

          /**
           * Commit the transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
           *
           * @param    chained
           *           Flag indicating if a new transaction will be started.
           */
          void commitTx(boolean chained)
          {
             TransactionManager.commitTx(wa, chained);
          }

          /**
           * Begin an explicit transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
           * <p>
           * This explicitly marks this tx as being open by the
           * {@link TransactionResource#getTxInitProcedure() TRANS-INIT-PROCEDURE}.
           */
          void beginTx()
          {
             TransactionManager.beginTx(wa, null);
          }

          /**
           * End an explicit transaction, at the {@link #APPSERVER_ROOT_BLOCK_INDEX root FWD block}.
           */
          void endTx()
          {
             TransactionManager.endTx(wa, null);
          }

          /**
           * Detect if the top-most block is a database trigger
           * @return
           *    True if the top-most block is a database trigger, false otherwise
           */
          public boolean isTopLevelDatabaseTrigger()
          {
             return TransactionManager.isTopLevelDatabaseTrigger(wa);
          }

          /**
           * Used to make a weak block stack snapshot. The stack is a copy while the content
           * should still reference the original definitions.
           *
           * @return    A new stack containing the same block definitions.
           */
          public Stack<BlockDefinition> copyStack()
          {
             Stack<BlockDefinition> copy = new Stack<>();
             copy.addAll(wa.blocks);
             return copy;
          }
       }

       /**
        * Helper class to expose context-local state for undoable support.
        */
       static class UndoableHelper
       {
          /** The {@link WorkArea} instance. */
          private WorkArea wa;

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

          /**
           * Obtain the context-local error helper.
           *
           * @return   The error helper.
           */
          public ErrorManager.ErrorHelper getErrorHelper()
          {
             return wa.em;
          }

          /**
           * Register the undoable with all transaction blocks (currently in the stack) started after
           *  the given transaction level.
           *
           * @param    src
           *           The undoable instance.
           *
           * @return   The current transaction level, as in {@link WorkArea#txNestingLevel}.
           */
          public int registerUndoable(LazyUndoable src)
          {
             // the transaction level at which the given Undoable was last registered.
             int transLevel = src.getTransLevel();

             if (wa.txNestingLevel <= transLevel)
             {
                // nothing to do, no new tx blocks on the stack
                return wa.txNestingLevel;
             }

             boolean global = src.isGlobal();

             LazyUndoable snapshot = null;
             BlockDefinition blk = null;

             int idx = wa.blocks.size() - 1;
             while (idx >= 0)
             {
                blk = wa.blocks.get(idx);

                if (blk.level == NO_TRANSACTION)
                {
                   idx = idx - 1;
                   continue;
                }

                // global vars register all the way up the stack, until the full tx block is found
                if (!global && blk.txNestingLevel <= transLevel)
                {
                   break;
                }

                if (blk.undoables == null)
                {
                   blk.undoables = new ArrayList<>();
                }

                if (snapshot == null)
                {
                   if (global && src instanceof BaseDataType)
                   {
                      // TODO: this is an attempt to fix some quirks with NEW GLOBAL SHARED variables
                      // which are undone in a outer scope, greater than the defining one: in this
                      // case, the var seems to undo to unknown, but the solution is not yet complete.
                      // only the first global var gets undone, the other get 'deleted'.
                      snapshot = ((BaseDataType) src).instantiateUnknown();
                   }
                   else
                   {
                      snapshot = (LazyUndoable) src.deepCopy();
                   }
                }

                blk.undoables.add(new UndoablePair(src, snapshot));

                idx = idx - 1;

                if (blk.full)
                {
                   break;
                }
             }

             return wa.txNestingLevel;
          }

          /**
           * Check if undoable can be processed.
           *
           * @param   currentBlock
           *          {@code true} to check only for the current block;
           *          {@code false} to check only if a transaction has been started.
           *
           * @return  {@code true} if undoable can be processed.
           */
          public boolean processUndoables(boolean currentBlock)
          {
             return wa.processUndoables(currentBlock);
          }
       }

       /**
        * Stores global data relating to the state of the current context's
        * transactions.  Includes convenience methods for notifying listeners.
        */
       private static class WorkArea
       {
          /** Sprintf format specification for string representation. */
          public static final String spec = "<depth = %d; trans_level = %d; "   +
                                            "trans_label = %s; "                +
                                            "rollback_scope = %d; "             +
                                            "rollback_label = %s; "             +
                                            "rollback_pending = %b; in_quit = " +
                                            "%b; retry_scope = %d; "            +
                                            "retry_label = %s; "                +
                                            "ignore_err = %b>";

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

          /** Helper to use the ErrorManager without any context local lookups. */
          public ErrorManager.ErrorHelper em;

          /** The buffer manager instance, active only on server-side. */
          public BufferManager bm;

          /** The logical terminal instance, active only on server-side. */
          public LogicalTerminal lt;

          /** The set of pending scopeables to be registered for the next block. */
          private Scopeable[] pendingScopeables = null;

          /** StopAfterTimer of the current conversation block. */
          private StopAfterTimer stopAfterTimer = null;

          /**
           * Map of Commitables to get a commit notification after all other commits occur in a full transaction;
           * keys are the listeners, values are flags indicating whether listeners are auto-removed.
           */
          private Map<Object, Boolean> masterCommit = null;

          /**
           * Map of Finalizables to get a finish notification after all other finish notifications occur in a full
           * transaction;  keys are the listeners, values are flags indicating whether listeners are auto-removed.
           */
          private Map<Object, Boolean> masterFinish = null;

          /** Map of BatchListeners to get batch start and stop notifications. */
          private final List<BatchListener> batchListeners = new LinkedList<>();

          /** Stack level of the full transaction if one is currently active. */
          // having a package-private field avoids a synthetic getter method emitted by javac
          int transLevel = -1;

          /** The number of blocks with full or sub-transaction level, in the current stack.*/
          private int txNestingLevel = -1;

          /** Defines the scope at which rollback will occur. */
          private int rollbackScope = -1;

          /** Flag to denote if a rollback is pending. */
          private boolean rollbackPending = false;

          /** Cause (if available) of pending rollback. */
          private Throwable rollbackPendingCause = null;

          /** Flag to denote if the exit is due to a quit condition. */
          private boolean processingQuit = false;

          /** Defines the scope at which a pending retry will occur. */
          private int retryScope = -1;

          /**
           * Defines if an error condition should be ignored until reaching the
           * caller of a top-level block.
           */
          private boolean ignoreError = false;

          /** Throwable caught during commit/rollback processing but deferred. */
          private Throwable deferredError = null;

          /** Enables/disables nested error support at runtime. */
          private boolean nested = false;

          /** Exception caught during processing another error. */
          private ConditionException nestedError = null;

          /** Defines a single global scope which is not kept in the stack. */
          private BlockDefinition globalBlock = null;

          /**
           * Tracks block definitions with the top of the stack as the innermost
           * scope.
           */
          // having a package-private field avoids a synthetic getter method emitted by javac
          Stack<BlockDefinition> blocks = new Stack<>();

          /** <code>Finalizable</code>s which need deferred registration. */
          private ArrayList<Finalizable> deferredExtFinalizables = null;

          /** <code>Undoable</code>s which need deferred registration. */
          private IdentityHashMap<LazyUndoable, Object> deferredUndoables = null;

          /** Set of undoable dynamic extent vars, to be registered with the invoked procedure. */
          private Set<BaseDataType[]> deferredDynExt = null;

          /** Map of undoables defined in persistent procedures. */
          private Map<Object, IdentityHashMap<LazyUndoable, Object>> procUndoables = new IdentityHashMap<>();

          /**
           * Flag indicating if undoables need to be tracked at ext program instantiation, thus to
           * be registered with a persistent procedure.  It will be activated only when the
           * instantiated external program is being ran persistent.
           */
          private boolean trackUndoables = false;

          /**
           * The map of undoables which are defined in a new instantiated external program, ran
           * persistent.
           */
          private IdentityHashMap<LazyUndoable, Object> trackedUndoables = null;

          /** Collection or map currently being iterated. */
          private Object nowIterating = null;

          /** Current operation. */
          private int op = OP_NONE;

          /** Registered STOP condition handlers */
          private List<StopConditionVetoHandler> stopHandlers = new LinkedList<>();

          /**
            * A stack of registered query off-end listeners, which interrupt the
            * FOR or FOR EACH block when a {@link QueryOffEndException} is
            * caught (the user defined END-KEY action is ignored and will perform
            * a UNDO, LEAVE).
            */
          private ArrayDeque<Set<QueryOffEndListener>> offEndListeners = new ArrayDeque<>(10);

          /**
           * Registered <code>Resettable</code>s which should be reset at
           * specific points of block processing.
           */
          private ArrayList<Resettable> resettables = new ArrayList<>(1);

          /**
           * A stack of sets of queries registered during FOR or FOR EACH block initialization. Only
           * {@link QueryOffEndException} triggered by the queries from a given set can end a FOR or FOR EACH
           * block, even if the END-KEY action is overridden. The sets are organized in the stack because
           * in the {@code init()} section of a block not only off-end query registration can happen, but also
           * buffer flush and call of database triggers which may have their own FOR or FOR EACH blocks.
           */
          private ArrayDeque<Set<P2JQuery>> offEndQueries = new ArrayDeque<>();

          /**
           * Flag indicating that the block executes its {@link Block#init()}
           * method. Only variants of FOR or FOR EACH blocks will set the flag to
           * <code>true</code>. During block initialization, all created queries
           * will be collected in the {@link #offEndQueries} set.
           */
          private boolean offEndRegistration = false;

          /**
           * Value of {@code offEndRegistration} flag saved for the period of off-end query registration pause.
           */
          private boolean savedOffEndRegistration = false;

          /**
           * Flag indicating the processing should be terminated.
           */
          private volatile boolean abortProcessing = false;

          /** Flag that disables calls to the client side. */
          private boolean headless = false;

          /**
           * The cached {@link ProcedureManager}'s scopeable instance, to avoid resolving
           * context-local data.
           */
          private Scopeable procScopeable;

          /** Cached value of the {@link AppServerManager#isRemote()} state.*/
          private Boolean remote = null;

          /** Flag indicating if this is a user-interactive session. */
          private Boolean interactive = null;

          /** Keep stop-after exception in order to re-throw it to the timeout block */
          private LegacyStopException stopAfterException = null;

          /**
           * Worker that notifies all objects in the commit list that they must
           * commit (persist any state).
           * <p>
           * On the last iteration of a loop or block which ends in success (in
           * which the flow of control reaches the natural end of the block), both
           * will be called in succession.  However, other forms of exit which are
           * still considered successful (i.e. return and break which are not the
           * result of condition processing) will only execute <code>popScope</code>
           * and <code>iterate</code> will be bypassed.
           * <p>
           * It is important to note that the list of commitables in blk is
           * iterated in reverse order from that in which they are registered with
           * the <code>TransactionManager</code>.  This is done to ensure that
           * database transactions (which are registered before their corresponding
           * buffers) are always committed after all buffers are committed.
           *
           * @throws   ErrorConditionException
           *           If validation fails.
           */
          private void processValidate()
          throws ErrorConditionException
          {
             if (FwdServerJMX.JMX_DEBUG)
             {
                VALIDATE.timer(this::processValidateImpl);
             }
             else
             {
                processValidateImpl();
             }
          }

          /**
           * Worker that notifies all objects in the commit list that they must
           * commit (persist any state).
           * <p>
           * On the last iteration of a loop or block which ends in success (in
           * which the flow of control reaches the natural end of the block), both
           * will be called in succession.  However, other forms of exit which are
           * still considered successful (i.e. return and break which are not the
           * result of condition processing) will only execute <code>popScope</code>
           * and <code>iterate</code> will be bypassed.
           * <p>
           * It is important to note that the list of commitables in blk is
           * iterated in reverse order from that in which they are registered with
           * the <code>TransactionManager</code>.  This is done to ensure that
           * database transactions (which are registered before their corresponding
           * buffers) are always committed after all buffers are committed.
           *
           * @throws   ErrorConditionException
           *           If validation fails.
           */
          private void processValidateImpl()
          {
             if (LOG.isLoggable(Level.FINE))
             {
                log(Level.FINE, "processValidate", null);
             }

             BlockDefinition blk = blocks.peek();
             if (blk.commitables == null)
             {
                return;
             }

             try
             {
                nowIterating = blk.commitables;
                boolean aggressiveFlush = blk.forFlushing || blk.external;

                ArrayList<Commitable> commitables = blk.commitables;
                for (int i = 0; i < commitables.size(); i++)
                {
                   Commitable target = commitables.get(i);
                   target.validate(blk.full, aggressiveFlush);
                }
             }

             finally
             {
                nowIterating = null;
             }
          }

          /**
           * Check if the current session is interactive (i.e. not appserver agent or batch process).
           *
           * @return    See above.
           */
          private boolean isInteractive()
          {
             if (interactive == null)
             {
                interactive = !(isRemote() || LogicalTerminal.isInBatchMode());
             }

             return interactive;
          }

          /**
           * Check if this session is an appserver session or not.
           *
           * @return   The {@link #remote} state.
           */
          private boolean isRemote()
          {
             if (remote == null)
             {
                remote = AppServerManager.isRemote();
             }

             return remote.booleanValue();
          }

          /**
           * Reports if the current scope defines the outermost transaction block
           * (which is the scope at which final commit processing must occur).
           *
           * @return   <code>true</code> if the current scope defines the top-level
           *           transaction.
           */
          private boolean isFullTransaction()
          {
             BlockDefinition blk = blocks.peek();
             return blk.full;
          }

          /**
           * Reports if there is a current transaction which is active.
           *
           * @return    <code>true</code> if a transaction is active.
           */
          private boolean isTransaction()
          {
             return transLevel != -1;
          }

          /**
           * Reports if there is a rollback operation pending.
           *
           * @return    <code>true</code> if a rollback has been requested but not
           *            yet processed.
           */
          private boolean isRollbackPending()
          {
             return rollbackPending;
          }

          /**
           * Reports if there is a current transaction which is active.
           *
           * @return    <code>true</code> if a transaction is active.
           */
          private boolean isTransactionBlock()
          {
             return (transLevel != -1) && (transLevel == (blocks.size() - 1));
          }

          /**
           * Starts a new top-level transaction (requires that there is no current
           * transaction which is active).
           *
           * @param     level
           *            Stack level of the scope that is to be specified as the top
           *            level scope.
           */
          private void setTransactionLevel(int level)
          {
             if (transLevel != -1 && level != -1)
             {
                throw new IllegalArgumentException("Transaction is already active!");
             }

             transLevel = level;

             // 'close' all active transactions
             if (!isTransaction())
             {
                synchronized (transactionData)
                {
                   SecurityManager sm = SecurityManager.getInstance();
                   if (sm != null)
                   {
                      Integer userId = sm.sessionSm.getSessionId();
                      if (userId != null)
                      {
                         for (Map.Entry<String, DbTransactions> entry : transactionData.entrySet())
                         {
                            DbTransactions dbData = entry.getValue();
                            // do not remove the [entry] from [transactionData] even if there are
                            // no more transactions in the database because it also keeps the
                            // transactions counter that will be used for the upcoming transactions
                            dbData.transactions.remove(userId);
                            // mark the in-memory as dirty in order to remove the record with
                            // next flush operation
                            dbData.isDirty = true;
                         }
                      }
                   }
                }
             }
          }

          /**
           * Tests the interrupted status of the current thread using
           * <code>Thread.interrupted</code>, if <code>true</code> then a
           * {@link StopConditionException} will be thrown. The interrupted status
           * will be <code>true</code> if a call to <code>Thread.interrupt</code>
           * was previously made.
           * <p>
           * The thread's interrupted status is cleared by this method before the
           * <code>StopConditionException</code> is thrown.
           *
           * @throws   StopConditionException
           *           If the current thread's was previously interrupted.
           * @throws   SilentUnwindException
           *           If the connection ended prematurely end the current thread
           *           needs to stop.
           */
          private void honorStopCondition()
          throws StopConditionException,
                 SilentUnwindException
          {
             if (abortProcessing)
             {
                throw new SilentUnwindException();
             }

             if (Thread.interrupted())
             {
                if (LOG.isLoggable(Level.FINE))
                {
                   log(Level.FINE,
                       "honorStopCondition",
                       "STOP (honoring deferred thread interruption)!");
                }

                String errmsg = "Thread was asynchronously interrupted.";
                StopConditionException exe = null;

                if (stopAfterTimer != null && stopAfterTimer.isStopAfterConditionRaised())
                {
                   exe = new StopAfterConditionException(errmsg);
                }
                else
                {
                   exe = new StopConditionException(errmsg);
                }

                ErrorManager.handleStopException(exe);
             }
          }

          /**
           * If the LegacyStopException should be propagated up the stack
           * to the block that raised the STOP condition after a stop-after
           * timeout.
           *
           * @throws   blk
           *           BlockDefinition of the current block.
           */
          private void honorStopAfterCondition(BlockDefinition blk)
          throws LegacyStopException
          {
             LegacyStopException lex = stopAfterException;

             if (lex == null)
             {
                return;
             }
             ObjectVar<? extends Stop> error = lex.getError();

             if (stopAfterTimer != null && !stopAfterTimer.isStoppedBlock(blk))
             {
                throw lex;
             }
             else if (stopAfterTimer != null && stopAfterTimer.isStopAfterConditionRaised())
             {
                if (stopAfterTimer.isStoppedBlock(blk))
                {
                   stopAfterTimer.cleanUp();
                }

                // we need a decrement here, as the instance is also 'hooked' by LegacyErrorException
                error.decrement();
                // release this reference
                error.set(null);
                stopAfterException = null;
             }
          }

          /**
           * Returns the level specifying the rollback scope that is pending.
           *
           * @return    The level of the scope to be rolled back or -1 if the
           *            closest containing transaction or sub-transaction should
           *            be rolled back.
           */
          private int getRollbackScope()
          {
             return rollbackScope;
          }

          /**
           * Handle a pending, deferred error, represented by an exception thrown
           * by a commit or rollback notification callback on a registered {@link
           * Commitable}, and caught within {@link #processCommit} or {@link
           * #processRollback}, respectively.  If there is currently no deferred
           * error, this method returns immediately.  Otherwise, a debug message
           * is logged, the deferred error is reset to <code>null</code>, and the
           * stored exception is thrown.  It is intended that this method is called
           * at the end of iterate or pop scope processing, after all registered
           * <code>Commitable</code>s and <code>Scopeable</code>s have been notified
           * of the state change.
           *
           * @see     #deferError
           */
          private void handleDeferredError()
          throws RuntimeException
          {
             if (deferredError == null)
             {
                // nothing to do
                return;
             }

             if (LOG.isLoggable(Level.SEVERE))
             {
                String msg = "Throwing deferred error (" + deferredError.getMessage() + ")";
                log(Level.SEVERE, "handleDeferredError", msg);
             }

             // reset the error condition
             Throwable thr = deferredError;
             deferredError = null;

             // condition exceptions get thrown directly since they are "safe"
             if (!(thr instanceof ConditionException))
             {
                // anything else is wrapped in an unchecked exception and becomes an abnormal end
                thr = new RuntimeException("Deferred error processing.", thr);
             }

             // honor the error
             throw (RuntimeException) thr;
          }

          /**
           * Report scope depth.
           *
           * @return  Number of blocks currently on stack.
           */
          private int depth()
          {
             return blocks.size();
          }

          /**
           * Notify all master Finalizables of a retry event.  Listeners are
           * notified in the order in which they were registered.
           */
          private void notifyMasterRetry()
          {
             if (masterFinish == null)
             {
                return;
             }

             if (LOG.isLoggable(Level.FINE))
             {
                log(Level.FINE, "notifyMasterRetry", null);
             }

             try
             {
                nowIterating = masterFinish;
                op = OP_RETRY;

                for (Object it : masterFinish.keySet())
                {
                   // Process notification, deferring any error until we are in a
                   // safe state to handle it.
                   Finalizable target = (Finalizable) it;
                   try
                   {
                      target.retry();
                   }
                   catch (Throwable thr)
                   {
                      deferError("master retry", thr);
                   }
                }
             }

             finally
             {
                nowIterating = null;
                op = OP_NONE;
             }
          }

          /**
           * Notify all master Finalizables of an iteration or finished event.
           * <p>
           * After each notification, the corresponding {@code Finalizable} is removed, if it was registered as
           * auto-removable.  Listeners are notified in the order in which they were registered.
           *
           * @param   iterate
           *          {@code true} to notify of an iterate event; {@code false} to notify of a finished event.
           */
          private void notifyMasterFinish(boolean iterate)
          {
             if (masterFinish == null)
             {
                return;
             }

             if (LOG.isLoggable(Level.FINE))
             {
                log(Level.FINE, "notifyMasterFinish", "iterate = " + iterate);
             }

             try
             {
                nowIterating = masterFinish;
                op = iterate ? OP_ITERATE : OP_FINISHED;

                for (Object it : masterFinish.keySet())
                {
                   // process notification, deferring any error until we are in a safe state to handle it
                   Finalizable target = (Finalizable) it;
                   if (iterate)
                   {
                      try
                      {
                         target.iterate();
                      }
                      catch (Throwable thr)
                      {
                         deferError("master iterate", thr);
                      }
                   }
                   else
                   {
                      try
                      {
                         target.finished();
                      }
                      catch (Throwable thr)
                      {
                         deferError("master finish", thr);
                      }
                   }
                }
             }
             finally
             {
                nowIterating = null;
                op = OP_NONE;
             }
          }

          /**
           * Notify all master Commitables of a rollback or commit event.
           * After each notification, the corresponding Commitable is removed,
           * if it was registered as auto-removable and <code>autoRemove</code>
           * is <code>true</code>.  Listeners are notified in the order in which
           * they were registered.
           *
           * @param   rollback
           *          <code>true</code> to notify of a rollback event;
           *          <code>false</code> to notify of a commit event.
           */
          private void notifyMasterCommit(boolean rollback)
          {
             if (masterCommit == null)
             {
                return;
             }

             if (LOG.isLoggable(Level.FINE))
             {
                log(Level.FINE, "notifyMasterCommit", "rollback = " + rollback);
             }

             try
             {
                nowIterating = masterCommit;

                for (Object it : masterCommit.keySet())
                {
                   // Process notification, deferring any error until we are in a safe state to handle it.
                   Commitable target = (Commitable) it;
                   if (rollback)
                   {
                      try
                      {
                         target.rollback(true);
                      }
                      catch (Throwable thr)
                      {
                         deferError("master rollback", thr);
                      }
                   }
                   else
                   {
                      try
                      {
                         target.commit(true);
                      }
                      catch (Throwable thr)
                      {
                         deferError("master commit", thr);
                      }
                   }
                }
             }
             finally
             {
                nowIterating = null;
             }
          }

          /**
           * Iterate through registered, master Finalizables and deregister those marked for auto-remove.
           */
          private void autoRemoveMasterFinish()
          {
             autoRemoveMasterFinish(false);
          }

          /**
           * Iterate through registered, master Finalizables and deregister those marked for auto-remove.
           * Before deregistering, it will also call {@link Finalizable#deleted()} based on the parameter
           * value given.
           *
           * @param   delete
           *          If the deleted() method of the Finalizable should be called.
           */
          private void autoRemoveMasterFinish(boolean delete)
          {
             if (masterFinish != null && canRemoveFromMap(masterFinish, "Finalizables"))
             {
                autoRemoveAndDelete(masterFinish, delete);
                if (masterFinish.isEmpty())
                {
                   masterFinish = null;
                }
             }
          }

          /** Iterate through registered, master Commitables and deregister those marked for auto-remove. */
          private void autoRemoveMasterCommit()
          {
             if (masterCommit != null && canRemoveFromMap(masterCommit, "Commitables"))
             {
                autoRemoveAndDelete(masterCommit, false);
                if (masterCommit.isEmpty())
                {
                   masterCommit = null;
                }
             }
          }

          /**
           * Test whether the {@code map} is NOT being iterated so elements stored there can be removed.
           *
           * @param   map
           *          The map to be tested.
           * @param   name
           *          The name/type of the elements contained in the map. Only for debugging.
           *
           * @return  {@code true} if the map can be altered by removing elements or {@code false} when a remove
           *          operation will cause a {@code ConcurrentModificationException}.
           */
          private boolean canRemoveFromMap(Map<Object, Boolean> map, String name)
          {
             if (nowIterating == map)
             {
                // prevent ConcurrentModificationException
                if (LOG.isLoggable(Level.WARNING))
                {
                   log(Level.WARNING,
                       "autoRemoveMaster" + name,
                       "Illegal attempt to auto-remove master " + name,
                       new Throwable());
                }

                return false;
             }

             return true;
          }

          /**
           * Iterate through the given map entries and remove all those whose values are {@code true}.
           * The key is a {@code Commitable} or {@code Finalizable} target, the value is its registered
           * auto-remove setting. For a {@code Finalizable} target, it also calls the
           * {@link Finalizable#deleted()} method.
           *
           * @param   map
           *          Map of registered targets and their auto-remove settings.
           * @param   delete
           *          Should be {@code true} when the map contains Finalizable targets and in this case
           *          the deleted() method of the Finalizable will be called.
           */
          private void autoRemoveAndDelete(Map<Object, Boolean> map, boolean delete)
          {
             Iterator<Map.Entry<Object, Boolean>> iter = map.entrySet().iterator();

             while (iter.hasNext())
             {
                Map.Entry<Object, Boolean> current = iter.next();
                if (current.getValue())
                {
                   if (delete)
                   {
                      Object key = current.getKey();
                      if (key instanceof Finalizable)
                      {
                         ((Finalizable) key).deleted();
                      }
                   }
                   iter.remove();
                }
             }
          }

          /**
           * Render this work area's data as a human readable string.
           *
           * @return   The human readable form of the global data.
           */
          public String toString()
          {
             BlockDefinition tblk = null;
             BlockDefinition rollbackBlk = null;
             BlockDefinition retryBlk = null;
             int size = blocks.size();

             if (transLevel != -1 && transLevel < size)
             {
                tblk = blocks.elementAt(transLevel);
             }

             if (rollbackScope != -1 && rollbackScope < size)
             {
                rollbackBlk = blocks.elementAt(rollbackScope);
             }

             if (retryScope != -1 && retryScope < size)
             {
                retryBlk = blocks.elementAt(retryScope);
             }

             Object[] parms = new Object[]
             {
                depth(),
                transLevel,
                (tblk == null) ? null : tblk.label,
                rollbackScope,
                (rollbackBlk == null) ? null : rollbackBlk.label,
                rollbackPending,
                processingQuit,
                retryScope,
                (retryBlk == null) ? null : retryBlk.label,
                ignoreError
             };

             return String.format(spec, parms);
          }

          /**
           * Check if undoable can be processed.
           *
           * @param   currentBlock
           *          {@code true} to check only for the current block;
           *          {@code false} to check only if a transaction has been started.
           *
           * @return  {@code true} if undoable can be processed.
           */
          private boolean processUndoables(boolean currentBlock)
          {
             if (!currentBlock)
             {
                return this.transLevel != -1;
             }

             return processUndoables(this.blocks.peek());
          }

          /**
           * Check if undoable can be processed by the current block.
           *
           * @return   {@code true} if a transaction has been started and the current block's
           *           transaction level is not {@link TransactionManager#NO_TRANSACTION}.
           */
          private boolean processUndoables()
          {
             return processUndoables(this.blocks.peek());
          }

          /**
           * Check if undoable can be processed by the current block.
           *
           * @param    blk
           *           The target block.
           *
           * @return   {@code true} if a transaction has been started and the current block's
           *           transaction level is not {@link TransactionManager#NO_TRANSACTION}.
           */
          private boolean processUndoables(BlockDefinition blk)
          {
             return this.transLevel != -1 && blk.level != NO_TRANSACTION;
          }
       }

       /**
        * Simple container that stores and returns a context-local instance of
        * the global work area.
        */
       private static class ContextContainer
       extends ContextLocal<WorkArea>
       {
          /**
           * Get the weight of this context-local var.
           *
           * @return   Always {@link WeightFactor#LEVEL_4}.
           */
          @Override
          public WeightFactor getWeight()
          {
             return WeightFactor.LEVEL_4;
          };

          /**
           * Obtains the context-local instance of the contained global work
           * area.  The first time this method is invoked in a context, a new
           * <code>WorkArea</code> instance is lazily created.
           *
           * @return  The work area associated with this context.
           */
          public WorkArea obtain()
          {
             WorkArea wa = get();
             if (wa == null)
             {
                wa = new WorkArea();
                wa.globalBlock = new BlockDefinition("globalScope",
                                                     NO_TRANSACTION,
                                                     PROP_NONE,
                                                     false,
                                                     false,
                                                     false,
                                                     false,
                                                     false,
                                                     false,
                                                     false,
                                                     UNKNOWN);

                // detect if headless mode is needed
                wa.headless = _isHeadless();

                // the following call MUST occur before creating Scopeables
                // below, in case a Scopeable initializer calls back into the
                // TransactionManager in such a way as to call this class'
                // obtain() method (prevents infinite loop and stack overflow)
                set(wa);

                wa.pm = ProcedureManager.getProcedureHelper();
                wa.em = ErrorManager.getErrorHelper();
                if (SessionManager.get() != null && !SessionManager.get().isLeaf())
                {
                   // only on server-side
                   wa.bm = BufferManager.get();
                   wa.lt = (wa.headless ? null : LogicalTerminal.getInstance());
                }

                wa.em.setHeadless(wa.headless);

                wa.procScopeable = ProcedureManager.getScopeable();
             }

             return wa;
          }

          /**
           * Return <code>null</code> rather than providing a true initial value;
           * initial value is instead lazily created in {@link #obtain}.
           *
           * @return  <code>null</code>.
           */
          protected final WorkArea initialValue()
          {
             return null;
          }
       }

       /**
        * The transaction information for a database. The object holds the transaction counter and the
        * set of transactions mapped by the connection id of the users that created them.
        */
       private static class DbTransactions
       {
          /** The id of last transaction generated for this database. */
          private int lastTransactionId = 0;

          /**
           * Flags whether a transaction has started or ended since the last flush to persistence.
           * This is only used to quickly decide whether there are transactions to be flushed.
           */
          private boolean isDirty = false;

          /** The set of transaction ids of this database mapped by the users that opened them. */
          private Map<Integer, Integer> transactions = new HashMap<>();
       }
    }