ErrorManager.java

/*
** Module   : ErrorManager.java
** Abstract : Provides ERROR-STATUS, NO-ERROR and error message support.
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 GES 20050823   @21210 Created initial version which provides
**                           ERROR-STATUS and NO-ERROR support.
** 002 GES 20050829   @22362 Minor mods to more closely duplicate how
**                           Progress handles error text. There are
**                           cases where error text is added but no
**                           error condition is raised (even in a
**                           non-silent mode situation).
** 003 ECF 20051104   @23262 Added capability to extract error data from
**                           Java exceptions. This includes both formatted
**                           error texts as well as recording ErrorEntry
**                           objects. Overloaded recordOrThrowError for
**                           this purpose and added a set of overloaded
**                           errorTexts() methods.
** 004 ECF 20051205   @23698 Added recordOrShowError method variants.
**                           Displays an error message in an alert box and
**                           logs the error.
** 005 ECF 20051219   @23752 Added warning mode. When in warning mode,
**                           errors are reported to the UI using an alert
**                           box, rather than an exception being thrown.
** 006 ECF 20060117   @23959 Enabled show error code. Removed temporary
**                           code which used stderr and replaced with
**                           calls to LogicalTerminal.message().
** 007 GES 20060123   @24021 Moved to context-local storage.
** 008 GES 20060208   @24389 Errors are now displayed via message when
**                           not in silent error mode.
** 009 NVS 20060306   @24903 Errors occurring on the client are now passed
**                           to the server for centralized logging.
** 010 NVS 20060313   @24994 Error messages are displayed using Logical
**                           Terminal methods if on the server, and Client
**                           Exports directly otherwise.
** 011 GES 20060313   @24995 Use new logging infrastructure.
** 012 GES 20060626   @27569 Implemented scoped context local data which
**                           makes it safe to use silent error mode in
**                           a nested manner.
** 013 ECF 20060719   @28122 Changed logging level for silent mode errors
**                           from WARNING to FINE.
** 014 GES 20060807   @28454 Match new signature in ClientExports.
** 015 GES 20060927   @29966 Exposed displayError() as public.
** 016 GES 20061004   @30134 Added support for nested errors (errors
**                           generated during processing of another
**                           error).
** 017 NVS 20061017   @30470 Implemented ability to restart the session
**                           without exiting the process.
** 018 ECF 20071217   @36499 Added headless mode. Intended to support
**                           environments with no UI and limited logging.
**                           Implementation at this time is limited.
** 019 CA  20080530   @38463 Added methods related to displaying an error
**                           message before the application stops.
** 020 GES 20080619   @38886 Added a convenience method to format and
**                           display an error without throwing any
**                           exception, w/o being affected by silent
**                           error mode and without recording anything.
** 021 GES 20080624   @38912 Added an ignore flag to NOP all display,
**                           recording or exception generation. Cleaned
**                           up the API to meet coding standards.
** 022 CA  20080815   @39447 Added displayAbend - it displays an abend
**                           message, but no actual abend is triggered.
** 023 SVL 20081208   @40826 Match client exports API change.
** 024 ECF 20081215   @40913 Added throwError() variants. Unconditionally
**                           displays an error message and throws an error
**                           condition exception, regardless of silent
**                           error mode. Headless mode is still honored.
** 025 GES 20090422   @41908 Converted to standard string formatting.
** 026 SVL 20090612   @42669 showErrorAndAbend() can be executed on the client
**                           side.
** 027 ECF 20090622   @42987 Added new display option for errors. New variants
**                           of displayError() and buildErrorText() allow the
**                           standard message prefix to be omitted.
** 028 SIY 20090701   @43012 Made ErrorEntry public for use by other classes.
**                           Inferred generics. Minor cleanups.
** 029 SVL 20090803   @43476 showErrorAndAbend(int[],String[]) has been added.
** 030 GES 20090831   @43802 Removed stacking from the error-status data
**                           members. The 4GL doesn't stack the error list or
**                           the error flag. The previous stacking was an
**                           over-compensation for saving state across top-
**                           level block boundaries. The transaction mgr now
**                           handles saving/restoring the silent error mode
**                           flag in the necessary cases. This class now can
**                           be greatly simplified which also fixes some issues
**                           with the error-status system handle.
** 031 SVL 20090908   @43847 recordOrThrowError() saves error code in the
**                           ErrorConditionException.
** 032 GES 20090924   @44010 Completed headless support. Both the global flag
**                           and a context-specific flag are honored.
** 033 SIY 20110112          Minor refactorings and cleanups.
** 034 CA  20130112          Added asHandle, getType, isValid and isUnknown 
**                           methods, accessed by the ERROR-STATUS handle.
**                           Added new recordOrThrowError APIs.
** 035 CA  20130216          Added support for ERROR-OBJECT-DETAIL attribute.
** 036 CA  20130221          getType getter for TYPE attribute was renamed to getResourceType.
** 037 CS  20130221          Added conversion support for _msg() function
** 038 CA  20130222          Added missing setter for ERROR attribute.
** 039 OM  20130304          Refactored isValid and isUnknown of WrappedResource to valid 
**                           and unknown.
** 040 VMN 20130411          Added boolean parameter modal to recordOrShowError() method.
** 041 EVK 20130413          Implemented method getErrorNumberAtIndex
** 042 CA  20130612          ErrorConditionException's generated in recordOrThrowError will carry
**                           as cause a NumberedException identifying the error number and the
**                           associated message.
** 043 OM  20130606          Handle for soapFault set to null for the moment. This is a bug that
**                           manifests with pl/java and should be fixed (with lazy initialization ?)
** 044 EVL 20130718          Adding new method to display message and abend. Also it silently
**                           returns if NO-ERROR option is specified.
** 045 VMN 20130731          Adding asMsg parameter to recordOrThrowError() method.
** 046 CA  20130909          Added "prefix" parameter to recordOrShowError, to allow error 
**                           messages without double-asterisk prefix.
**                           Added "isError" parameter to recordOrShowError, to allow messages to 
**                           be recorded without setting the pending error flag.
** 047 CA  20130920          Fixed H046 - if ERROR attribute is set to false by recordOrShowError,
**                           than do not clean the error message list.
** 048 EVK 20130922          Added new addError version which will conditionally add the "**" to 
**                           the error message, based on the new prefix parameter.
** 049 CA  20130927          Static proxies need the unknown() API. Resource type is determined 
**                           from LegacyResource constants.
** 050 CA  20131219          Added setDontClean static implementation. Fixed implementation of
**                           isErrorFlag.
** 051 EVL 20131119          Separating error output from client implementation.
** 052 CA  20140107          Made setErrorObjectDetail package private.  The WorkArea.soapFault
**                           must be initialized to null by default, to allow ErrorManager usage
**                           from pl/java (for this reason, setErrorObjectDetail will create a new
**                           handle and assign it to WorkArea.soapFault).
** 053 GES 20140121          Added noRecordOrThrowError().
** 054 CA  20140513          Added a weight for the context-local var, to ensure predetermined 
**                           order during context reset.
** 055 VIG 20140701          Added separate error statuses for the current and pending statuses.
**                           Added class ErrorStatus, that corresponds to ERROR-STATUS handle.
**                           Removed clearList and ServerDataAccess.setError methods.
** 056 SVL 20140709          Prefix field was added to ErrorEntry.
** 057 VIG 20140709          Changed implementation of getElementAtIndex methods (_MSG),
**                           Added raisedConditions - list of error numbers and corresp. methods.
** 058 OM  20141015          Eliminate NPE in addRaisedCondition() when argument is null.
**                           Fixed postprocessOnAbend(String[] text) message.
** 059 SVL 20141209          Added ability to display error messages without finishing dot.
** 060 CA  20150308          Added support for client-side error data storage.  This allows 
**                           temporary disabling the error-management without a server-side trip.
** 061 CA  20150502          The resource ID needs to be kept at the resource, so it can be 
**                           accessed after the resource is deleted.
** 062 OM  20151004          Added setter stub for ERROR-STATUS:NUM-MESSAGES attribute. 
** 063 EVL 20160224          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 064 IAS 20160328          Client log support
** 065 HC  20160722          Added recordOrShowWarning() stub forwarding calls to
**                           recordOrShowError().
** 066 ECF 20160827          Minor refactoring to avoid expense of getting context-local work
**                           area multiple times in some API calls.
** 067 ECF 20160912          Minor logging change.
** 068 HC  20171003          Implemented SESSION:SUPPRESS-WARNINGS runtime support and the
**                           ability to handle (display or output depending on the runtime state)
**                           legacy warnings.
** 069 SVL 20171024          Added displayError(errmsg, noMessageBox).
** 070 CA  20171030          Made addError public.
** 071 GES 20171205          Reimplemented silent error mode using a worker that takes a lambda.
**                           This reads better in calling code. With this change, the silent mode
**                           now actually throws the error condition and it gets caught in the
**                           worker, which is more functionally correct because it aborts further
**                           processing on errors in the calling context rather than using
**                           isPendingError() to bypass other work. Anything with side effects
**                           causes issues in the old approach.
**     ECF 20171212          Added method to clear pending error status. In silent error mode, do
**                           not generate NumberedException in recordOrThrowError, in order to
**                           avoid the overhead of stack trace generation.
**     GES 20171220          Changed throwError() to set a force flag on the raised error
**                           condition such that the silent mode working will re-throw the error
**                           instead of supressing it. This is needed for some deferred error
**                           processing cases found in the persistence layer.
** 072 CA  20180503          Added SESSION:SYSTEM-ALERT-BOXES.
** 073 ECF 20190326          Minor performance improvement.
** 074 OM  20190611          Added id/parameters based APIs.
** 075 OM  20190720          Updated id/parameters error list.
** 076 OM  20191014          Added ERROR, schema validation and SAVE-ROW-CHANGES error messages.
** 077 IAS 20191022          Added DEBUG-ALERT support to the displayError method.
** 078 CA  20191119          Added nestedSilent(), a FWD-internal helper, which allows execution
**                           of code in silent-mode, even if the caller is already in NO-ERROR 
**                           mode (and preserving any previous error state).
** 079 CA  20200324          More fixes related to OO-like error processing via AppError.
**     CA  20200406          mustManageLegacyError must delegate the call to the server side.
**                           ERROR conditions generated by the FWD runtime must be deferred, so
**                           that they can be emitted by the client-side, too.
**     SBI 20200430          Added UnableToSetComHandleProperty and WhileProcessingComponentProperty
**                           errors.
**     CA  20200503          Added ERROR:ERROR-STRING stubs.
** 080 GES 20200803          Rework to expose ErrorHelper so that server-side callers can avoid context-local
**                           lookups.  Fixed swapped parameters to ErrorConditionException constructor which
**                           was treating an error code as a key code.
**     GES 20200820          Safety code in case ErrorHelper is requested on the client side.
**     GES 20200821          Added ErrorHelper.isSuppressWarnings().
**     ME  20201014          Do not append error number if zero, there are system errors thrown with number
**                           set to zero.
**     OM  20201020          Renamed warning to errorsAsWarnings and added accessors in ErrorHelper.
**                           Added some missing error messages.
**     OM  20201120          Changed method name to match no-bean prefixes buffer convention.
**         20201125          displayError is no-op in silent mode.
**     OM  20201203          Fixed handling of READ/ONLY attributes.
**     ME  20210128          Add helper method for 'invalid parameter' error and push all messages from an 
**                           OO error up the stack for silent execution.
**     CA  20210216          Allow the nestedSilent caller to process the errors logged during the invocation.
**     VVT 20210322          Fixed after the review. See #4880-107.
**     ME  20210322          Add an overload for recordOrThrowError to avoid adding the dot between message
**                           and number.
**     OM  20210312          Added error messages related to LOBs and LONGCHAR variables.
**     CA  20210629          Added error messages related to indexed dereferenced fields (lvalue and rvalue).
**     OM  20210809          Added error messages for serialization of tables to JSON objects.
**     CA  20210928          Added the text for error 12355.
**     CA  20211001          Added setter support for ERROR-STATUS:ERROR.
**     IAS 20211223          Special processing of errors raised by UDFs.
**     OM  20220112          Added error messages related to SKIP-DELETED-RECORD attribute.
**     CA  20220120          In NO-ERROR mode, delete any legacy exception once it was consumed.
**     OM  20220217          Added error messages related to ATTACH-DATA-SOURCE method.
**     CA  20220304          An ERROR condition being raised from within a block which can catch SysError must
**                           be morphed into a legacy SysError exception, and not raise an ERROR condition.
**     OM  20220328          Added new JSON and XML deserialization errors (related to unique idx validation).
**     ME  20220512          Do not show warning messages if suppressed for the session.
**     CA  20220524          recordForLegacyThrow must rebuild the SysError reference if it got deleted.
**     OM  20220525          Added text for error no. 15376.
**     CA  20220526          Added displayAndRaiseError, which shows error texts and raises an ERROR condition
**                           regardless of NO-ERROR status (required by DELETE OBJECT extProg, when there are
**                           buffer validation errors).
**     OM  20220527          Added text for error no. 12298. Flattened the stack-trace a bit.
**     CA  20220531          displayError must throw a legacy exception, if the block is configured it.
**     OM  20220531          Added some missing errors related to sequences.
**     OM  20220604          Added some missing errors related to dereferenciation support.
**     CA  20220614          recordOrShowError will raise legacy OO exceptions if the ERROR flag is set and
**                           the surrounding code allows for it.
**     VVT 20220911          ErrorManager$ErrorEntry must be serializable. See #6694-16.
**     VVT 20221003          CommonHandle.getResourceType() method renamed to resourceType() to prevent conflicts
**                           with namesakes in DMO. See #6694.
**     CA  20221012          Fixed JSON import of tables with extent fields, when the source is a JsonObject.
**     CA  20220524          recordForLegacyThrow must rebuild the SysError reference if it got deleted.
**     OM  20220525          Added text for error no. 15376.
**     CA  20220526          Added displayAndRaiseError, which shows error texts and raises an ERROR condition
**                           regardless of NO-ERROR status (required by DELETE OBJECT extProg, when there are
**                           buffer validation errors).
**     OM  20220527          Added text for error no. 12298. Flattened the stack-trace a bit.
**     CA  20220531          displayError must throw a legacy exception, if the block is configured it.
**     OM  20220531          Added some missing errors related to sequences.
**     OM  20220604          Added some missing errors related to dereferenciation support.
**     CA  20220613          Allow OO exceptions to propagate to the remote side, when exiting the root block 
**                           on a appserver call.
**     OM  20220706          New error message related to PRIMARY attribute validation.
**     VVT 20220913          CommonHandle.getResourceType() method renamed to resourceType() to prevent conflicts
**                           with namesakes in DMO. See #6694.
**     IAS 20221003          Fixed error message for error 3131
**     IAS 20221006          Reverted error message for error 3131 change.
**     IAS 20221102          Added message for error 12323.
**     IAS 20221100          Added message for warning 13095.
**     IAS 20221112          Added recordOrShowError(int id, boolean isError, boolean isModal, String... params)
**     CA  20230110          When NO-ERROR is used, it is assumed that an ERROR condition will be added to the
**                           'errorList' in a significant less cases than the number of calls - thus let this
**                           list be created in a lazy manner, when an ERROR message is actually registered 
**                           via NO-ERROR.
**     CA  20230116          Improvement for context-local state resolution.
** 081 GBB 20230410          ErrorWriter.logStackTrace() replaced with rpc call to ErrorDataAccess#writeLog().
**                           Flag logCallStack added.
** 082 IAS 20230325          Added recordOrShowWarning(int id, String... params) method. Added
**                           error messages for 13184 and 15357.
**     IAS 20230405          Added error message for error 14282.
** 083 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
** 084 GBB 20230608          LOG-MANAGER constant for a subsys renamed.
** 085 TT  20230614          Added the functionality for session:suppress-warnings-list.
** 086 CA  20230907          Added missing 10515 error text.
** 087 GBB 20231101          Hardcoded true replaced by boolean isFromLogManager in recordOrThrowError.
** 088 OM  20231110          Prevented downstream NPE caused by log accessing non-existing user context.
** 089 OM  20240110          Added message no. 12349.
** 090 DDF 20240423          Added an overload for showErrorAndAbend which forces an error to be displayed
**                           even if it's silent and does not mark it as an error (it is not recorded).
** 091 DDF 20240523          Added a displayError() method that receives an additional parameter.
**     DDF 20240524          isError flag can be influenced by the existence of a CATCH block in the
**                           current block, removed unnecessary displayError() method.
**     DDF 20240524          Modified condition for setting the isError flag in recordOrShowError().
**     DDF 20240527          Added recordOrShowDatabaseError().
** 092 CA  20240609          An ERROR condition must be triggered in the caller (outside of the top-level 
**                           block) is current file has ROUTINE-/BLOCK-LEVEL statetements and this condition
**                           can't be morphed in a structured exception in the current stacktrace.
** 093 GBB 20240610          noRecordOrThrowErrorWorker() should not log errors. Implementing
**                           RemoteErrorData.handleSoupFault() to be handled server-side.
** 094 GBB 20240621          New appserver connect error msgs added to numbered errors.
**                           Adding static throwErrorConditionException for network interface impl.
** 095 DDF 20240814          Added handleForwardPending(), getErrorStatus() and processLegacyErrorException()
**                           methods.
**     DDF 20240822          Moved processLegacyErrorException() method from ErrorHelper to ErrorManager.
** 096 AS  20240911          Added 12311 and 12314 error support.
** 097 TJD 20230224          isSilent should be checked before calling <code>displayError</code>, not inside it.
**     TJD 20240422          checking isSilent restored in <code>displayError</code>, temporary disable 
**                           isSilent in <code>throwError</code> instead 
**     TJD 20240423          No need to restore silent mode if it was not set 
** 098 OM  20241001          Added tenant-related error messages.
** 099 OM  20240925          The error message was not parametrised in recordOrShowError().
** 100 AL2 20241108          Reduce logging level if the error is going to be caught by a catch block.
** 101 CA  20240625          Certain database-level ERROR conditions (like 132/buffer already exists with) 
**                           must be processed as if BLOCK-LEVEL ON ERROR UNDO, THROW is set (in other words,
**                           it can get morphed into a strcutured exceptions). 
** 102 OM  20241128          A static method cannot be final. (It cannot be overridden). Formatting fixes.
** 103 AS  20241204          Do not log caught errors when using LOG-MANAGER.
** 104 ICP 20241202          Added buffer copy and temp table copy error messages.
** 105 FER 20241212          Added a displayError() method is facilitates an error box with custom msg.
** 106 ICP 20250129          Used logical constants to leverage cached instances.
** 107 GBB 20250403          Support for the new multi-session appserver.
** 108 ES  20250304          Generate a StopError legacy exception when error code is 
**                           EXIT_CODE_PROCEDURE_NOT_FOUND
*/

/*
** 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.io.*;
import java.util.*;
import java.util.function.*;
import java.util.logging.*;
import com.goldencode.p2j.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.util.appserver.*;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.proxy.*;

/**
 * A context-local class providing static methods to support the 
 * <code>ERROR-STATUS</code> and <code>NO-ERROR</code> Progress features.
 * <p>
 * The following is the mapping of Progress features to Java:
 * <p>
 * <pre>
 * Progress Feature             Type          Java Feature
 * ---------------------------  ------------  ----------------------------
 * error-status:error           attribute     {@link #isError}
 * error-status:num-messages    attribute     {@link #numErrors}
 * error-status:get-message     method        {@link #getErrorText}
 * error-status:get-number      method        {@link #getErrorNumber}
 * </pre>
 * <p>
 * In order to properly use this class, all processing which must be handled
 * on a <code>NO-ERROR</code> basis must be run as delegated code block processed by
 * {@link #silent}.  This code catches any error exception so that the business logic
 * aborts at the point of the error but the exception does not reach the block level.
 * All called code that honors <code>NO-ERROR</code> must be aware of silent error
 * mode.  This can be done via the following methods (in a library):
 * <p>
 * <ol>
 *    <li> If a simple return from a library is easily possible, delegate
 *         processing to this class using {@link #recordOrThrowError}. After
 *         calling this method, return silently from the library.
 *    <li> Do it yourself.
 *    <ul>
 *       <li> Use {@link #isSilentError} to determine if the silent mode is  
 *            enabled.
 *       <li> Use {@link #addError} to add an error to the list if silent
 *            mode is enabled.
 *       <li> Throw {@link ErrorConditionException}.
 *       <li> This may be useful if a simple silent return is not easy to do
 *            as one can use the {@link ErrorUnwindException} to unwind the
 *            stack to the point in the library where a silent return is
 *            possible.  This exception must be caught in the library code
 *            and will not be caught in the client application.  Use of
 *            this exception should be completely hidden from the client
 *            application.
 *    </ul>
 * </ol>
 * <p>
 * On the server, the silent error mode flag is saved at every top level
 * scope (see {@link TransactionManager#pushScope}) and it is restored at
 * the end of that scope (see {@link TransactionManager#popScope()}).
 * This is done so that "nested" NO-ERROR conditions (e.g. during a RUN
 * statement on an internal or external procedure or an assignment statement
 * with a call to a user defined function) are maintained properly.  In
 * particular, the called procedure or function does NOT execute as if in
 * silent mode unless the code there is so bracketed.  However, the one
 * exception to this is that the RETURN ERROR behavior implemented by the
 * <code>TransactionManager</code> is aware of whether or not silent error
 * mode was enabled in the caller.
 * <p>
 * It must be noted that Progress seems to provide a single global instance of
 * ERROR-STATUS handle and all its data. Thus this data can be seen across
 * call/return boundaries and is only modified at very specific points. for
 * example, the error flag is only changed at the end of a NO-ERROR statement.
 * Likewise, the previous list of errors can be read until the first error
 * occurs after a NO-ERROR statement starts. This deferred clearing of the
 * error list is implemented using the pending error flag. When that flag is
 * set, the error list is cleared. If no errors occur in the NO-ERROR statement
 * then the error list is cleared at the same time the error flag is cleared
 * at the end of the NO-ERROR statement.
 */
public class ErrorManager
{
   /** Capacity of error conditions stack for _MSG function */
   private static final int ERR_CONDITIONS_CAPACITY = 25;
   
   /** Logger (safe as a JVM-wide value rather than as context-local). */
   private static final CentralLogger LOG = CentralLogger.get(ErrorManager.class);
   
   /** Stores context-local state variables. */
   private static final ContextContainer work = new ContextContainer(); 
   
   /**
    * Server-side it's a local instance.
    * Client-side it's a remote interface accessing the error data on the server.
    */
   private static RemoteErrorData server = new ServerErrorDataAccessor();
   
   /** The multi-session appserver manager. */
   private static final MultiSessionAppserverManager msaManager = MultiSessionAppserverManager.getInstance();
   
   /** Handler for ignored errors. */
   private static final RemoteErrorData suppressedHandler = new SuppressedErrorDataAccessor();
   
   /** Headless mode */
   private static boolean headless = false;
   
   /** Error writer object instance for client. */
   private static ErrorWriter errorWriter = null;
   
   /** Does this instance belong to the server side part. */
   private static boolean serverSide = true;
   
   /**
    * Get a the instance for the ERROR-STATUS system handle.  Is obtained using
    * a call to the {@link StaticProxy#obtain(Class, Class[])}, using the
    * {@link CommonErrorStatus} interface and its methods implemented by these
    * classes: {@link ErrorManager}.
    * 
    * @return   See above.
    */
   public static handle asHandle()
   {
      WorkArea wa = work.get();

      Class[] classes = { ErrorManager.class };
      CommonErrorStatus proxy = StaticProxy.obtain(CommonErrorStatus.class, classes);

      if (wa.id == null)
      {
         wa.id = handle.resourceId(proxy);
      }

      return new handle(proxy);
   }   
   
   /**
    * Get this resource's ID.
    * 
    * @return   The resource's ID.
    */
   public static Long id()
   {
      WorkArea wa = work.get();
      
      return wa.id;
   }
   
   /**
    * Set this resource's ID.
    * <p>
    * This is a no-op for system handles.
    *  
    * @param    id
    *           The resource's ID.
    */
   public static void id(long id)
   {
      // no-op
   }

   /**
    * Implementation for the {@link WrappedResource#valid()} API.
    * 
    * @return   Always true.
    */
   public static boolean valid()
   {
      return true;
   }
   
   /**
    * Implementation for the {@link WrappedResource#unknown()} API.
    * 
    * @return   Always false.
    */
   public static boolean unknown()
   {
      return false;
   }
   
   /**
    * API needed to implement read-only attribute assignment (a 4GL "feature").
    * 
    * @param    attribute
    *           The attribute's name.
    *           
    * @see      handle#readOnlyError(handle, String)
    */
   public static void readOnlyError(String attribute)
   {
      handle.readOnlyError(asHandle(), attribute);
   }
   
   /**
    * API needed to implement read-only attribute assignment (a 4GL "feature").
    * 
    * @param    attribute
    *           The attribute's name.
    * @param    expr
    *           The value which is attempted to be assigned to the read-only attribute. 
    *
    * @see      handle#readOnlyError(handle, String, Object)
    */
   public static void readOnlyError(String attribute, Object expr)
   {
      handle.readOnlyError(asHandle(), attribute);
   }
   
   /**
    * Initialize <code>ErrorManager</code> for headless mode.
    */
   public static void setHeadless()
   {
      headless = true;
   }
   
   /**
    * Set the state of SESSION:SYSTEM-ALERT-BOXES attribute.
    * 
    * @param   on
    *          The new state for SESSION:SYSTEM-ALERT-BOXES attribute.
    */
   public static void setSystemAlertBoxes(boolean on)
   {
      work.obtain().systemAlertBoxes = on;
   }

   /**
    * Get the state of SESSION:SYSTEM-ALERT-BOXES attribute. 
    * 
    * @return  the state of SESSION:SYSTEM-ALERT-BOXES attribute.
    */
   public static boolean isSystemAlertBoxes()
   {
      return server.isSystemAlertBoxes();
   }

   /**
    * Initializes the proper error data access on the client.
    * 
    * @param   errorServer
    *          The remote error interface 
    */
   public static void initClient(RemoteErrorData errorServer)
   {
      server = errorServer;
      serverSide = false;
   }
      
   /**
    * Initializes the proper error writer on the client.
    * 
    * @param    errorWriterInitial
    *           Error writer implementation for client side.
    */
   public static void initErrorWriter(ErrorWriter errorWriterInitial)
   {
      errorWriter = errorWriterInitial;
   }
      
   /**
    * Checks if the client is running in batch mode.
    * 
    * @return  <code>true</code> for batch mode, <code>false</code> otherwise.
    * 
    */
   public static boolean isInBatchMode()
   {
      return errorWriter != null && errorWriter.isInBatchMode();
   }

   /**
    * Stops the associated client session by generating UnstoppableExitException.
    */
   public static void stopBatchSession()
   {
      if (errorWriter != null)
      {
         errorWriter.stopBatchSession();
      }
   }
      
   /**
    * Reverts the error data access on the client.
    */
   public static void resetClient()
   {
      server = new ServerErrorDataAccessor();
   }
      
   /**
    * Reports if silent error mode has been enabled.
    *
    * @return   <code>true</code> if silent mode is enabled, otherwise
    *           <code>false</code>.
    */
   public static boolean isSilentError()
   {
      return server.isSilent();
   }
   
   /**
    * Queries the state of <code>ignore</code> mode.
    *
    * @return   <code>true</code> if ignore mode is enabled, otherwise <code>false</code>.
    */
   public static boolean isIgnore()
   {
      return isIgnoreWorker(work.obtain());
   }
   
   /**
    * Execute the given block while supressing normal error processing by temporarily enabling the
    * ignore flag.
    *
    * @param    code
    *           The code to execute.
    */
   public static void ignore(Runnable code)
   {
      ignoreWorker(code, work.obtain());
   }
   
   /**
    * Execute the given block while suppressing ALL error processing.
    *
    * @param    code
    *           The code to execute.
    */
   public static void nullErrors(Runnable code)
   {
      RemoteErrorData old = server;
      
      server = suppressedHandler;
      
      try
      {
         ignore(code);
      }
      finally
      {
         server = old;
      }
   }
      
   /**
    * Reports if warning mode has been enabled.  When in warning mode, error
    * messages are reported to the UI using an alert box.
    *
    * @return   <code>true</code> if warning mode is enabled, otherwise
    *           <code>false</code>.
    */
   public static boolean isWarningMode()
   {
      return work.obtain().errorsAsWarnings;
   }
   
   /**
    * Worker method that executes a given block of code with silent error mode turned on for
    * the duration of the code block.  Before the code block is executed, silent error mode
    * is turned on and after the code block completes (normally or abnormally), silent error
    * mode will be disabled.
    * <p>
    * This method can only be executed on the server side.
    *
    * @param    code
    *           The block of code to execute in silent mode.
    *
    * @return   The value of the ERROR-STATUS:ERROR flag when the execution is complete. 
    */
   public static boolean silent(Runnable code)
   {
      return silentWorker(code, (ServerErrorDataAccessor) server, work.obtain());
   }
      
   /**
    * Worker method that executes a given block of code with silent error mode turned on for
    * the duration of the code block.  Before the code block is executed, silent error mode
    * is turned on and after the code block completes (normally or abnormally), silent error
    * mode will be disabled.
    * <p>
    * This method can only be executed on the server side.  It will backup and restore the current
    * {@link WorkArea#errorStatus}, to allow for nested silent-mode usages (internally by FWD).
    *
    * @param    code
    *           The block of code to execute in silent mode.
    *
    * @return   The value of the ERROR-STATUS:ERROR flag when the execution is complete. 
    */
   public static boolean nestedSilent(Runnable code)
   {
      return nestedSilent(code, null);
   }
   
   /**
    * Worker method that executes a given block of code with silent error mode turned on for
    * the duration of the code block.  Before the code block is executed, silent error mode
    * is turned on and after the code block completes (normally or abnormally), silent error
    * mode will be disabled.
    * <p>
    * This method can only be executed on the server side.  It will backup and restore the current
    * {@link WorkArea#errorStatus}, to allow for nested silent-mode usages (internally by FWD).
    *
    * @param    code
    *           The block of code to execute in silent mode.
    * @param    errorProcessor
    *           A consumer of the logged errors in silent mode.
    *
    * @return   The value of the ERROR-STATUS:ERROR flag when the execution is complete. 
    */
   public static boolean nestedSilent(Runnable code, BiConsumer<String, Integer> errorProcessor)
   {
      return nestedSilent(null, code, errorProcessor);
   }
   
   /**
    * Worker method that executes a given block of code with silent error mode turned on for
    * the duration of the code block.  Before the code block is executed, silent error mode
    * is turned on and after the code block completes (normally or abnormally), silent error
    * mode will be disabled.
    * <p>
    * This method can only be executed on the server side.  It will backup and restore the current
    * {@link WorkArea#errorStatus}, to allow for nested silent-mode usages (internally by FWD).
    *
    * @param    wa
    *           The context-local state.
    * @param    code
    *           The block of code to execute in silent mode.
    * @param    errorProcessor
    *           A consumer of the logged errors in silent mode.
    *
    * @return   The value of the ERROR-STATUS:ERROR flag when the execution is complete. 
    */
   public static boolean nestedSilent(WorkArea wa, Runnable code, BiConsumer<String, Integer> errorProcessor)
   {
      ServerErrorDataAccessor srv = (ServerErrorDataAccessor) server;
      if (wa == null)
      {
         wa  = work.obtain();
      }
      
      ErrorStatus saveError = wa.errorStatus;
      ErrorStatus savePending = new ErrorStatus();
      savePending.set(wa.pendingErrorStatus);
      wa.errorStatus = new ErrorStatus();
      wa.pendingErrorStatus.initialize();
      forwardPending(wa);
      
      boolean saveSilent = srv.isSilent();
      
      // enable silent error processing
      srv.setSilent(wa, true);
      boolean hadError = false;
      
      try
      {
         code.run();
      }
      
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      
      finally
      {
         // disable further silent error processing
         srv.setSilent(wa, false);
         forwardPending(wa);
         hadError = wa.errorStatus.error;
         
         if (errorProcessor != null && wa.errorStatus.errorList != null)
         {
            for (Object o : wa.errorStatus.errorList)
            {
               ErrorEntry entry = (ErrorEntry) o;
               
               String msg = (entry.prefix ? "** " : "") + entry.text;
               
               errorProcessor.accept(msg, entry.num);
            }
         }
         
         wa.errorStatus = saveError;
         wa.pendingErrorStatus.set(savePending);
         
         if (saveSilent)
         {
            // re-enable it if it was enabled
            srv.setSilent(wa, true);
         }
      }
      
      return hadError;
   }

   /**
    * Enables warning mode, during which errors are non-fatal, but are
    * reported to the UI of the current context via a call to
    * <code>LogicalTerminal.message()</code>.
    * <p>
    * Each call to this method must be matched with a corresponding call  
    * to {@link #warningModeDisable}.
    */
   public static void warningModeEnable()
   {
      // enable warning processing
      work.obtain().errorsAsWarnings = true;
   }
   
   /**
    * Disables warning mode.
    * <p>
    * Each call to this method must be matched with a corresponding, previous
    * call to {@link #warningModeEnable}.
    */
   public static void warningModeDisable()
   {
      // disable further warning processing
      work.obtain().errorsAsWarnings = false;
   }
   
   /**
    * Manufacture error text based on the given parameters, if silent error mode is enabled,
    * then this error number and the generated error text will be stored in the error list,
    * otherwise this generated error text will be used to display modal or modeless box in
    * the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> and the <code>pending</code>
    * error flag will be set to <code>true</code>.
    * 
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    modal
    *           <code>true</code> if message will be shown in modal dialog.
    */
   public static void recordOrShowError(int num, String text, boolean modal)
   {
      recordOrShowError(new int[] { num }, new String[] { text }, modal, true, true);
   }
   
   /**
    * Manufacture error text based on the given parameters, if silent error mode is enabled,
    * then this error number and the generated error text will be stored in the error list,
    * otherwise this generated error text will be used to display modal or modeless box in
    * the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> and the <code>pending</code>
    * error flag will be set to <code>true</code>.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    modal
    *           <code>true</code> if message will be shown in modal dialog.
    */
   public static void recordOrShowError(int[] num, String[] text, boolean modal)
   {
      recordOrShowError(num, text, modal, true, true);
   }
    
   /**
    * Manufacture error text based on the given parameters, if silent error mode is enabled,
    * then this error number and the generated error text will be stored in the error list,
    * otherwise this generated error text will be used to display modal or modeless box in
    * the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the 
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to 
    * <code>true</code>.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    modal
    *           <code>true</code> if message will be shown in modal dialog.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the formatted error 
    *           message, <code>false</code> to omit this text.
    */
   public static void recordOrShowError(int num, String text, boolean modal, boolean prefix)
   {
      recordOrShowError(new int[] { num }, new String[] { text }, modal, prefix, true);
   }
   
   /**
    * Manufacture error text based on the given parameters, if silent error mode is enabled,
    * then this error number and the generated error text will be stored in the error list,
    * otherwise this generated error text will be used to display modal or modeless box in
    * the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the 
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to 
    * <code>true</code>.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    modal
    *           <code>true</code> if message will be shown in modal dialog.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the formatted error 
    *           message, <code>false</code> to omit this text.
    */
   public static void recordOrShowError(int[] num, String[] text, boolean modal, boolean prefix)
   {
      recordOrShowError(num, text, modal, prefix, true);
   }

   /**
    * Manufacture one or more error texts based on the given parameters, if silent error mode is
    * enabled, then these error numbers and the respective generated error text messages will be
    * stored in the error list, otherwise this generated error texts will be used to display
    * modal or modeless box in the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the 
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
    * value specified by the <code>isError</code> flag.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    modal
    *           <code>true</code> if message will be shown in modal dialog.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the formatted error 
    *           message, <code>false</code> to omit this text.
    * @param    isError
    *           The new value for the <code>pending</code> error flag.
    */
   public static void recordOrShowError(int     num,
                                        String  text,
                                        boolean modal,
                                        boolean prefix,
                                        boolean isError)
   {
      recordOrShowError(num, text, modal, prefix, isError, false);
   }

   /**
    * Manufacture one or more error texts based on the given parameters, if silent error mode is
    * enabled, then these error numbers and the respective generated error text messages will be
    * stored in the error list, otherwise this generated error texts will be used to display
    * modal or modeless box in the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the 
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
    * value specified by the <code>isError</code> flag.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    modal
    *           <code>true</code> if message will be shown in modal dialog.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the formatted error 
    *           message, <code>false</code> to omit this text.
    * @param    isError
    *           The new value for the <code>pending</code> error flag.
    * @param    asMsg
    *           <code>true</code> to use the given text as the message text else
    *           <code>false</code> to use buildErrorText.
    */
   public static void recordOrShowError(int     num,
                                        String  text,
                                        boolean modal,
                                        boolean prefix,
                                        boolean isError,
                                        boolean asMsg)
   {
      recordOrShowError(new int[] { num }, new String[] { text }, modal, prefix, isError, asMsg);
   }
   
   /**
    * Manufacture one or more error texts based on the given parameters, if silent error mode is
    * enabled, then these error numbers and the respective generated error text messages will be
    * stored in the error list, otherwise this generated error texts will be used to display
    * modal or modeless box in the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the 
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
    * value specified by the <code>isError</code> flag.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    modal
    *           <code>true</code> if message will be shown in modal dialog.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the formatted error 
    *           message, <code>false</code> to omit this text.
    * @param    isError
    *           The new value for the <code>pending</code> error flag.
    */
   public static void recordOrShowError(int[]    num,
                                        String[] text,
                                        boolean  modal,
                                        boolean  prefix,
                                        boolean  isError)
   {
      recordOrShowError(num, text, modal, prefix, isError, false);
   }

   /**
    * Manufacture one or more error texts based on the given parameters, if silent error mode is
    * enabled, then these error numbers and the respective generated error text messages will be
    * stored in the error list, otherwise this generated error texts will be used to display
    * modal or modeless box in the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
    * value specified by the <code>isError</code> flag.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    modal
    *           <code>true</code> if message will be shown in modal dialog.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the formatted error
    *           message, <code>false</code> to omit this text.
    * @param    isError
    *           The new value for the <code>pending</code> error flag.
    * @param    asMsg
    *           <code>true</code> to use the given text as the message text else
    *           <code>false</code> to use buildErrorText.
    */
   public static void recordOrShowError(int[]    num,
                                        String[] text,
                                        boolean  modal,
                                        boolean  prefix,
                                        boolean  isError,
                                        boolean  asMsg)
   {
      recordOrShowError(num, text, modal, prefix, isError, asMsg, true);
   }
   
   /**
    * Manufacture one or more error texts based on the given parameters, if silent error mode is
    * enabled, then these error numbers and the respective generated error text messages will be
    * stored in the error list, otherwise this generated error texts will be used to display
    * modal or modeless box in the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the 
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
    * value specified by the <code>isError</code> flag.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    modal
    *           <code>true</code> if message will be shown in modal dialog.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the formatted error 
    *           message, <code>false</code> to omit this text.
    * @param    isError
    *           The new value for the <code>pending</code> error flag.
    * @param    asMsg
    *           <code>true</code> to use the given text as the message text else
    *           <code>false</code> to use buildErrorText.
    * @param    addDot
    *           <code>true</code> if dot(s) are added to the end of the message(s).
    */
   public static void recordOrShowError(int[]    num,
                                        String[] text,
                                        boolean  modal,
                                        boolean  prefix,
                                        boolean  isError,
                                        boolean  asMsg,
                                        boolean  addDot)
   {
      recordOrShowError(num, text, modal, prefix, isError, asMsg, addDot, false);
   }      
   /**
    * Manufacture one or more error texts based on the given parameters, if silent error mode is
    * enabled, then these error numbers and the respective generated error text messages will be
    * stored in the error list, otherwise this generated error texts will be used to display
    * modal or modeless box in the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the 
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
    * value specified by the <code>isError</code> flag.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    modal
    *           <code>true</code> if message will be shown in modal dialog.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the formatted error 
    *           message, <code>false</code> to omit this text.
    * @param    isError
    *           The new value for the <code>pending</code> error flag.
    * @param    asMsg
    *           <code>true</code> to use the given text as the message text else
    *           <code>false</code> to use buildErrorText.
    * @param    addDot
    *           <code>true</code> if dot(s) are added to the end of the message(s).
    * @param    force
    *           <code>true</code> if errors which cause a modal dialog should be logged even in 
    *           silent (ignore NO-ERROR clause) and headless modes   
    */
   public static void recordOrShowError(int[]    num,
                                         String[] text,
                                         boolean  modal,
                                         boolean  prefix,
                                         boolean  isError,
                                         boolean  asMsg,
                                         boolean  addDot,
                                         boolean  force)
   {
      recordOrShowError(num, text, modal, prefix, isError, asMsg, addDot, force, false);
   }
      
   /**
    * Manufacture one or more error texts based on the given parameters, if silent error mode is
    * enabled, then these error numbers and the respective generated error text messages will be
    * stored in the error list, otherwise this generated error texts will be used to display
    * modal or modeless box in the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the 
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
    * value specified by the <code>isError</code> flag.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    modal
    *           <code>true</code> if message will be shown in modal dialog.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the formatted error 
    *           message, <code>false</code> to omit this text.
    * @param    isError
    *           The new value for the <code>pending</code> error flag.
    * @param    asMsg
    *           <code>true</code> to use the given text as the message text else
    *           <code>false</code> to use buildErrorText.
    * @param    addDot
    *           <code>true</code> if dot(s) are added to the end of the message(s).
    * @param    force
    *           <code>true</code> if errors which cause a modal dialog should be logged even in 
    *           silent (ignore NO-ERROR clause) and headless modes
    * @param    isFromLogManager
    *           <code>true</code> if used for LOG-MANAGER, <code>false</code>
    */
   public static void recordOrShowError(int[]   num,
                                        String[] text,
                                        boolean  modal,
                                        boolean  prefix,
                                        boolean  isError,
                                        boolean  asMsg,
                                        boolean  addDot,
                                        boolean  force,
                                        boolean  isFromLogManager)
   {
      recordOrShowError(num, text, modal, prefix, isError, asMsg, addDot, force, isFromLogManager, false);
   }
   
   /**
    * Manufacture one or more error texts based on the given parameters, if silent error mode is
    * enabled, then these error numbers and the respective generated error text messages will be
    * stored in the error list, otherwise this generated error texts will be used to display
    * modal or modeless box in the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the 
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
    * value specified by the <code>isError</code> flag.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    modal
    *           <code>true</code> if message will be shown in modal dialog.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the formatted error 
    *           message, <code>false</code> to omit this text.
    * @param    isError
    *           The new value for the <code>pending</code> error flag.
    * @param    asMsg
    *           <code>true</code> to use the given text as the message text else
    *           <code>false</code> to use buildErrorText.
    * @param    addDot
    *           <code>true</code> if dot(s) are added to the end of the message(s).
    * @param    force
    *           <code>true</code> if errors which cause a modal dialog should be logged even in 
    *           silent (ignore NO-ERROR clause) and headless modes
    * @param    isFromLogManager
    *           <code>true</code> if used for LOG-MANAGER, <code>false</code>
    * @param    isDatabaseError
    *           <code>true</code> to allow errors related to database influence the value of the
    *           isError flag, <code>false</code> otherwise. THis flag must only be used for
    *           database related errors.
    */
   private static void recordOrShowError(int[]    num,
                                        String[] text,
                                        boolean  modal,
                                        boolean  prefix,
                                        boolean  isError,
                                        boolean  asMsg,
                                        boolean  addDot,
                                        boolean  force,
                                        boolean  isFromLogManager,
                                        boolean  isDatabaseError)
   {
      if (isIgnore())
         return;
      
      if (num.length != text.length)
      {
         throw new IllegalArgumentException("Unbalanced error number and text message pairs");
      }
      
      StringBuilder buf = new StringBuilder();
      String sep = System.getProperty("line.separator");
      boolean silent = isSilentError();
      // isError flag can be influenced if there is a CATCH block in the current block,
      // making it possible to turn it into true even if it is specified as false.
      if (isDatabaseError)
      {
         isError = isError || hasLegacyError();
      }
      boolean manageLegacyError = !silent && isError && server.mustThrowLegacyError();
      
      for (int i = 0; i < num.length; i++)
      {
         String errmsg = asMsg ? text[i] : buildErrorText(num[i], text[i], prefix, addDot);
         
         if (i > 0)
         {
            buf.append(sep);
         }
         buf.append(errmsg);
         
         if (manageLegacyError)
         {
            server.recordForLegacyThrow(errmsg, num[i]);
         }
         else if (silent)
         {
            if (i == 0)
            {
               // this may reset the error list so it must be done BEFORE we add the new error(s) to the list;
               // this only needs to be done the first time through this loop
               server.setPending(isError);
            }
            
            server.addRecord(new ErrorEntry(num[i], errmsg));
         }
      }
      
      String masterMsg = buf.toString();

      if (manageLegacyError)
      {
         if (!isFromLogManager && !hasLegacyError())
         {
            writeLog(masterMsg);
         }
         throw new DeferredLegacyErrorException();
      }

      log(masterMsg, silent);
      
      if ((!silent && !isHeadless()) || force)
      {
         if (errorWriter != null)
         {
            if (modal && server.isSystemAlertBoxes())
            {
               errorWriter.messageBox(masterMsg, "Error", force);
            }
            else
            {
               if (!isFromLogManager && !hasLegacyError())
               {
                  writeLog(masterMsg);
               }
               errorWriter.message(masterMsg);
            }
         }
         else
         {
            if (!isFromLogManager && !hasLegacyError())
            {
               writeLog(masterMsg);
            }
         }
         
         addRaisedCondition(num);
      }
   }
   
   /**
    * Manufacture warning text based on the given parameters.
    * 
    * @param   id
    *          The id of the message. The associated text is filled with {@code params} tokens.
    * @param   params
    *          The parameters for the message.
    */
   public static void recordOrShowWarning(int id, String... params)
   {
      ErrorManager.recordOrShowError(new int[] { id },
                                     new String[] { replaceTokens(id, params) },
                                     true,   // modal
                                     false,  // no prefix
                                     false,  // error
                                     false,  // not as message
                                     true);  // add final dot
   }
   /**
    * Manufacture one or more warning texts based on the given parameters. 
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the 
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
    * value specified by the <code>isError</code> flag.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the formatted error 
    *           message, <code>false</code> to omit this text.
    * @param    isError
    *           The new value for the <code>pending</code> error flag.
    * @param    asMsg
    *           <code>true</code> to use the given text as the message text else
    *           <code>false</code> to use buildErrorText.
    * @param    addDot
    *           <code>true</code> if dot(s) are added to the end of the message(s).
    */
   public static void recordOrShowWarning(int      num,
                                          String   text,
                                          boolean  prefix,
                                          boolean  isError,
                                          boolean  asMsg,
                                          boolean  addDot)
   {
      // TODO: Currently this method just forwards the call to recordOrShowError(),
      //       unless warnings are suppressed for the session.
      //       Depending on the expected semantics of the method, its name may change to 
      //       showWarning.
      boolean suppressWarningCode = work.get().suppressWarningsSet.contains(num);

      if (!isSuppressWarnings() && !suppressWarningCode)
      {
         recordOrShowError(new int[] {num}, new String[] {text}, true, prefix, isError, asMsg, addDot);
      }
   }

   /**
    * Manufacture one or more warning texts based on the given parameters. 
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the 
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
    * value specified by the <code>isError</code> flag.
    * <p>
    * Used by LOG-MANAGER exclusively.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the formatted error 
    *           message, <code>false</code> to omit this text.
    */
   static void recordOrShowLogManagerWarning(int num, String text, boolean prefix)
   {
      // TODO: Currently this method just forwards the call to recordOrShowError(),
      //       unless warnings are suppressed for the session.
      //       Depending on the expected semantics of the method, its name may change to 
      //       showWarning.
      if (!isSuppressWarnings())
      {
         recordOrShowError(new int[] {num}, new String[] {text}, true, prefix, false, false, false, false,
                           true);
      }
   }
   
   /**
    * Manufacture error text based on the given parameters, if silent error mode is enabled, then
    * this error number and the generated error text will be stored in the error list, otherwise
    * this generated error text will be used to display modal or modeless box in the UI.
    *
    * @param   id
    *          The id of the message. The associated text is filled with {@code params} tokens.
    * @param   params
    *          The parameters for the message.
    */
   public static void recordOrShowError(int id, String... params)
   {
      ErrorManager.recordOrShowError(new int[] { id },
                                     new String[] { replaceTokens(id, params) },
                                     true,   // modal
                                     false,  // no prefix
                                     true,   // error
                                     false,  // not as message
                                     true);  // add final dot
   }
   
   /**
    * Manufacture error text based on the given parameters, if silent error mode is enabled, then
    * this error number and the generated error text will be stored in the error list, otherwise
    * this generated error text will be used to display modal or modeless box in the UI.
    *
    * @param   id
    *          The id of the message. The associated text is filled with {@code params} tokens.
    * @param   isError
    *          The new value for the <code>pending</code> error flag.
    * @param   params
    *          The parameters for the message.
    */
   public static void recordOrShowError(int id, boolean isError, String... params)
   {
      ErrorManager.recordOrShowError(new int[] { id },
                                     new String[] { replaceTokens(id, params) },
                                     true,    // modal
                                     false,   // no prefix
                                     isError, // error
                                     false,   // not as message
                                     true);   // add final dot
   }

   /**
    * Manufacture error text based on the given parameters, if silent error mode is enabled, then
    * this error number and the generated error text will be stored in the error list, otherwise
    * this generated error text will be thrown (and execution of the procedure halted).
    *
    * @param   id
    *          The id of the message. The associated text is filled with {@code params} tokens.
    * @param    isModal
    *           <code>true</code> if message will be shown in modal dialog.
    * @param    isError
    *           The new value for the <code>pending</code> error flag.
    * @param   params
    *          The parameters for the message. If there are less than two token-parameters
    *          use empty Strings ({@code ""}) to make the compiler select the proper signature.
    */
   public static void recordOrShowError(int id, boolean isError, boolean isModal, String... params)
   {
      ErrorManager.recordOrShowError(new int[] { id },
                                     new String[] { replaceTokens(id, params) },
                                     isModal,    // not modal
                                     false,      // no prefix
                                     isError,    // not an error
                                     false,      // not as message
                                     true);      // add final dot
   }
   
   /**
    * Manufacture one or more error texts based on the given parameters, if silent error mode is
    * enabled, then these error numbers and the respective generated error text messages will be
    * stored in the error list, otherwise this generated error texts will be used to display
    * modal or modeless box in the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the 
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
    * value specified by the <code>isError</code> flag.
    * <p>
    * This is a specific method for errors related to the database. Such errors can influence
    * the value of the isError flag, for example a CATCH block in the current block will
    * force the isError flag to become <code>true</code>.
    * <p>
    * This method will call the recordOrShowError() method with the isError parameter set to
    * <code>false</code> and isDatabaseError set to <code>true</code>.
    * 
    * @param   id
    *          The error number.
    * @param   message
    *          The error message.
    * @param   modal
    *          <code>true</code> if message will be shown in modal dialog.
    * @param   prefix
    *          <code>true</code> to prepend a double asterisk prefix to the formatted error 
    *          message, <code>false</code> to omit this text.
    */
   public static void recordOrShowDatabaseError(int id, String message, boolean modal, boolean prefix)
   {
      recordOrShowDatabaseError(new int[] { id }, new String[] { message }, modal, prefix);
   }
   
   /**
    * Manufacture one or more error texts based on the given parameters, if silent error mode is
    * enabled, then these error numbers and the respective generated error text messages will be
    * stored in the error list, otherwise this generated error texts will be used to display
    * modal or modeless box in the UI.
    * <p>
    * The error message(s) will be prefixed with <code>**</code> depending on the 
    * <code>prefix</code> parameter and the <code>pending</code> error flag will be set to the
    * value specified by the <code>isError</code> flag.
    * <p>
    * This is a specific method for errors related to the database. Such errors can influence
    * the value of the isError flag, for example a CATCH block in the current block will
    * force the isError flag to become <code>true</code>.
    * <p>
    * This method will call the recordOrShowError() method with the isError parameter set to
    * <code>false</code> and isDatabaseError set to <code>true</code>.
    * 
    * @param   ids
    *          The error numbers.
    * @param   messages
    *          The error messages.
    * @param   modal
    *          <code>true</code> if message will be shown in modal dialog.
    * @param   prefix
    *          <code>true</code> to prepend a double asterisk prefix to the formatted error 
    *          message, <code>false</code> to omit this text.
    */
   public static void recordOrShowDatabaseError(int[] ids, String[] messages, boolean modal, boolean prefix)
   {
      recordOrShowError(ids, messages, modal, prefix, false, false, true, false, false, true);
   }

   /**
    * Display error text and throw an exception based upon the given exception.
    * Manufactures error text based on the given parameters, if silent error mode is enabled, then
    * this error number and the generated error text will be stored in the error list, otherwise
    * this generated error text will be used to display modal or modeless box in the UI.
    *
    * @param   id
    *          The id of the message. The associated text is filled with {@code params} tokens.
    * @param   params
    *          The parameters for the message.
    */
   public static void throwError(int id, String... params)
   {
      ErrorManager.throwError(new NumberedException(replaceTokens(id, params), id, false), isSilent());
   }
   
   /**
    * Manufacture error text based on the given parameters; after displaying
    * the error text on the client, a pause is forced before stopping the
    * application.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * 
    * @throws   StopConditionException
    *           always, unless the error manager is in ignore mode.
    */
   public static void showErrorAndAbend(int num, String text)
   throws StopConditionException
   {
      if (isIgnore())
         return;
      
      String errmsg = buildAbendText(num, text);
      try
      {
         if (!isHeadless())
         {
            // display the error message (because of pause processing, an
            // endkey, error... even stop condition can be generated here)
            displayError(errmsg);
            
            if (errorWriter != null)
            {
               errorWriter.pause();
            }
         }
      }
      finally
      {
         postprocessOnAbend(errmsg);
      }
   }
   
   /**
    * Manufacture error text based on the given parameters; After displaying
    * the error text on the client, a pause is forced ignoring the NO-ERROR 
    * clause and headless modes. At the end, a StopErrorCondition is thrown. 
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    * 
    * @throws   StopConditionException
    *           always, unless the error manager is in ignore mode.
    */
   public static void showErrorAndAbend(int num, String text, boolean prefix)
   throws StopConditionException
   {
      if (isIgnore())
      {
         return;
      }
      
      String errmsg = buildErrorText(num, text, prefix, true);
      try
      {
         recordOrShowError(new int[] { num },
                           new String[] { text },
                           true,
                           prefix,
                           false,
                           false,
                           true,
                           true,
                           false);
      }
      finally
      {
         postprocessOnAbend(errmsg);
      }
   }

   /**
    * Manufacture error text based on the given parameters; after displaying the error text on
    * the client, the application is stopping.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * 
    * @throws   StopConditionException
    *           always, unless the error manager is in ignore mode.
    */
   public static void conditionalShowErrorAndAbend(int num, String text)
   throws StopConditionException
   {
      // If the NO-ERROR option was specified this method just silently retuns doing nothing
      if (isSilentError())
      {
         return;
      }
      
      if (isIgnore())
      {
         return;
      }
      
      String errmsg = buildAbendText(num, text);
      try
      {
         if (!isHeadless())
         {
            // display the error message (because of pause processing, an
            // endkey, error... even stop condition can be generated here)
            displayError(errmsg);
         }
      }
      finally
      {
         postprocessOnAbend(errmsg);
      }
   }

   /**
    * Perform postprocessing for {@link #showErrorAndAbend(int, String)}.
    *
    * @param   errmsg
    *          Message text.
    */
   private static void postprocessOnAbend(String errmsg)
   {
      throw new StopConditionException(errmsg);
   }

   /**
    * Manufacture one or more error texts based on the given parameters, if
    * silent error mode is enabled, then these error numbers and the
    * respective generated error text messages will be stored in the error
    * list, otherwise this generated error texts will be used to display a
    * modal, alert (error) box in the UI.<p>
    * After error messages has been processed, a STOP condition is raised.
    *
    * @param    num
    *           The error numbers.
    * @param    text
    *           The text of the error messages.
    *
    * @throws   StopConditionException
    *           always, unless the error manager is in ignore mode.
    */
   public static void showErrorAndAbend(int[] num, String[] text)
   throws StopConditionException
   {
      if (isIgnore())
      {
         return;
      }
      
      try
      {
         recordOrShowError(num, text, true);
      }
      finally
      {
         postprocessOnAbend(text);
      }
   }

   /**
    * Raise an error condition and optionally record the given error (in silent mode).
    * <p>
    * In silent mode, the {@link #silent} worker will catch the exception and it will not be
    * seen by the converted code.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    *           
    * @throws   ErrorConditionException 
    */
   public static void recordOrThrowError(int num, String text)
   throws ErrorConditionException
   {
      recordOrThrowError(num, text, true);
   }
   
   /**
    * Raise an error condition and optionally record the given error (in silent mode).
    * <p>
    * In silent mode, the {@link #silent} worker will catch the exception and it will not be
    * seen by the converted code.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    *           
    * @throws   ErrorConditionException 
    */
   public static void recordOrThrowError(int num, String text, boolean prefix)
   {
      recordOrThrowError(new int[] { num }, new String[] { text }, prefix, false);
   }

   /**
    * Raise an error condition and optionally record the given error (in silent mode).
    * <p>
    * In silent mode, the {@link #silent} worker will catch the exception and it will not be
    * seen by the converted code.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    *
    * @throws   ErrorConditionException
    */
   static void recordOrThrowLogManagerError(int num, String text, boolean prefix)
   {
      recordOrThrowError(new int[] { num }, new String[] { text }, prefix, false, true, true);
   }

   /**
    * Raise an error condition and optionally record the given error (in silent mode).
    * <p>
    * In silent mode, the {@link #silent} worker will catch the exception and it will not be
    * seen by the converted code.
    *
    * @param    nums
    *           The error numbers.
    * @param    texts
    *           The text of the error messages.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    *
    * @throws   ErrorConditionException
    */
   public static void recordOrThrowError(int[] nums, String[] texts, boolean prefix)
   throws ErrorConditionException
   {
      recordOrThrowError(nums, texts, prefix, false);
   }

   /**
    * Raise an error condition and optionally record the given error (in silent mode).
    * <p>
    * In silent mode, the {@link #silent} worker will catch the exception and it will not be
    * seen by the converted code.
    *
    * @param    nums
    *           The error numbers.
    * @param    texts
    *           The text of the error messages.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    * @param    asMsg
    *           <code>true</code> to use the given text as the message text else
    *           <code>false</code> to use buildErrorText.
    *
    * @throws   ErrorConditionException 
    */
   public static void recordOrThrowError(int[]    nums,
                                         String[] texts, 
                                         boolean  prefix,
                                         boolean  asMsg)
   throws ErrorConditionException
   {
      recordOrThrowError(nums, texts, prefix, asMsg, true);
   }

   /**
    * Raise an error condition and optionally record the given error (in silent mode).
    * <p>
    * In silent mode, the {@link #silent} worker will catch the exception and it will not be
    * seen by the converted code.
    *
    * @param    nums
    *           The error numbers.
    * @param    texts
    *           The text of the error messages.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    * @param    asMsg
    *           <code>true</code> to use the given text as the message text else
    *           <code>false</code> to use buildErrorText.
    * @param    addDot
    *           <code>true</code> if a dot is added to the end of the message.
    *
    * @throws   ErrorConditionException 
    */
   public static void recordOrThrowError(int[]    nums,
                                         String[] texts, 
                                         boolean  prefix,
                                         boolean  asMsg,
                                         boolean  addDot)
   throws ErrorConditionException
   {
      recordOrThrowError(nums, texts, prefix, asMsg, addDot, false);
   }
   
   /**
    * Raise an error condition and optionally record the given error (in silent mode).
    * <p>
    * In silent mode, the {@link #silent} worker will catch the exception and it will not be
    * seen by the converted code.
    *
    * @param    nums
    *           The error numbers.
    * @param    texts
    *           The text of the error messages.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    * @param    asMsg
    *           <code>true</code> to use the given text as the message text else
    *           <code>false</code> to use buildErrorText.
    * @param    addDot
    *           <code>true</code> if a dot is added to the end of the message.
    * @param    isFromLogManager
    *           Flag to indicate the error comes from LOG-MANAGER.
    *
    * @throws   ErrorConditionException
    */
   private static void recordOrThrowError(int[]    nums,
                                          String[] texts,
                                          boolean  prefix,
                                          boolean  asMsg,
                                          boolean  addDot,
                                          boolean  isFromLogManager)
   throws ErrorConditionException
   {
      if (isIgnore())
         return;
      
      boolean silent = isSilentError();
      boolean manageLegacyError = server.mustThrowLegacyError();
      boolean mustManageError = !manageLegacyError && server.mustManageLegacyError();
      boolean forceExplicitException = false;
      
      if (!manageLegacyError && nums[0] == 132 && !ProcedureManager.getProcedureHelper().isBlockLevelUndoThrow())
      {
         forceExplicitException = true;
         manageLegacyError = server.mustThrowLegacyError(true);
      }
      
      boolean throwError = false;
      
      for (int i = 0; i < nums.length; i++)
      {
         int num = nums[i];
         String text = texts[i];
         boolean last = i == nums.length - 1;
         
         String errmsg = asMsg ? text : buildErrorText(num, text, prefix, addDot);

         if (!silent && !isFromLogManager && !hasLegacyError())
         {
            writeLog(errmsg);
         }
         log(errmsg, silent);
         
         if (silent)
         {
            // this may reset the error list so it must be done BEFORE we add
            // the new error to the list
            server.setPending(true);
            
            server.addRecord(new ErrorEntry(num, errmsg, false));
            
            // this will be caught by the worker method silent()
            if (last)
            {
               if (serverSide)
               {
                  throw new ErrorConditionException(num, errmsg);
               }
               else
               {
                  server.throwErrorConditionException(num, errmsg);
               }
            }
         }
         else if (isHeadless())
         {
            if (last)
            {
               if (mustManageError || (manageLegacyError && msaManager.isMsaContext()))
               {
                  TransactionManager.triggerErrorInCaller(num, text, true, prefix);
               }
               throw new ErrorConditionException(num, errmsg, new NumberedException(text, num));
            }
         }
         else
         {
            // TODO: if the current block can catch this condition OR if the current block has
            // ON ERROR UNDO, THROW OR if BLOCK-LEVEL or ROUTINE-LEVEL is active for the current
            // block (Depending on block type), then:
            // - don't display
            // - morph this into a SysError and throw it
            try
            {
               if (manageLegacyError)
               {
                  throwError = true;
                  server.recordForLegacyThrow(errmsg, num);
               }
               else
               {
                  // display the error message (because of pause processing, an
                  // endkey, error... even stop condition can be generated here)
                  // it's already been logged earlier, so marking it for not logging
                  displayError(errmsg, false, null, true, isFromLogManager);
               }
            }
            
            catch (ConditionException err)
            {
               if (serverSide && TransactionManager.isNestedMode())
               {
                  // honor nested mode here
                  TransactionManager.saveNestedError(err);
               }
               else
               {
                  throw err;
               }
            }
            
            if (last && throwError)
            {
               work.get().forceExplicit = forceExplicitException;
               throw new DeferredLegacyErrorException();
            }

            if (last && !isWarningMode())
            {
               if (mustManageError)
               {
                  TransactionManager.triggerErrorInCaller(num, text, true, prefix);
               }

               // raise the error condition
               throw new ErrorConditionException(num, errmsg, new NumberedException(text, nums));
            }
         }
      }
   }

   /**
    * Raise an error condition and optionally record the given error (in silent mode).
    * <p>
    * In silent mode, the {@link #silent} worker will catch the exception and it will not be
    * seen by the converted code.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    * @param    asMsg
    *           <code>true</code> to use the given text as the message text else
    *           <code>false</code> to use buildErrorText.
    *
    * @throws   ErrorConditionException
    */
   public static void recordOrThrowError(int num, String text, boolean prefix, boolean asMsg)
   {
      recordOrThrowError(new int[] { num }, new String[] { text }, prefix, asMsg);
   }

   /**
    * Raise an error condition and optionally record a chain of errors (in silent mode).
    * <p>
    * In silent mode, the {@link #silent} worker will catch the exception and it will not be
    * seen by the converted code.
    * <p>
    * In silent mode, the error messages and numbers are extracted from <code>error</code>
    * and any other instances of <code>NumberedException</code> present in the chain of root
    * cause exceptions.
    * <p>
    * The first error entry recorded is derived from the given error number and text, however.
    *
    * @param   num
    *          The error number.
    * @param   text
    *          The text of the error message.
    * @param   error
    *          Exception containing an error number, message, and optionally
    *          a root cause chain.
    */
   public static void recordOrThrowError(int num,
                                         String text,
                                         NumberedException error)
   {
      recordOrThrowError(new NumberedException(text, num, error));
   }
   
   /**
    * Manufacture error text based on the given parameters, if silent error mode is enabled, then
    * this error number and the generated error text will be stored in the error list, otherwise
    * this generated error text will be thrown (and execution of the procedure halted).
    *
    * @param   id
    *          The id of the message. The associated text is filled with {@code params} tokens.
    * @param   params
    *          The parameters for the message. If there are less than two token-parameters
    *          use empty Strings ({@code ""}) to make the compiler select the proper signature.
    */
   public static void recordOrThrowError(int id, String... params)
   {
      recordOrThrowError(new int[] { id }, new String[] { replaceTokens(id, params) }, false, false, true);
   }
   
   /**
    * Raise an error condition and optionally record a chain of errors (in silent mode).
    * <p>
    * In silent mode, the {@link #silent} worker will catch the exception and it will not be
    * seen by the converted code.
    * <p>
    * In silent mode, the error messages and numbers are extracted from <code>error</code>
    * and any other instances of <code>NumberedException</code> present in the chain of root
    * cause exceptions.
    *
    * @param   error
    *          Exception containing an error number, message, and optionally
    *          a root cause chain.
    */
   public static void recordOrThrowError(NumberedException error)
   {
      if (isIgnore())
      {
         return;
      }
      
      boolean silent = isSilentError();
      
      log("stack trace follows", error, silent);
      
      if (silent)
      {
         // this may reset the error list so it must be done BEFORE we add
         // the new error(s) to the list
         server.setPending(true);
         
         for (ErrorEntry errorEntry : compileErrorEntries(error))
         {
            server.addRecord(errorEntry);
         }
      }
      
      throwError(error, silent);
   }

   /**
    * Throws an {@link ErrorConditionException} and if in silent mode, it sets the
    * <code>pending</code> flag to <code>true</code>.  NO error information (messages or
    * error numbers) will be recorded in either case (i.e. silent mode or not).  In silent
    * mode the <code>ERROR-STATUS:ERROR</code> will be <code>true</code> but the other "records"
    * of the error that normally exist, will not exist. No message will be displayed.  Because
    * no message is ever displayed or recorded, the normal error number/message processing
    * is not executed.
    * <p>
    * This only works on the server side.
    *
    * @param    msg
    *           The text to use in throwing the error exception.
    * @param    isFromLogManager
    *           Flag to indicate the error comes from LOG-MANAGER.
    */
   public static void noRecordOrShowError(String msg, boolean isFromLogManager)
   {
      WorkArea wa = work.obtain();

      if (isSilentWorker(wa))
      {
         // ERROR-STATUS:ERROR flag is set, but no error is recorded
         setPendingWorker(wa, true);
         return;
      }
      
      if (isIgnore())
         return;

      // show the ERROR message
      if (!isHeadless() && errorWriter != null)
      {
         if (server.isSystemAlertBoxes())
         {
            errorWriter.messageBox(msg, "Error", false);
         }
         else
         {
            errorWriter.message(msg);
         }
      } 
   }
   
   /**
    * Throws an {@link ErrorConditionException} and if in silent mode, it sets the
    * <code>pending</code> flag to <code>true</code>.  NO error information (messages or
    * error numbers) will be recorded in either case (i.e. silent mode or not).  In silent
    * mode the <code>ERROR-STATUS:ERROR</code> will be <code>true</code> but the other "records"
    * of the error that normally exist, will not exist. No message will be displayed.  Because
    * no message is ever displayed or recorded, the normal error number/message processing
    * is not executed.
    * <p>
    * This only works on the server side.
    * 
    * @param    msg
    *           The text to use in throwing the error exception.
    */
   public static void noRecordOrThrowError(String msg)
   {
      noRecordOrThrowErrorWorker(work.obtain(), msg);
   }
   
   /**
    * Display error text and throw an exception based upon the given
    * exception.  The error messages and numbers are extracted from
    * <code>error</code> and any other instances of
    * <code>NumberedException</code> present in the chain of root cause
    * exceptions.  The first error entry recorded is derived from the given
    * error number and text, however.
    *
    * @param   num
    *          The error number.
    * @param   text
    *          The text of the error message.
    * @param   error
    *          Exception containing an error number, message, and optionally
    *          a root cause chain.
    */
   public static void throwError(int num, String text, NumberedException error)
   {
      throwError(new NumberedException(text, num, error));
   }
   
   /**
    * Display error text and throw an exception based upon the given
    * exception.  The error messages and numbers are extracted from
    * <code>error</code> and any other instances of
    * <code>NumberedException</code> present in the chain of root cause
    * exceptions.
    *
    * @param   error
    *          Exception containing an error number, message, and optionally
    *          a root cause chain.
    */
   public static void throwError(NumberedException error)
   {
      throwError(error, isSilent());
   }
   
   /**
    * Display error text and throw an exception based upon the given exception.  The error
    * messages and numbers are extracted from <code>error</code> and any other instances of
    * <code>NumberedException</code> present in the chain of root cause exceptions.  The first
    * error entry recorded is derived from the given error number and text, however.
    * <p>
    * This version of the method allows explicit control over silent error mode processing. This
    * means that error processing which is normally suppressed, can be explicitly re-enabled
    * by passing {@code suppress} as {@code false} even if {@link #isSilentError} would return
    * {@code true}.  ONLY USE THIS IF YOU REALLY KNOW WHAT YOU ARE DOING. Bypassing silent error
    * mode is usually a bad idea.  There are some use cases in the 4GL where this is needed and
    * so that is why this method exists.
    *
    * @param   num
    *          The error number.
    * @param   text
    *          The text of the error message.
    * @param   error
    *          Exception containing an error number, message, and optionally a root cause chain.
    * @param   suppress
    *          {@code true} to suppress the display of error texts, else {@code false}.
    */
   public static void throwError(int num, String text, NumberedException error, boolean suppress)
   {
      throwError(new NumberedException(text, num, error), suppress);
   }
   
   /**
    * Display error text and throw an exception based upon the given exception.  The error
    * messages and numbers are extracted from <code>error</code> and any other instances of
    * <code>NumberedException</code> present in the chain of root cause exceptions.
    * <p>
    * This version of the method allows explicit control over silent error mode processing. This
    * means that error processing which is normally suppressed, can be explicitly re-enabled
    * by passing {@code suppress} as {@code false} even if {@link #isSilentError} would return
    * {@code true}.  ONLY USE THIS IF YOU REALLY KNOW WHAT YOU ARE DOING. Bypassing silent error
    * mode is usually a bad idea.  There are some use cases in the 4GL where this is needed and
    * so that is why this method exists.
    *
    * @param   error
    *          Exception containing an error number, message, and optionally a root cause chain.
    * @param   suppress
    *          {@code true} to suppress the display of error texts, else {@code false}.
    */
   public static void throwError(NumberedException error, boolean suppress)
   {
      if (!isHeadless() && !suppress)
      {
         // display the messages
         Iterator<String> iter = errorTexts(error);

         // check if in silent mode
         boolean wasSilent = ErrorManager.isSilent();
         try
         {
            // temporarily disable silent mode
            ErrorManager.setSilent(false);
            while (iter.hasNext())
            {
               try
               {
                  // because of possible pause processing, an endkey, error...
                  // even stop condition can be generated here
                  displayError(iter.next());
               }
               catch (ConditionException err)
               {
                  if (serverSide && TransactionManager.isNestedMode())
                  {
                     // honor nested mode here
                     TransactionManager.saveNestedError(err);
                  }
                  else
                  {
                     throw err;
                  }
               }
            }
         }
         finally
         {
            // restore silent mode if needed
            if (wasSilent)
            {
               ErrorManager.setSilent(true);
            }
         }
      }
      
      if (!isWarningMode())
      {
         // raise the error condition
         throw new ErrorConditionException(error).setForce(!suppress);
      }
      else
      {
         addRaisedCondition(error.getNumbers());
      }
   }

   /**
    * Throws 4GL error #5890.
    * 
    * @param    method
    *           The method name
    * @param    reason
    *           The error reason
    */
   public static void throwErrorWhileProcessingComponentProperty(String method, String reason)
   {
      recordOrShowError(5890,
                        "Error occuired while processing component property/method: " +
                        method + ".\n" + reason,
                        true);
   }

   /**
    * Throws 4GL error #5677.
    */
   public static void throwErrorUnableToSetComHandleProperty()
   {
      recordOrShowError(5677, "Unable to set com-handle property", true);
   }

   /**
    * Manufactures Progress compatible formatted error text based on the
    * given error data.  The given data is substituted into the following
    * string "** &#37;s. (&#37;i)".
    *
    * @param    num
    *           The error number substituted for &#37;i in the format string.
    * @param    text
    *           The text of the error message substituted for &#37;s in the
    *           format string.
    *
    * @return   The formatted text.
    */
   public static String buildErrorText(int num, String text)
   {
      return buildErrorText(num, text, true);
   }
   
   /**
    * Manufactures Progress compatible formatted error text based on the
    * given error data.  The given data is substituted into the following
    * string "** &#37;s. (&#37;i)".
    *
    * @param    num
    *           The error number substituted for &#37;i in the format string.
    * @param    text
    *           The text of the error message substituted for &#37;s in the
    *           format string.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    *
    * @return   The formatted text.
    */
   public static String buildErrorText(int num, String text, boolean prefix)
   {
      return buildErrorText(num, text, prefix, true);
   }
   
   /**
    * Manufactures Progress compatible formatted error text based on the
    * given error data.  The given data is substituted into the following
    * string "** &#37;s. (&#37;i)".
    *
    * @param    num
    *           The error number substituted for &#37;i in the format string.
    * @param    text
    *           The text of the error message substituted for &#37;s in the
    *           format string.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    * @param    addDot
    *           <code>true</code> if a dot is added to the end of the message.
    *
    * @return   The formatted text.
    */
   public static String buildErrorText(int num, String text, boolean prefix, boolean addDot)
   {
      // build up the message with StringBuilder rather than String.format, as this code may
      // be called quite often in normal control flow, and String.format is expensive
      StringBuilder buf = (prefix ? new StringBuilder("** ") : new StringBuilder());
      buf.append(text);
      
      // SysError with error number 0 does not add this
      if (num > 0)
      {
         buf.append(addDot ? ". (" : " (");
         buf.append(num);
         buf.append(')');
      }
      
      return buf.toString();
   }
   
   /**
    * Manufactures Progress compatible formatted abend error text based on the
    * given error data.  The given data is substituted into the following
    * string "&#37;s. (&#37;i)".
    *
    * @param    num
    *           The error number substituted for &#37;i in the format string.
    * @param    text
    *           The text of the error message substituted for &#37;s in the
    *           format string.
    *
    * @return   The formatted text.
    */
   public static String buildAbendText(int num, String text)
   {
      return String.format("%s. (%d)", text, num);
   }
   
   /**
    * Adds an error entry to the error list.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    errFlag
    *           If <code>false</code> this should add the number and text to
    *           the error list but not set the error flag.  If
    *           <code>true</code> the error flag should also be set.
    */
   public static void addError(int num, String text, boolean errFlag)
   {
      addError(num, text, errFlag, true);
   }
   
   /**
    * Adds an error entry to the error list.
    *
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    errFlag
    *           If <code>false</code> this should add the number and text to
    *           the error list but not set the error flag.  If
    *           <code>true</code> the error flag should also be set.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    */
   public static void addError(int num, String text, boolean errFlag, boolean prefix)
   {
      addErrorWorker(work.obtain(), num, text, errFlag, prefix);
   }
   
   /**
    * Returns the state of the current error flag which denotes if any error has occurred
    * while silent mode is active.  If silent error mode is not active, then this method always
    * will return <code>false</code>.
    *
    * @return    State of the current error flag.
    */
   public static boolean isPendingError()
   {
      return server.isSilent() ? server.isPending() : false;
   }
   
   /**
    * Obtain the current ERROR:ERROR-STRING attribute.
    *
    * @return  the current value.
    */
   public static character errorString()
   {
      UnimplementedFeature.missing("ERROR-STATUS:ERROR-STRING");
      return new character();
   }
   
   /**
    * Sets the ERROR:ERROR-STRING attribute.
    *
    * @param   errStr
    *          The new value.
    */
   public static void changeErrorString(String errStr)
   {
      // TODO: 4GL reports this as invalid attribute, but it was found in customer code logic...
      // maybe is related to appserver/rest/web calls?
      UnimplementedFeature.missing("ERROR-STATUS:ERROR-STRING");
   }
   
   /**
    * Sets the ERROR:ERROR-STRING attribute.
    *
    * @param   errStr
    *          The new value.
    */
   public static void changeErrorString(character errStr)
   {
      UnimplementedFeature.missing("ERROR-STATUS:ERROR-STRING");
   }
   
   /**
    * Returns the state of the error flag which was set during the last silent mode processing,
    * even if silent error mode is not currently active. If silent error mode was never used,
    * then <code>false</code> will be returned.
    *
    * @return    Current state of the error flag.
    */
   @LegacyAttribute(name = "ERROR", ignore = true)
   public static logical isError()
   {
      return logical.of(work.obtain().errorStatus.error);
   }
   
   /**
    * Returns the state of the error flag which was set during the last silent mode processing,
    * even if silent error mode is not currently active. If silent error mode was never used,
    * then <code>false</code> will be returned.
    *
    * @return    Current state of the error flag.
    */
   public static logical error()
   {
      return isError();
   }
   
   /**
    * Returns the number of the errors (recorded in the error list) which have occurred during
    * the last usage of silent error mode.
    *
    * @return    Current number of errors in the error list if silent mode has been enabled.
    *            This will be 0 if no errors have occurred since the last use of silent error
    *            or if silent error mode has never been used.
    */
   public static integer numErrors()
   {
      ArrayList<Object> errors = work.obtain().errorStatus.errorList;
      return new integer(errors == null ? 0 : errors.size());
   }
   
   /**
    * Undocumented feature encountered in customer code. After all, NUM-MESSAGES (KW_NUM_MSG)
    * attribute seems to be writable.
    * 
    * TODO: investigate if any number can be set or if there are some constraints:
    *    Ex: only 0 can be assigned, meaning some kind of clear error status.
    * 
    * @param   errCount
    *          New value.
    */
   @LegacyAttribute(name = "NUM-MESSAGES", setter = true)
   public static void setNumErrors(int64 errCount)
   {
      UnimplementedFeature.missing("ERROR-STATUS:NUM-MESSAGES not implemented.");
   }
   
   
   /**
    * Throw a StopConditionException condition exception, or differ it as a LegacyStop if 
    * -catchStop startup parameter is enabled and CATCH for Progress.Lang.Stop or of of it's
    * subclasses was detected. 
    *
    * @param    msg
    *           The error message.
    */
   public static void handleStopException(String msg)
   {
      handleStopException(new StopConditionException(msg));
   }
   
   /**
    * Throw a StopConditionException condition exception, or differ it as a LegacyStop if 
    * -catchStop startup parameter is enabled and CATCH for Progress.Lang.Stop or of of it's
    * subclasses was detected. 
    *
    * @param    msg
    *           The error message.
    */
   public static void handleStopException(object<? extends Stop> stopException)
   {
       if (isIgnore())
       {
          return;          
       }
       
       boolean manageLegacyStop = server.mustThrowLegacyStop();
       
       if (!manageLegacyStop)
       {
          throw new StopConditionException("UNDO, THROW STOP FALLBACK");
       }
       
       LegacyStopException lex = new LegacyStopException(stopException); 
       throw lex;
   }
   
   /**
    * Throw a StopConditionException condition exception, or differ it as a LegacyStop if 
    * -catchStop startup parameter is enabled and CATCH for Progress.Lang.Stop or of it's
    * subclasses was detected. 
    *
    * @param    msg
    *           The error message.
    */
   public static void handleStopException(StopConditionException ex)
   {
       if (isIgnore())
       {
          return;          
       }
       
       boolean manageLegacyStop = server.mustThrowLegacyStop();
       
       if (manageLegacyStop)
       {
          server.recordForLegacyStop(ex);
          throw new DeferredLegacyStopException();     
       }
       
       throw ex;
   }
   
   /**
    * Returns the Progress compatible error number associated with the error
    * entry at the given index in the error list.  If the list is empty or
    * if the index is invalid, 0 is returned.
    *
    * @param    index
    *           Position in the error list from which to return the entry.
    *           This is 1-based list index, where 1 is the oldest error number
    *           and highest number is the most recent error number.
    *
    * @return   The error number at that index or 0 if the list is empty or
    *           if the index is invalid.
    */
   public static integer getErrorNumber(integer index)
   {
      if (index.isUnknown())
      {
         return new integer();
      }
      
      return getErrorNumber(index.intValue());
   }
   
   /**
    * Returns the Progress compatible error number associated with the error
    * entry at the given index in the error list.  If the list is empty or
    * if the index is invalid, 0 is returned.
    *
    * @param    index
    *           Position in the error list from which to return the entry.
    *           This is 1-based list index, where 1 is the oldest error number
    *           and highest number is the most recent error number.
    *
    * @return   The error number at that index or 0 if the list is empty or
    *           if the index is invalid.
    */
   public static integer getErrorNumber(int index)
   {
      int result = 0;
      
      if (index > 0 && index <= server.getSize())
      {
         ErrorEntry ee = (ErrorEntry) server.getRecord(--index);
         
         result = ee.num;
      }
      
      return new integer(result);
   }
   
   /**
    * Java implementation for the _msg() function.
    * <p>
    * This will return the error number found on the error stack at the given position, 
    * or 0 if there isn't any error in the stack at the given position.
    * <p>
    * This implementation always will return 0 for any non-positive number (0 or negative). 
    * <p>
    * In the 4GL, some negative numbers will return what appears to be a random number. 
    * This is most likely a flaw in the 4GL implementation where there is improper boundary 
    * testing and an invalid index value is used to dereference memory that is not associated 
    * with the error stack.
    * <p> 
    * As such the behavior is non-deterministic and also is a likely security flaw.
    * <p> 
    * This implementation is deliberately not duplicating this bad behavior and it seems unlikely
    * that an application would be coded to rely upon it.
    * 
    * @param  index
    *         The position of the stack from which we want to get the value error number.
    *         This should be greater than 0 for valid results.
    *         This is 1-based stack offset, where 1 is the most recent error number
    *         and highest number is oldest error number in the stack.
    * 
    * @return The error number found at the given position in the error stack, or 0 if there
    *         isn't any error number at the specified position
    */
   public static integer getErrorNumberAtIndex(long index)
   {
      return getErrorNumberAtIndex(new int64(index));
   }
   
   /**
    * Java implementation for the _msg() function.
    * <p>
    * This will return the error number found on the error stack at the given position, 
    * or 0 if there isn't any error in the stack at the given position.
    * <p>
    * This implementation always will return 0 for any non-positive number (0 or negative). 
    * <p>
    * In the 4GL, some negative numbers will return what appears to be a random number. 
    * This is most likely a flaw in the 4GL implementation where there is improper boundary 
    * testing and an invalid index value is used to dereference memory that is not associated 
    * with the error stack.
    * <p> 
    * As such the behavior is non-deterministic and also is a likely security flaw.
    * <p> 
    * This implementation is deliberately not duplicating this bad behavior and it seems unlikely
    * that an application would be coded to rely upon it.
    * 
    * @param  index
    *         The position of the stack from which we want to get the value error number.
    *         This should be greater than 0 for valid results.
    *         This is 1-based stack offset, where 1 is the most recent error number
    *         and highest number is oldest error number in the stack.
    * 
    * @return The error number found at the given position in the error stack, or 0 if there
    *         isn't any error number at the specified position
    */
   public static integer getErrorNumberAtIndex(integer index)
   {
      return getErrorNumberAtIndex(new int64(index));
   }
   
   /**
    * Java implementation for the _msg() function.
    * <p>
    * This will return the error number found on the error stack at the given position, 
    * or 0 if there isn't any error in the stack at the given position.
    * <p>
    * This implementation always will return 0 for any non-positive number (0 or negative). 
    * <p>
    * In the 4GL, some negative numbers will return what appears to be a random number. 
    * This is most likely a flaw in the 4GL implementation where there is improper boundary 
    * testing and an invalid index value is used to dereference memory that is not associated 
    * with the error stack.
    * <p> 
    * As such the behavior is non-deterministic and also is a likely security flaw.
    * <p> 
    * This implementation is deliberately not duplicating this bad behavior and it seems unlikely
    * that an application would be coded to rely upon it.
    * 
    * @param  index
    *         The position of the stack from which we want to get the value error number.
    *         This should be greater than 0 for valid results.
    *         This is 1-based stack offset, where 1 is the most recent error number
    *         and highest number is oldest error number in the stack.
    * 
    * @return The error number found at the given position in the error stack, or 0 if there
    *         isn't any error number at the specified position
    */
   public static integer getErrorNumberAtIndex(int64 index)
   {
      // default value for incorrect index or not existent value for it is 0
      integer result = new integer(0);
      
      int intIdx = index.intValue();
      
      // check if index is valid
      if (intIdx > 0 && intIdx <= ERR_CONDITIONS_CAPACITY)
      {
         LinkedList<Integer> conds = getRaisedConditionsWorker(work.obtain());
         if (intIdx <= conds.size())
         {
            Integer errorNumber = conds.get(intIdx - 1);
            
            // check if there are error number exists for specified index
            if (errorNumber != null)
            {
               result = new integer(errorNumber);
            }
         }
      }

      return result;
   }
   
   /**
    * Returns the Progress compatible error text associated with the error
    * entry at the given index in the error list.  If the list is empty or
    * if the index is invalid, an empty string "" is returned.
    *
    * @param    index
    *           Position in the error list from which to return the entry.
    *
    * @return   The error text at that index or the empty string "" if the
    *           list is empty or if the index is invalid.
    */
   public static character getErrorText(integer index)
   {
      if (index.isUnknown())
      {
         return new character();
      }
      
      return getErrorText(index.intValue());
   }
   
   /**
    * Returns the Progress compatible error text associated with the error
    * entry at the given index in the error list.  If the list is empty or
    * if the index is invalid, an empty string "" is returned.
    *
    * @param    index
    *           Position in the error list from which to return the entry.
    *
    * @return   The error text at that index or the empty string "" if the
    *           list is empty or if the index is invalid.
    */
   public static character getErrorText(int index)
   {
      String result = "";
      
      if (index > 0 && index <= server.getSize())
      {
         ErrorEntry ee = (ErrorEntry) server.getRecord(--index);
         
         result = ee.text;
      }
      
      return new character(result);
   }   
   
   /**
    * Queries the state of <code>silent</code> variable.
    *
    * @return   <code>true</code> if silent mode is enabled, otherwise
    *           <code>false</code>.
    */
   public static boolean isSilent()
   {
      return server.isSilent();
   }
   
   /**
    * Sets the <code>silent</code> variable.
    *
    * @param    value
    *           The new state of the variable.
    */
   public static void setSilent(boolean value)
   {
      server.setSilent(value);
   }
   
   /**
    * Queries the state of <code>error</code> variable.
    *
    * @return   <code>true</code> if error flag is turned on, otherwise
    *           <code>false</code>.
    */
   public static boolean isErrorFlag()
   {
      return server.isErrorFlag();
   }
   
   /**
    * Sets the error variable.
    *
    * @param    value
    *           The new state of the variable.
    */
   @LegacyAttribute(name = "ERROR", setter = true)
   public static void setError(boolean value)
   {
      // this does not affect the recorded error messages or numbers.
      work.obtain().errorStatus.error = value;
   }
   
   /**
    * Sets the error variable.
    *
    * @param    value
    *           The new state of the variable.
    */
   public static void error(boolean value)
   {
      setError(value);
   }
   
   /**
    * Sets the error variable.
    *
    * @param    value
    *           The new state of the variable.
    */
   @LegacyAttribute(name = "ERROR", setter = true)
   public static void setError(logical value)
   {
      if (value.isUnknown())
      {
         // unknown value is ignored
         return;
      }
      
      setError(value.booleanValue());
   }
   
   /**
    * Sets the error variable.  This is a no-op, as the ERROR-STATUS:ERROR attribute
    * is read-only.
    *
    * @param    value
    *           The new state of the variable.
    */
   public static void error(logical value)
   {
      setError(value);
   }
   
   /**
    * Queries the state of <code>pendingError</code> variable.
    *
    * @return   <code>true</code> if pending error flag is turned on, otherwise
    *           <code>false</code>.
    */
   public static boolean isPending()
   {
      return server.isPending();
   }
   
   /**
    * Sets the <code>pendingError</code> variable.
    *
    * @param    value
    *           The new state of the variable.
    */
   public static void setPending(boolean value)
   {
      server.setPending(value);
   }
   
   /**
    * Determines if headless mode should be supported. In headless mode, no client-side output will occur.
    * If this is running on the client, then the return value is always {@code false} (because by definition
    * there is a client side even in batch mode).
    *
    * @return   <code>true</code> if the headless flag is turned on, otherwise <code>false</code>.
    */
   public static boolean isHeadless()
   {
      return serverSide ? isHeadlessWorker(work.obtain()) : false;
   }
   
   /**
    * Gets the size of the list of error records.
    *
    * @return   The size of the list.
    */
   public static int getSize()
   {
      return server.getSize();
   }
   
   /**
    * Adds a record to the list of error records.
    *
    * @param    record
    *           The object to add to the list of error records.
    */
   public static void addRecord(Object record)
   {
      server.addRecord(record);
   }
   
   /**
    * Gets a record from the list of error records.
    *
    * @param    index
    *           The record index.
    *
    * @return   The object associated with the given index in the list.
    */
   public static Object getRecord(int index)
   {
      return server.getRecord(index);
   }
   
   // GES_TODO: remove when all references are switched to ErrorHelper 
   /**
    * Clears pending error status.
    */
   public static void clearPending()
   {
      work.obtain().pendingErrorStatus.initialize();
   }
   
   /**
    * Check if the top-level block is ROUTINE-LEVEL UNDO, THROW or the current program is 
    * BLOCK-LEVEL UNDO, THROW.
    * 
    * @return  see above.
    */
   public static boolean mustManageLegacyError()
   {
      return BlockManager.mustManageLegacyError();
   }
   /**
    * Check if the current ERROR condition must be raised as a legacy {@link SysError}.
    * 
    * @param   forceBlockLevelUndoThrow
    *          Flag indicating that, when nothing else matches, a BLOCK-LEVEL UNDO, THROW exists by default.
    *          
    * @return  see above.
    */
   public static boolean mustManageLegacyError(boolean forceBlockLevelUndoThrow)
   {
      return BlockManager.mustManageLegacyError(forceBlockLevelUndoThrow);
   }
   
   /**
    * Check if the current ERROR condition must be raised as a legacy {@link SysError}.
    * 
    * @return  see above.
    */
   public static boolean mustThrowLegacyError()
   {
      return mustThrowLegacyError(false);
   }
   
   /**
    * Check if the current STOP condition must be raised as a legacy {@link LegacyStop}.
    * 
    * @return  see above.
    */
   public static boolean mustThrowLegacyStop()
   {
      return BlockManager.hasLegacyStop(Stop.class);
   }
   
   /**
    * Check if the current ERROR condition must be raised as a legacy {@link SysError}.
    *
    * @param   forceBlockLevelUndoThrow
    *          Flag indicating that, when nothing else matches, a BLOCK-LEVEL UNDO, THROW exists by default.
    * 
    * @return  see above.
    */
   public static boolean mustThrowLegacyError(boolean forceBlockLevelUndoThrow)
   {
      return BlockManager.hasLegacyError(SysError.class, 0) ||
             (BlockManager.mustManageLegacyError(forceBlockLevelUndoThrow) && 
              (BlockManager.hasLegacyError(SysError.class, -1) || 
               (TransactionManager.isRootNestingLevel() && AppServerManager.isRemote())));
   }
   
   /**
    * Check if any CATCH blocks defined at the specified enclosing block can manage this exception.
    * 
    * @return   <code>true</code> if this exception can be processed by the current block.
    */
   public static boolean hasLegacyError()
   {
      return BlockManager.hasLegacyError(SysError.class, 0);
   }
   
   /**
    * Record the specified error message and number, to be later thrown as a 
    * {@link LegacyErrorException}.
    * 
    * @param    errmsg
    *           The error message.
    * @param    num
    *           The error number.
    */
   public static void recordForLegacyThrow(String errmsg, int num)
   {
      WorkArea wa = work.get();
      
      if (num == 293 && (wa.legacyError == null || !wa.legacyError._isValid()))
      {
         // StopError is raised when a procedure in a run statement is not found.
         // Catch blocks that handles StopError errors, will not be able to handle
         // the error if raised as a SysError.
         wa.legacyError = StopError.newInstance(errmsg, num);
      }
      else if (wa.legacyError == null || !wa.legacyError._isValid())
      {
         wa.legacyError = SysError.newInstance(errmsg, num);
      }
      else
      {
         wa.legacyError.ref().addMessage(new character(errmsg), new integer(num));
      }
   }
   
   /**
    * STOP condition registered, to be later thrown as a 
    * {@link LegacyStopException}.
    * 
    * @param    ex
    *           StopConditionException to be converted in LegacyStop.
    */
   public static void recordForLegacyStop(StopConditionException ex)
   {
      WorkArea wa = work.get();
      StopAfterTimer stopAfterTimer = wa.stopAfterTimer;
      
      if (wa.legacyStop == null || !wa.legacyStop._isValid())
      {  
         if (stopAfterTimer != null && stopAfterTimer.isStopAfterConditionRaised())
         {
            wa.legacyStop = StopAfter.newInstance();
            return;
         }
         else if (ex instanceof UserInterruptConditionException)
         {
            wa.legacyStop = UserInterrupt.newInstance();
            return;
         }
         else if (ex instanceof LockConflictConditionException) 
         {
            wa.legacyStop = LockConflict.newInstance();
            return;
         }
         
         wa.legacyStop = Stop.newInstance();
      }
   }
   
   /**
    * Throw the specified error message and number.
    *
    * @param    num
    *           The error number.
    * @param    errmsg
    *           The error message.
    */
   public static void throwErrorConditionException(int num, String errmsg)
   {
      throw new ErrorConditionException(num, errmsg);
   }
   
   /**
    * Get the deferred legacy error from {@link WorkArea#legacyError}, and wrap it as a 
    * {@link LegacyErrorException}.
    * <p>
    * This approach allows the FWD client-side to defer throwing a {@link LegacyErrorException},
    * as the FWD {@link object} instances (and any {@link _BaseObject_} instance) are not
    * serializable, and can't be sent over the network.
    * 
    * @return   See above.
    */
   public static LegacyErrorException getDeferredLegacyError()
   {
      WorkArea wa = work.get();
      object<? extends SysError> legacyError = wa.legacyError;
      wa.legacyError = null;
      
      LegacyErrorException lex = new LegacyErrorException(legacyError);
      lex.setExplicit(wa.forceExplicit);
      wa.forceExplicit = false;
      return lex;
   }
   
   /**
    * Get the deferred legacy stop error from {@link WorkArea#legacyStop}, and wrap it as a 
    * {@link LegacyStopException}.
    * <p>
    * This approach allows the FWD client-side to defer throwing a {@link LegacyStopException},
    * as the FWD {@link object} instances (and any {@link _BaseObject_} instance) are not
    * serializable, and can't be sent over the network.
    * 
    * @return   See above.
    */
   public static LegacyStopException getDeferredLegacyStop()
   {
      WorkArea wa = work.get();
      object<? extends Stop> legacyStop = wa.legacyStop;
      wa.legacyStop = null;
      
      LegacyStopException lex = null;

      lex = new LegacyStopException(legacyStop);
      return lex;
   }
   
   /**
    * Display and raise an ERROR condition indiscriminately of the NO-ERROR state.
    * 
    * @param    errors
    *           The error texts to display.
    */
   public static void displayAndRaiseError(List<String> errors)
   {
      boolean silent = ErrorManager.isSilentError();
      try
      {
         ErrorManager.setSilent(false);
         for (String err : errors)
         {
            displayError(err);
         }
         ErrorConditionException exc = new ErrorConditionException("raising multiple errors");
         exc.setForce(true);
         throw exc;
      }
      finally
      {
         ErrorManager.setSilent(silent);
      }
   }
   
   /**
    * Format the text of the error message and then display that text on
    * on the terminal. Adds the error number to _MSG stack.
    *
    * @param    num
    *           Error number.
    * @param    msg
    *           The text to display in the message line.
    */
   public static void displayError(int num, String msg)
   {
      displayError(num, msg, true);
   }
   
   /**
    * Format the text of the error message and then display that text on
    * on the terminal. Adds the error number to _MSG stack.
    *
    * @param    num
    *           Error number.
    * @param    msg
    *           The text to display in the message line.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    */
   public static void displayError(int num, String msg, boolean prefix)
   {
      boolean manageLegacyError = server.mustThrowLegacyError();

      msg = buildErrorText(num, msg, prefix);
      if (manageLegacyError)
      {
         server.recordForLegacyThrow(msg, num);
         throw new DeferredLegacyErrorException();
      }
      
      displayError(msg);
      addRaisedCondition(new int[] {num});
   }
   
   /**
    * Format the text of the warning message and then display that text on
    * on the terminal.
    *
    * @param    num
    *           Error number.
    * @param    msg
    *           The text to display.
    */
   public static void displayWarning(int num, String msg)
   {
      displayWarning(num, msg, true);
   }
   
   /**
    * Format the text of the warning message and then display that text on
    * on the terminal.
    *
    * @param    num
    *           Error number.
    * @param    msg
    *           The text to display.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    */
   public static void displayWarning(int num, String msg, boolean prefix)
   {
      String errText = buildErrorText(num, msg, prefix);

      // manually using the work area is OK in this case since this method
      // is only used on the server
      WorkArea wa = work.get();

      boolean silentError = isSilentError();

      if (!silentError)
      {
         addRaisedConditionWorker(wa, new int[] {num});
      }
      
      if (wa.suppressWarnings)
      {
         // when SESSION:SUPPRESS-WARNING set to TRUE the warning is not displayed, output or set in
         // ERROR-STATUS, only added to _MSG
         return;
      }
      
      if (silentError)
      {
         // no-error will modify ERROR-STATUS, but only when SUPPRESS-WARNINGS not set to TRUE
         wa.pendingErrorStatus.addError(new ErrorEntry(num, errText));
      }
      else
      {
         displayWarning(errText);
      }
   }
   
   /**
    * Format the text of the error message and then display that text on the terminal.  This is
    * the redirected version of the displayError.  If the output is redirected to file the message
    * is writing to the file. Adds the error number to _MSG stack.
    *
    * @param    num
    *           Error number.
    * @param    msg
    *           The text to display in the message line.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    */
   public static void displayErrorRedirected(int num, String msg, boolean prefix)
   {
      displayErrorRedirected(buildErrorText(num, msg, prefix));
      addRaisedCondition(new int[] {num});
   }
   
   /**
    * Format the text of the error message and then display that text on the terminal.  This is
    * the redirected version of the displayError.  If the output is redirected to file the message
    * is writing to the file.
    *
    * @param    msg
    *           The text to display in the message line.
    */
   public static void displayErrorRedirected(String msg)
   {
      displayErrorRedirected(msg, true);
   }

   /**
    * Format the text of the error message and then display that text on the terminal.  This is
    * the redirected version of the displayError.  If the output is redirected to file the message
    * is writing to the file.
    *
    * @param    msg
    *           The text to display in the message line.
    * @param    logCallStack
    *           Add call stack to log. <code>false</code> when ALERT_BOX message.
    */
   public static void displayErrorRedirected(String msg, boolean logCallStack)
   {
      if (errorWriter != null)
      {
         errorWriter.displayErrorRedirected(msg, logCallStack);
      }
      else
      {
         writeLog(msg, logCallStack);
      }
   }
   
   /**
    * Format the text of the abend message and then display that text on
    * on the terminal.
    *
    * @param    num
    *           Error number.
    * @param    msg
    *           The text to display in the message line.
    */
   public static void displayAbend(int num, String msg)
   {
      displayError(buildAbendText(num, msg));
      addRaisedCondition(new int[]{num});
   }
   
   /**
    * Display the given error message on the terminal.
    * This method does not add a record to _MSG stack.
    *
    * @param    errmsg
    *           The text to display in the message line.
    */
   public static void displayError(String errmsg)
   {
      displayError(errmsg, false);
   }
   
   /**
    * Display the given error message on the terminal. This method does not add a record to
    * _MSG stack.
    *
    * @param    errmsg
    *           The text to display in the message line.
    * @param    noMessageBox
    *           <code>true</code> if error is always displayed in message area or logged rather
    *           than displayed as a message box.
    */
   public static void displayError(String errmsg, boolean noMessageBox)
   {
      displayError(errmsg, noMessageBox, null, true);
   }

   /**
    * Display the given error message.
    * This method does not add a record to _MSG stack.
    * @param    num
    *           Error number.
    * @param    msg
    *           The text to display in the message line.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    * @param    noMessageBox
    *           <code>true</code> if error is always displayed in message area or logged rather
    *           than displayed as a message box.
    */
   public static void displayError(int num, String msg, boolean prefix, boolean noMessageBox)
   {
      msg = buildErrorText(num, msg, prefix);
      displayError(msg, noMessageBox);
   }

   /**
    * Display the given error message on the terminal. This method does not add a record to
    * _MSG stack.
    *
    * @param    errmsg
    *           The text to display in the message line.
    * @param    noMessageBox
    *           <code>true</code> if error is always displayed in message area or logged rather
    *           than displayed as a message box.
    * @param    stack
    *           Progress stack trace
    * @param    logCallStack
    *           Add call stack to log. <code>false</code> when ALERT_BOX message.
    */
   public static void displayError(String errmsg, boolean noMessageBox, String[] stack, boolean logCallStack)
   {
      displayError(errmsg, noMessageBox, stack, logCallStack, false);
   }

   /**
    * Display the given error message on the terminal. This method does not add a record to
    * _MSG stack.
    *
    * @param    errmsg
    *           The text to display in the message line.
    * @param    noMessageBox
    *           <code>true</code> if error is always displayed in message area or logged rather
    *           than displayed as a message box.
    * @param    stack
    *           Progress stack trace
    * @param    logCallStack
    *           Add call stack to log. <code>false</code> when ALERT_BOX message.
    * @param    isFromLogManager
    *           Flag to indicate the error comes from LOG-MANAGER.
    */
   public static void displayError(String errmsg, boolean noMessageBox, String[] stack,
                                   boolean logCallStack, boolean isFromLogManager)
   {
      if (isIgnore() || isHeadless() || isSilent())
      {
         return;
      }
      
      if (errorWriter != null)
      {
         errorWriter.displayError(errmsg, noMessageBox, stack, logCallStack, isFromLogManager);
      }
   }
   
   /**
    * Display the given warning message on the terminal.
    * This method does not add a record to _MSG stack.
    *
    * @param    wrnmsg
    *           The text to display in the message line.
    */
   public static void displayWarning(String wrnmsg)
   {
      if (isIgnore() || isHeadless())
      {
         return;
      }

      if (errorWriter != null)
      {
         errorWriter.displayWarning(wrnmsg);
      }
   }
   
   /**
    * Process a LegacyErrorException, setting the pendingError flag and recording the errors.
    * 
    * @param   lex
    *          The LegacyErrorException that is processed.
    * @param   eh
    *          the context-local error helper for adding the error entries.
    */
   public static void processLegacyErrorException(LegacyErrorException lex, ErrorHelper eh)
   {
      eh.srv.setPending(true);
      
      LegacyError err = lex.getErrorRef();
      if (!(err instanceof AppError) || !((AppError) err).isFromReturn())
      {
         ProError proErr = (ProError) err;
         int numErr = err.getNumMessages().intValue();
         int[] errnums = new int[numErr];
         String[] errmsgs = new String[numErr];
         
         for (int i = 0; i < numErr; i++)
         {
            errnums[i] = proErr.getMessageNum(new integer(i + 1)).intValue();
            errmsgs[i] = proErr.getMessage(new integer(i + 1)).toStringMessage();
            eh.wa.pendingErrorStatus.addError(new ErrorEntry(errnums[i], errmsgs[i]));
         }
         addRaisedConditionWorker(eh.wa, errnums);
      }
      
      if (lex.getError()._isValid())
      {
         ObjectOps.delete(err);
      }
   }
   
   /**
    * Handles a SOAP fault.
    *
    * @param    operationName
    *           The operation that failed.
    * @param    faultMsg
    *           The fault error msg.
    * @param    faultActor
    *           The fault actor.
    * @param    faultCode
    *           The fault code.
    * @param    faultString
    *           The fault string.
    * @param    faultDetailXML
    *           The fault details as xml.
    */
   public static void handleSoapFault(String operationName,
                                      String faultMsg,
                                      String faultActor,
                                      String faultCode,
                                      String faultString,
                                      String faultDetailXML)
   {
      server.handleSoapFault(operationName, faultMsg, faultActor, faultCode, faultString, faultDetailXML);
   }
   
   /**
    * Writes log record.
    *
    * @param    msg
    *           The message to write.
    */
   public static void writeLog(String msg)
   {
      if (!msaManager.isMsaContext() && 
         (SessionManager.get() == null || SessionManager.get().getSession() == null))
      {
         return; // no context: import or server starting up
      }
      server.writeLog(msg, true, Thread.currentThread().getId());
   }
   
   /**
    * Writes log record.
    *
    * @param    msg
    *           The log message.
    * @param    logCallStack
    *           Add call stack to log. <code>false</code> when ALERT_BOX message.
    */
   public static void writeLog(String msg, boolean logCallStack)
   {
      server.writeLog(msg, logCallStack, Thread.currentThread().getId());
   }

   /**
    * Writes log record.
    *
    * @param    msg
    *           The log message.
    * @param    logCallStack
    *           Add call stack to log. <code>false</code> when ALERT_BOX message.
    * @param    threadId
    *           The id of the thread recording the error message.
    */
   public static void writeLog(String msg, boolean logCallStack, long threadId)
   {
      server.writeLog(msg, logCallStack, threadId);
   }
   
   /**
    * Get the type of its associated handle.
    * 
    * @return   Always return the PSEUDO-WIDGET value.
    */
   public static character resourceType()
   {
      return new character(LegacyResource.PSEUDO_WIDGET);
   }
   
   /**
    * Returns a handle for the ERROR-OBJECT-DETAIL attribute, set in cases of SOAP-related faults.
    * 
    * @return   See above.
    */
   public static handle getErrorObjectDetail()
   {
      WorkArea wa = work.get();
      
      return (wa.errorStatus.soapFault == null)
             ? new handle()
             : new handle(wa.errorStatus.soapFault);
   }
   
   /**
    * Reflects the value of SESSION:SUPPRESS-WARNINGS attribute.
    *
    * @return   see above.
    */
   public static boolean isSuppressWarnings()
   {
      return server.isSuppressWarnings();
   }

   /**
    * Reflects the value of SESSION:SUPPRESS-WARNINGS-LIST attribute.
    *
    * @return   see above.
    */
   public static String getSuppressWarningsList()
   {
      return server.getSuppressWarningsList();
   }
   
   /**
    * This method can be only called on the server.
    *
    * @param   value
    *          The new value of {@code  SESSION:SUPPRESS-WARNINGS} attribute.
    */
   public static void setSuppressWarnings(boolean value)
   {
      work.get().suppressWarnings = value;
   }

   /**
    * This method can be only called on the server.
    *
    * @param   value
    *          The new value of {@code  SESSION:SUPPRESS-WARNINGS-LIST} attribute.
    */
   public static void setSuppressWarningsList(String value)
   {
      WorkArea workArea = work.get();

      for (String s : value.split(","))
      {
         s = s.trim();
         try
         {
            workArea.suppressWarningsSet.add(Integer.parseInt(s));
         }
         catch (Exception e)
         {
            int[] nums = {16478, 3131};
            String[] texts = {"The entry " + s + " in the warning list is invalid",
               "Unable to set attribute SUPPRESS-WARNINGS-LIST in widget of type PSEUDO-WIDGET"};
            recordOrThrowError(nums, texts, false);

            workArea.suppressWarningsSet.clear();

            return;
         }
      }

      workArea.suppressWarningsList = value;
   }
   
   /**
    * Get a helper to access certain context-local state required for error support.
    *
    * @return   The context-local helper.
    */
   public static ErrorHelper getErrorHelper()
   {
      // the usage of Stream.putWorker() is used on the client side, which calls TM.setNestedMode() which
      // initializes the TransactionManager on the client; we need safety code here
      ServerErrorDataAccessor errorDataAccessor = server instanceof ServerErrorDataAccessor ?
         (ServerErrorDataAccessor) server :
         null;
      return new ErrorHelper(work.obtain(), errorDataAccessor);
   }
   
   /**
    * Generate an error if a character is invalid in a format string.
    *
    * @param    idx
    *           The 0-based index of the incorrect character in the format
    *           string (this will be converted to a 1-based index).  
    * @param    fmt
    *           The incorrect format string.
    *
    * @return   -1.
    * 
    * @throws   ErrorConditionException
    */
   public static int genInvalidCharError(int idx, String fmt)
   throws ErrorConditionException
   {
      String spec = "Character number %d of format %s is invalid";
      recordOrThrowError(22, String.format(spec, idx + 1, fmt));
      
      // this is the safest thing to return (it should be undone anyway)
      return -1;
   }

   /**
    * Generate an "Invalid ... parameter..." error string.
    * 
    * @param type
    *        the type name, e.g "index", "array-value"
    * @param instance
    *        the instance
    * @param method
    *        the method name
    * @param errMsg
    *        the error message format string
    *        
    * @return the generated string
    */
   public static String getInvalidParameterError(String type,
            _BaseObject_ instance, String method, String errMsg) 
   {
      return String.format("Invalid %s parameter to %s:%s( ). %s", 
               type, 
               instance.getLegacyClass().ref().getTypeName().getValue(),
               method,
               errMsg);
   }
   
   /**
    * Manufacture error text based on the given parameters. We start with the id of a template
    * and replace the positional placeholders to be replaced with the provided string parameters.
    * <p>
    * Because the errors are inconsistent relative to the double asterisk prefix, the returned
    * value will contain it, if the case; following that when the message is displayed, the 
    * prefix to be used to be always {@code false};
    *
    * @param   id
    *          The id of the message. The associated text is filled with {@code params} tokens.
    * @param   params
    *          The parameters for the message.
    */
   public static String replaceTokens(int id, String... params)
   {
// NOTE: cases without indentation were found while doing investigations but are not YET implemented in code.
      String msg = "";
      switch (id)
      {
         case     0: msg = "%1"; break;
         case    26: msg = "** Array subscript %1 is out of range"; break;
         case    43: msg = "** Cannot find or open file %1, errno = %2"; break;
         case   142: msg = "** Unable to update %1 Field"; break;
         case   143: msg = "** %1: Unable to evaluate field for assignment"; break;
         case   223: msg = "** Incompatible data types in expression or assignment"; break;
         case   257: msg = "** The first character of %1 must be alphabetic"; break;
         case   274: msg = "** The character %1 is not permitted in name %2"; break;
         case   293: msg = "** \"%1\" was not found"; break;
         case   341: msg = "** There is no current %1 record. Delete failed"; break;
         case   912: msg = "Code page attribute table for %1 was not found in %2"; break;
         case  1043: msg = "Collation table for code page %1 and collation name %2 was not found in %3"; break;
         case  1587: msg = "ASC could not find the appropriate conversion table in %1"; break;
         
         case  2913: msg = "Unknown sequence expression %1"; break;
         case  3131: msg = "Unable to set attribute %1 in widget %2 of type %3"; break;
         case  3133: msg = "SYSTEM ERROR: Sequence %1 not found"; break;
         case  3140: msg = "Cannot access the %1 attribute because the widget does not exist"; break; // when invoking APPLY-CALLBACK on an uninitialized handle.
         case  3147: msg = "Cannot browse query %1 because query is already being browsed"; break;
         case  3588: msg = "Sequence %1 not found"; break;
         case  4052: msg = "**%1 is not a %2 attribute for %3"; break;
         case  4056: msg = "**Attribute %1 for the %2 has an invalid value of UNKNOWN"; break;
         case  4058: msg = "**Attribute %1 for the %2 has an invalid value of %3"; break;
         case  4061: msg = "**Attribute %1 for the %2 has an invalid value"; break;
         case  4065: msg = "**The %1 attribute on the %2 has invalid arguments"; break;
         case  4078: msg = "**Unable to set attribute %1 for %2"; break;
         case  4083: msg = "**Unable to assign UNKNOWN value to attribute %1 on %2"; break;
         case  5368: msg = "Couldn't update field %1 of target in a BUFFER-COPY statement"; break;
         case  5442: msg = "Invalid datatype for argument to method '%1'. Expecting '%2'"; break;
         case  5468: msg = "Application server connect failure"; break;
         case  5482: msg = "Unknown hostname %1"; break;
         case  5484: msg = "Unknown service %1 transport TCP"; break;
         case  5729: msg = "Incompatible datatypes found during runtime conversion"; break;
         case  6063: msg = "Code page conversion table for %1 to %2 was not found in %3."; break;
         
         case  7319: msg = "ADD/SET-BUFFERS argument %1 was invalid or not found"; break;
         case  7331: msg = "Cannot reposition query %1 to recid/rowid(s) given"; break;
         case  7351: msg = "BUFFER-FIELD %1 was not found in buffer %2"; break;
         case  7352: msg = "You may only invoke EMPTY-TEMP-TABLE on valid temp-tables"; break;
         case  7363: msg = "SKIP-DELETED-RECORD cannot be set on during BROWSE"; break;
         case  7365: msg = "%1 BUFFER-VALUE argument must be valid array index, less than or equal to field extent %2"; break;
         case  7372: msg = "Object of type DATASET widget used where BUFFER-FIELD object required"; break;
         case  9034: msg = "Pair with %1 failed to have a member in each table"; break;
         case  9035: msg = "Pairs list had too many pairs or odd number of entries"; break;
         case  9065: msg = "Cannot set PRIMARY on a TEMP-TABLE that has been prepared with TEMP-TABLE-PREPARE"; break;
         case  9069: msg = "Unable to get TEMP-TABLE handle for parameter"; break;
         case  9092: msg = "Invalid COMPARE operator: %1"; break;
         case  9093: msg = "Invalid COMPARE strength: %2"; break;
         case  9407: msg = "Connection failure for host %1 port %2 transport TCP"; break;
         
         case 10068: msg = "Lead attributes in a chained-attribute expression (a:b:c) must be type HANDLE or a user-defined type and valid (not UNKNOWN)"; break;
         
         case 10515: msg = "Handle type not valid as XML input source"; break;

         case 11265: msg = "Attempt to access blob beyond its end"; break;
         case 11279: msg = "File offset %1 is greater than size of file %2 for file '%3'"; break;              // no DOT at the end
         case 11295: msg = "Attempt to access past end of MEMPTR during COPY-LOB"; break;                      // no DOT at the end
         case 11305: msg = "Cannot copy large object starting at %1 for %2 bytes when object size is %3"; break;
         case 11316: msg = "Could not obtain LONGCHAR"; break;
         case 11329: msg = "File offset %1 is greater than size of file %2 for file '%3'"; break;
         case 11330: msg = "File offset %1 plus copy length %2 is greater than size of file %3 for '%4'"; break;
         case 11331: msg = "File offset %1 is greater than size of file %2 for file '%3'"; break;
         case 11332: msg = "Invalid offset specified in COPY-LOB statement"; break;
         case 11334: msg = "Offset supplied for source large object is greater than object size"; break;
         case 11335: msg = "Offset supplied for target large object is greater than object size"; break;
         case 11339: msg = "The source codepage can not be specified for a CLOB/LONGCHAR field"; break;
         case 11343: msg = "Unknown or invalid file offset specified in COPY-LOB statement"; break;
         case 11344: msg = "Unknown or invalid length specified in COPY-LOB statement"; break;
         case 11345: msg = "Unknown or invalid source file specified in COPY-LOB statement"; break;
         case 11341: msg = "The target codepage conflicts with the longchar's fixed codepage"; break;
         case 11346: msg = "Unknown or invalid target file specified in COPY-LOB statement"; break;
         case 11350: msg = "Write to file '%1' failed during COPY-LOB"; break;
         case 11382: msg = "INPUT/OUTPUT operations are not allowed with RAW, ROWID, MEMPTR, BLOB, CLOB or LONGCHAR type variables"; break;
         case 11393: msg = "MEMPTR target of COPY-LOB must be initialized"; break;
         case 11394: msg = "MEMPTR target of COPY-LOB is not big enough"; break;
         case 11395: msg = "Large object assign or copy failed"; break;
         
         case 11679: msg = "Mismatch between source-codepage argument and LONGCHAR codepage"; break;
         case 11683: msg = "FIX-CODEPAGE can only be run when the LONGCHAR variable is empty"; break;
         case 11851: msg = "COPY-LOB operations between BLOB and CLOB fields are not supported"; break;
         case 11858: msg = "Cannot add/set buffers on a static dataset"; break;
         case 11860: msg = "Buffer %1 may not appear more than once in any dataset"; break;
         case 11863: msg = "FILL is not valid for table %1 when TRACKING-CHANGES is on"; break;
         case 11867: msg = "Query buffers and data-source buffers must match"; break;
         case 11872: msg = "Invalid timezone expression"; break;                                               // no DOT at the end
         case 11874: msg = "FILL requires an attached data-source for buffer %1, or an active before-fill callback procedure"; break;
         case 11875: msg = "FILL of buffer %1 requires a DATA-SOURCE QUERY to be prepared and assigned for this type of buffer"; break;
         case 11876: msg = "FILL of buffer %1 with user-supplied query requires that the query be QUERY-PREPARED"; break;
         case 11878: msg = "FILL got error %1 creating record %2"; break;
         case 11883: msg = "Unable to auto-generate a FILL query predicate for %1 if field %2 in %3 is not mapped"; break;
         case 11884: msg = "Dataset buffer %1 must be for a temp-table"; break;
         case 11885: msg = "A unique primary index is required in the target table, each field of which is mapped to some source field, in order to do a replace-mode or parent-mode COPY/GET/MERGE/FILL type of operation on a dataset table"; break;
         case 11887: msg = "ACCEPT-CHANGES run on table %1 that has no BEFORE-TABLE"; break;
         case 11891: msg = "Found error during GET-CHANGES method"; break;
         case 11892: msg = "Attempt to reference dataset buffer of a data-source before ATTACH-DATA-SOURCE for data-source buffer %1"; break;
         case 11893: msg = "SAVE-WHERE-STRING cannot be generated when there is no BEFORE-TABLE--define one or use TRACKING-CHANGES"; break;
         case 11894: msg = "KEYS phrase for data-source buffer %1 contains ROWID, but no field in %2 was mapped to the rowid during ATTACH-DATA-SOURCE"; break;
         case 11895: msg = "Unable to get SAVE-WHERE-STRING because no KEYS phrase given in DATA-SOURCE definition and no unique index found in %1"; break;
         case 11896: msg = "Unable to generate a save predicate for %1 if field %2 is not mapped in %3"; break;
         case 11897: msg = "Record %1 is not available to set ERROR-STRING"; break;
         case 11906: msg = "Unable to identify data-source buffer for SAVE-ROW-CHANGES"; break;
         case 11909: msg = "Unable to find sequence field  %1 in table %2 during SAVE-ROW-CHANGES"; break;
         case 11910: msg = "SAVE-ROW-CHANGES was unable to update the database table %1"; break;
         case 11912: msg = "Unable to find record for db buffer %1 during SAVE-ROW-CHANGES"; break;
         case 11913: msg = "SAVE-ROW-CHANGES found %1 record with conflicting change by another user"; break;
         case 11916: msg = "%1 record is not available to set ERROR in"; break;
         case 11917: msg = "Cannot set ERROR if there is no BEFORE-TABLE for %1"; break;
         case 11918: msg = "Cannot set ERROR if there is no BEFORE-TABLE row for %1"; break;
         case 11919: msg = "REJECT-CHANGES run on table %1 that has no BEFORE-TABLE"; break;
         case 11922: msg = "MERGE-CHANGES may not be run on table %1 with TRACKING-CHANGES on"; break;
         case 11923: msg = "MERGE-CHANGES run on original table %1 that does not match the ORIGIN-HANDLE in the GET-CHANGES table %2"; break;
         case 11924: msg = "MERGE-CHANGES run on original table %1 and change table %2 whose columns do not match exactly"; break;
         case 11925: msg = "GET/MERGE-CHANGES require a BEFORE-TABLE for table %1"; break;
         case 11926: msg = "Found unexpected error while merging changes"; break;
         case 11928: msg = "INVALID origin-rowid of the change table %1 of %2"; break;
         case 11933: msg = "ACCEPT-ROW-CHANGES must be run on the BEFORE table of buffer %1"; break;
         case 11934: msg = "ACCEPT-ROW-CHANGES buffer %1 not available"; break;
         case 11935: msg = "SAVE-ROW-CHANGES buffer %1 not available"; break;
         case 11936: msg = "REJECT-ROW-CHANGES buffer %1 not available"; break;
         case 11937: msg = "MERGE-ROW-CHANGES buffer %1 not available"; break;
         case 11938: msg = "SAVE-ROW-CHANGES must be run on the BEFORE table of buffer %1"; break;
         case 11939: msg = "REJECT-ROW-CHANGES must be run on the BEFORE table of buffer %1"; break;
         case 11942: msg = "SAVE-ROW-CHANGES for BEFORE buffer %1, requires that the corresponding AFTER dataset member buffer %2 be attached to a DATA-SOURCE"; break;
         case 11943: msg = "First argument to MERGE-ROW-CHANGES must be a valid BUFFER handle"; break;
         case 11944: msg = "MERGE-ROW-CHANGES finds ORIGIN-HANDLE in table %1 is no longer valid"; break;
         case 11945: msg = "BUFFER:FILL is only valid for buffers that are members of a dataset"; break;
         case 11946: msg = "You cannot EMPTY a BEFORE-TABLE, Use ACCEPT-CHANGES or EMPTY the AFTER-TABLE"; break;
         case 11947: msg = "bufferhdl:GET-CHANGES must have a valid buffer handle to the original table, from which changes are coming, as the first argument"; break;
         case 11948: msg = "First argument to MERGE-CHANGES must be a valid BUFFER handle"; break;
         case 11949: msg = "APPLY-CALLBACK is only for buffers that are members of datasets"; break;
         case 11950: msg = "Argument %1 to CALLBACK method must be a valid buffer or dataset event name"; break;
         case 11951: msg = "SET-CALLBACK-PROCEDURE is only valid for buffers that are members of a dataset"; break;
         case 11952: msg = "ATTACH-DATA-SOURCE is only valid for buffers that are members of a dataset"; break;
         case 11953: msg = "Dataset GET-CHANGES and MERGE-CHANGES must have a valid dataset handle as the first argument"; break;
         case 11954: msg = "Dataset GET-CHANGES and MERGE-CHANGES must have a change dataset and an original dataset with the same number of members and the member buffers must have matching schema"; break;
         case 11955: msg = "The argument to Dataset-handle:ADD-RELATION method was invalid"; break;
         case 11956: msg = "ADD-RELATION method requires buffer objects and not table objects"; break;
         case 11957: msg = "ATTACH-DATA-SOURCE must have a valid data-source handle as the first argument"; break;
         case 11958: msg = "ATTACH-DATA-SOURCE may not be called until source buffers have been assigned to the DATA-SOURCE"; break;
         case 11959: msg = "FILL-MODE is only valid for a dataset buffer"; break;
         case 11960: msg = "FILL-MODE must be one of NO-FILL, MERGE, EMPTY, etc."; break;
         case 11961: msg = "Buffers in ADD-RELATION must be members of the dataset"; break;
         case 11962: msg = "Odd number of join pairs in ADD-RELATON"; /* TYPO in ABL */ break;
         case 11963: msg = "ADD-RELATION join pairs list entry %1 not valid--list must have unqualified field names with no embedded blanks, in parent-fld,child-fld order, e.g. 'cust-num,cust-num'"; break;
         case 11964: msg = "ADD-RELATION join pairs list was not valid"; break;
         case 11965: msg = "Attempt to relate dataset buffer %1 to parent with itself in ancestry"; break;
         case 11967: msg = "CLEAR may not be used on a static dataset"; break;
         case 11968: msg = "First argument to DATASET:CREATE-LIKE must be a valid dataset handle or name\""; break; // yes, the double quote is part of error message
         case 11969: msg = "Unable to CLEAR target CREATE-LIKE dataset"; break;
         case 11971: msg = "Cannot delete a relation of a dataset"; break;
         case 11973: msg = "You may not change the buffers for a static data-source"; break;
         case 11974: msg = "The first argument to ADD-SOURCE-BUFFER must be a valid buffer handle"; break;
         case 11975: msg = "ADD-SOURCE-BUFFER key name %1 must be a field in buffer %2"; break;
         case 11976: msg = "Data-source QUERY attribute requires valid query handle or unknown"; break;
         case 11979: msg = "Buffer %1 can have only 1 active parent relation"; break;
         case 11980: msg = "Must assign a user query to a data-source that is a join before filling"; break;
         case 11981: msg = "Failed to auto-prepare an automatic fill query"; break;
         case 12008: msg = "Invalid character code found in data for codepage %1"; break;                         // no DOT at the end!
         case 12012: msg = "Invalid character data found in MEMPTR for codepage %1"; break;                       // no DOT at the end!
         case 12117: msg = "Unacceptable datatype for %1 argument"; break;                                        // no DOT at the end!
         case 12125: msg = "The codepage \"%1\" cannot be used for CLOB or LONGCHAR variables"; break;
         
         case 12298: msg = "Buffer %1 may be in only one data-source at a time.  Use different buffers for the same table"; break;
         case 12301: msg = "Source and target are the same table in temp-table copy operation for %1"; break;     // dataset-copy (itself)
         case 12303: msg = "COPY-TEMP-TABLE requires %1 and %2 columns to match exactly unless the fourth loose-mode parameter is passed as TRUE"; break; // dataset-copy (table structure not matching)
         case 12304: msg = "Error found during COPY-TEMP-TABLE operation on %1 and %2"; break;
         case 12310: msg = "Caller and called dataset parameters have different number of members"; break;
         case 12311: msg = "Parameters (table %1 and table %2) do not match"; break;
         case 12314: msg = "Invalid DATASET-Handle parameter given"; break;
         case 12323: msg = "A NO-SCHEMA-MARSHAL table cannot be used as a parameter where the receiving side does not have a pre-prepared schema"; break;
         case 12344: msg = "Cannot turn on TRACKING-CHANGES for a static temp-table unless DEFINED with a BEFORE-TABLE phrase"; break;
         case 12349: msg = "Cannot delete a dataset relation auto-generated query"; break;
         case 12352: msg = "Argument %1 to query callback method must be a valid query event name such as OFF-END"; break;
         case 12355: msg = "TRACKING-CHANGES may not be turned on for a non-dataset temp-table"; break;
         case 12360: msg = "LAST-BATCH may only be set for dataset temp-tables"; break;
         case 12361: msg = "BATCH-SIZE may only be set for dataset temp-tables"; break;
         case 12362: msg = "COPY-DATASET must have a valid dataset handle as first argument"; break;
         
         case 12373: msg = "You may not create BEFORE-TABLE %1 record"; break;
         case 12374: msg = "You may not delete BEFORE-TABLE %1 record.  Use ACCEPT-ROW-CHANGES instead"; break;
         case 12375: msg = "You may not update BEFORE-TABLE %1 record"; break;
         case 12377: msg = "DYNAMIC-NEXT-VALUE and DYNAMIC-CURRENT-VALUE require the second DBNAME argument"; break;
         
         case 12384: msg = "DATA-RELATION parent and child buffers must also be in the dataset"; break;
         case 12385: msg = "Pairs in DATA-RELATION RELATION-FIELDS phrase must be in the form: parent-field, child-field, parent-field, child-field etc"; break;
         case 12411: msg = "Cannot set attribute for a static object"; break;
         
         case 12744: msg = "Cannot update fields in a BEFORE-TABLE"; break;   // This is a compile/conversion time error.
         case 12747: msg = "You must provide a valid connected database name to dynamic sequence functions"; break;
         case 12748: msg = "Unable to evaluate sequence name for dynamic sequence function"; break;
         
         case 12753: msg = "In COPY-TEMP-TABLE operation the static target table %1 must have an empty BEFORE-TABLE if the source table has any BEFORE-TABLE records"; break;
         case 12758: msg = "You may not EMPTY temp-table %1 when TRACKING-CHANGES is true"; break;
         case 12764: msg = "REFERENCE-ONLY temp-table is unexpectedly UNKNOWN for %1"; break;
         case 12766: msg = "%1 Cannot bind BY-REFERENCE parameter tables or datasets unless column datatypes, positions, extents and indexes match for tables %2 and %3"; break;
         case 12778: msg = "GET-PARENT mode not supported for buffer GET-CHANGES"; break;
         case 12779: msg = "Cannot have both exclude list and include list"; break;
         case 12780: msg = "DATA-SOURCE-COMPLETE-MAP only legal for dataset tables"; break;
         case 12781: msg = "DATA-SOURCE-COMPLETE-MAP requires prior ATTACH-DATA-SOURCE method to be run"; break;
         case 12783: msg = "Odd number of tables in pairs list parameter in COPY-DATASET"; break;
         case 12784: msg = "COPY-DATASET pair %1 and %2 must have a table in each dataset"; break;
         case 12785: msg = "Table %1 not found in dataset %2"; break;
         case 12786: msg = "FILL-WHERE-STRING cannot be assigned unless the DATA-SOURCE is attached to some dataset buffer by a prior ATTACH-DATA-SOURCE"; break;
         case 12787: msg = "Attempt to reference uninitialized dataset"; break;
         case 12788: msg = "OFF-END CALLBACK not supported for query %1 whose buffer %2 is not in a dataset"; break;
         case 12790: msg = "Invalid value given for SCHEMA-MARSHAL %1"; break;
         case 12860: msg = "Datatypes in pairlist argument for ATTACH-DATA-SOURCE, BUFFER-COPY or BUFFER-COMPARE methods must match for fields %1 and %2"; break;
         
         case 13009: msg = "%1 BIND modifier not allowed for cases where neither the caller nor the called TABLE or DATASET parameter %2 has been defined REFERENCE-ONLY and neither caller nor called parameter is a TABLE-HANDLE or DATASET-HANDLE"; break;
         case 13011: msg = "%1 If BIND used when caller TABLE or DATASET parameter is bound and called parameter %2 is also bound, the caller and called parameters must be the same instance"; break;
         case 13012: msg = "%1 BIND or BY-REFERENCE modifier required on RUN, FUNCTION or METHOD invocation parameter when called TABLE or DATASET parameter %2 is REFERENCE-ONLY and is still not bound"; break;
         case 13031: msg = "Cannot have both schema-location and write-schema arguments set"; break;
         case 13032: msg = "Unable to create Temp-Table or dataset schema from XML Schema"; break;
         case 13033: msg = "Verify Temp-Table or dataset schema against XML Schema failed"; break;
         case 13035: msg = "Error reading XML file '%1'"; break;
         case 13055: msg = "Unable to update indexes for table '%1'"; break;
         
         case 13036: msg = "Error reading XML from a MEMPTR or LONGCHAR"; break;
         case 13048: msg = "The XML for READ-XMLSCHEMA must start with a 'schema' element"; break;
         case 13050: msg = "Unable to create record for '%1' during READ-XML/READ-JSON"; break;
         case 13053: msg = "Unable to update field '%1' in table '%2'"; break;
         case 13054: msg = "Invalid number of array elements for field '%1' in table '%2'"; break;
         case 13058: msg = "No data instance in diffgram to parse"; break;
         case 13063: msg = "REPLACE mode requires a unique primary index in the target table %1"; break;
         case 13064: msg = "READ-XML encountered an error while parsing the XML Document: %1"; break;
         case 13066: msg = "Unable to write XML Schema for temp-table %1"; break;
         case 13078: msg = "Unsupported data type for XML Serialization: %1"; break;
         case 13079: msg = "XML Schema definition for temp-table '%1' not found"; break;
         case 13082: msg = "XML Schema definition for data-relation between '%1' and '%2' not found"; break;
         case 13081: msg = "XML Schema definition for '%1' does not match dataset definition."; break;
         case 13083: msg = "XML Schema contains extra temp-table definition '%1'"; break;
         case 13086: msg = "XML Schema contains extra data-relation definition '%1'"; break;
         case 13089: msg = "XML Schema definition for field '%1' in table '%2' does not match Temp-Table field definition"; break;
         case 13090: msg = "XML Schema definition for field '%1' in table '%2' not found"; break;
         case 13091: msg = "XML Schema definition for table '%1' contains extra field '%2'"; break;
         case 13093: msg = "Write temp-table data failed for WRITE-XML"; break;
         case 13095: msg = "WRITE-XML target-type must be 'FILE' when buffers of a dataset have a different NAMESPACE-URI"; break;
         case 13097: msg = "Write schema failed for WRITE-XMLSCHEMA"; break;
         case 13138: msg = "Temp-table '%1' not found in XML Schema"; break;
         case 13139: msg = "Field '%1' already added from XML Schema"; break;
         case 13140: msg = "Invalid 'maxOccurs' attribute value '%1' for field '%2'"; break;
         case 13141: msg = "Invalid 'maxOccurs' attribute value set for BLOB or CLOB field '%1'"; break;
         case 13142: msg = "Invalid format '%1' in XML Schema for field '%2'"; break;
         case 13143: msg = "Unable to set initial value '%1' from XML Schema for field '%2'"; break;
         case 13158: msg = "XML Methods only allowed on TEMP-TABLE buffers"; break;
         case 13159: msg = "Dynamic temp-table must be in CLEAR or PREPARED state for READ-XML Methods"; break;
         case 13160: msg = "Dynamic temp-table must be in PREPARED state for WRITE-XML Methods"; break;
         case 13161: msg = "%1 If a caller or called REFERENCE-ONLY parameter %2 will become BOUND to its opposite as a result of the call, then the BIND keyword must be supplied on both the caller invocation and on the called parameter definition"; break;
         case 13184: msg = "Invalid source-type for READ-XML: %1"; break;
         case 13185: msg = "Invalid target-type for WRITE-XML: %1"; break;
         case 13186: msg = "Handle type not valid as WRITE-XML target"; break;
         case 13200: msg = "Odd number of values in field-type-mapping argument"; break;
         case 13201: msg = "Invalid data type override '%1' for field '%2'"; break;
         case 13233: msg = "Cannot set XML-NODE-TYPE of field '%1' to 'TEXT'"; break;
         case 13273: msg = "Third argument to SET-CALLBACK must be a valid procedure handle or object reference"; break;
         case 13272: msg = "Use SET-CALLBACK instead of SET-CALLBACK-PROCEDURE for methods in a class"; break;
         case 13452: msg = "Method '%1' for callback must be valid public method"; break;
         case 13511: msg = "The Session-free connection failed because the Appserver is in a session-managed operating mode"; break;
         case 13514: msg = "%1 name '%2' in namespace '%3' not found in XML Document"; break;
         case 13515: msg = "Invalid encoding for WRITE-XML"; break;
         
         case 14274: msg = "MARK-ROW-STATE and MARK-NEW may only be used on a dataset member buffer with an existing BEFORE-TABLE"; break;
         case 14275: msg = "Dataset record for %1 required for DATA-SOURCE-ROWID"; break;
         case 14276: msg = "Must run ATTACH-DATA-SOURCE on buffer %1 in order to get DATA-SOURCE-ROWID"; break;
         case 14277: msg = "Unable to identify DATA-SOURCE buffer %1 in DATA-SOURCE-ROWID"; break;
         case 14278: msg = "Unable to generate FIND for %1 in DATA-SOURCE-ROWID"; break;
         case 14282: msg = "Unable to recurse during FILL"; break;
         case 14288: msg = "Invalid ROW-STATE given to MARK-ROW-STATE"; break;
         case 14289: msg = "Invalid handle or handle type for MARK-ROW-STATE"; break;
         case 14293: msg = "TOP-NAV-QUERY requires a valid query on top dataset buffer %1"; break;
         
         case 14435: msg = "Large object fields can only be defined in NO-UNDO Temp-Tables"; break;
         case 14500: msg = "Source file does not contain sufficient data starting at %1 to copy %2 bytes"; break; // no final DOT ?
         
         case 14902: msg = "Indeterminate extent error"; break;
         case 14904: msg = "Unable to get right-hand-side array for assignment"; break;
         case 14905: msg = "Whole-array assignment target and source must have the same extent unless the target is indeterminate"; break;
         case 14906: msg = "Uninitialized array used as source of assignment"; break;
         case 14907: msg = "Could not convert data for array assignment"; break;
         case 14908: msg = "Could not update indeterminate array in assignment"; break;
         case 14909: msg = "Unable to extract array %1"; break;
         case 14910: msg = "Dynamic BUFFER-VALUE array assignment for field %1 requires that extents match"; break;
         
         case 15008: msg = "You may not add before-table buffer %1 to a DATASET"; break;
         case 15342: msg = "Invalid encoding '%1' argument for %2"; break;
         case 15344: msg = "Invalid mode '%1' for %2"; break;
         case 15357: msg = "Invalid source-type for READ-JSON: %1"; break;
         case 15358: msg = "Error parsing JSON: expected %1, but found %2"; break;
         
         case 15360: msg = "Error parsing JSON: unexpected token: %1"; break;
         case 15364: msg = "Array token encountered, but %1 is not an array field"; break;
         case 15366: msg = "Unable to update indexes for table '%1'"; break;
         case 15374: msg = "Unable to infer Temp-Table or dataset schema from JSON Data"; break;
         case 15375: msg = "Dataset name '%1' in JSON does not match '%2'"; break;
         case 15376: msg = "Temp-table name '%1' in JSON does not match '%2'"; break;
         case 15378: msg = "JSON methods only allowed on TEMP-TABLE buffers"; break;
         case 15391: msg = "Unsupported data type for JSON serialization: %1"; break;
         
         case 15956: msg = "Only super-tenants may use a tenant-id other than their own in a sequence function"; break;
         case 15974: msg = "Second argument to dynamic sequence function must be a database name character expression"; break;
         
         case 15976: msg = "Database '%1' must be connected for tenant function %2"; break;
         case 15977: msg = "Must supply a connected database name for tenant function %1"; break;
         case 15991: msg = "Attempt to create in multi-tenant table '%1' where the partition (usually default) has not been allocated"; break;
         
         case 16017: msg = "No record in buffer for SERIALIZE-ROW"; break;
         case 16019: msg = "Invalid target-format '%1' for SERIALIZE-ROW"; break;
         case 16020: msg = "omit-outer-object argument not allowed in SERIALIZE-ROW for XML"; break; // this message DOES NOT have a "." at the end! 
         case 16025: msg = "The argument to Dataset-handle:ADD-PARENT-ID-RELATION method was invalid."; break;
         case 16027: msg = "Buffers in ADD-PARENT-ID-RELATION must be members of the dataset"; break;
         case 16028: msg = "ADD-PARENT-ID-RELATION parent-id-field %1 is not valid"; break;
         case 16029: msg = "ADD-PARENT-ID-RELATION parent-fields-before entry %1 is not valid"; break;
         case 16030: msg = "ADD-PARENT-ID-RELATION parent-fields-after entry %1 is not valid"; break;
         case 16031: msg = "Invalid XML-NODE-TYPE %1 for Dataset %2"; break;
         case 16484: msg = "Duplicate field %1 found in PARENT-FIELDS-BEFORE / PARENT-FIELDS-AFTER"; break;
         
         case 16617: msg = "%1 is not a %2 attribute for %3"; break; // unlike 4052, this does not have ** prefix
         
         case 17092: msg = "Cannot set the DECIMALS attribute for a database field"; break;
         case 17093: msg = "DECIMALS attribute only allowed on DECIMAL datatypes"; break;
         case 17366: msg = "write-before-image not allowed for TEMP-TABLE or BUFFER object"; break;
         case 17586: msg = "Cannot serialize object of unsupported type '%1'"; break;
         
         case 19043: msg = "JsonArray is not a valid target-type for %1 method"; break;
         case 19079: msg = "%1 argument for %2 is not valid"; break;
         case 19080: msg = "JsonArray is not a valid source-type for READ-JSON on a ProDataset"; break;
         
         default:
            LOG.warning("Unknown error id: " + id);
      }
      
      if (params != null && params.length != 0)
      {
         StringBuilder sb = new StringBuilder();
         
         boolean[] used = new boolean[params.length];
         int lastWord = 0;
         int scan = msg.indexOf('%');
         while (scan >= 0)
         {
            sb.append(msg, lastWord, scan);
            boolean found = false;
            if (scan + 1 < msg.length())
            {
               char ch = msg.charAt(scan + 1);
               if (ch >= '1' && ch <= '9')
               {
                  int idx = ch - '0' - 1;
                  if (idx < params.length)
                  {
                     sb.append(params[idx]);
                     used[idx] = true;
                  }
                  else
                  {
                     LOG.warning("Not enough positional parameters (" + idx + "+) in ErrorMessage (" + id + ")");
                  }
                  found = true;
               }
            }
            
            if (!found)
            {
               sb.append('%');
               lastWord = scan + 1;
            }
            else
            {
               lastWord = scan + 2;
            }
            
            scan = msg.indexOf('%', lastWord);
         }
         
         // append the rest of the message, which does not contain any positional markers:
         sb.append(msg.substring(lastWord));
         
         boolean remains = false;
         for (int i = 0; i < used.length; i++)
         {
            if (!used[i] && params[i] != null && !params[i].isEmpty())
            {
               remains = true;
               break;
            }
         }
         
         if (remains)
         {
            LOG.warning("Unused positional parameter in ErrorMessage (" + id + ")");
         }
         
         msg = sb.toString();
      }
      return msg;
   }
   
   /**
    * Set the ERROR-OBJECT-DETAIL attribute, a handle of type SOAP-FAULT.
    * <p>
    * If the handle is valid and the referred resource is not a {@link SOAPFault} instance, then
    * this will throw a {@link RuntimeException}.
    * <p>
    * If the handle is {@code null} or invalid, the {@link WorkArea#pendingErrorStatus} 's 
    * <code>soapFault</code> field will be set to {@code null}.
    * 
    * @param   fault
    *          The resource representing the soap-fault.
    */
   static void setErrorObjectDetail(SOAPFaultImpl fault)
   {
      handle hFault = new handle();
      // we need to force assignment, as passing the resource at the c'tor will not save it,
      // because at this step we are in no-error mode.
      hFault.set(fault);

      work.get().pendingErrorStatus.soapFault = hFault;
   }
   
   /**
    * Get an iterator on Progress-style error text strings, derived from the
    * error information stored in <code>error</code> (including its possible
    * chain of root causes).
    *
    * @param   error
    *          A throwable which is an instance of
    *          <code>NumberedException</code> or which contains instances
    *          thereof.
    *
    * @return  Iterator on error text strings.
    */
   static Iterator<String> errorTexts(Throwable error)
   {
      return errorTexts(compileErrorEntries(error));
   }
   
   /**
    * Get an iterator on Progress-style error text strings, derived from the
    * error information stored in <code>error</code> (including its possible
    * chain of root causes), and from the given error number and text.
    *
    * @param   num
    *          Error number of the first message to occur in the resulting
    *          iteration.
    * @param   text
    *          Error text of the first message to occur in the resulting
    *          iteration.
    * @param   error
    *          A throwable which is an instance of
    *          <code>NumberedException</code> or which contains instances
    *          thereof.
    *
    * @return  Iterator on error text strings.
    */
   static Iterator<String> errorTexts(int num, String text, Throwable error)
   {
      List<ErrorEntry> list = compileErrorEntries(error);
      ErrorEntry entry = new ErrorEntry(num, text);
      list.add(entry);
      
      return errorTexts(list);
   }
   
   /**
    * Get an iterator on Progress-style error text strings, based upon the
    * <code>ErrorEntry</code> objects stored in a list.
    *
    * @param   entries
    *          List of error entries.
    *
    * @return  Iterator on error text strings.
    */
   private static Iterator<String> errorTexts(final List<ErrorEntry> entries)
   {
      return new Iterator<String>()
      {
         private final Iterator<ErrorEntry> iter = entries.iterator();

         @Override
         public boolean hasNext()
         {
            return iter.hasNext();
         }

         @Override
         public String next()
         {
            ErrorEntry entry = iter.next();
            
            return buildErrorText(entry.num, entry.text, entry.prefix);
         }

         @Override
         public void remove()
         {
            throw new UnsupportedOperationException();
         }
      };
   }
   
   /**
    * Extract from <code>error</code> and any chained root cause exceptions
    * of type {@link com.goldencode.p2j.NumberedException NumberedException}
    * error messages and numbers with which to compile a list of error
    * entries.  This list is compiled in reverse order of the root cause
    * chain traversal, such that the most specific errors appear earliest in
    * the list.  If no instances of <code>NumberedException</code> are
    * detected during the traversal, the list will be empty.
    *
    * @param   error
    *          Error from which to extract error messages and numbers.
    *
    * @return  List of <code>ErrorEntry</code> objects.
    */
   private static List<ErrorEntry> compileErrorEntries(Throwable error)
   {
      List<ErrorEntry> list = new LinkedList<>();
      
      Throwable next = error;
      do
      {
         if (next instanceof NumberedException)
         {
            NumberedException exc = (NumberedException) next;
            int num        = exc.getNumber();
            String msg     = exc.getMessage();
            boolean prefix = exc.isPrefix();
            ErrorEntry entry = new ErrorEntry(num, msg, prefix);
            list.add(0, entry);
         }
         next = next.getCause();
      }
      while (next != null);
      
      return list;
   }
   
   /**
    * Write a formatted log entry with the error text.  In silent error mode
    * generate a warning and in normal mode generate a severe entry. 
    *
    * @param    message
    *           The text to insert into the log.
    * @param    silent
    *           <code>true</code> if silent error mode is active.
    */
   private static void log(String message, boolean silent)
   {
      log(message, null, silent);
   }
   
   /**
    * Write a formatted log entry with the error text.  In silent error mode
    * generate a warning and in normal mode generate a severe entry. 
    *
    * @param    message
    *           The text to insert into the log.
    * @param    trw
    *           The cause of the failure.
    * @param    silent
    *           <code>true</code> if silent error mode is active.
    */
   private static void log(String message, Throwable trw, boolean silent)
   {
      if (isHeadless())
      {
         return;
      }
      
      Level lvl = silent || hasLegacyError() ? Level.FINE : Level.SEVERE;
      
      if (LOG.isLoggable(lvl))
      {
         String msg = silent ? "(silent mode) " + message : message;
         if (trw == null)
         {
            LOG.log(lvl, msg);
         }
         else
         {
            LOG.log(lvl, trw, msg);
         }
      }
   }
   
   /**
    * Perform postprocessing for {@link #showErrorAndAbend(int[], String[])}.
    *
    * @param   text
    *          Message text.
    */
   private static void postprocessOnAbend(String[] text)
   {
      StringBuilder sb = new StringBuilder();
      String sep = System.getProperty("line.separator");
      
      for (int i = 0; i < text.length; i++)
      {
         sb.append(text[i]);
         
         if (i != text.length - 1)
         {
            sb.append(sep);
         }
      }
      
      throw new StopConditionException(sb.toString());
   }
   
   /**
    * Reports on the context-local {@code silent} flag.
    *
    * @param    wa
    *           Context-local error state.
    *
    * @return   {@code true} if silent error mode is active.
    */
   private static boolean isSilentWorker(WorkArea wa)
   {
      return wa.silent;
   }
      
   /**
    * Throws an {@link ErrorConditionException} and if in silent mode, it sets the
    * <code>pending</code> flag to <code>true</code>.  NO error information (messages or
    * error numbers) will be recorded in either case (i.e. silent mode or not).  In silent
    * mode the <code>ERROR-STATUS:ERROR</code> will be <code>true</code> but the other "records"
    * of the error that normally exist, will not exist. No message will be displayed.  Because
    * no message is ever displayed or recorded, the normal error number/message processing
    * is not executed.
    *
    * @param    wa
    *           Context-local error state.
    * @param    msg
    *           The text to use in throwing the error exception.
    */
   private static void noRecordOrThrowErrorWorker(WorkArea wa, String msg)
   {
      if (isSilentWorker(wa))
      {
         // ERROR-STATUS:ERROR flag is set, but no error is recorded
         setPendingWorker(wa, true);
      }
      else
      {
         writeLog(msg);
      }

      // raise the ERROR condition
      throw new ErrorConditionException(msg);
   }
   
   /**
    * Worker method that executes a given block of code with silent error mode turned on for
    * the duration of the code block.  Before the code block is executed, silent error mode
    * is turned on and after the code block completes (normally or abnormally), silent error
    * mode will be disabled.
    * <p>
    * This method can only be executed on the server side.
    *
    * @param    code
    *           The block of code to execute in silent mode.
    * @param    srv
    *           Server-side error workers.
    * @param    wa
    *           Context-local error state.
    *
    * @return   The value of the ERROR-STATUS:ERROR flag when the execution is complete. 
    */
   private static boolean silentWorker(Runnable code, ServerErrorDataAccessor srv, WorkArea wa)
   {
      // enable silent error processing
      boolean wasSilent = srv.isSilent();
      if (!wasSilent)
      {
         srv.setSilent(wa, true);
      }
      
      try
      {
         code.run();
      }
      catch (LegacyErrorException lex)
      {
         srv.setPending(true);
         
         LegacyError err = lex.getErrorRef();
         if (!(err instanceof AppError) || !((AppError) err).isFromReturn())
         {
            ProError proErr = (ProError) err;
            int numErr = err.getNumMessages().intValue();
            int[] errnums = new int[numErr];
            String[] errmsgs = new String[numErr];
            
            for (int i = 0; i < numErr; i++)
            {
               errnums[i] = proErr.getMessageNum(new integer(i + 1)).intValue();
               errmsgs[i] = proErr.getMessage(new integer(i + 1)).toStringMessage();
               wa.pendingErrorStatus.addError(new ErrorEntry(errnums[i], errmsgs[i]));
            }
            addRaisedConditionWorker(wa, errnums);
         }
         if (lex.getError()._isValid())
         {
            ObjectOps.delete(err);
         }
      }
      catch (ErrorConditionException err)
      {
         // normally we just eat the exception because it is being used to abort processing
         // exactly where the error occurred, which will restart processing after the given
         // code block; however, there is an exception for some deferred error processing use
         // cases where we must bypass silent error mode (throwError(NumberedException, boolean))
         if (err.isForce())
         {
            // don't honor silent mode, no suppression of errors here
            throw err;
         }
      }
      finally
      {
         // disable further silent error processing
         if (!wasSilent)
         {
            srv.setSilent(wa, false);
         }
         
         forwardPending(wa);
      }
      
      return wa.errorStatus.error;
   }
   
   /**
    * Execute the given block while supressing normal error processing by temporarily enabling the
    * ignore flag.
    *
    * @param    code
    *           The block of code to execute in ignore mode.
    * @param    wa
    *           Context-local error state.
    */
   private static void ignoreWorker(Runnable code, WorkArea wa)
   {
      wa.ignore++;
      
      try
      {
         code.run();
      }
      
      finally
      {
         wa.ignore--;
      }
   }
   
   /**
    * Queries the state of <code>ignore</code> mode.
    *
    * @param    wa
    *           Context-local error state.
    *
    * @return   <code>true</code> if ignore mode is enabled, otherwise <code>false</code>.
    */
   private static boolean isIgnoreWorker(WorkArea wa)
   {
      return wa.ignore > 0;
   }
   
   /**
    * Determines if headless mode should be supported. In headless mode, no
    * client-side output will occur.
    *
    * @param    wa
    *           Context-local error state.
    *
    * @return   <code>true</code> if the headless flag is turned on, otherwise <code>false</code>.
    */
   private static boolean isHeadlessWorker(WorkArea wa)
   {
      return headless || wa.headless || wa.canFindLevel > 0;
   }
   
   /**
    * Adds an error entry to the error list.
    *
    * @param    wa
    *           Context-local error state.
    * @param    num
    *           The error number.
    * @param    text
    *           The text of the error message.
    * @param    errFlag
    *           If <code>false</code> this should add the number and text to
    *           the error list but not set the error flag.  If
    *           <code>true</code> the error flag should also be set.
    * @param    prefix
    *           <code>true</code> to prepend a double asterisk prefix to the
    *           formatted error message, <code>false</code> to omit this text.
    */
   private static void addErrorWorker(WorkArea wa, int num, String text, boolean errFlag, boolean prefix)
   {
      if (errFlag)
      {
         // this may reset the error list so it must be done BEFORE we add the new error to the list
         setPendingWorker(wa, true);
      }

      String errmsg = buildErrorText(num, text, prefix, true);

      // manually using the work area is OK in this case since this method is only used on the server
      wa.pendingErrorStatus.addError(new ErrorEntry(num, errmsg));
      
      // add error number to _MSG stack.
      addRaisedConditionWorker(wa, new int[] { num });
   }
   
   /**
    * Sets the <code>pendingError</code> variable.
    *
    * @param    wa
    *           Context-local error state.
    * @param    value
    *           The new state of the variable.
    */
   private static void setPendingWorker(WorkArea wa, boolean value)
   {
      wa.pendingErrorStatus.error = value;  
   }
   
   /**
    * Makes pending error status current.
    *
    * @param    wa
    *           Context-local error state.
    */
   private static void forwardPending(WorkArea wa)
   {
      wa.errorStatus.set(wa.pendingErrorStatus);
      wa.pendingErrorStatus.initialize();
   }
   
   /**
    * Gets err nums for raised errors.
    *
    * @param    wa
    *           Context-local error state.
    * 
    * @return   stack of errors numbers
    */
   private static LinkedList<Integer> getRaisedConditionsWorker(WorkArea wa)
   {
      return wa.raisedConditions;
   }
      
   /**
    * Adds error numbers to raised error numbers stack (for _MSG function).
    * 
    * @param    errNums
    *           errors numbers.
    */
   private static void addRaisedCondition(int[] errNums)
   {
      addRaisedConditionWorker(work.obtain(), errNums);
   }
   
   /**
    * Adds error numbers to raised error numbers stack (for _MSG function).
    *
    * @param    wa
    *           Context-local error state.
    * @param    errNums
    *           errors numbers.
    */
   private static void addRaisedConditionWorker(WorkArea wa, int[] errNums)
   {
      if (isSilent() || errNums == null)
      {
         return;
      }
      
      LinkedList<Integer> raisedConditions = getRaisedConditionsWorker(wa);
      for (int errNum : errNums)
      {
         raisedConditions.addFirst(errNum);
         if (raisedConditions.size() > ERR_CONDITIONS_CAPACITY)
         {
            raisedConditions.removeLast();
         }
      }
   }
   
   /**
    * Helper to expose error state in a way that avoids context local lookups.
    */
   public static class ErrorHelper
   {
      /** Context-local error state. */
      private WorkArea wa;
      
      /** Server-side error methods. */
      private ServerErrorDataAccessor srv;
      
      /**
       * Create a new instance and associate the given WorkArea instance.
       * 
       * @param    wa
       *           Context-local error state.
       * @param    srv
       *           Server-side error methods.
       */
      public ErrorHelper(WorkArea wa, ServerErrorDataAccessor srv)
      {
         this.wa  = wa;
         this.srv = srv;
      }
      
      /**
       * Worker method that executes a given block of code with silent error mode turned on for
       * the duration of the code block.  Before the code block is executed, silent error mode
       * is turned on and after the code block completes (normally or abnormally), silent error
       * mode will be disabled.
       * <p>
       * This method can only be executed on the server side.  It will backup and restore the current
       * {@link WorkArea#errorStatus}, to allow for nested silent-mode usages (internally by FWD).
       *
       * @param    code
       *           The block of code to execute in silent mode.
       * @param    errorProcessor
       *           A consumer of the logged errors in silent mode.
       *           
       * @return   The value of the ERROR-STATUS:ERROR flag when the execution is complete. 
       */
      public boolean nestedSilent(Runnable code, BiConsumer<String, Integer> errorProcessor)
      {
         return ErrorManager.nestedSilent(wa, code, errorProcessor);
      }

      /**
       * Reflects the value of SESSION:SUPPRESS-WARNINGS attribute.
       *
       * @return   The suppress warnings flag.
       */
      public boolean isSuppressWarnings()
      {
         return wa.suppressWarnings;
      }

      /**
       * Reflects the value of SESSION:SUPPRESS-WARNINGS-LIST attribute.
       *
       * @return The suppress warnings list flag.
       */
      public String getSuppressWarningsList()
      {
         return wa.suppressWarningsList;
      }
      
      /**
       * Reports on the context-local {@code silent} flag.
       *
       * @return   {@code true} if silent error mode is active.
       */
      public boolean isSilent()
      {
         return isSilentWorker(wa);
      }
      
      /**
       * Sets the <code>silent</code> variable.
       *
       * @param   value
       *          The new state of the variable.
       */
      public void setSilent(boolean value)
      {
         srv.setSilent(wa, value);
      }
      
      /**
       * Worker method that executes a given block of code with silent error mode turned on for
       * the duration of the code block.  Before the code block is executed, silent error mode
       * is turned on and after the code block completes (normally or abnormally), silent error
       * mode will be disabled.
       * <p>
       * This method can only be executed on the server side.
       *
       * @param    code
       *           The block of code to execute in silent mode.
       *
       * @return   The value of the ERROR-STATUS:ERROR flag when the execution is complete. 
       */
      public boolean silent(Runnable code)
      {
         return silentWorker(code, srv, wa);
      }
      
      /**
       * Queries the state of <code>pendingError</code> variable.
       *
       * @return   <code>true</code> if pending error flag is turned on, otherwise
       *           <code>false</code>.
       */
      public boolean isPending()
      {
         return wa.pendingErrorStatus.error; 
      }
      
      /**
       * Sets the <code>pendingError</code> variable.
       *
       * @param    value
       *           The new state of the variable.
       */
      public void setPending(boolean value)
      {
         setPendingWorker(wa, value);  
      }
      
      /**
       * Clears pending error status.
       */
      public void clearPending()
      {
         wa.pendingErrorStatus.initialize();
      }
      
      /**
       * Handles the change of the pending error status, making it
       * into the current error status.
       */
      public void handleForwardPending()
      {
         forwardPending(wa);
      }
      
      /**
       * Retrieves the ERROR-STATUS:ERROR of the current WorkArea.
       * 
       * @return  value of ERROR-STATUS:ERROR of the current WorkArea.
       */
      public boolean getErrorStatus()
      {
         return wa.errorStatus.error;
      }
      
      /**
       * Determines if headless mode should be supported. In headless mode, no
       * client-side output will occur.
       *
       * @return   <code>true</code> if the headless flag is turned on, otherwise <code>false</code>.
       */
      public boolean isHeadless()
      {
         return isHeadlessWorker(wa);
      }
      
      /**
       * Set the headless flag for this context.
       *
       * @param    headless
       *           {@code true} if this context has no UI.
       */
      public void setHeadless(boolean headless)
      {
         wa.headless = headless;
      }
      
      /**
       * Set the headless flag for this context.
       *
       * @param    headless
       *           {@code true} if this context has no UI.
       */
      public void setStopAfterTimer(StopAfterTimer stopAfterTimer)
      {
         wa.stopAfterTimer = stopAfterTimer;
      }
      
      /**
       * Detects if the temporary CAN-FIND headless mode is active.  This really should be something
       * generic instead of being hard coded to the persistence package.
       * 
       * @return   <code>true</code> if this is a CAN-FIND bracket.
       */
      public boolean isInCanFindBracket()
      {
         return wa.canFindLevel > 0;
      }
      
      /**
       * Sets silent mode during CAN-FIND calls. The semaphore keep tracks of the nested CAN-FIND
       * calls. 
       * If the counter is positive <code>ErrorManager</code> will run similar to headless mode 
       * and will silently discard error messages.
       * <p> 
       * <strong>Note</strong>Always call this method in pairs (one with enter = true before 
       * executing the query and, one with enter = false, immediately after) to assure the 
       * correct management of the headless context flag. 
       * 
       * @param   enter
       *          If <code>true</code> will increment the nested CAN-FIND count.
       */
      public void setCanFindBracket(boolean enter)
      {
         if (enter)
         {
            // push a CAN-FIND level
            wa.canFindLevel++;
         }
         else
         {
            // pop put CAN-FIND level:
            wa.canFindLevel--;
         }
      }
      
      /**
       * Execute the given block while supressing normal error processing by temporarily enabling the
       * ignore flag.
       *
       * @param    code
       *           The code to execute.
       */
      public void ignore(Runnable code)
      {
         ignoreWorker(code, wa);
      }
      
      /**
       * Adds error numbers to raised error numbers stack
       *
       * @param    e
       *           The condition exception meant to be iterated for causes
       */
      public void addNumbersToConditionException(ConditionException e) 
      {
         boolean found = false;
         
         for (Throwable cause = e; cause != null; cause = cause.getCause())
         {
            if (cause instanceof NumberedException)
            {
               addRaisedConditionWorker(wa, ((NumberedException)cause).getNumbers());
               found = true;
               break;
            }
         }
         
         if (!found)
         {
            addRaisedConditionWorker(wa, new int[]{0});
         }
      }
      
      /**
       * Adds an error entry to the error list.
       *
       * @param    num
       *           The error number.
       * @param    text
       *           The text of the error message.
       * @param    errFlag
       *           If <code>false</code> this should add the number and text to
       *           the error list but not set the error flag.  If
       *           <code>true</code> the error flag should also be set.
       * @param    prefix
       *           <code>true</code> to prepend a double asterisk prefix to the
       *           formatted error message, <code>false</code> to omit this text.
       */
      public void addError(int num, String text, boolean errFlag, boolean prefix)
      {
         addErrorWorker(wa, num, text, errFlag, prefix);
      }
      
      /**
       * Throws an {@link ErrorConditionException} and if in silent mode, it sets the
       * <code>pending</code> flag to <code>true</code>.  NO error information (messages or
       * error numbers) will be recorded in either case (i.e. silent mode or not).  In silent
       * mode the <code>ERROR-STATUS:ERROR</code> will be <code>true</code> but the other "records"
       * of the error that normally exist, will not exist. No message will be displayed.  Because
       * no message is ever displayed or recorded, the normal error number/message processing
       * is not executed.
       * <p>
       * This only works on the server side.
       * 
       * @param    msg
       *           The text to use in throwing the error exception.
       */
      public void noRecordOrThrowError(String msg)
      {
         noRecordOrThrowErrorWorker(wa, msg);
      }
      
      /**
       * Reports if warning mode has been enabled. When in warning mode, error messages are reported to the UI
       * using an alert box.
       *
       * @return   {@code true} if warning mode is enabled, otherwise {@code false}.
       */
      public boolean isWarningMode()
      {
         return wa.errorsAsWarnings;
      }
      
      /**
       * Configures warning mode, during which errors are non-fatal (when {@code on} is {@code true}), but are
       * reported to the UI of the current context via a call to {@code LogicalTerminal.message()}.
       * <p>
       * Each call to this method must be matched with a corresponding call to {@link #warningModeDisable}.
       *
       * @param   on
       *          Pass {@code true} to enable reported but non-fatal errors, and {@code false} to handle
       *          errors normally.
       */
      public void setWarningMode(boolean on)
      {
         wa.errorsAsWarnings = on;
      }
   }
   
   /**
    * Stores the data related to a single error.
    */
   public static class ErrorEntry
   implements Serializable
   {
      /** Error number. */
      public final int num;
      
      /** Error text. */
      public final String text;

      /** Determines if the prefix should be displayed for the error message. */
      public final boolean prefix;
      
      /**
       * Construct an instance.
       *
       * @param    num
       *           The error number.
       * @param    text
       *           The text of the error message.
       */
      public ErrorEntry(int num, String text)
      {
         this.num  = num;
         this.text = text;
         this.prefix = true;
      }

      /**
       * Construct an instance.
       *
       * @param    num
       *           The error number.
       * @param    text
       *           The text of the error message.
       * @param    prefix
       *           Determines if the prefix should be displayed for the error message.
       */
      public ErrorEntry(int num, String text, boolean prefix)
      {
         this.num  = num;
         this.text = text;
         this.prefix = prefix;
      }
   }
   
   /**
    * Process errors with ignore.
    */
   private static class SuppressedErrorDataAccessor
   extends ServerErrorDataAccessor
   {
      /**
       * Sets the {@code silent} variable.
       *
       * @param    value
       *           The new state of the variable.
       */
      @Override
      public void setSilent(boolean value)
      {
         setSilent(work.obtain(), value);
      }
      
      /**
       * Sets the {@code silent} variable.
       *
       * @param    wa
       *           Context local data storage.
       * @param    value
       *           The new state of the variable.
       */
      @Override
      protected void setSilent(WorkArea wa, boolean value)
      {
         wa.silent = value;
      }

      /**
       * Check if the top-level block is ROUTINE-LEVEL UNDO, THROW or the current program is 
       * BLOCK-LEVEL UNDO, THROW.
       * 
       * @return  see above.
       */
      @Override
      public boolean mustManageLegacyError()
      {
         return false;
      }
      
      /**
       * Check if the current ERROR condition must be raised as a legacy {@link SysError}.
       * 
       * @param   forceBlockLevelUndoThrow
       *          Flag indicating that, when nothing else matches, a BLOCK-LEVEL UNDO, THROW exists by default.
       *          
       * @return  see above.
       */
      @Override
      public boolean mustManageLegacyError(boolean forceBlockLevelUndoThrow)
      {
         return false;
      }
      
      /**
       * Check if the current ERROR condition must be raised as a legacy {@link SysError}.
       * 
       * @return  see above.
       */
      @Override
      public boolean mustThrowLegacyError()
      {
         return false;
      }
      
      /**
       * Check if the current ERROR condition must be raised as a legacy {@link SysError}.
       *
       * @param   forceBlockLevelUndoThrow
       *          Flag indicating that, when nothing else matches, a BLOCK-LEVEL UNDO, THROW exists by default.
       * 
       * @return  see above.
       */
      @Override
      public boolean mustThrowLegacyError(boolean forceBlockLevelUndoThrow)
      {
         return false;
      }
      
      /**
       * Record the specified error message and number, to be later thrown as a 
       * {@link LegacyErrorException}.
       * 
       * @param    errmsg
       *           The error message.
       * @param    num
       *           The error number.
       */
      @Override
      public void recordForLegacyThrow(String errmsg, int num)
      {
         // no-op
      }

      /**
       * Writes a log record.
       *
       * @param    msg
       *           The log message.
       * @param    logCallStack
       *           Flag to indicate if call stack should be included in log.
       * @param    threadId
       *           The id of the thread recording the error message.
       */
      @Override
      public void writeLog(String msg, boolean logCallStack, long threadId)
      {
         // no-op
      }
   }

   /**
    * Provides access to the error data.
    */
   private static class ServerErrorDataAccessor
   implements RemoteErrorData
   {
      /**
       * Queries the state of <code>silent</code> variable.
       *
       * @return   <code>true</code> if silent mode is enabled, otherwise
       *           <code>false</code>.
       */
      @Override
      public boolean isSilent()
      {
         return work.obtain().silent;
      }
   
      /**
       * Queries the state of <code>error</code> variable.
       *
       * @return   <code>true</code> if error flag is turned on, otherwise
       *           <code>false</code>.
       */
      @Override
      public boolean isErrorFlag()
      {
         return work.obtain().errorStatus.error;
      }
      
      /**
       * Sets the <code>pendingError</code> variable.
       *
       * @param    value
       *           The new state of the variable.
       */
      @Override
      public void setPending(boolean value)
      {
         setPendingWorker(work.obtain(), value);  
      }
      
      /**
       * Gets the size of the list of error records.
       *
       * @return   The size of the list.
       */
      @Override
      public int getSize()
      {
         List<Object> errors = work.obtain().errorStatus.errorList;
         return errors == null ? 0 : errors.size();
      }
   
      /**
       * Adds a record to the list of error records.
       *
       * @param    record
       *           The object to add to the list of error records.
       */
      @Override
      public void addRecord(Object record)
      {
         work.obtain().pendingErrorStatus.addError(record);
      }
   
      /**
       * Gets a record from the list of error records.
       *
       * @param    index
       *           The record index.
       *
       * @return   The object associated with the given index in the list.
       */
      @Override
      public Object getRecord(int index)
      {
         return work.obtain().errorStatus.getError(index);
      }

      /**
       * Queries the state of <code>pendingError</code> variable.
       *
       * @return   <code>true</code> if pending error flag is turned on, otherwise
       *           <code>false</code>.
       */
      @Override
      public boolean isPending()
      {
         return work.obtain().pendingErrorStatus.error; 
      }

      /**
       * Reflects the value of SESSION:SUPPRESS-WARNINGS attribute.
       *
       * @return see above.
       */
      @Override
      public boolean isSuppressWarnings()
      {
         return work.obtain().suppressWarnings;
      }

      /**
       * Reflects the value of SESSION:SUPPRESS-WARNINGS-LIST attribute.
       *
       * @return see above.
       */
      @Override
      public String getSuppressWarningsList()
      {
         return work.obtain().suppressWarningsList;
      }

      /**
       * Get the state of SESSION:SYSTEM-ALERT-BOXES attribute. 
       * 
       * @return  the state of SESSION:SYSTEM-ALERT-BOXES attribute.
       */
      @Override
      public boolean isSystemAlertBoxes()
      {
         return work.obtain().systemAlertBoxes;
      }

      /**
       * Sets the <code>silent</code> variable.
       *
       * @param   value
       *          The new state of the variable.
       */
      @Override
      public void setSilent(boolean value)
      {
         setSilent(work.obtain(), value);
      }

      /**
       * Sets the {@code silent} variable.
       *
       * @param    wa
       *           Context local data storage.
       * @param    value
       *           The new state of the variable.
       */
      protected void setSilent(WorkArea wa, boolean value)
      {
         wa.silent = value;

         if (value)
         {
            // we can't delete the soapFault handle here, as this might be a call for accessing
            // some soap-fault attribute/method with the NO-ERROR clause set!
         }
         else
         {
            if (wa.errorStatus.soapFault != null && wa.errorStatus.soapFault._isValid())
            {
               // delete it only if we haven't deleted it already
               HandleOps.delete(wa.errorStatus.soapFault);
            }

            wa.errorStatus.soapFault = wa.pendingErrorStatus.soapFault;
            wa.pendingErrorStatus.soapFault = null;
         }
      }

      /**
       * Check if the top-level block is ROUTINE-LEVEL UNDO, THROW or the current program is 
       * BLOCK-LEVEL UNDO, THROW.
       *
       * @return  see above.
       */
      @Override
      public boolean mustManageLegacyError()
      {
         return ErrorManager.mustManageLegacyError();
      }

      /**
       * Check if the current ERROR condition must be raised as a legacy {@link SysError}.
       * 
       * @param   forceBlockLevelUndoThrow
       *          Flag indicating that, when nothing else matches, a BLOCK-LEVEL UNDO, THROW exists by default.
       *          
       * @return  see above.
       */
      @Override
      public boolean mustManageLegacyError(boolean forceBlockLevelUndoThrow)
      {
         return ErrorManager.mustManageLegacyError(forceBlockLevelUndoThrow);
      }
      
      /**
       * Check if the current ERROR condition must be raised as a legacy {@link SysError}.
       *
       * @return  see above.
       */
      @Override
      public boolean mustThrowLegacyError()
      {
         return ErrorManager.mustThrowLegacyError();
      }
      
      /**
       * Check if the current STOP condition must be raised as a legacy {@link Stop}.
       *
       * @return  see above.
       */
      public boolean mustThrowLegacyStop()
      {
         return ErrorManager.mustThrowLegacyStop();
      }
      
      /**
       * Check if the current ERROR condition must be raised as a legacy {@link SysError}.
       *
       * @param   forceBlockLevelUndoThrow
       *          Flag indicating that, when nothing else matches, a BLOCK-LEVEL UNDO, THROW exists by default.
       * 
       * @return  see above.
       */
      @Override
      public boolean mustThrowLegacyError(boolean forceBlockLevelUndoThrow)
      {
         return ErrorManager.mustThrowLegacyError(forceBlockLevelUndoThrow);
      }

      /**
       * Record the specified error message and number, to be later thrown as a 
       * {@link LegacyErrorException}.
       *
       * @param    errmsg
       *           The error message.
       * @param    num
       *           The error number.
       */
      @Override
      public void recordForLegacyThrow(String errmsg, int num)
      {
         ErrorManager.recordForLegacyThrow(errmsg, num);
      }
      
      /**
       * Record the STOP condition, to be later thrown as a 
       * {@link LegacyStopException}.
       *
       * @param    errmsg
       *           The error message.
       * @param    num
       *           The error number.
       */
      public void recordForLegacyStop(StopConditionException ex)
      {
         ErrorManager.recordForLegacyStop(ex);
      }

      /**
       * Throw the specified error message and number.
       *
       * @param    num
       *           The error number.
       * @param    errmsg
       *           The error message.
       */
      @Override
      public void throwErrorConditionException(int num, String errmsg)
      {
         // throw on the Conversation thread
         throw new ErrorConditionException(num, errmsg);
      }

      /**
       * Writes a log record.
       *
       * @param    msg
       *           The log message.
       * @param    logCallStack
       *           Flag to indicate if call stack should be included in log.
       * @param    threadId
       *           The id of the thread recording the error message.
       */
      @Override
      public void writeLog(String msg, boolean logCallStack, long threadId)
      {
         String[] callStack = !logCallStack ? null : ProcedureManager.getStackTrace().toArray(new String[0]);
         LegacyLogOps.logMgr().writeMessage(msg,
                                            null,
                                            null,
                                            LoggingSubsys.EMPTY,
                                            LoggingLevel.ERROR,
                                            callStack,
                                            threadId);
      }

      /**
       * Handles a SOAP fault.
       *
       * @param    operationName
       *           The operation that failed.
       * @param    faultMsg
       *           The fault error msg.
       * @param    faultActor
       *           The fault actor.
       * @param    faultCode
       *           The fault code.
       * @param    faultString
       *           The fault string.
       * @param    faultDetailXML
       *           The fault details as xml.
       */
      @Override
      public void handleSoapFault(String operationName,
                                  String faultMsg,
                                  String faultActor,
                                  String faultCode,
                                  String faultString,
                                  String faultDetailXML)
      {
         final String msg = "Web service operation %s generated a SOAP Fault. SOAP faultstring is: %s";
         recordOrThrowError(new int[] { 11506 },
                            new String[] { String.format(msg, operationName, faultMsg) },
                            false, 
                            true);

         // if this is reached, we are in silent-error mode, thus generate the SOAP-fault object
         SOAPFaultDetailImpl soapFaultDetail = SOAPFactory.createSOAPFaultDetail(faultDetailXML);
         SOAPFaultImpl soapFault = SOAPFactory.createSOAPFault(
            faultActor, faultCode, faultString, soapFaultDetail);

         setErrorObjectDetail(soapFault);
      }
   }
   
   /**
    * Stores global data relating to the state of the current context's
    * transactions.
    */
   private static class WorkArea
   {  
      /** The resource's ID. */
      private Long id = null;
      
      /** Current error status value. */
      private ErrorStatus errorStatus = new ErrorStatus();
      
      /** Pending error status value. */
      private final ErrorStatus pendingErrorStatus = new ErrorStatus();
      
      /** No error flag (if <code>true</code> exceptions are silently recorded rather than thrown). */ 
      private boolean silent = false;
      
      /**
       * Flags when a query is executed in a CAN-FIND function.
       * In a CAN-FIND, errors are "swallowed" similar to headless mode. 
       */
      private int canFindLevel = 0;
      
      /** <code>true</code> to NO-OP all error recording, display and exception generation. */
      private int ignore = 0;
      
      /** Whether exceptions are processed as non-fatal, warning messages. */
      private boolean errorsAsWarnings = false;
      
      /** {@code true} to mark this context as having NO UI. */
      private boolean headless = false;
      
      /** 
       * Raised errors numbers stack. Used for undocumented _MSG function implementation.
       * Can contain &le;25 elements, indexed starting from 1.
       */
      private final LinkedList<Integer> raisedConditions = new LinkedList<>();
      
      /** Reflects the value of SESSION:SUPPRESS-WARNINGS attribute. */
      private boolean suppressWarnings = false;

      /** Reflects the value of SESSION:SUPPRESS-WARNINGS-LIST attribute. */
      private String suppressWarningsList = "";

      /** Reflects the value of SESSION:SUPPRESS-WARNINGS-LIST attribute. */
      private Set<Integer> suppressWarningsSet = new HashSet<>();

      /** Holds the state of the SESSION:SYSTEM-ALERT-BOXES attribute. */
      private boolean systemAlertBoxes = false;
      
      /** The legacy-style OE error which is being built. */
      private object<? extends SysError> legacyError = null;
      
      /** The legacy-style OE STOP error which is being built. */
      private object<? extends Stop> legacyStop = null;
      
      /** Flag indicating that the deferred {@link #legacyError} must be marked as explicit. */
      private boolean forceExplicit = false;
      
      /** Object responsible for managing stop-after on Progress blocks. */
      private StopAfterTimer stopAfterTimer = null;
   }
   
   /**
    * 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#LAST}.
       */
      @Override
      public WeightFactor getWeight()
      {
         return WeightFactor.LAST;
      }
      
      /**
       * Obtains the context-local instance of the contained global work
       * area.
       *
       * @return   The work area associated with this context.
       */
      public WorkArea obtain()
      {
         return this.get();
      }   
      
      /**
       * Initializes the work area, the first time it is requested within a
       * new context.
       *
       * @return   The newly instantiated work area.
       */
      @Override
      protected synchronized WorkArea initialValue()
      {
         return new WorkArea();
      }   
   }

   /**
    * This class corresponds ERROR-STATUS p2j handle, that can contain supressed errors 
    * information in case of NO-ERROR expression used in P4GL statement. 
    */
   private static class ErrorStatus 
   {
      /** List of errors. */
      private ArrayList<Object> errorList = null;
      
      /** Stores whether an error condition has occurred previously. */
      private boolean error = false;
      
      /**
       * Handle containing the value of the ERROR-OBJECT-DETAIL attribute set by the last statement
       * executed with a NO-ERROR clause.
       */
      private handle soapFault = null;

      /**
       * Add an error entry to the {@link #errorList}.
       * 
       * @param    error
       *           The error to add.
       */
      private void addError(Object error)
      {
         if (errorList == null)
         {
            errorList = new ArrayList<>(1);
         }
         
         errorList.add(error);
      }
      
      /**
       * Get an error from the {@link #errorList}.
       * 
       * @param    index
       *           The index.
       *           
       * @return   See above.
       */
      public Object getError(int index)
      {
         if (errorList == null)
         {
            throw new ArrayIndexOutOfBoundsException();
         }
         
         return errorList.get(index);
      }

      /**
       * Sets all fields to initial state.
       */
      private void initialize()
      {
         if (this.errorList != null)
         {
            this.errorList.clear();
         }
         
         this.error = false;
         this.soapFault = null;
      }
      
      /**
       * Sets all fields with values from specified errStatus.
       * 
       * @param   errStatus
       *          source error status.
       */
      private void set(ErrorStatus errStatus)
      {
         if (errStatus.errorList != null && !errStatus.errorList.isEmpty())
         {
            if (this.errorList == null)
            {
               this.errorList = new ArrayList<>();
            }
            else
            {
               this.errorList.clear();
            }
            
            this.errorList.addAll(errStatus.errorList);
         }
         else
         {
            this.errorList = null;
         }
         
         this.error = errStatus.error;
         this.soapFault = errStatus.soapFault;
      }
   }
}