# StaleProcedureManager.java

    /*
    ** Module   : StaleProcedureManager.java
    ** Abstract : Manager which should handle the work with stale persistent procedures.
    **
    ** Copyright (c) 2020-2023, Golden Code Development Corporation.

    **
    ** -#- -I- --Date-- --------------------------------------Description-----------------------------------------
    ** 001 AIL 20200622 Created initial version.
    **         20200623 Made all methods static and now using the local context.
    **     CA  20201015 Replaced java.util.Stack with a non-synchronized custom implementation.
    ** 002 CA  20230713 Small optimization, use TM.getBlock() which returns the top of the stack, instead of
    **                  TM.getBlock(0).
    ** 003 AL2 20240322 Moved away from Stack.iterator as it doesn't return the global scope.
    */
    /*
    ** 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.util.Stack;
    import com.goldencode.p2j.security.*;

    /**
     * Enclosing several methods for working with stale referents.
     */
    public class StaleProcedureManager
    {

       /** The stale procedure managers are private per each context. */
       private static final ContextLocal<WorkArea> work = new ContextLocal<WorkArea>()
       {
          protected WorkArea initialValue()
          {
             return new WorkArea();
          };
       };

       /**
        * Used for marking the fact that a referent is in a stale state. It means that the referent was
        * deleted, but is still able to be deleted again.
        *
        * @param    referent
        *           The persistent procedure which needs to be marked as stale.
        *
        * @return   {@code true} if the marking was done for the first time.
        */
       public static boolean addStaleReferent(Object referent)
       {
          WorkArea wa = work.get();
          boolean result = wa.pm.addStaleReferent(referent);
          invalidateResource(wa, referent);
          return result;
       }

       /**
        * Invalidates the resource in terms of stale attributes. If the referent is neither active or stale,
        * this method should fail. Otherwise try to delete the referent right now or postpone it to a
        * suitable block.
        *
        * @param    referent
        *           The persistent procedure which should be invalidated.
        *
        * @return   <code>true</code> if the referent was invalidated, <code>false</code> if the referent was
        *           already deleted.
        */
       public static boolean invalidateResource(Object referent)
       {
          return invalidateResource(null, referent);
       }

       /**
        * Invalidates the resource in terms of stale attributes. If the referent is neither active or stale,
        * this method should fail. Otherwise try to delete the referent right now or postpone it to a
        * suitable block.
        *
        * @param    wa
        *           The {@link WorkArea} instance. If {@code null}, a context lookup will be triggered.
        * @param    referent
        *           The persistent procedure which should be invalidated.
        *
        * @return   <code>true</code> if the referent was invalidated, <code>false</code> if the referent was
        *           already deleted.
        */
       public static boolean invalidateResource(WorkArea wa, Object referent)
       {
          if (wa == null)
          {
             wa = work.get();
          }

          if (!wa.pm.hasReferent(referent) &&
              !wa.pm.hasStaleReferent(referent))
          {
             return false;
          }

          int handlingBlock = getHandlingBlock(wa, referent);

          if (canDeleteNow(wa, referent, handlingBlock))
          {
             notifyResourceDelete(wa, referent);
          }
          else
          {
             DeferredDeletablesManager.registerAt(handlingBlock, new ExternalProgramWrapper(referent));
          }

          return true;
       }

       /**
        * This should be called when a persistent procedure is deleted. This clears the contents
        * stored for this specific procedure.
        *
        * @param    referent
        *           The wrapper which contains the referent for which the clear should be done.
        */
       public static void notifyResourceDelete(Object referent)
       {
          notifyResourceDelete(null, referent);
       }

       /**
        * This should be called when a persistent procedure is deleted. This clears the contents
        * stored for this specific procedure.
        *
        * @param    wa
        *           The {@link WorkArea} instance. If {@code null}, a context lookup will be triggered.
        * @param    referent
        *           The wrapper which contains the referent for which the clear should be done.
        */
       public static void notifyResourceDelete(WorkArea wa, Object referent)
       {
          if (wa == null)
          {
             wa = work.get();
          }

          wa.pm.removeStaleProcedure(referent);
       }

       /**
        * Core method for obtaining the index of the block which is responsible with the deletion of the
        * resource. This method is context dependent, thus this should be called exactly when the delete
        * procedure statement is used in order to accurately detect the responsible block. This also can
        * return the index of the current block if the deletion is not meant to be delayed.
        *
        * This is the strategy which was used in order to get the responsible block. Consider the afferent
        * {@code persistentProcstack} of one procedure (the stack snapshot of the moment when the first
        * global static buffer was used) and the current block stack. The handling block here is considered
        * to be the first block which does not appear in both stacks. This means that when the common block
        * is reached, the referent shouldn't be stale - but deleted altogether.
        *
        * @param    wa
        *           The {@link WorkArea} instance. If {@code null}, a context lookup will be triggered.
        * @param    referent
        *           The resource which is used in order to detect if the deletion of it should be delayed
        *           or not.
        *
        * @return   The index of the block which is responsible for deleting the resource. 0 represents
        *           the global scope.
        */
       private static int getHandlingBlock(WorkArea wa, Object referent)
       {
          if (wa == null)
          {
             wa = work.get();
          }

          Stack<BlockDefinition> currentStack = wa.tm.copyStack();
          Stack<BlockDefinition> persistentProcStack = wa.pm.getPersistentProcStack(referent);

          if (persistentProcStack == null)
          {
             return currentStack.size();
          }

          int currentStackSize = currentStack.size();
          int persistentProcStackSize = persistentProcStack.size();

          for (int idx = 0; idx < currentStackSize && idx < persistentProcStackSize; idx++)
          {
             BlockDefinition currentBlock = currentStack.elementAt(idx);
             BlockDefinition persistentProcBlock = persistentProcStack.elementAt(idx);
             if (currentBlock != persistentProcBlock)
             {
                return idx;
             }
          }

          if (currentStackSize <= persistentProcStackSize)
          {
             return currentStackSize - 1;
          }

          return persistentProcStackSize;
       }

       /**
        * Check if the referent of the ExternalProcedureWrapper can be deleted now or it should be
        * delayed.
        *
        * @param    wa
        *           The {@link WorkArea} instance. If {@code null}, a context lookup will be triggered.
        * @param    referent
        *           The referent container which should be checked in order to detect if it need invalidation.
        * @param    handlingBlock
        *           The value of the block which is responsible for deleting this procedure. If {@code null},
        *           the value will be computed using the {@code getHandlingBlock}.
        *
        * @return   <code>true</code> if the referent can be deleted now or <code>false</code> if the deletion
        *           should be postponed
        */
       private static boolean canDeleteNow(WorkArea wa, Object referent, Integer handlingBlock)
       {
          if (wa == null)
          {
             wa = work.get();
          }

          Stack<BlockDefinition> persistentProcStack = wa.pm.getPersistentProcStack(referent);
          if (persistentProcStack == null)
          {
             return true;
          }

          if (handlingBlock == null)
          {
             handlingBlock = getHandlingBlock(wa, referent);
          }

          return handlingBlock == wa.tm.getNestingLevel() - 1 &&
                 persistentProcStack.contains(wa.tm.getBlock());
       }

       /**
        * Stores global data relating to the state of the current context.
        */
       private static class WorkArea
       implements Initializable
       {
          /** Helper to use the TM without any context local lookups. */
          private TransactionManager.TransactionHelper tm = null;

          /** Helper to use the ProcedureManager without any context local lookups. */
          private ProcedureManager.ProcedureHelper pm = null;

          /**
           * Initialize the required managers.
           */
          @Override
          public void init()
          {
             tm = TransactionManager.getTransactionHelper();
             pm = ProcedureManager.getProcedureHelper();
          }
       }
    }