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