DictionaryWorker.java

/*
** Module   : DictionaryWorker.java
** Abstract : exports named and scoped dictionaries to pattern engine rules
**
** Copyright (c) 2005-2021, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
** 001 GES 20050310   @20258 Created initial version supporting any
**                           number of user controlled scoped dictionaries
**                           that map names to any of the 4 supported
**                           pattern engine types (string, long, double
**                           and boolean).
** 002 GES 20050406   @20645 JavaDoc and coding standards improvements.
** 003 ECF 20050415   @20813 Changed addGeneric method's return value.
**                           Previously this method always returned true.
**                           It now returns the result of its internal
**                           call to ScopedSymbolDictionary's addSymbol
**                           method. This lets the caller know whether the
**                           entry was new or a replacement of an existing
**                           entry.
** 004 GES 20050428   @20923 Modified addScope() to create the dictionary
**                           if it doesn't already exist.
** 005 ECF 20050526   @21290 Changes to support new expression engine
**                           implementation. Changed library registration
**                           mechanism based on superclass' modifications
**                           to support single library per pattern worker
**                           limit.
** 006 GES 20050614   @21492 Added Object versions of add/lookup methods
**                           and added a dictionaryKeyDepth() method to
**                           report on the depth at which a key exists.
** 007 SIY 20050607   @21710 Added scope-based setting of the values.
** 008 SIY 20050919   @23091 Added scope-based lookup of the values.
** 009 GES 20051115   @23363 Added simple level oriented helpers and a
**                           new List helper.
** 010 GES 20061023   @30617 Made dictionaryValueDepth() more generic.
** 011 ECF 20070816   @34870 Added dumpDictionary() debug user function.
** 012 GES 20080621   @38911 Added integer variants of get/set methods.
** 013 ECF 20140830          Added methods to retrieve symbol sets for the entire dictionary, or
**                           for a single scope.
** 014 EVL 20160223          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 015 CA  20211214          The AST manager plugin must be context-local, for runtime conversion, as the 
**                           InMemoryRegistryPlugin is not thread-safe.
**                           All worker's state must be context-local, for runtime conversion to work in 
**                           multi-context mode, as the pattern workers are singletons.
*/
/*
** 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.pattern;

import java.util.*;

import com.goldencode.p2j.security.*;
import com.goldencode.p2j.util.*;

/**
 * Exports named and scoped dictionaries to pattern engine rules supporting
 * any number of user controlled scoped dictionaries that map names to any of
 * the 4 supported pattern engine types (string, long, double and boolean).
 */
public class DictionaryWorker
extends AbstractPatternWorker
{
   /** Context local work area. */
   private static final ContextLocal<WorkArea> context = new ContextLocal<WorkArea>()
   {
      protected WorkArea initialValue()
      {
         return new WorkArea();
      }
   };

   /**
    * Default constructor which defines the symbol libraries to be registered.
    */
   DictionaryWorker()
   {
      super();
      
      setLibrary(new Dictionary());
   }
   
   /**
    * Provides generic scoped symbol dictionary services to pattern engine
    * users.
    */
   public class Dictionary
   {
      /** Default constructor which creates an instance and initializes. */
      public Dictionary()
      {
      }
      
      /**
       * Adds a new named dictionary to the list of those available.
       *
       * @param    dict
       *           The name of the new dictionary.
       * @param    sensitive
       *           If <code>true</code> the dictionary will match on a case-
       *           sensitive basis, <code>false</code> specifies 
       *           case-insensitive matching.
       *
       * @return   <code>true</code> if the dictionary was created or already
       *           exists, <code>false</code> on error.
       */
      public boolean addDictionary(String dict, boolean sensitive)
      {
         if (dict == null)
         {
            return false;
         }
         
         WorkArea wa = context.get();
         
         if (wa.dictList.get(dict) == null)
         {
            wa.dictList.put(dict, new ScopedSymbolDictionary<>(null, sensitive));
         }
         
         return true;
      }
      
      /**
       * Deletes a named dictionary from the list of those available.  All
       * mapped data in that dictionary will be removed.
       *
       * @param    dict
       *           The name of the new dictionary.
       *
       * @return   Always returns <code>true</code>.
       */
      public boolean deleteDictionary(String dict)
      {
         WorkArea wa = context.get();
         wa.dictList.remove(dict);
         
         return true;
      }
      
      /**
       * Creates a new scope in the named dictionary. The new scope appears on
       * top of the stack of scopes.  All subsequent additions of new entries
       * will be added to this scope, as long as it is at the top of the 
       * stack.  Creates the dictionary if it doesn't exist yet.
       *
       * @param    dict
       *           The name of the dictionary.
       *
       * @return   Always returns <code>true</code>.
       */
      public boolean addScope(String dict)
      {  
         ScopedSymbolDictionary ssd = obtainDict(dict);
         
         ssd.addScope(null);
         
         return true;
      }
      
      /**
       * Deletes the current top of stack scope in the named dictionary. All
       * contained entries will be deleted.
       *
       * @param    dict
       *           The name of the dictionary.
       *
       * @return   Always returns <code>true</code>.
       */
      public boolean deleteScope(String dict)
      {
         WorkArea wa = context.get();
         ScopedSymbolDictionary ssd = wa.dictList.get(dict);
         ssd.deleteScope();
         
         return true;
      }
      
      /**
       * Adds/replaces a key/value pair to a named dictionary, in the top
       * scope.  The value must be a string.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be added.
       *
       * @return   <code>true</code> if the entry has been added,
       *           <code>false</code> if its value has been replaced.
       */
      public boolean addDictionaryString(String dict, String key, String value)
      {
         return addGeneric(dict, key, value);
      }
      
      /**
       * Adds/replaces a key/value pair to a named dictionary, in the top
       * scope.  The value must a long.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be added.
       *
       * @return   <code>true</code> if the entry has been added,
       *           <code>false</code> if its value has been replaced.
       */
      public boolean addDictionaryLong(String dict, String key, Long value)
      {
         return addGeneric(dict, key, value);
      }
      
      /**
       * Adds/replaces a key/value pair to a named dictionary, in the top
       * scope.  The value must an integer.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be added.
       *
       * @return   <code>true</code> if the entry has been added,
       *           <code>false</code> if its value has been replaced.
       */
      public boolean addDictionaryInteger(String dict, String key, Integer value)
      {
         return addGeneric(dict, key, value);
      }
      
      /**
       * Adds/replaces a key/value pair to a named dictionary, in the top
       * scope.  The value must be a double.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be added.
       *
       * @return   <code>true</code> if the entry has been added,
       *           <code>false</code> if its value has been replaced.
       */
      public boolean addDictionaryDouble(String dict, String key, Double value)
      {
         return addGeneric(dict, key, value);
      }
      
      /**
       * Adds/replaces a key/value pair to a named dictionary, in the top
       * scope.  The value must be a boolean.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be added.
       *
       * @return   <code>true</code> if the entry has been added,
       *           <code>false</code> if its value has been replaced.
       */
      public boolean addDictionaryBoolean(String dict, String key, Boolean value)
      {
         return addGeneric(dict, key, value);
      }
      
      /**
       * Adds/replaces a key/value pair to a named dictionary, in the top
       * scope.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be added.
       *
       * @return   <code>true</code> if the entry has been added,
       *           <code>false</code> if its value has been replaced.
       */
      public boolean addDictionaryObject(String dict, String key, Object value)
      {
         return addGeneric(dict, key, value);
      }
      
      /**
       * Replace value of the key/value pair in the specified scope depth. 
       * The scope depth is the 0-based index where 0 means current scope.
       * The value must be a string. 
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope depth at which value must be set.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be returned from a lookup.
       *
       * @return   <code>true</code> if the symbol has been replaced,
       *           <code>false</code> if no such dictionary/key or no such
       *           scope depth.
       */
      public boolean setDictionaryStringAtScope(String dict, int scope, String key, String value)
      {
         return setGenericAtScope(dict, scope, key, value);
      }
      
      /**
       * Replace value of the key/value pair in the specified scope depth. 
       * The scope depth is the 0-based index where 0 means current scope.
       * The value must be a long. 
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope depth at which value must be set.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be returned from a lookup.
       *
       * @return   <code>true</code> if the symbol has been replaced,
       *           <code>false</code> if no such dictionary/key or no such
       *           scope depth.
       */
      public boolean setDictionaryLongAtScope(String dict, int scope, String key, Long value)
      {
         return setGenericAtScope(dict, scope, key, value);
      }
      
      /**
       * Replace value of the key/value pair in the specified scope depth. 
       * The scope depth is the 0-based index where 0 means current scope.
       * The value must be an integer. 
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope depth at which value must be set.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be returned from a lookup.
       *
       * @return   <code>true</code> if the symbol has been replaced,
       *           <code>false</code> if no such dictionary/key or no such
       *           scope depth.
       */
      public boolean setDictionaryIntegerAtScope(String  dict, 
                                                 int     scope, 
                                                 String  key, 
                                                 Integer value)
      {
         return setGenericAtScope(dict, scope, key, value);
      }
      
      /**
       * Replace value of the key/value pair in the specified scope depth. 
       * The scope depth is the 0-based index where 0 means current scope.
       * The value must be a boolean. 
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope depth at which value must be set.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be returned from a lookup.
       *
       * @return   <code>true</code> if the symbol has been replaced,
       *           <code>false</code> if no such dictionary/key or no such
       *           scope depth.
       */
      public boolean setDictionaryBooleanAtScope(String  dict, 
                                                 int     scope, 
                                                 String  key, 
                                                 Boolean value)
      {
         return setGenericAtScope(dict, scope, key, value);
      }
      
      /**
       * Replace value of the key/value pair in the specified scope depth. 
       * The scope depth is the 0-based index where 0 means current scope.
       * The value must be a double. 
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope depth at which value must be set.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be returned from a lookup.
       *
       * @return   <code>true</code> if the symbol has been replaced,
       *           <code>false</code> if no such dictionary/key or no such
       *           scope depth.
       */
      public boolean setDictionaryDoubleAtScope(String dict, int scope, String key, Double value)
      {
         return setGenericAtScope(dict, scope, key, value);
      }
      
      /**
       * Replace value of the key/value pair in the specified scope depth. 
       * The scope depth is the 0-based index where 0 means current scope. 
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope depth at which value must be set.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be returned from a lookup.
       *
       * @return   <code>true</code> if the symbol has been replaced,
       *           <code>false</code> if no such dictionary/key or no such
       *           scope depth.
       */
      public boolean setDictionaryObjectAtScope(String dict, int scope, String key, Object value)
      {
         return setGenericAtScope(dict, scope, key, value);
      }
      
      /**
       * Queries the total number of scopes in the dictionary (including a
       * global scope if it exists).
       * 
       * @param    dict
       *           The name of the dictionary.
       *
       * @return   The number of scopes in the dictionary (returns -1 if the
       *           dictionary does not exist).
       */
      public int dictionaryMaxDepth(String dict)
      {
         WorkArea wa = context.get();
         ScopedSymbolDictionary<Object> ssd = wa.dictList.get(dict);
         
         if (ssd == null)
         {
            return -1;
         }
         
         return ssd.size();
      }
      
      /**
       * Tests for the existence of a specified key in a named dictionary.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   <code>true</code> if the key exists.
       */
      public boolean dictionaryContains(String dict, String key)
      {
         return lookupGeneric(dict, key) != null;
      }
      
      /**
       * Returns the list of all matches of a specified key in a named dictionary.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The list of all matches, may be an empty list.
       */
      public List<Object> dictionaryListMatches(String dict, String key)
      {
         WorkArea wa = context.get();
         ArrayList<Object> list = new ArrayList<>();
         ScopedSymbolDictionary<Object> ssd = wa.dictList.get(dict);
         
         if (ssd != null)
         {         
            int size = ssd.size();
            
            for (int i = 0; i < size; i++) 
            {
               Object current = ssd.getValueAtScope(key, i);
               
               if (current != null)
               {
                  list.add(current);
               }
            }
         }
         
         return list;
      }
      
      /**
       * Returns the number of matches of a specified key in a named
       * dictionary.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The number of levels in which a match was found.
       */
      public int dictionaryNumMatches(String dict, String key)
      {
         return dictionaryListMatches(dict, key).size();
      }
      
      /**
       * Tests for the existence of a specified key in a named dictionary
       * and returns the 0-based depth at which the key is found or -1 if
       * the key does not exist.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The depth of the scope in which the specified key exists
       *           (0 if the key is found in the current scope, a value &gt; 1
       *           if the key exists in a scope below the current scope) or
       *           -1 if the key is not found in any scope (or if the 
       *           dictionary does not exist).
       */
      public int dictionaryKeyDepth(String dict, String key)
      {
         WorkArea wa = context.get();
         ScopedSymbolDictionary<Object> ssd = wa.dictList.get(dict);
         
         if (ssd == null)
         {
            return -1;
         }
         
         return ssd.locateSymbol(key);
      }
      
      /**
       * Locate the 0-based scope depth of the specified value of specified
       * variable.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be found.
       * @param    value
       *           The value of the entry to compare with.
       * 
       * @return   The depth of the scope in which the specified value exists
       *           (0 if the value is found in the current scope, a value &gt; 1
       *           if the key exists in a scope below the current scope) or
       *           -1 if the key is not found in any scope (or if the 
       *           dictionary does not exist).
       */
      public int dictionaryValueDepth(String dict, String key, Object value)
      {
         WorkArea wa = context.get();
         ScopedSymbolDictionary<Object> ssd = wa.dictList.get(dict);
         
         if (ssd == null)
         {
            return -1;
         }
         
         return ssd.locateSymbolValue(key, value);
      }
      
      /**
       * Returns a set containing all defined symbol names as strings, for the given dictionary.
       * 
       * @param    dict
       *           The name of the dictionary.
       * 
       * @return   All symbols in the given dictionary (from all scopes).
       */
      public Set<String> dictionarySymbolSet(String dict)
      {
         WorkArea wa = context.get();
         ScopedSymbolDictionary<Object> ssd = wa.dictList.get(dict);
         
         if (ssd == null)
         {
            return Collections.emptySet();
         }
         
         return ssd.symbolSet();
      }
      
      /**
       * Returns a set containing all defined symbol names as strings, for the given scope in the
       * given dictionary.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @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   All symbols in the given dictionary (from all scopes).
       */
      public Set<String> dictionarySymbolSet(String dict, int scope)
      {
         WorkArea wa = context.get();
         ScopedSymbolDictionary<Object> ssd = wa.dictList.get(dict);
         
         if (ssd == null)
         {
            return Collections.emptySet();
         }
         
         return ssd.symbolSet(scope);
      }
      
      /**
       * Searches the named dictionary for the value associated with a 
       * specified key or <code>null</code> if the key does not exist.
       * The search is limited to specified scope.
       * The value must be a string.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope at which value should be located.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The value found for the specified key or <code>null</code>
       *           if the key does not exist.
       */
      public String lookupDictionaryStringAtScope(String dict, int scope, String key)
      {
         return (String) lookupGenericAtScope(dict, scope, key);
      }
      
      /**         
       * Searches the named dictionary for the value associated with a 
       * specified key or <code>null</code> if the key does not exist.
       * The search is limited to specified scope.
       * The value must be a long.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope at which value should be located.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The value found for the specified key or <code>null</code>
       *           if the key does not exist.
       */
      public Long lookupDictionaryLongAtScope(String dict, int scope, String key)
      {
         return (Long) lookupGenericAtScope(dict, scope, key);
      }
      
      /**         
       * Searches the named dictionary for the value associated with a 
       * specified key or <code>null</code> if the key does not exist.
       * The search is limited to specified scope.
       * The value must be an integer.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope at which value should be located.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The value found for the specified key or <code>null</code>
       *           if the key does not exist.
       */
      public Integer lookupDictionaryIntegerAtScope(String dict, int scope, String key)
      {
         return (Integer) lookupGenericAtScope(dict, scope, key);
      }
      
      /**
       * Searches the named dictionary for the value associated with a 
       * specified key or <code>null</code> if the key does not exist.
       * The search is limited to specified scope.
       * The value must be a double.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope at which value should be located.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The value found for the specified key or <code>null</code>
       *           if the key does not exist.
       */
      public Double lookupDictionaryDoubleAtScope(String dict, int scope, String key)
      {
         return (Double) lookupGenericAtScope(dict, scope, key);
      }
      
      /**
       * Searches the named dictionary for the value associated with a 
       * specified key or <code>null</code> if the key does not exist.
       * The search is limited to specified scope.
       * The value must be a boolean.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope at which value should be located.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The value found for the specified key or <code>null</code>
       *           if the key does not exist.
       */
      public Boolean lookupDictionaryBooleanAtScope(String dict, int scope, String key)
      {
         return (Boolean) lookupGenericAtScope(dict, scope, key);
      }
      
      /**
       * Searches the named dictionary for the value associated with a 
       * specified key or <code>null</code> if the key does not exist.
       * The search is limited to specified scope.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope at which value should be located.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The value found for the specified key or <code>null</code>
       *           if the key does not exist.
       */
      public Object lookupDictionaryObjectAtScope(String dict, int scope, String key)
      {
         return lookupGenericAtScope(dict, scope, key);
      }
      
      /**
       * Searches the named dictionary for the value associated with a 
       * specified key or <code>null</code> if the key does not exist.
       * The value must be a string.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The value found for the specified key or <code>null</code>
       *           if the key does not exist.
       */
      public String lookupDictionaryString(String dict, String key)
      {
         return (String) lookupGeneric(dict, key);
      }
      
      /**
       * Searches the named dictionary for the value associated with a 
       * specified key or <code>null</code> if the key does not exist.
       * The value must be a long.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The value found for the specified key or <code>null</code>
       *           if the key does not exist.
       */
      public Long lookupDictionaryLong(String dict, String key)
      {
         return (Long) lookupGeneric(dict, key);
      }
      
      /**
       * Searches the named dictionary for the value associated with a 
       * specified key or <code>null</code> if the key does not exist.
       * The value must be an integer.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The value found for the specified key or <code>null</code>
       *           if the key does not exist.
       */
      public Integer lookupDictionaryInteger(String dict, String key)
      {
         return (Integer) lookupGeneric(dict, key);
      }
      
      /**
       * Searches the named dictionary for the value associated with a 
       * specified key or <code>null</code> if the key does not exist.
       * The value must be a double.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The value found for the specified key or <code>null</code>
       *           if the key does not exist.
       */
      public Double lookupDictionaryDouble(String dict, String key)
      {
         return (Double) lookupGeneric(dict, key);
      }
      
      /**
       * Searches the named dictionary for the value associated with a 
       * specified key or <code>null</code> if the key does not exist.
       * The value must be a boolean.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The value found for the specified key or <code>null</code>
       *           if the key does not exist.
       */
      public Boolean lookupDictionaryBoolean(String dict, String key)
      {
         return (Boolean) lookupGeneric(dict, key);
      }
      
      /**
       * Searches the named dictionary for the value associated with a 
       * specified key or <code>null</code> if the key does not exist.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   The value found for the specified key or <code>null</code>
       *           if the key does not exist.
       */
      public Object lookupDictionaryObject(String dict, String key)
      {
         return lookupGeneric(dict, key);
      }
      
      /**
       * Dump the contents of the specifified dictionary to
       * <code>stdout</code>.  Prints an error message if the given dictionary
       * is not found.
       * 
       * @param   dict
       *          The name of the dictionary.
       */
      public void dumpDictionary(String dict)
      {
         WorkArea wa = context.get();
         ScopedSymbolDictionary<Object> ssd = wa.dictList.get(dict);;
         
         if (ssd == null)
         {
            System.out.println("Dictionary '" + dict + "' not found");
            
            return;
         }
         
         ssd.dump(System.out);
      }
      
      /**
       * Generic worker to add/replace a key/value pair in a named dictionary,
       * in the top scope.  Creates the dictionary if it doesn't exist yet.
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be returned from a lookup.
       *
       * @return   <code>true</code> if the symbol has been added,
       *           <code>false</code> if its value has been replaced.
       */
      private boolean addGeneric(String dict, String key, Object value)
      {
         ScopedSymbolDictionary<Object> ssd = obtainDict(dict);
         
         return ssd.addSymbol(false, key, value);
      }
      
      /**
       * Generic worker to replace a key/value pair in a named dictionary,
       * in the specified scope depth. The scope depth is the 0-based index
       * where 0 means current scope. 
       * 
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope depth at which value must be set.
       * @param    key
       *           The name of the entry to be made.
       * @param    value
       *           The value to be returned from a lookup.
       *
       * @return   <code>true</code> if the symbol has been replaced,
       *           <code>false</code> if no such dictionary/key or no such
       *           scope depth.
       */
      private boolean setGenericAtScope(String dict, int scope, String key, Object value)
      {
         WorkArea wa = context.get();
         ScopedSymbolDictionary<Object> ssd = wa.dictList.get(dict);
         
         if (ssd == null)
         {
            return false;
         }
         
         return ssd.setSymbolAtScope(key, scope, value);
      }
      
      /**
       * Generic worker to search the named dictionary in all scopes from the
       * top of the stack down for a given key.
       *
       * @param    dict
       *           The name of the dictionary.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   Value of the key or <code>null</code> if not found.
       */
      private Object lookupGeneric(String dict, String key)
      {         
         WorkArea wa = context.get();
         ScopedSymbolDictionary<Object> ssd = wa.dictList.get(dict);
         
         if (ssd == null)
         {
            return null;
         }
         
         return ssd.lookupSymbol(key);
      }
      
      /**
       * Generic worker to search the named dictionary in all scopes from the
       * top of the stack down for a given key.
       *
       * @param    dict
       *           The name of the dictionary.
       * @param    scope
       *           The scope depth at which value must be retrieved.
       * @param    key
       *           The name of the entry to be found.
       *
       * @return   Value of the key or <code>null</code> if not found.
       */
      private Object lookupGenericAtScope(String dict, int scope, String key)
      {
         WorkArea wa = context.get();
         ScopedSymbolDictionary<Object> ssd = wa.dictList.get(dict);
         
         if (ssd == null)
            return null;
         
         return ssd.getSymbolAtScope(key, scope);
      }
      
      /**
       * Returns the existing dictionary of the given name or if a dictionary
       * does not exist, a case-insensitive dictionary will be created and
       * then returned.
       *
       * @param    dict
       *           The name of the dictionary.
       *
       * @return   The dictionary or <code>null</code> if the input string
       *           is <code>null</code>.
       */
      private ScopedSymbolDictionary<Object> obtainDict(String dict)
      {         
         if (dict == null)
         {
            return null;
         }
         
         WorkArea wa = context.get();
         ScopedSymbolDictionary<Object> d = wa.dictList.get(dict);
         
         // add a new case-insensitive dictionary if it doesn't exist yet
         if (d == null)
         {
            addDictionary(dict, false);
            d = wa.dictList.get(dict);
         }
         
         return d;
      }
   }
   
   /**
    * Context local work area.
    */
   private static class WorkArea
   {
      /** Stores all named dictionaries that are active. */
      private Map<String, ScopedSymbolDictionary<Object>> dictList = new HashMap<>();
   }
}