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