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