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