UnnamedStreams.java

/*
** Module   : UnnamedStreams.java
** Abstract : support for the unnamed in and unnamed out streams
**
** Copyright (c) 2006-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description----------------------------------
** 001 GES 20060207   @24331 Created initial version, supports unnamed
**                           input and unnamed output.
** 002 GES 20060215   @24678 New features to redirect the terminal and
**                           handle stacking of top level block defaults.
** 003 GES 20060227   @24773 Redirection clearing flow control change.
** 004 GES 20060323   @25208 Stream related processing can be called on
**                           the unnamed streams when they are not
**                           redirected.  These all need to be protected
**                           from null pointers by using a special "safe"
**                           getter that always returns a stream instance
**                           even if that instance is essentially bogus.
** 005 GES 20061009   @30223 Support for headless mode.
** 006 NVS 20070817   @34882 Quick return for no-op call of assignOut().
** 007 NVS 20070829   @34974 Added isOut() method that checks for the
**                           redirection status of the unnamed stream.
** 008 EVL 20071003   @35355 Fix to register callback to automatically
**                           close input or output stream for internal
**                           block as well as for external.
** 009 EVL 20071010   @35416 Additional check for in/out stream to be
**                           valid for the input/output method.
** 010 CA  20071121   @35920 Implemented the correct behavior for the
**                           situation when there is no explicit
**                           "output/input close" statement. Refactored
**                           the assignIn/Out methods to call
**                           assignIn/OutWorker(Stream,boolean).
** 011 ECF 20071213   @36378 Fixed #010 (@35920). New instance variables
**                           closerIn/closerOut were incorrectly added to
**                           ContextContainer inner class, when they were
**                           required in WorkArea. This was causing these
**                           stacks to be shared across contexts, which
**                           resulted in abnormal ends of client sessions.
** 012 CA  20080304   @37296 Mark the stream as unnamed when a new stream
**                           stream is opened, in assignOutWorker.
** 013 SIY 20080408   @37869 Do not close current stream if switching to
**                           terminal stream. Otherwise explicit opening of
**                           terminal stream closes previously opened
**                           non-terminal stream.
**                           Inferred generic type arguments.
**                           Ref: #37868
** 014 SVL 20080603   @38471 Fixed assignOutWorker() for the case when the
**                           output is redirected to a non-terminal then
**                           to a terminal stream.
** 015 SVL 20090612   @42674 If a StopCondition has been raised into
**                           assignOutWorker(), we let the old stream to be
**                           closed in a usual way.
** 016 CA  20131013          Added no-op deleted() and scopeDeleted() methods, requried by the
**                           changes in Scopeable and Finalizable interfaces.
** 017 EVL 20131205          Adding support for batch mode error messages.
** 018 OM  20160128          Use TransactionManager._isHeadless() in initialize().
** 019 CA  20190326          Implemented TransactionHelper instead of direct usage of the static
**                           API.  This allows the elimination of context local usage in TM.
** 020 CA  20190707          Stub support for OUTPUT TO ... LOB-DIR.
** 021 CA  20200930          Refactored to remove the singleton scopeable instance (which forced the scope
**                           notifications to use context-local state).
**     CA  20201015          Replaced java.util.Stack with a non-synchronized custom implementation.
**     OM  20211006          Streams are closed on procedure return instead of TM's scope finished.
**     RFB 20220114          Clearing of the closer stacks is the shared responsibility of executeOnReturn
**                           and scopeFinished. Once something non-null is added to the closer stack, then
**                           it's the job of executeOnReturn to pop the stack, otherwise, it's on the
**                           scopeFinished. Ref #5880.
**     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.
**     AL2 20230613          Added procedure helper to the contextual state. Use it where possible.
** 022 CA  20230703          If an unnamed stream is explicitly closed, deregister its 'executeOnReturn' 
**                           cleaner.
** 023 CA  20230929          Javadoc fixes.
** 024 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 com.goldencode.util.Stack;
import com.goldencode.p2j.security.ContextLocal;
import com.goldencode.p2j.ui.*;

/**
 * Stores state and provides functional support for the unnamed in and unnamed out streams.
 */
public class UnnamedStreams
implements Scopeable
{
   /** Stores context-local state variables. */
   private static final ContextContainer work = new ContextContainer();
   
   /** The state for this instance. */
   private final WorkArea wa = new WorkArea();
   
   /**
    * Private constructor so external code cannot instantiate this class.
    */
   private UnnamedStreams()
   {
   }
   
   /**
    * Gets the stream representing the unnamed input stream.
    *
    * @return   The unnamed input stream or <code>null</code> if the unnamed
    *           input stream has not been explicitly redirected.
    */
   public static Stream input()
   {
      WorkArea wa = work.get().wa;
      Stream result = wa.in;

      if (result != null && result.isValid())
         return result;
      else
      {
         wa.in = null;
         return null;
      }
   }
   
   /**
    * Gets the stream representing the unnamed input stream or a safe stream
    * instance that can be used for operations that should effectively be
    * <code>NOP</code>.
    *
    * @return   The unnamed input stream to which input is redirected or
    *           a <code>NullStream</code> instance if the unnamed
    *           input stream has not been explicitly redirected.
    */
   public static Stream safeInput()
   {
      return makeSafe(work.get().wa.in);
   }
   
   /**
    * Assigns the stream representing the unnamed input stream. Closes any
    * unnamed input stream that is already open. 
    * Calls assignInWorker(Stream,true).
    *
    * @param    in
    *           The unnamed input stream to set or <code>null</code> if any
    *           current input stream redirection is to be cleared.
    */
   public static void assignIn(Stream in)
   {
      // Batch mode special processing.
      if (in == null)
      {
         // Trying to return input to terminal inside batch mode
         if (ErrorManager.isInBatchMode())
         {
            // We have to display message and stop the client execution
            ErrorManager.displayError(120, "There is no terminal", true);
            ErrorManager.stopBatchSession();
         }
      }

      assignInWorker(work.get().wa, in, true);
   }
      
   /**
    * Closes the stream representing the unnamed input stream.  This will
    * restore the default input stream for the current top level scope, which
    * may be <code>null</code> (if the current redirection when the scope
    * opened was <code>null</code>).
    */
   public static void closeIn()
   {
      closeInInt(work.get().wa);
   }
   
   /**
    * Gets the stream representing the unnamed output stream.
    *
    * @return   The unnamed output stream or <code>null</code> if the unnamed
    *           output stream has not been explicitly redirected.
    */
   public static Stream output()
   {
      WorkArea wa = work.get().wa;
      Stream result = wa.out;

      if (result != null && result.isValid())
         return result;
      else
      {
         wa.out = null;
         return null;
      }
   }
   
   /**
    * Gets the stream representing the unnamed output stream or a safe stream
    * instance that can be used for operations that should effectively be
    * <code>NOP</code>.
    *
    * @return   The unnamed output stream to which output is redirected or
    *           a <code>NullStream</code> instance if the unnamed
    *           output stream has not been explicitly redirected.
    */
   public static Stream safeOutput()
   {
      return makeSafe(work.get().wa.out);
   }
   
   /**
    * Assigns the stream representing the unnamed output stream. Closes any
    * stream that is already open. Calls assignOutWorker(Stream,true).
    *
    * @param    out
    *           The unnamed output stream to set or <code>null</code> if any
    *           current output stream redirection is to be cleared.
    * @param    lobDir
    *           The folder where to write any CLOB or BLOB fields.
    */
   public static void assignOut(Stream out, String lobDir)
   {
      // TODO: manager lobDir
      assignOut(out);
   }
   
   /**
    * Assigns the stream representing the unnamed output stream. Closes any
    * stream that is already open. Calls assignOutWorker(Stream,true).
    *
    * @param    out
    *           The unnamed output stream to set or <code>null</code> if any
    *           current output stream redirection is to be cleared.
    */
   public static void assignOut(Stream out)
   {
      // Batch mode special processing.
      if (out == null)
      {
         // Trying to return output to terminal inside batch mode
         if (ErrorManager.isInBatchMode())
         {
            // We have to display message and stop the client execution
            ErrorManager.displayError(120, "There is no terminal", true);
            ErrorManager.stopBatchSession();
         }
      }

      assignOutWorker(work.get().wa, out, true);
   }
   
   /**
    * Closes the stream representing the unnamed output stream.  This will
    * restore the default output stream for the current top level scope, which
    * may be <code>null</code> (if the current redirection when the scope
    * opened was <code>null</code>).
    */
   public static void closeOut()
   {
      closeOutInt(work.get().wa);
   }   
   
   /**
    * Assigns the streams representing both the unnamed output stream and the
    * unnamed input stream. Closes the input or output streams if either
    * are already open.
    *
    * @param    stream
    *           The unnamed output stream to set or <code>null</code> if any
    *           current output stream redirection is to be cleared.
    */
   public static void assignBoth(Stream stream)
   {
      assignIn(stream);
      assignOut(stream);
      
      work.get().wa.both = true;
   }   
   
   /**
    * Closes both the unnamed input stream and the unnamed output stream.
    */
   public static void closeBoth()
   {
      closeIn();
      closeOut();
      
      work.get().wa.both = false;
   }      

   /**
    * Provides a notification that a new scope is about to be entered. Used
    * to store the new default input and output streams for this scope.
    * 
    * @param    block
    *           The explicit block definition which required this notification.
    */
   @Override
   public void scopeStart(BlockDefinition block)
   {
      wa.lastIn.push(wa.in);
      wa.lastOut.push(wa.out);
      wa.closerIn.push(null);
      wa.closerOut.push(null);
   }
   
   /**
    * Provides a notification that a scope is about to be exited. Used to
    * clear the default input and output streams back to the previous scope's
    * versions, unless it is the responsibility of executeOnReturn.
    */
   public void scopeFinished()
   {
      wa.lastIn.pop();
      wa.lastOut.pop();

      if (wa.closerIn.peek() == null)
      {
         wa.closerIn.pop();
      }
      if (wa.closerOut.peek() == null)
      {
         wa.closerOut.pop();
      }
   }

   /**
    * Provides notification that the external procedure scope has been deleted.
    * <p>
    * This is a no-op for unnamed streams.
    */
   @Override
   public void scopeDeleted()
   {
      // no-op
   }
   
   /**
    * Get the {@link ScopeId} for the instance.
    * 
    * @return   {@link ScopeId#UNNAMED_STREAMS}.
    */
   @Override
   public ScopeId getScopeId()
   {
      return ScopeId.UNNAMED_STREAMS;
   }

   /**
    * State of the output side of the unnamed stream.
    *
    * @return   <code>true</code> if the output side of the stream is active.
    */
   public static boolean isOut()
   {
      return work.get().wa.out != null;
   }

   /**
    * Returns the given stream if it is not <code>null</code>, otherwise it
    * returns a safe stream instance that can be used for operations that
    * should effectively be a <code>NOP</code>.
    *
    * @param    safe
    *           The stream to check.
    *
    * @return   The unnamed input stream given or a <code>NullStream</code>
    *           instance if the given stream is <code>null</code>.
    */
   private static Stream makeSafe(Stream safe)
   {  
      // we aren't redirected
      if (safe == null)
      {
         WorkArea wa = work.get().wa;
         
         // setup the bogus stream the first time in
         if (wa.bogus == null)
         {
            wa.bogus = new NullStream();
         }
         
         safe = wa.bogus;
      }
      
      return safe;
   }
      
   /**
    * Assigns the stream representing the unnamed input stream. Closes any
    * unnamed input stream that is already open.
    *
    * @param    wa
    *           The context-local state.
    * @param    in
    *           The unnamed input stream to set or <code>null</code> if any
    *           current input stream redirection is to be cleared.
    * @param    register
    *           If <code>true</code> and the stream parameter is non-null,
    *           then a call to registerTopLevelFinalizable will be performed.
    */
   private static void assignInWorker(WorkArea wa, Stream in, boolean register)
   {
      wa.tm.registerTopLevelScopeable(work.get());

      // default input for this top level scope
      Stream def = wa.lastIn.peek();
      
      if (wa.in != null)
      {
         if (wa.in == def && in != null)
         {
            // don't close the default stream when we are switching to
            // another (non-terminal) stream
         }
         else
         {
            if (wa.both)
            {
               wa.in.closeIn();
            }
            else
            {
               wa.in.close();
            }
         }
      }
      
      wa.both = false;
      wa.in   = in;
      
      if (register && in != null)
      {
         // the InputCloser must be executed BEFORE the Stream is finalized this is required because the
         // RemoteStream.close will set the stream ID to -1 and so the UnnamedStreams.closeIn method will 
         // never be executed if there is no registered input closer, we register it
         if (wa.closerIn.peek() == null) 
         {
            InputCloser closer = new InputCloser(wa);
            wa.pm.executeOnReturn(closer, closer.weight());
            // add the closer to the stack
            wa.closerIn.pop();
            wa.closerIn.push(closer);
         }

         wa.pm.executeOnReturn(in::finished, in.weight());
      }
   }
   
   /**
    * Assigns the stream representing the unnamed output stream. Closes any
    * stream that is already open.
    *
    * @param    wa
    *           The context-local state.
    * @param    out
    *           The unnamed output stream to set or <code>null</code> if any
    *           current output stream redirection is to be cleared.
    * @param    register
    *           If <code>true</code> and the stream parameter is non-null,
    *           then a call to registerTopLevelFinalizable will be performed.
    */
   private static void assignOutWorker(WorkArea wa, Stream out, boolean register)
   {
      wa.tm.registerTopLevelScopeable(work.get());
      
      // save ids before we make any state changes
      int oldId = ((wa.out == null) ? -1 : ((RemoteStream) wa.out).getId()); 
      int newId = ((out    == null) ? -1 : ((RemoteStream) out).getId());
      
      if (out != null)
      {
         out.setUnnamed(true);
      }
      
      if (newId == oldId)
      {
         return;
      }
      
      // default output for this top level scope
      Stream def = wa.lastOut.peek();
      
      StopConditionException exc = null;
      
      // close the stream resources
      if (wa.out != null)
      {
         if (wa.out == def && out != null)
         {
            // don't close the default stream when we are switching to
            // another (non-terminal) stream
         }
         else if (wa.out == def && newId == -1 && register)
         {
            // don't close the default stream when we are switching to
            // terminal stream, but since we're switching do some cleanups
         }
         else
         {
            try
            {
               if (wa.both)
               {
                  wa.out.closeOut();
               }
               else
               {
                  wa.out.close();
               }
            }
            catch(StopConditionException e)
            {
               exc = e;
            }
         }
      }
      
      // we must notify the UI to redirect output/cleanup resources (must
      // be AFTER closing the stream as close() can cause page footers to
      // be output and thus the redirection must stay until that is done)
      LogicalTerminal.redirectOutput(oldId, newId);
      
      wa.both = false;
      wa.out  = out;
      
      if (register && out != null)
      {
         // the OutputCloser must be executed BEFORE the Stream is finalized
         // this is required because the RemoteStream.close will set the 
         // stream ID to -1 and so the UnnamedStreams.closeOut method will 
         // never be executed 
         // if there is no registered output closer, we register it
         if (wa.closerOut.peek() == null) 
         {
            OutputCloser closer = new OutputCloser(wa);
            wa.pm.executeOnReturn(closer, closer.weight());
            // add the closer to the stack
            wa.closerOut.pop();
            wa.closerOut.push(closer);
         }
         
         wa.pm.executeOnReturn(out::finished, out.weight());
      }
      
      if (exc != null)
      {
         throw exc;
      }
   }
   
   /**
    * Closes the stream representing the unnamed input stream.  This will
    * restore the default input stream for the current top level scope, which
    * may be <code>null</code> (if the current redirection when the scope
    * opened was <code>null</code>).
    * 
    * @param    wa
    *           The context-local state.
    */
   private static void closeInInt(WorkArea wa)
   {
      assignInWorker(wa, wa.lastIn.peek(), false);
      
      // we pop the closer from the stack
      if (!wa.closerIn.empty()) 
      {
         InputCloser closer = wa.closerIn.pop();
         if (closer != null)
         {
            wa.pm.deregisterExecuteOnReturn(closer, closer.weight());
         }
         wa.closerIn.push(null);
      }
   }

   /**
    * Closes the stream representing the unnamed output stream.  This will
    * restore the default output stream for the current top level scope, which
    * may be <code>null</code> (if the current redirection when the scope
    * opened was <code>null</code>).
    * 
    * @param    wa
    *           The context-local state.
    */
   private static void closeOutInt(WorkArea wa)
   {
      assignOutWorker(wa, wa.lastOut.peek(), false);
      
      // we pop the closer from the stack
      if (!wa.closerOut.empty()) 
      {
         OutputCloser closer = wa.closerOut.pop();;
         if (closer != null)
         {
            wa.pm.deregisterExecuteOnReturn(closer, closer.weight());
         }
         wa.closerOut.push(null);
      }
   }

   /** 
    * 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();
      
      /** Helper to use the PM without any context local lookups. */
      private final ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();
      
      /** Flags whether the input stream represents both destinations. */
      private boolean both = false;
      
      /** A fake stream for safe getters for this user's session. */
      private Stream bogus = null;
      
      /** The current unnamed input stream for this user's session. */
      private Stream in = null;
      
      /** The current unnamed output stream for this user's session. */
      private Stream out = null;
      
      /** Track the default unnamed input stream for the top level block. */
      private final Stack<Stream> lastIn = new Stack<>();
      
      /** Track the default unnamed output stream for the top level block. */
      private final Stack<Stream> lastOut = new Stack<>();
      
      /** Track the current InputCloser instance for this block. */
      public Stack<InputCloser> closerIn = new Stack<>();
      
      /** Track the current OutputCloser instance for this block. */
      public Stack<OutputCloser> closerOut = new Stack<>();
   }
   
   /**
    * Simple container that stores and returns a context-local instance of
    * the global work area.
    */
   private static class ContextContainer
   extends ContextLocal<UnnamedStreams>
   {
      /**
       * Initializes the work area, the first time it is requested within a
       * new context.
       * <p>
       * Initializes the closerIn and closerOut trackers, the first time it is
       * requested within a new context.
       *
       * @return   The newly instantiated work area.
       */
      protected UnnamedStreams initialValue()
      {
         UnnamedStreams instance = new UnnamedStreams();
         
         // make sure we always have an entry in the stacks (avoids empty
         // stack exceptions)
         instance.wa.lastIn.push(null);
         instance.wa.lastOut.push(null);
         
         // add a null reference to know there are no Input/OutputClosers
         // registered 
         instance.wa.closerIn.push(null);
         instance.wa.closerOut.push(null);
         
         return instance;
      }   
   }   
   
   /**
    * Finalizable class which will perform a closeIn call only if this class
    * is in the top of the closerIn stack - this way we make sure there are 
    * not possible two subsequent calls to the closeIn statement.
    */
   private static class InputCloser
   implements Finalizable,
              Runnable
   {
      /** The context-local state. */
      private final WorkArea wa;
      
      /**
       * Create a new instance and cache the context-local state.
       * 
       * @param    wa
       *           The context-local state.
       */
      public InputCloser(WorkArea wa)
      {
         this.wa = wa;
      }

      /**
       * Execute {@link #finished()}, required for {@link ProcedureManager$ProcedureHelper#executeOnReturn}.
       */
      @Override
      public void run()
      {
         finished();
      }
      
      /**
       * This method will be called when the current block will be popped out 
       * of scope and will close the stream if there is no explicit 
       * "input close" statement. 
       */
      public void finished()
      {
         // Determine if the internal close is to be executed,
         // and if cleanup is our reponsibility instead of scopeFinished
         InputCloser topItem = wa.closerIn.peek();
         if (topItem == this)
         {
            closeInInt(wa);
         }

         if (topItem != null)
         {
            wa.closerIn.pop();
         }
      }
      
      /**
       * 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.
       * <p>
       * This is a no-op for unnamed streams.
       */
      @Override
      public void deleted()
      {
         // no-op
      }
    
      /**
       * This method is unused.
       *
       * @see  com.goldencode.p2j.util.Finalizable#iterate()
       */
      public void iterate()
      { 
         // do nothing
      }

      /**
       * This method is unused.
       *
       * @see  com.goldencode.p2j.util.Finalizable#iterate()
       */
      public void retry()
      {
         // do nothing
      }
   }
   
   /**
    * Finalizable class which will perform a closeOut call only if this class
    * is in the top of the closerOut stack - this way we make sure there are 
    * not possible two subsequent calls to the closeOut statement.
    */
   private static class OutputCloser
   implements Finalizable,
              Runnable
   {
      /** The context-local state. */
      private final WorkArea wa;
      
      /**
       * Create a new instance and cache the context-local state.
       * 
       * @param    wa
       *           The context-local state.
       */
      public OutputCloser(WorkArea wa)
      {
         this.wa = wa;
      }

      /**
       * Execute {@link #finished()}, required for {@link ProcedureManager$ProcedureHelper#executeOnReturn}.
       */
      @Override
      public void run()
      {
         finished();
      }
      
      /**
       * This method will be called when the current block will be popped out 
       * of scope and will close the stream if there is no explicit 
       * "output close" statement. 
       */
      public void finished()
      {
         // Determine if the internal close is to be executed,
         // and if cleanup is our reponsibility instead of scopeFinished
         OutputCloser topItem = wa.closerOut.peek();
         if (topItem == this)
         {
            closeOutInt(wa);
         }

         if (topItem != null)
         {
            wa.closerOut.pop();
         }
      }
      
      /**
       * 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.
       * <p>
       * This is a no-op for unnamed streams.
       */
      @Override
      public void deleted()
      {
         // no-op
      }
      
      /**
       * This method is unused.
       *
       * @see  com.goldencode.p2j.util.Finalizable#iterate()
       */
      public void iterate()
      { 
         // do nothing
      }

      /**
       * This method is unused.
       *
       * @see  com.goldencode.p2j.util.Finalizable#iterate()
       */
      public void retry()
      {
         // do nothing
      }
   }
}