AccumulatorManager.java
/*
** Module : AccumulatorManager.java
** Abstract : provides block-behavior for the accumulators.
**
** Copyright (c) 2008-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- -T- --JPRM-- ----------------Description-----------------
** 001 CA 20080129 @37024 Created initial version. Provides context
** local scoped accumulators.
** 002 CA 20080214 @37130 The set for each block must contain all
** accumulator instances which can be accesed
** from that block. Removed master set. Now,
** when a new scope is started, the set for the
** new scope is added all known accumulators
** from enclosing scope.
** 003 GES 20080625 @38956 Moved to new BlockType enum.
** 004 ECF 20080630 @39025 Fixed memory leak. Some Accumulators were
** being added 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 during scopeStart(), creating a
** CPU drain.
** 005 ECF 20080715 @39126 Fixed NPE. Created default WorkArea ctor
** which initializes deque and pushes the first
** entry.
** 006 GES 20080917 @39849 Minor class name change.
** 007 CA 20131013 Added no-op scopeDeleted() method, required by the changes in
** Scopeable interface.
** 008 CA 20160728 Cleaned up, all accums register with the next scope.
** 009 CA 20190326 Implemented TransactionHelper instead of direct usage of the static
** API. This allows the elimination of context local usage in TM.
** 010 CA 20200930 Refactored to remove the singleton scopeable instance (which forced the scope
** notifications to use context-local state).
** CA 20210310 Small performance improvement.
** 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.
** 011 CA 20231031 Added 'BlockDefinition' parameter to 'scopeStart'.
*/
/*
** 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 java.util.*;
import com.goldencode.p2j.security.ContextLocal;
/**
* Provides context-local scoped accumulators.
* <p>
* All references can be added to the top scope (closest enclosing scope).
* <p>
* Implements the <code>Scopeable</code> interface to allow this class to be
* automatically notified of all scope start and end occurances. This removes
* the need for client code to be instrumented to provide this service. The
* use of this class is now tied to the {@link TransactionManager} class. In
* particular, the singleton instance of this class is used in each context-
* local list of <code>Scopeable</code> objects as a dispatcher. This bit
* of nastiness is necessary since the Java Interface can't be implemented
* on a static basis but instead requires an instance method implementation.
* These instance methods then call the static counterparts which is where
* the content-local versions of the resource pools are accessed.
*
* @author CA
*/
public class AccumulatorManager
implements Scopeable
{
/** Pool of accums which is context-local. Initializes upon first use. */
private static ContextContainer context = new ContextContainer();
/** The container with the instance state. */
private final WorkArea wa = new WorkArea();
/**
* Get the {@link AccumulatorManager} context-local instance.
*
* @return See above.
*/
public static AccumulatorManager getInstance()
{
return context.get();
}
/**
* Adds an accumulator to the current set of accumulators.
*
* @param accum
* The accumulator
*/
public static void addAccumulator(Accumulator accum)
{
AccumulatorManager mgr = context.get();
WorkArea wa = mgr.wa;
wa.tm.registerPendingScopeable(mgr);
if (wa.pendingSet == null)
{
wa.initPending();
}
wa.pendingSet.add(accum);
}
/**
* Provides a notification that a new scope is about to be entered.
*
* @param block
* The explicit block definition which required this notification.
*/
@Override
public void scopeStart(BlockDefinition block)
{
// ignore editing blocks
if (block.type == BlockType.EDITING)
return;
Set<Accumulator> prevSet = wa.stack.peek();
Set<Accumulator> currentSet = wa.pendingSet;
// if no pending set exists, create one
if (currentSet == null)
{
wa.initPending();
currentSet = wa.pendingSet;
}
if (prevSet != null && !prevSet.isEmpty())
{
// roll up all accumulators from the previous scope to the new,
// current scope
currentSet.addAll(prevSet);
}
// make the pending set of accumulators the current scope's active set
wa.stack.push(currentSet);
// notify all registered accumulators a new scope is starting
Iterator<Accumulator> itr = currentSet.iterator();
while (itr.hasNext())
{
Accumulator a = itr.next();
a.scopeStart(block);
}
// clear the pending set for the next scope
wa.pendingSet = null;
}
/**
* Provides a notification that a scope is about to be exited.
*/
public void scopeFinished()
{
// ignore editing blocks
if (wa.tm.getBlockType() == BlockType.EDITING)
return;
Set<Accumulator> set = wa.stack.pop();
if (set != null)
{
Iterator<Accumulator> itr = set.iterator();
while (itr.hasNext())
{
Accumulator a = itr.next();
a.scopeFinished();
}
}
}
/**
* 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 {@link ScopeId#ACCUMULATOR_MANAGER}.
*/
@Override
public ScopeId getScopeId()
{
return ScopeId.ACCUMULATOR_MANAGER;
}
/**
* Stores global data relating to the state of the current context.
*/
private static class WorkArea
{
/** Helper to use the TM without any context local lookups. */
private final TransactionManager.TransactionHelper tm = TransactionManager.getTransactionHelper();
/** Set of Accumulators to be added when the next block scope opens */
private Set<Accumulator> pendingSet = null;
/** Stack of Accumulator sets for each open block scope */
private Deque<Set<Accumulator>> stack = new ArrayDeque<Set<Accumulator>>();
/**
* Default constructor.
*/
private WorkArea()
{
stack.push(new HashSet<Accumulator>(8));
}
/**
* Initialize the set of pending Accumulator instances.
*/
private void initPending()
{
pendingSet = new HashSet<Accumulator>(8);
}
}
/**
* Simple <code>ContextLocal</code> subclass that properly initializes the
* context-local reference to a new instance of a case-sensitive
* <code>ScopedSymbolDictionary</code> that has a global scope.
*/
private static class ContextContainer
extends ContextLocal<AccumulatorManager>
{
/**
* Initializes the dictionary, the first time it is requested within a
* new context.
*
* @return The newly instantiated dictionary.
*/
protected synchronized AccumulatorManager initialValue()
{
AccumulatorManager wa = new AccumulatorManager();
return wa;
}
}
}