JavaPatternWorker.java

/*
** Module   : JavaPatternWorker.java
** Abstract : Pattern worker with specific knowledge of the Java language
**
** Copyright (c) 2005-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 GES 20050324   @20599 Created initial version. Provides Java AST
**                           creation/persistence services and resolves
**                           token names to token types.
** 002 GES 20050425   @20863 Additional functions added for peer and
**                           arbitrary node creation directly from rules.
** 003 ECF 20050501   @20975 Changed superclass to
**                           convert.AbstractConversionWorker. Abstracted
**                           useful common conversion functionality into
**                           that class so it can be shared among workers
**                           which need to process peer, target ASTs.
** 004 ECF 20050526   @21290 Changes to support new expression engine
**                           implementation. Changed library registration
**                           mechanism based on superclass' modifications
**                           to support single library per pattern worker
**                           limit.
** 005 ECF 20050628   @21600 Added variant of persistJavaFile which
**                           accepts a filename. The original no-arg
**                           version now uses this new method as a worker.
** 006 ECF 20050707   @21674 Modified persistJavaFile. Creates directories
**                           in target file's path if not already present.
** 007 ECF 20050721   @21770 Added getSectionAnchor convenience methods.
**                           Allow simple access to a class section AST
**                           which serves as an anchor point to which
**                           class member ASTs are added.
** 008 GES 20050928   @22874 Improved createJavaAst() methods to provide
**                           new index option.
** 009 ECF 20050928   @22885 Modified getSectionAnchor(). Added support
**                           for interfaces.
** 010 GES 20060131   @24165 Added a tracking mode by which external
**                           callers can capture a list of all files
**                           that were persisted between 2 specific
**                           points in time.
** 011 GES 20090429   @42062 Match package and class name changes.
** 012 GES 20090515   @42228 Moved to AstManager from AstRegistry/AstPersister.
** 013 SVL 20130624          Added support for virtual files.
** 014 CA  20131023          Changed needed to support in-memory AST storage.
** 015 ECF 20140405          Replaced static variables with context local work area.
** 016 ECF 20140913          Prevent generateJavaFilename from creating names with multiple jast
**                           suffixes.
** 017 ECF 20150715          Replace StringBuffer with StringBuilder.
** 018 CA  20201008          Added createJavaFile(String), to create a standalone COMPILE_UNIT AST.
**     CA  20210917          Javadoc fixes.
**     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.
**     TJD 20220504          Java 11 compatibility minor changes
*/
/*
** 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.uast;

import java.io.*;
import java.util.*;
import com.goldencode.ast.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.pattern.*;
import com.goldencode.p2j.security.ContextLocal;

/**
 * A pattern worker that provides Java AST services and conversion of Java
 * language token names (as defined by the {@link JavaTokenTypes}) to their
 * integral types.
 */
public final class JavaPatternWorker
extends AbstractConversionWorker
implements JavaTokenTypes
{
   /**
    * Text to append to a filename to generate a unique name for persisting
    * a Java AST.
    */
   public static final String JAST_POSTFIX = ".jast";
   
   /** Context local work area. */
   private static final ContextLocal<WorkArea> context = new ContextLocal<WorkArea>()
   {
      protected WorkArea initialValue()
      {
         return new WorkArea();
      }
   };
   
   /** 
    * Cache current instance of the helper so that common methods have
    * access.
    */
   private JavaAstHelper helper = null;
   
   /**
    * Worker routine to centralize creation of new Java ASTs.  All ID setup
    * and parent linkages are established if the parent AST is passed as
    * a parameter, otherwise only an unidentified and standalone AST is
    * created.  If a parent AST is passed, a resolver instance should also 
    * be passed.
    *
    * @param    type
    *           The token type for the Java AST.
    * @param    text
    *           The text for the AST.
    * @param    parent
    *           The parent AST node or <code>null</code>.
    * @param    resolver
    *           The current instance of the {@link AstSymbolResolver} in use
    *           or <code>null</code>.
    * @param    index
    *           0 to insert as the first child, -1 to insert as the last
    *           child or a 0-based index to specify an exact child index
    *           at which to insert the new child.  All current children at
    *           that index or at a greater index, are moved right. If the
    *           index is greater than the current number of children, the
    *           new child will be added at the end.
    *
    * @return   The AST that was created.
    */
   public static JavaAst createAst(int               type,
                                   String            text,
                                   JavaAst           parent,
                                   AstSymbolResolver resolver,
                                   int               index)
   {
      JavaAst ast = new JavaAst();
      initializeAst(ast, type, text, parent, resolver, index);
      
      return ast;
   }
   
   /**
    * Worker routine to peer AST creation.  All ID setup and parent linkages
    * are established and bidirectional peer ID annotations are written
    * in source and target nodes.
    *
    * @param    type
    *           The token type for the Java AST.
    * @param    text
    *           The text for the AST.
    * @param    parent
    *           The parent AST node.
    * @param    resolver
    *           The current instance of the {@link AstSymbolResolver} in use.
    * @param    index
    *           0 to insert as the first child, -1 to insert as the last
    *           child or a 0-based index to specify an exact child index
    *           at which to insert the new child.  All current children at
    *           that index or at a greater index, are moved right. If the
    *           index is greater than the current number of children, the
    *           new child will be added at the end.
    *
    * @return   The AST that was created.
    */
   public static JavaAst createPeerAst(Aast              source,
                                       int               type,
                                       String            text,
                                       JavaAst           parent,
                                       AstSymbolResolver resolver,
                                       int               index)
   {
      JavaAst target = createAst(type, text, parent, resolver, index);
      
      // save our cross-references
      crossReferencePeerAsts(source, target);
            
      return target;
   }
   
   /**
    * Worker routine to centralize creation of a new Java AST that is a 
    * root node.  A new file ID is registered in the {@link AstManager}
    * and set into the AST.
    *
    * @param    file
    *           The file in which to persist the new AST.
    * @param    resolver
    *           The current instance of the {@link AstSymbolResolver} in use
    *           or <code>null</code>.
    *
    * @return   The root AST that was created or <code>null</code> on error.
    */   
   public static JavaAst createRootAst(File file, AstSymbolResolver resolver)
   {
      JavaAst result = createAst(COMPILE_UNIT,
                                 "compilation unit",
                                 null,
                                 null,
                                 -1);
      initializeRootAst(result, file, resolver);
      
      return result;
   }
   
   /**
    * Worker routine to centralize creation of a new Java AST that is a root node.  A new file ID
    * is registered in the {@link AstManager} and set into the AST.
    *
    * @param    filename
    *           The name of the file in which to persist the new AST. Can be real or virtual (i.e.
    *           the file can be never persisted into the file system, the name is used to map some
    *           ID to the AST).
    * @param    resolver
    *           The current instance of the {@link AstSymbolResolver} in use
    *           or <code>null</code>.
    *
    * @return   The root AST that was created or <code>null</code> on error.
    */
   public static JavaAst createRootAst(String filename, AstSymbolResolver resolver)
   {
      JavaAst result = createAst(COMPILE_UNIT,
                                 "compilation unit",
                                 null,
                                 null,
                                 -1);
      initializeRootAst(result, filename, resolver);

      return result;
   }
   
   /**
    * Generate a unique Java AST persistence filename based on the passed
    * filename.
    *
    * @param    filename
    *           The filename used as a base for the Java AST filename.
    *
    * @return   A unique filename based on the source filename with '.jast'
    *           appended.
    */
   public static String generateJavaFilename(String filename)
   {
      if (filename.endsWith(JAST_POSTFIX))
      {
         return filename;
      }
      
      StringBuilder sb = new StringBuilder(filename);
      
      return sb.append(JAST_POSTFIX).toString();
   }
   
   /**
    * Clears past contents of the new files list and enables tracking mode
    * where all JAST files that are persisted are logged in a set that
    * can be queried by {@link #getPersistedFiles}.  Use 
    * {@link #disableNewFileTracking} when the tracking session is no
    * longer needed.
    */
   public static void enableNewFileTracking()
   {
      WorkArea wa = context.get();
      wa.pfiles = new HashSet<>();
      wa.track = true;
   }
   
   /**
    * Disables tracking mode such that any JAST files that are persisted
    * will no longer be logged in the set that can be queried by
    * {@link #getPersistedFiles}.  Does not alter the contents of the current
    * persisted files list.  Use this to finish a session started with 
    * {@link #enableNewFileTracking}
    */
   public static void disableNewFileTracking()
   {
      context.get().track = false;
   }
   
   /**
    * Accesses the list of files persisted since the last call
    * to {@link #enableNewFileTracking}.  There are no duplicate entries in
    * the list.
    *
    * @return   The list of files that have been recently persisted.
    */
   public static String[] getPersistedFiles()
   {
      return (String[]) context.get().pfiles.toArray(new String[0]);
   }
   
   /**
    * Default constructor which initializes libraries.
    */
   public JavaPatternWorker()
   {
      super();
      helper = new JavaAstHelper();
      setLibrary(helper);
   }   
   
   /**
    * This method is called each time the pattern engine needs to resolve
    * a string constant into a numeric, boolean, or string literal value.
    * Currently, it only handles Java language token names.
    
    * @param   constant
    *          A case-insensitive token name to be resolved to a token type.
    *
    * @return  The <code>Long</code> value associated with the token type
    *          represented by the token name <code>constant</code>, or
    *          <code>null</code> if the given constant is not the name of a
    *          valid, Java language token.
    */
   public Object resolveConstant(String constant)
   {
      int type = JavaSymbolResolver.lookupTokenType(constant);
      
      return (type >= 0 ? Long.valueOf(type) : null);
   }
   
   /**
    * Optionally creates or loads a Java target AST that is associated with 
    * the passed-in source AST.  This processing only occurs if auto load
    * is enabled.  If a persisted AST exists with the source file name  
    * and a postfix of '.jast', then it will be loaded.  Otherwise a new
    * Java AST will be created.
    * <p>
    * Any changes made during processing of the previous AST will be lost if
    * they haven't been persisted by this point.
    *
    * @param    ast
    *           AST about to be processed by the pattern engine.
    */
   public void visitAst(Aast ast)
   {
      WorkArea wa = context.get();
      
      // reset the current/root AST to null
      wa.root = wa.current = null;
      getResolver().setTargetRootAst(null);
      
      if (wa.autoload)
      {
         String filename = generateJavaFilename(ast);
         filename = Configuration.normalizeFilename(filename);

         try
         {
            if (AstManager.get().isExistingAst(filename))
            {
               helper.loadJavaFile(filename, true);
            }
            else
            {
               // create a new target tree
               wa.root = wa.current = createRootAst(new File(filename), getResolver());
            }            
         }
         
         catch (Exception exc)
         {
            // some failure, we just won't have a Java AST!
         }
      }
   }
   
   /**
    * Generate a unique Java AST persistence filename based on the current
    * source AST filename.
    *
    * @return   A unique filename based on the source filename with '.jast'
    *           appended.
    */
   private String generateJavaFilename(Aast ast)
   {
      return generateJavaFilename(ast.getFilename());
   }     
   
   /**
    * Helper to create, edit, move and delete Java ASTs.
    */
   public class JavaAstHelper
   {
      /**
       * Enables or disables autoloading of the target Java AST each time a
       * new Progress source AST is visited.  If a persistent Java AST is
       * not found, one will be created.
       *
       * @param    auto
       *           <code>true</code> to enable autoloading.
       *
       * @return   Always <code>true</code>.
       */
      public boolean setAutoload(boolean auto)
      {
         context.get().autoload = auto;
         return true;
      }
      
      /**
       * Exposes the root node ID of the current Java file as a variable. 
       *
       * @return   The root node ID of the current Java file.
       */
      public long getJavaRootId()
      {
         WorkArea wa = context.get();
         
         if (wa.root == null)
            return -1L;
         
         return wa.root.getId().longValue();
      }
      
      /**
       * Search the current source AST node and up each ancestor node in the
       * path to the root to find the first instance of the 'peerid' 
       * annotation.  This allows Java ASTs to be attached to the right block
       * of code, without necessarily knowing what parent might be enclosing
       * it.  This is exposed as a ruleset variable.
       *
       * @return   The first 'peerid' annotation found in the current node
       *           or the closest ancestor node that has a valid peerid.  
       *           Returns -1 if no peerid is found in any ancestor.
       */
      public long getClosestPeerId()
      {
         return JavaPatternWorker.getClosestPeerId(getCopy());
      }
      
      /**
       * General purpose Java AST creation function which can create a
       * <code>JavaAst</code> node using the passed token type and text 
       * and then it will attach that node to the specified parent.
       * <p>
       * The new node will be added as the last child of the parent.
       *
       * @param    type
       *           The token type for the Java AST.
       * @param    text
       *           The text for the AST.
       * @param    parent
       *           The ID of the parent AST node.
       *
       * @return   The unique ID if the AST was created successfully or
       *           <code>null</code> if the parent ID is invalid, if the type
       *           is not valid or on any unexpected error during AST 
       *           creation.
       */
      public Long createJavaAst(long type, String text, Long parent)
      {
         return createJavaAst(type, text, parent, -1);
      }
      
      /**
       * General purpose Java AST creation function which can create a
       * <code>JavaAst</code> node using the passed token type and text 
       * and then it will attach that node to the specified parent.
       * <p>
       * The new node will be added as the last child of the parent.
       *
       * @param    type
       *           The token type for the Java AST.
       * @param    text
       *           The text for the AST.
       * @param    parent
       *           The parent AST node.
       *
       * @return   The AST which was created or <code>null</code> if the
       *           parent node is invalid, if the type is not valid or on any
       *           unexpected error during AST creation.
       */
      public Aast createJavaAst(long type, String text, Aast parent)
      {
         return createJavaAst(type, text, parent, -1);
      }
      
      /**
       * General purpose Java AST creation function which can create a
       * <code>JavaAst</code> node using the passed token type and text 
       * and then it will attach that node to the specified parent at the
       * given index position.
       *
       * @param    type
       *           The token type for the Java AST.
       * @param    text
       *           The text for the AST.
       * @param    parent
       *           The ID of the parent AST node.
       * @param    index
       *           0 to insert as the first child, -1 to insert as the last
       *           child or a 0-based index to specify an exact child index
       *           at which to insert the new child.  All current children at
       *           that index or at a greater index, are moved right. If the
       *           index is greater than the current number of children, the
       *           new child will be added at the end.
       *
       * @return   The unique ID if the AST was created successfully or
       *           <code>null</code> if the parent ID is invalid, if the type
       *           is not valid or on any unexpected error during AST 
       *           creation.
       */
      public Long createJavaAst(long   type,
                                String text,
                                Long   parent,
                                int    index)
      {
         JavaAst jparent = (JavaAst) getResolver().getAst(parent);
         
         if (jparent == null)
         {
            return null;
         }
         
         JavaAst node = (JavaAst) createJavaAst(type, text, jparent, index);
         
         if (node != null)
         {
            return node.getId();
         }
         
         return null;
      }
      
      /**
       * General purpose Java AST creation function which can create a
       * <code>JavaAst</code> node using the passed token type and text 
       * and then it will attach that node to the specified parent at the
       * given index position.
       *
       * @param    type
       *           The token type for the Java AST.
       * @param    text
       *           The text for the AST.
       * @param    parent
       *           The parent AST node.
       * @param    index
       *           0 to insert as the first child, -1 to insert as the last
       *           child or a 0-based index to specify an exact child index
       *           at which to insert the new child.  All current children at
       *           that index or at a greater index, are moved right. If the
       *           index is greater than the current number of children, the
       *           new child will be added at the end.
       *
       * @return   The AST which was created or <code>null</code> if the
       *           parent node is invalid, if the type is not valid or on any
       *           unexpected error during AST creation.
       */
      public Aast createJavaAst(long   type,
                                String text,
                                Aast   parent,
                                int    index)
      {
         if (parent == null)
         {
            return null;
         }
         
         return createAst((int)type, text, (JavaAst) parent, getResolver(), index);
      }
      
      /**
       * General purpose Java AST creation function which can create a
       * <code>JavaAst</code> node using the passed token type and text 
       * and then it will attach that node to the specified parent. Most
       * importantly, on success the ID of the generated AST node will be
       * stored in the current source AST node's 'peerid' annotation. This
       * provides a standard mechanism to duplicate the structure of a
       * source AST in the target Java AST.  The source AST's ID will also
       * be stored in the target Java AST 'peerid' annotation which allows 
       * subsequent bidirectional referencing between these peers. This
       * is used in cases where the conversion is simple enough to drive
       * it completely from the rule set rather than from a custom worker.
       *
       * @param    type
       *           The token type for the Java AST.
       * @param    text
       *           The text for the AST.
       * @param    parent
       *           The ID of the parent AST node.
       *
       * @return   The unique ID if the AST was created successfully or
       *           <code>null</code> if the parent ID is invalid, if the type
       *           is not valid or on any unexpected error during AST 
       *           creation.
       */
      public Long createPeerAst(long type, String text, Long parent)
      {
         return createPeerAst(type, text, parent, -1);
      }
      
      /**
       * General purpose Java AST creation function which can create a
       * <code>JavaAst</code> node using the passed token type and text 
       * and then it will attach that node to the specified parent. Most
       * importantly, on success the ID of the generated AST node will be
       * stored in the current source AST node's 'peerid' annotation. This
       * provides a standard mechanism to duplicate the structure of a
       * source AST in the target Java AST.  The source AST's ID will also
       * be stored in the target Java AST 'peerid' annotation which allows 
       * subsequent bidirectional referencing between these peers. This
       * is used in cases where the conversion is simple enough to drive
       * it completely from the rule set rather than from a custom worker.
       * <p>
       * The new node will be added as the last child of the parent.
       *
       * @param    type
       *           The token type for the Java AST.
       * @param    text
       *           The text for the AST.
       * @param    parent
       *           The ID of the parent AST node.
       *
       * @return   The AST which was created or <code>null</code> if the
       *           parent node is invalid, if the type is not valid or on any
       *           unexpected error during AST creation.
       */
      public Aast createPeerAst(long type, String text, Aast parent)
      {
         return createPeerAst(type, text, parent, -1);
      }
      
      /**
       * General purpose Java AST creation function which can create a
       * <code>JavaAst</code> node using the passed token type and text 
       * and then it will attach that node to the specified parent. Most
       * importantly, on success the ID of the generated AST node will be
       * stored in the current source AST node's 'peerid' annotation. This
       * provides a standard mechanism to duplicate the structure of a
       * source AST in the target Java AST.  The source AST's ID will also
       * be stored in the target Java AST 'peerid' annotation which allows 
       * subsequent bidirectional referencing between these peers. This
       * is used in cases where the conversion is simple enough to drive
       * it completely from the rule set rather than from a custom worker.
       *
       * @param    type
       *           The token type for the Java AST.
       * @param    text
       *           The text for the AST.
       * @param    parent
       *           The ID of the parent AST node.
       * @param    index
       *           0 to insert as the first child, -1 to insert as the last
       *           child or a 0-based index to specify an exact child index
       *           at which to insert the new child.  All current children at
       *           that index or at a greater index, are moved right. If the
       *           index is greater than the current number of children, the
       *           new child will be added at the end.
       *
       * @return   The unique ID if the AST was created successfully or
       *           <code>null</code> if the parent ID is invalid, if the type
       *           is not valid or on any unexpected error during AST 
       *           creation.
       */
      public Long createPeerAst(long   type,
                                String text,
                                Long   parent,
                                int    index)
      {
         JavaAst jparent = (JavaAst) getResolver().getAst(parent);
         
         if (jparent == null)
         {
            return null;
         }
         
         JavaAst node = (JavaAst) createPeerAst(type, text, jparent, index);
                  
         if (node != null)
         {
            return node.getId();
         }
         
         return null;
      }
      
      /**
       * General purpose Java AST creation function which can create a
       * <code>JavaAst</code> node using the passed token type and text 
       * and then it will attach that node to the specified parent. Most
       * importantly, on success the ID of the generated AST node will be
       * stored in the current source AST node's 'peerid' annotation. This
       * provides a standard mechanism to duplicate the structure of a
       * source AST in the target Java AST.  The source AST's ID will also
       * be stored in the target Java AST 'peerid' annotation which allows 
       * subsequent bidirectional referencing between these peers. This
       * is used in cases where the conversion is simple enough to drive
       * it completely from the rule set rather than from a custom worker.
       *
       * @param    type
       *           The token type for the Java AST.
       * @param    text
       *           The text for the AST.
       * @param    parent
       *           The ID of the parent AST node.
       * @param    index
       *           0 to insert as the first child, -1 to insert as the last
       *           child or a 0-based index to specify an exact child index
       *           at which to insert the new child.  All current children at
       *           that index or at a greater index, are moved right. If the
       *           index is greater than the current number of children, the
       *           new child will be added at the end.
       *
       * @return   The AST which was created or <code>null</code> if the
       *           parent node is invalid, if the type is not valid or on any
       *           unexpected error during AST creation.
       */
      public Aast createPeerAst(long   type,
                                String text,
                                Aast   parent,
                                int    index)
      {
         if (parent == null)
         {
            return null;
         }
         
         return JavaPatternWorker.createPeerAst(getCopy(),
                                                (int) type,
                                                text,
                                                (JavaAst) parent,
                                                getResolver(),
                                                index);
      }
      
      /**
       * Simplest case for  Java AST creation which creates a 
       * <code>JavaAst</code> node using the passed token type and text 
       * and then it will attach that node to the parent specified by the
       * 'peerid' annotation of the source AST's parent node. This greatly
       * simplifies creation of entire hierarchies of structure that 
       * match between the source and target ASTs.  This uses the services
       * of {@link #createPeerAst(long,String,Long)}.
       * <p>
       * The new node will be added as the last child of the parent.
       *
       * @param    type
       *           The token type for the Java AST.
       * @param    text
       *           The text for the AST.
       *
       * @return   The unique ID if the AST was created successfully or
       *           <code>null</code> if the parent ID is invalid, if the type
       *           is not valid or on any unexpected error during AST 
       *           creation.
       */
      public Long createPeerAst(long type, String text)
      {
         Long id = (Long) getCopy().getParent().getAnnotation("peerid");
         
         if (id == null)
            return null;
         
         return createPeerAst(type, text, id);
      }
      
      /**
       * Convenience method to retrieve the class section AST which anchors
       * a certain category of class member. The target class is the
       * top level class defined in the current compile unit.
       *
       * @param   type
       *          Token type indicating which section is required. Must be
       *          within the range <code>CS_BEGIN &lt; <i>type</i> &lt;
       *          CS_END</code>.
       *
       * @return  AST node representing the anchor point for the specified
       *          section, or <code>null</code> if the section does not
       *          exist.
       *
       * @throws  IllegalArgumentException
       *          if <code>type</code> is not within the valid range of
       *          token types specified above.
       */
      public Aast getSectionAnchor(int type)
      {
         WorkArea wa = context.get();
         
         Aast ast = wa.root.getImmediateChild(KW_CLASS, null);
         if (ast == null)
         {
            ast = wa.root.getImmediateChild(KW_INTERFACE, null);
         }
         
         return getSectionAnchor(ast.getId(), type);
      }
      
      /**
       * Convenience method to retrieve the class section AST which anchors
       * a certain category of class member.
       *
       * @param   classId
       *          ID of the class AST within which the target section
       *          resides.
       * @param   type
       *          Token type indicating which section is required. Must be
       *          within the range <code>CS_BEGIN &lt; <i>type</i> &lt;
       *          CS_END</code>.
       *
       * @return  AST node representing the anchor point for the specified
       *          section, or <code>null</code> if the section does not
       *          exist.
       *
       * @throws  IllegalArgumentException
       *          if <code>type</code> is not within the valid range of
       *          token types specified above.
       */
      public Aast getSectionAnchor(Long classId, int type)
      {
         return getSectionAnchor(getResolver().getAst(classId), type);
      }
      
      /**
       * Convenience method to retrieve the class section AST which anchors
       * a certain category of class member.
       *
       * @param   classAst
       *          The class AST within which the target section resides.
       * @param   type
       *          Token type indicating which section is required. Must be
       *          within the range <code>CS_BEGIN &lt; <i>type</i> &lt;
       *          CS_END</code>.
       *
       * @return  AST node representing the anchor point for the specified
       *          section, or <code>null</code> if the section does not
       *          exist.
       *
       * @throws  IllegalArgumentException
       *          if <code>type</code> is not within the valid range of
       *          token types specified above.
       */
      public Aast getSectionAnchor(Aast classAst, int type)
      {
         if (type <= CS_BEGIN || type >= CS_END)
         {
            throw new IllegalArgumentException(
               "Invalid class section token type:  " + type);
         }
         
         return classAst.getImmediateChild(type, null);
      }
      
      /**
       * Creates a new Java target AST that is associated with a name of the 
       * caller's selection, and sets this AST as the current target AST.
       * <p>
       * Any changes made during processing of the previous AST will be lost
       * if they haven't been persisted by this point.
       *
       * @param   filename
       *          The filename to use as the Java AST filename.
       * @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.
       */
      public boolean createJavaFile(String filename, boolean projectRelative)
      {
         return createJavaFile(filename, projectRelative, false);
      }

      /**
       * Creates a new Java target AST that is associated with a name of the 
       * caller's selection, and sets this AST as the current target AST.
       * <p>
       * Any changes made during processing of the previous AST will be lost
       * if they haven't been persisted by this point.
       *
       * @param   filename
       *          The filename to use as the Java AST filename.
       */
      public JavaAst createJavaFile(String filename)
      {
         JavaAst result = createAst(COMPILE_UNIT, "compilation unit", null, null, -1);
         result.brainwash(filename);
         
         return result;
      }
      
      /**
       * Creates a new Java target AST that is associated with a name of the 
       * caller's selection, and sets this AST as the current target AST.
       * <p>
       * Any changes made during processing of the previous AST will be lost
       * if they haven't been persisted by this point.
       *
       * @param   filename
       *          The filename to use as the Java AST filename.
       * @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   virtualFile
       *          If <code>true</code> then the file is not supposed to be persisted into the file
       *          system, the file name is used to map some ID to the AST.
       */
      public boolean createJavaFile(String filename, boolean projectRelative, boolean virtualFile)
      {
         WorkArea wa = context.get();
         
         try
         {
            if (virtualFile)
            {
               wa.root = wa.current = createRootAst(filename, getResolver());
            }
            else
            {
               File file;
            
               if (projectRelative)
               {
                  file = Configuration.toFile(filename);
               }
               else
               {
                  file = new File(filename);
               }
         
               // create a new target tree
               wa.root = wa.current = createRootAst(file, getResolver());
            }
         }
         
         catch (Exception exc)
         {
            // some failure, we just won't have a Java AST!
            return false;
         }
         
         return true;
      }
      
      /**
       * Loads a persisted Java target AST that is associated with a name of  
       * the caller's selection, and sets this AST as the current target AST.
       * <p>
       * Any changes made during processing of the previous AST will be lost
       * if they haven't been persisted by this point.
       *
       * @param   filename
       *          The filename from which to read the Java AST.
       * @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.
       */
      public boolean loadJavaFile(String filename, boolean projectRelative)
      {
         WorkArea wa = context.get();
         
         try
         {
            if (projectRelative)
            {
               filename = Configuration.normalizeFilename(filename);
            }
            
            if (AstManager.get().isExistingAst(filename))
            {
               // read the existing target tree
               wa.root = wa.current = (JavaAst) AstManager.get().loadTree(filename);
               
               // set the current Java target AST into the resolver
               getResolver().setTargetRootAst(wa.root);
               
               // for each node in the loaded tree, register them with the
               // resolver
               Iterator nodes = wa.root.iterator();
               
               while (nodes.hasNext())
               {
                  Aast next = (Aast) nodes.next();
                  getResolver().registerAst(next);
               }               
            }
            else
            {
               return false;
            }
         }
         
         catch (Exception exc)
         {
            // some failure, we just won't have a Java AST!
            return false;
         }
         
         return true;
      }
      
      /**
       * Save the current target Java AST to file if the tree is not empty
       * (the root has at least 1 child).  If this AST is empty, it will be
       * removed from the {@link AstManager} which will essentially disable
       * further use of this AST.
       *
       * @return   <code>true</code> if the tree is persisted, 
       *           <code>false</code> if the tree is empty or on any failure  
       *           during persistence.
       *
       * @throws   ConfigurationException
       *           if environment is not properly configured
       * @throws   AstException
       *           if there is an error writing Java AST.
       */
      public boolean persistJavaFile()
      throws ConfigurationException,
             AstException,
             IOException
      {
         return persistJavaFile(getSource().getFilename());
      }
      
      /**
       * Save the specified Java AST to file if the tree is not empty (the root has at least 1 
       * child).  If this AST is empty, it will be removed from the {@link AstManager} which will
       * essentially disable further use of this AST.
       *
       * @param    ast
       *           The AST to be persisted.
       * 
       * @return   <code>true</code> if the tree is persisted, 
       *           <code>false</code> if the tree is empty or on any failure during persistence.
       *
       * @throws   ConfigurationException
       *           if environment is not properly configured
       * @throws   AstException
       *           if there is an error writing Java AST.
       */
      public boolean persistJavaAst(JavaAst ast)
      throws ConfigurationException,
             AstException,
             IOException
      {
         return persistJavaAst(ast.getFilename(), ast);
      }

      /**
       * Save the specified Java AST to file if the tree is not empty (the root has at least 1 
       * child).  If this AST is empty, it will be removed from the {@link AstManager} which will
       * essentially disable further use of this AST.
       *
       * @param    filename
       *           Base filename (without <code>jast</code> extension) under which to save the 
       *           Java AST. Must be an absolute path or relative to the project root.
       * @param    ast
       *           The AST to be persisted.
       * 
       * @return   <code>true</code> if the tree is persisted, 
       *           <code>false</code> if the tree is empty or on any failure during persistence.
       *
       * @throws   ConfigurationException
       *           if environment is not properly configured
       * @throws   AstException
       *           if there is an error writing Java AST.
       */
      public boolean persistJavaAst(String filename, JavaAst ast)
      throws ConfigurationException,
             AstException,
             IOException
      {
         WorkArea wa = context.get();
         
         filename = Configuration.normalizeFilename(filename);

         // bypass persistence if there is an empty tree
         if (ast.getNumImmediateChildren() == 0)
         {
            try
            {
               // no need to maintain this value in the registry
               AstManager.get().removeTree(filename);
            }
            catch (Exception exc)
            {
               // best efforts only
            }
            
            return false;
         }
         
         // read the existing target tree
         AstManager.get().saveTree(ast, filename, false);
         
         // save this into the list of new files if tracking is enabled
         if (wa.track)
         {
            wa.pfiles.add(filename);
         }
         
         return true;
      }
      
      /**
       * Save the current target Java AST to file if the tree is not empty
       * (the root has at least 1 child).  If this AST is empty, it will be
       * removed from the {@link AstManager} which will essentially disable
       * further use of this AST.
       *
       * @param    filename
       *           Base filename (without <code>jast</code> extension) under
       *           which to save the Java AST. Must be an absolute path or
       *           relative to the project root.
       *
       * @return   <code>true</code> if the tree is persisted, 
       *           <code>false</code> if the tree is empty or on any failure  
       *           during persistence.
       *
       * @throws   ConfigurationException
       *           if environment is not properly configured
       * @throws   AstException
       *           if there is an error writing Java AST.
       */
      public boolean persistJavaFile(String filename)
      throws ConfigurationException,
             AstException,
             IOException
      {
         filename = generateJavaFilename(filename);

         return persistJavaAst(filename, context.get().root);
      }
   }
   
   /**
    * Context local work area.
    */
   private static class WorkArea
   {
      /** The list of persisted files between 2 arbitrary points in time. */
      private Set<String> pfiles = null;
      
      /** <code>true</code> if persisted file names must be tracked. */
      private boolean track = false;
      
      /**
       * If <code>true</code>, the target Java AST will be created or loaded
       * when a new source AST is visited.
       */
      private boolean autoload = false;
      
      /** Current active Java AST associated with the current source AST. */
      private JavaAst current = null;
      
      /** Root of the target tree currently being processed. */
      private JavaAst root = null;
   }
}