AverageAccumulator.java
/*
** Module : AverageAccumulator.java
** Abstract : Accumulator implementation which provides numeric averages
**
** Copyright (c) 2004-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20051227 @23779 Created initial version. Accumulates totals
** and subtotals of a numeric value within an
** iterating loop, as well as iteration counts
** within each category. Calculates average and
** sub-average values. Sub-average is available
** for the current break group only.
** 002 ECF 20060104 @23814 Consolidated constructors. Separate ctors
** which accepted a FieldReference and an
** Expression were replaced with a single ctor
** which accepts a Resolvable data source.
** 003 ECF 20060111 @23862 Added constructors for primitive types. These
** are needed to support accumulation of literal
** values.
** 004 ECF 20060118 @23983 Added support for break groups. Made mods to
** support new base class architecture.
** 005 ECF 20060503 @25949 Added support for accumulation using external
** data sources. Added features required by code
** converted from aggregators in display
** statements.
** 006 ECF 20060615 @27355 Initialize and reset data sources to unknown
** value. This is consistent with Progress.
** 007 GES 20060830 @28982 Removed H006 and moved back to a zero based
** approach. The count variable is never
** accessed directly so there is no reason to
** use a wrapper. 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.
** 008 ECF 20070627 @34323 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.
** 009 CA 20080213 @37131 Added Data.deepCopy and Data.assign method
** implementation. Added new constructor -
** AverageAccumulator(Resolvable, boolean) used
** by Accumulator.deepCopy() to instantiate a new
** accumulator of this type without registering
** to AccumulatorManager or TransactionManager.
** 010 CA 20080326 @37832 On instantiation, the accumulator is set to
** unknown; the accumulator will be reset only
** when a block which uses it is entered.
** 011 ECF 20080702 @39026 Refactored constructors. This was done to
** support conversion changes made to fix a
** memory leak in the framework.
** 012 GES 20080917 @39850 Minor class name change. Also changed the
** default scope level to NEXT instead of
** CURRENT.
** 013 CA 20081008 @40057 Modified the c'tor which receive the expr type
** as a parameter. The default c'tor was removed.
** 014 CA 20160721 Cleaned up the c'tors - all accumulators register in the next scope,
** as they are defined and created as instance fields.
** 015 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;
/**
* An accumulator implementation which maintains a running, numeric total
* and iteration count across all iterations of a loop, as well as across the
* current break group. Calculates averages for full loop and for the
* current break group. Results are retrieved using one of the
* <code>getResult</code> method variants.
*/
public final class AverageAccumulator
extends Accumulator
{
/** Relative weight of this accumulator type for sorting purposes */
static final int WEIGHT = MinimumAccumulator.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 AverageAccumulator(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 AverageAccumulator(int value)
{
super(new integer(value));
}
/**
* Create an instance which accumulates an decimal 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 AverageAccumulator(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 AverageAccumulator(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 AverageAccumulator(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 AverageAccumulator(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 "AVG";
}
/**
* 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(double v)
{
UnimplementedFeature.missing("Accumulator assignment is not supported: AVERAGE");
}
/**
* Assign this accumulator's value.
*
* @param v
* The new value.
*/
public void setResult(NumberType v)
{
UnimplementedFeature.missing("Accumulator assignment is not supported: AVERAGE");
}
/**
* Get the numeric average of all instances of the accumulated data source
* across all iterations of the loop.
*
* @return Numeric average across full loop. Returns zero if no
* iterations have been performed.
*/
public decimal getResult()
{
return getResult(null);
}
/**
* Get the numeric average of all instances of the accumulated data source
* across all iterations of the specified break group.
*
* @param breakGroupKey
* Key identifying the break group for which the data is desired.
*
* @return Numeric average across specified break group. Returns zero if
* no iterations have been performed since the break group state
* was last reset.
*/
public decimal getResult(Resolvable breakGroupKey)
{
return (decimal) calculate(breakGroupKey);
}
/**
* Create a data bundle which understands how to calculate an average.
*
* @return An instance of {@link AverageAccumulator.Data}.
*/
protected DataBundle createDataBundle()
{
return (new Data());
}
/**
* A <code>DataBundle</code> implementation which keeps a running total of
* a numeric value and an iteration count, and provides the average value
* when requested.
*/
private class Data
implements DataBundle
{
/** Running total for accumulated value. */
private decimal total = null;
/** Iteration counter. */
private integer count = 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()
{
count = new integer();
total = new decimal();
}
/**
* Reset (to zero) the total and count variables, in preparation for
* beginning a new break group.
*/
public void reset()
{
total = new decimal(0);
count = new integer(0);
}
/**
* Add the numeric value of <code>datum</code> to the running total.
* Increment the count internal variable.
*
* @param datum
* Datum to be accumulated; it is expected that this is a
* numeric type. If <code>unknown</code> then the accumulated
* average value 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
count.increment();
total = MathOps.plus(total, (NumberType) datum);
}
/**
* Calculate the average, based on the current total and iteration
* count. If no iterations have occurred since creation (or last
* reset), return 0.
*
* @return Average value (zero if no {@link #accumulate} ever
* occurred).
*/
public BaseDataType calculate()
{
if (count.isUnknown())
{
return (new decimal());
}
if (count.intValue() == 0)
{
return (new decimal(0));
}
return MathOps.divide(total, count);
}
/**
* 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.count = (integer) this.count.deepCopy();
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.count.assign(copy.count);
this.total.assign(copy.total);
}
}
}