# Accumulator.java

    /*
    ** Module   : Accumulator.java
    ** Abstract : Abstract base class for concrete accumulator implementations
    **
    ** Copyright (c) 2004-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- -----------------------------Description-----------------------------
    ** 001 ECF 20051227   @23778 Created initial version. Base class for
    **                           accumulator implementations. Performs common
    **                           data collection tasks. Calculations of
    **                           statistics and retrieval of results are
    **                           specific to subclass implementations.
    ** 002 ECF 20060104   @23813 Removed Resolver inner interface. Has been
    **                           promoted to top-level interface Resolvable.
    **                           Added method to get data source's return
    **                           type.
    ** 003 ECF 20060118   @23981 Added support for break groups. Each instance
    **                           now manages data for the primary accumulation
    **                           and optionally for any number of break group
    **                           categories.
    ** 004 ECF 20060419   @25612 Added support for deferred accumulation. In
    **                           this mode, accumulation is enabled by a call
    **                           to one of the variants of iterate() which
    **                           accept a 'defer' parameter of true, and is
    **                           triggered by a call to the no-arg variant of
    **                           of accumulate().
    ** 005 ECF 20060503   @25948 Added support for accumulation using external
    **                           data sources. Added features required by code
    **                           converted from aggregators in display
    **                           statements.
    ** 006 ECF 20070627   @34321 Ensure safe result is returned if no data has
    **                           been accumulated. Added safety ctor and new
    **                           methods to implement this (isUninitialized()
    **                           and reset()).
    ** 007 ECF 20070705   @34375 Fixed reset(). Create a primary data bundle if
    **                           it has not been done previously.
    ** 008 SIY 20071219   @36521 Fixed initialization of default group.
    **                           Ref: #36508
    ** 009 SIY 20071224   @36567 Added support for sub-results only mode.
    **                           Ref: #36564
    ** 010 CA  20080130   @37022 Added support for block behavior - for each
    **                           block, its reported value (used to retrieve
    **                           accumulated result) and its pending value
    **                           (which will be set as primary data bundle
    **                           for the enclosing block, when the block will
    **                           exit) are kept onto a stack. The sub-only
    **                           property of an accumulator is also block
    **                           specific. Also, sub-only accumulators with
    **                           break by expressions do not update the parent
    **                           blocks when the accumulation is performed.
    ** 011 CA  20080213   @37129 Added undo behavior for accumulators.
    **                           Accumulator and DataBundle will both implement
    **                           Undoable. At instantiation time, the
    **                           accumulator is registered with the
    **                           TransactionManager. Sub-only accumulators
    **                           must not set the reported data bundle for the
    **                           parent block, when the block is finished.
    ** 012 CA  20080313   @37441 Copy accumulator objects must not register
    **                           with the AccumulatorManager.
    ** 013 CA  20080324   @37639 On instantiation, perform a scopeStart call
    **                           so the accumulator will be notified about
    **                           iterate events. On iterate, the pending data
    **                           bundle will be set as the primary data bundle
    **                           in the bundle stack.
    ** 014 CA  20080326   @37835 Added block-awareness for accumulators - when
    **                           entering a block which does not use the accum,
    **                           it must not reset the accumulator; implemented
    **                           the behavior about the "entry" value: this will
    **                           be reported if no ACCUM statement has been
    **                           executed.
    ** 015 CA  20080411   @37937 Implemented the IF-awarness behavior; method
    **                           accumulate(BaseDataType) was changed to allow
    **                           accumulation when the data source is not an
    **                           instance of ExternalDataSource class - this is
    **                           needed to allow ACCUM statements and DISPLAY
    **                           with aggregate phrase stmt to work together.
    ** 016 ECF 20080630   @39024 Fixed memory leak. Accumulators which are
    **                           instance vars of business classes were being
    **                           registered with the TransactionManager and
    **                           with the AccumulatorManager to a block scope
    **                           one level above where they were first being
    **                           used. If that scope was never popped, but the
    **                           lower scope was, these instances would be
    **                           dead, but still doing work when their
    **                           Undoable/Scopeable hooks were being called,
    **                           creating a CPU drain. Refactored constructors
    **                           to support conversion changes.
    ** 017 NVS 20080717   @39131 Two stack were checked with peek() and there
    **                           was no elements: ifstack and
    **                           blockExecutedStack. The checks were protected
    **                           against this condition. See #39131 for
    **                           details.
    ** 018 SVL 20080831   @39632 Added pendingResetStack in order to reset
    **                           bundles after the first entry of the group
    **                           has been iterated.
    ** 019 GES 20080904   @39699 Fixed OutOfMemoryError caused by H018.
    ** 020 CA  20080908   @39730 Fixed missing case about H018: when no
    **                           accumulation is performed for a certain group,
    **                           the bundle must be reset just before the
    **                           result is retrieved for that break-group.
    ** 021 GES 20080917   @39848 Minor class name change.
    ** 022 GES 20080926   @39975 Minor signature change.
    ** 023 CA  20081003   @40056 Added c'tors which receive the expr type.
    **                           Default c'tors are available only for COUNT
    **                           accumulators. ExternalDataSource now saves
    **                           the type of the data source.
    ** 024 CA  20131013          Added no-op deleted() and scopeDeleted() methods, requried by the
    **                           changes in Scopeable and Finalizable interfaces.
    ** 025 EVL 20160224          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
    ** 026 CA  20160627          Reworked undoable support - the undoables register themselves with
    **                           all blocks up the stack, until either 1. the tx block which created
    **                           it is reached or 2. the last tx block where it was saved is reached.
    **         20160721          Cleaned up the c'tors - all accumulators register in the next scope,
    **                           as they are defined and created as instance fields.
    ** 027 EVL 20190121          Adding data reset() call to accumulator reset() method.  This fixes
    **                           issues with getResult() method returning unknown value instead of 0
    **                           when reset() called without accumulate().
    ** 028 CA  20190326          Implemented TransactionHelper instead of direct usage of the static
    **                           API.  This allows the elimination of context local usage in TM.
    ** 029 CA  20220901          Refactored scope notification support: ScopeableFactory was removed, and the
    **                           registration is now specific to each type of scopeable.  For each case, the block
    **                           will be registered for scope support (for that particular scopeable) only when
    **                           the scopeable is 'active' (i.e. unnamed streams or accumulators are used).  This
    **                           allows a lazy registration of scopeables, to avoid the unnecessary overhead of
    **                           processing all the scopeables for each and every block.
    **     TJD 20220504          Java 11 compatibility related minor changes
    ** 030 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    ** 031 CA  20231031          Added 'BlockDefinition' parameter to 'scopeStart'.
    ** 032 PMP 20240411          Modified the values that are added to fixedDataStack from 'scopeStart' method.
    */
    /*
    ** 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 com.goldencode.p2j.util.logging.*;

    import java.lang.reflect.*;
    import java.util.*;

    /**
     * Abstract base class for all accumulators.  An accumulator collects one
     * piece of data upon each pass through an iterative loop and calculates a
     * particular statistic upon the collected data.  For instance, an accumulator
     * might calculate an average numeric value or determine a lexicographical
     * maximum value from a list of strings.
     * <p>
     * An accumulator may derive its data from one of the following sources:
     * <ul>
     *   <li>a mutable variable (a {@link BaseDataType} instance -- <i>note:
     *       the reference provided to the accumulator must not be reassigned,
     *       it must be updated via its <code>assign</code> method only</i>);
     *   <li>a {@link Resolvable data source} instance,
     *       resolved once per iteration;
     * </ul>
     * <p>
     * The accumulator is updated by calling the {@link #iterate} method once per
     * loop iteration.  This is either done manually by client code, or the
     * accumulator can be {@link
     * com.goldencode.p2j.persist.AbstractQuery#addAccumulator added to a query}.
     * In the latter case, the <code>iterate</code> method is called directly by
     * the query.
     * <p>
     * A query may call a variant of the <code>iterate</code> method which
     * accepts a <code>defer</code> argument.  If this argument is
     * <code>true</code>, the data bundle(s) of the Accumulator instance is
     * not updated immediately, but instead is updated only on a subsequent call
     * to the no-argument variant of <code>accumulate</code>.  This alternate
     * mechanism is used in cases where the accumulation must occur conditionally
     * or otherwise cannot be triggered automatically by the query.
     * <p>
     * Each accumulator instance manages one or more {@link
     * Accumulator.DataBundle}s to gather and report its statistic.  Concrete
     * implementations must implement this inner interface and implement the
     * {@link #createDataBundle} abstract method to return a new instance of
     * their implementation-specific data bundle.  An accumulator will always
     * have one, primary data bundle, which will track results across the full
     * loop iteration (i.e., across all break groups).  Additional data bundles
     * may be added for each break group category the accumulator supports using
     * the {@link #addBreakGroup} method.  If an accumulator is associated with
     * a query containing a matching break group category, the data bundle will
     * be reset automatically whenever the query results enter a new break group.
     * <p>
     * The mechanism to retrieve the statistic(s) calculated by a concrete
     * accumulator implementation is specific to that implementation.
     */
    public abstract class Accumulator
    extends LazyUndoable
    implements Scopeable,
               Finalizable
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(Accumulator.class);

       /** Base weight for all subclass relative weight values */
       protected static final int BASE_WEIGHT = 0;

       /**
         * Value used to mark the IF statement that it doesn't need to postpone
         * anything.
         */
       private static final int POSTPONE_NONE    = 0;

       /**
         * Value used to mark the IF statement that it needs to reset the
         * accumulator, when the enclosed accumulator did not execute.
         */
       private static final int POSTPONE_RESET   = 1;

       /**
         * Value used to mark the IF statement that it needs to set the accumulator
         * to UNKNOWN, when the enclosed accumulator did not execute.
         */
       private static final int POSTPONE_UNKNOWN = 2;

       /** Abstraction of data source from which data values are accumulated */
       private final Resolvable dataSource;

       /**
        * Stack with maps of data bundles by break group. When a block is popped,
        * the primary data bundle in bundleStack for the currently top block
        * is assigned to the pending data bundle associated to the popped block.
        */
       private Deque<LinkedHashMap<Resolvable, DataBundle>> bundleStack =
                          new ArrayDeque<LinkedHashMap<Resolvable, DataBundle>>();

       /**
        * Stack with maps of data bundles to pending reset flag. This flag
        * indicates whether we should reset the value for a given bundle before
        * using it.
        */
       private Deque<LinkedHashMap<Resolvable, Boolean>> pendingResetStack =
                          new ArrayDeque<LinkedHashMap<Resolvable, Boolean>>();

       /**
        * Stack with pending accumulation values for each block. When an ACCUM
        * statement is executed, the value is accumulated to all DataBundles
        * contained in this stack.
        */
       private Deque<DataBundle> pendingStack = new ArrayDeque<DataBundle>();

       /** Stack with usage status for this accumulator. */
       private Deque<Boolean> usageStack = new ArrayDeque<Boolean>();

       /** Stack with each parent block type (top-level or not). */
       private Deque<Boolean> topLevelStack = new ArrayDeque<Boolean>();

       /** Stack with status of the ACCUM statement execution. */
       private Deque<Boolean> accumExecutedStack = new ArrayDeque<Boolean>();

       /** Stack with status of "block with ACCUM statement" execution. */
       private Deque<Boolean> blockExecutedStack = new ArrayDeque<Boolean>();

       /** Stack with status of "IF with enclosed ACCUM statement" execution. */
       private Deque<Boolean> ifExecutedStack = new ArrayDeque<Boolean>();

       /** Stack with the entry values for each block. */
       private Deque<DataBundle> entryStack = new ArrayDeque<DataBundle>();

       /** Stack with the postpone values for IF cases. */
       private Deque<Integer> postponeStack = new ArrayDeque<Integer>();

       /**
        * Stack with the IF statement execution status - true values mean that
        * an IF statement is currently being executed.
        */
       private Deque<Boolean> ifStack = new ArrayDeque<Boolean>();

       /** Stack with the fixed reported value. */
       private Deque<DataBundle> fixedDataStack = new ArrayDeque<DataBundle>();

       /** Mapping of break groups to "new group" status for next iteration */
       private Map<Resolvable, Boolean> breakMap = null;

       /** Is accumulator in uninitialized safe mode? */
       private boolean uninitialized = true;

       /** Is accumulator operating in deferred iteration mode? */
       private boolean deferred = false;

       /** Is accumulator enabled for the next iteration in its loop? */
       private boolean enabled = true;

       /** Does accumulator produce "grand" results or subgroup results only? */
       private boolean subgroupOnly = false;

       /** Is the most recent accumulation the last of the first break group? */
       private boolean lastOfGroup = false;

       /**
        * Track sub-only mode - an accumulate function can retrieve a sub-only
        * accumulator only if it is in the same block as the accumulate statement.
        */
       private Deque<Boolean> subOnlyStack = new ArrayDeque<Boolean>();

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

       /**
        * Constructor which stores no data source.  This instance expects to be
        * fed data from an external source via its <code>accumulate</code>
        * method.  The accumulator is registered with the AccumulatorManager and
        * the TransactionManager in the next scope.
        * <p>
        * This c'tor variant must only be used for the CountAccumulator type, as
        * this is the only aggregator which does not require knowledge about the
        * expression type.
        */
       protected Accumulator()
       {
          this(new ExternalDataSource(), true);
       }

       /**
        * Constructor which stores no data source.  This instance expects to be
        * fed data from an external source via its <code>accumulate</code>
        * method.  The accumulator is registered with the AccumulatorManager and
        * the TransactionManager in the next scope.
        *
        * @param   cls
        *          Data type of the value being accumulated.
        */
       protected Accumulator(Class<? extends BaseDataType> cls)
       {
          this(new ExternalDataSource(cls), true);
       }

       /**
        * Constructor which stores a mutable variable reference as the
        * accumulator's data source.  When an iteration occurs, the accumulator
        * gathers its data using the current value of a mutable variable. Also,
        * the accumulator is registered with the AccumulatorManager and the
        * TransactionManager, in the next scope.
        *
        * @param   variable
        *          Mutable variable from which data is collected.
        */
       protected Accumulator(final BaseDataType variable)
       {
          this(new Resolvable()
          {
             public BaseDataType resolve()
             {
                return variable.duplicate();
             }

             public Class<? extends BaseDataType> getType()
             {
                return variable.getClass();
             }
          }, true);

          uninitialized = variable.isUnknown();
       }

       /**
        * Constructor which stores a <code>Resolvable</code> as the accumulator's
        * data source.  When an iteration occurs, the accumulator gathers its data
        * by resolving the current value of the data source.
        *
        * @param   dataSource
        *          Data source from which data is collected.
        * @param   register
        *          This flag will be <code>false</code> only if a copy object is instantiated.
        *          Because we use the copy only for roll-back purposes, we do not need to add it to
        *          the TransactionManager or AccumulatorManager.
        */
       protected Accumulator(Resolvable dataSource, boolean register)
       {
          this.dataSource = dataSource;
          uninitialized = false;

          // push a "reset" value onto the stack
          bundleStack.push(new LinkedHashMap<Resolvable, DataBundle>());
          pendingResetStack.push(new LinkedHashMap<Resolvable, Boolean>());
          pendingStack.push(createDataBundle());
          entryStack.push(createDataBundle());

          usageStack.push(Boolean.FALSE);
          topLevelStack.push(Boolean.FALSE);
          accumExecutedStack.push(Boolean.FALSE);
          blockExecutedStack.push(Boolean.FALSE);
          ifExecutedStack.push(Boolean.FALSE);

          postponeStack.push(POSTPONE_NONE);
          ifStack.push(Boolean.FALSE);

          fixedDataStack.push(createDataBundle());
          bundleStack.peek().put(null, createDataBundle());

          subOnlyStack.push(Boolean.FALSE);

          if (register)
          {
             tm.registerNext(this);
             AccumulatorManager.addAccumulator(this);
          }
       }

       /**
        * Get current state of sub-only mode, for the current
        * block.
        *
        * @return  <code>true</code> if sub-only mode is active.
        */
       public boolean isSubOnly()
       {
          return subOnlyStack.peek();
       }

       /**
        * Turn sub-only mode for the current block. Note that
        * this is one-way operation.
        */
       public void setSubOnly()
       {
          subOnlyStack.pop();
          subOnlyStack.push(Boolean.TRUE);
       }

       /**
        * Track a new break group category within this accumulator.  Internally,
        * a new data bundle is created to manage data associated with the break
        * group.
        *
        * @param   breakGroupKey
        *          Key which will be used to manage and retrieve results for the
        *          specified break group.
        */
       public final void addBreakGroup(Resolvable breakGroupKey)
       {
          if (breakGroupKey == null)
          {
             throw new NullPointerException("Null break group key");
          }

          bundleStack.peek().put(breakGroupKey, createDataBundle());
       }

       /**
        * Report whether the most recent accumulation performed by this instance
        * represents the last accumulation to be performed for the current break
        * group.  Only the first registered break group is considered for
        * purposes of this determination.  If no break groups have been registered
        * with this accumulator, <code>false</code> is returned.
        *
        * @return  <code>true</code> if the most recent accumulation represents
        *          the last for the current break group;  else <code>false</code>.
        *
        * @see     #setLastOfGroup
        */
       public final boolean isLastOfGroup()
       {
          return lastOfGroup;
       }

       /**
        * Update this accumulator's current state to remember that the most
        * recent accumulation performed by this instance represents the last such
        * accumulation to be performed for the current break group.  Although
        * this method is <code>public</code>, it is intended that this method is
        * only invoked by the persistence layer of the P2J runtime, and not by
        * application code.
        *
        * @param   lastOfGroup
        *          <code>true</code> if the most recent accumulation represents
        *          the last for the current break group;  else <code>false</code>.
        *
        * @see     #isLastOfGroup
        */
       public final void setLastOfGroup(boolean lastOfGroup)
       {
          this.lastOfGroup = lastOfGroup;
       }

       /**
        * Indicate whether this accumulator instance is meant to report full
        * results or subgroup results only.
        *
        * @return  <code>true</code> if this instance should report subgroup
        *          results only;  <code>false</code> if it should also report
        *          full results (e.g., grand totals, etc.).
        *
        * @see     #setSubgroupOnly
        */
       public final boolean isSubgroupOnly()
       {
          return subgroupOnly;
       }

       /**
        * Set whether this accumulator instance is meant to report full results
        * or subgroup results only.
        *
        * @param   subgroupOnly
        *          <code>true</code> if this instance should report subgroup
        *          results only;  <code>false</code> if it should also report
        *          full results (e.g., grand totals, etc.).
        *
        * @see     #isSubgroupOnly
        */
       public final void setSubgroupOnly(boolean subgroupOnly)
       {
          this.subgroupOnly = subgroupOnly;
       }

       /**
        * Get the full result of all accumulations so far for this accumulator,
        * across all break groups.
        *
        * @return  Current full result.
        */
       public abstract BaseDataType getFullResult();

       /**
        * Get the current subgroup result for the first registered break group in
        * this accumulator.  Subsequent break groups, if any, are ignored.
        *
        * @return  Subgroup result for first registered break group, or
        *          <code>null</code> if no subgroups are being tracked by this
        *          accumulator.
        */
       public abstract BaseDataType getSubResult();

       /**
        * Get the label associated with this accumulator type, for use in the UI.
        *
        * @return  Label associated with this accumulator type.
        */
       public abstract String getLabel();

       /**
        * Get the relative weight associated with this accumulator type, for use
        * in sorting accumulator instances of different types according to their
        * natural display order.
        *
        * @return  An integer which represents this accumulator's sorting weight
        *          relative to other accumulator types.
        */
       public abstract int getWeight();

       /**
        * Get the resolvable data source which feeds this accumulator.
        *
        * @return  Data source.
        */
       public final Resolvable getDataSource()
       {
          return dataSource;
       }

       /**
        * This method must be called exactly once per loop iteration, either
        * directly by client code, or by a query with which this accumulator is
        * associated.  If it is not called (or called more than once) for each
        * iteration, the result of the accumulation may become corrupt.
        * <p>
        * Upon invocation, if <code>defer</code> is <code>false</code>, it
        * resolves the current value from the accumulator's data source, then
        * performs an implementation-specific action to update the accumulator's
        * internal state.
        * <p>
        * This implementation assumes break groups are disabled.
        *
        * @param   defer
        *          <code>true</code> to defer accumulation until the next
        *          invocation of the no-argument {@link #accumulate()} method;
        *          <code>false</code> to perform accumulation immediately.
        *
        * @see     #accumulate
        */
       public final void iterate(boolean defer)
       {
          iterate(null, defer);
       }

       /**
        * This method must be called exactly once per loop iteration, either
        * directly by client code, or by a query with which this accumulator is
        * associated.  If it is not called (or called more than once) for each
        * iteration, the result of the accumulation may become corrupt.
        * Upon invocation, if <code>defer</code> is <code>false</code>, it
        * resolves the current value from the accumulator's data source, then
        * performs an implementation-specific action to update the accumulator's
        * internal state.  Each data bundle being managed by this accumulator
        * (including the primary bundle and each break group bundle), is updated
        * with the data value being accumulated.
        * <p>
        * If a data bundle must be reset, because the result set being iterated
        * entered a new break group for that bundle's break group category, this
        * is done before the new data item is accumulated.
        *
        * @param   breakMap
        *          A map of break group category keys to boolean values, where
        *          the boolean values indicate whether the current loop iteration
        *          marks entry into a new break group for the associated category.
        *          For every key with a <code>true</code> value, the associated
        *          break group data bundle is reset.
        * @param   defer
        *          <code>true</code> to defer accumulation until the next
        *          invocation of the no-argument {@link #accumulate()} method;
        *          <code>false</code> to perform accumulation immediately.
        *
        * @see     #accumulate
        */
       public final void iterate(Map<Resolvable, Boolean> breakMap, boolean defer)
       {
          this.breakMap = breakMap;
          this.deferred = defer;
          enabled = true;

          Map<Resolvable, Boolean> pendingResetMap = pendingResetStack.peek();
          if (breakMap != null)
          {
             for (Resolvable key : breakMap.keySet())
             {
                if (breakMap.get(key) == Boolean.TRUE)
                {
                   pendingResetMap.put(key, Boolean.TRUE);
                }
             }
          }

          if (!defer)
          {
             accumulate();
          }
       }

       /**
        * This method may be called one or more times per loop iteration, either
        * directly by client code, or indirectly by a query with which this
        * accumulator is associated.  If it is not called at least once per
        * iteration, the result of the accumulation may become corrupt.
        * <p>
        * Upon invocation, it checks whether iteration is enabled at the current
        * time.  If so, it resolves the current value from the accumulator's
        * data source, then performs an implementation-specific action to update
        * the accumulator's internal state.  Each data bundle being managed by
        * this accumulator (including the primary bundle and each break group
        * bundle), is updated with the data value being accumulated.  If iteration
        * is not currently enabled (i.e., we are operating in deferred mode, and
        * the driving query has not enabled iteration for the current loop pass),
        * this method returns immediately.
        * <p>
        * If a data bundle must be reset, because the result set being iterated
        * entered a new break group for that bundle's break group category, this
        * is done before the new data item is accumulated.
        */
       public final void accumulate()
       {
          // an accumulator registered with 2 different frames, used with DISPLAY
          // with ACCUM statement must not perform accumulation twice
          if (!enabled /* || accumExecutedStack.peek() */)
          {
             return;
          }

          // Resolve data to be accumulated.
          BaseDataType datum = dataSource.resolve();

          // Update primary data bundle.
          accumulate(null, false, datum);

          // Update break group data bundles, if any.
          if (breakMap != null)
          {
             Iterator<Map.Entry<Resolvable, Boolean>> iter =
                                                 breakMap.entrySet().iterator();

             while (iter.hasNext())
             {
                Map.Entry<Resolvable, Boolean> entry  = iter.next();

                Resolvable key   = entry.getKey();
                Boolean reset = pendingResetStack.peek().get(key);
                if (reset == null)
                {
                   reset = Boolean.FALSE;
                }
                accumulate(key, reset, datum);
                if (reset)
                {
                   pendingResetStack.peek().put(key, Boolean.FALSE);
               }
             }

             breakMap = null;
          }

          if (deferred)
          {
             enabled = false;
          }
       }

       /**
        * This method may be called one or more times per loop iteration, either
        * directly by client code, or indirectly by a query with which this
        * accumulator is associated.  If it is not called at least once per
        * iteration, the result of the accumulation may become corrupt.
        * <p>
        * Upon invocation, it checks whether iteration is enabled at the current
        * time.  If so, it resolves the current value from the accumulator's
        * data source, then performs an implementation-specific action to update
        * the accumulator's internal state.  Each data bundle being managed by
        * this accumulator (including the primary bundle and each break group
        * bundle), is updated with the data value being accumulated.  If iteration
        * is not currently enabled (i.e., we are operating in deferred mode, and
        * the driving query has not enabled iteration for the current loop pass),
        * this method returns immediately.
        * <p>
        * If a data bundle must be reset, because the result set being iterated
        * entered a new break group for that bundle's break group category, this
        * is done before the new data item is accumulated.
        * <p>
        * This method can be invoked even if the {@link #dataSource} is not an
        * instance of class {@link ExternalDataSource}; this is needed because
        * the accumulator may be used standalone (by calling the
        * {@link #accumulate()} method) or used inside a DISPLAY with aggregate
        * phrase statement (and will be called by
        * {@link com.goldencode.p2j.ui.GenericFrame} when it will display the
        * value).
        * <p>
        * The only situation when the {@link #dataSource} will be an instance of
        * class {@link ExternalDataSource} is when the accumulator is used only
        * with a DISPLAY with aggregate phrase statement - in this case, the
        * default c'tor will be used to instantiate the accumulator.
        *
        * @param   datum
        *          The data item to be accumulated.
        */
       public final void accumulate(BaseDataType datum)
       {
          if (!enabled)
          {
             return;
          }

          ExternalDataSource source = null;

          if (dataSource instanceof ExternalDataSource)
          {
             source = (ExternalDataSource) dataSource;
          }
          else
          {
             source = new ExternalDataSource();
          }

          source.setDatum(datum);

          boolean doReset = uninitialized;

          // Reset accumulator if it is not initialized yet.

          // NOTE: Order of operations is important, since state of the
          //       uninitialized may affect behavior of createDataBundle() in
          //       subclasses.
          uninitialized = false;

          if (doReset)
             resetData();

          accumulate();
       }

       /**
        * Get an iterator of the break group keys for all break group categories
        * being managed by this accumulator.  A key for the primary data bundle
        * is not included in the iteration.
        *
        * @return  Iterator on all break group keys.
        */
       public final Iterator<Resolvable> breakGroupKeys()
       {
          return new Iterator<Resolvable>()
          {
             private Iterator<Resolvable> iter =
                bundleStack.peek().keySet().iterator();

             private Resolvable nextKey = null;

             public boolean hasNext()
             {
                while (nextKey == null && iter.hasNext())
                {
                   nextKey = iter.next();
                }

                return (nextKey != null);
             }

             public Resolvable next()
             {
                if (!hasNext())
                {
                   throw new NoSuchElementException();
                }

                Resolvable res = nextKey;
                nextKey = null;

                return res;
             }

             public void remove()
             {
                throw new UnsupportedOperationException();
             }
          };
       }

       /**
        * Get the first, non-<code>null</code> break group key registered with
        * this accumulator.  If no break group keys have been registered, return
        * <code>null</code>.
        *
        * @return  First non-<code>null</code> key or <code>null</code>.
        */
       public final Resolvable getFirstBreakGroupKey()
       {
          Iterator<Resolvable> iter = breakGroupKeys();
          if (iter.hasNext())
          {
             return iter.next();
          }

          return null;
       }

       /**
        * When an accumulator is used inside a block, a call to this method must
        * be made - this is because, if the block is not notified that it uses
        * this accumulator, then the accumulation value for the parent block
        * will not be updated when the child block is finished.
        */
       public void reset()
       {
          // because the iterate/finished/retry calls are ignored when it is not
          // in "use" mode, the usage flag is set only for the current block. the
          // parent blocks MUST have also a reset() call
          usageStack.pop();
          usageStack.push(Boolean.TRUE);

          // the accumulation value is set from unknown to the reset value
          pendingStack.peek().reset();
          bundleStack.peek().get(null).reset();
          // reset data as well
          entryStack.peek().reset();

          // if the parent block is executing an IF statement, then we remove the
          // postpone information
          ifStack.pop();
          if (ifStack.size() > 0 && ifStack.peek())
          {
             postponeStack.pop();
             postponeStack.pop();
             postponeStack.push(POSTPONE_NONE);
             postponeStack.push(POSTPONE_NONE);
          }
          ifStack.push(Boolean.FALSE);

          // a new iteration is started
          iterate();
       }

       /**
        * This method is used to notify the accumulator that a IF statement which
        * contains the ACCUM statement or a DISPLAY with aggregate phrase has
        * ended. The postpone information is removed when:<br>
        * <ul>
        *   <li>accumulation has been performed within the parent block</li>
        *   <li>a block "header" for a block which uses the accumulator has been
        * executed</li>
        *   <li>the postponed type is not {@link #POSTPONE_UNKNOWN}</li>
        * </ul>
        */
       public void ifEnded()
       {
          // remove postpone information if:
          // - accumulation has been performed within this block
          // - a block "header" for a block which uses the accumulator has been
          // executed
          // - the postponed value needs to be "reset"
          if (postponeStack.peek() != POSTPONE_UNKNOWN &&
              (accumExecutedStack.peek() || blockExecutedStack.peek()))
          {
             postponeStack.pop();
             postponeStack.push(POSTPONE_NONE);
          }

          // the IF statement has finished
          ifStack.pop();
          ifStack.push(Boolean.FALSE);
       }

       /**
        * When an IF statement which contains the ACCUM statement or DISPLAY with
        * aggregate phrase is started (and also the accumulation statement is not
        * also enclosed within a REPEAT/DO TRANSACTION/FOR block), the accumulator
        * must be reset when the enclosed accumulation did not execute.<br>
        * This method will call {@link #postpone(int)} with the parameter set to
        * {@link #POSTPONE_RESET}.
        */
       public void postponeReset()
       {
          postpone(POSTPONE_RESET);
       }

       /**
        * When an IF statement which contains the ACCUM statement or DISPLAY with
        * aggregate phrase is started (and the accumulation statement is also
        * enclosed within a REPEAT/DO TRANSACTION/FOR block), the accumulator must
        * be set to UNKNOWN when the enclosed accumulation did not execute.<br>
        * When there are enclosed 2 or more accumulation statements and at least
        * one is also enclosed within a block as specified above, then setting the
        * accumulator to UNKNOWN has precedence against resetting the accumulator.
        * This method will call {@link #postpone(int)} with the parameter set to
        * {@link #POSTPONE_UNKNOWN}.
        */
       public void postponeUnknown()
       {
          postpone(POSTPONE_UNKNOWN);
       }

       /**
        * Provides a notification that the block whose scope in which the object
        * is registered is about to iterate and attempt another pass.
        * <p>
        * When a new iteration is performed, if this accumulator is retrieved
        * before any more accumulation, then the reported value is the accumulation
        * value for the enclosing block.
        */
       public void iterate()
       {
          // do nothing if the ACCUM statement is not used
          if (!usageStack.peek().booleanValue())
          {
             return;
          }

          // on iterate, the primary data bundle in the bundle stack will be set
          // to the pending value
          bundleStack.peek().get(null).assign(pendingStack.peek());

    		// reset the ACCUM, "BLOCK with accum" and "IF with accum" executed
    		// status
          accumExecutedStack.pop();
          accumExecutedStack.push(Boolean.FALSE);
          blockExecutedStack.pop();
          blockExecutedStack.push(Boolean.FALSE);
          ifExecutedStack.pop();
          ifExecutedStack.push(Boolean.FALSE);

          // reset the IF execution status and postpone type
          ifStack.pop();
          ifStack.push(Boolean.FALSE);
          postponeStack.pop();
          postponeStack.push(POSTPONE_NONE);
       }

       /**
        * Cleanup that must occur at the end of the block to which the frame is
        * scoped.
        */
       public void finished()
       {
          // all pending data is already accumulated for all blocks

          LinkedHashMap<Resolvable, DataBundle> map = bundleStack.pop();
          pendingResetStack.pop();
          DataBundle pendingData = pendingStack.pop();
          boolean inUse = usageStack.pop();

          entryStack.pop();
          postponeStack.pop();
          ifStack.pop();
          fixedDataStack.pop();

          // if the accumulator is "in use", then the parent block will always
          // report the pending data for the currently finished block
          if (inUse && !(subOnlyStack.peek() && map.size() > 1))
          {
             fixedDataStack.peek().assign(pendingData);
          }

          // if this is a sub-only accumulator with break-by keys, do not modify
          // the reported value for the parent block
          // also, if this is a top-level block, do not update the previous block
          // (due to recursion restrictions)
          if (topLevelStack.peek() || (subOnlyStack.peek() && map.size() > 1))
          {
             return;
          }

          // set the previous block's reported data to be this blocks pending data
          // do this only if there is a parent block were to place the data and if
          // the block knows this accumulator
          if (!bundleStack.isEmpty() && inUse)
          {
             bundleStack.peek().put(null, pendingData);
          }

          if (inUse)
          {
             // the parent block must know that this block has executed
             boolean b1 = blockExecutedStack.pop();
             blockExecutedStack.pop();
             blockExecutedStack.push(Boolean.TRUE);
             blockExecutedStack.push(b1);
          }
       }

       /**
        * Provides a notification that the external program scope in which the object is registered is
        * is being deleted and the object's reference will be lost after this method is called.
        * <p>
        * This is a no-op for accumulators.
        */
       @Override
       public void deleted()
       {
          // no-op
       }

       /**
        * Provides a notification that the block whose scope in which the object
        * is registered is about to retry and re-attempt the current iteration.
        * This provides an opportunity to clear or reset state from the previous
        * pass.
        */
       public void retry()
       {
          // do nothing
       }

       /**
        * Process a notification that a scope is about to be exited. The used
        * accum maps for this block will be popped. The enclosing block will
        * have its primary data bundle set to the previous block's pending data
        * bundle.
        */
       public void scopeFinished()
       {
          // the sub-only property of an accumulator is distinct per each block.
          // this is popped only when the accumulator scope is ended - so all
          // finish processing (i.e. for a display accum stmt) is done
          subOnlyStack.pop();

          topLevelStack.pop();
          accumExecutedStack.pop();
          blockExecutedStack.pop();
          ifExecutedStack.pop();
       }

       /**
        * Provides notification that the external procedure scope has been deleted.
        * <p>
        * This is a no-op for accumulators.
        */
       @Override
       public void scopeDeleted()
       {
          // no-op
       }

       /**
        * Get the {@link ScopeId} for the instance.
        *
        * @return   <code>null</code>, as the scope notification is done via {@link AccumulatorManager}.
        */
       @Override
       public ScopeId getScopeId()
       {
          // no ID necessary, as it does not get registered explicitly, is called via AccumulatorManager
          return null;
       }

       /**
        * Process a notification that a new block scope has been entered. Each
        * stack is pushed a new map. Default data bundles are created both for
        * the pending and for the primary data bundles only when the accumulator
        * enters a block which uses the ACCUM statement (a reset() call is
        * performed)
        *
        * @param    block
        *           The explicit block definition which required this notification.
        */
       @Override
       public void scopeStart(BlockDefinition block)
       {
          tm.registerFinalizable(this, false);

          usageStack.push(Boolean.FALSE);
          topLevelStack.push(tm.isTopLevelBlock());

          boolean parentAccum = accumExecutedStack.peek() ||
                                blockExecutedStack.peek() ||
                                ifExecutedStack.peek();

          accumExecutedStack.push(Boolean.FALSE);
          blockExecutedStack.push(blockExecutedStack.peek());
          ifExecutedStack.push(ifExecutedStack.peek());

          // this notifies that a block which uses this accumulator is entered
          LinkedHashMap<Resolvable, DataBundle> map = bundleStack.peek();
          DataBundle nullBundle = map.get(null);

          // push a "reset" value onto the stack
          bundleStack.push(new LinkedHashMap<Resolvable, DataBundle>());
          pendingResetStack.push(new LinkedHashMap<Resolvable, Boolean>());
          pendingStack.push(createDataBundle());

          // set the the parent's data bundle as the entry value for this block
          DataBundle preventry = entryStack.peek();
          entryStack.push(createDataBundle());
          // if the direct parent block has no ACCUM or BLOCK with accum
          // execution, let the entry value propagate to the child
          if (!parentAccum)
          {
             entryStack.peek().assign(preventry);
             fixedDataStack.push(preventry);
          }
          else
          {
             entryStack.peek().assign(nullBundle);
             fixedDataStack.push(nullBundle);
          }

          Map<Resolvable, DataBundle> bundleMap = bundleStack.peek();
          DataBundle b = createDataBundle();
          bundleMap.put(null, b);
          b.assign(nullBundle);

          // add every non-null value from the previous block to this block
          Iterator<Resolvable> itr = map.keySet().iterator();
          while (itr.hasNext())
          {
             Resolvable r = itr.next();
             if (r != null)
             {
                b = createDataBundle();
                bundleMap.put(r, b);
                b.assign(map.get(r));
             }
          }

          // the sub-only property of an accumulator is distinct per each block.
          subOnlyStack.push(Boolean.FALSE);

          postponeStack.push(POSTPONE_NONE);
          ifStack.push(Boolean.FALSE);
       }

       /**
        * This is a form of a copy constructor that makes a deep copy of the
        * current instance and returns this copy as a new instance of the same
        * class.
        *
        * @return   An instance of the same class whose data is identical to
        *           that of this instance.
        */
       public Undoable deepCopy()
       {
          Accumulator copy = null;
          try
          {
             // instantiate a new object of this object's class
             Constructor<? extends Accumulator> ctr =
                this.getClass().getConstructor(Resolvable.class,
                                               boolean.class);
             copy = ctr.newInstance(this.dataSource, false);

             // clear the stacks
             copy.bundleStack.clear();
             copy.pendingResetStack.clear();
             copy.pendingStack.clear();
             copy.subOnlyStack.clear();
             copy.usageStack.clear();
             copy.entryStack.clear();

             // backup the bundle stack
             Iterator<LinkedHashMap<Resolvable, DataBundle>> itr;
             itr = this.bundleStack.descendingIterator();
             while (itr.hasNext())
             {
                LinkedHashMap<Resolvable,DataBundle> map = itr.next();
                LinkedHashMap<Resolvable,DataBundle> map2 =
                                       new LinkedHashMap<Resolvable,DataBundle>();
                copy.bundleStack.push(map2);

                for (Map.Entry<Resolvable, DataBundle> me : map.entrySet())
                {
                   Resolvable r = me.getKey();
                   DataBundle d = (DataBundle) me.getValue().deepCopy();
                   map2.put(r, d);
                }
             }

             // backup the pending reset stack
             Iterator<LinkedHashMap<Resolvable, Boolean>> itr1;
             itr1 = this.pendingResetStack.descendingIterator();
             while (itr1.hasNext())
             {
                LinkedHashMap<Resolvable,Boolean> map = itr1.next();
                LinkedHashMap<Resolvable,Boolean> map1 =
                                       new LinkedHashMap<Resolvable,Boolean>();
                copy.pendingResetStack.push(map1);

                for (Map.Entry<Resolvable, Boolean> me : map.entrySet())
                {
                   Resolvable r = me.getKey();
                   Boolean d = me.getValue();
                   map1.put(r, d);
                }
             }

             // backup the pending stack
             Iterator<DataBundle> itr2 = this.pendingStack.descendingIterator();
             while (itr2.hasNext())
             {
                DataBundle d = itr2.next();
                copy.pendingStack.push((DataBundle) d.deepCopy());
             }

             // backup the sub-only stack
             Iterator<Boolean> itr3 = this.subOnlyStack.descendingIterator();
             while (itr3.hasNext())
             {
                Boolean b = itr3.next();
                copy.subOnlyStack.push(b);
             }

             // backup the break-by map
             copy.breakMap = null;
             if (this.breakMap != null)
             {
                copy.breakMap = this.breakMap.getClass().getDeclaredConstructor().newInstance();
                for (Map.Entry<Resolvable, Boolean> me : this.breakMap.entrySet())
                {
                   Resolvable r = me.getKey();
                   Boolean b = me.getValue();
                   copy.breakMap.put(r, b);
                }
             }

             // backup the usage stack
             Iterator<Boolean> itr4 = this.usageStack.descendingIterator();
             while (itr4.hasNext())
             {
                Boolean b = itr4.next();
                copy.usageStack.push(b);
             }

             // backup the entry stack
             Iterator<DataBundle> itr5 = this.entryStack.descendingIterator();
             while (itr5.hasNext())
             {
                DataBundle d = itr5.next();
                copy.entryStack.push((DataBundle) d.deepCopy());
             }

             // backup the remaining values
             copy.uninitialized = this.uninitialized;
             copy.deferred      = this.deferred;
             copy.enabled       = this.enabled;
             copy.subgroupOnly  = this.subgroupOnly;
             copy.lastOfGroup   = this.lastOfGroup;
          }
          catch (ReflectiveOperationException e)
          {
             LOG.severe("", e);
          }
          return copy;
       }

       /**
        * Sets the state of this instance based on the state of the passed instance.
        *
        * @param    value
        *           The instance from which to copy state.
        */
       public void assign(Undoable value)
       {
          Accumulator copy = (Accumulator) value;

          // clear the stacks
          this.bundleStack.clear();
          this.pendingResetStack.clear();
          this.pendingStack.clear();
          this.subOnlyStack.clear();
          this.usageStack.clear();
          this.entryStack.clear();

          // restore the bundle stack
          Iterator<LinkedHashMap<Resolvable, DataBundle>> itr;
          itr = copy.bundleStack.descendingIterator();
          while (itr.hasNext())
          {
             LinkedHashMap<Resolvable,DataBundle> map = itr.next();
             LinkedHashMap<Resolvable,DataBundle> map2 =
                                    new LinkedHashMap<Resolvable,DataBundle>();
             this.bundleStack.push(map2);

             for (Resolvable r : map.keySet())
             {
                map2.put(r, (DataBundle) map.get(r).deepCopy());
             }
          }

          // restore the pending reset stack
          Iterator<LinkedHashMap<Resolvable, Boolean>> itr1;
          itr1 = copy.pendingResetStack.descendingIterator();
          while (itr1.hasNext())
          {
             LinkedHashMap<Resolvable,Boolean> map = itr1.next();
             LinkedHashMap<Resolvable,Boolean> map1 =
                                    new LinkedHashMap<Resolvable,Boolean>();
             this.pendingResetStack.push(map1);

             for (Resolvable r : map.keySet())
             {
                map1.put(r, map.get(r));
             }
          }

          // restore the pending stack
          Iterator<DataBundle> itr2 = copy.pendingStack.descendingIterator();
          while (itr2.hasNext())
          {
             DataBundle d = itr2.next();
             this.pendingStack.push((DataBundle) d.deepCopy());
          }

          // restore the sub-only stack
          Iterator<Boolean> itr3 = copy.subOnlyStack.descendingIterator();
          while (itr3.hasNext())
          {
             Boolean b = itr3.next();
             this.subOnlyStack.push(b);
          }

          // restore the break-by map
          this.breakMap = null;
          if (copy.breakMap != null)
          {
             try
             {
                this.breakMap = copy.breakMap.getClass().getDeclaredConstructor().newInstance();
             }
             catch (Exception e)
             {
                // do nothing - an exception should never be thrown here
             }
             for (Resolvable r : copy.breakMap.keySet())
             {
                this.breakMap.put(r, copy.breakMap.get(r));
             }
          }

          // restore the usage stack
          Iterator<Boolean> itr4 = copy.usageStack.descendingIterator();
          while (itr4.hasNext())
          {
             Boolean b = itr4.next();
             this.usageStack.push(b);
          }

          // restore the entry stack
          Iterator<DataBundle> itr5 = copy.entryStack.descendingIterator();
          while (itr5.hasNext())
          {
             DataBundle d = itr5.next();
             this.entryStack.push((DataBundle) d.deepCopy());
          }

          // restore the remaining values
          this.uninitialized = copy.uninitialized;
          this.deferred      = copy.deferred;
          this.enabled       = copy.enabled;
          this.subgroupOnly  = copy.subgroupOnly;
          this.lastOfGroup   = copy.lastOfGroup;
       }

       /**
        * Convenience method to create a new instance of the data source's
        * return data type, which represents the unknown value.
        *
        * @return  Unknown value, using the data type appropriate to the data
        *          source.
        */
       protected BaseDataType createUnknown()
       {
          return BaseDataType.generateUnknown(getType());
       }

       /**
        * Get the data type returned by this accumulator's data source.
        *
        * @return  Class indicating the data source's return type.
        */
       protected Class<?> getType()
       {
          return dataSource.getType();
       }

       /**
        * Indicate whether this accumulator is in uninitialized, safe mode.  An
        * uninitialized accumulator gives a safe response when asked for a result,
        * even if it has never accumulated a single value.
        *
        * @return  <code>true</code> if uninitialized, else <code>false</code>.
        */
       protected boolean isUninitialized()
       {
          return uninitialized;
       }

       /**
        * Reset all data managed by this accumulator to default values.  If this
        * accumulator has no primary data bundle, install one now.
        */
       protected void resetData()
       {
          if (!bundleStack.peek().containsKey(null))
          {
             bundleStack.peek().put(null, createDataBundle());
          }

          for (DataBundle bundle : bundleStack.peek().values())
          {
             bundle.reset();
          }
       }

       /**
        * Calculate the accumulator's result for the specified break group.  The
        * data bundle associated with <code>breakGroupKey</code> is retrieved and
        * is asked to calculate a result.
        *
        * @param   breakGroupKey
        *          Key which identifies a particular break group category.
        *
        * @return  Result calculated for the given break group category.
        */
       protected BaseDataType calculate(Resolvable breakGroupKey)
       {
          DataBundle data = null;
          if (breakGroupKey == null)
          {
             boolean b1 = blockExecutedStack.pop();
             boolean parentBlockExecuted = false;
             if (blockExecutedStack.size() > 0)
             {
                parentBlockExecuted = blockExecutedStack.peek();
             }
             blockExecutedStack.push(b1);

             boolean isFinalizables = tm.currentOperation() == TransactionManager.OP_FINISHED;

             // the entry data bundle must be returned only if:
             // - this call is not made while TransactionManager is processing
             // finalizables
             // - no block which uses the accumulator has been executed prior
             // to this ACCUM function call
             // - no IF statement which uses the accumulator has been executed
             // prior to this ACCUM function call
             // - no accumulation has been performed in the current block
             // - the direct parent block performed had no ACCUM statement or
             // BLOCK with accum statement execution
             if (!isFinalizables &&
                 !blockExecutedStack.peek() &&
                 !accumExecutedStack.peek() &&
                 !ifExecutedStack.peek()    &&
                 !parentBlockExecuted)
             {
                data = entryStack.peek();
             }
             else
             {
                data = bundleStack.peek().get(breakGroupKey);
             }

             // override case 1
             if (!ifStack.peek())
             {
                // use the override cases for IF statement, only when an IF
                // statement is not currently being executed
                switch (postponeStack.peek())
                {
                   case POSTPONE_NONE:
                      // do nothing
                      break;
                   case POSTPONE_RESET:
                      // return the currently computed value
                      data = bundleStack.peek().get(breakGroupKey);
                      break;
                   case POSTPONE_UNKNOWN:
                      // instantiate an unknown value
                      data = createDataBundle();
                      break;
                }
             }

             // override case 2: if a child block has been executed prior to this
             // accum function call, use the fixedDataStack to retrieve the value
             if (blockExecutedStack.peek() && !topLevelStack.peek())
             {
                data = fixedDataStack.peek();
             }
          }
          else
          {
             Boolean reset = pendingResetStack.peek().get(breakGroupKey);
             if (reset == null)
             {
                reset = Boolean.FALSE;
             }
             if (reset)
             {
                bundleStack.peek().get(breakGroupKey).reset();
                pendingResetStack.peek().put(breakGroupKey, Boolean.FALSE);
             }
             data = bundleStack.peek().get(breakGroupKey);
          }

          return data.calculate();
       }

       /**
        * Create and return a new instance of an implementation-specific data
        * bundle.
        *
        * @return  New data bundle instance.
        */
       protected abstract DataBundle createDataBundle();

       /**
        * Apply a piece of accumulated data to the appropriate data bundle,
        * resetting the bundle first if we are entering a new break group.
        *
        * @param   key
        *          Data bundle key.
        * @param   newGroup
        *          <code>true</code> to indicate that the current loop iteration
        *          marks entry into a new break group;  else <code>false</code>.
        * @param   datum
        *          The data item to be accumulated.
        */
       private void accumulate(Resolvable key,
                               boolean newGroup,
                               BaseDataType datum)
       {
          // all accumulators are marked as 'changed' as soon as they accumulate something...
          checkUndoable(true);

          if (key == null)
          {
             // for sub-only accumulators, if there are break-by keys, then we
             // do not update the primary data bundle
             if (subOnlyStack.peek() && this.bundleStack.peek().size() > 1)
             {
                return;
             }

             // keep track of the ACCUM statement execution for this block
             accumExecutedStack.pop();
             accumExecutedStack.push(Boolean.TRUE);

             // iterate trough all blocks and accumulate this data to the
             // pending bundle for each block
             Iterator<DataBundle> itr = pendingStack.iterator();
             Iterator<Boolean> itrBlock = topLevelStack.iterator();
             while (itr.hasNext())
             {
                DataBundle bundle = itr.next();
                bundle.accumulate(datum);

                // do not accumulate past the first found top level block; this is
                // needed to protect the accumulator from recursion
                boolean isTopLevel = itrBlock.next();
                if (isTopLevel)
                {
                   break;
                }
             }

             // once an UNKNOWN is postponed, the unknown value will always be
             // reported; else, remove the postpone information
             if (postponeStack.peek() != POSTPONE_UNKNOWN)
             {
                postponeStack.pop();
                postponeStack.push(POSTPONE_NONE);
             }

             // for this block, accumulate this to the primary data bundle
             DataBundle bundle = bundleStack.peek().get(null);
             bundle.accumulate(datum);
          }
          else
          {
             DataBundle bundle = bundleStack.peek().get(key);

             if (newGroup)
             {
                bundle.reset();
             }

             bundle.accumulate(datum);
          }
       }

       /**
        * Internal method to save the postponed action type, when an IF statement
        * which encloses an accumulation statement is started.
        *
        * @param    what
        *           The type of postponed action.
        */
       private void postpone(int what)
       {
          postponeStack.push(what);
          ifStack.pop();
          ifStack.push(Boolean.TRUE);
          ifExecutedStack.pop();
          ifExecutedStack.push(Boolean.TRUE);
       }

       /**
        * Check if this instance is undoable.
        *
        * @return   <code>true</code>, as the accumulators are always undoable.
        */
       @Override
       protected boolean isUndoable()
       {
          return true;
       }

       /**
        * This interface must be implemented by concrete subclasses.  An instance
        * of this interface is returned from {@link Accumulator#createDataBundle}
        * whenever an accumulator is first created (the primary bundle), and
        * thereafter, whenever a break group is added to the accumulator (a break
        * group bundle).
        * <p>
        * A data bundle implementation manages the implementation specific data
        * required to generate an accumulator's statistic.  It must be able to:
        * <ul>
        *   <li>incorporate a new piece of data into its calculation ({@link
        *       #accumulate});
        *   <li>reset its state when entering a new break group ({@link #reset});
        *       and
        *   <li>calculate a result ({@link #calculate}).
        * </ul>
        * <p>
        * The implementation need not be concerned with when to do these things;
        * the methods will be invoked by the enclosing class at the appropriate
        * times.
        */
       protected interface DataBundle
       extends Undoable
       {
          /**
           * Reset any data bundle state which is break group specific.  How this
           * impacts the accumulator is implementation specific.  This method is
           * invoked by the {@link #iterate} method whenever the iteration
           * transitions into a new break group.
           */
          public void reset();

          /**
           * Accumulate a piece of data for the current loop iteration.  How this
           * data is stored and/or manipulated is implementation specific.  This
           * method is invoked by the {@link #iterate} method on each loop
           * iteration.
           *
           * @param   datum
           *          Datum to be accumulated.
           */
          public void accumulate(BaseDataType datum);

          /**
           * Calculate the result, based on the data currently stored in the
           * bundle.  The meaning of the result and the algorithm is of course
           * implementation specific.
           *
           * @return  Calculated result.
           */
          public BaseDataType calculate();
       }

       /**
        * A data source implementation which expects to be fed its data from an
        * external source.
        */
       private static class ExternalDataSource
       implements Resolvable
       {
          /** Current data value */
          private BaseDataType datum = null;

          /** The type of this source. */
          private Class<? extends BaseDataType> type = null;

          /**
           * Default c'tor.
           */
          private ExternalDataSource()
          {
             // no-op
          }

          /**
           * This c'tor must provide the type of the values being accumulated.
           * In the case when no accumulation is performed, but the result needs
           * to be retrieved, this the only way to determine the type.
           *
           * @param   cls
           *          Data type of the value being accumulated.
           */
          private ExternalDataSource(Class<? extends BaseDataType> cls)
          {
             this.type = cls;
          }

          /**
           * Get the current data value.
           *
           * @return  Current datum.
           */
          public BaseDataType resolve()
          {
             return datum;
          }

          /**
           * Get the type of the current datum.
           *
           * @return  Datum type.
           */
          public Class<? extends BaseDataType> getType()
          {
             return datum != null ? datum.getClass() : type;
          }

          /**
           * Set the current data value, supplied by an external source.
           *
           * @param   datum
           *          Current data value.
           */
          void setDatum(BaseDataType datum)
          {
             this.datum = datum;
          }
       }
    }