# KeywordDictionary.java

    /*
    ** Module   : KeywordDictionary.java
    ** Abstract : provides a keyword lookup facility, handles abbreviations
    **
    ** Copyright (c) 2004-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- -------------------Description--------------------
    ** 001 GES 20041104   @18647 First version, with support for:
    **                           - all objects stored are instances of a
    **                             separate Keyword class
    **                           - keys are all strings
    **                           - multiple instances of the same Keyword
    **                             object are referenced/found via all valid
    **                             abbreviations
    **                           - keys used in the HashMap are lowercased
    **                             before insertion and the lookup is done
    **                             using a lowercased string
    ** 002 GES 20041116   @18648 Added an option to control case-sensitivity
    **                           in matching. The default is to be
    **                           case-insensitive.
    ** 003 GES 20110712          Added a size() method.
    ** 004 GES 20190929          Added a copy constructor.
    ** 005 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    */

    /*
    ** 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.uast;

    import java.io.*;
    import java.util.*;

    /**
     * Provides a mechanism for storage and retrieval of language keywords as
     * represented by the <code>{@link Keyword}</code> object.  During the
     * lookup process each keyword can be literally matched or can be optionally
     * matched with a set of valid abbreviations.
     * <p>
     * The namespace for the dictionary is flat. Each keyword and all possible
     * abbreviations must uniquely reference a single <code>Keyword</code>
     * object.
     * <p>
     * Lookups can be done on a case-sensitive basis or can be case-insensitive.
     * Case-insensitivity is the default behavior.
     */
    public class KeywordDictionary
    {
       /** Associates 1 or more match strings with a <code>Keyword</code>. */
       private HashMap list;

       /** Controls case-sensitivity of add, delete and lookup operations. */
       private boolean matchCase;

       /**
        * Default constructor which instantiates a <code>KeywordDictionary</code>
        * object which matches on a case-insensitive basis.
        */
       public KeywordDictionary()
       {
          this(false);
       }

       /**
        * Instantiates a <code>KeywordDictionary</code> object.
        *
        * @param    matchCase
        *           Controls lookup algorithm case-sensitivity.
        */
       public KeywordDictionary(boolean matchCase)
       {
          list           = new HashMap();
          this.matchCase = matchCase;
       }

       /**
        * Copy constructor, makes an independent duplicate of the given instance.
        *
        * @param    other
        *           Instance to duplicate.
        */
       public KeywordDictionary(KeywordDictionary other)
       {
          this.list      = new HashMap(other.list);
          this.matchCase = other.matchCase;
       }

       /**
        * Adds a <code>Keyword</code> object to the dictionary.  The keyword is
        * mapped to its full text and any abbreviations, if supported by this
        * <code>Keyword</code> object.  All such mappings must be unique in the
        * dictionary because each unique text mapping can only have one
        * associated <code>Keyword</code>.
        *
        * @param     word
        *            Keyword object to add.
        */
       public void addKeyword(Keyword word)
       {
          processKeyword(word, true);
       }

       /**
        * Removes a <code>Keyword</code> object from the dictionary.  Each
        * possible mapping representing this <code>Keyword</code> is removed,
        * including its full text and all valid abbreviations, if supported.
        *
        * @param     word
        *            Keyword object to remove.
        */
       public void deleteKeyword(Keyword word)
       {
          processKeyword(word, false);
       }

       /**
        * Processes a dictionary lookup for a specified keyword.  If a match is
        * found, the associated <code>Keyword</code> object is returned.  This
        * method honors the case-sensitivity setting defined at time of
        * this instance's construction.
        *
        * @param    text
        *           The match string to be used as a key in the lookup. If case-
        *           sensitivity is off, this key may be in any case (including
        *           mixed case) and the result will always match.
        * @return   A <code>Keyword</code> object or null if no match was found.
        */
       public Keyword lookupKeyword(String text)
       {
          String match;

          if (!matchCase)
          {
             // lookup lowercase version of this key
             match = text.toLowerCase();
          }
          else
          {
             match = text;
          }

          // return the Keyword or null
          return (Keyword) list.get((Object) match);
       }

       /**
        * Report the number of keywords in this dictionary.
        *
        * @return   Dictionary size.
        */
       public int size()
       {
          return list.size();
       }

       /**
        * Prints a report to <code>stderr</code> which includes 1 line per
        * entry in the dictionary.  If a <code>Keyword</code> supports
        * abbreviations, then multiple entries will be found that refer to the
        * same <code>Keyword</code>.
        */
       public void dump(PrintStream output)
       {
          Iterator elements = list.keySet().iterator();

          while (elements.hasNext())
          {
             String nextkey = (String) elements.next();
             output.println("Key = "     +
                               nextkey      +
                               "; Value = " +
                               lookupKeyword(nextkey));
          }
       }

       /**
        * Returns an iterator list of all <code>Keyword</code> objects in the
        * dictionary.
        *
        * @return   Enumeration of all keywords in the dictionary.
        */
       Iterator keywordList()
       {
          return list.values().iterator();
       }

       /**
        * Worker method to add and remove <code>Keyword</code> objects in the
        * dictionary.
        * <p>
        * This method handles all logic related to abbreviation processing. Each
        * <code>Keyword</code> object contains a
        * <code>{@link Keyword#minChars}</code> member.  If this member is less
        * than 1 or greater than the size of the <code>Keyword's</code> full
        * text, no abbreviations are supported.  Otherwise, <code>minChars</code>
        * specifies the minimum number of the left-most full text characters that
        * constitute a valid match with this <code>Keyword</code>. To simplify
        * the lookup processing, all possible abbreviations are calculated and
        * inserted into our <code>HashMap</code>.  Each map entry stores a
        * reference to the <code>Keyword</code> object being added.  On removal,
        * the same abbreviation algorithm is used to determine all valid keys
        * which should be removed from the map.
        * <p>
        * Depending on the setting of the <code>{@link #matchCase}</code>
        * instance variable, the map keys are optionally lowercased before
        * insertion into the map.  On removal, the same case-sensitivity setting
        * is used to correctly match the case of the keys to be removed.
        *
        * @param    word
        *           The <code>Keyword</code> to add or remove.
        * @param    add
        *           The operation to perform. If true, add to the map.  If false,
        *           remove from the map.
        */
       private void processKeyword(Keyword word, boolean add)
       {
          int     min  = word.getMinChars();
          String  text = word.getFullText();

          if (!matchCase)
          {
             // all keys must be lowercased before insertion
             text = text.toLowerCase();
          }

          // in these situations, there are no abbreviations
          if (min < 1 || min > text.length())
          {
             min = word.getFullText().length();
          }

          for (int i = text.length(); i >= min; i--)
          {
             if (add)
             {
                // add the keyword and each valid abbreviation with an
                // association to the same object that represents the keyword
                list.put((Object) text.substring(0, i), (Object) word);
             }
             else
             {
                // remove all keys based on the keyword and each valid abbrev.
                list.remove((Object) text.substring(0, i));
             }
          }
       }
    }