# Rule.java

    /*
    ** Module   : Rule.java
    ** Abstract : Performs arbitrary actions based upon a conditional expression
    **
    ** Copyright (c) 2005-2022, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
    ** 001 ECF 20050221   @20102 Created initial version. Implementation of a
    **                           conditional rule with zero or more actions.
    ** 002 ECF 20050318   @20422 Added support for an "else" condition. Now
    **                           actions can be designated to run either upon
    **                           the rule's condition evaluating to true or to
    **                           false.
    ** 003 ECF 20050318   @20439 Store raw text of condition currently being
    **                           evaluated in symbol resolver. This allows
    **                           workers to access this information for
    **                           reporting purposes.
    ** 004 ECF 20050429   @20961 Allow actions to be either logical or
    **                           arithmetic expressions. This eliminates the
    **                           need to wrap a user function which returns a
    **                           numeric result inside a bogus logical
    **                           expression.
    ** 005 ECF 20050525   @21291 Changes to support the new expression engine
    **                           implementation. This code was significantly
    **                           simplified by using the universal Expression
    **                           object. Also added debug reporting.
    ** 006 GES 20050602   @21381 Changes to implement the RuleListElement
    **                           interface.
    ** 007 ECF 20050603   @21396 Added rule type to debug rule report.
    ** 008 GES 20050603   @21397 Added support for nested rules and converted
    **                           actions to be a simple case of a Rule object.
    ** 009 ECF 20050604   @21415 Create expressions using AstSymbolResolver's
    **                           createExpression method. Changed constructor
    **                           to remove AstSymbolResolver parameter, which
    **                           is no longer necessary.
    ** 010 GES 20050623   @21545 Added WHILE looping support and left the
    **                           current IF/ELSE construct intact.
    ** 011 ECF 20050726   @21831 Added break and continue support for while
    **                           loops. These constructs are implemented as
    **                           specialized actions which are added as any
    **                           other actions to a while rule. In something
    **                           of an abuse of exception handling, these
    **                           actions throw BreakException and
    **                           ContinueException, respectively, when
    **                           applied. The exceptions are caught within the
    **                           innermost while loop and the flow of control
    **                           is altered accordingly.
    ** 012 ECF 20060421   @25643 Added debug feature. apply() method sets rule
    **                           as the active rule in the pattern engine.
    **                           Refactored rule report.
    ** 013 ECF 20080730   @39266 Added cleanup() method. Provides a hook for
    **                           termination processing. Made break and
    **                           continue processing slightly more efficient.
    ** 014 GES 20090518   @42388 Import change.
    ** 015 ECF 20150715          Replace StringBuffer with StringBuilder.
    ** 016 HC  20160907          Implemented a simple tracing support for rules processing.
    ** 017 ECF 20171228          Improve performance by reducing context-local lookups.
    ** 018 CA  20221010          Avoid using iterators on ArrayList - instead, use a 'for' loop and get the
    **                           element by index, as iterators are more expensive in terms of memory and
    **                           performance.  Refs #6813
    */
    /*
    ** 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.pattern;

    import java.util.*;
    import com.goldencode.ast.*;
    import com.goldencode.expr.*;

    /**
     * A single rule which defines a list of zero of more actions and/or nested
     * rules to be performed if this rule's condition is met. Consists of a
     * conditional expression and an optional list of action expressions and/or
     * nested rules.
     * <p>
     * As the pattern recognition engine processes an AST, it applies rules
     * in a {@link RuleSet} to various nodes in the tree. A rule's condition
     * expression is executed to determine whether that rule's actions need to
     * be executed. A symbol resolver uses the data stored in an AST object to
     * supply the information needed by the condition expression during
     * expression. This data is exposed to user expressions via libraries
     * registered by {@link PatternWorker} implementations.
     * <p>
     * An expression used for the rule's condition may contain any combination
     * of variables and user functions, so long as the final operation of the
     * expression resolves to a boolean result. It is this boolean result which
     * determines whether any of the rule's actions/nested rules will be executed.
     * Actions and nested rules can be set to register on either a
     * <code>true</code> or <code>false</code> result from the condition
     * expression.
     * <p>
     * Action expressions will typically contain a user function to act as a
     * callback or hook to some processing, since variables and simple operators
     * cannot be made to perform any task beyond simple evaluation to a boolean
     * or numeric result. The value returned by an action is always ignored.
     * <p>
     * Rules are used in multiple ways. By embedding an action as a function in
     * a rule's condition expression, one can perform some desired functionality
     * unconditionally. This is in fact how a {@link RuleSet}'s initialization
     * and termination rules are typically used, though they are not limited to
     * this use.
     * <p>
     * By nesting rules within rules, one can build up an arbitrary set of
     * control flow to match specific application requirements.
     * <p>
     * Using the <code>loop</code> instance member, this rule can be converted
     * into a <code>while</code> loop instead of the default <code>if/else</code>
     * control structure.  Please see {@link #apply} for more details.
     *
     * @see     RuleSet
     * @see     AstSymbolResolver
     */
    public final class Rule
    implements RuleListElement
    {
       /** Logical expression which forms the condition of this rule */
       private Expression condition = null;

       /** List of actions/rules to trigger if rule's condition is met */
       private ArrayList<RuleListElement> trueActions = null;

       /** List of actions/rules to trigger if rule's condition is not met */
       private ArrayList<RuleListElement> falseActions = null;

       /** Type of rule from {@link RuleContainer} constants */
       private int type = RuleContainer.RULE_NONE;

       /** If <code>true</code> this rule acts as a WHILE instead as an IF. */
       private boolean loop = false;

       /** The file this rule is defined in, used for tracing. */
       private String file;

       /** The file line this rule is defined at, used for tracing. */
       private int line;

       /**
        * Constructor which accepts a resolver and an expression which represents
        * the condition of this rule.
        *
        * @param   infix
        *          A logical (boolean) expression, in infix notation, which
        *          defines the condition which will trigger this rule's action(s)
        *          to be executed.
        * @param   type
        *          Type of rule from {@link RuleContainer} constants.
        *
        * @see     #apply
        */
       public Rule(String infix, int type)
       {
          this(infix, type, null, 0);
       }

       /**
        * Constructor which accepts a resolver and an expression which represents
        * the condition of this rule.
        *
        * @param   infix
        *          A logical (boolean) expression, in infix notation, which
        *          defines the condition which will trigger this rule's action(s)
        *          to be executed.
        * @param   type
        *          Type of rule from {@link RuleContainer} constants.
        * @param   file
        *          The file this rule is defined in, used for tracing.
        * @param   line
        *          The file line this rule is defined at, used for tracing.
        * @see     #apply
        */
       public Rule(String infix, int type, String file, int line)
       {
          condition = AstSymbolResolver.createExpression(infix);
          this.type = type;
          this.file = file;
          this.line = line;
       }

       /**
        * Sets the state variable for whether this rule acts as a WHILE instead
        * of as an IF.
        *
        * @param    loop
        *           If <code>true</code>, this is a WHILE loop.
        */
       public void setLoop(boolean loop)
       {
          this.loop = loop;
       }

       /**
        * Gets the state variable for whether this rule acts as a WHILE instead
        * of as an IF.
        *
        * @return   If <code>true</code>, this is a WHILE loop.
        */
       public boolean getLoop()
       {
          return loop;
       }

       /**
        * Add an action to be executed in the event this rule's condition
        * evaluates to <code>trigger</code>. This action is added to the end of
        * the list of such actions to be performed, and it will be executed in the
        * order of that list. The list is lazily created.
        *
        * @param   infix
        *          An expression, in infix notation, which defines an action to
        *          execute if the specified condition evaluates to the value
        *          specified by <code>trigger</code>. This typically consists
        *          of one or more user functions.
        * @param   trigger
        *          The boolean result of evaluating this rule's condition which
        *          should trigger this action. That is, if <code>true</code>, the
        *          action will be triggered only if the condition evaluates to
        *          <code>true</code>. If <code>false</code>, the action will be
        *          triggered only if the condition evaluates to
        *          <code>false</code>.
        * @param   file
        *          The file the action is defined in, used for tracing.
        * @param   line
        *          The file line the action is defined at, used for tracing.
        *
        * @see     #apply
        */
       public void addAction(String infix, boolean trigger, String file, int line)
       {
          Rule action = new Rule(infix, type, file, line);

          // Store the expression in the correct trigger list.
          if (trigger)
          {
             if (trueActions == null)
             {
                trueActions = new ArrayList<RuleListElement>(1);
             }
             trueActions.add(action);
          }
          else
          {
             if (falseActions == null)
             {
                falseActions = new ArrayList<RuleListElement>(1);
             }
             falseActions.add(action);
          }
       }

       /**
        * Add a nested <code>Rule</code> to be executed in the event this rule's
        * condition evaluates to <code>trigger</code>. This rule is added to the
        * end of the list of such actions/rules to be performed, and it will be
        * executed in the order of that list. The list is lazily created.
        *
        * @param   rule
        *          The rule to execute if the specified condition evaluates to
        *          the value specified by <code>trigger</code>.
        * @param   trigger
        *          The boolean result of evaluating this rule's condition which
        *          should trigger this action. That is, if <code>true</code>, the
        *          rule will be triggered only if the condition evaluates to
        *          <code>true</code>. If <code>false</code>, the rule will be
        *          triggered only if the condition evaluates to
        *          <code>false</code>.
        *
        * @see     #apply
        */
       public void addRule(RuleListElement rule, boolean trigger)
       {
          // Store the expression in the correct trigger list.
          if (trigger)
          {
             if (trueActions == null)
             {
                trueActions = new ArrayList<RuleListElement>(1);
             }
             trueActions.add(rule);
          }
          else
          {
             if (falseActions == null)
             {
                falseActions = new ArrayList<RuleListElement>(1);
             }
             falseActions.add(rule);
          }
       }

       /**
        * Add a break action to be executed in the event this rule's condition
        * evaluates to <code>trigger</code>. This action is added to the end of
        * the list of such actions to be performed, and it will be executed in the
        * order of that list. The list is lazily created.
        * <p>
        * Upon execution of the break action, a {@link Rule.BreakException} is
        * thrown.
        *
        * @param   trigger
        *          The boolean result of evaluating this rule's condition which
        *          should trigger this action. That is, if <code>true</code>, the
        *          action will be triggered only if the condition evaluates to
        *          <code>true</code>. If <code>false</code>, the action will be
        *          triggered only if the condition evaluates to
        *          <code>false</code>.
        *
        * @see     #apply
        */
       public void addBreak(boolean trigger)
       {
          ExceptionAction action = new ExceptionAction()
          {
             public void apply(AstSymbolResolver resolver, int type)
             {
                throw BreakException.SINGLETON;
             }
          };

          addRule(action, trigger);
       }

       /**
        * Add a continue action to be executed in the event this rule's condition
        * evaluates to <code>trigger</code>. This action is added to the end of
        * the list of such actions to be performed, and it will be executed in the
        * order of that list. The list is lazily created.
        * <p>
        * Upon execution of the break action, a {@link Rule.ContinueException} is
        * thrown.
        *
        * @param   trigger
        *          The boolean result of evaluating this rule's condition which
        *          should trigger this action. That is, if <code>true</code>, the
        *          action will be triggered only if the condition evaluates to
        *          <code>true</code>. If <code>false</code>, the action will be
        *          triggered only if the condition evaluates to
        *          <code>false</code>.
        *
        * @see     #apply
        */
       public void addContinue(boolean trigger)
       {
          ExceptionAction action = new ExceptionAction()
          {
             public void apply(AstSymbolResolver resolver, int type)
             {
                throw ContinueException.SINGLETON;
             }
          };

          addRule(action, trigger);
       }

       /**
        * Apply this rule (if and only if this rule's type matches the specified
        * type) by evaluating its condition expression and executing
        * the appropriate list of actions, based upon the result of evaluating
        * the rule's condition. That is, if the condition evaluates to
        * <code>true</code>, all "on true" actions defined for this rule are
        * executed, in the order in which they were added, regardless of the
        * success of any action previously executed in the list. If the condition
        * evaluates to <code>false</code>, all "on false" actions defined for
        * this rule are executed, in the order in which they were added,
        * regardless of the success of any action previously executed in the
        * list.
        * <p>
        * If the loop instance member is <code>true</code>, the core processing
        * will be executed multiple times as long as the return value of the
        * condition expression is not <code>null</code> and the return type
        * is a <code>Boolean</code>.  If the loop member is <code>false</code>,
        * the core processing is essentially an IF/ELSE construct.  It is
        * important to note that any <code>falseActions</code> WILL NOT EVER be
        * executed if <code>loop</code> is <code>true</code> since actions
        * are only processed in the WHILE loop when the condition is
        * <code>true</code>.
        * <p>
        * If trace level debugging is enabled, a rule report is emitted to
        * <code>stdout</code> for debug purposes. This includes the current
        * source and copy AST set in the symbol resolver, as well as details
        * about the condition expression and each action which is executed in
        * the course of applying this rule.
        *
        * @param   resolver
        *          AST symbol resolver.
        * @param   type
        *          Type of rule for which to apply: init, walk, or post, using
        *          the <code>RULE_*</code> constants.
        */
       public void apply(AstSymbolResolver resolver, int type)
       {
          // Immediately return if we do not represent a rule of the requested type
          if (this.type != type)
          {
             return;
          }

          resolver.setCurrentCondition(condition.getInfix());
          resolver.getPatternEngine().setActiveRule(this);

          boolean trace = (RuleContainer.getDebugLevel() >= DebugLevels.MSG_TRACE);

          if (trace)
          {
             System.out.println(createReportHeader());

             if (trueActions != null || falseActions != null)
             {
                System.out.println("Actions:");

                if (trueActions != null)
                {
                   System.out.println("  On True:");
                   Iterator<RuleListElement> iter = trueActions.iterator();
                   while (iter.hasNext())
                   {
                      RuleListElement next = iter.next();
                      if (next instanceof Rule)
                      {
                         Rule rule = (Rule) next;
                         System.out.println("      " + rule.condition.getInfix());
                      }
                   }
                }

                if (falseActions != null)
                {
                   System.out.println("  On False:");
                   Iterator<RuleListElement> iter = falseActions.iterator();
                   while (iter.hasNext())
                   {
                      RuleListElement next = iter.next();
                      if (next instanceof Rule)
                      {
                         Rule rule = (Rule) next;
                         System.out.println("      " + rule.condition.getInfix());
                      }
                   }
                }
             }
          }

          try
          {
             if (RulesTracing.isOn)
             {
                RulesTracing.pushLocation(file, line);
             }

             Object result = condition.execute();

             // are we a WHILE or an IF
             if (loop)
             {
                Boolean control = (Boolean) result;
                // execute multiple times (null == false and the return type
                // MUST be a boolean)
                while (control != null && control.booleanValue())
                {
                   try
                   {
                      coreProcessing(resolver, result);
                   }
                   catch (BreakException exc)
                   {
                      if (trace)
                      {
                         System.out.println("-- BREAK loop [" + condition.getInfix() + "] --");
                      }
                      break;
                   }
                   catch (ContinueException exc)
                   {
                      if (trace)
                      {
                         System.out.println("-- CONTINUE loop: [" + condition.getInfix() + "] --");
                      }
                      control = (Boolean) condition.execute();
                      continue;
                   }
                   control = (Boolean) condition.execute();
                }
             }
             else
             {
                // only execute once
                coreProcessing(resolver, result);
             }
          }
          finally
          {
             if (RulesTracing.isOn)
             {
                RulesTracing.popLocation();
             }
          }

          resolver.setCurrentCondition(null);

          if (trace)
          {
             System.out.println(createReportFooter());
          }
       }

       /**
        * Indicates whether this object either represents a rule of the specified
        * type.
        *
        * @param   type
        *          Type of rule for which to search: init, walk, or post, using
        *          the <code>RULE_*</code> constants.
        *
        * @return  <code>true</code> if the object is a rule of the specified
        *          type, else <code>false</code>.
        */
       public boolean hasType(int type)
       {
          return (this.type == type);
       }

       /**
        * Reports that this instance is NOT rule container.
        *
        * @return  Always <code>false</code>.
        */
       public boolean isContainer()
       {
          return false;
       }

       /**
        * Termination hook to allow resources to be cleaned up when this object is
        * about to go out of service.
        * <p>
        * Cascades to all <code>RuleListElement</code>s managed by this rule.
        */
       public void cleanup()
       {
          condition = null;

          if (trueActions != null)
          {
             int size = trueActions.size();
             RuleListElement elem;
             for (int i = 0; i < size; i++)
             {
                elem = trueActions.get(i);
                elem.cleanup();
             }

             trueActions = null;
          }

          if (falseActions != null)
          {
             int size = falseActions.size();
             RuleListElement elem;
             for (int i = 0; i < size; i++)
             {
                elem = falseActions.get(i);
                elem.cleanup();
             }

             falseActions = null;
          }
       }

       /**
        * Get the rule type; one of the {@link RuleContainer} constants.
        *
        * @return  Rule type.
        */
       int getType()
       {
          return type;
       }

       /**
        * Create a simple report which describes the current state of this rule.
        * Includes rule type, condition expression, whether it is a loop, and the
        * source and copy AST to which the rule currently is being applied.
        *
        * @return  Text report as described above.
        */
       String createReport()
       {
          StringBuilder buf = new StringBuilder();

          buf.append(createReportHeader());
          buf.append(createReportFooter());

          return buf.toString();
       }

       /**
        * Create a simple report header which describes the current state of this
        * rule.  Includes rule type, condition expression, whether it is a loop,
        * and the source and copy AST to which the rule currently is being
        * applied.
        *
        * @return  Text report header as described above.
        */
       private String createReportHeader()
       {
          AstSymbolResolver resolver = AstSymbolResolver.getResolver();
          Aast srcAst = resolver.getSourceAst();
          Aast copyAst = resolver.getCopyAst();

          String sep = System.getProperty("line.separator");
          StringBuilder buf = new StringBuilder();

          buf.append("-----------------------");
          buf.append(sep);
          buf.append("      RULE REPORT      ");
          buf.append(sep);
          buf.append("-----------------------");
          buf.append(sep);
          buf.append("Rule Type :   " + getRuleTypeName());
          buf.append(sep);
          buf.append("Source AST:  " + composeAstString(srcAst));
          buf.append(sep);
          buf.append("Copy AST  :  " + composeAstString(copyAst));
          buf.append(sep);
          buf.append("Condition :  " + condition.getInfix());
          buf.append(sep);
          buf.append("Loop      :  " + loop);
          buf.append(sep);

          return buf.toString();
       }

       /**
        * Create a standard footer which is appended to a report describing the
        * current state of this rule.
        *
        * @return  Static report footer.
        */
       private String createReportFooter()
       {
          String sep = System.getProperty("line.separator");
          StringBuilder buf = new StringBuilder();

          buf.append("--- END RULE REPORT ---");
          buf.append(sep);
          buf.append(sep);

          return buf.toString();
       }

       /**
        * Centralizes the core processing of this rule so that multiple types
        * of control flow construct can be implemented externally but the common
        * processing can be shared.
        *
        * @param    resolver
        *           AST symbol resolver.
        * @param    result
        *           The return value from the condition expression of this rule.
        */
       private void coreProcessing(AstSymbolResolver resolver, Object result)
       {
          if (result == null || result instanceof Boolean)
          {
             boolean rc = (result == null) ? false : ((Boolean) result).booleanValue();

             executeActions(resolver, rc ? trueActions : falseActions);
          }
          else if (trueActions != null || falseActions != null)
          {
             throw new IllegalArgumentException(
                "Condition must evaluate to true/false to trigger action(s): " +
                condition.getInfix());
          }
       }

       /**
        * Execute all actions/rules in the specified list, in order, discarding
        * results of the action expressions themselves.
        *
        * @param   resolver
        *          AST symbol resolver.
        * @param   actions
        *          The list of actions/rules to be executed, either
        *          {@link #trueActions} (if the rule's condition evaluated to
        *          <code>true</code>), or {@link #falseActions} (if the rule's
        *          condition evaluated to <code>false</code>).
        */
       private void executeActions(AstSymbolResolver resolver, ArrayList<RuleListElement> actions)
       {
          if (actions == null)
          {
             return;
          }

          int size = actions.size();
          RuleListElement action;
          for (int i = 0; i < size; i++)
          {
             action = actions.get(i);
             action.apply(resolver, type);
          }
       }

       /**
        * Compose and return a string which represents the contents of an AST.
        * This includes the AST's text, its token path from its root, its line
        * and column number and its ID.
        * <p>
        * This text is used in the rule report generated for debug purposes.
        *
        * @param   ast
        *          AST on which the string is based.
        *
        * @return  Text describing the AST.
        */
       private String composeAstString(Aast ast)
       {
          if (ast == null)
          {
             return null;
          }

          StringBuilder buf = new StringBuilder();
          buf.append("[ ")
             .append(ast.getText())
             .append(" ] ")
             .append(ast.getPath())
             .append(" @")
             .append(ast.getLine())
             .append(":")
             .append(ast.getColumn())
             .append(" {")
             .append(ast.getId())
             .append("}");

          return buf.toString();
       }

       /**
        * Debug helper method to get the descriptive name of this rule's type.
        *
        * @return  Rule type name.
        */
       private String getRuleTypeName()
       {
          switch (type)
          {
             case RuleContainer.RULE_WALK:
                return "WALK";
             case RuleContainer.RULE_INIT:
                return "INIT";
             case RuleContainer.RULE_POST:
                return "POST";
             case RuleContainer.RULE_DESCENT:
                return "DESCENT";
             case RuleContainer.RULE_NEXT_CHILD:
                return "NEXT_CHILD";
             case RuleContainer.RULE_ASCENT:
                return "ASCENT";
             case RuleContainer.RULE_NONE:
                return "NONE";
          }

          return "UNKNOWN";
       }

       /**
        * Abstract base class for specialized action implementations which throw
        * an exception when applied. Provides a common base class adapter for
        * anonymous class implementations when adding break and continue actions
        * which terminate and continue while loops respectively. This is simply
        * a convenience adapter to implement some of the methods required by the
        * {@link RuleListElement} interface.
        */
       private abstract class ExceptionAction
       implements RuleListElement
       {
          /**
           * Returns whether the enclosing rule's type matches the specified
           * type.
           *
           * @return  <code>true</code> if the enclosing rule's type ==
           *          <code>type</code>, else <code>false</code>.
           */
          public boolean hasType(int type)
          {
             return (Rule.this.type == type);
          }

          /**
           * Returns false.
           *
           * @return  <code>false</code>.
           */
          public boolean isContainer()
          {
             return false;
          }

          /**
           * Termination hook to allow resources to be cleaned up when this object
           * is about to go out of service.
           */
          public void cleanup()
          {
          }
       }

       /**
        * Specialized exception type thrown by a break action to terminate a
        * loop within a while rule.
        */
       private static class BreakException
       extends RuntimeException
       {
          /** Exception that is re-used for every break rule */
          private static final BreakException SINGLETON = new BreakException();

          /**
           * Default constructor.
           */
          BreakException()
          {
             super();
          }
       }

       /**
        * Specialized exception type thrown by a continue action to continue a
        * loop within a while rule.
        */
       private static class ContinueException
       extends RuntimeException
       {
          /** Exception that is re-used for every continue rule */
          private static final ContinueException SINGLETON = new ContinueException();

          /**
           * Default constructor.
           */
          ContinueException()
          {
             super();
          }
       }
    }