# ProgressPatternWorker.java

    /*
    ** Module   : ProgressPatternWorker.java
    ** Abstract : Pattern worker with specific knowledge of the Progress language
    **
    ** Copyright (c) 2005-2022, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
    ** 001 ECF 20050310   @20279 Created initial version. Resolves token names
    **                           to token types.
    ** 002 ECF 20050315   @20342 Added visitAst method. No-op implementation
    **                           to match change in PatternWorker interface.
    ** 003 ECF 20050430   @20974 Changed method signatures to reflect changes
    **                           in PatternWorker interface. The initialize
    **                           and finish methods are affected.
    ** 004 ECF 20050526   @21304 Extend AbstractPatternWorker instead of
    **                           implementing PatternWorker directly. This was
    **                           made possible by allowing a worker library to
    **                           be optional in AbstractPatternWorker. This
    **                           significantly simplified this implementation.
    ** 005 GES 20050722   @21795 Moved parent to AbstractConversionWorker so
    **                           that a simple ProgressAst can be created
    **                           from a rule-set.
    ** 006 GES 20050928   @22875 Modified current and added new versions of
    **                           createProgressAst() methods to provide more
    **                           flexibility and features such as creation
    **                           of a node at a specific index position.
    ** 007 GES 20051017   @23057 Removed null parent protection logic.
    ** 008 GES 20090518   @42410 Import change.
    ** 009 CA  20140320          Added lookupTokenName, to resolve a progress.g token.
    ** 010 GES 20180906          Added helper to get closestPeerId from the Progress AST hierarchy.
    **         20181023          Added helper for a sort comparator by line and column.
    ** 011 HC  20181212          Added method lookupKeywordToken.
    ** 012 GES 20191003          Added createShadow() and insertInStream().
    **     CA  20191008          Enhanced insertInStream to allow insertion relative to a shadow node.
    ** 013 CA  20211214          The AST manager plugin must be context-local, for runtime conversion, as the
    **                           InMemoryRegistryPlugin is not thread-safe.
    **     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 com.goldencode.ast.*;
    import com.goldencode.p2j.pattern.*;
    import com.goldencode.p2j.convert.*;

    import antlr.*;
    import java.util.*;

    /**
     * A simple implementation of a pattern worker whose purpose is to convert
     * Progress language token names (as defined by the {@link ProgressParser})
     * to their integral types.
     * <p>
     * This worker also provides helpers to create <code>ProgressAst</code>
     * nodes.
     */
    public final class ProgressPatternWorker
    extends AbstractConversionWorker
    {
       /**
        * Cache current instance of the helper so that common methods have
        * access.
        */
       private ProgressAstHelper helper = null;

       /**
        * Default constructor.
        */
       public ProgressPatternWorker()
       {
          super();
          helper = new ProgressAstHelper();
          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 Progress language token names.

        * @param   constant
        *          A case-insensitive token name to be resolved to a token type.
        *
        * @return  The <code>Integer</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, Progress language token.
        */
       public Object resolveConstant(String constant)
       {
          int type = ProgressParser.lookupTokenType(constant);

          return (type >= 0 ? Integer.valueOf(type) : null);
       }

       /**
        * Helper to create Progress ASTs.
        */
       public class ProgressAstHelper
       {
          /** Map with the type of all known attributes and methods */
          private Map<Integer, Integer> attrMethTypes = ProgressParser.getAttributesAndMethods();

          /**
           * Search the given AST node and up each ancestor node in the path to the root to find the
           * first instance of the 'peerid' annotation.  This allows ASTs to be attached to the
           * right block of code, without necessarily knowing what parent might be enclosing
           * it.
           *
           * @param    start
           *           The node at which to start the search.
           *
           * @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(Aast start)
          {
             return ProgressPatternWorker.getClosestPeerId(start);
          }

          /**
           * General purpose Progress AST creation function which can create a
           * <code>ProgressAst</code> node using the passed token type and the
           * empty string as the text of the node.  It will then attach that node
           * to the specified parent as the last child.
           *
           * @param    type
           *           The token type for the Progress AST.
           * @param    parent
           *           The parent AST node.
           *
           * @return   The created AST node if successful or <code>null</code> if
           *           the parent is invalid, if the type is not valid or on any
           *           unexpected error during AST creation.
           */
          public Aast createProgressAst(int type, Aast parent)
          {
             return createProgressAst(type, "", parent, -1);
          }

          /**
           * General purpose Progress AST creation function which can create a
           * <code>ProgressAst</code> node using the passed token type and text
           * and then it will attach that node to the specified parent as the
           * last child.
           *
           * @param    type
           *           The token type for the Progress AST.
           * @param    text
           *           The text for the AST.
           * @param    parent
           *           The parent AST node.
           *
           * @return   The created AST node if successful or <code>null</code> if
           *           the parent is invalid, if the type is not valid or on any
           *           unexpected error during AST creation.
           */
          public Aast createProgressAst(int type, String text, Aast parent)
          {
             return createProgressAst(type, text, parent, -1);
          }

          /**
           * General purpose Progress AST creation function which can create a
           * <code>ProgressAst</code> node using the passed token type and the
           * empty string as the text of the node.  It will then attach that node
           * to the specified parent at the given index.
           *
           * @param    type
           *           The token type for the Progress 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 created AST node if successful or <code>null</code> if
           *           the parent is invalid, if the type is not valid or on any
           *           unexpected error during AST creation.
           */
          public Aast createProgressAst(int type, Aast parent, int index)
          {
             return createProgressAst(type, "", parent, index);
          }

          /**
           * General purpose Progress AST creation function which can create a
           * <code>ProgressAst</code> node using the passed token type and text
           * and then it will attach that node to the specified parent at the
           * given index.
           *
           * @param    type
           *           The token type for the Progress 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 created AST node if successful or <code>null</code> if
           *           the parent is invalid, if the type is not valid or on any
           *           unexpected error during AST creation.
           */
          public Aast createProgressAst(int    type,
                                        String text,
                                        Aast   parent,
                                        int    index)
          {
             ProgressAst node = new ProgressAst();

             // actually make this useful
             initializeAst(node, type, text, parent, getResolver(), index);

             return node;
          }

          /**
           * Create a shadow node representing the given token type and text.  This new node
           * will be associated with tree in which the currently active {@code copy} node
           * exists.  It will be parented by the root node of that tree, but it will not be
           * in the list of children of the root node (the parent linkage is created but the
           * child linkages are not created). This is how all shadow nodes exist.  The id will
           * be properly set but there will be no line/column numbers and the left/right
           * nodes will be {@code null}.
           *
           * @param    type
           *           The token type for the Progress AST.
           * @param    text
           *           The text for the AST.
           *
           * @return   The new shadow node.
           */
          public ShadowNode createShadow(int type, String text)
          {
             Aast node = new ProgressAst();
             Aast copy = getResolver().getCopyAst();
             Aast root = copy.getAncestor(-1);

             AstManager mgr    = AstManager.get();
             Long       treeId = mgr.getTreeId(root.getId());
             Long       nextId = mgr.getNextNodeId(treeId);

             node.initialize(new CommonToken(type, text));
             node.setParent(root); // the parent doesn't have us linked in as a child, that is good
             node.setId(nextId);

             ShadowNode shadow = new ShadowNode();
             shadow.setNode(node);

             return shadow;
          }

          /**
           * Insert the node in the stream before or after an existing node.  This node should have
           * NO linked shadow nodes. This is intended to maintain the order of the file contents on
           * anti-parsing.  It DOES NOT graft into the tree structure.  This purely maintains the
           * stream ordering by properly de-linking/linking/re-linking all of the EXISTING node's
           * shadow nodes.
           *
           * @param    node
           *           The node to insert.  If the existing node has shadow nodes on the insertion
           *           side, it will be linked to this node.
           * @param    existing
           *           The already existing node relative to whom the insertion will occur. Any
           *           shadow nodes already connected to the existing node on the insertion side
           *           will be de-linked from this node and re-linked on the other side of the
           *           insertion node.
           * @param    before
           *           {@code true} to insert before and {@code false} to insert after.
           */
          public void insertInStream(Aast node, Aast existing, boolean before)
          {
             Long nodeId  = node.getId();
             Long existId = existing.getId();

             TreeLocal local = existing.getLocal();

             if (before)
             {
                ShadowNode leftEx = existing.getLeftShadow();

                // only a left shadow node for the existing node gets linked
                if (leftEx != null)
                {
                   // update the existing shadow node
                   leftEx.setRight(nodeId);
                   local.insertMappings(leftEx);
                }
             }
             else
             {
                ShadowNode rightEx = existing.getRightShadow();

                if (rightEx != null)
                {
                   // update the existing shadow node
                   rightEx.setLeft(nodeId);
                   local.insertMappings(rightEx);
                }
             }
          }

          /**
           * Insert the specific shadow node (and no other nodes) into the stream before or after
           * an existing node.  This is intended to maintain the order of the file contents on
           * anti-parsing.  It DOES NOT graft into the tree structure.  This purely maintains the
           * stream ordering by properly de-linking/linking/re-linking all shadow nodes and ASTs.
           * <p>
           * The shadow node should not have any existing left or right linkages!
           *
           * @param    node
           *           The shadow node to insert.  Both sides will be linked if the existing
           *           node has linkages on both sides.
           * @param    existing
           *           The already existing node relative to whom the insertion will occur. Any
           *           shadow nodes already connected to the existing node on the insertion side
           *           will be de-linked from this node and re-linked on the other side of the
           *           insertion node.
           * @param    before
           *           {@code true} to insert before and {@code false} to insert after.
           */
          public void insertInStream(ShadowNode node, Aast existing, boolean before)
          {
             Long nodeId     = node.getNode().getId();
             Long existingId = existing.getId();

             TreeLocal local = existing.getLocal();
             ShadowNode existingShadow = local.getShadowNode(existingId);
             if (existingShadow != null)
             {
                if (before)
                {
                   node.setLeft(existingShadow.getLeft());
                   existingShadow.setLeft(nodeId);
                   node.setRight(existingId);
                }
                else
                {
                   node.setRight(existingShadow.getRight());
                   existingShadow.setRight(nodeId);
                   node.setLeft(existingId);
                }

                local.addShadowNode(node);

                return;
             }

             if (before)
             {
                ShadowNode leftEx = existing.getLeftShadow();

                if (leftEx == null)
                {
                   // find the next non-shadow node on the left side
                   Aast left = existing.getLeftAdjacent();

                   if (left != null)
                   {
                      node.setLeft(left.getId());
                   }
                }
                else
                {
                   node.setLeft(leftEx.getNode().getId());

                   // update the existing shadow node
                   leftEx.setRight(nodeId);
                   local.insertMappings(leftEx);
                }

                node.setRight(existingId);
             }
             else
             {
                ShadowNode rightEx = existing.getRightShadow();

                if (rightEx == null)
                {
                   // find the next non-shadow node on the left side
                   Aast right = existing.getRightAdjacent();

                   if (right != null)
                   {
                      node.setRight(right.getId());
                   }
                }
                else
                {
                   node.setRight(rightEx.getNode().getId());

                   // update the existing shadow node
                   rightEx.setLeft(nodeId);
                   local.insertMappings(rightEx);
                }

                node.setLeft(existingId);
             }

             local.addShadowNode(node);
          }

          /**
           * Translates an integer value of a parser token type into the associated
           * human readable text representation.
           *
           * @param    type
           *           The integer token type.
           *
           * @return   A valid parser-defined symbolic name or <code>null</code> if
           *           no match was found.
           */
          public String lookupTokenName(int type)
          {
             return ProgressParser.lookupTokenName(type);
          }

          /**
           * Helper to return a comparator that can be used to sort a list by the order of
           * the nodes in the file (line and then column).
           *
           * @return   A sort comparator that sorts by line number and then by column number.
           */
          public Comparator<? extends Aast> getSortComparator()
          {
             return Comparator.comparingInt(Aast::getLine).thenComparingInt(Aast::getColumn);
          }

          /**
           * Given a partial (abbreviated) or full keyword text, lookup its token type.
           *
           * @param    text
           *           The keyword text.
           *
           * @return   Its {@link ProgressParserTokenTypes token type} or <code>-1</code> if not found.
           */
          public int lookupKeywordToken(String text)
          {
             return ProgressAst.lookupKeywordToken(text);
          }

          /**
           * Searches the attribute and method dictionary for a token type matching
           * the passed keyword.  The token type of the method return type or the
           * attribute data type is returned, if a match is found.  A token type
           * of -1 is invalid in the Lexer and Parser, so this value is used
           * to denote a search that found no match.
           *
           * @param    ktype
           *           Token type of the keyword to lookup.
           *
           * @return   The attribute or method token type that was found or -1 if no
           *           match was found.
           */
          public int lookupAttributeOrMethod(int ktype)
          {
             Integer res = attrMethTypes.get(ktype);
             if (res == null)
             {
                return -1;
             }

             return res;
          }
       }
    }