# NameNode.java

    /*
    ** Module   : NameNode.java
    ** Abstract : Node in a progress schema namespace
    **
    ** Copyright (c) 2004-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
    ** 001 ECF 20041217   @19170 Created initial version.
    ** 002 ECF 20040118   @19340 Added getParent method for use by
    **                           SchemaDictionary.
    ** 003 ECF 20050210   @19744 Added toAliasedName method for use by
    **                           SchemaDictionary. Modified toQualfiedName
    **                           to delegate to this new method.
    ** 004 ECF 20050216   @19839 Added setParent method to allow parent to be
    **                           changed. Automatically removes this node from
    **                           old parent's namespace before adding it to
    **                           new parent's namespace.
    ** 005 ECF 20050308   @20246 Modified toAliasedName to correctly handle
    **                           temp- and work-table names. These do not have
    **                           database qualifiers, which broke the previous
    **                           algorithm.
    ** 006 ECF 20050413   @20705 Changed internal data storage mechanism to an
    **                           AST. External interface is mostly unchanged,
    **                           except that a different constructor must now
    **                           be used, and there are new methods to get/set
    **                           the backing AST. Added a convenience
    **                           constructor to create a NameNode based only
    **                           on a name (package private for use by the
    **                           Namespace class).
    ** 007 GES 20070402   @32702 Provided access to a new flag to identify
    **                           name nodes that should be preferred in
    **                           ambiguous name conflicts.
    ** 008 ECF 20070628   @35341 Minor optimization. Replaced StringBuffer
    **                           with StringBuilder.
    ** 009 GES 20090518   @42396 Import change.
    ** 010 ECF 20111217          Specified generics for raw types.
    ** 011 CA  20130524          Added getOrder(), used by temp-table field ordering fix.
    ** 012 ECF 20160328          Minor code/doc cleanup.
    ** 013 OM  20211122          Added hints-based support for 'unloading' schema.
    **     GES 20220404          Handle the case where there is no ORDER specified in the schema for a field.
    **     CA  20221006          Keep a lowercase version of the AST text.  Refs #6820
    ** 014 OM  20230115          Replaced absolutePath(), relativePath(), upPath() and downPath() with faster
    **                           versions, based on node types rather on string paths.
    */

    /*
    ** 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.schema;

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

    /**
     * A single node in a Progress database entity's name, which represents a
     * database, a table, or a field entity. Each node holds a {@link Namespace}
     * object, which may contain children nodes (only for nodes which represent
     * databases and tables; field nodes have no children).
     * <p>
     * Nodes representing fields have a parent table node; those representing
     * tables have a parent database node; database nodes represent the top of
     * the hierarchy and as such, have no parent node. Thus, any node will be
     * able to reconstruct its own fully qualified name by walking up the chain
     * of its ancestors ({@link #toQualifiedName} does this).
     * <p>
     * This class defines two comparators as inner classes ({@link
     * NameNode.ExactMatchComparator} and {@link NameNode.AbbreviationComparator})
     * which are used by {@link Namespace} for name lookups. The former is also
     * used to define the natural order of <code>NameNode</code> instances for
     * sorting purposes.
     * <p>
     * NameNode is backed by an AST which is stored internally, and which is
     * accessible via accessor methods. The token type of the backing AST defines
     * the data type of the name node. The text of the AST defines the name of
     * the name node.
     * <p>
     * <strong>Note:</strong> instances of this object are used as keys in a
     * <code>WeakHashMap</code> {@link SchemaDictionary#scopeMap mapping} in the
     * <code>SchemaDictionary</code>. That mapping relies on this object using
     * <code>==</code> as its implementation of the <code>equals</code> method
     * (inherited from <code>java.lang.Object</code>). Any other implementation
     * of <code>equals</code> will break that mapping!
     */
    public class NameNode
    implements Comparable<NameNode>
    {
       /** Comparator which requires exact lexical match of node names */
       public static final ExactMatchComparator EXACT_COMPARATOR = new ExactMatchComparator();

       /** Comparator which allows abbreviations of node names */
       public static final AbbreviationComparator ABBREVIATION_COMPARATOR = new AbbreviationComparator();

       /** Optional parent associated with this name node */
       private NameNode parent = null;

       /** Namespace containing child nodes, if any */
       private final Namespace children = new Namespace(true);

       /** AST node associated with this name node */
       private Aast ast = null;

       /** Cache the lower version of the AST's text, to avoid lowercasing in {@link #getName()}. */
       private String astLowerText = null;

       /**
        * Constructor which specifies node's parent, AST, and name.
        *
        * @param   parent
        *          Optional parent name node. If not <code>null</code> this node
        *          will be added to <code>parent</code>'s namespace.
        * @param   ast
        *          AST node to be associated with this name node.
        *
        * @throws  NullPointerException
        *          if <code>name</code> is <code>null</code>.
        */
       public NameNode(NameNode parent, Aast ast)
       {
          this.parent = parent;

          setAst(ast);
          setParent(parent);
       }

       /**
        * Convenience constructor to create a temporary name node.
        *
        * @param   name
        *          Name assigned to the name node. May not be <code>null</code>.
        */
       NameNode(String name)
       {
          ast = new ProgressAst();
          ast.setText(name.toLowerCase());
          astLowerText = ast.getText();
       }

       /**
        * Get the parent, if any, associated with this node.
        *
        * @return  Parent node, may be <code>null</code>.
        */
       public NameNode getParent()
       {
          return parent;
       }

       /**
        * Set the parent name node of this node. If we had an existing parent,
        * remove this node from its private namespace. If <code>parent</code> is
        * not <code>null</code>, add this node to its private namespace.
        *
        * @param   parent
        *          New parent name node.
        */
       public void setParent(NameNode parent)
       {
          // Remove ourselves from existing parent's private namespace.
          if (this.parent != null)
          {
             this.parent.getNamespace().remove(this);
          }

          // Add ourselves to new parent's private namespace.
          if (parent != null)
          {
             parent.getNamespace().add(this);
          }

          this.parent = parent;
       }

       /**
        * Get the AST which backs this name node.
        *
        * @return  Backing AST.
        */
       public Aast getAst()
       {
          return ast;
       }

       /**
        * Set the AST which backs this name node.
        *
        * @param   ast
        *          Backing AST. Should not be <code>null</code>.
        */
       public void setAst(Aast ast)
       {
          this.ast = ast;
          this.astLowerText = ast == null ? null : ast.getText().toLowerCase();
       }

       /**
        * Get the name associated with this node.
        *
        * @return  Name associated with this node; always lower case.
        */
       public String getName()
       {
          return astLowerText;
       }

       /**
        * Get the data type associated with this node.
        *
        * @return  A Progress token type representing the AST backing this node.
        */
       public int getType()
       {
          return ast.getType();
       }

       /**
        * Reports if this node is to be preferred in ambiguous name conflicts
        * over other name nodes that do not have this flag set.
        *
        * @return  The preferred flag.
        */
       public boolean isPreferred()
       {
          boolean prefer = false;

          if (ast.isAnnotation("preferred"))
          {
             prefer = ((Boolean) ast.getAnnotation("preferred")).booleanValue();
          }

          return prefer;
       }

       /**
        * Get the namespace which contains this node's children, if any.
        *
        * @return  Namespace for this node's children.
        */
       public Namespace getNamespace()
       {
          return children;
       }

       /**
        * Get the fully qualified, unabbreviated name which corresponds with this
        * node. For this purpose, ancestors are considered, but not descendants.
        * No alias substitutions are made.
        *
        * @return  Qualified name in the form {@code [[grandparent_name.]parent_name.]name}.
        *          Note that this node might not have a grandparent or even a parent, in
        *          which case the missing ancestor names are omitted.
        */
       public String toQualifiedName()
       {
          StringBuilder buf = new StringBuilder(getName());
          NameNode next = this;
          while ((next = next.getParent()) != null)
          {
             buf.insert(0, '.');
             buf.insert(0, next.getName());
          }

          return buf.toString();
       }

       /**
        * Compares this name node with the specified name node to determine the
        * natural order of name nodes. Delegates this comparison to {@link
        * NameNode.ExactMatchComparator}.
        *
        * @param   o
        *          <code>NameNode</code> for comparison. If <code>null</code> or
        *          not of type <code>NameNode</code>, <code>1</code> is returned.
        *
        * @return  <code>0</code> if objects are considered equal by the rules
        *          of the {@link NameNode.ExactMatchComparator}; <code>-1</code>
        *          if <code>this</code> is considered less than <code>o</code>;
        *          <code>1</code> if <code>this</code> is considered greater than
        *          <code>o</code>.
        */
       @Override
       public int compareTo(NameNode o)
       {
          return EXACT_COMPARATOR.compare(this, o);
       }

       /**
        * Returns a string representation of this name node, primarily for debug
        * purposes.
        *
        * @return  String representation of the internal state of this object.
        */
       @Override
       public String toString()
       {
          return toQualifiedName();
       }

       /**
        * Obtain the schema of this {@code NameNode}.
        *
        * @return  the schema component of this {@code NameNode}. Always in lowercase.
        */
       public String getSchema()
       {
          NameNode nameNode = this;
          while (nameNode.parent != null)
          {
             nameNode = nameNode.parent;
          }
          return nameNode.getName();
       }

       /**
        * Get the fully qualified, unabbreviated name which corresponds with this
        * node. For this purpose, ancestors are considered, but not descendants.
        * This implementation is aware of database aliases. It will substitute the
        * database name portion of <code>entityName</code>, if any, for the
        * database name portion of the qualified name
        *
        * @param   type
        *          Entity type, using {@link EntityName} constants.
        * @param   entityName
        *          If this object specifies a database qualifier, it is used in
        *          preference to the qualifier discovered by inspection of this
        *          node's internal state or lineage. May be <code>null</code>; if
        *          so, no database alias substitution is performed.
        *
        * @return  Qualified name in the form {@code database[.table[.field]]}
        *          for regular schema entities, or <code>table[.field]</code> for temp
        *          or work tables (or their fields).
        */
       String toAliasedName(int type, EntityName entityName)
       {
          List<String> parts = new LinkedList<>();
          NameNode next = this;

          // Assemble a list of name parts from this node's name and those of its
          // ancestors. The list is ordered from least to most specific name part.
          do
          {
             parts.add(0, next.getName());
             next = next.getParent();
          } while (next != null);

          // Normalize the list: make sure we have 3 name parts, even if the
          // first or last is null.

          // A database, temp-table or work-table will only have one part at
          // this point.
          if (parts.size() == 1)
          {
             switch (type)
             {
                case EntityName.DATABASE:
                   parts.add(null);     // add null table reference
                   break;
                case EntityName.TABLE:
                   parts.add(0, null);  // add null database qualifier
                   break;
             }
          }

          // A database, temp-table or work-table, or a field of a temp-table or
          // of a work-table will only have two parts at this point.
          if (parts.size() == 2)
          {
             switch (type)
             {
                case EntityName.DATABASE:
                case EntityName.TABLE:
                   parts.add(null);     // add null field reference
                   break;
                case EntityName.FIELD:
                   parts.add(0, null);  // add null database qualifier
                   break;
             }
          }

          // If an entity name was provided, and that name included a database
          // or table part, prefer those qualifier names to the ones determined
          // by this node's lineage. This will replace the real names with the
          // appropriate aliases.
          if (entityName != null)
          {
             // Replace database qualifier with alias, if any.
             String alias = entityName.getPart(EntityName.DATABASE);
             if (alias != null)
             {
                parts.set(0, alias);
             }

             // Replace table qualifier with alias, if any.
             alias = entityName.getPart(EntityName.TABLE);
             if (alias != null)
             {
                parts.set(1, alias);
             }
          }

          // Compose a dot-delimited, fully qualified name from the part names in
          // the list. If a part is still null at this point, omit it.
          StringBuilder buf = new StringBuilder();
          boolean first = true;
          Iterator<String> iter = parts.iterator();
          while (iter.hasNext())
          {
             String part = iter.next();
             if (part == null)
             {
                continue;
             }

             if (!first)
             {
                buf.append('.');
             }
             first = false;

             buf.append(part);
          }

          return buf.toString();
       }

       /**
        * Must be called only for fields. Gets the field's ORDER setting.
        *
        * @return   See above.
        */
       long getOrder()
       {
          // TODO: can this even be used? the annotation is not available until fixups
          if (ast.isAnnotation("order"))
          {
             return (Long) ast.getAnnotation("order");
          }

          // this path is used during parsing before fixups have converted child nodes into annotations
          if (ast.downPath(ProgressParserTokenTypes.ORDER))
          {
             Aast oAst = ast.getImmediateChild(ProgressParserTokenTypes.ORDER, null);
             return Long.parseLong(oAst.getFirstChild().getText());
          }

          // some schemata, when manually edited, can be missing the ORDER property; calculate an artificial
          // ORDER based on index position
          return (long) (ast.getIndexPos() + 1) * 10;
       }

       /**
        * Comparator which requires the name portion of the {@code NameNode}s being compared lexically to match
        * exactly for the nodes to be reported as equivalent (return value of {@code 0} from {@link
        * Comparable#compareTo(java.lang.Object) compare} method). This is used when looking up an unabbreviated
        * name, or when determining the natural order of name nodes within a {@link Namespace}.
        */
       private static class ExactMatchComparator
       implements Comparator<NameNode>,
                  Serializable
       {
          /**
           * Resolves a comparison of two <code>NameNode</code> objects. After
           * performing object reference identity check, and null checks, uses an
           * exact lexical match of the nodes' names to determin equivalence.
           *
           * @param   n1
           *          First <code>NameNode</code> for comparison. If
           *          <code>null</code>, <code>-1</code> is returned.
           * @param   n2
           *          Second <code>NameNode</code> for comparison. If
           *          <code>null</code>, <code>1</code> is returned.
           *
           * @return  <code>0</code> if objects are considered equal by the rules
           *          of this comparator; <code>-1</code> if <code>n1</code> is
           *          considered less than <code>n2</code>; <code>1</code> if
           *          <code>n1</code> is considered greater than <code>n2</code>.
           */
          public int compare(NameNode n1, NameNode n2)
          {
             // Both references refer to same object in memory.
             if (n1 == n2)
             {
                return 0;
             }

             // Handle case where n1 is null.
             if (n1 == null)
             {
                return -1;
             }

             // Handle case where n2 is null.
             if (n2 == null)
             {
                return 1;
             }

             return n1.getName().compareTo(n2.getName());
          }
       }

       /**
        * Comparator which will report two <code>NameNode</code> objects as
        * equivalent if the second node's name represents an abbreviation for the
        * first node's name. This is used when looking up a name which may be
        * abbreviated, when an exact match lookup has already failed.
        * <p>
        * <strong>Note:</strong> this comparator imposes orderings that are
        * inconsistent with equals.
        */
       private static class AbbreviationComparator
       extends ExactMatchComparator
       {
          /**
           * Resolves a comparison of two <code>NameNode</code> objects. The
           * criteria for this comparison to determine equivalence is either an
           * exact lexical match of the nodes' names, or that the name of node
           * <code>n1</code> lexically begins with the name of node
           * <code>n2</code>.
           * <p>
           * <strong>Important Node:</strong> this implementation supports the
           * lookup of an abbreviated name within a sorted list of unabbreviated
           * names. It relies upon the binary search implementation provided by
           * <code>java.util.Collections</code>, in that it assumes the search key
           * will always be <code>n2</code>, and <code>n1</code> always represents
           * a node from the list being searched. <strong>This assumption is
           * fundamental to the correct behavior of the {@link Namespace#find}
           * method.</strong>
           *
           * @param   n1
           *          First <code>NameNode</code> for comparison.
           * @param   n2
           *          Second <code>NameNode</code> for comparison.
           *
           * @return  <code>0</code> if nodes' names are considered equal
           *          according to the rules described above; <code>-1</code> if
           *          <code>o1</code> is considered lexically less than
           *          <code>o2</code>; <code>1</code> if <code>o1</code> is
           *          considered lexically greater than <code>o2</code>.
           */
          public int compare(NameNode n1, NameNode n2)
          {
             // Let superclass implementation try to detect an exact match first.
             int result = super.compare(n1, n2);

             // If superclass would sort n2 ahead of n1, check whether n1's name
             // begins with n2's name (i.e., n2 is an abbreviation for n1).
             if (result > 0 && n1.getName().startsWith(n2.getName()))
             {
                result = 0;
             }

             return result;
          }
       }
    }