ScopedSymbolDictionary.java

/*
** Module   : ScopedSymbolDictionary.java
** Abstract : Provides multiscoped symbol dictionary functionality.
**
** Copyright (c) 2004-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------Description----------------------------
** 001 NVS 20041104   @18750 Created an initial implementation of a mul-
**                           tiscoped symbol dictionary class.
** 002 NVS 20041110   @18751 Implemented configurable case sensitivity.
**                           Case sensitivity is a parameter to the object
**                           constructors. Once instantiated, dictionary's
**                           case sensitivity cannot be changed.
** 003 NVS 20050209   @19729 Implemented new method locateSymbol which
**                           returns the nesting level of the first
**                           appearance of a symbol. This method is needed
**                           to fix a Preprocessor bug related to visibi-
**                           lity of arguments beyond the scope where they
**                           are defined.
** 004 NVS 20050210   @19757 Enhanced debugging features by adding dump-
**                           Scope private method, recoding dump() and 
**                           adding dumpCurrentScope public method. Now
**                           the output is indented according to the depth
**                           of the scope dump for easier interpretation.
** 005 SIY 20050607   @21711 Added value-based scope lookup and scope-
**                           based value setting. Minor cleanups.
** 006 GES 20050801   @21943 Added getSymbolAtScope() to allow arbitrary
**                           symbol lookups at any specific scope
**                           including the global scope.
** 007 ECF 20051028   @23221 Abstracted out majority of function to a new
**                           superclass, ScopedDictionary. This class now
**                           delegates its methods to the superclass. The
**                           superclass uses Object for its keys, while
**                           this class continues to use String. The case
**                           sensitivity feature is thus still implemented
**                           within this class.
** 008 SIY 20080904   @39717 Inferred generics.
** 009 GES 20080917   @39855 Added isCaseSensitive().
** 010 GES 20111205          Exposed processKey() as public.
** 011 ECF 20140830          Added symbolSet(int).
** 012 CA  20160728          Added copyAll API, to allow a dictionary duplication; if the 
**                           keepGlobal flag is true, then the global scope is shared between the
**                           source and the copy; otherwise, it's duplicated.
** 013 CA  20201003          Disallow IdentityHashMap dictionaries (as the key is a String).
** 014 CA  20230727          ScopedSymbolDictionary now uses a CaseInsensitiveHashMap for case-insensitive
**                           dictionaries, so 'processKey' is no longer needed.
*/
/*
** 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.util;

import java.util.*;

import com.goldencode.util.*;

/**
 * Implements a symbol dictionary with multiple levels of scope.  Symbols can
 * be added to, removed from and queried in the dictionary.  Each symbol
 * consists of the symbol's <code>String</code> representation and a user-
 * defined object that can store arbitrary user data.  As a result of the
 * lookup process, if a symbol is found, the user-defined object is returned.
 * <p>
 * When symbols are added, they are stored in a flat list called a scope. Each
 * scope can store a set of symbols where each symbol name must be unique.
 * The user has control over how many scopes exist.  By default, no scopes
 * are available.  The user of this class is responsible for adding scopes
 * using the <code>addScope</code> method.  When the symbols stored in the
 * current scope should no longer be available, the scope can be removed
 * using the <code>deleteScope</code> method.  This multilevel scope
 * approach can be thought of as a stack of scopes where the current scope
 * is the top of the stack and the bottom of the stack has a special scope
 * that is considered "global".  Symbols can be added to the current scope
 * or to the global scope using the <code>addSymbol</code> method.
 * <p>
 * Since the global scope has a special meaning, a convenience constructor
 * is provided that automatically creates the global scope.
 * <p>
 * Symbols must only be unique within a single scope.  If the same symbol
 * exists in multiple scopes, the instance found closest to the current
 * scope (top of stack) is the one that is found on lookup.  Likewise, when
 * a symbol is deleted using <code>deleteSymbol</code>, the topmost symbol
 * is the one removed.
 * <p>
 * When instantiating objects of this class, one can chose between case-
 * sensitive and case-insensitive operations. Only the symbol names can be
 * case-(in)sensitive. Once instantiated, the mode of operation cannot be
 * changed. Case sensitivity affects <code>addSymbol</code>,
 * <code>deleteSymbol</code> and <code>lookupSymbol</code> methods.
 * <p>
 * This class is designed to provide a symbol lookup facility such as a
 * compiler would need to properly reference scoped variables or functions.
 * Most functionality is delegated to the superclass.
 * <p>
 * @author  NVS
 * @version 1.0.6
 * @param   <V>
 *          Type of value stored in dictionary. 
 */
public class ScopedSymbolDictionary<V>
extends ScopedDictionary<String, V>
{
   /** Defines case sensitivity mode for lookups */
   private boolean caseSensitive = false;

   /**
    * Default constructor.
    */
   public ScopedSymbolDictionary()
   {
      super();
   }
   
   /**
    * Factory method for {@link HashMap} when {@link #caseSensitive} is <code>true</code> and 
    * {@link CaseInsensitiveHashMap} when {@link #caseSensitive} is <code>false</code>. 
    * 
    * @param   size 
    *          The maximum expected size of the Map 
    *
    * @return  a new map depending on the {@link #caseSensitive} flag.
    */
   @Override
   protected Map<String, V> factoryNodeHashMap(int size)
   {
      return caseSensitive ? super.factoryNodeHashMap(size) : new CaseInsensitiveHashMap<>(size);
   }
   
   /**
    * Factory method for a {@link Set} as returned by {@link #keySet}.  This will be a set from a 
    * {@link HashMap} when {@link #caseSensitive} is <code>true</code> and from {@link CaseInsensitiveHashMap}
    * when {@link #caseSensitive} is <code>false</code>.
    * 
    *
    * @return  a new set depending on the {@link #caseSensitive} flag.
    */
   @Override
   protected Set<String> factoryHashSetFromMap()
   {
      return caseSensitive ? super.factoryHashSetFromMap() 
                           : Collections.newSetFromMap(new CaseInsensitiveHashMap<>());
   }
   
   /**
    * Set the {@link #identityKeys} flag.
    * <p>
    * This is a no-op, as the key is a {@link String}.
    * 
    * @param    identityKeys
    *           Not used.
    */
   @Override
   public final void setIdentityKeys(boolean identityKeys)
   {
      // no-op
   }

   /**
    * Constructor with explicit case-sensitivity requested.
    *
    * @param  caseSensitive
    *         <code>true</code> makes all symbol lookups case-sensitive.
    */
   public ScopedSymbolDictionary(boolean caseSensitive)
   {
      super();
      this.caseSensitive = caseSensitive;
   }

   /**
    * Convenience constructor that automatically creates the global scope.
    *
    * @param  extra
    *         Object to be associated with the global scope.
    */
   public ScopedSymbolDictionary(Object extra)
   {
      super(extra);
   }

   /**
    * Convenience constructor that automatically creates the global scope
    * with explicit case-sensitivity requested.
    *
    * @param  extra
    *         Object to be associated with the global scope.
    * @param  caseSensitive
    *         <code>true</code> makes all symbol lookups case-sensitive.
    */
   public ScopedSymbolDictionary(Object extra, boolean caseSensitive)
   {
      this(extra);
      this.caseSensitive = caseSensitive;
   }
   
   /**
    * Reports if this dictionary is case-sensitive.
    *
    * @return   <code>true</code> if this dictionary is case-sensitive.
    */
   public boolean isCaseSensitive()
   {
      return caseSensitive;
   }

   /**
    * Adds a symbol to the current or global scope or replaces
    * its user-defined object. The current scope is the one on the top of the
    * stack. The global scope is always the first scope created on the stack.
    * If only one scope is on the stack, both refer to the same scope.
    *
    * @param   global 
    *          Specifies the target scope. <code>false</code> specifies the
    *          current scope, and <code>true</code> specifies the global
    *          scope.
    * @param   name
    *          The symbol name.
    * @param   value
    *          The symbol value which may be any object.
    *
    * @return  <code>true</code> if the symbol has been added, 
    *          <code>false</code> if its value has been replaced.
    */
   public boolean addSymbol(boolean global, String name, V value)
   {
      return addEntry(global, name, value);
   }

   /**
    * Searches all scopes from the top of the stack down and deletes
    * the first found occurrence of the symbol.
    *
    * @param   name
    *          The symbol name.
    *
    * @return  <code>true</code> if the symbol has been deleted.
    */
   public boolean deleteSymbol(String name)
   {
      return deleteEntry(name);
   }

   /**
    * Searches all scopes from the top of the stack down for the specified
    * key and value pair and returns 0-based index of the depth where
    * that pair is found.  Both the key and the value must be matched to
    * the given parameters for this to succeed.
    *
    * @param   key
    *          The symbol name.
    * @param   value
    *          The value to search.
    * 
    * @return  0-based index of the scope depth at which value is found.
    */
   public int locateSymbolValue(String key, V value)
   {
      return locate(key, value);
   }

   /**
    * Sets the given named symbol in the scope specified, to the given
    * value.
    *
    * @param   name
    *          The symbol name.
    * @param   scope
    *          The depth (in number of scopes from the top of the stack) at
    *          which the value must be set. 
    * @param   value
    *          The replacement value.
    * 
    * @return  <code>true</code> if the value was successfully set, or
    *          <code>false</code> if any error occurred.
    */
   public boolean setSymbolAtScope(String name, int scope, V value)
   {
      return setValueAtScope(name, scope, value);
   }
   
   /**
    * Gets the value of the given named symbol in the scope specified.
    *
    * @param   name
    *          The symbol name.
    * @param   scope
    *          The depth (in number of scopes from the top of the stack) at
    *          which the value must be set. Use -1 to reference the global
    *          scope.
    * 
    * @return  The object found or <code>null</code> if no such name exists.
    */
   public Object getSymbolAtScope(String name, int scope)
   {
      return getValueAtScope(name, scope);
   }
   
   /**
    * Searches all scopes from the top of the stack down for a given symbol.
    *
    * @param   name
    *          The symbol name.
    *
    * @return  The user-defined object associated with symbol or
    *          <code>null</code> if not found.
    */
   public Object lookupSymbol(String name)
   {
      return lookup(name);
   }

   /**
    * Searches all scopes from the top of the stack down for a given symbol.
    *
    * @param   name
    *          The symbol name.
    * @return  The nesting level (depth) of the scope where the symbol is
    *          defined first or -1 if not found.
    */
   public int locateSymbol(String name)
   {
      return locate(name);
   }

   /**
    * Returns a set containing all defined symbol names as strings.
    *
    * @return   The set view of all symbol names defined in all scopes,
    */
   public Set<String> symbolSet()
   {
      return keySet();
   }

   /**
    * Returns a set containing all defined symbol names as strings, in the scope specified.
    *
    * @param    scope
    *           The depth (in number of scopes from the top of the stack) from which the symbols
    *           are collected. Use -1 to reference the global scope.
    * 
    * @return   The set view of all symbols from the specified scope (and only that scope).
    */
   public Set<String> symbolSet(int scope)
   {
      return keySet(scope);
   }
   
   /**
    * Clear this dictionary and copy all content from the source.  The global scope will be either
    * duplicated or kept as a standalone reference, to be shared among copies.
    * 
    * @param    source
    *           The source dictionary.
    * @param    keepGlobal
    *           When <code>true</code>, the global dictionary is exposed directly to this copy;
    *           otherwise is duplicated.
    */
   public void copyAll(ScopedSymbolDictionary<V> source, boolean keepGlobal)
   {
      this.caseSensitive = source.caseSensitive;
      
      super.copyAll(source, keepGlobal);
   }
}