TemplateWorker.java

/*
** Module   : TemplateWorker.java
** Abstract : provides functions for accessing J2SE system properties
**
** Copyright (c) 2005-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description----------------------------------
** 001 GES 20050620   @21527 Created initial version supporting a simple
**                           AST template loading and grafting mechanism.
** 002 GES 20050622   @21541 Added depot services to store and retrieve
**                           ASTs between rulesets that are disconnected
**                           in time and place (still requires such
**                           sharing to occur within a single pattern
**                           engine run).  Also added resolver 
**                           registration to the grafting process.
** 003 ECF 20050627   @21555 Modification to 'graft' user function to be
**                           more lenient. The source parameter may now be
**                           null, even if the grafted template contains
**                           an AST which has a peernode annotation. The
**                           peernode will still be returned from the
**                           graft method in this case.
** 004 ECF 20050711   @21688 Added a variant of graft method. This version
**                           takes an Aast as the parent, rather than an
**                           ID. This is needed when grafting to a parent
**                           which is not part of the AST registry.
** 005 GES 20050819   @22186 Added graftAt() support for adding at a
**                           specific index.
** 006 GES 20051013   @23059 Removed requirement that parent be non-null
**                           in graft/graftAt variants.
** 007 GES 20090424   @41935 Import change.
** 008 GES 20090429   @42049 Match package and class name changes.
** 009 GES 20090515   @42214 Moved to AstManager from AstRegistry/AstPersister.
** 010 GES 20090518   @42389 Import change.
** 011 SVL 20130624          Allow to load templates at server run time.
** 012 ECF 20140808          Set all AST nodes IDs to zero when loaded from XML, as a safety precaution.
** 013 ECF 20150715          Replace StringBuffer with StringBuilder.
** 014 CA  20170825          Allow graftAt to work without having to attach to a parent.
** 015 CA  20191008          Template ASTs are allowed to have inlined shadow nodes.
** 016 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.
**     OM  20220512          Developer mode: allows to load resources from file system if jar is not built.
*/

/*
** 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.util.*;
import java.util.concurrent.*;

import org.w3c.dom.*;
import com.goldencode.ast.*;
import com.goldencode.util.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.security.*;

/**
 * Provides a set of user-defined functions for accessing libraries of
 * pre-defined AST subtrees.  Each AST subtree is called a 'template' and
 * can be accessed by a name which must be unique within a given template
 * library.  Each template library is an XML file stored in a directory  
 * in the <code>patpath</code>.  All template library filenames must end
 * in a suffix specified by {@link #TPL_SUFFIX}.
 * <p>
 * Each template is defined in the format required to be loaded using the
 * <code>AstPersister</code>.  Each valid template must be rooted at
 * an <code>AstPersister.ELEM_ROOT</code> element.
 * <p>
 * A generic facility for text replacements is provided.  This facility is
 * implemented after the reading of the elements from the XML file but
 * before those elements are converted into an AST subtree by the persister.
 * This allows any attribute in the same template to be customized on the fly
 * for different purposes.  Since the persister translates everything from
 * its text form into a binary AST form, the result of these text 
 * transformations is the same as if the original XML file had been hard
 * coded to the runtime values provided.  Thus token types, token text and
 * other attributes (even annotations) can be transformed in this manner.
 * Any replaceable text must be formatted with <code>${key}</code> where
 * a list of string-based key/value pairs are passed at runtime and are used  
 * as the replacements for each matching key found.  Note that the
 * <code>${</code> and <code>}</code> are only stored in the XML file, they
 * are not actually passed as part of the key in the replacements list. 
 * <p>
 * This class also provides depot services to store and retrieve ASTs between
 * rulesets that are disconnected in time and place (it still requires such
 * sharing to occur within a single pattern engine run). For example, if
 * a tree transformation requires a separation between the location at which
 * an AST subtree is created and the location at which that subtree is 
 * attached, the subtree can be stored in the depot under a name and then
 * retrieved 'downstream' in the same pattern engine run (the same pipeline).
 * Please note that only 1 AST can be stored per name so this facility 
 * operates like a shared local variable pool of ASTs.
 */
public class TemplateWorker
extends AbstractPatternWorker
{
   /** Suffix for all template library filenames. */
   public static final String TPL_SUFFIX = ".tpl";
   
   /** Annotation name to mark a template node as the peer node. */
   public static final String PEERNODE = "peernode";
   
   /** Context local work area. */
   private static final ContextLocal<WorkArea> context = new ContextLocal<WorkArea>()
   {
      protected WorkArea initialValue()
      {
         return new WorkArea();
      }
   };

   /**
    * Default constructor which calls the super constructor and registers
    * libraries with the pattern engine.
    */
   public TemplateWorker()
   {
      super();
      setLibrary(new Template());
   }
   
   /**
    * This is a pattern engine library that provides easy access to 
    * libraries of pre-defined AST subtrees.
    */
   public class Template
   {
      /** Names a given template. */
      private static final String ATTR_NAME = "name";
      
      /** Caches the list of loaded AST templates. */  
      private final Map<String, Node> templates = new ConcurrentHashMap<>();
      
      /** Caches the list of loaded XML libraries (to avoid reloading). */  
      private final Set<String> libraries = Collections.newSetFromMap(new ConcurrentHashMap<>());
      
      /**
       * Load a named template library from an XML file. The file is named
       * <code>&lt;name&gt;.&lt;tpl&gt;</code>. it must be located somewhere
       * in the path defined by the global configuration parameter
       * <code>patpath</code>.
       *
       * @param   name
       *          Root name of the XML library containing AST templates.
       *
       * @throws  ConfigurationException
       *          if any error occurs loading the template library.
       */
      public void load(String name)
      throws ConfigurationException
      {
         // avoid reloading
         if (libraries.contains(name))
         {
            return;
         }
         
         Element root = null;
         Document dom = null;
         
         try
         {
            if (Configuration.isRuntimeConfig())
            {
               InputStream stream = ConfigLoader.getConversionResource(name, TPL_SUFFIX);
               if (stream != null)
               {
                  dom = XmlHelper.parse(stream);
               }
            }
            
            // developer mode: allow resource to be loaded at runtime from file system if present, avoiding
            // having the jar built
            if (dom == null)
            {
               File file = ConfigLoader.findConfigurationFile(name, TPL_SUFFIX);
               dom = XmlHelper.parse(file);
            }
            
            if (dom == null)
            {
               throw new ConfigurationException("Cannot find '" + name + TPL_SUFFIX + "' resource");
            }
            root = dom.getDocumentElement();
         }
         catch (Exception exc)
         {
            throw new ConfigurationException("Cannot load template library named " + name + ".", exc);
         }
         
         // cache our library name so it doesn't get reloaded
         libraries.add(name);
         
         // tag name of the element we are looking for
         String search = XmlFilePlugin.ELEM_ROOT;
         
         // the list of children will include #Text and other DOM defined
         // trash... get the first of our children
         Node tpl = root.getFirstChild();
         
         // search for all children that have a tagName of ELEM_ROOT
         while (tpl != null)
         {
            // is this a template?
            if (tpl.getNodeName().equals(search))
            {
               // walk child ast nodes, setting any non-zero id attribute to zero as a safety
               // precaution
               zeroAstIds((Element) tpl);
               
               // we have a template, get its name and cache it
               String tname = ((Element) tpl).getAttribute(ATTR_NAME);
               
               if (tname.trim().length() == 0)
               {
                  String errmsg = "Missing name for element " + search + " in template library " + name + ".";
                  throw new ConfigurationException(errmsg);
               }
               
               templates.put(tname, tpl);
            }
            
            tpl = tpl.getNextSibling();
         }
      }
      
      /**
       * Lookup a named template, process any attribute text replacements
       * (if specified), attach the new AST subtree as the last child of the
       * specified parent and, if any of the AST nodes has a 
       * <code>PEERNODE</code> attribute AND the <code>source</code>
       * parameter is not <code>null</code>, cross-reference that node with
       * <code>source</code>, using the respective 'peerid' values. Any
       * specified replacement key found in the form <code>${key}</code>
       * will be replaced with the matching value.  The object array must
       * be made as sequential pairs of key string and matching value string.
       *
       * @param    name
       *           The name of the template to use.  This template must have
       *           been previously loaded using {@link #load}.
       * @param    source
       *           The source node to annotate if the <code>PEERNODE</code>
       *           annotation exists in the AST subtree. May be
       *           <code>null</code>.
       * @param    parentid
       *           The ID of the target AST to be used as a parent for this
       *           subtree. If <code>null</code> no attachment occurs but the
       *           tree is still created.
       * @param    replacements
       *           An array of key/value string pairs where any keys found
       *           in the template node's attribute values will be replaced
       *           with the associated values.
       *
       * @return   The node which was specified as the peer node or the
       *           root node of the newly created subtree if no peer node
       *           was specified.
       *
       * @throws   AstException
       *           if there is any problem creating or attaching the template.
       */
      public Aast graft(String name, Aast source, long parentid, Object[] replacements)
      throws AstException
      {
         return graftAt(name, source, parentid, -1, replacements);
      }
      
      /**
       * Lookup a named template, process any attribute text replacements
       * (if specified), attach the new AST subtree as a child of the
       * specified parent AT THE SPECIFIED INDEX and, if any of the AST nodes 
       * has a <code>PEERNODE</code> attribute AND the <code>source</code>
       * parameter is not <code>null</code>, cross-reference that node with
       * <code>source</code>, using the respective 'peerid' values. Any
       * specified replacement key found in the form <code>${key}</code>
       * will be replaced with the matching value.  The object array must
       * be made as sequential pairs of key string and matching value string.
       *
       * @param    name
       *           The name of the template to use.  This template must have
       *           been previously loaded using {@link #load}.
       * @param    source
       *           The source node to annotate if the <code>PEERNODE</code>
       *           annotation exists in the AST subtree. May be
       *           <code>null</code>.
       * @param    parentid
       *           The ID of the target AST to be used as a parent for this
       *           subtree. If <code>null</code> no attachment occurs but the
       *           tree is still created.
       * @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.
       * @param    replacements
       *           An array of key/value string pairs where any keys found
       *           in the template node's attribute values will be replaced
       *           with the associated values.
       *
       * @return   The node which was specified as the peer node or the
       *           root node of the newly created subtree if no peer node
       *           was specified.
       *
       * @throws   AstException
       *           if there is any problem creating or attaching the template.
       */
      public Aast graftAt(String name, Aast source, long parentid, int index, Object[] replacements)
      throws AstException
      {
         // obtain the parent node
         Aast parent = getResolver().getAst(parentid);
         
         if (parent == null)
         {
            throw new IllegalArgumentException("Invalid parentid:  " + parentid);
         }
         
         return graftAt(false, name, source, parent, index, replacements);
      }
      
      /**
       * Lookup a named template, process any attribute text replacements
       * (if specified), attach the new AST subtree as the last child of the
       * specified parent and, if any of the AST nodes has a 
       * <code>PEERNODE</code> attribute AND the <code>source</code>
       * parameter is not <code>null</code>, cross-reference that node with
       * <code>source</code>, using the respective 'peerid' values. Any
       * specified replacement key found in the form <code>${key}</code>
       * will be replaced with the matching value.  The object array must
       * be made as sequential pairs of key string and matching value string.
       *
       * @param    name
       *           The name of the template to use.  This template must have
       *           been previously loaded using {@link #load}.
       * @param    source
       *           The source node to annotate if the <code>PEERNODE</code>
       *           annotation exists in the AST subtree. May be
       *           <code>null</code>.
       * @param    parent
       *           The target AST to be used as a parent for this subtree.
       *           If <code>null</code> no attachment occurs but the tree
       *           is still created.
       * @param    replacements
       *           An array of key/value string pairs where any keys found
       *           in the template node's attribute values will be replaced
       *           with the associated values.
       *
       * @return   The node which was specified as the peer node or the
       *           root node of the newly created subtree if no peer node
       *           was specified.
       *
       * @throws   AstException
       *           if there is any problem creating or attaching the template.
       */
      public Aast graft(String name, Aast source, Aast parent, Object[] replacements)
      throws AstException
      {
         return graftAt(false, name, source, parent, -1, replacements);
      }
      
      /**
       * Lookup a named template, process any attribute text replacements
       * (if specified), attach the new AST subtree as a child of the
       * specified parent AT THE SPECIFIED INDEX and, if any of the AST nodes 
       * has a <code>PEERNODE</code> attribute AND the <code>source</code>
       * parameter is not <code>null</code>, cross-reference that node with
       * <code>source</code>, using the respective 'peerid' values. Any
       * specified replacement key found in the form <code>${key}</code>
       * will be replaced with the matching value.  The object array must
       * be made as sequential pairs of key string and matching value string.
       *
       * @param    name
       *           The name of the template to use.  This template must have
       *           been previously loaded using {@link #load}.
       * @param    source
       *           The source node to annotate if the <code>PEERNODE</code>
       *           annotation exists in the AST subtree. May be
       *           <code>null</code>.
       * @param    parent
       *           The target AST to be used as a parent for this subtree.
       *           If <code>null</code> no attachment occurs but the tree
       *           is still created.
       * @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.
       * @param    replacements
       *           An array of key/value string pairs where any keys found
       *           in the template node's attribute values will be replaced
       *           with the associated values.
       *
       * @return   The node which was specified as the peer node or the
       *           root node of the newly created subtree if no peer node
       *           was specified.
       *
       * @throws   AstException
       *           if there is any problem creating or attaching the template.
       */
      public Aast graftAt(String   name,
                          Aast     source,
                          Aast     parent,
                          int      index,
                          Object[] replacements)
      {
         return graftAt(false, name, source, parent, index, replacements);
      }
      
      /**
       * Lookup a named template, process any attribute text replacements
       * (if specified), attach the new AST subtree as a child of the
       * specified parent AT THE SPECIFIED INDEX and, if any of the AST nodes 
       * has a <code>PEERNODE</code> attribute AND the <code>source</code>
       * parameter is not <code>null</code>, cross-reference that node with
       * <code>source</code>, using the respective 'peerid' values. Any
       * specified replacement key found in the form <code>${key}</code>
       * will be replaced with the matching value.  The object array must
       * be made as sequential pairs of key string and matching value string.
       *
       * @param    inlineShadows
       *           Flag indicating that the template needs to have the shadows inlined.
       * @param    name
       *           The name of the template to use.  This template must have
       *           been previously loaded using {@link #load}.
       * @param    source
       *           The source node to annotate if the <code>PEERNODE</code>
       *           annotation exists in the AST subtree. May be
       *           <code>null</code>.
       * @param    parent
       *           The target AST to be used as a parent for this subtree.
       *           If <code>null</code> no attachment occurs but the tree
       *           is still created.
       * @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.
       * @param    replacements
       *           An array of key/value string pairs where any keys found
       *           in the template node's attribute values will be replaced
       *           with the associated values.
       *
       * @return   The node which was specified as the peer node or the
       *           root node of the newly created subtree if no peer node
       *           was specified.
       *
       * @throws   AstException
       *           if there is any problem creating or attaching the template.
       */
      public Aast graftAt(boolean  inlineShadows,
                          String   name,
                          Aast     source,
                          Aast     parent,
                          int      index,
                          Object[] replacements)
      throws AstException
      {
         Node tpl = templates.get(name);
         
         if (tpl == null)
         {
            throw new IllegalArgumentException("Template named '" + name + "' doesn't exist.");
         }
         
         // make a temporary deep copy of this subtree
         Node template = tpl.cloneNode(true);
         
         HashMap<String, String> replace = null;
         
         // create a map of all parameters to be replaced
         if (replacements != null)
         {
            replace = new HashMap<>();
            for (int i = 0; i < replacements.length; i += 2)
            {
               replace.put(replacements[i] == null ? null : replacements[i].toString(),
                           replacements[i + 1] == null ? null : replacements[i + 1].toString());
            }
         }
         
         // "sweep" all replacement keys with their runtime values
         processReplacements(template, replace);
         
         // instantiate the AST subtree
         Aast result = XmlFilePlugin.loadTree((Element)template, inlineShadows);
         
         // graft and register the subtree
         if (parent != null)
         {
            attach(parent, result, index);
         }
         
         // if one of the nodes was marked as the peer node, implement the
         // proper peer relationship with the passed source node
         Iterator<Aast> iter = result.iterator();
         
         while (iter.hasNext())
         {
            Aast child = iter.next();
            
            // detect if this node is marked as the peer
            if (child.isAnnotation(PEERNODE))
            {
               // return this node instead of the root
               result = child;
               
               // remove the annotation since it was for our use only
               result.removeAnnotation(PEERNODE);
               
               // fixup the peer ids
               if (source != null)
               {
                  source.putAnnotation("peerid", result.getId());
                  result.putAnnotation("peerid", source.getId());
               }
            }
         }
         
         return result;
      }
      
      /**
       * Grafts the child to the parent as the last childe and registers
       * the subtree (rooted at the child) with the resolver.  All nodes
       * within the child subtree will have their IDs and parents fixed up to
       * properly integrate with the new tree.
       *
       * @param    parent
       *           The node at which to graft the child subtree.  If 
       *           <code>null</code> this method immediately returns.
       * @param    child
       *           The root of the subtree to be grafted.
       *
       * @throws   AstException
       *           if there is any problem grafting the child to the parent.
       */
      public void attach(Aast parent, Aast child)
      throws AstException
      {
         attach(parent, child, -1);
      }
      
      /**
       * Grafts the child to the parent at the specified index and registers
       * the subtree (rooted at the child) with the resolver.  All nodes
       * within the child subtree will have their IDs and parents fixed up to
       * properly integrate with the new tree.
       *
       * @param    parent
       *           The node at which to graft the child subtree.  If 
       *           <code>null</code> this method immediately returns.
       * @param    child
       *           The root of the subtree to be grafted.
       * @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.
       *
       * @throws   AstException
       *           if there is any problem grafting the child to the parent.
       */
      public void attach(Aast parent, Aast child, int index)
      throws AstException
      {
         if (parent == null)
         {
            // nothing to do
            return;
         }
         
         // graft the subtree to the parent
         parent.graftAt(child, index);
         
         // register all children with the resolver if parent has an ID
         if (parent.getId() != null)
         {
            Iterator<Aast> iter = child.iterator();
            
            while (iter.hasNext())
            {
               Aast next = iter.next();
               getResolver().registerAst(next);
            }
         }
      }
      
      /**
       * Adds or replaces the given AST in the depot using the specified name
       * as a key by which the AST can be retrieved later.
       *
       * @param    name
       *           The name to use as a key for the depot.
       * @param    ast
       *           The AST to add to the depot.
       *
       * @return   Always <code>true</code>.
       */
      public boolean addToDepot(String name, Aast ast)
      {
         context.get().depot.put(name, ast);
         return true;
      }
      
      /**
       * Returns the AST found in the depot by the given name, removing it
       * first. If no such AST exists, <code>null</code> is returned.
       *
       * @param    name
       *           The name to use as a key for the depot lookup.
       *
       * @return   The AST found or <code>null</code> if no such AST exists.
       */
      public Aast removeFromDepot(String name)
      {
         return context.get().depot.remove(name);
      }
      
      /**
       * Returns the AST found in the depot by the given name or 
       * <code>null</code> if no such AST exists.
       *
       * @param    name
       *           The name to use as a key for the depot lookup.
       *
       * @return   The AST found or <code>null</code> if no such AST exists.
       */
      public Aast getFromDepot(String name)
      {
         return context.get().depot.get(name);
      }
      
      /**
       * Detects if an AST exists in the depot under the given name.
       *
       * @param    name
       *           The name to use as a key for the depot lookup.
       */
      public boolean isInDepot(String name)
      {
         return context.get().depot.containsKey(name);
      }
      
      /**
       * Recursively sweep all attribute values of this node and its subtree,
       * based on a map of key/value pairs.  Replacements occur for any
       * instances of <code>${key}</code> in any attribute value.  Once the
       * current node's attributes are processed, all child nodes are visited
       * and this method is called recursively for each child.
       * 
       * @param    tpl
       *           The node whose attributes must be processed.
       * @param    replacements
       *           A collection of key/value pairs. 
       */
      private void processReplacements(Node tpl, Map<String, String> replacements)
      {
         // is there anything to do?
         if (replacements == null)
         {
            return;
         }
         
         // read all attributes from this node
         NamedNodeMap list = tpl.getAttributes();
         
         // are there any attributes?
         if (list != null)
         {
            // loop through elements and sweep each one
            int len = list.getLength();
            
            for (int i = 0; i < len; i++)
            {
               Node next = list.item(i);
               
               // process the value replacements for all ${key} constructs
               String replace = StringHelper.sweep(next.getNodeValue(), replacements);
               
               // save our changes
               next.setNodeValue(replace);
            }
         }
         
         // process all children recursively
         Node child = tpl.getFirstChild();
         
         while (child != null)
         {
            processReplacements(child, replacements);
            child = child.getNextSibling();
         }
      }
      
      /**
       * As a safety measure, set all AST IDs to 0, so they will have appropriate, available IDs
       * applied when the template is grafted to a parent AST.
       * 
       * @param   tpl
       *          Java template XML element, whose descendant elements represent ASTs.
       */
      private void zeroAstIds(Element tpl)
      {
         NodeList descendants = tpl.getElementsByTagName(XmlFilePlugin.ELEM_AST);
         int len = descendants.getLength();
         
         for (int i = 0; i < len; i++)
         {
            Element next = (Element) descendants.item(i);
            if (next.hasAttribute(XmlFilePlugin.ATTR_ID))
            {
               next.setAttribute(XmlFilePlugin.ATTR_ID, "0");
            }
         }
      }
   }
   
   /**
    * Context local work area.
    */
   private static class WorkArea
   {
      /** Temporary storage for ASTs that are created but not yet attached. */ 
      private final Map<String, Aast> depot = new HashMap<>();
  }
}