# BufferScopeTracker.java

    /*
    ** Module   : BufferScopeTracker.java
    ** Abstract : contains data and methods to manage the buffer scoping to a
    **            specific block and the list of associated schema references
    **
    ** Copyright (c) 2005-2022, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
    ** 001 GES 20050921   @22786 First version which provides a helper class which can be easily used
    **                           from a rule set. This class contains data and methods to manage the
    **                           buffer scoping to a specific block and the list of associated schema
    **                           references.
    ** 002 GES 20090518   @42376 Import change.
    ** 003 OM  20161011          nearestRecordScopingBlock() takes into account type and location of
    **                           the buffer to match the P4GL buffer scoping. Added generics.
    ** 004 CA  20220613          'nearestRecordScopingBlock' must consider the OO blocks, too (constructor, method,
    **                           property block, destructor).
    */
    /*
    ** 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.convert;

    import java.util.*;
    import com.goldencode.ast.*;
    import com.goldencode.p2j.uast.*;

    /**
     * This class contains the data and helper methods to implement/manage the
     * buffer scoping process during conversion.  Each instance of a buffer will
     * correspond with an instance of this class.  More than one instance may
     * exist for a given buffer name in a given AST.  Each instance contains
     * the knowledge of the specific block to which it is scoped as well as the
     * list of all associated schema references which use this buffer.
     */
    public class BufferScopeTracker
    implements ProgressParserTokenTypes
    {
       /** The list of {@code Aast} nodes which use this buffer. */
       private Set<Aast> refNodes = null;

       /** The {@code Aast} node to which this buffer is scoped. */
       private Aast block = null;

       /** The type of the scope. */
       private int type = -1;

       /**
        * Create an instance to track a buffer scope of a given buffer name.
        *
        * @param    type
        *           The token type representing the type of reference being
        *           represented (strong, weak or free).
        */
       public BufferScopeTracker(int type)
       {
          setType(type);
          refNodes  = new HashSet<>();
       }

       /**
        * Add the given AST node to the list of references to this buffer scope.
        * <p>
        * This method does not modify or set the block node to which the buffer
        * is scoped.  For that, use {@link #setScopeBlock}.
        *
        * @param    ast
        *           The node in the AST that references the buffer.
        */
       public void addReference(Aast ast)
       {
          if (!refNodes.isEmpty())
          {
             setType(EXPANDED_SCOPE);
          }

          refNodes.add(ast);
       }

       /**
        * Add the given AST node to the list of references to this buffer scope
        * and update the block node to the nearest enclosing AST node which has
        * the record scoping property.  This assumes that the <code>block</code>
        * member is a valid AST node.
        *
        * @param    ast
        *           The node in the AST that references the buffer.
        */
       public void mergeReference(Aast ast)
       {
          // is our block already the root node?
          if (!block.isRoot())
          {
             // find the nearest enclosing block
             Aast nearest = block.nearestEnclosing(ast);

             // walk up to find a block with record scoping, set the enclosing block
             block = nearestRecordScopingBlock(nearest, false);
          }

          // add the reference
          addReference(ast);
       }

       /**
        * Search up the tree from the given AST node to find the first block node that also has the
        * {@code recordScoping} property. Note that if the given node is of type {@code BLOCK} and
        * this node has the {@code recordScoping} property, then the given node amy be returned.
        * <p>
        * The search takes into consideration the type of the buffer (whether it was explicitly
        * created) and the block location (whether the buffer usage is inside an internal procedure,
        * function or database trigger). In the case of implicit buffers in these location the free
        * references and weak references will create scopes in the main procedure (external). This
        * method assumes the strong references are always using explicit buffers for these locations.
        * Using an implicit buffer in a procedure is an error (3503) in 4GL.
        *
        * This code assumes that only {@code BLOCK} nodes can have the {@code recordScoping} property.
        *
        * @param   ast
        *          The AST node from which to search upward.
        * @param   implicit
        *          Whether the buffer for this search is not explicit. In this case, if the current
        *          block is included in a procedure, the scope is the one of the external procedure
        *          instead of the local one.
        *
        * @return  A block node with the {@code recordScoping} property (this should never be
        *          {@code null} since the root node of every tree meets this criteria at a minimum.
        */
       public Aast nearestRecordScopingBlock(Aast ast, boolean implicit)
       {
          // for non strong-scopes not explicitly/locally defined in a procedure/function/trigger we
          // use the external procedure scope.
          // (strong-scope buffers MUST be explicitly created inside a procedure/function/trigger)
          if (implicit)
          {
             Aast pAst = ast.getAncestor(-1, PROCEDURE);
             if (pAst == null)
             {
                pAst = ast.getAncestor(-1, FUNCTION);
             }
             if (pAst == null)
             {
                pAst = ast.getAncestor(-1, CONSTRUCTOR);
             }
             if (pAst == null)
             {
                pAst = ast.getAncestor(-1, DEFINE_PROPERTY);
             }
             if (pAst == null)
             {
                pAst = ast.getAncestor(-1, METHOD_DEF);
             }
             if (pAst == null)
             {
                pAst = ast.getAncestor(-1, DESTRUCTOR);
             }
             if (pAst == null)
             {
                pAst = ast.getAncestor(-1, TRIGGER_BLOCK);
             }
             if (pAst != null)
             {
                ast = pAst.getParent();
             }
          }
          Boolean recordProp = (Boolean) ast.getAnnotation("recordScoping");

          // manually work up the tree from there until the block node with the record scoping
          // property is found (this will at least be the root node so it should never be null)
          while (recordProp == null || !recordProp)
          {
             // get the next node
             ast = ast.getParent();

             // check integrity
             if (ast == null)
             {
                throw new RuntimeException(
                      "This source tree is missing 'recordScoping' annotations!");
             }

             recordProp = (Boolean) ast.getAnnotation("recordScoping");
          }

          // safety first
          if (ast.getType() != BLOCK)
          {
             throw new RuntimeException("Expecting a BLOCK node and found a " +
                                        ast.toString() + " instead.");
          }

          return ast;
       }

       /**
        * Returns an iterator to the list of reference nodes.  Each node is of
        * type <code>Aast</code>.
        *
        * @return   An iterator to all reference nodes.
        */
       public Iterator getReferenceList()
       {
          return refNodes.iterator();
       }

       /**
        * Scopes this buffer to a specific block AST node.  This method uses the
        * {@link #nearestRecordScopingBlock} method to check and find (if needed)
        * the nearest enclosing block that contains the record scoping property.
        * If the given AST node is of type <code>BLOCK</code> and has the record
        * scoping property, then this value is set.  Otherwise, this will search
        * up the tree from this given node to find the first qualifying block.
        *
        * @param   block
        *          The block AST node to which this buffer is scoped OR the AST node at which to
        *          start the search.
        * @param   implicit
        *          {@code true} if the the analysed buffer is implicit defined.
        */
       public void setScopeBlock(Aast block, boolean implicit)
       {
          this.block = nearestRecordScopingBlock(block, implicit);
       }

       /**
        * Gets the specific block AST node to which this buffer is scoped.
        *
        * @return   The block AST node to which this buffer is scoped.
        */
       public Aast getScopeBlock()
       {
          return block;
       }

       /**
        * Sets the Progress token type describing the scope.
        *
        * @param    type
        *           One of the following: {@code STRONG_REFERENCE}, {@code WEAK_REFERENCE},
        *           {@code FREE_REFERENCE} or {@code EXPANDED_SCOPE}.
        */
       public void setType(int type)
       {
          if (type == STRONG_REFERENCE || type == WEAK_REFERENCE ||
              type == FREE_REFERENCE   || type == EXPANDED_SCOPE )
          {
             this.type = type;
          }
       }

       /**
        * Accesses the Progress token type describing the scope.
        *
        * @return   One of the following: <code>STRONG_REFERENCE</code>,
        *           <code>WEAK_REFERENCE</code>, <code>FREE_REFERENCE</code> or
        *           <code>EXPANDED_SCOPE</code>.
        */
       public int getType()
       {
          return type;
       }

       /**
        * Adds all of the nodes in the given tracker's list of references to this
        * tracker's reference list.  No state is changed in the passed tracker.
        * <p>
        * This method does not modify or set the block node to which the buffer
        * is scoped.  For that, use {@link #setScopeBlock}.
        *
        * @param    bst
        *           The tracker from which to copy the references.
        */
       public void combine(BufferScopeTracker bst)
       {
          // merge the references
          refNodes.addAll(bst.refNodes);

          // set the type
          setType(EXPANDED_SCOPE);
       }
    }