ScopedSymbolDictionary.java
/*
** Module : ScopedSymbolDictionary.java
** Abstract : Provides multiscoped symbol dictionary functionality.
**
** Copyright (c) 2004-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------Description----------------------------
** 001 NVS 20041104 @18750 Created an initial implementation of a mul-
** tiscoped symbol dictionary class.
** 002 NVS 20041110 @18751 Implemented configurable case sensitivity.
** Case sensitivity is a parameter to the object
** constructors. Once instantiated, dictionary's
** case sensitivity cannot be changed.
** 003 NVS 20050209 @19729 Implemented new method locateSymbol which
** returns the nesting level of the first
** appearance of a symbol. This method is needed
** to fix a Preprocessor bug related to visibi-
** lity of arguments beyond the scope where they
** are defined.
** 004 NVS 20050210 @19757 Enhanced debugging features by adding dump-
** Scope private method, recoding dump() and
** adding dumpCurrentScope public method. Now
** the output is indented according to the depth
** of the scope dump for easier interpretation.
** 005 SIY 20050607 @21711 Added value-based scope lookup and scope-
** based value setting. Minor cleanups.
** 006 GES 20050801 @21943 Added getSymbolAtScope() to allow arbitrary
** symbol lookups at any specific scope
** including the global scope.
** 007 ECF 20051028 @23221 Abstracted out majority of function to a new
** superclass, ScopedDictionary. This class now
** delegates its methods to the superclass. The
** superclass uses Object for its keys, while
** this class continues to use String. The case
** sensitivity feature is thus still implemented
** within this class.
** 008 SIY 20080904 @39717 Inferred generics.
** 009 GES 20080917 @39855 Added isCaseSensitive().
** 010 GES 20111205 Exposed processKey() as public.
** 011 ECF 20140830 Added symbolSet(int).
** 012 CA 20160728 Added copyAll API, to allow a dictionary duplication; if the
** keepGlobal flag is true, then the global scope is shared between the
** source and the copy; otherwise, it's duplicated.
** 013 CA 20201003 Disallow IdentityHashMap dictionaries (as the key is a String).
** 014 CA 20230727 ScopedSymbolDictionary now uses a CaseInsensitiveHashMap for case-insensitive
** dictionaries, so 'processKey' is no longer needed.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import java.util.*;
import com.goldencode.util.*;
/**
* Implements a symbol dictionary with multiple levels of scope. Symbols can
* be added to, removed from and queried in the dictionary. Each symbol
* consists of the symbol's <code>String</code> representation and a user-
* defined object that can store arbitrary user data. As a result of the
* lookup process, if a symbol is found, the user-defined object is returned.
* <p>
* When symbols are added, they are stored in a flat list called a scope. Each
* scope can store a set of symbols where each symbol name must be unique.
* The user has control over how many scopes exist. By default, no scopes
* are available. The user of this class is responsible for adding scopes
* using the <code>addScope</code> method. When the symbols stored in the
* current scope should no longer be available, the scope can be removed
* using the <code>deleteScope</code> method. This multilevel scope
* approach can be thought of as a stack of scopes where the current scope
* is the top of the stack and the bottom of the stack has a special scope
* that is considered "global". Symbols can be added to the current scope
* or to the global scope using the <code>addSymbol</code> method.
* <p>
* Since the global scope has a special meaning, a convenience constructor
* is provided that automatically creates the global scope.
* <p>
* Symbols must only be unique within a single scope. If the same symbol
* exists in multiple scopes, the instance found closest to the current
* scope (top of stack) is the one that is found on lookup. Likewise, when
* a symbol is deleted using <code>deleteSymbol</code>, the topmost symbol
* is the one removed.
* <p>
* When instantiating objects of this class, one can chose between case-
* sensitive and case-insensitive operations. Only the symbol names can be
* case-(in)sensitive. Once instantiated, the mode of operation cannot be
* changed. Case sensitivity affects <code>addSymbol</code>,
* <code>deleteSymbol</code> and <code>lookupSymbol</code> methods.
* <p>
* This class is designed to provide a symbol lookup facility such as a
* compiler would need to properly reference scoped variables or functions.
* Most functionality is delegated to the superclass.
* <p>
* @author NVS
* @version 1.0.6
* @param <V>
* Type of value stored in dictionary.
*/
public class ScopedSymbolDictionary<V>
extends ScopedDictionary<String, V>
{
/** Defines case sensitivity mode for lookups */
private boolean caseSensitive = false;
/**
* Default constructor.
*/
public ScopedSymbolDictionary()
{
super();
}
/**
* Factory method for {@link HashMap} when {@link #caseSensitive} is <code>true</code> and
* {@link CaseInsensitiveHashMap} when {@link #caseSensitive} is <code>false</code>.
*
* @param size
* The maximum expected size of the Map
*
* @return a new map depending on the {@link #caseSensitive} flag.
*/
@Override
protected Map<String, V> factoryNodeHashMap(int size)
{
return caseSensitive ? super.factoryNodeHashMap(size) : new CaseInsensitiveHashMap<>(size);
}
/**
* Factory method for a {@link Set} as returned by {@link #keySet}. This will be a set from a
* {@link HashMap} when {@link #caseSensitive} is <code>true</code> and from {@link CaseInsensitiveHashMap}
* when {@link #caseSensitive} is <code>false</code>.
*
*
* @return a new set depending on the {@link #caseSensitive} flag.
*/
@Override
protected Set<String> factoryHashSetFromMap()
{
return caseSensitive ? super.factoryHashSetFromMap()
: Collections.newSetFromMap(new CaseInsensitiveHashMap<>());
}
/**
* Set the {@link #identityKeys} flag.
* <p>
* This is a no-op, as the key is a {@link String}.
*
* @param identityKeys
* Not used.
*/
@Override
public final void setIdentityKeys(boolean identityKeys)
{
// no-op
}
/**
* Constructor with explicit case-sensitivity requested.
*
* @param caseSensitive
* <code>true</code> makes all symbol lookups case-sensitive.
*/
public ScopedSymbolDictionary(boolean caseSensitive)
{
super();
this.caseSensitive = caseSensitive;
}
/**
* Convenience constructor that automatically creates the global scope.
*
* @param extra
* Object to be associated with the global scope.
*/
public ScopedSymbolDictionary(Object extra)
{
super(extra);
}
/**
* Convenience constructor that automatically creates the global scope
* with explicit case-sensitivity requested.
*
* @param extra
* Object to be associated with the global scope.
* @param caseSensitive
* <code>true</code> makes all symbol lookups case-sensitive.
*/
public ScopedSymbolDictionary(Object extra, boolean caseSensitive)
{
this(extra);
this.caseSensitive = caseSensitive;
}
/**
* Reports if this dictionary is case-sensitive.
*
* @return <code>true</code> if this dictionary is case-sensitive.
*/
public boolean isCaseSensitive()
{
return caseSensitive;
}
/**
* Adds a symbol to the current or global scope or replaces
* its user-defined object. The current scope is the one on the top of the
* stack. The global scope is always the first scope created on the stack.
* If only one scope is on the stack, both refer to the same scope.
*
* @param global
* Specifies the target scope. <code>false</code> specifies the
* current scope, and <code>true</code> specifies the global
* scope.
* @param name
* The symbol name.
* @param value
* The symbol value which may be any object.
*
* @return <code>true</code> if the symbol has been added,
* <code>false</code> if its value has been replaced.
*/
public boolean addSymbol(boolean global, String name, V value)
{
return addEntry(global, name, value);
}
/**
* Searches all scopes from the top of the stack down and deletes
* the first found occurrence of the symbol.
*
* @param name
* The symbol name.
*
* @return <code>true</code> if the symbol has been deleted.
*/
public boolean deleteSymbol(String name)
{
return deleteEntry(name);
}
/**
* Searches all scopes from the top of the stack down for the specified
* key and value pair and returns 0-based index of the depth where
* that pair is found. Both the key and the value must be matched to
* the given parameters for this to succeed.
*
* @param key
* The symbol name.
* @param value
* The value to search.
*
* @return 0-based index of the scope depth at which value is found.
*/
public int locateSymbolValue(String key, V value)
{
return locate(key, value);
}
/**
* Sets the given named symbol in the scope specified, to the given
* value.
*
* @param name
* The symbol name.
* @param scope
* The depth (in number of scopes from the top of the stack) at
* which the value must be set.
* @param value
* The replacement value.
*
* @return <code>true</code> if the value was successfully set, or
* <code>false</code> if any error occurred.
*/
public boolean setSymbolAtScope(String name, int scope, V value)
{
return setValueAtScope(name, scope, value);
}
/**
* Gets the value of the given named symbol in the scope specified.
*
* @param name
* The symbol name.
* @param scope
* The depth (in number of scopes from the top of the stack) at
* which the value must be set. Use -1 to reference the global
* scope.
*
* @return The object found or <code>null</code> if no such name exists.
*/
public Object getSymbolAtScope(String name, int scope)
{
return getValueAtScope(name, scope);
}
/**
* Searches all scopes from the top of the stack down for a given symbol.
*
* @param name
* The symbol name.
*
* @return The user-defined object associated with symbol or
* <code>null</code> if not found.
*/
public Object lookupSymbol(String name)
{
return lookup(name);
}
/**
* Searches all scopes from the top of the stack down for a given symbol.
*
* @param name
* The symbol name.
* @return The nesting level (depth) of the scope where the symbol is
* defined first or -1 if not found.
*/
public int locateSymbol(String name)
{
return locate(name);
}
/**
* Returns a set containing all defined symbol names as strings.
*
* @return The set view of all symbol names defined in all scopes,
*/
public Set<String> symbolSet()
{
return keySet();
}
/**
* Returns a set containing all defined symbol names as strings, in the scope specified.
*
* @param scope
* The depth (in number of scopes from the top of the stack) from which the symbols
* are collected. Use -1 to reference the global scope.
*
* @return The set view of all symbols from the specified scope (and only that scope).
*/
public Set<String> symbolSet(int scope)
{
return keySet(scope);
}
/**
* Clear this dictionary and copy all content from the source. The global scope will be either
* duplicated or kept as a standalone reference, to be shared among copies.
*
* @param source
* The source dictionary.
* @param keepGlobal
* When <code>true</code>, the global dictionary is exposed directly to this copy;
* otherwise is duplicated.
*/
public void copyAll(ScopedSymbolDictionary<V> source, boolean keepGlobal)
{
this.caseSensitive = source.caseSensitive;
super.copyAll(source, keepGlobal);
}
}