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