CommonAstSupport.java

/*
** Module   : CommonAstSupport.java
** Abstract : Common symbol resolution and function support for AST processing
**
** Copyright (c) 2005-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20050228   @20100 Created initial version. Exposes basic AST
**                           properties and provides AST-related actions,
**                           including editing and annotation processing.
** 002 GES 20050228   @20116 Added an eval() user function to evaluate
**                           a runtime-specified string as an expression.
** 003 ECF 20050301   @20126 Fixed defect in ancestor user function. Was
**                           testing current AST first instead of its
**                           parent.
** 004 ECF 20050303   @20157 Added matches and sibling methods to Basic
**                           support class. The former is for regex string
**                           matches, the latter is to test for sibling
**                           AST token type matches.
** 005 ECF 20050303   @20166 Call Aast.iterator method directly instead of
**                           calling through AstWalker.
** 006 ECF 20050304   @20171 Added sprintf, printf, and printfln methods
**                           to Action. These add C-style printf-like
**                           capability to the base user function support.
** 007 GES 20050308   @20262 Added new methods for relativePath, various
**                           sibling oriented ops, additional versions of
**                           matches and other basic operations.  Cleaned
**                           up some of the naming conventions and
**                           added/modified persistence actions.
** 008 ECF 20050310   @20267 Modified user function backing methods which
**                           accepted token names to accept token types
**                           instead. Token names are now resolved to
**                           literals during expression parsing.
** 009 GES 20050314   @20315 Added a path user variable that provides
**                           the absolute path (as a string) from the
**                           source AST to the root.
** 010 ECF 20050314   @20327 Added user functions to assign data to user
**                           variables.
** 011 GES 20050316   @20349 Added support for addParentToView().
** 012 GES 20050316   @20402 Fix bug in descendant() which incorrectly
**                           incremented the maxlevel variable to descend
**                           1 level too far.
** 013 GES 20050317   @20403 Added support for addAncestorToView().
** 014 GES 20050317   @20404 Added support for user-generated exceptions.
** 015 ECF 20050316   @20414 Change invocation of Aast.iterator method to
**                           support signature change in Aast interface.
** 016 GES 20050318   @20428 Added tree dump support for the current
**                           source node and for an arbitrary ancestor
**                           of that node.
** 017 GES 20050321   @20451 Added escape processing for the format
**                           string on sprintf() and friends. This is
**                           needed because normal escape processing
**                           is done by javac and thus the formatting
**                           code has no concept of whether the input
**                           string is a javac (already converted) or
**                           non-javac string. In our case, we always
**                           know that it is a string that needs
**                           conversion, so we manually convert it
**                           before calling the sprintf function.
** 018 GES 20050328   @20512 Added ancestor annotation support (get/put
**                           and remove).
** 019 ECF 20050328   @20517 Replace references to getTarget method with
**                           getCopy method. This reflects a change in the
**                           superclass' interface.
** 020 GES 20050401   @20604 Added isNote() and getNoteType() functions.
** 021 GES 20050401   @20612 Added getId() user variable for the node ID.
** 022 ECF 20050405   @20646 Added upper and lower methods. These methods
**                           respectively uppercase and lowercase their
**                           input text.
** 023 GES 20050405   @20648 Added support for adding an AST to the view
**                           based on its ID as long as it is in the
**                           copy AST.
** 024 GES 20050411   @20675 Added support for array annotations.
** 025 GES 20050411   @20680 Added downPath() method.
** 026 GES 20050414   @20716 Added annotation support for ASTs accessed
**                           via their unique ID ("refid" or reference
**                           ID).  This allows one AST which is a
**                           reference to another, to access, create,
**                           edit and delete annotations on the referenced
**                           node.
** 027 GES 20050427   @20910 Added substring, index and last index string
**                           functions.  Also added copies function.
** 028 ECF 20050428   @20930 Added parseLong and parseDouble user
**                           functions.
** 029 GES 20050430   @20945 Added stateful streams approach to writing
**                           file output, which also allows one to query
**                           the number of bytes written at any given
**                           point in time.  This new approach is more
**                           efficient than the stateless fprintf and it
**                           also has the advantage of allowing rules to
**                           detect if anything has been written at
**                           known points and thus process off of that
**                           knowledge.
** 030 GES 20050501   @20976 Added getNumImmediateChildren().
** 031 ECF 20050502   @20986 Changed implementation of eval and expr user
**                           function implementations to let the symbol
**                           resolver do the compilation and evaluation.
**                           Added a version of persist which accepts a
**                           terse flag.
** 032 ECF 20050526   @21301 Changes to support new expression engine
**                           implementation. Combined 3 worker library
**                           class definitions into 1 to accommodate the
**                           1 library per worker limit imposed by the
**                           requirement to name each registered library.
**                           Added accessors for "this" and "copy" which
**                           represent the current source and copy ASTs
**                           set in the resolver, respectively. As rule
**                           sets are migrated, we should be able to
**                           remove large portions of the inner Library
**                           class.
** 033 ECF 20050527   @21319 Added create() user function. Instantiates
**                           an arbitrary object using reflection and
**                           variable argument list support.
** 034 GES 20050601   @21348 Made evalLib/execLib and eval/exec methods
**                           for processing named or user-defined
**                           expressions respectively.  The only
**                           difference between eval and exec is that
**                           eval casts the return to a Boolean and
**                           exec returns an Object.  The ...Lib versions
**                           handle named expressions.  To use a named
**                           expression as an action, use the execLib
**                           form.  This replaces the old expr() with
**                           evalLib().
** 035 GES 20050601   @21360 Added user-defined parameter lists to named
**                           expressions and exposed these via the
**                           evalLib/execLib user functions.
** 036 ECF 20050602   @21395 Changes to parent and type properties.
**                           Changed the semantic of getParent() to return
**                           the parent reference, rather than its token
**                           type. This gives more flexibility in terms of
**                           querying the parent AST directly. Changed the
**                           return type of getType() from long to int.
** 037 ECF @0050603   @21420 Added convenience user functions to create
**                           and initialize lists, sets, and maps:
**                           - createList
**                           - createSet
**                           - createMap
** 038 GES 20050613   @21476 Added next child index support.
** 039 GES 20050623   @21547 Fixed getRightSiblings() which returned a
**                           value that was always 1 too large.
** 040 ECF 20050629   @21597 Added user methods wrapText and removeQuotes.
**                           The former wraps a text string into multiple
**                           lines of a maximum width; the latter strips
**                           enclosing double quotes, if any, from a
**                           string.
** 041 ECF 20050708   @21670 Added trimLeading and trimTrailing user
**                           functions.
** 042 ECF 20050721   @21773 Added variants of create and createList.
**                           Added user functions to support reflection
**                           use from expressions.
** 043 ECF 20050805   @22013 Added "now" property as getNow() in callback
**                           library. Returns System.currentTimeMillis().
** 044 SIY 20050819   @22168 Added accessors for the configuration
**                           variables.
** 045 ECF 20050819   @22174 Added endCurrentWalk() user function to end
**                           the current AST walk. No further nodes in the
**                           current AST are processed after this method
**                           is executed. Processing continues with the
**                           next AST loaded by the pattern engine, if
**                           any.
** 046 ECF 20050908   @22660 New variant of parseLong() user function.
**                           This variant accepts a radix as well as the
**                           string representation of a long value.
** 047 ECF 20051017   @23055 Added brainwash user function. Clears IDs of
**                           AST, reregisters AST under a new filename,
**                           fixes up parent-child relationships.
** 048 GES 20051113   @23319 Moved ancestor/descendant function to the
**                           Aast implementation classes.
** 049 ECF 20051122   @23534 Added variant of throwException that accepts
**                           an AST. Appends a formatted descriptor of AST
**                           to error message.
** 050 GES 20051129   @23547 Changed one of the createList() signatures
**                           to createListFromObject() to resolve function
**                           name resolution ambiguity.
** 051 GES 20060129   @24134 Added AST ID to the formatted descriptor of
**                           an AST during throwException.
** 052 GES 20060201   @24184 Create any needed parent dirs when opening
**                           streams.
** 053 ECF 20060215   @24608 Added getAncestor user functions. Find an
**                           ancestor with a given annotation, optionally
**                           checking annotation value and token type as
**                           well.
** 054 ECF 20060225   @24831 Added getAncestorOfType user function. Finds
**                           first ancestor whose type matches any of a
**                           list of token types.
** 055 GES 20060302   @24847 Added upPath() which is similar to
**                           relativePath() except the current node's
**                           type is not included in the path.
** 056 ECF 20060410   @25454 Disambiguate getAncestor() variants. Reorder
**                           parameters so that expression engine does not
**                           encounter ambiguity among variants.
** 057 ECF 20060522   @26444 Added getAnalog() method. Retrieves the AST
**                           node from the copy tree which is at the same
**                           position relative to its parent as the
**                           current source node. This may differ from the
**                           current copy, if it has been displaced.
** 058 ECF 20070430   @33373 Fixed getAncestor(). Was not properly
**                           matching annotation value in the event the
**                           match value was an Integer.
** 059 GES 20070531   @33947 Moved useful helper code into Aast and the
**                           AnnotatedAst classes so that that code could
**                           be used in a wider variety of situations.
** 060 GES 20070723   @34644 Fix for case-insensitive matches().
** 061 ECF 20071015   @35559 Added parseLongQuiet() and parseDoubleQuiet()
**                           methods. These parse numeric strings without
**                           throwing NumberFormatException.
** 062 GES 20080326   @37823 Force fprintf() output to be UTF-8 encoded.
** 063 GES 20090422   @41897 Converted to standard string formatting.
** 064 GES 20090424   @41932 Import change.
** 065 GES 20090429   @42047 Match package and class name changes.
** 066 GES 20090515   @42210 Moved to AstManager from AstRegistry/AstPersister.
** 067 GES 20090518   @42386 Import change.
** 068 GES 20100630          Fixed latent bug which would save at the wrong
**                           node if persist() was called except when the root
**                           node was "this".
** 069 CA  20101105          Allow null AST parameter in formatMessage.
** 070 EVL 20121102          Adding helper functions to double backslashes in
**                           string under Windows environment. Adding helper
**                           functions to substitute the filename in Windows
**                           environment if it contains the Linux file
**                           separator. The char '/' replaced with '\' if
**                           the current OS is Windows.
** 071 CA  20130226          removeQuotes will remove single quotes too, if enclosed.
** 072 SVL 20130624          Added some helper function for use in rules pipelines.
** 073 CA  20131023          Added helper functions for use with conditional rule-set processing.
** 074 ECF 20130806          Added parseTree and loadTree user functions.
** 075 CA  20131023          The filename must always be normalized.
** 076 OM  20131021          Altered loadTree function to return the loaded AST.
** 077 HC  20140518          Added several functions to support decimal numbers.
** 078 ECF 20150328          Added variant of getAst which loads tree of specified ID if it is not
**                           found within the currently loaded trees. Added registerTree and
**                           getStoredObject methods to support certain runtime conversion cases.
** 079 OM  20150617          Added compileError() method that throws a CompileException.
** 080 CA  20150804          Added switchQuotes() method to prepare a string to have its quote
**                           char switched.
** 081 ECF 20150715          Replace StringBuffer with StringBuilder.
** 082 OM  20160608          Added trimTrailingSpaces() method to exposed library interface.
** 083 ECF 20160827          Cache classes for classForName to avoid more expensive calls to
**                           Class.forName.
** 084 OM  20160914          Added compareNodes() utility. Added breakpoint() support.
** 085 OM  20170118          Fixed compatibility for Solaris compiler.
** 086 GES 20170306          Added isAst().
** 087 CA  20170825          Added a mechanism to inject new ASTs into the source list, generated
**                           by the TRPL rules.
** 088 CA  20180507          Fixed getAst - if the extension is not .ast, .dict or .schema, add 
**                           the .ast extension.
** 089 GES 20181023          Added addAllToView().
** 090 CA  20181213          Added getBuiltInClasses().
**     HC  20181212          Added method overload getAncestorNoteBoolean and getLegacyName.
** 091 HC  20190224          Added equality check to compareNodes.
** 092 CA  20190516          Added getClassDefinition() and charAt() APIs.
** 093 CA  20191023          Added getGapMarking() APIs.
** 094 CA  20200412          Added incremental conversion support.
** 095 HC  20200324          Improved OCX conversion support - added support for infinite COM
**                           property/method chaining, removed the requirement for parser keyword
**                           tokens, added support for handle type resolution, plus oher fixes.
** 096 CA  20200508          OO class references are qualified only if 4GL qualifies them.
**     EVL 20200601          Fix for NPE in getGapMarking() with certain conditions.
** 097 CA  20200718          ConversionData must not be used in runtime conversion mode.
** 098 HC  20200903          Improved method resolution when wrapping arguments passed to OCX-
**                           converted methods.
**     CA  20200910          Fixed the datetime(-tz) field's initial value at import - it uses ISO8601
**                           format or NOW function.
**     OM  20200925          Added support for generating denormalized extent fields in dynamic temp-table
**                           conversion.
**     GES 20210715          Added loadConvertedClassByQname().
**     HC  20211001          Implementation of i18n support.
**     CA  20211012          Fixed generated WSDL and reworked SOAP support to rely on namespace, when
**                           resolving an operation.
**     CA  20211214          The AST manager plugin must be context-local, for runtime conversion, as the 
**                           InMemoryRegistryPlugin is not thread-safe.
**                           All worker's state must be context-local, for runtime conversion to work in 
**                           multi-context mode, as the pattern workers are singletons.
**     HC  20211215          Added support for implicit OCX arguments.
**     ME  20211025          Added AST node as input to loadConvertedClass method.
**     CA  20221010          For the 'create' TRPL API, cache the resolved constructor, based on the arguments
**                           signature. Refs #6813
**     TJD 20220504          Upgrade do Java 11 minor changes
**     GES 20220427          Modified to match code refactoring for OO method matching. Rework to eliminate
**                           ConvertedClassName. Instead the ClassDefinition handles all converted name
**                           processing.
**     CA  20221010          For the 'create' TRPL API, cache the resolved constructor, based on the arguments
**                           signature. Refs #6813
**     SVL 20230108          Improved performance by replacing some "for-each" loops with indexed "for" loops.
** 099 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
** 100 OM  20230115          Replaced absolutePath(), relativePath(), upPath() and downPath() with faster
**                           versions, based on node types.
** 101 GBB 20230517          println and printfln levels elevated to INFO for rules' logs to not be 
**                           filtered out.
** 102 GBB 20230613          println and printfln levels to FINE for runtime conversions. A new 'log' method 
**                           added to support all logging levels.
** 103 CA  20231004          Fixed print-related methods to use INFO for conversion mode.
** 104 CA  20240324          Performance improvement for runtime annotations: are stored using an integer
**                           instead of string.
** 105 OM  20240416          Extracted UserGeneratedException in its own source file.
*/

/*
** 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.io.*;
import java.lang.reflect.*;
import java.math.*;
import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.logging.*;
import com.goldencode.ast.*;
import com.goldencode.expr.*;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.convert.db.*;
import com.goldencode.p2j.schema.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.uast.SymbolResolver;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.util.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.io.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.report.*;
import com.goldencode.p2j.uast.*;

/**
 * Provides common symbol resolution and function support for AST processing.
 * An instance of this class is loaded by default by the pattern engine, since
 * the functionality provided is required by nearly every ruleset.
 * <p>
 * The implementations of variables and user functions are contained within a
 * number of public, inner classes. They must be public in order for their
 * members to be accessible via reflection (without policy file changes). They
 * are registered just after construction with the {@link AstSymbolResolver}
 * provided by the {@link PatternEngine}. They are accessed by instances of
 * the {@link Rule} class via logical expressions at runtime.
 */
final class CommonAstSupport
extends AbstractPatternWorker
{
   /** Logger. */
   private static final ConversionStatus LOG = ConversionStatus.get(CommonAstSupport.class);
   
   /** Context local work area. */
   private static final ContextLocal<WorkArea> context = new ContextLocal<WorkArea>()
   {
      protected WorkArea initialValue()
      {
         return new WorkArea();
      }
   };

   /** The engine which created this worker. */
   private final PatternEngine engine;

   /**
    * Default constructor which defines the symbol libraries to be registered.
    * 
    * @param    engine
    *           The engine which created this worker.
    */
   CommonAstSupport(PatternEngine engine)
   {
      super();
      
      setLibrary(new Library());
      
      this.engine = engine;
   }
   
   /**
    * Resolve the numeric key for the specified annotation.
    * 
    * @param    key
    *           The string annotation.
    *           
    * @return   The numeric key for this annotation.
    */
   public static int resolveRuntimeAnnotationKey(String key)
   {
      switch (key.toLowerCase())
      {
         case "class":
            return Aast.RUNTIME_CLASS;
         case "method":
            return Aast.RUNTIME_METHOD;
         case "value":
            return Aast.RUNTIME_VALUE;
         case "constructor":
            return Aast.RUNTIME_CONSTRUCTOR;
      }
      
      throw new UnsupportedOperationException("Runtime annotation " + key + " is not supported.");
   }
   
   /**
    * Hook to provide termination processing for a pattern worker.
    */
   @Override
   public void finish()
   {
      super.finish();
      
      ConversionData.disconnect();
   }
   
   /**
    * Try to match a constructor from the list of candidates, based upon a
    * parameter signature.
    *
    * @param   candidates
    *          An array of constructors from which to select a match.
    * @param   signature
    *          The parameter signature to be matched among the candidate
    *          constructors.
    * @param   strict
    *          <code>true</code> to conduct a strict match of primitive
    *          numeric arguments, else <code>false</code>. In a strict match,
    *          a primitive, numeric parameter type (or its wrapper class
    *          type) will match only the same primitive, numeric parameter
    *          type (or its wrapper class type). In a non-strict match, any
    *          primitive, numeric parameter type (or its wrapper class type)
    *          will match any other primitive, numeric parameter type (or
    *          its wrapper class type). The assumption in the non-strict case
    *          is that with enough wrapping/unwrapping and casting, any
    *          numeric type can be converted to any other numeric type
    *          (though information may be lost in narrowing conversions).
    *
    * @return  The matching <code>Constructor</code> instance (if any), or
    *          <code>null</code> if no match was found.
    *
    * @throws  IllegalArgumentException
    *          if more than one suitable matching constructor was detected
    *          (indicating ambiguity).
    */
   private Constructor matchConstructor(Constructor[] candidates,
                                        Class[] signature,
                                        boolean strict)
   {
      Constructor ctor = null;
      
      int len = candidates.length;
      for (int i = 0; i < len; i++)
      {
         Constructor next = candidates[i];
         Class[] required = next.getParameterTypes();
         if (Function.matchSignature(required, signature, -1, strict))
         {
            if (ctor != null)
            {
               throw new IllegalArgumentException(
                  "Multiple constructors found for '" +
                  ctor.getDeclaringClass().getName() +
                  "' which match the supplied argument list");
            }
            ctor = next;
         }
      }
      
      return ctor;
   }
   
   /**
    * Implementation of basic symbol resolution and comparison-type user
    * function services specific to AST processing. By convention, these
    * functions are typically used in the conditional expression of a rule,
    * though they are not limited to this use.
    * <p>
    * Implementation of common, AST related actions, such as persistence, AST
    * node addition and removal, string and basic output services, adding
    * nodes to the results view, etc.
    * <p>
    * Implementation of services to put, get, and remove annotations to/from
    * ASTs.
    */
   public class Library
   {
      /**
       * Get the gap marking for the specified Java class, implementing a legacy OE class.
       * <p>
       * The gap is read from the {@link LegacyResourceSupport} annotation.
       * 
       * @param    classname
       *           The fully-qualified Java class name.
       *           
       * @return   The conversion and runtime support.
       */
      public int getGapMarking(String classname)
      {
         try
         {
            Class<?> cls = Class.forName(classname);
            
            // special case for _BaseObject_
            if (cls == _BaseObject_.class)
            {
               cls = BaseObject.class;
            }
            
            // look for the annotation
            LegacyResourceSupport lrs = cls.getAnnotation(LegacyResourceSupport.class);
            if (lrs == null)
            {
               // no annotation, default to partial for conversion and non for runtime
               return ReportConstants.CVT_LVL_PARTIAL | ReportConstants.RT_LVL_NONE;
            }
            
            return lrs.supportLvl();
         }
         catch (ClassNotFoundException e)
         {
            // class does not exist in FWD (even as stubs)
            return ReportConstants.CVT_LVL_NONE | ReportConstants.RT_LVL_NONE;
         }
      }
      
      /**
       * Get the gap marking for the specified method in the given Java class, implementing a 
       * legacy OE method.
       * <p>
       * The gap is read from the {@link LegacyResourceSupport} annotation.
       * 
       * @param    classname
       *           The fully-qualified Java class name.
       * @param    method
       *           The Java method name.
       * @param    signature
       *           The legacy signature for the OE method.  This must match with the Java method's
       *           {@link LegacySignature}.
       *           
       * @return   The conversion and runtime support.
       */
      public int getGapMarking(String classname, String method, String signature)
      {
         try
         {
            Class<?> cls = Class.forName(classname);
            
            // special case for _BaseObject_
            if (cls == _BaseObject_.class)
            {
               cls = BaseObject.class;
            }
            
            // build the list of classes where to look
            ArrayList<Class<?>> classes = new ArrayList<>();
            if (cls.isInterface())
            {
               // look in this interface and all super-interfaces
               Set<Class<?>> ifaces = new LinkedHashSet<>();
               Utils.collectInterfaces(cls, ifaces);
               classes.addAll(ifaces);
            }
            else
            {
               boolean isAbstract = Modifier.isAbstract(cls.getModifiers());
               
               // look in all super-classes
               Class<?> parent = cls;
               while (parent != Object.class)
               {
                  classes.add(parent);
                  if (isAbstract)
                  {
                     // if the source class is abstract, interfaces need to be considered, too 
                     // (as the implementation may not be in the class hierarchy)
                     Set<Class<?>> ifaces = new LinkedHashSet<>();
                     Utils.collectInterfaces(parent, ifaces);
                     classes.addAll(ifaces);
                  }
                  parent = parent.getSuperclass();
               }
            }

            for (int i = 0; i < classes.size(); i++)
            {
               Class<?> acls = classes.get(i);
               // look for a method
               Method[] mthds = acls.getDeclaredMethods();
               for (Method mthd : mthds)
               {
                  if (mthd.getName().equals(method))
                  {
                     LegacySignature ls = mthd.getAnnotation(LegacySignature.class);
                     if (ls != null)
                     {
                        String lsig = SymbolResolver.calculateLegacySignature(ls.name(), ls);
                        if (lsig != null && lsig.equalsIgnoreCase(signature))
                        {
                           LegacyResourceSupport lrs = mthd.getAnnotation(LegacyResourceSupport.class);
                           if (lrs == null)
                           {
                              return ReportConstants.CVT_LVL_FULL | ReportConstants.RT_LVL_STUB;
                           }
                           else
                           {
                              return lrs.supportLvl();
                           }
                        }
                     }
                  }
               }
            }
            
            return ReportConstants.CVT_LVL_NONE | ReportConstants.RT_LVL_NONE;
         }
         catch (ClassNotFoundException e)
         {
            // class does not exist in FWD (even as stubs)
            return ReportConstants.CVT_LVL_NONE | ReportConstants.RT_LVL_NONE;
         }
      }
      
      /**
       * Load the specified class definition.  This will either create a {@link ClassDefinition}
       * structure for a new class (to be later populated with members by the parser), or will restore
       * an instance from an already-converted class.
       * 
       * @param    filename
       *           The file name for the legacy OO class.
       * @param    isNew
       *           Flag indicating that this is a new class (now being parsed), or a previously
       *           converted class.
       * @param    ast 
       *           The abstract syntax tree node.
       *           
       * @return   The class definition.
       * 
       * @throws   ClassNotFoundException
       *           If the class can't be resolved or loaded.
       */
      public ClassDefinition loadConvertedClass(String filename, boolean isNew, Aast ast)
      throws ClassNotFoundException
      {
         if (filename == null)
         {
            throw new ClassNotFoundException("Can not find class for accessing " + ast.toStringVerbose());
         }
         
         return SymbolResolver.loadConvertedClass(filename, isNew);
      }
      
      /**
       * Load the specified class definition.
       * 
       * @param    qname
       *           The legacy OO class qualified name.
       *           
       * @return   The found converted class.
       * 
       * @throws   ClassNotFoundException
       *           If the class can't be resolved or loaded.
       */
      public ClassDefinition loadConvertedClassByQname(String qname)
      throws ClassNotFoundException
      {
         return SymbolResolver.loadConvertedClassByQname(qname);
      }
      
      /**
       * Get the parser's {@link ClassDefinition} for a given qualified class name, in legacy form.
       * 
       * @param     clsName
       *            The qualified legacy class name.
       *            
       * @return    The {@link ClassDefinition} instance, or <code>null</code> if it does not 
       *            exist.
       */
      public ClassDefinition loadClassDefinition(String clsName)
      {
         return SymbolResolver.loadClassDefinition(clsName);
      }
      
      /**
       * End the current AST walk. This can be called at any time within any
       * rules section. No further nodes in the current AST will be visited;
       * however, any remaining rules and/or actions for the current node
       * will still be processed. The pattern engine will continue processing
       * with the next AST root node in its list, or complete its run if no
       * further AST roots exist.
       *
       * @see  AstSymbolResolver#setEndWalk
       */
      public void endCurrentWalk()
      {
         getResolver().setEndWalk(true);
      }
      
      /**
       * Get the class with the given name. For primitives, return the class
       * which represents that type via the wrapper's <code>TYPE</code>
       * member.
       *
       * @param   name
       *          Name of a class, or the type name of a primitive.
       *
       * @return  Class associated with the given name.
       *
       * @throws  ClassNotFoundException
       *          if no class is found for <code>name</code>.
       */
      public Class classForName(String name)
      throws ClassNotFoundException
      {
         return classForName(name, false);
      }

      /**
       * Get the class with the given name. For primitives, return the class
       * which represents that type via the wrapper's <code>TYPE</code>
       * member.
       *
       * @param   name
       *          Name of a class, or the type name of a primitive.
       * @param   noThrow
       *          Set to {@code true} to never throw any exception.
       *
       * @return  Class associated with the given name or {@code null}.
       *
       * @throws  ClassNotFoundException
       *          if no class is found for <code>name</code> and {@code noThrow} is false.
       */
      public Class classForName(String name, boolean noThrow)
      throws ClassNotFoundException
      {
         WorkArea wa = context.get();
         Class<?> clazz = wa.classCache.get(name);
         if (clazz != null)
         {
            return clazz;
         }

         switch (name)
         {
            case "int":
               clazz = Integer.TYPE;
               break;
            case "long":
               clazz = Long.TYPE;
               break;
            case "boolean":
               clazz = Boolean.TYPE;
               break;
            case "double":
               clazz = Double.TYPE;
               break;
            case "float":
               clazz = Float.TYPE;
               break;
            case "char":
               clazz = Character.TYPE;
               break;
            case "byte":
               clazz = Byte.TYPE;
               break;
            default:
               try
               {
                  clazz = Class.forName(name);
               }
               catch (ClassNotFoundException e)
               {
                  if (!noThrow)
                  {
                     throw e;
                  }
               }
               break;
         }

         if (clazz != null)
         {
            wa.classCache.putIfAbsent(name, clazz);
         }

         return clazz;
      }

      /**
       * Resolve a reflective method, given a class, method name, and
       * parameter signature.
       *
       * @param   cls
       *          Method's class.
       * @param   name
       *          Name of the method.
       * @param   paramTypes
       *          Array of classes representing the types of the parameters
       *          expected by the method.
       *
       * @return  The method if it is found.
       *
       * @throws  NoSuchMethodException
       *          if the method as described does not exist.
       */
      public Method getMethod(Class cls, String name, Object[] paramTypes)
      throws NoSuchMethodException
      {
         int len = paramTypes.length;
         Class[] classes = new Class[len];
         for (int i = 0; i < len; i++)
         {
            classes[i] = (Class) paramTypes[i];
         }
         
         return cls.getMethod(name, classes);
      }
      
      /**
       * Helper function to invoke an arbitrary method through reflection.
       *
       * @param   method
       *          Method to invoke.
       * @param   target
       *          Object upon which the method will be invoked.
       * @param   args
       *          Parameters passed to the method.
       *
       * @throws  IllegalAccessException
       *          if method object enforces Java language access control and
       *          the underlying method is inaccessible.
       * @throws  InvocationTargetException
       *          if the underlying method throws an exception.
       */
      public Object invoke(Method method, Object target, Object[] args)
      throws IllegalAccessException,
             InvocationTargetException
      {
         return method.invoke(target, args);
      }

      /**
       * Tests whether the specified class declares member of the supplied name.
       *
       * @param   cls
       *          Class to test.
       * @param   name
       *          Target member name.
       *
       * @return  {@code true} if the class declares the supplied member, {@code false} otherwise.
       */
      public boolean declaresMember(Class cls, String name)
      {
         for (Field m : cls.getDeclaredFields())
      {
            if (m.getName().equals(name))
            {
               return true;
            }
         }

         for (Method m : cls.getDeclaredMethods())
         {
            if (m.getName().equals(name))
            {
               return true;
            }
         }

         for (Class m : cls.getDeclaredClasses())
         {
            if (m.getSimpleName().equals(name))
            {
               return true;
            }
         }

         for (Constructor m : cls.getDeclaredConstructors())
         {
            if (m.getName().equals(name))
            {
               return true;
            }
         }

         return false;
      }

      /**
       * Tests whether the specified class has tge method of the supplied name. The comparison is performed
       * case-insensitively and the method returns the java method name as declared.
       *
       * @param   cls
       *          Class to test.
       * @param   insensitiveName
       *          Case insensitive target method name.
       *
       * @return  Returns the matched method name or {@code null} if not found.
       */
      public String getMethodNoCase(Class cls, String insensitiveName)
      {
         for (Method m : cls.getMethods())
         {
            String mname = m.getName();
            if (mname.equalsIgnoreCase(insensitiveName))
            {
               return mname;
            }
         }

         return null;
      }

      /**
       * Returns method parameter type on the specified index.
       *
       * @param   cls
       *          The target class.
       * @param   methodName
       *          The target case sensitive method name.
       * @param   requiredParamNum
       *          Only match methods with the specified number of parameters.
       * @param   paramIdx
       *          The index of the parameter type to return.
       * @param   expectedParamType
       *          Expected paramater type. This helps in finding the correct method overload.
       *
       * @return  Java class or {@code null} if the target method is not found or the target method
       *          doesn't declare the requested parameter.
       */
      public Class getMethodParamType(Class cls,
                                      String methodName,
                                      int requiredParamNum,
                                      int paramIdx,
                                      Class expectedParamType)
      {
         Method[] methods = cls.getMethods();
         ArrayList<Method> matchedMethods = new ArrayList<>();
         for (Method m : methods)
         {
            if (m.getParameterTypes().length != requiredParamNum)
            {
               continue;
            }

            if (!m.getName().equals(methodName))
            {
               continue;
            }

            matchedMethods.add(m);
         }

         if (matchedMethods.isEmpty())
         {
            return null;
         }

         Class[] params = null;

         if (expectedParamType != null)
         {
            // find exact match
            for (int i = 0; i < matchedMethods.size(); i++)
            {
               Method m = matchedMethods.get(i);
               params = m.getParameterTypes();
               if (params[paramIdx].isAssignableFrom(expectedParamType))
               {
                  return expectedParamType;
               }
            }
         }

         // find BaseDataType
         for (int i = 0; i < matchedMethods.size(); i++)
         {
            Method m = matchedMethods.get(i);
            params = m.getParameterTypes();
            if (BaseDataType.class.isAssignableFrom(params[paramIdx]))
            {
               return params[paramIdx];
            }
         }

         // last resort
         return params[paramIdx];
      }

      /**
       * Returns the resource type declared by the supplied method. Method may declare a resource type (the
       * type of the value held by the returned handle) when its return type is {@link handle} by declaring
       * the annotation {@link ResourceType} with the
       * resource type defined.
       *
       * @param   clazz
       *          The class to check.
       * @param   method
       *          The method to check.
       *
       * @return  See above.
       */
      public Class getResourceType(Class clazz, String method)
      {
         for (Method m : clazz.getMethods())
         {
            if (m.getName().equals(method))
            {
               ResourceType resType = m.getAnnotation(ResourceType.class);
               if (resType != null)
               {
                  return resType.type();
               }
               else
               {
                  Class result = m.getReturnType();
                  return result == Void.class ? null : result;
               }
            }
         }

         return null;
      }

      /**
       * Returns the simple class name of the supplied (fully qualified) class name.
       *
       * @param   className
       *          Simple or fully qualified class name.
       *
       * @return  Returns the simple class name.
       */
      public String getClassSimpleName(String className)
      {
         if (!className.contains("."))
         {
            return className;
         }

         return className.substring(className.lastIndexOf('.') + 1);
      }

      /**
       * Returns {@code true} if the supplied method declares {@link handle} as its return type.
       *
       * @param   clazz
       *          The class to check.
       * @param   method
       *          The method to check.
       *
       * @return  See above.
       */
      public boolean returnsHandle(Class clazz, String method)
      {
         for (Method m : clazz.getMethods())
         {
            if (m.getName().equals(method))
            {
               return m.getReturnType().equals(handle.class);
            }
         }

         return false;
      }

      /**
       * Returns the legacy name of the supplied method on the supplied class. The method searches the
       * supplied class for a method of the specified name (case insensitively) and returns the name as
       * defined in {@link LegacyMethod} or {@link LegacyAttribute} annotations.
       *
       * @param   cls
       *          Class where to search for the supplied method.
       * @param   methodName
       *          The class method name.
       *
       * @return  legacy name or null if no match is found.
       */
      public String getLegacyName(Class<?> cls, String methodName)
      {
         for (Method m : cls.getMethods())
         {
            if (m.getName().equalsIgnoreCase(methodName))
            {
               LegacyMethod lMethod = m.getAnnotation(LegacyMethod.class);
               if (lMethod != null)
               {
                  return lMethod.name();
               }

               LegacyAttribute lAttr = m.getAnnotation(LegacyAttribute.class);
               if (lAttr != null)
               {
                  return lAttr.name();
               }
            }
         }
         
         return null;
      }
      
      /**
       * Create a new instance of the specified class, passing the provided
       * arguments to its constructor.
       *
       * @param   className
       *          Fully qualified name of the class to be instantiated.
       * @param   args
       *          Constructor arguments. Currently, <code>null</code>
       *          arguments within this array are not supported.
       *
       * @return  Instance of the specified class.
       *
       * @throws  ClassNotFoundException
       *          if a class named <code>className</code> cannot be found.
       * @throws  NullPointerException
       *          if any argument in <code>args</code> is <code>null</code>.
       * @throws  NoSuchMethodException
       *          if the signature defined by <code>args</code> matches no
       *          constructor in the specified class.
       * @throws  IllegalAccessException
       *          if the <code>Constructor</code> used enforces Java language
       *          access control and the underlying constructor is
       *          inaccessible.
       * @throws  IllegalArgumentException
       *          if the number of actual and formal parameters required by
       *          the underlying constructor differ; if an unwrapping
       *          conversion for primitive arguments fails; or if, after
       *          possible unwrapping, a parameter value cannot be converted
       *          to the corresponding formal parameter type by a method
       *          invocation conversion; if more than one suitable matching
       *          constructor was detected (indicating ambiguity).
       * @throws  InstantiationException
       *          if the class that declares the underlying constructor
       *          represents an abstract class.
       * @throws  InvocationTargetException
       *          if the underlying constructor throws an exception.
       */
      public Object create(String className, Object[] args)
      throws ReflectiveOperationException
      {
         // look up class
         Class<?> target = classForName(className);
         
         return create(target, args);
      }
         
      /**
       * Create a new instance of the specified class, passing the provided
       * arguments to its constructor.
       *
       * @param   target
       *          Class to be instantiated.
       * @param   args
       *          Constructor arguments. Currently, <code>null</code>
       *          arguments within this array are not supported.
       *
       * @return  Instance of the specified class.
       *
       * @throws  NullPointerException
       *          if any argument in <code>args</code> is <code>null</code>.
       * @throws  NoSuchMethodException
       *          if the signature defined by <code>args</code> matches no
       *          constructor in the specified class.
       * @throws  IllegalAccessException
       *          if the <code>Constructor</code> used enforces Java language
       *          access control and the underlying constructor is
       *          inaccessible.
       * @throws  IllegalArgumentException
       *          if the number of actual and formal parameters required by
       *          the underlying constructor differ; if an unwrapping
       *          conversion for primitive arguments fails; or if, after
       *          possible unwrapping, a parameter value cannot be converted
       *          to the corresponding formal parameter type by a method
       *          invocation conversion; if more than one suitable matching
       *          constructor was detected (indicating ambiguity).
       * @throws  InstantiationException
       *          if the class that declares the underlying constructor
       *          represents an abstract class.
       * @throws  InvocationTargetException
       *          if the underlying constructor throws an exception.
       */
      public Object create(Class<?> target, Object[] args)
      throws ReflectiveOperationException

      {
         // determine provided signature from var-args array
         int len = args.length;
         Class<?>[] sig = new Class[len];
         StringBuilder strSig = new StringBuilder();
         boolean cache = true;
         for (int i = 0; i < len; i++)
         {
            Object next = args[i];
            if (next == null)
            {
               cache = false;
               sig[i] = null;
            }
            else
            {
               sig[i] = next.getClass();
               if (cache)
               {
                  strSig.append(sig[i].getName()).append(',');
               }
            }
         }
         
         WorkArea wa = null;
         Constructor<?> ctor = null;
         Map<String, Constructor<?>> sigCache = null;
         if (cache)
         {
            wa = context.get();
            sigCache = wa.ctorCache.get(target);
            if (sigCache != null)
            {
               ctor = sigCache.get(strSig.toString());
               if (ctor != null)
               {
                  return ctor.newInstance(args);
               }
            }
         }
         
         // scan each constructor in the target class to test for null matches and assignment
         // compatible parameters
         Constructor<?>[] ctors = target.getConstructors();
         
         // test for a strict match with all available candidate constructors
         ctor = matchConstructor(ctors, sig, true);
         
         // if no match was found with the strict test, try a more lenient match test
         if (ctor == null)
         {
            ctor = matchConstructor(ctors, sig, false);
         }
         
         // if still no match, we're out of luck
         if (ctor == null)
         {
            throw new IllegalArgumentException(
               "No constructor found for '" + target.getName() +
               "' which matches the supplied argument list");
         }
         
         if (cache)
         {
            if (sigCache == null)
            {
               sigCache = new HashMap<>();
               wa.ctorCache.put(target, sigCache);
            }
            
            sigCache.put(strSig.toString(), ctor);
         }
         
         // instantiate it
         return ctor.newInstance(args);
      }
      
      /**
       * Create a new <code>List</code> which contains the specified
       * elements as its members.
       *
       * @param   elements
       *          Elements of the newly created list object.
       *
       * @return  List object which contains <code>elements</code>.
       */
      public List<?> createList(Object[] elements)
      {
         return Arrays.asList(elements);
      }
      
      /**
       * Split the given value into a list of {@link String}'s, using the given delimiter.
       * 
       * @param    val
       *           The value to split.
       * @param    delim
       *           The delimiter.
       */
      public List<?> splitToList(String val, String delim)
      {
         return Arrays.asList(val.split(delim));
      }
      
      /**
       * Generate a list from an array of objects. This variant is used when
       * an array of objects as returned from a method invocation to a user
       * expression must be converted into a list. The object array is not
       * declared as such, to prevent the expression engine's symbol resolver
       * from treating this as a user function which expects a variable
       * argument list.
       *
       * @param   array
       *          An object that represents an array of objects.
       *
       * @return  List object which contains all elements within
       *          <code>array</code>.
       */
      public List<?> createListFromObject(Object array)
      {
         return Arrays.asList((Object[]) array);
      }
      
      /**
       * Create a linked hash set containing the specified elements. Because
       * it is a set, duplicates (as determined by the <code>equals</code>
       * method of the elements) are discarded. Iteration over the resulting
       * set is guaranteed to be in the same order as unique elements were
       * found in the <code>elements</code> array.
       *
       * @param   elements
       *          Array whose unique elements define the contents of the
       *          newly created set.
       *
       * @return  Linked hash set based upon <code>elements</code>.
       */
      public LinkedHashSet<?> createSet(Object[] elements)
      {
         return new LinkedHashSet<>(Arrays.asList(elements));
      }
      
      /**
       * Create a linked hash map whose keys are defined by <code>keys</code>
       * and whose values are defined by <code>values</code>. The map is
       * populated by iterating over <code>keys</code> and associating the
       * value at the corresponding position in the <code>values</code> list.
       * Thus, it is important that the iteration order of <code>keys</code>
       * is <b>well-defined, and corresponds identically</b> with that of
       * <code>values</code>.
       *
       * @param   keys
       *          Set which defines the keys of the new map.
       * @param   values
       *          List which defines the values of the new map.
       *
       * @return  New instance of map.
       *
       * @throws  IllegalArgumentException
       *          if the sizes of the specified data structures are not
       *          equal.
       */
      public <K, V> LinkedHashMap<K, V> createMap(Set<K> keys, List<V> values)
      throws IllegalArgumentException
      {
         if (keys.size() != values.size())
         {
            throw new IllegalArgumentException(
               "Set of map keys must be the same size as list of map values");
         }
         
         LinkedHashMap<K, V> map = new LinkedHashMap<>();
         Iterator<K> iter = keys.iterator();
         for (int i = 0; iter.hasNext(); i++)
         {
            map.put(iter.next(), values.get(i));
         }
         
         return map;
      }

      /**
       * Creates new {@linkplain UUID} instance from the supplied data.
       *
       * @param   high
       *          The most significant bits of the UUID
       * @param   low
       *          The least significant bits of the UUID
       *
       * @return  New {@linkplain UUID} instance.
       */
      public UUID createUUID(long high, long low)
      {
         return new UUID(high, low);
      }
      
      /**
       * Get the current system time in milliseconds since the epoch.
       *
       * @return  <code>System.currentTimeMillis()</code>
       */
      public long getNow()
      {
         return System.currentTimeMillis();
      }
      
      /**
       * Get the instance of the source AST currently set in the resolver.
       *
       * @return  Current source AST.
       */
      public Aast getThis()
      {
         return getSource();
      }
      
      /**
       * Get the instance of the copy AST currently set in the resolver.
       *
       * @return  Current copy AST.
       */
      public Aast getCopy()
      {
         return CommonAstSupport.this.getCopy();
      }
      
      /**
       * Get the AST node which is located in the copy tree at the analogous
       * index position as the current source AST, with respect to their
       * respective parents.  Only one level of ancestor is considered.
       * <p>
       * This is meant to enable ascent rules which change the copy tree at
       * the current level, such that the copy map is no longer a reliable
       * mechanism to retrieve the proper analog to the current source node.
       *
       * @return  AST from the copy tree, or <code>null</code> if no such AST
       *          exists.
       */
      public Aast getAnalog()
      {
         Aast source = getSource();
         Aast parent = source.getParent();
         if (parent == null)
         {
            return null;
         }
         
         Long id = parent.getId();
         int index = source.getIndexPos();
         
         return getResolver().getAst(id).getChildAt(index);
      }
      
      /**
       * Load the AST stored in the given file and return it. Do not register it with the AST
       * resolver
       * 
       * @param   filename
       *          Name of file in which AST is stored.
       * 
       * @return  Aast read from the file.
       * 
       * @throws  IllegalArgumentException
       *          if there is any error loading <code>filename</code>.
       */
      public Aast parseTree(String filename)
      {
         return AstManager.get().loadTree(filename);
      }
      
      /**
       * Load the AST stored in the given file. This will register the AST and all of its
       * descendants with the current AST resolver.
       * 
       * @param   filename
       *          Name of file in which AST is stored.
       * 
       * @return  Aast read from the file.
       *
       * @throws  IllegalArgumentException
       *          if there is any error loading <code>filename</code>.
       */
      public Aast loadTree(String filename)
      {
         return AstSymbolResolver.loadTree(getResolver(), filename);
      }
      
      /**
       * Register the given AST and all its descendants with the AST resolver.
       * 
       * @param   ast
       *          Tree to register.
       */
      public void registerTree(Aast ast)
      {
         getResolver().registerTree(ast);
      }
      
      /**
       * Returns an AST instance referenced by its ID if it is loaded (and
       * registered) in the current resolver.
       *
       * @param    id
       *           The project-unique ID that specifies the AST. 
       *
       * @return   The AST or <code>null</code> if the associated AST either
       *           doesn't exist or is not loaded (and registered in the
       *           resolver).
       */
      public Aast getAst(Long id)
      {
         return getAst(id, true);
      }
      
      /**
       * Returns an AST instance referenced by its ID. If it is not loaded and registered with
       * the current resolver, optionally load and register its enclosing tree.
       * 
       * @param    id
       *           The project-unique ID that specifies the AST.
       * @param    loadTree
       *           if <code>true</code>, the tree enclosing the AST with the given ID will be
       *           loaded and registered with the current resolver. <strong>Use this option with
       *           caution.</strong> ASTs can be large and this can consume a lot of memory.
       * 
       * @return   The AST or <code>null</code> if the associated AST doesn't exist.
       */
      public Aast getAst(Long id, boolean loadTree)
      {
         return AstSymbolResolver.getAst(getResolver(), id, loadTree);
      }
      
      /**
       * Remove the entire tree enclosing the specified AST from the resolver.
       * 
       * @param    id
       *           The project-unique ID that specifies the AST.
       */
      public void removeAst(Long id)
      {
         Aast ast = getAst(id, false);
         if (ast == null)
         {
            return;
         }

         getResolver().deregisterTree(ast);
      }
      
      /**
       * Determines if the given file is a valid AST.
       *
       * @param    filename
       *           The filename to check.  It does not need to have the AST extension.
       *
       * @return   <code>true</code> if there is an AST for the given file.
       */
      public boolean isAst(String filename)
      {
         if (filename == null || filename.trim().length() == 0)
         {
            return false;
         }
         
         if (!filename.endsWith(AstGenerator.AST_POSTFIX))
         {
            filename = filename + AstGenerator.AST_POSTFIX;
         }
         
         return AstManager.get().isExistingAst(filename);
      }
      
      /**
       * Get the token type of the current, source AST.
       * <p>
       * Corresponds with runtime variable <code>type</code>.
       *
       * @return  Token type.
       */
      public int getType()
      {
         return getSource().getType();
      }
      
      /**
       * Set the token type of the current, copy AST.
       *
       * @return  Always <code>true</code>.
       */
      public boolean setType(long type)
      {
         getCopy().setType((int)type);
         
         return true;
      }
      
      /**
       * Get the token name of the current, source AST.  The token name is
       * the symbolic text representation of the integer token type.
       * <p>
       * Corresponds with runtime variable <code>name</code>.
       *
       * @return  Token name.
       */
      public String getName()
      {
         return getSource().getSymbolicTokenType();
      }
      
      /**
       * Get the current, source AST's parent, if any.
       * <p>
       * Corresponds with runtime variable <code>parent</code>.
       *
       * @return  Parent AST, or <code>null</code> if no source or parent.
       */
      public Aast getParent()
      {
         Aast src = getSource();
         
         return (src != null ? src.getParent() : null);
      }
      
      /**
       * Get the token text of the current source AST.
       * <p>
       * Corresponds with runtime variable <code>text</code>.
       *
       * @return  Token text.
       */
      public String getText()
      {
         return getSource().getText();
      }
      
      /**
       * Set the token text of the current copy AST.
       *
       * @return  Always <code>true</code>.
       */
      public boolean setText(String text)
      {
         getCopy().setText(text);
         
         return true;
      }
      
      /**
       * Get the project-unique AST node ID of the current, source AST.
       * <p>
       * Corresponds with runtime variable <code>id</code>.
       *
       * @return  The project-unique node ID.
       */
      public Long getId()
      {
         return getSource().getId();
      }
      
      /**
       * Get the name of the source code file associated with the current
       * source AST. The filename will be relative to <code>P2J_HOME</code>.
       * <p>
       * Corresponds with runtime variable <code>file</code>.
       *
       * @return  Source code filename.
       */
      public String getFile()
      {
         return Configuration.normalizeFilename(getSource().getFilename());
      }
      
      /**
       * Returns the project relative filename of the copy AST persistence
       * file, using the default persistence naming convention.
       * <p>
       * Corresponds with runtime variable <code>persistenceFilename</code>.
       *
       * @return  The filename or <code>null</code> if the filename cannot
       *          be obtained.
       */
      public String getPersistenceFilename()
      {
         String name = getCopy().getFilename();
         
         if (name == null)
            return null;
         
         StringBuilder buf = new StringBuilder(name);
         buf.append(AstGenerator.AST_POSTFIX);
         
         return buf.toString();
      }
      
      /**
       * Get the line number in the source code file at which the current
       * source AST was found.
       * <p>
       * Corresponds with runtime variable <code>line</code>.
       *
       * @return  Source code line number.
       */
      public long getLine()
      {
         return getSource().getLine();
      }
      
      /**
       * Get the column number in the source code file at which the current
       * source AST was found.
       * <p>
       * Corresponds with runtime variable <code>column</code>.
       *
       * @return  Source code column number.
       */
      public long getColumn()
      {
         return getSource().getColumn();
      }
      
      /**
       * Get the number of generations away from the root AST node this AST
       * is located (in the source AST). A depth of 0 corresponds with the
       * root node itself; 1 with the root's immediate children; and so on.
       * <p>
       * Corresponds with runtime variable <code>depth</code>.
       *
       * @return  AST depth from root.
       */
      public long getDepth()
      {
         return getSource().getDepth();
      }
      
      /**
       * Accesses the full path from the current source AST to the root.
       *
       * @return  Full path from the current AST to the root node, formatted
       *          as in {@link Aast#getPath}.
       */
      public String getPath()
      {
         // get our full path to the root
         return getSource().getPath(); 
      }
      
      /**
       * Accesses the text representation (human readable 'parser output') of
       * the tree rooted at the current AST node.
       *
       * @return  Text dump of the tree structure and contents.
       */
      public String getTreeDump()
      {
         return getSource().dumpTree(); 
      }
      
      /**
       * Accesses the text representation (human readable 'parser output') of
       * the tree rooted at the specified ancestor AST node, if such a node
       * exists.
       *
       * @param   level
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       *
       * @return  Text dump of the tree structure and contents or 
       *          <code>null</code> if no such node exists (if the 
       *          <code>level</code> specified is greater than depth of the
       *          tree).
       */
      public String getAncestorTreeDump(long level)
      {
         Aast ancestor = getSource().getAncestor((int)level);
         
         if (ancestor == null)
            return null;
         
         return ancestor.dumpTree(); 
      }
      
      /**
       * Determine whether <code>text</code> matches the specified regular
       * expression.
       *
       * @param   text
       *          String to test.
       * @param   regex
       *          Regular expression to apply against <code>text</code>.
       *
       * @return  <code>true</code> for a match, else <code>false</code>.
       */
      public boolean matches(String text, String regex)
      {
         return text.matches(regex);
      }
      
      /**
       * Determine whether <code>text</code> matches the specified regular
       * expression, taking the caller's case-sensitivity into account. If
       * the case-insensitive mode is chosen, both the input text and the
       * regular expression will be lowercased before match processing occurs.
       *
       * @param   text
       *          String to test.
       * @param   regex
       *          Regular expression to apply against <code>text</code>.
       * @param   sensitive
       *          <code>true</code> if normal case-sensitivity should be
       *          used, <code>false</code> for case-insensitive matching.
       *
       * @return  <code>true</code> for a match, else <code>false</code>.
       */
      public boolean matches(String text, String regex, boolean sensitive)
      {
         String source = text;
         String expr   = regex;
         
         if (!sensitive)
         {
            source = text.toLowerCase();
            expr   = regex.toLowerCase();
         }
         
         return source.matches(expr);
      }
      
      /**
       * Implementation of the <code>evalLib</code> user function. Accepts
       * the name of a named expression and executes that expression. The
       * expression to which this function refers is assumed to return a
       * <code>Boolean</code> result.    This is a convenience form of
       * <code>execLib</code> which eliminates the cast when used as a
       * condition.
       *
       * @param   name
       *          Name of an expression to execute.
       * @param   args
       *          The array of <code>Object</code> instances of the correct
       *          data type and in the correct order to match our parameter
       *          list.  Each element of the array will be assigned into the
       *          matching parameter in our parameter list before the
       *          expression is executed.
       *
       * @return  Boolean result of executing the named expression.
       *
       * @throws  ExpressionException
       *          if any error occurs compiling the named expression before
       *          its first use.
       */
      public Boolean evalLib(String name, Object[] args)
      {
         return (Boolean) execLib(name, args);
      }
      
      /**
       * Implementation of the <code>execLib</code> user function. Accepts
       * the name of a named expression and executes that expression.
       *
       * @param   name
       *          Name of an expression to execute.
       * @param   args
       *          The array of <code>Object</code> instances of the correct
       *          data type and in the correct order to match our parameter
       *          list.  Each element of the array will be assigned into the
       *          matching parameter in our parameter list before the
       *          expression is executed.
       *
       * @return  Boolean result of executing the named expression.
       *
       * @throws  ExpressionException
       *          if any error occurs compiling the named expression before
       *          its first use.
       */
      public Object execLib(String name, Object[] args)
      {
         return getResolver().execute(name, args);
      }
      
      /**
       * Implementation of the <code>eval</code> user function. Accepts the
       * an expression, then compiles (if necessary) and executes that 
       * expression returning a <code>Boolean</code> result.  This is a
       * convenience form of <code>exec</code> which eliminates the cast
       * when used as a condition.
       *
       * @param   expr
       *          An expression to execute.
       *
       * @return  Result of executing the passed expression.
       *
       * @throws  ExpressionException
       *          if any error occurs compiling the expression before it's
       *          first use.
       */
      public Boolean eval(String expr)
      {
         return (Boolean) exec(expr);
      }
      
      /**
       * Implementation of the <code>exec</code> user function. Accepts the
       * an expression, then compiles (if necessary) and executes that 
       * expression.
       *
       * @param   expr
       *          An expression to execute.
       *
       * @return  Result of executing the passed expression.
       *
       * @throws  ExpressionException
       *          if any error occurs compiling the expression before it's
       *          first use.
       */
      public Object exec(String expr)
      {
         return getResolver().execute(expr);
      }
      
      /**
       * Implementation of the <code>root</code> user function. Determines
       * whether the current AST is the root of the source tree.
       *
       * @return  <code>true</code> if AST has no parent, else
       *          <code>false</code>.
       */
      public boolean root()
      {
         return (getSource().getParent() == null);
      }
      
      /**
       * Implementation of the <code>leaf</code> user function. Determines
       * whether the current AST is a leaf node of the source tree.
       *
       * @return  <code>true</code> if AST has no children, else
       *          <code>false</code>.
       */
      public boolean leaf()
      {
         return (getSource().getFirstChild() == null);
      }
      
      /**
       * Implementation of the <code>numImmediateChildren</code> user 
       * variable.
       *
       * @return   The number of immediate children for the current AST node. 
       */
      public long getNumImmediateChildren()
      {
         return getSource().getNumImmediateChildren();
      }
      
      /**
       * Implementation of the <code>nextChildIndex</code> user 
       * variable which denotes which child is about to be processed when
       * processing a 'next-child' rule.  Since child indices are 0-based,
       * the next child index will always be greater than 0 during the
       * processing of a 'next-child' rule, since the first time a 
       * 'next-child' rule is called, the previous child is 0 and the next
       * child is 1 (so the return from this function will be 1).
       *
       * @return   The next child to be processed or -1 if a 'next-child' 
       *           rule is not in progress.
       */
      public int getNextChildIndex()
      {
         return getResolver().getNextChildIndex();
      }
      
      /**
       * Implementation of the <code>imaginary</code> user function.
       * Determines whether the token underlying the current AST was created
       * artificially, or is based upon a source code element.
       *
       * @return  <code>true</code> if AST has a line:column position of 0:0,
       *          else <code>false</code>.
       */
      public boolean imaginary()
      {
         Aast ast = getSource();
         
         return (ast.getLine() == 0 && ast.getColumn() == 0);
      }
      
      /**
       * Compares the current source AST's path from the root with the
       * specified path.
       *
       * @param    path
       *           Root-to-current path, formatted as described in 
       *           {@link Aast#getPath}.
       *
       * @return   <code>true</code> if specified path matches current source
       *           AST's path exactly, else <code>false</code>.
       */
      @Deprecated
      public boolean absolutePath(String path)
      {
         return getSource().absolutePath(path);
      }
      
      /**
       * Compares the current source AST's path from the root with the specified path.
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    path
       *           Root-to-current path, formatted as described in {@link Aast#getPath}.
       *
       * @return   {@code true} if specified path matches current source AST's path exactly.
       */
      public boolean absolutePath(Aast node, Object[] path)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.absolutePath(path);
      }
      
      /**
       * Determines if the current source AST's path from the root <b>ends
       * with</b> the specified path, on a case-insensitive basis. 
       * <p>
       * The current source AST's type IS included in this path, which is
       * different from the {@link #upPath} which compares paths upward
       * from the parent node.
       *
       * @param    path
       *           Partial path from the current AST up to an arbitrary
       *           number of parent nodes, formatted as described in
       *           {@link Aast#getPath}.
       *
       * @return   <code>true</code> if specified path matches the end of the 
       *           current source AST's path exactly, else <code>false</code>.
       */
      @Deprecated
      public boolean relativePath(String path)
      {
         return  getSource().relativePath(path);
      }
      
      /**
       * Determines if the node's path from the root <b>ends with</b> the specified path.
       * <p>
       * The node's type IS included in this path, which is different from the {@link #upPath} which compares
       * paths upward starting at the parent node.
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    path
       *           Partial path from the node up to an arbitrary number of parent nodes. Must contain only
       *           {@code int} / {@code Integer} values.
       *
       * @return   {@code true} if specified path matches the end of the node's path exactly.
       */
      public boolean relativePath(Aast node, Object[] path)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.relativePath(path);
      }
      
      /**
       * Determines if the node's path from the root <b>ends with</b> the specified path.
       * <p>
       * The node's type IS included in this path, which is different from the {@link #upPath} which compares
       * paths upward starting at the parent node.
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    tok1
       *           The first token for this path match.
       *
       * @return   {@code true} if specified path matches the end of the node's path exactly.
       */
      public boolean relativePath(Aast node, int tok1)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.relativePath(tok1);
      }
      
      /**
       * Determines if the node's path from the root <b>ends with</b> the specified path.
       * <p>
       * The node's type IS included in this path, which is different from the {@link #upPath} which compares
       * paths upward starting at the parent node.
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    tok1
       *           The first token for this path match.
       * @param    tok2
       *           The second token for this path match.
       *
       * @return   {@code true} if specified path matches the end of the node's path exactly.
       */
      public boolean relativePath(Aast node, int tok1, int tok2)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.relativePath(tok1, tok2);
      }
      
      /**
       * Determines if the node's path from the root <b>ends with</b> the specified path.
       * <p>
       * The node's type IS included in this path, which is different from the {@link #upPath} which compares
       * paths upward starting at the parent node.
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    tok1
       *           The first token for this path match.
       * @param    tok2
       *           The second token for this path match.
       * @param    tok3
       *           The third token for this path match.
       *
       * @return   {@code true} if specified path matches the end of the node's path exactly.
       */
      public boolean relativePath(Aast node, int tok1, int tok2, int tok3)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.relativePath(tok1, tok2, tok3);
      }
      
      /**
       * Determines if the node's path from the root <b>ends with</b> the specified path.
       * <p>
       * The node's type IS included in this path, which is different from the {@link #upPath} which compares
       * paths upward starting at the parent node.
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    tok1
       *           The first token for this path match.
       * @param    tok2
       *           The second token for this path match.
       * @param    tok3
       *           The third token for this path match.
       * @param    tok4
       *           The fourth token for this path match.
       *
       * @return   {@code true} if specified path matches the end of the node's path exactly.
       */
      public boolean relativePath(Aast node, int tok1, int tok2, int tok3, int tok4)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.relativePath(tok1, tok2, tok3, tok4);
      }
      
      /**
       * Determines if the current source AST's path upward <b>matches</b>
       * the specified path, on a case-insensitive basis. The format of the
       * 'segments' of the up path starts with the parent's type and each
       * subsequent segment must be the type of the next parent up in the
       * hierarchy.
       * <p>
       * The type of the current node is NOT included in the up path.  This
       * is the only difference with {@link #relativePath}.
       *
       * @param    path
       *           Partial path from the current AST's parent upward to an
       *           arbitrary number of ancestor nodes, formatted as described
       *           in {@link Aast#getPath}.
       *
       * @return   <code>true</code> if specified path exactly matches the 
       *           upward path from the current source AST's parent, else 
       *           <code>false</code>.
       */
      @Deprecated
      public boolean upPath(String path)
      {
         return getSource().upPath(path);
      }
      
      /**
       * Determines if the node's path upward <b>matches</b> the specified path. The value of the 'segments' of
       * the up path starts with the parent's type and each subsequent segment must be the type of the next
       * parent up in the hierarchy.
       * <p>
       * The type of the node is NOT included in the up path. This is the only difference between this
       * method and {@link #relativePath}.
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    path
       *           Partial path from the node's parent upward to an arbitrary number of ancestor nodes. Must
       *           contain only {@code int} / {@code Integer} values.
       *
       * @return   {@code true} if specified path exactly matches the upward path from the node's parent.
       */
      public boolean upPath(Aast node, Object[] path)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.upPath(path);
      }
      
      /**
       * Determines if the node's path upward <b>matches</b> the specified path. The value of the 'segments' of
       * the up path starts with the parent's type and each subsequent segment must be the type of the next
       * parent up in the hierarchy.
       * <p>
       * The type of the node is NOT included in the up path. This is the only difference between this
       * method and {@link #relativePath}.
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    tok1
       *           The first token for this path match.
       *
       * @return   {@code true} if specified path exactly matches the upward path from the node's parent.
       */
      public boolean upPath(Aast node, int tok1)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.upPath(tok1);
      }
      
      /**
       * Determines if the node's path upward <b>matches</b> the specified path. The value of the 'segments' of
       * the up path starts with the parent's type and each subsequent segment must be the type of the next
       * parent up in the hierarchy.
       * <p>
       * The type of the node is NOT included in the up path. This is the only difference between this
       * method and {@link #relativePath}.
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    tok1
       *           The first token for this path match.
       * @param    tok2
       *           The second token for this path match.
       *
       * @return   {@code true} if specified path exactly matches the upward path from the node's parent.
       */
      public boolean upPath(Aast node, int tok1, int tok2)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.upPath(tok1, tok2);
      }
      
      /**
       * Determines if the node's path upward <b>matches</b> the specified path. The value of the 'segments' of
       * the up path starts with the parent's type and each subsequent segment must be the type of the next
       * parent up in the hierarchy.
       * <p>
       * The type of the node is NOT included in the up path. This is the only difference between this
       * method and {@link #relativePath}.
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    tok1
       *           The first token for this path match.
       * @param    tok2
       *           The second token for this path match.
       * @param    tok3
       *           The third token for this path match.
       *
       * @return   {@code true} if specified path exactly matches the upward path from the node's parent.
       */
      public boolean upPath(Aast node, int tok1, int tok2, int tok3)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.upPath(tok1, tok2, tok3);
      }
      
      /**
       * Determines if the node's path upward <b>matches</b> the specified path. The value of the 'segments' of
       * the up path starts with the parent's type and each subsequent segment must be the type of the next
       * parent up in the hierarchy.
       * <p>
       * The type of the node is NOT included in the up path. This is the only difference between this
       * method and {@link #relativePath}.
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    tok1
       *           The first token for this path match.
       * @param    tok2
       *           The second token for this path match.
       * @param    tok3
       *           The third token for this path match.
       * @param    tok4
       *           The fourth token for this path match.
       *
       * @return   {@code true} if specified path exactly matches the upward path from the node's parent.
       */
      public boolean upPath(Aast node, int tok1, int tok2, int tok3, int tok4)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.upPath(tok1, tok2, tok3, tok4);
      }
      
      /**
       * Determines if the current source AST's path downward <b>matches</b>
       * the specified path, on a case-insensitive basis. More than 1 down
       * path may match this specification, if any such match exists then
       * this function will return <code>true</code>.  The format of the
       * 'segments' of the down path starts with the immediate child's type
       * and each subsequent segment must be the type of the next child
       * down in the hierarchy.  The type of the current node is NOT included
       * in the down path.
       *
       * @param    path
       *           Partial path from the current AST down to an arbitrary
       *           number of descendant nodes, formatted as described in
       *           {@link Aast#getPath}.
       *
       * @return   <code>true</code> if specified path exactly matches the 
       *           downward path from the current source AST, else 
       *           <code>false</code>.
       */
      @Deprecated
      public boolean downPath(String path)
      {
         return getSource().downPath(path);
      }
      
      /**
       * Determines if the node's path downward <b>matches</b> the specified path. More than 1 down path may
       * match this specification since sibling nodes can have the same token type. If any such match exists
       * then this function will return {@code true}.
       * <p>
       * The list of the 'segments' of the down path starts with the immediate child's type and each
       * subsequent segment must be the type of the next child down in the hierarchy. The type of the current
       * node is NOT included in the down path.
       * <p>
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    path
       *           Partial path from the node down to an arbitrary number of descendant nodes. Must contain
       *           only {@code int} / {@code Integer} values.
       *
       * @return   {@code true} if specified path exactly matches the downward path from the node.
       */
      public boolean downPath(Aast node, Object[] path)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.downPath(path);
      }
      
      /**
       * Determines if the node's path downward <b>matches</b> the specified path. More than 1 down path may
       * match this specification since sibling nodes can have the same token type. If any such match exists
       * then this function will return {@code true}.
       * <p>
       * The list of the 'segments' of the down path starts with the immediate child's type and each
       * subsequent segment must be the type of the next child down in the hierarchy. The type of the current
       * node is NOT included in the down path.
       * <p>
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    tok1
       *           The first token for this path match.
       *
       * @return   {@code true} if specified path exactly matches the downward path from the node.
       */
      public boolean downPath(Aast node, int tok1)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.downPath(tok1);
      }
      
      /**
       * Determines if the node's path downward <b>matches</b> the specified path. More than 1 down path may
       * match this specification since sibling nodes can have the same token type. If any such match exists
       * then this function will return {@code true}.
       * <p>
       * The list of the 'segments' of the down path starts with the immediate child's type and each
       * subsequent segment must be the type of the next child down in the hierarchy. The type of the current
       * node is NOT included in the down path.
       * <p>
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    tok1
       *           The first token for this path match.
       * @param    tok2
       *           The second token for this path match.
       *
       * @return   {@code true} if specified path exactly matches the downward path from the node.
       */
      public boolean downPath(Aast node, int tok1, int tok2)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.downPath(tok1, tok2);
      }
      
      /**
       * Determines if the node's path downward <b>matches</b> the specified path. More than 1 down path may
       * match this specification since sibling nodes can have the same token type. If any such match exists
       * then this function will return {@code true}.
       * <p>
       * The list of the 'segments' of the down path starts with the immediate child's type and each
       * subsequent segment must be the type of the next child down in the hierarchy. The type of the current
       * node is NOT included in the down path.
       * <p>
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    tok1
       *           The first token for this path match.
       * @param    tok2
       *           The second token for this path match.
       * @param    tok3
       *           The third token for this path match.
       *
       * @return   {@code true} if specified path exactly matches the downward path from the node.
       */
      public boolean downPath(Aast node, int tok1, int tok2, int tok3)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.downPath(tok1, tok2, tok3);
      }
      
      /**
       * Determines if the node's path downward <b>matches</b> the specified path. More than 1 down path may
       * match this specification since sibling nodes can have the same token type. If any such match exists
       * then this function will return {@code true}.
       * <p>
       * The list of the 'segments' of the down path starts with the immediate child's type and each
       * subsequent segment must be the type of the next child down in the hierarchy. The type of the current
       * node is NOT included in the down path.
       * <p>
       * <p>NOTE:<br>
       * This method honors the TRPL's "Automatic Null Checking" and returns {@code false} if the first
       * parameter is {@code null}.
       *
       * @param    node
       *           The node to be checked.
       * @param    tok1
       *           The first token for this path match.
       * @param    tok2
       *           The second token for this path match.
       * @param    tok3
       *           The third token for this path match.
       * @param    tok4
       *           The fourth token for this path match.
       *
       * @return   {@code true} if specified path exactly matches the downward path from the node.
       */
      public boolean downPath(Aast node, int tok1, int tok2, int tok3, int tok4)
      {
         if (node == null)
         {
            return false;
         }
         
         return node.downPath(tok1, tok2, tok3, tok4);
      }
      
      /**
       * Implementation of the <code>ancestor</code> user function. Indicates
       * whether a token type exists within the ancestry of the current source
       * AST.
       *
       * @param   type
       *          Token type to be matched.
       * @param   maxLevels
       *          Maximum number of generations up the tree to search for an 
       *          ancestor.  Set to <code>-1</code> to search up to the tree 
       *          root, <code>0</code> for the current node, <code>1</code>
       *          for the parent, <code>2</code> for the 'grandparent' and so
       *          forth.
       *
       * @return  <code>true</code> if <code>token</code> was found as an
       *          ancestor's token type within <code>maxLevels</code>
       *          generations, else <code>false</code>.
       */
      public boolean ancestor(long type, long maxLevels)
      {
         return getSource().ancestor((int) maxLevels, (int) type);
      }
      
      /**
       * Implementation of the <code>descendant</code> user function.
       * Searches the current source AST's descendants for a token type
       * match.
       *
       * @param   type
       *          Token type to be matched.
       * @param   maxLevels
       *          Number of generations to search before abandoning the test.
       *          Set to <code>0</code> to search exhaustively.
       *
       * @return  <code>true</code> if <code>token</code> was found as an
       *          descendant's token type within <code>maxLevels</code>
       *          generations, else <code>false</code>.
       */
      public boolean descendant(long type, long maxLevels)
      {
         return getSource().descendant((int) maxLevels, (int) type);
      }
      
      /**
       * Compares the current source AST's immediate descendant (direct
       * child) at a specific 0-based index for a match with a specific token
       * type.
       *
       * @param   type
       *          Token type to be matched.
       * @param   index
       *          0-based index of the child to test.
       *
       * @return  <code>true</code> if <code>token</code> was found in a
       *          specific child's token type, else <code>false</code> if
       *          no child exists at that index or if the child does not
       *          match the token type.
       */
      public boolean childAt(long type, long index)
      {
         // start at the first child (index 0)
         Aast child = (Aast) getSource().getFirstChild();
         
         // find the specific child index requested
         for (int i = 0; child != null && i < index; i++) 
         {
            child = (Aast) child.getNextSibling();
         }
         
         // test if the child at that index exists and if the type matches
         if (child != null && child.getType() == type)
         {
            return true;
         }
         
         return false;
      }
      
      /**
       * Test whether any of the current AST's siblings matches the given
       * token type.
       *
       * @param   type
       *          Token type to match.
       *
       * @return  <code>true</code> if any of the current AST's siblings
       *          match, else <code>false</code>.
       */
      public boolean sibling(long type)
      {
         return getSource().sibling(type);
      }
      
      /**
       * Returns the number of siblings to the current AST's left.
       * <p>
       * Corresponds with runtime variable <code>leftSiblings</code>.
       *
       * @return  The number of left siblings or 0 if this AST is the first
       *          child or has no parent (i.e. it is the tree root).
       */
      public long getLeftSiblings()
      {
         return getSource().getNumLeftSiblings();
      }
      
      /**
       * Returns the number of siblings to the current AST's right.
       * <p>
       * Corresponds with runtime variable <code>rightSiblings</code>.
       *
       * @return  The number of right siblings or 0 if this AST is the last
       *          child or has no parent (i.e. it is the tree root).
       */
      public long getRightSiblings()
      {
         return getSource().getNumRightSiblings();
      }
      
      /**
       * Returns the 0-based child index of the current AST.
       * <p>
       * Corresponds with runtime variable <code>childIndex</code>.
       *
       * @return  The number of left siblings or 0 if this AST is the
       *          first child or has no parent (i.e. it is the tree root).
       */
      public long getChildIndex()
      {
         return getLeftSiblings();
      }
      
      /**
       * Add the current copy AST to the filtered view of ASTs which can
       * be pipelined to the next ruleset.
       *
       * @return  <code>true</code>
       */
      public boolean addToView()
      {
         getResolver().addToView();
         
         return true;
      }
      
      /**
       * Add the current copy AST to the filtered view of ASTs which can
       * be pipelined to the next ruleset.
       *
       * @param    list
       *           The nodes to add to the view.
       *
       * @return   <code>true</code>
       */
      public boolean addAllToView(List<Aast> list)
      {
         getResolver().addAllToView(list);
         
         return true;
      }
      
      /**
       * Add the AST specified by the passed ID to the filtered view of ASTs
       * which can be pipelined to the next ruleset.  The AST will only be
       * added if it exists in the current copy of the source tree.
       *
       * @return  <code>true</code> if the AST is valid (and thus was added),
       *          <code>false</code> otherwise.
       */
      public boolean addToView(Long id)
      {
         try
         {
            AstManager mgr = AstManager.get();
            
            long treeId = mgr.getTreeId(getSource().getId());
            long astId  = mgr.getTreeId(id);
            
            if (treeId != astId)
            {
               return false;
            }
         }
         
         catch (AstException ae)
         {
            // some problem
            return false;
         }
         
         return getResolver().addToView(id);
      }
      
      /**
       * Add the parent of the current copy AST to the filtered view of
       * ASTs which can be pipelined to the next ruleset, if the parent
       * node is <b>not</b> <code>null</code>.
       *
       * @return  <code>true</code>
       */
      public boolean addParentToView()
      {
         getResolver().addParentToView();
         
         return true;
      }      
      
      /**
       * Add a specified ancestor of the current copy AST to the filtered
       * view of ASTs which can be pipelined to the next ruleset, if the
       * all nodes in the ancestry chain (up to and including the specified
       * level) are <b>not</b> <code>null</code>.
       *
       * @param    level
       *           Identifies the ancestor by the number of generations 'up' 
       *           the tree from the current node. 0 specifies the current 
       *           node, 1 specifies the parent node, 2 specifies the
       *           'grandparent' node and so forth.
       *
       * @return  <code>true</code>
       */
      public boolean addAncestorToView(long level)
      {
         getResolver().addAncestorToView((int) level);
         
         return true;
      }      
      
      /**
       * Add the current copy AST, and all of its descendants through
       * <code>maxLevels</code> generations, to the filtered view of ASTs
       * which can be pipelined to the next ruleset.
       *
       * @param   maxLevels
       *          Number of descendant generations to add. To add only the
       *          current AST, specify <code>1</code> (or better yet, use
       *          {@link #addToView}).
       *
       * @return   <code>true</code>
       */
      public boolean addTreeToView(long maxLevels)
      {
         getResolver().addTreeToView((int) maxLevels);
         
         return true;
      }
      
      /**
       * Add to the filtered view of ASTs all of the nodes which form the
       * path from an ancestor node of type <code>type</code> to the current
       * copy AST. The nodes are added in root to leaf order. If an
       * ancestor node of type <code>type</code> does not exist, no nodes
       * are added (not even the current one).
       *
       * @param   type
       *          Ancestor token type which marks the beginning of the path.
       *
       * @return  <code>true</code> if an ancestor of type <code>type</code>
       *          was found and the path of nodes was successfully added to
       *          the filtered view, else <code>false</code>.
       */
      public boolean addPathToViewFrom(long type)
      {
         return getResolver().addPathToViewFrom(type);
      }
      
      /**
       * Prints a string representation of the current source AST to stdout,
       * indented appropriately to indicate its depth in the AST structure.
       *
       * @return  <code>true</code>
       */
      public boolean printAst()
      {
         getResolver().printAst();
         
         return true;
      }
      
      /**
       * Removes the current AST node from the <em>copy</em> AST.
       *
       * @return  <code>true</code>
       */
      public boolean remove()
      {
         getCopy().remove();
         
         return true;
      }
      
      /**
       * Get the character at the specified index, as a string.
       * 
       * @param    text
       *           The source text.
       * @param    index
       *           The character's index.
       *           
       * @return   See above.
       */
      public String charAt(String text, int index)
      {
         return "" + text.charAt(index);
      }
      
      /**
       * Uppercase and return the given string.
       *
       * @param   text
       *          The string to uppercase.
       *
       * @return  <code>text</code> string in all upper case.
       */
      public String upper(String text)
      {
         return text.toUpperCase();
      }
      
      /**
       * Lowercase and return the given string.
       *
       * @param   text
       *          The string to lowercase.
       *
       * @return  <code>text</code> string in all lower case.
       */
      public String lower(String text)
      {
         return text.toLowerCase();
      }
      
      /**
       * Convert a numeric value to its default string representation.
       *
       * @param   value
       *          Number to convert.
       *
       * @return  Default string representation.
       */
      public String string(long value)
      {
         return String.valueOf(value);
      }
      
      /**
       * Convert a numeric value to its default string representation.
       *
       * @param   value
       *          Number to convert.
       *
       * @return  Default string representation.
       */
      public String string(double value)
      {
         return String.valueOf(value);
      }
      
      /**
       * Convert a boolean value to its default string representation.
       *
       * @param   value
       *          Number to convert.
       *
       * @return  Default string representation.
       */
      public String string(boolean value)
      {
         return String.valueOf(value);
      }
      
      /**
       * Returns the portion of the input string from the 0-based index
       * to the end.
       *
       * @param    text
       *           The input string.
       * @param    index
       *           The character at which to start the substring.
       *
       * @return   The portion of the string specified.
       */
      public String substring(String text, long index)
      {
         return text.substring((int) index);
      }
      
      /**
       * Returns the portion of the input string from the 0-based index
       * to the end.
       *
       * @param    text
       *           The input string.
       * @param    begin
       *           The character at which to start the substring.
       * @param    end
       *           The character before which to end the substring.
       *
       * @return   The portion of the string specified.
       */
      public String substring(String text, long begin, long end)
      {
         return text.substring((int) begin, (int) end);
      }
      
      /**
       * Returns the 0-based index of the first character that matches
       * the find string in the input string or -1 if no match was found.
       *
       * @param    text
       *           The input string.
       * @param    find
       *           The string to search for.
       *
       * @return   The index of the beginning of the match.
       */
      public long indexOf(String text, String find)
      {
         return (long) text.indexOf(find);
      }
      
      /**
       * Returns the 0-based index of the first character that matches
       * the find string after the starting point in the input string or -1
       * if no match was found.
       *
       * @param    text
       *           The input string.
       * @param    find
       *           The string to search for.
       * @param    start
       *           The starting index from which to search.
       *
       * @return   The index of the beginning of the match.
       */
      public long indexOf(String text, String find, long start)
      {
         return text.indexOf(find, (int) start);
      }
      
      /**
       * Returns the 0-based index of the first character of the last match
       * to the find string in the input string or -1 if no match was found.
       * This match algorithm works backward.
       *
       * @param    text
       *           The input string.
       * @param    find
       *           The string to search for.
       *
       * @return   The index of the beginning of the match.
       */
      public long lastIndexOf(String text, String find)
      {
         return text.lastIndexOf(find);
      }
      
      /**
       * Returns the 0-based index of the first character of the last match
       * of the find string before the starting point in the input string or
       * -1 if no match was found.  This match algorithm works backward.
       *
       * @param    text
       *           The input string.
       * @param    find
       *           The string to search for.
       * @param    start
       *           The starting index from which to search backward.
       *
       * @return   The index of the beginning of the match.
       */
      public long lastIndexOf(String text, String find, long start)
      {
         return text.lastIndexOf(find, (int) start);
      }
      
      /**
       * Returns the length in characters of the given string.
       *
       * @param    text
       *           The input string.
       *
       * @return   The length of the string in characters.
       */
      public long stringLength(String text)
      {
         return text.length();
      }
      
      /**
       * Returns a string that is comprised of the input string concatenated
       * <code>num</code> times without intervening spaces.
       *
       * @param    text
       *           The input string.
       * @param    num
       *           The number of times to repeat the input string in the
       *           output string.
       *
       * @return   The concatenated string.
       */
      public String copies(String text, int num)
      {
         StringBuilder sb = new StringBuilder();
         
         for (int i = 0; i < num; i++)
            sb.append(text);
         
         return sb.toString();
      }
      
      /**
       * Wrap a text string to fit into one or more lines of the specified
       * width. Expects the space character to delimit the words in the
       * input string. Returns a list of as many strings as are necessary
       * to meet the per-line width requirement. Single words that are wider
       * than <code>width</code> are <em>not</em> split, but will appear in
       * their own line. All lines are left-aligned within the alloted width.
       *
       * @param   text
       *          Text to be split.
       * @param   width
       *          Per-line width in characters.
       *
       * @return  List of strings representing <code>text</code> split into
       *          lines of the specified width.
       */
      public List wrapText(String text, int width)
      {
         return Arrays.asList(StringHelper.wrapText(text, width));
      }
      
      /**
       * Strip enclosing double or single quotes, if any, plus any options, from the given string.
       *
       * @param   text
       *          String optionally enclosed in quotes.
       *
       * @return  A string based on <code>text</code>, but with quotes
       *          removed.
       */
      public String removeQuotes(String text)
      {
         // are we starting with ' or " 
         char lead = text.charAt(0);
         
         // if not a quote, return original
         if (lead != '"' && lead != '\'')
            return text;

         String contents = null;

         try
         {   
            // this will always work as long as the input string is valid
            contents = text.substring(1, text.lastIndexOf(lead));
         }

         catch (IndexOutOfBoundsException iob)
         {
            throw new IllegalArgumentException("Missing closing quote (" +
                                               lead + ").");
         }
         
         return contents;
      }
      
      /**
       * Prepare the given text so that its quotes can be switched from the previous quote to the
       * new quotes.
       * <p>
       * This includes un-escaping and un-doubling the previous quote char and escaping the new
       * quote char.
       * 
       * @param    text
       *           The text to have its quotes switched.
       * @param    prevQuoteChar
       *           The previous quote.
       * @param    newQuoteChar
       *           The new quote.
       *           
       * @return   The prepared string.
       */
      public String switchQuotes(String text, String prevQuoteChar, String newQuoteChar)
      {
         StringBuilder res = new StringBuilder();
         
         char pq = prevQuoteChar.charAt(0);
         char nq = newQuoteChar.charAt(0);
         
         // the text has already had its quotes removed
         // ensure the previous quote is un-escaped or un-doubled in the text
         int i = 0;
         while (i < text.length())
         {
            char ch = text.charAt(i);
            
            if (ch == '~' || ch == '\\' || ch == pq)
            {
               i = i + 1;
               char chNext = text.charAt(i);
               
               if (chNext != pq)
               {
                  // append only if prev quote is not escaped or doubled
                  res.append(ch);
               }
               
               res.append(chNext);
            }
            else if (ch == nq)
            {
               // the new quote, if is not already escaped, needs to be escaped
               res.append('~');
               res.append(nq);
            }
            else
            {
               res.append(ch);
            }

            i = i + 1;
         }
         
         return res.toString();
      }

      /**
       * Trim leading whitespace from <code>text</code>, without triggering
       * a <code>NullPointerException</code> if <code>text</code> is
       * <code>null</code>.
       *
       * @param   text
       *          Text string to be trimmed.
       *
       * @return  <code>text</code> with leading whitespace removed, or 
       *          <code>null</code> if <code>text</code> is <code>null</code>.
       */
      public String trimLeading(String text)
      {
         return StringHelper.safeTrimLeading(text);
      }
      
      /**
       * Trim trailing whitespace (space, tab, CR and LF) from {@code text}, without triggering a
       * {@code NullPointerException} if {@code text} is {@code null}.
       *
       * @param   text
       *          Text string to be trimmed.
       *
       * @return  {@code text} with trailing whitespace removed, or {@code null} if {@code text}
       *          is {@code null}.
       */
      public String trimTrailing(String text)
      {
         return StringHelper.safeTrimTrailing(text);
      }
      
      /**
       * Trim only trailing spaces from {@code text}, without triggering a
       * {@code NullPointerException} if {@code text} is {@code null}.
       *
       * @param   text
       *          Text string to be trimmed.
       *
       * @return  {@code text} with trailing whitespace removed, or {@code null} if {@code text}
       *          is {@code null}.
       */
      public String trimTrailingSpaces(String text)
      {
         return StringHelper.safeTrimTrailingSpaces(text);
      }
      
      /**
       * Parse a <code>Long</code> from <code>text</code> and return it.
       *
       * @param   text
       *          String representing a base-10 <code>long</code> value.
       *
       * @return  <code>text</code> as a <code>Long</code>.
       *
       * @throws  NumberFormatException
       *          if <code>text</code> cannot be parsed into a long.
       */
      public Long parseLong(String text)
      throws NumberFormatException
      {
         return (Long.valueOf(text));
      }
      
      /**
       * Parse a <code>Long</code> from <code>text</code> using the given
       * radix, and return it.
       *
       * @param   text
       *          String representing a <code>long</code> value with the
       *          specified radix.
       * @param   radix
       *          Radix to be used interpreting <code>text</code>.
       *
       * @return  <code>text</code> as a <code>Long</code>.
       *
       * @throws  NumberFormatException
       *          if <code>text</code> cannot be parsed into a long.
       */
      public Long parseLong(String text, int radix)
      throws NumberFormatException
      {
         return Long.valueOf(text, radix);
      }
      
      /**
       * Parse a <code>Long</code> from <code>text</code> and return it.  Do
       * not raise an exception if <code>text</code> cannot be parsed.
       *
       * @param   text
       *          String representing a base-10 <code>long</code> value.
       *
       * @return  <code>text</code> as a <code>Long</code>, or
       *          <code>null</code> if <code>text</code> cannot be parsed into
       *          a <code>Long</code>.
       */
      public Long parseLongQuiet(String text)
      {
         return ExpressionConversionWorker.parseLongQuiet(text);
      }
      
      /**
       * Parse a <code>Long</code> from <code>text</code> using the given
       * radix, and return it.  Do not raise an exception if <code>text</code>
       * cannot be parsed.
       *
       * @param   text
       *          String representing a <code>long</code> value with the
       *          specified radix.
       * @param   radix
       *          Radix to be used interpreting <code>text</code>.
       *
       * @return  <code>text</code> as a <code>Long</code>, or
       *          <code>null</code> if <code>text</code> cannot be parsed into
       *          a <code>Long</code>.
       */
      public Long parseLongQuiet(String text, int radix)
      {
         return ExpressionConversionWorker.parseLongQuiet(text, radix);
      }
      
      /**
       * Parse a <code>Double</code> from <code>text</code> and return it.
       *
       * @param   text
       *          String representing a <code>double</code> value.
       *
       * @return  <code>text</code> as a <code>Double</code>.
       *
       * @throws  NumberFormatException
       *          if <code>text</code> cannot be parsed into a double.
       */
      public Double parseDouble(String text)
      throws NumberFormatException
      {
         return (Double.valueOf(text));
      }
      
      /**
       * Parse a <code>Double</code> from <code>text</code> and return it.
       * Do not raise an exception if <code>text</code> cannot be parsed.
       *
       * @param   text
       *          String representing a <code>double</code> value.
       *
       * @return  <code>text</code> as a <code>Double</code> or
       *          <code>null</code> if <code>text</code> cannot be parsed.
       */
      public Double parseDoubleQuiet(String text)
      {
         try
         {
            return (Double.valueOf(text));
         }
         catch (NumberFormatException exc)
         {
            return null;
         }
      }
      
      /**
       * Parse a <code>BigDecimal</code> from <code>text</code> and return it.
       *
       * @param   text
       *          String representing a decimal value.
       *
       * @return  <code>text</code> as a <code>BigDecimal</code>.
       *
       * @throws  NumberFormatException
       *          if <code>text</code> cannot be parsed into a decimal.
       */
      public BigDecimal parseBigDecimal(String text)
      {
         return new BigDecimal(text);
      }
      
      /**
       * Get the NOW value.  Used by import to resolve the NOW function at the field's inital value.
       * 
       * @return   See above.
       */
      public datetimetz now()
      {
         return datetimetz.now();
      }
      
      /**
       * Get the TODAY value.  Used by import to resolve the TODAY function at the field's inital value.
       * 
       * @return   See above.
       */
      public date today()
      {
         return date.today();
      }
      
      /**
       * Parse the ISO8601 text as a datetimetz literal.  Used by import for the field's initial value.
       * 
       * @param    text
       *           The datetimetz literal in ISO8601 format.
       * 
       * @return   See above.
       */
      public datetimetz parseDatetimetzLiteral(String text)
      {
         return datetimetz.fromLiteral(text);
      }
      
      /**
       * Parse the ISO8601 text as a datetime literal.  Used by import for the field's initial value.
       * 
       * @param    text
       *           The datetime literal in ISO8601 format.
       * 
       * @return   See above.
       */
      public datetime parseDatetimeLiteral(String text)
      {
         return datetime.fromLiteral(text);
      }
      
      /**
       * Convert a <code>double</code> value to a <code>BigDecimal</code>.
       *
       * @param   num
       *          A <code>Double</code> instance to be converted.
       *
       * @return  An exact representation of the input value.
       */
      public BigDecimal toBigDecimal(Double num)
      {
         return new BigDecimal(num);
      }
      
      /**
       * Debug user function implementation to print text to stdout.
       *
       * @param   text
       *          Text to be printed.
       *
       * @return  <code>true</code>.
       */
      public boolean print(String text)
      {
         LOG.log(isRuntimeConfig() ? Level.FINE : Level.INFO, text);

         return true;
      }
      
      /**
       * Debug user function implementation to print text to stdout, followed
       * by a line break.
       *
       * @param   text
       *          Text to be printed.
       *          
       * @return  <code>true</code>.
       */
      public boolean println(String text)
      {
         LOG.log(isRuntimeConfig() ? Level.FINE : Level.INFO, text);
         
         return true;
      }
      
      /**
       * Logs a message on the requested level allowing formatting.
       *
       * @param   levelString
       *          The string representation of a java.util.logging.Level.
       * @param   format
       *          A format string using C-like <code>sprintf</code> syntax
       *          for its substitution placeholders.
       * @param   args
       *          An array of objects used as formatting parameters.
       *
       * @return  <code>true</code>.
       */
      public boolean log(String levelString, String format, Object... args)
      {
         Level level = LoggingUtil.convertToLevel(levelString).orElse(Level.FINE);
         String fmt = StringHelper.processEscapes(format);
         String msg = (args == null || args.length == 0) ? fmt : String.format(fmt, args);
         LOG.log(level, msg);
         
         return true;
      }
      
      /**
       * Persist the copy AST to file, using the default persistence naming
       * convention. This will overwrite any existing file of the same name
       * without complaint or warning.
       *
       * @return  <code>true</code>.
       *
       * @throws  ConfigurationException
       *          if <code>P2J_HOME</code> has not been defined for this
       *          process.
       * @throws  AstException
       *          if any error occurs persisting the AST.
       */
      public boolean persist()
      throws ConfigurationException,
             AstException
      {
         Aast root = getCopy().getAncestor(-1);
         
         StringBuilder buf = new StringBuilder(root.getFilename());
         buf.append(AstGenerator.AST_POSTFIX);
         String fname = Configuration.normalizeFilename(buf.toString());
         
         AstManager.get().saveTree(root, fname, false);
         
         return true;
      }
      
      /**
       * Persist the copy AST to file, using the default persistence naming
       * convention. This will overwrite any existing file of the same name
       * without complaint or warning.
       *
       * @param   filename
       *          The file to persist.
       * @param   projectRelative
       *          <code>true</code> if the filename is relative to the
       *          project home directory, <code>false</code> if the filename 
       *          is absolute or relative to the current directory.
       *
       * @return  <code>true</code>.
       *
       * @throws  ConfigurationException
       *          if <code>P2J_HOME</code> has not been defined for this
       *          process.
       * @throws  AstException
       *          if any error occurs persisting the AST.
       */
      public boolean persist(String filename, boolean projectRelative)
      throws ConfigurationException,
             AstException
      {
         return persist(filename, projectRelative, false);
      }
      
      /**
       * Persist the copy AST to file, using the default persistence naming
       * convention. This will overwrite any existing file of the same name
       * without complaint or warning.
       *
       * @param   filename
       *          The file to persist.
       * @param   projectRelative
       *          <code>true</code> if the filename is relative to the
       *          project home directory, <code>false</code> if the filename 
       *          is absolute or relative to the current directory.
       * @param   terse
       *          <code>true</code> to persist the AST in terse mode,
       *          otherwise <code>false</code>.
       *
       * @return  <code>true</code>.
       *
       * @throws  ConfigurationException
       *          if <code>P2J_HOME</code> has not been defined for this
       *          process.
       * @throws  AstException
       *          if any error occurs persisting the AST.
       */
      public boolean persist(String  filename,
                             boolean projectRelative,
                             boolean terse)
      throws ConfigurationException,
             AstException
      {
         Aast root = getCopy().getAncestor(-1);

         return persist(root, filename, projectRelative, terse);
      }

      
      /**
       * Persist the specified AST to file, using the default persistence naming
       * convention. This will overwrite any existing file of the same name
       * without complaint or warning.
       *
       * @param   root
       *          The root AST.
       * @param   filename
       *          The file to persist.
       * @param   projectRelative
       *          <code>true</code> if the filename is relative to the
       *          project home directory, <code>false</code> if the filename 
       *          is absolute or relative to the current directory.
       * @param   terse
       *          <code>true</code> to persist the AST in terse mode,
       *          otherwise <code>false</code>.
       *
       * @return  <code>true</code>.
       *
       * @throws  ConfigurationException
       *          if <code>P2J_HOME</code> has not been defined for this
       *          process.
       * @throws  AstException
       *          if any error occurs persisting the AST.
       */
      public boolean persist(Aast    root,
                             String  filename,
                             boolean projectRelative,
                             boolean terse)
      throws ConfigurationException,
             AstException
      {
         if (projectRelative)
         {
            filename = Configuration.normalizeFilename(filename);
         }
         
         AstManager.get().saveTree(root, filename, terse);
         
         return true;
      }
      
      /**
       * Add the given AST as a new program file, by registering its filename.
       * 
       * @param    root
       *           The AST root.
       */
      public void addNewProgramFile(Aast root)
      {
         engine.addNewProgramFile(root.getFilename());
      }
      
      /**
       * Clear the list of newly, generated, {@link PatternEngine#newProgramFiles program file 
       * names}.
       */
      public void clearNewProgramFiles()
      {
         engine.clearNewProgramFiles();
      }

      /**
       * Debug method to print a stack trace dump to <code>stderr</code>.
       *
       * @return  <code>true</code>.
       */
      public boolean dumpStack()
      {
         Thread.dumpStack();
         
         return true;
      }
      
      /**
       * Throws a {@link CompileException} with a user-defined message. This will halt pattern
       * engine processing.
       * <p>
       * Data associated with the currently processing AST node will NOT be included in the
       * formatted message.
       * <p>
       * Both {@code id} and {@code message} should match one of the Progress' set of compile
       * errors, so it would be properly recognized by the dynamic conversion module.
       *
       * @param   id
       *          The identifier of the error message.
       * @param   message
       *          Error message to include in the exception's text.
       * @param   ast
       *          The AST that caused the error.
       * 
       * @throws  CompileException
       *          Always, with the provided information.
       */
      public void compileError(int id, String message, Aast ast)
      throws CompileException
      {
         throw new CompileException(id, message, ast);
      }
      
      /**
       * Throws a {@link CompileException} with a user-defined message. This will halt pattern
       * engine processing.
       * <p>
       * Data associated with the currently processing AST node will NOT be included in the
       * formatted message.
       * <p>
       * Both {@code id} and {@code message} should match one of the Progress' set of compile
       * errors, so it would be properly recognized by the dynamic conversion module.
       *
       * @param   id
       *          The identifier of the error message.
       * @param   message
       *          Error message to include in the exception's text.
       * 
       * @throws  CompileException
       *          Always, with the provided information.
       */
      public void compileError(int id, String message)
      throws CompileException
      {
         compileError(id, message, getSource());
      }
      
      /**
       * Throws a {@link UserGeneratedException} with a user-defined message. This will halt pattern engine
       * processing.
       * <p>
       * Data associated with the currently processing AST node will be
       * included in the formatted message.
       * <p>
       * Use this method if the error you encounter is a P2J specific issue. If it is an error
       * detected in code, use the {@link #compileError(int, String)} instead, with the
       * parameters compatible to P4GL compiler error set.
       *
       * @param   message
       *          Error message to include in the exception's text.
       */
      public void throwException(String message)
      throws UserGeneratedException
      {
         throwException(message, getSource());
      }
      
      /**
       * Throws a {@link UserGeneratedException} with a user-defined message. This will halt pattern engine
       * processing.
       *
       * @param   message
       *          Error message.
       * @param   ast
       *          AST at which failure occurred.
       */
      public void throwException(String message, Aast ast)
      throws UserGeneratedException
      {
         throw new UserGeneratedException(message, ast);
      }
      
      /**
       * Given a format string using C-like <code>printf</code> syntax, and
       * an array of runtime replacement arguments, perform formatted variable
       * substitution and return the resulting string.
       *
       * @param   format
       *          A format string using C-like <code>sprintf</code> syntax
       *          for its substitution placeholders.
       * @param   args
       *          An array of objects which must contain only the following
       *          types:
       *          <ul>
       *          <li>java.lang.String
       *          <li>java.lang.Long
       *          <li>java.lang.Double
       *          </ul>
       *          These are the values to be substituted into the format
       *          string at placeholders of the appropriate, matching type.
       *
       * @return  A string which is based upon the format string, with values
       *          from <code>args</code> substituted in for the appropriate,
       *          respective placeholder variables.
       */
      public String sprintf(String format, Object[] args)
      {
         String fmt = StringHelper.processEscapes(format);
         
         return String.format(fmt, args);
      }
      
      /**
       * Given a format string using C-like <code>printf</code> syntax, and
       * an array of runtime replacement arguments, perform formatted variable
       * substitution and APPEND the resulting string to a named file. The
       * file will be opened and closed in this method, such that no state
       * is maintained between calls.
       *
       * @param    filename
       *           The file to which the resulting string will be appended.
       *           If the file doesn't exist it will be created.  The filename
       *           MUST be specified in a format that is relative to the
       *           project root.
       * @param    format
       *           A format string using C-like <code>sprintf</code> syntax
       *           for its substitution placeholders.
       * @param    args
       *           An array of objects which must contain only the following
       *           types:
       *           <ul>
       *           <li>java.lang.String
       *           <li>java.lang.Long
       *           <li>java.lang.Double
       *           </ul>
       *           These are the values to be substituted into the format
       *           string at placeholders of the appropriate, matching type.
       *
       * @return   <code>true</code>.
       *
       * @throws   ConfigurationException
       *           if <code>P2J_HOME</code> has not been defined for this
       *           process.
       * @throws   IllegalArgumentException
       *           if a stream for the specified filename cannot be created.
       */
      public boolean fprintf(String filename, String format, Object[] args)
      throws ConfigurationException,
             IllegalArgumentException,
             IOException
      {
         String fmt  = StringHelper.processEscapes(format);
         String line = String.format(fmt, args);
         File   file = Configuration.toFile(filename);
         
         PrintStream out;
         
         // if this filename exists as a directory, we can't save results
         if (file.isDirectory())
         {   
            throw new IllegalArgumentException(filename + " is a directory!");
         }
         
         try
         {
            out = new PrintStream( new FileOutputStream(file, true) );
         }
         catch (Exception exc)
         {
            throw new IllegalArgumentException("Cannot create stream for " +
                                               filename + "(" +
                                               exc.toString() + ")!");
         }
         
         out.print(line);
         out.close();
         
         return true;
      }
      
      /**
       * Opens a new stream for writing and associates a unique ID with that
       * stream.  Any path to the target file is created if needed before the
       * stream for that file is opened.
       *
       * @param    filename
       *           The name of the file to be opened.
       * @param    projectRelative
       *           <code>true</code> if the filename is relative to the
       *           project home directory, <code>false</code> if the filename 
       *           is absolute or relative to the current directory.
       *           
       * @return   The unique ID associated with this stream.
       *
       * @throws   ConfigurationException
       *           if <code>P2J_HOME</code> has not been defined for this
       *           process.
       * @throws   IllegalArgumentException
       *           if a stream for the specified filename cannot be created.
       */
      public Long openStream(String filename, boolean projectRelative)
      throws ConfigurationException,
             IllegalArgumentException,
             IOException
      {
         File file = null;
         
         if (projectRelative)
         {
            file = Configuration.toFile(filename);
         }
         else
         {
            file = new File(filename);
         }
         
         ByteCountOutputStream out;
         
         // if this filename exists as a directory, we can't save results
         if (file.isDirectory())
         {   
            throw new IllegalArgumentException(filename + " is a directory!");
         }
         
         try
         {
            // this is a file, get the containing directory
            File parent = file.getParentFile();
            
            if (!parent.exists())
            {
               // the directory doesn't exist, so create it (including all
               // intermediate directories needed
               parent.mkdirs();
            }
            
            // create the file
            FileOutputStream fos = new FileOutputStream(file, true);
            
            // init our counting stream wrapper 
            out = new ByteCountOutputStream(fos);
         }
         catch (Exception exc)
         {
            throw new IllegalArgumentException("Cannot create stream for " +
                                               filename + "(" +
                                               exc.toString() + ")!");
         }
         
         WorkArea wa = context.get();
         Long id = wa.nextId;
         
         wa.streams.put(id, out);
         wa.nextId++;
         
         return id;
      }
      
      /**
       * Closes a stream that was previously opened and which was associated
       * with a specific ID.
       *
       * @param    id
       *           The ID of a stream which muct have previously been opened.
       *
       * @return   Always <code>true</code>.
       */
      public boolean closeStream(Long id)
      throws IOException
      {
         WorkArea wa = context.get();
         ByteCountOutputStream out = wa.streams.remove(id);
         
         if (out != null)
            out.close();
         
         return true;
      }      
      
      /**
       * Returns the number of bytes written to this stream since it was
       * opened.
       *
       * @param    id
       *           The ID of a stream which muct have previously been opened.
       *
       * @return   The number of bytes written since opening the stream or
       *           -1 if the stream doesn't exist.
       */
      public long getNumBytesWritten(Long id)
      {
         WorkArea wa = context.get();
         ByteCountOutputStream out = wa.streams.get(id);
         
         long result = -1;
         
         if (out != null)
            result = out.getBytesWritten();
         
         return result;
      }      
      
      /**
       * Given a format string using C-like <code>printf</code> syntax, and
       * an array of runtime replacement arguments, perform formatted variable
       * substitution and APPEND the resulting string to previously opened
       * stream identified by a numeric ID.  This is a stateful version of 
       * the {@link #fprintf(String,String,Object[])}.  Use the methods
       * {@link #openStream} and {@link #closeStream} to open and close the 
       * stream.
       * <p>
       * The output from this method will be UTF-8 encoded.
       *
       * @param    id
       *           The ID of a stream which muct have previously been opened.
       * @param    format
       *           A format string using C-like <code>sprintf</code> syntax
       *           for its substitution placeholders.
       * @param    args
       *           An array of objects which must contain only the following
       *           types:
       *           <ul>
       *           <li>java.lang.String
       *           <li>java.lang.Long
       *           <li>java.lang.Double
       *           </ul>
       *           These are the values to be substituted into the format
       *           string at placeholders of the appropriate, matching type.
       *
       * @return  Always <code>true</code>.
       *
       * @throws  IllegalArgumentException
       *          if the specified stream does not exist.
       */
      public boolean fprintf(Long id, String format, Object[] args)
      throws IllegalArgumentException,
             IOException
      {
         WorkArea wa = context.get();
         ByteCountOutputStream out = wa.streams.get(id); 
         
         if (out == null)
            throw new IllegalArgumentException("Invalid stream ID " + id);
         
         String fmt  = StringHelper.processEscapes(format);
         String line = String.format(fmt, args);
         
         out.write(line.getBytes("UTF-8"));
         
         return true;
      }
      
      /**
       * Prints a string formatted by {@link #sprintf} to <code>stdout</code>
       * with no trailing line break.
       *
       * @param   format
       *          A format string using C-like <code>sprintf</code> syntax
       *          for its substitution placeholders.
       * @param   args
       *          An array of objects which must contain only the following
       *          types:
       *          <ul>
       *          <li>java.lang.String
       *          <li>java.lang.Long
       *          <li>java.lang.Double
       *          </ul>
       *          These are the values to be substituted into the format
       *          string at placeholders of the appropriate, matching type.
       *
       * @return  <code>true</code>.
       */
      public boolean printf(String format, Object[] args)
      {
         String fmt = StringHelper.processEscapes(format);
         LOG.log(isRuntimeConfig() ? Level.FINE : Level.INFO, String.format(fmt, args));
         
         return true;
      }
      
      /**
       * Logs a list of {@link Object}s in {@code ERR} console with a (usually short) description.
       * Optionally, the processing can be suspended if a debugger is attached to process and
       * inspect the logged entities. The advantage of this method is the direct access to
       * objects passed in as parameters, instead of looking them up in the TRPL runtime
       * structures and over the {@link #printf} is the lack of formatting parameter (the 
       * {@link Object#toString()}) used here is powerful enough to display lists, integer and
       * boolean values usually encountered in TRPL code. It also can display any datatypes
       * instead of only 3 supported by {@code printf}.
       * <p>
       * Notes: 
       * <ul>
       *    <li>this method is only used for debugging. It is NOT recommended to use it in
       *        production code;</li>
       *    <li>the method uses the stderr console so the output may not be synchronized with
       *        normal stdout console.</li>
       * </ul>
       * 
       * @param   location
       *          A short message that will displayed in front of the list of other objects.
       * @param   suspend
       *          Activate a special breakpoint location after the values were logged. If this is
       *          {@code false} or no breakpoint was set in debugger, this method behaves like
       *          {@code printfln} to err console that displays the values in a column.
       * @param   args
       *          The list of entities to be displayed. Depending on their type, the output for
       *          each one is different, the emphasis being put on {@link Aast} objects. The items
       *          in this list will be TAB-indented.
       * 
       * @return  always {@code true}.
       */
      public boolean breakpoint(String location, boolean suspend, Object... args)
      {
         LOG.log(Level.FINEST, "#B# " + location);
         for (Object arg : args)
         {
            if (arg instanceof Aast)
            {
               Aast aast = (Aast) arg;
               if (aast.getType() == ProgressParserTokenTypes.EXPRESSION)
               {
                  LOG.log(Level.FINEST, "\t" + aast);
                  aast = (Aast) aast.getFirstChild();
               }
               LOG.log(Level.FINEST, "\t" + aast + " " + aast.getText() + " @" + aast.getLine() + 
                                  ":" + aast.getColumn());
            }
            else
            {
               LOG.log(Level.FINEST, "\t" + arg);
            }
         }
         if (suspend)
         {
            suspend = false; // put the breakpoint on this line
         }
         return true;
      }
      
      /**
       * Prints a string formatted by {@link #sprintf} to <code>stdout</code>
       * with a trailing line break.
       *
       * @param   format
       *          A format string using C-like <code>sprintf</code> syntax
       *          for its substitution placeholders.
       * @param   args
       *          An array of objects which must contain only the following
       *          types:
       *          <ul>
       *          <li>java.lang.String
       *          <li>java.lang.Long
       *          <li>java.lang.Double
       *          </ul>
       *          These are the values to be substituted into the format
       *          string at placeholders of the appropriate, matching type.
       *
       * @return  <code>true</code>.
       */
      public boolean printfln(String format, Object[] args)
      {
         String fmt = StringHelper.processEscapes(format);
         LOG.log(isRuntimeConfig() ? Level.FINE : Level.INFO, String.format(fmt, args));
         
         return true;
      }
      
      /**
       * Return the first ancestor node which matches one of the specified
       * token types.
       *
       * @param   types
       *          Token types to match.  May not be <code>null</code> or
       *          empty.
       *
       * @return  First ancestor to match one of the given types, or
       *          <code>null</code> if no such node was found.
       */
      public Aast getAncestorOfType(Object[] types)
      {
         if (types == null || types.length == 0)
         {
            throw new IllegalArgumentException("No token types were specified");
         }
         
         for (Aast next = getCopy().getParent();
              next != null;
              next = next.getParent())
         {
            if (matchTokenType(next, types))
            {
               return next;
            }
         }
         
         return null;
      }
      
      /**
       * Return the first ancestor node with the specified annotation,
       * optionally refining the match by annotation value and/or token type.
       *
       * @param   key
       *          Annotation key for which to search;  may not be
       *          <code>null</code>.
       * @param   value
       *          Optional annotation value to match.  If <code>null</code>,
       *          a match on the annotation key (and optionally on token
       *          type) is sufficient to constitute success.
       * @param   types
       *          Optional array of <code>Integer</code> values representing
       *          valid token types.  If empty, token type is not considered
       *          as a match criterion.
       *
       * @return  First ancestor node to match the given criteria, or
       *          <code>null</code> if no match was found.
       *
       * @throws  ClassCastException
       *          if <code>types</code> contains any object type other than
       *          <code>Integer</code>.
       */
      public Aast getAncestor(String key, Object value, Object[] types)
      {
         return getAncestor(null, key, value, types);
      }
      
      /**
       * Return the first ancestor node with the specified annotation,
       * optionally refining the match by annotation value and/or token type.
       *
       * @param   index
       *          Index of annotation to compare when a node is found with
       *          the specfied annotation <code>key</code>.  Only valid for
       *          list type annotations;  must be <code>null</code> for
       *          simple annotations.
       * @param   key
       *          Annotation key for which to search;  may not be
       *          <code>null</code>.
       * @param   value
       *          Optional annotation value to match.  If <code>null</code>,
       *          a match on the annotation key (and optionally on token
       *          type) is sufficient to constitute success.
       * @param   types
       *          Optional array of <code>Integer</code> values representing
       *          valid token types.  If empty, token type is not considered
       *          as a match criterion.
       *
       * @return  First ancestor node to match the given criteria, or
       *          <code>null</code> if no match was found.
       *
       * @throws  ClassCastException
       *          if <code>types</code> contains any object type other than
       *          <code>Integer</code>.
       */
      public Aast getAncestor(Integer index,
                              String key,
                              Object value,
                              Object[] types)
      {
         boolean tokMatch = (types != null && types.length > 0);
         
         if (value instanceof Integer)
         {
            // When we retrieve the associated annotation, it will be a Long.
            // value must be of the same type for equals() to work correctly.
            value = Long.valueOf(((Integer) value).intValue());
         }
         
         for (Aast next = getCopy().getParent();
              next != null;
              next = next.getParent())
         {
            if (!next.isAnnotation(key))
            {
               continue;
            }
            
            if (value == null)
            {
               // In this case, we are only checking for the presence of the
               // annotation (and possibly for a token type match), not for a
               // match on the annotation's value.
               if (!tokMatch)
               {
                  return next;
               }
               
               if (matchTokenType(next, types))
               {
                  return next;
               }
               
               continue;
            }
            
            Object note = (index == null
                           ? next.getAnnotation(key)
                           : next.getAnnotation(key, index.intValue()));
            
            if (value.equals(note))
            {
               if (tokMatch)
               {
                  if (matchTokenType(next, types))
                  {
                     return next;
                  }
               }
               else
               {
                  // In this case, we are only checking for an annotation
                  // match and we don't care what token type we found.
                  return next;
               }
            }
         }
         
         return null;
      }
      
      /**
       * Determines if an annotation exists of any <code>String</code>,
       * <code>Double</code>, <code>Long</code>, or <code>Boolean</code>
       * value, indexed by an arbitrary key.
       *
       * @param   key
       *          Key under which the annotation is stored.
       *
       * @return  <code>true</code> if an annotation exists mapped by that
       *          key.
       */
      public boolean isNote(String key)
      {
         return getCopy().isAnnotation(key);
      }
      
      /**
       * Determines the type of an annotation (if it exists) as indexed by an
       * arbitrary key.  The full class name will be returned, e.g.
       * <code>java.lang.String</code>.
       *
       * @param   key
       *          Key under which the annotation is stored.
       *
       * @return  Class name of the annotation or <code>null</code> if the
       *          annotation doesn't exist.
       */
      public String getNoteType(String key)
      {
         Object a = getCopy().getAnnotation(key);
         
         if (a == null)
            return null;
                  
         return a.getClass().getName();
      }   
         
      /**
       * Returns the size of an array annotation as indexed by an arbitrary
       * key.
       *
       * @param   key
       *          Key under which the annotation is stored.
       *
       * @return  The number of elements in an array annotation that exists
       *          mapped by that key or 0 if no such named array annotation
       *          exists.  A scalar annotation using that same name will also
       *          result in a return of 0.
       */
      public long getNoteSize(String key)
      {
         return (long) getCopy().annotationSize(key);
      }
       
      /**
       * Gets a <code>String</code> annotation from the current,
       * <em>copy</em> AST.
       *
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  The desired annotation value, or <code>null</code> if it
       *          does not exist in the current, copy AST.
       */
      public String getNoteString(String key)
      {
         Object a = getCopy().getAnnotation(key);
         
         return (a instanceof String ? (String) a : null);
      }
      
      /**
       * Gets a <code>String</code> annotation at a specific index from an 
       * array annotation in the current <em>copy</em> AST.
       *
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          find the annotation.
       *
       * @return  The annotation object stored under the specified key and at
       *          the specified index, or <code>null</code> if the annotation
       *          doesn't exist, if the annotation is not an array or if no
       *          element exists at the given index.
       */
      public String getNoteString(String key, long index)
      {
         Object a = getCopy().getAnnotation(key, (int)index);
         
         return (a instanceof String ? (String) a : null);
      }
      
      /**
       * Gets a <code>Long</code> annotation from the current,
       * <em>copy</em> AST.
       *
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  The desired annotation value, or <code>null</code> if it
       *          does not exist in the current, copy AST.
       */
      public Long getNoteLong(String key)
      {
         Object a = getCopy().getAnnotation(key);
         
         return (a instanceof Long ? (Long) a : null);
      }
      
      /**
       * Gets a <code>Long</code> annotation at a specific index from an 
       * array annotation in the current <em>copy</em> AST.
       *
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          find the annotation.
       *
       * @return  The annotation object stored under the specified key and at
       *          the specified index, or <code>null</code> if the annotation
       *          doesn't exist, if the annotation is not an array or if no
       *          element exists at the given index.
       */
      public Long getNoteLong(String key, long index)
      {
         Object a = getCopy().getAnnotation(key, (int)index);
         
         return (a instanceof Long ? (Long) a : null);
      }
      
      /**
       * Gets a <code>Double</code> annotation from the current,
       * <em>copy</em> AST.
       *
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  The desired annotation value, or <code>null</code> if it
       *          does not exist in the current, copy AST.
       */
      public Double getNoteDouble(String key)
      {
         Object a = getCopy().getAnnotation(key);
         
         return (a instanceof Double ? (Double) a : null);
      }
      
      /**
       * Gets a <code>Double</code> annotation at a specific index from an 
       * array annotation in the current <em>copy</em> AST.
       *
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          find the annotation.
       *
       * @return  The annotation object stored under the specified key and at
       *          the specified index, or <code>null</code> if the annotation
       *          doesn't exist, if the annotation is not an array or if no
       *          element exists at the given index.
       */
      public Double getNoteDouble(String key, long index)
      {
         Object a = getCopy().getAnnotation(key, (int)index);
         
         return (a instanceof Double ? (Double) a : null);
      }
       
      /**
       * Gets a <code>Boolean</code> annotation from the current,
       * <em>copy</em> AST.
       *
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  The desired annotation value, or <code>null</code> if it
       *          does not exist in the current, copy AST.
       */
      public Boolean getNoteBoolean(String key)
      {
         Object a = getCopy().getAnnotation(key);
         
         return (a instanceof Boolean ? (Boolean) a : null);
      }
      
      /**
       * Gets a <code>Boolean</code> annotation at a specific index from an 
       * array annotation in the current <em>copy</em> AST.
       *
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          find the annotation.
       *
       * @return  The annotation object stored under the specified key and at
       *          the specified index, or <code>null</code> if the annotation
       *          doesn't exist, if the annotation is not an array or if no
       *          element exists at the given index.
       */
      public Boolean getNoteBoolean(String key, long index)
      {
         Object a = getCopy().getAnnotation(key, (int)index);
         
         return (a instanceof Boolean ? (Boolean) a : null);
      }       
       
      /**
       * Gets a <code>String</code> annotation from the specified ancestor
       * in the <em>copy</em> AST.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  The desired annotation value, or <code>null</code> if
       *          either the annotation or the specified ancestor does not
       *          exist.
       */
      public String getAncestorNoteString(long level, String key)
      {
         Aast   ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key);
         
         return (a instanceof String ? (String) a : null);
      }
      
      /**
       * Gets a <code>String</code> annotation at a specific index from an 
       * array annotation in the specified ancestor of the current
       * <em>copy</em> AST.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          find the annotation.
       *
       * @return  The annotation object stored under the specified key and at
       *          the specified index, or <code>null</code> if the annotation
       *          doesn't exist, if the annotation is not an array or if no
       *          element exists at the given index.
       */
      public String getAncestorNoteString(long level, String key, long index)
      {
         Aast   ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key, (int)index);
         
         return (a instanceof String ? (String) a : null);
      }
       
      /**
       * Gets a <code>Long</code> annotation from the specified ancestor
       * in the <em>copy</em> AST.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  The desired annotation value, or <code>null</code> if
       *          either the annotation or the specified ancestor does not
       *          exist.
       */
      public Long getAncestorNoteLong(long level, String key)
      {
         Aast   ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key);
         
         return (a instanceof Long ? (Long) a : null);
      }
      
      /**
       * Gets a <code>Long</code> annotation at a specific index from an 
       * array annotation in the specified ancestor of the current
       * <em>copy</em> AST.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          find the annotation.
       *
       * @return  The annotation object stored under the specified key and at
       *          the specified index, or <code>null</code> if the annotation
       *          doesn't exist, if the annotation is not an array or if no
       *          element exists at the given index.
       */
      public Long getAncestorNoteLong(long level, String key, long index)
      {
         Aast   ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key, (int)index);
         
         return (a instanceof Long ? (Long) a : null);
      }
       
      /**
       * Gets a <code>Double</code> annotation from the specified ancestor
       * in the <em>copy</em> AST.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  The desired annotation value, or <code>null</code> if
       *          either the annotation or the specified ancestor does not
       *          exist.
       */
      public Double getAncestorNoteDouble(long level, String key)
      {
         Aast   ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key);
         
         return (a instanceof Double ? (Double) a : null);
      }
      
      /**
       * Gets a <code>Double</code> annotation at a specific index from an 
       * array annotation in the specified ancestor of the current
       * <em>copy</em> AST.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          find the annotation.
       *
       * @return  The annotation object stored under the specified key and at
       *          the specified index, or <code>null</code> if the annotation
       *          doesn't exist, if the annotation is not an array or if no
       *          element exists at the given index.
       */
      public Double getAncestorNoteDouble(long level, String key, long index)
      {
         Aast   ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key, (int)index);
         
         return (a instanceof Double ? (Double) a : null);
      }
       
      /**
       * Gets a <code>Boolean</code> annotation from the specified ancestor
       * in the <em>copy</em> AST.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  The desired annotation value, or <code>null</code> if
       *          either the annotation or the specified ancestor does not
       *          exist.
       */
      public Boolean getAncestorNoteBoolean(long level, String key)
      {
         Aast   ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key);
         
         return (a instanceof Boolean ? (Boolean) a : null);
      }

      /**
       * Gets a <code>Boolean</code> annotation from the specified ancestor
       * in the <em>copy</em> AST.
       *
       * @param   level
       *          Number of generations up the tree to walk to obtain the
       *          ancestor.  Set to <code>-1</code> to obtain the tree root,
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   defaultValue
       *          When the annotation is not found, this is the value returned from the method.
       *
       * @return  The desired annotation value, or <code>null</code> if
       *          either the annotation or the specified ancestor does not
       *          exist.
       */
      public Boolean getAncestorNoteBoolean(long level, String key, Boolean defaultValue)
      {
         Boolean result = getAncestorNoteBoolean(level, key);
         return result != null ? result : defaultValue;
      }

      /**
       * Gets a <code>Boolean</code> annotation at a specific index from an 
       * array annotation in the specified ancestor of the current
       * <em>copy</em> AST.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          find the annotation.
       *
       * @return  The annotation object stored under the specified key and at
       *          the specified index, or <code>null</code> if the annotation
       *          doesn't exist, if the annotation is not an array or if no
       *          element exists at the given index.
       */
      public Boolean getAncestorNoteBoolean(long level, String key, long index)
      {
         Aast ans = getCopy().getAncestor((int) level);
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key, (int)index);
         
         return (a instanceof Boolean ? (Boolean) a : null);
      }
       
      /**
       * Gets a <code>String</code> annotation from the referenced node
       * in the <em>copy</em> AST.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  The desired annotation value, or <code>null</code> if
       *          either the annotation or the referenced node does not
       *          exist.
       */
      public String getReferenceNoteString(long id, String key)
      {
         Aast   ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key);
         
         return (a instanceof String ? (String) a : null);
      }
      
      /**
       * Gets a <code>String</code> annotation at a specific index from an 
       * array annotation in the referenced node of the current
       * <em>copy</em> AST.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          find the annotation.
       *
       * @return  The annotation object stored under the specified key and at
       *          the specified index, or <code>null</code> if the annotation
       *          doesn't exist, if the annotation is not an array, if no
       *          element exists at the given index or the referenced node
       *          does not exist.
       */
      public String getReferenceNoteString(long id, String key, long index)
      {
         Aast   ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key, (int)index);
         
         return (a instanceof String ? (String) a : null);
      }
       
      /**
       * Gets a <code>Long</code> annotation from the referenced node
       * in the <em>copy</em> AST.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  The desired annotation value, or <code>null</code> if
       *          either the annotation or the referenced node does not
       *          exist.
       */
      public Long getReferenceNoteLong(long id, String key)
      {
         Aast   ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key);
         
         return (a instanceof Long ? (Long) a : null);
      }
      
      /**
       * Gets a <code>Long</code> annotation at a specific index from an 
       * array annotation in the referenced node of the current
       * <em>copy</em> AST.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          find the annotation.
       *
       * @return  The annotation object stored under the specified key and at
       *          the specified index, or <code>null</code> if the annotation
       *          doesn't exist, if the annotation is not an array, if no
       *          element exists at the given index or the referenced node
       *          does not exist.
       */
      public Long getReferenceNoteLong(long id, String key, long index)
      {
         Aast   ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key, (int)index);
         
         return (a instanceof Long ? (Long) a : null);
      }
       
      /**
       * Gets a <code>Double</code> annotation from the referenced node
       * in the <em>copy</em> AST.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  The desired annotation value, or <code>null</code> if
       *          either the annotation or the referenced node does not
       *          exist.
       */
      public Double getReferenceNoteDouble(long id, String key)
      {
         Aast   ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key);
         
         return (a instanceof Double ? (Double) a : null);
      }
      
      /**
       * Gets a <code>Double</code> annotation at a specific index from an 
       * array annotation in the referenced node of the current
       * <em>copy</em> AST.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          find the annotation.
       *
       * @return  The annotation object stored under the specified key and at
       *          the specified index, or <code>null</code> if the annotation
       *          doesn't exist, if the annotation is not an array, if no
       *          element exists at the given index or the referenced node
       *          does not exist.
       */
      public Double getReferenceNoteDouble(long id, String key, long index)
      {
         Aast   ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key, (int)index);
         
         return (a instanceof Double ? (Double) a : null);
      }
       
      /**
       * Gets a <code>Boolean</code> annotation from the referenced node
       * in the <em>copy</em> AST.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  The desired annotation value, or <code>null</code> if
       *          either the annotation or the referenced node does not
       *          exist.
       */
      public Boolean getReferenceNoteBoolean(long id, String key)
      {
         Aast   ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key);
         
         return (a instanceof Boolean ? (Boolean) a : null);
      }
      
      /**
       * Gets a <code>Boolean</code> annotation at a specific index from an 
       * array annotation in the referenced node of the current
       * <em>copy</em> AST.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          find the annotation.
       *
       * @return  The annotation object stored under the specified key and at
       *          the specified index, or <code>null</code> if the annotation
       *          doesn't exist, if the annotation is not an array, if no
       *          element exists at the given index or the referenced node
       *          does not exist.
       */
      public Boolean getReferenceNoteBoolean(long id, String key, long index)
      {
         Aast   ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return null;
         
         Object a   = ans.getAnnotation(key, (int)index);
         
         return (a instanceof Boolean ? (Boolean) a : null);
      }
       
      /**
       * Store an annotation of type <code>String</code> in the current
       * <em>copy</em> AST. If an annotation exists with the identical key,
       * it is replaced.
       *
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       */
      public boolean putNote(String key, String value)
      {
         getCopy().putAnnotation(key, value);
         
         return true;
      }
      
      /**
       * Store an annotation of type <code>String</code> at the specified
       * index in an array annotation in the current <em>copy</em> AST.
       *
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          place the annotation.
       */
      public boolean putNote(String key, String value, long index)
      {
         getCopy().putAnnotation(key, value, (int)index);
         
         return true;
      }
      
      /**
       * Store an annotation of type <code>Long</code> in the current
       * <em>copy</em> AST. If an annotation exists with the identical key,
       * it is replaced.
       *
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       */
      public boolean putNote(String key, Long value)
      {
         getCopy().putAnnotation(key, value);
         
         return true;
      }
      
      /**
       * Store an annotation of type <code>Long</code> at the specified
       * index in an array annotation in the current <em>copy</em> AST.
       *
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          place the annotation.
       */
      public boolean putNote(String key, Long value, long index)
      {
         getCopy().putAnnotation(key, value, (int)index);
         
         return true;
      }
      
      /**
       * Store an annotation of type <code>Double</code> in the current
       * <em>copy</em> AST. If an annotation exists with the identical key,
       * it is replaced.
       *
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       */
      public boolean putNote(String key, Double value)
      {
         getCopy().putAnnotation(key, value);
         
         return true;
      }
      
      /**
       * Store an annotation of type <code>Double</code> at the specified
       * index in an array annotation in the current <em>copy</em> AST.
       *
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          place the annotation.
       */
      public boolean putNote(String key, Double value, long index)
      {
         getCopy().putAnnotation(key, value, (int)index);
         
         return true;
      }
      
      /**
       * Store an annotation of type <code>Boolean</code> in the current
       * <em>copy</em> AST. If an annotation exists with the identical key,
       * it is replaced.
       *
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       */
      public boolean putNote(String key, Boolean value)
      {
         getCopy().putAnnotation(key, value);
         
         return true;
      }
      
      /**
       * Store an annotation of type <code>Boolean</code> at the specified
       * index in an array annotation in the current <em>copy</em> AST.
       *
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          place the annotation.
       */
      public boolean putNote(String key, Boolean value, long index)
      {
         getCopy().putAnnotation(key, value, (int)index);
         
         return true;
      }
      
      /**
       * Remove an annotation from the current <em>copy</em> AST.
       *
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  <code>true</code> if an entry for <code>key</code> was
       *          found and removed; <code>false</code> if none was found.
       */
      public boolean removeNote(String key)
      {
         return (getCopy().removeAnnotation(key) != null);
      }
      
      /**
       * Remove an element (at the specified index) of an array annotation
       * from an annotation specified by the key in the current <em>copy</em>
       * AST. The special index of -1 indicates the removal of the last
       * element of the array.
       *
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          remove the annotation.
       *
       * @return  <code>true</code> if an entry for <code>key</code> was
       *          found and removed; <code>false</code> if none was found,
       *          if the annotation is not an array or if no element exists
       *          at the given index.
       */
      public boolean removeNote(String key, long index)
      {
         return (getCopy().removeAnnotation(key, (int)index) != null);
      }
      
      /**
       * Store an annotation of type <code>String</code> in the specified
       * ancestor in the <em>copy</em> AST. If an annotation exists with
       * the identical key, it is replaced.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such ancestor was found.
       */
      public boolean putAncestorNote(long level, String key, String value)
      {
         Aast ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value);
         
         return true;
      }
      
      /**
       * Store an element in an array annotation of type <code>String</code>
       * at the specified index in the specified ancestor in the <em>copy</em>
       * AST. The special index of -1 indicates the add as the last element
       * of the array.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          insert the annotation.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such ancestor was found.
       */
      public boolean putAncestorNote(long level, String key, String value, long index)
      {
         Aast ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value, (int)index);
         
         return true;
      }
            
      /**
       * Store an annotation of type <code>Long</code> in the specified
       * ancestor in the <em>copy</em> AST. If an annotation exists with
       * the identical key, it is replaced.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such ancestor was found.
       */
      public boolean putAncestorNote(long level, String key, Long value)
      {
         Aast ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value);
         
         return true;
      }
      
      /**
       * Store an element in an array annotation of type <code>Long</code>
       * at the specified index in the specified ancestor in the <em>copy</em>
       * AST. The special index of -1 indicates the add as the last element
       * of the array.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          insert the annotation.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such ancestor was found.
       */
      public boolean putAncestorNote(long level, String key, Long value, long index)
      {
         Aast ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value, (int)index);
         
         return true;
      }
            
      /**
       * Store an annotation of type <code>Double</code> in the specified
       * ancestor in the <em>copy</em> AST. If an annotation exists with
       * the identical key, it is replaced.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such ancestor was found.
       */
      public boolean putAncestorNote(long level, String key, Double value)
      {
         Aast ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value);
         
         return true;
      }
      
      /**
       * Store an element in an array annotation of type <code>Double</code>
       * at the specified index in the specified ancestor in the <em>copy</em>
       * AST. The special index of -1 indicates the add as the last element
       * of the array.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          insert the annotation.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such ancestor was found.
       */
      public boolean putAncestorNote(long level, String key, Double value, long index)
      {
         Aast ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value, (int)index);
         
         return true;
      }
            
      /**
       * Store an annotation of type <code>Boolean</code> in the specified
       * ancestor in the <em>copy</em> AST. If an annotation exists with
       * the identical key, it is replaced.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such ancestor was found.
       */
      public boolean putAncestorNote(long level, String key, Boolean value)
      {
         Aast ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value);
         
         return true;
      }
      
      /**
       * Store an element in an array annotation of type <code>Boolean</code>
       * at the specified index in the specified ancestor in the <em>copy</em>
       * AST. The special index of -1 indicates the add as the last element
       * of the array.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          insert the annotation.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such ancestor was found.
       */
      public boolean putAncestorNote(long    level,
                                     String  key,
                                     Boolean value,
                                     long    index)
      {
         Aast ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value, (int)index);
         
         return true;
      }
            
      /**
       * Remove an annotation from the specified ancestor in the
       * <em>copy</em> AST.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  <code>true</code> if an entry for <code>key</code> was
       *          found and removed; <code>false</code> if no such annotation
       *          or no such ancestor was found.
       */
      public boolean removeAncestorNote(long level, String key)
      {
         Aast ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return false;
         
         return (ans.removeAnnotation(key) != null);
      }
      
      /**
       * Remove an element (at the specified index) of an array annotation
       * from an annotation specified by the key in the specified ancestor of
       * the current <em>copy</em> AST. The special index of -1 indicates the
       * removal of the last element of the array.
       *
       * @param   level 
       *          Number of generations up the tree to walk to obtain the 
       *          ancestor.  Set to <code>-1</code> to obtain the tree root, 
       *          <code>0</code> is the current node, <code>1</code> is the
       *          parent, <code>2</code> is the 'grandparent' and so forth.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          remove the annotation.
       *
       * @return  <code>true</code> if an entry for <code>key</code> was
       *          found and removed; <code>false</code> if none was found,
       *          if the annotation is not an array or if no element exists
       *          at the given index or if the specified ancestor does not
       *          exist.
       */
      public boolean removeAncestorNote(long level, String key, long index)
      {
         Aast ans = getCopy().getAncestor((int)level);
         
         if (ans == null)
            return false;
         
         return (ans.removeAnnotation(key, (int)index) != null);
      }      
      
      /**
       * Store an annotation of type <code>String</code> in the referenced
       * node in the <em>copy</em> AST. If an annotation exists with
       * the identical key, it is replaced.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such node was found.
       */
      public boolean putReferenceNote(long id, String key, String value)
      {
         Aast ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value);
         
         return true;
      }
      
      /**
       * Store an element in an array annotation of type <code>String</code>
       * at the specified index in the referenced node in the <em>copy</em>
       * AST. The special index of -1 indicates the add as the last element
       * of the array.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          insert the annotation.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such node was found.
       */
      public boolean putReferenceNote(long   id,
                                     String key,
                                     String value,
                                     long   index)
      {
         Aast ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value, (int)index);
         
         return true;
      }
            
      /**
       * Store an annotation of type <code>Long</code> in the referenced
       * node in the <em>copy</em> AST. If an annotation exists with
       * the identical key, it is replaced.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such node was found.
       */
      public boolean putReferenceNote(long id, String key, Long value)
      {
         Aast ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value);
         
         return true;
      }
      
      /**
       * Store an element in an array annotation of type <code>Long</code>
       * at the specified index in the referenced node in the <em>copy</em>
       * AST. The special index of -1 indicates the add as the last element
       * of the array.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          insert the annotation.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such node was found.
       */
      public boolean putReferenceNote(long   id,
                                     String key,
                                     Long   value,
                                     long   index)
      {
         Aast ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value, (int)index);
         
         return true;
      }
            
      /**
       * Store an annotation of type <code>Double</code> in the referenced
       * node in the <em>copy</em> AST. If an annotation exists with
       * the identical key, it is replaced.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such node was found.
       */
      public boolean putReferenceNote(long id, String key, Double value)
      {
         Aast ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value);
         
         return true;
      }
      
      /**
       * Store an element in an array annotation of type <code>Double</code>
       * at the specified index in the referenced node in the <em>copy</em>
       * AST. The special index of -1 indicates the add as the last element
       * of the array.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          insert the annotation.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such node was found.
       */
      public boolean putReferenceNote(long   id,
                                     String key,
                                     Double value,
                                     long   index)
      {
         Aast ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value, (int)index);
         
         return true;
      }
            
      /**
       * Store an annotation of type <code>Boolean</code> in the referenced
       * node in the <em>copy</em> AST. If an annotation exists with
       * the identical key, it is replaced.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such node was found.
       */
      public boolean putReferenceNote(long id, String key, Boolean value)
      {
         Aast ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value);
         
         return true;
      }
      
      /**
       * Store an element in an array annotation of type <code>Boolean</code>
       * at the specified index in the referenced node in the <em>copy</em>
       * AST. The special index of -1 indicates the add as the last element
       * of the array.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   value
       *          Annotation data.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          insert the annotation.
       *
       * @return  <code>true</code> if the annotation was added,
       *          <code>false</code> if no such node was found.
       */
      public boolean putReferenceNote(long    id,
                                     String  key,
                                     Boolean value,
                                     long    index)
      {
         Aast ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return false;
         
         ans.putAnnotation(key, value, (int)index);
         
         return true;
      }
            
      /**
       * Remove an annotation from the referenced node in the
       * <em>copy</em> AST.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       *
       * @return  <code>true</code> if an entry for <code>key</code> was
       *          found and removed; <code>false</code> if no such annotation
       *          or no such node was found.
       */
      public boolean removeReferenceNote(long id, String key)
      {
         Aast ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return false;
         
         return (ans.removeAnnotation(key) != null);
      }
      
      /**
       * Remove an element (at the specified index) of an array annotation
       * from an annotation referenced by the key in the referenced node of
       * the current <em>copy</em> AST. The special index of -1 indicates the
       * removal of the last element of the array.
       *
       * @param   id 
       *          The unique ID that references a specific node in the tree.
       * @param   key
       *          Case-insensitive annotation key.
       * @param   index
       *          The index of -1 indicates the last element of the array.
       *          Otherwise this is the explicit zero-based index at which to 
       *          remove the annotation.
       *
       * @return  <code>true</code> if an entry for <code>key</code> was
       *          found and removed; <code>false</code> if none was found,
       *          if the annotation is not an array or if no element exists
       *          at the given index or if the referenced node does not
       *          exist.
       */
      public boolean removeReferenceNote(long id, String key, long index)
      {
         Aast ans = getResolver().getAst(Long.valueOf(id));
         
         if (ans == null)
            return false;
         
         return (ans.removeAnnotation(key, (int)index) != null);
      }
      
      /**
       * Retrieve value of specified configuration variable.
       * 
       * @param   id
       *          Configuration variable ID.
       * 
       * @return  configuration parameter value.
       */
      public String getConfigParameter(String id)
      {
         return Configuration.getParameter(id);
      }
      
      /**
       * Save all the 4GL classes (builtin or business logic), .NET or Java classes encountered 
       * during parsing.
       */
      public void saveClasses()
      {
         SymbolResolver.saveClasses();
      }

      /**
       * Retrieve value of specified configuration variable.
       * 
       * @param   id
       *          Configuration variable ID.
       * @param   def
       *          Default value for the variable.
       * 
       * @return  configuration parameter value.
       */
      public String getConfigParameter(String id, String def)
      {
         return Configuration.getParameter(id, def);
      }

      /**
       * Retrieve value of specified configuration variable.
       *
       * @param   id
       *          Configuration variable ID.
       * @param   def
       *          Default value for the variable.
       *
       * @return  configuration parameter value.
       */
      public Boolean getConfigParameter(String id, boolean def)
      {
         return Configuration.getParameter(id, def);
      }
      
      /**
       * Determines whether the {@link Configuration} is in conversion mode (configuration is read
       * from the configuration directory) or server run time mode.
       *
       * @return <code>true</code> if it is a server run time mode (when configuration is NOT read
       *         from the configuration directory).
       */
      public boolean isRuntimeConfig()
      {
         return Configuration.isRuntimeConfig();
      }
      
      /**
       * Check whether the extent properties in dynamic temp-tables should be denormalized. When this is
       * enabled , the extent fields will NOT use secondary tables for SQL storage. <p>
       *
       * @return  {@code true} if the extent properties in dynamic temp-tables should be denormalized.
       */
      public boolean isDenormalizeDynamicTempTables()
      {
         return Configuration.isRuntimeConfig() && TableHints.isDenormalizeDynamicTempTables();
      }
      
      /**
       * If conditional rule-set is enabled, check if the given flag is activated.
       * 
       * @param    flag
       *           The flag to check.
       *           
       * @return   See above.
       * 
       * @see      PatternEngine#isFlagActivated(String)
       */
      public boolean isFlagActivated(String flag)
      {
         return CommonAstSupport.this.isFlagActivated(flag);
      }
      
      /**
       * Check if conditional rule-sets is activated.
       * 
       * @return   See above.
       * 
       * @see      PatternEngine#isConditionalRuleSets
       */
      public boolean isConditionalRuleSets()
      {
         return CommonAstSupport.this.isConditionalRuleSets();
      }
      
      /**
       * Check if the current mode is for runtime-conversion of query/tables.
       * 
       * @return   See above.
       */
      public boolean isRuntimeQueryMode()
      {
         return CommonAstSupport.this.isRuntimeQueryMode();
      }
      
      /**
       * Determine whether the token type of the specified node matches any
       * of the given types.
       *
       * @param   node
       *          An AST.
       * @param   types
       *          Array of <code>Integer</code>s representing valid token
       *          types.  May be empty, must not be <code>null</code>.
       *
       * @return  <code>true</code> if a match is detected, else
       *          <code>false</code>.
       *
       * @throws  ClassCastException
       *          if <code>types</code> contains any object type other than
       *          <code>Integer</code>.
       */
      private boolean matchTokenType(Aast node, Object[] types)
      {
         int len = types.length;
         int type = node.getType();
         for (int i = 0; i < len; i++)
         {
            Number ttype = (Number) types[i];
            if (ttype.intValue() == type)
            {
               return true;
            }
         }
         
         return false;
      }
      
      /**
       * Fixing the regular expression string for special Windows requirement.
       * Now it solves the backslash issue in Windows Java API call involving
       * regular expression string. Does nothing if OS other than Windows.
       *
       * @param   text
       *          Regular expression string to be adjusted acccording to
       *          OS special requirements.
       *
       * @return  Specially modified regular expression string. Now doubles
       *          the backslash character containing in the input string.
       */
      public String fixupRegex(String text)
      {
         return StringHelper.fixupRegex(text);
      }
      
      /**
       * Fixing the file name for special Windows requirement. Now it
       * substitutes the possible '/' character with backslash '\' to
       * accept to Windows filename requirements. Does nothing if OS other
       * than Windows.
       *
       * @param   text
       *          The string representing the filename with path separator.
       *
       * @return  Specially modified file name string. The Linux hardcoded
       *          '/' character replaced with '\' if current OS is Windows.
       */
      public String fixupFileName(String text)
      {
         return StringHelper.fixupFileName(text);
      }
      
      /**
       * Calling the java.util.regex.Matcher.quoteReplacement().
       *
       * @param   text
       *          String to replace special characters. An examples: \ or $.
       *
       * @return  The new string with added characters according to respecive
       *          Java API call documentation.
       */
      public String quoteReplacement(String text)
      {
         return java.util.regex.Matcher.quoteReplacement(text);
      }
      
      /**
       * Store the given object under the given id. Can be retrieved later using
       * {@link PatternEngine#getStoredObject(String)}.
       *
       * @param   id
       *          Arbitrary object id.
       * @param   object
       *          Object to store.
       */
      public void storeObject(String id, Object object)
      {
         getResolver().getPatternEngine().storeObject(id, object);
      }
      
     /**
      * Store the given object under the given category and id. Can be retrieved later using
      * {@link PatternEngine#getStoredObject(String, String)}.
      *
      * @param    category
      *           Arbitrary object category.
      * @param    id
      *           Arbitrary object id.
      * @param    object
      *           Object to store.
      */
      public void storeObject(String category, String id, Object object)
      {
         getResolver().getPatternEngine().storeObject(category, id, object);
      }
      
      /**
       * Get the object stored by the pattern engine under the given id.
       * 
       * @param   id
       *          Object's identifier.
       * 
       * @return  Stored object, or <code>null</code> if none.
       */
      public Object getStoredObject(String id)
      {
         return getResolver().getPatternEngine().getStoredObject(id);
      }
      
      /**
       * Get the object stored by the pattern engine under the given category and id.
       * 
       * @param   id
       *          Object's identifier.
       * 
       * @return  Stored object, or <code>null</code> if none.
       */
      public Object getStoredObject(String category, String id)
      {
         return getResolver().getPatternEngine().getStoredObject(category, id);
      }
      
      /**
       * Opens a new in-memory stream for writing, associates an unique ID with that stream and
       * stores it (can be retrieved later using {@link PatternEngine#getStoredObject(String)}).
       * Note that {@link ByteArrayOutputStream} is stored under the given name, then this stream
       * is wrapped with {@link ByteCountOutputStream} and the ID of {@link ByteCountOutputStream}
       * is returned for using in rules pipeline.
       *
       * @param  name
       *         Unique name of this stream under which it will be stored. Doesn't match returned
       *         ID.
       *
       * @return the unique ID associated with this stream.
       */
      public Long openStoredStream(String name)
      {
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
         
         // init our counting stream wrapper
         ByteCountOutputStream out = new ByteCountOutputStream(bos);
         
         WorkArea wa = context.get();
         Long id = wa.nextId;
         
         wa.streams.put(id, out);
         wa.nextId++;
         
         storeObject(name, bos);
         
         return id;
      }
   
      /**
       * Check if two nodes are equivalent (if they represent the same expression so they will
       * probably generate the same code. The nodes and their subnodes are walked recursively DFS
       * and stop at first difference, if any.
       * <p>
       * This feature was introduced because of the lack of support for recursive call in TRPL.
       * At this moment only the type/text of the nodes and their subnodes are checked. Depending
       * on necessities, the compare operation may be extended to annotations, too. 
       * 
       * @param   node1
       *          An {@link Aast} node to be check. 
       * @param   node2
       *          Another {@link Aast} node to be check.
       *
       * @return  {@code true} if the nodes are equivalent
       */
      public boolean compareNodes(Aast node1, Aast node2)
      {
         // before doing equivalence checks, test for equality
         if (node1 == node2)
         {
            // TODO: confirm this assertion when both nodes are null
            return true;
         }
         
         if (node1 == null || node2 == null)
         {
            // one is null, the other isn't
            return false;
         }
         
         // check primary properties
         if (node1.getType() != node2.getType()       ||
             !node1.getText().equals(node2.getText()) ||
             node1.getNumImmediateChildren() != node2.getNumImmediateChildren())
         {
            return false;
         }
         
         // if the nodes represent a denormalized extent field then the idx is no more a subnode,
         // we need to check the annotations
         if (node1.isAnnotation("extent"))
         {
            // the up-path are the same, so if the nodes represent the same node, the "extent" 
            // annotation should also a match 
            if (node1.getAnnotation("extent_elem_idx") != node2.getAnnotation("extent_elem_idx"))
            {
               // same EXTENT field, but different index, the nodes are different 
               return false;
            }
         }
         
         // recursively check children 
         Aast subnode1 = (Aast) node1.getFirstChild();
         Aast subnode2 = (Aast) node2.getFirstChild();
         
         while (subnode1 != null)
         {
            if (!compareNodes(subnode1, subnode2))
            {
               return false; // there was a difference down the tree
            }
            subnode1 = (Aast) subnode1.getNextSibling();
            subnode2 = (Aast) subnode2.getNextSibling();
         }
         
         // if no differences found:
         return true;
      }

      /**
       * Mark this file 'with_services'.
       * 
       * @param    filename
       *           The file name.
       */
      public void withServicesFile(String filename)
      {
         if (isRuntimeConfig())
         {
            return;
         }
         
         ConversionData.withServicesFile(filename);
      }
      
      /**
       * Create a db-backed map, with a {@link String} key and an {@link Integer} value.
       * <p>
       * For runtime conversion, a simple {@link HashMap} will be returned.
       * 
       * @return   See above.
       *  
       * @see   ConversionData#createMap
       */
      public Map<?, ?> createString2IntMap(String name)
      {
         return isRuntimeConfig() 
                   ? new HashMap<>() 
                   : ConversionData.createMap(String.class, Integer.class, name);
      }
      
      /**
       * Create a db-backed map, with a {@link String} key and a {@link Long} value.
       * <p>
       * For runtime conversion, a simple {@link HashMap} will be returned.
       * 
       * @return   See above.
       *  
       * @see   ConversionData#createMap
       */
      public Map<?, ?> createString2LongMap(String name)
      {
         return isRuntimeConfig() 
                  ? new HashMap<>() 
                  : ConversionData.createMap(String.class, Long.class, name);
      }
      
      /**
       * Create a db-backed map, with a {@link String} key and a {@link String} value.
       * <p>
       * For runtime conversion, a simple {@link HashMap} will be returned.
       * 
       * @return   See above.
       *  
       * @see   ConversionData#createMap
       */
      public Map<?, ?> createString2StringMap(String name)
      {
         return isRuntimeConfig() 
                  ? new HashMap<>() 
                  : ConversionData.createMap(String.class, String.class, name);
      }
      
      /**
       * Create a db-backed set, with a {@link String} key.
       * <p>
       * For runtime conversion, a simple {@link TreeSet} will be returned.
       * 
       * @return   See above.
       *  
       * @see   ConversionData#createSet
       */
      public Set<?> createStringSet(String name)
      {
         return isRuntimeConfig() 
                  ? new TreeSet<>() 
                  : ConversionData.createSet(String.class, name);
      }
      
      /**
       * Create a db-backed map, with a {@link String} key and as values another map, having 
       * {@link String} for both key and value.
       * <p>
       * For runtime conversion, a simple {@link HashMap} will be returned.
       * 
       * @return   See above.
       *  
       * @see   ConversionData#createMapToMap
       */
      public Map<?, ?> createStringMapToStringMap(String name)
      {
         return isRuntimeConfig()
                  ? new HashMap<>()
                  : ConversionData.createMapToMap(String.class, String.class, String.class, name);
      }
      
      /**
       * Create a db-backed map, with a {@link String} key and as values another set, having 
       * {@link String} for key.
       * <p>
       * For runtime conversion, a simple {@link HashMap} will be returned.
       * 
       * @return   See above.
       *  
       * @see   ConversionData#createMapToSet
       */
      public Map<?, ?> createStringMapToStringSet(String name)
      {
         return isRuntimeConfig() 
                  ? new HashMap<>() 
                  : ConversionData.createMapToSet(String.class, String.class, name);
      }
      
      /**
       * Create a db-backed map, with a {@link String} key and as values another set, having 
       * {@link Long} for key.
       * <p>
       * For runtime conversion, a simple {@link HashMap} will be returned.
       * 
       * @return   See above.
       *  
       * @see   ConversionData#createMapToSet
       */
      public Map<?, ?> createStringMapToLongSet(String name)
      {
         return isRuntimeConfig() 
                  ? new HashMap<>() 
                  : ConversionData.createMapToSet(String.class, Long.class, name);
      }
      
      /**
       * Create a db-backed map, with a {@link AstKey} key and {@link String} values.
       * <p>
       * For runtime conversion, a simple {@link HashMap} will be returned.
       * 
       * @return   See above.
       *  
       * @see   ConversionData#createMap
       */
      public Map<?, ?> createMapAstKeyToString(String name)
      {
         return isRuntimeConfig() 
                  ? new HashMap<>() 
                  : ConversionData.createMap(AstKey.class, String.class, name);
      }
      
      /**
       * Create a db-backed map, with a {@link AstKey} key and {@link Integer} values.
       * <p>
       * For runtime conversion, a simple {@link HashMap} will be returned.
       * 
       * @return   See above.
       *  
       * @see   ConversionData#createMap
       */
      public Map<?, ?> createMapAstKeyToInt(String name)
      {
         return isRuntimeConfig() 
                  ? new HashMap<>() 
                  : ConversionData.createMap(AstKey.class, Integer.class, name);
      }
      
      /**
       * Create a db-backed map, with a {@link FrameAstKey} key and {@link String} values.
       * <p>
       * For runtime conversion, a simple {@link HashMap} will be returned.
       * 
       * @return   See above.
       *  
       * @see   ConversionData#createMap
       */
      public Map<?, ?> createMapFrameAstKeyToString(String name)
      {
         return isRuntimeConfig() 
                  ? new HashMap<>() 
                  : ConversionData.createMap(FrameAstKey.class, String.class, name);
      }
      
      /**
       * Create a db-backed map, with a {@link AstKey} key and {@link Aast} values.
       * <p>
       * For runtime conversion, a simple {@link HashMap} will be returned.
       * 
       * @return   See above.
       *  
       * @see   ConversionData#createMap
       */
      public Map<?, ?> createMapAstKeyToAst(String name)
      {
         return isRuntimeConfig() 
                  ? new HashMap<>() 
                  : ConversionData.createMap(AstKey.class, Aast.class, name);
      }
      
      /**
       * For a given legacy class, set its converted Java name and package.
       * .
       * @param    filename
       *           The associated file name.
       * @param    legacyName
       *           The legacy fully qualified name.
       * @param    pkgname
       *           The converted package name.
       * @param    classname
       *           The simple Java class name.
       */
      public void setConvertedClassName(String filename, 
                                        String legacyName, 
                                        String pkgname, 
                                        String classname)
      {
         ClassDefinition cls = SymbolResolver.getClassDefinition(legacyName);
         cls.setJavaClassName(pkgname, classname);
         
         ConversionData.setConvertedClassName(filename, pkgname, classname);
      }
      
      /**
       * For the given db-backed map (if not in runtime conversion mode), force a logged change
       * with the given key and value, for the currently processed AST.  This is required to keep
       * a history of changes, when (for a certain key) the value is semantically the same, but 
       * not the same object (for example, a temp-table AST node is saved in p2o.xml for each 
       * authoritative definition, but we need to keep track of all temp-table ASTs, in case the 
       * authoritative program gets re-converted).
       * 
       * @param    map
       *           The db-backed map.
       * @param    key
       *           The key for which the value needs to be logged.
       * @param    value
       *           The value which needs to be logged.
       */
      public void forceMapValue(Map<?, ?> map, Object key, Object value)
      {
         if (map instanceof H2Map)
         {
            ((H2Map) map).forceChange(key, value);
         }
      }

      /**
       * Get the next value for the given sequence.
       * <p>
       * For runtime, it will use the {@code sequences} map to store them.
       * 
       * @param    name
       *           The sequence name.
       *           
       * @return   The next sequence value.
       */
      public int nextSequenceValue(String name)
      {
         if (isRuntimeConfig())
         {
            WorkArea wa = context.get();
            AtomicInteger seq = wa.sequences.get(name);
            if (seq == null)
            {
               wa.sequences.put(name, seq = new AtomicInteger(0));
            }
            
            return seq.incrementAndGet();
         }
         
         return ConversionData.nextSequenceValue(name);
      }
      
      /**
       * Get the simple java class names for all classes in this package (builtin or converted code).
       * 
       * @param    pkg
       *           The package name.
       *           
       * @return   The set of simple java class names.
       */
      public Set<String> getAllClassesInPackage(String pkg)
      {
         if (pkg == null)
         {
            return Collections.emptySet();
         }
         
         Set<String> res = null;
         
         if (pkg.endsWith(".*"))
         {
            String pack = pkg.substring(0, pkg.length() - ".*".length());
            res = ConversionData.getClassesInPackage(pack);
         }
         else
         {
            res = new HashSet<>();
            res.add(pkg);
         }
         
         return res;
      }
      
      /**
       * Get the converted simple class name for the specified fully-qualified 4GL class name.
       * 
       * @param    qname
       *           The qualified class name.
       *           
       * @return   The simple Java class name or <code>null</code> if it was not found.
       */
      public String getConvertedSimpleClassName(String qname)
      {
         return ConversionData.getConvertedSimpleClassName(qname);
      }
      
      /**
       * Set the runtime annotation into the AST, using a numeric key.
       * <p>
       * The annotation is resolved via {@link CommonAstSupport#resolveRuntimeAnnotationKey}.
       * 
       * @param    node
       *           The ast node.
       * @param    key
       *           The string annotation.
       * @param    value
       *           The annotation value.
       */
      public void setRuntimeAnnotation(Aast node, String key, Object value)
      {
         int runtimeKey = resolveRuntimeAnnotationKey(key);
         
         node.setRuntimeAnnotation(runtimeKey, value);
      }
      
      /**
       * Get the runtime annotation from the AST, using a numeric key.
       * 
       * The annotation is resolved via {@link CommonAstSupport#resolveRuntimeAnnotationKey}.
       * 
       * @param    node
       *           The ast node.
       * @param    key
       *           The string annotation.
       *           
       * @return   The annotation value, <code>null</code> if is not yet set.
       */
      public Object getRuntimeAnnotation(Aast node, String key)
      {
         int runtimeKey = resolveRuntimeAnnotationKey(key);
         
         return node.getRuntimeAnnotation(runtimeKey);
      }
   }
   
   /**
    * Context local work area.
    */
   private static class WorkArea
   {
      /** A map of in-memory sequences. */
      private final Map<String, AtomicInteger> sequences = new HashMap<>();

      /** Cache of found classes for object instantiation */
      private final Map<String, Class<?>> classCache = new HashMap<>();
      
      /** Cache of resolved constructors by their class and signature. */
      private final Map<Class<?>, Map<String, Constructor<?>>> ctorCache = new HashMap<>();
      
      /** Stores all open streams. */
      private Map<Long, ByteCountOutputStream> streams = new HashMap<>();
      
      /** Stores the next available ID for streams. */
      private long nextId = 0;
   }
}