/*
** Module   : ScopedSymbolDictionary.java
** Abstract : Provides multiscoped symbol dictionary functionality.
**
** Copyright (c) 2004-2009, Golden Code Development Corporation.
**
** -#- -I- --Date-- -----------------------Description------------------------
** 001 NVS 20041104 Created an initial implementation of a multiscoped symbol
**                  dictionary class.
** 002 NVS 20041110 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 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
**                  visibility of arguments beyond the scope where they are
**                  defined.
** 004 NVS 20050210 Enhanced debugging features by adding dumpScope 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 Added value-based scope lookup and scope-based value
**                  setting. Minor cleanups.
** 006 GES 20050801 Added getSymbolAtScope() to allow arbitrary symbol lookups
**                  at any specific scope including the global scope.
** 007 ECF 20051028 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 Inferred generics.
** 009 GES 20080917 Added isCaseSensitive().
*/

/*
** 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 should have received a copy of the GNU Affero General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.goldencode.lang;

import java.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();
   }

   /**
    * Copy constructor. This will create an instance with a new list of scopes
    * that has as its contents the same exact scopes as the original (the
    * scope instances are the same, but exist in two separate lists that have
    * indentical contents).
    * <p>
    * This means that the maintenance of the list of scopes can safely be done
    * independently between the two copies. Scopes can be deleted (even if they
    * are common scopes) and scopes can be added. The scope list changes will
    * only be visible in the instance on which they are made.
    * <p>
    * This must be differentiated from how the contents of a scope are managed.
    * Since the current scope instances themselves are shared, any modifications
    * to the contents of the existing scopes must be properly synchronized
    * because the changes will be shared between all dictionaries that have
    * been duplicated in this manner. This means that any entries added, changed
    * or removed in common scopes will be visible to all duplicated instances.
    *
    * @param    original
    *           The instance to copy from.
    */
   public ScopedSymbolDictionary(ScopedSymbolDictionary<V> original)
   {
      super(original);
      this.caseSensitive = original.caseSensitive;
   }

   /**
    * 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 V 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 V 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();
   }

   /**
    * Process the key, which is expected to be a string, to take the case
    * sensitivity setting into account.  If case sensitivity is disabled,
    * the string key is uppercased before it is returned, to ensure all uses
    * of the key are case insensitive.  Otherwise, the key is returned
    * unchanged.
    *
    * @param   key
    *          The lookup key, assumed to be a string.
    *
    * @return  The key, possibly modified as described above.
    */
   protected String processKey(String key)
   {
      if (!caseSensitive)
      {
         return key.toUpperCase();
      }
      
      return key;
   }
}
