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