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