# StopAfterTimer.java

    /*
    ** Module   : StopAfterTimer.java
    ** Abstract : Class responsible for stopping of a Progress block after timeout
    **            expiration.
    **
    ** Copyright (c) 2008-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
    ** 001 ES  20240513          Created initial version.
    ** 002 ES  20240711          Reseting previousTaskBlock in a delete finalizable method.
    */

    /*
    ** 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 java.util.concurrent.Semaphore;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.logging.Level;

    import com.goldencode.p2j.net.Control;
    import com.goldencode.p2j.net.Session;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.security.SecurityManager;
    import com.goldencode.p2j.ui.LogicalTerminal;
    import com.goldencode.p2j.util.logging.CentralLogger;

    /**
     * Provides runtime support for stop-after statement for blocks
     * doBlock, forBlock, repeatBlock.
     */
    public class StopAfterTimer
    {
        /** Logger */
        private static final CentralLogger LOG = CentralLogger.get(StopAfterTimer.class.getName());

        /** Stores the interruption task for each block. */
        private Map<BlockDefinition, Task> tasks = new HashMap<>();

        /** Session for the current execution context */
        private Control control;

        /** Timer responsible of execution a interruption tasks. */
        private Timer timer;

        /** Previous task created for the last block */
        private Task previousTaskBlock;

        /** Blocked stopped by stop-after statement */
        private BlockDefinition stopedBlock;

        /** StopCondition was raised by a stop-after timeout */
        private boolean stopConditionRaised;

        /** Name of the thread for which the timer is set */
        private String contextName;

        /** Semaphore used for avoid race condition between two consecutive interrupts */
        private Semaphore semaphore = new Semaphore(1);

        /** Lock used for locking cleanup methods */
        private final ReentrantLock lock = new ReentrantLock();

        /**
         * Creates a managing <code>StopAfterTimer</code> object for a given
         * session.
         *
         * @param    session
         *           The session of the current execution context.
         */
        public StopAfterTimer(String contextName, Control threadControl)
        {
           this.control = threadControl;
           this.contextName = contextName;
        }

        /**
         * Checks if the block that was given as an parameter was stopped
         * by a <code>StopCondition</code> raised by a stop-after timeout.
         *
         * @param    blockDefinition
         *           Progress block for which we check stop-after timeout.
         *
         * @return   true, if <code>blockDefinition</code> was stopped by an
         *           stop-after timeout, or false otherwise.
         */
        public boolean isStoppedBlock(BlockDefinition blockDefinition)
        {
           return blockDefinition == stopedBlock || tasks.get(blockDefinition) != null;
        }

        /**
         * Checks if the a <code>StopCondition</code> was raised by stop-after timeout.
         */
        public boolean isStopAfterConditionRaised()
        {
           return stopConditionRaised;
        }

        /**
         * Checks if the block that was given as an parameter is already
         * present in the timer task
         *
         * @param    blockDefinition
         *           Progress block.
         *
         * @return   true, if <code>blockDefinition</code> is already present in
         *           timer task queue, or false otherwise.
         */
        public boolean isBlockRegistered(BlockDefinition blockDefinition)
        {
           return tasks.get(blockDefinition) != null;
        }

        /**
         * Release of the acquired semaphore and resets <code>stopConditionRaised</code>.
         */
        public void cleanUp()
        {
            stopConditionRaised = false;
            semaphore.release();
        }

        /**
         * Adds a new timer task to the <code>timer</code> queue.
         *
         * @param    blockDefinition
         *           Block for which the timeout wants to be set.
         * @param    timeout
         *           Number of seconds to be set as a timeout for
         *           <code>blockDefinition</code>.
         *
         * @returns  Timer task created for Progress task send as parameter.
         *
         * @throws   TimeOutException if timeout is equals to zero.
         */
        public Task addTimeOut(BlockDefinition blockDefinition, long timeout)
        {
           if (timer == null)
           {
              timer = new Timer("Timer-" + contextName, true);
           }

           Task newTask = new Task(blockDefinition, timeout);
           tasks.put(blockDefinition, newTask);
           newTask.prevTask = previousTaskBlock;

           if (previousTaskBlock != null)
           {
              previousTaskBlock.nextTask = newTask;
           }
           previousTaskBlock = newTask;

           timer.schedule(newTask, timeout * 1000);

           return newTask;
        }

        /**
         * Task class responsible for interruption of the Progress block.
         */
        class Task extends TimerTask implements Finalizable
        {
           /** Block definition related to timeout task */
           private BlockDefinition blockDefinition;

           /** Task related to the the previous stop-after block on the stack. */
           private Task prevTask;

           /** Task related to the the next stop-after block on the stack. */
           private Task nextTask;

           /** Number of seconds for block timeout */
           private long timeout;

           /**
            * Constructor.
            *
            * @param blockDefinition
            *        Progress block for which timeout is set.
            *
            * @param timeout
            *        Number of seconds which will be set as a timeout.
            *
            */
           public Task(BlockDefinition blockDefinition, long timeout)
           {
              this.timeout = timeout;
              this.blockDefinition = blockDefinition;
           }

           /**
            * Copy constructor.
            *
            * @param existingTask
            *        Canceled task from which a new timer task will be created.
            */
           public Task(Task existingTask)
           {
              this.blockDefinition = existingTask.blockDefinition;
              this.prevTask = existingTask.prevTask;
              this.nextTask = existingTask.nextTask;
              this.timeout = existingTask.timeout;
           }

           /**
            * Method responsible for stopping the thread corresponding
            * to the <code>session</code> and subsequent cleanup of the
            * children task's.
            *
            */
            @Override
           public void run()
           {
              try
              {
                 semaphore.acquire();
              }
              catch (InterruptedException e)
              {
                 LOG.log(Level.WARNING,
                         "Intrerrupt exception while tring to aquire sempahore in StopAfter timer");
              }
              lock.lock();
              Task iter = nextTask;

              while (iter != null)
              {
                 iter.cancel();
                 tasks.remove(iter.blockDefinition);

                 iter = iter.nextTask;
              }

              timer.purge();
              if (prevTask != null)
              {
                 prevTask.nextTask = null;
              }

              stopedBlock = blockDefinition;
              stopConditionRaised = true;

              try
              {
                 SecurityManager.stopAfterInterrupt(control);
              }
              catch (RestrictedUseException e)
              {
                 LOG.log(Level.WARNING, "Restriced use of Control.setControl()", e);
              }

              lock.unlock();
           }

           /**
            * Called by the transaction manager just before a block closes.
            * This is our hook to update the state of semaphore and timer task related
            * to current block.
            */
           @Override
           public void finished()
           {
              lock.lock();

              this.cancel();
              timer.purge();
              tasks.remove(blockDefinition);
              lock.unlock();
           }

           /**
            * Provides a notification that the external program scope in which the object is registered is
            * is being deleted and the object's reference will be lost after this method is called.
            */
           @Override
           public void deleted()
           {
              lock.lock();
              if (tasks.size() == 0)
              {
                 previousTaskBlock = null;
                 timer.cancel();
                 timer = null;
              }

              lock.unlock();
           }

           /**
            * Called by the transaction manager after each iteration of a repeating
            * block. Cancel the timer task for the previous iteration. Relinquishes the semaphore
            * and starts a new timer task for next iteration.
            */
           @Override
           public void iterate()
           {
              lock.lock();

              this.cancel();
              timer.purge();
              Task task = new Task(this);
              tasks.put(blockDefinition, task);
              timer.schedule(task, timeout * 1000);
              lock.unlock();
           }

           /**
            * Called by the transaction manager before a block is retried after an
            * error. Cancel the timer task for the previous iteration. Relinquishes the semaphore
            * and starts a new timer task for next iteration.
            */
           @Override
           public void retry()
           {
              lock.lock();

              this.cancel();
              timer.purge();
              Task task = new Task(this);
              tasks.put(blockDefinition, task);
              timer.schedule(task, timeout * 1000);
              lock.unlock();
           }
        }
    }