# 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;
       }
    }