TotalAccumulator.java
/*
** Module : TotalAccumulator.java
** Abstract : Accumulator implementation which provides numeric totals and
** subtotals
**
** Copyright (c) 2004-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20051227 @23783 Created initial version. Accumulates totals
** and subtotals of a numeric value within an
** iterating loop. Subtotal is available for the
** current break group only.
** 002 ECF 20060104 @23820 Consolidated constructors. Separate ctors
** which accepted a FieldReference and an
** Expression were replaced with a single ctor
** which accepts a Resolvable data source.
** Also ensure getFullResult and getSubResult
** return the correct data type.
** 003 ECF 20060111 @23866 Added constructors for primitive types. These
** are needed to support accumulation of literal
** values.
** 004 ECF 20060118 @23987 Added support for break groups. Made mods to
** support new base class architecture.
** 005 ECF 20060503 @25953 Added support for accumulation using external
** data sources. Added features required by code
** converted from aggregators in display
** statements.
** 006 ECF 20060615 @27356 Initialize and reset data sources to unknown
** value. This is consistent with Progress.
** 007 ECF 20060621 @27509 Fixed regression in Data.calculate. Unknown
** value was not handled correctly in integer
** total case.
** 008 GES 20060830 @28984 Removed H006/H007 and moved back to a zero
** based approach. By default, if one has a
** CONDITIONAL accumulate stmt an accumulator
** is initialized to zero so that the accum
** function will return zero and not unknown
** even if the condition never occurs (so that
** the accumulate stmt is never called before
** the accum function is used to query the
** accumulated result). There must have been
** some case which H006 was designed to fix but
** at this time that case cannot be found AND
** it is certainly not correct for the default
** result. In the case that an accumulate does
** occur, the accumulated datum will determine
** the outcome (if unknown the result is
** unknown, otherwise the real value is
** accumulated). So at this time, the unknown
** value processing seems to be OK.
** 009 ECF 20070627 @34326 Ensure safe result is returned if no data has
** been accumulated. Added safety ctor. Reworked
** inner Data class. New implementation ensures
** proper default result (either unknown value
** or 0) is returned, depending upon how the
** accumulator has been initialized and used.
** 010 CA 20080213 @37133 Added Data.deepCopy and Data.assign method
** implementation. Added new constructor -
** TotalAccumulator(Resolvable, boolean) used
** by Accumulator.deepCopy() to instantiate a new
** accumulator of this type without registering
** to AccumulatorManager or TransactionManager.
** 011 CA 20080326 @37834 On instantiation, the accumulator is set to
** unknown; the accumulator will be reset only
** when a block which uses it is entered.
** 012 ECF 20080702 @39030 Refactored constructors. This was done to
** support conversion changes made to fix a
** memory leak in the framework.
** 013 GES 20080917 @39854 Minor class name change. Also changed the
** default scope level to NEXT instead of
** CURRENT.
** 014 CA 20081008 @40060 Modified the c'tor which receive the expr type
** as a parameter. The default c'tor was removed.
** 015 CA 20160721 Cleaned up the c'tors - all accumulators register in the next scope,
** as they are defined and created as instance fields.
** 016 CA 20220514 Accumulators can be explicitly assigned via their associated variable - added
** stubbed setters.
*/
/*
** 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;
/**
* A summation accumulator implementation which maintains a running, numeric
* total across all iterations of a loop, as well as across the current break
* group. Results are retrieved using one of the <code>getResult</code>
* method variants.
*/
public final class TotalAccumulator
extends Accumulator
{
/** Relative weight of this accumulator type for sorting purposes */
static final int WEIGHT = BASE_WEIGHT + 1;
/**
* 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.
*/
public TotalAccumulator(Class<? extends BaseDataType> cls)
{
super(cls);
}
/**
* Create an instance which accumulates an integral constant upon each loop
* iteration. The accumulator is registered with the AccumulatorManager and
* the TransactionManager, in the next scope.
*
* @param value
* Constant which will be accumulated at each iteration.
*/
public TotalAccumulator(int value)
{
super(new integer(value));
}
/**
* Create an instance which accumulates an integral constant upon each loop
* iteration. The accumulator is registered with the AccumulatorManager and
* the TransactionManager, in the next scope.
*
* @param value
* Constant which will be accumulated at each iteration.
*/
public TotalAccumulator(double value)
{
super(new decimal(value));
}
/**
* 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.
* The accumulator is registered with the AccumulatorManager and the
* TransactionManager, in the next scope.
*
* @param variable
* Mutable variable from which data is collected.
*/
public TotalAccumulator(BaseDataType variable)
{
super(variable);
}
/**
* 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.
* <p>
* The accumulator will be automatically registered at the NEXT scope that
* is opened. This is perfect for use in class members that are
* initialized in an initializer which occurs before the scope is open.
*
* @param dataSource
* Data source from which data is collected.
*/
public TotalAccumulator(Resolvable dataSource)
{
this(dataSource, true);
}
/**
* 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. The accumulator is
* registered with the AccumulatorManager and the TransactionManager, in the next scope.
*
* @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.
*/
public TotalAccumulator(Resolvable dataSource, boolean register)
{
super(dataSource, register);
}
/**
* Get the full result of all accumulations so far for this accumulator,
* across all break groups.
*
* @return Current full result.
*/
public BaseDataType getFullResult()
{
return getResult(null);
}
/**
* 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 BaseDataType getSubResult()
{
Resolvable key = getFirstBreakGroupKey();
if (key == null)
{
return null;
}
return getResult(key);
}
/**
* Get the label associated with this accumulator type, for use in the UI.
*
* @return Label associated with this accumulator type.
*/
public String getLabel()
{
return "TOTAL";
}
/**
* 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 int getWeight()
{
return WEIGHT;
}
/**
* Assign this accumulator's value.
*
* @param v
* The new value.
*/
public void setResult(long v)
{
UnimplementedFeature.missing("Accumulator assignment is not supported: TOTAL");
}
/**
* Assign this accumulator's value.
*
* @param v
* The new value.
*/
public void setResult(NumberType v)
{
UnimplementedFeature.missing("Accumulator assignment is not supported: TOTAL");
}
/**
* Get the numeric total of all instances of the accumulated data source
* across all iterations of the loop.
*
* @return Numeric total across full loop.
*/
public NumberType getResult()
{
return getResult(null);
}
/**
* Get the numeric subtotal of all instances of the accumulated data value
* across all iterations of the specified break group.
*
* @param breakGroupKey
* Key identifying the break group for which the data is desired.
*
* @return Numeric total across specified break group.
*/
public NumberType getResult(Resolvable breakGroupKey)
{
return (NumberType) calculate(breakGroupKey);
}
/**
* Create a data bundle which understands how to calculate a running total.
*
* @return An instance of {@link TotalAccumulator.Data}.
*/
protected DataBundle createDataBundle()
{
return (new Data());
}
/**
* A <code>DataBundle</code> implementation which calculates a running
* total, and returns it upon request.
*/
private class Data
implements DataBundle
{
/** Running total */
private decimal total = null;
/**
* Construct an instance of this class, setting the starting total to
* either unknown value or 0, depending upon whether the enclosing
* accumulator is initialized for use.
*/
private Data()
{
total = new decimal();
}
/**
* Reset (to zero) the total variable, in preparation for beginning a
* new break group.
*/
public void reset()
{
total = new decimal(0);
}
/**
* Add the numeric value of <code>datum</code> to the running total.
*
* @param datum
* Datum to be accumulated; it is expected that this is a
* numeric type. If this is <code>unknown</code> then the
* accumulated total will be <code>unknown</code>.
*
* @throws ClassCastException
* if <code>datum</code> cannot be cast to
* <code>NumberType</code>.
*/
public void accumulate(BaseDataType datum)
{
// if the datum is *ever* unknown, then the result will be unknown
// (until the next reset) even if subsequent datum values are not
// unknown; we get this behavior "for free" since the result of
// any addition where one or both operands are unknown, is the
// unknown value
total = MathOps.plus(total, (NumberType) datum);
}
/**
* Return the current, running total.
*
* @return Current total value (zero if no {@link #accumulate} ever
* occurred).
*/
public BaseDataType calculate()
{
if (getType() == integer.class)
{
// the expression type is integer not decimal so convert (this
// naturally handles unknown value so it is OK)
return new integer(total);
}
return total.duplicate();
}
/**
* 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()
{
Data copy = new Data();
copy.total = (decimal) this.total.deepCopy();
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)
{
Data copy = (Data) value;
this.total.assign(copy.total);
}
}
}