package com.swabunga.spell.event;
import com.swabunga.spell.engine.*;
import java.util.*;
/**
* This is the main class for spell checking (using the new event based spell
* checking).
*
* @author Jason Height (jheight@chariot.net.au)
* @created 19 June 2002
*/
public class SpellChecker
{
/** Flag indicating that the Spell Check completed without any errors present*/
public static final int SPELLCHECK_OK=-1;
/** Flag indicating that the Spell Check completed due to user cancellation*/
public static final int SPELLCHECK_CANCEL=-2;
private List eventListeners = new ArrayList();
private SpellDictionary dictionary;
private Configuration config = Configuration.getConfiguration();
/**This variable holds all of the words that are to be always ignored */
private Set ignoredWords = new HashSet();
private Map autoReplaceWords = new HashMap();
/**
* Constructs the SpellChecker. The default threshold is used
*
* @param dictionary Description of the Parameter
*/
public SpellChecker(SpellDictionary dictionary) {
if (dictionary == null) {
throw new IllegalArgumentException("dictionary must non-null");
}
this.dictionary = dictionary;
}
/**
* Constructs the SpellChecker with a threshold
*
* @param dictionary Description of the Parameter
* @param threshold Description of the Parameter
*/
public SpellChecker(SpellDictionary dictionary, int threshold) {
this(dictionary);
config.setInteger( Configuration.SPELL_THRESHOLD, threshold );
}
/**
*Adds a SpellCheckListener
*
* @param listener The feature to be added to the SpellCheckListener attribute
*/
public void addSpellCheckListener(SpellCheckListener listener) {
eventListeners.add(listener);
}
/**
*Removes a SpellCheckListener
*
* @param listener Description of the Parameter
*/
public void removeSpellCheckListener(SpellCheckListener listener) {
eventListeners.remove(listener);
}
/**
* Fires off a spell check event to the listeners.
*
* @param event Description of the Parameter
*/
protected void fireSpellCheckEvent(SpellCheckEvent event) {
for (int i = eventListeners.size() - 1; i >= 0; i--) {
((SpellCheckListener) eventListeners.get(i)).spellingError(event);
}
}
/**
* This method clears the words that are currently being remembered as
* Ignore All words and Replace All words.
*/
public void reset() {
ignoredWords.clear();
autoReplaceWords.clear();
}
/**
* Checks the text string.
* <p>
* Returns the corrected string.
*
* @param text Description of the Parameter
* @return Description of the Return Value
* @deprecated use checkSpelling(WordTokenizer)
*/
public String checkString(String text) {
StringWordTokenizer tokens = new StringWordTokenizer(text);
checkSpelling(tokens);
return tokens.getFinalText();
}
/**
* Returns true iif this word contains a digit
*
* @param word Description of the Parameter
* @return The digitWord value
*/
private final static boolean isDigitWord(String word) {
for (int i = word.length() - 1; i >= 0; i--) {
if (Character.isDigit(word.charAt(i))) {
return true;
}
}
return false;
}
/**
* Returns true iif this word looks like an internet address
*
* @param word Description of the Parameter
* @return The iNETWord value
*/
private final static boolean isINETWord(String word) {
//JMH TBD
return false;
}
/**
* Returns true iif this word contains all upper case characters
*
* @param word Description of the Parameter
* @return The upperCaseWord value
*/
private final static boolean isUpperCaseWord(String word) {
for (int i = word.length() - 1; i >= 0; i--) {
if (Character.isLowerCase(word.charAt(i))) {
return false;
}
}
return true;
}
/**
* Returns true iif this word contains mixed case characters
*
* @param word Description of the Parameter
* @param startsSentance True if this word is at the start of a sentance
* @return The mixedCaseWord value
*/
private final static boolean isMixedCaseWord(String word, boolean startsSentance) {
int strLen = word.length();
boolean isUpper = Character.isUpperCase(word.charAt(0));
//Ignore the first character if this word starts the sentance and the first
//character was upper cased, since this is normal behaviour
if ((startsSentance) && isUpper && (strLen > 1))
isUpper = Character.isUpperCase(word.charAt(1));
if (isUpper) {
for (int i = word.length() - 1; i > 0; i--) {
if (Character.isLowerCase(word.charAt(i))) {
return true;
}
}
} else {
for (int i = word.length() - 1; i > 0; i--) {
if (Character.isUpperCase(word.charAt(i))) {
return true;
}
}
}
return false;
}
/**
* This method will fire the spell check event and then handle the event
* action that has been selected by the user.
*
* @param tokenizer Description of the Parameter
* @param event Description of the Parameter
* @return Returns true if the event action is to cancel the current spell checking, false if the spell checking should continue
*/
protected boolean fireAndHandleEvent(WordTokenizer tokenizer, SpellCheckEvent event) {
fireSpellCheckEvent(event);
String word = event.getInvalidWord();
//Work out what to do in response to the event.
switch (event.getAction()) {
case SpellCheckEvent.INITIAL:
break;
case SpellCheckEvent.IGNORE:
break;
case SpellCheckEvent.IGNOREALL:
if (!ignoredWords.contains(word)) {
ignoredWords.add(word);
}
break;
case SpellCheckEvent.REPLACE:
tokenizer.replaceWord(event.getReplaceWord());
break;
case SpellCheckEvent.REPLACEALL:
String replaceAllWord = event.getReplaceWord();
if (!autoReplaceWords.containsKey(word)) {
autoReplaceWords.put(word, replaceAllWord);
}
tokenizer.replaceWord(replaceAllWord);
break;
case SpellCheckEvent.ADDTODICT:
String addWord = event.getReplaceWord();
tokenizer.replaceWord(addWord);
dictionary.addWord(addWord);
break;
case SpellCheckEvent.CANCEL:
return true;
default:
throw new IllegalArgumentException("Unhandled case.");
}
return false;
}
/**
* This method is called to check the spelling of the words that are returned
* by the WordTokenizer.
* <p>For each invalid word the action listeners will be informed with a new SpellCheckEvent</p>
*
* @param tokenizer Description of the Parameter
* @return Either SPELLCHECK_OK, SPELLCHECK_CANCEL or the number of errors found. The number of errors are those that are found BEFORE and corretions are made.
*/
public final int checkSpelling(WordTokenizer tokenizer) {
int errors = 0;
boolean terminated = false;
//Keep track of the previous word
String previousWord = null;
while (tokenizer.hasMoreWords() && !terminated) {
String word = tokenizer.nextWord();
//Check the spelling of the word
if (!dictionary.isCorrect(word)) {
if (
(config.getBoolean(Configuration.SPELL_IGNOREMIXEDCASE) && isMixedCaseWord(word, tokenizer.isNewSentance())) ||
(config.getBoolean(Configuration.SPELL_IGNOREUPPERCASE) && isUpperCaseWord(word)) ||
(config.getBoolean(Configuration.SPELL_IGNOREDIGITWORDS) && isDigitWord(word)) ||
(config.getBoolean(Configuration.SPELL_IGNOREINTERNETADDRESSES) && isINETWord(word))) {
//Null event. Since we are ignoring this word due
//to one of the above cases.
} else {
//We cant ignore this misspelt word
//For this invalid word are we ignoreing the misspelling?
if (!ignoredWords.contains(word)) {
errors++;
//Is this word being automagically replaced
if (autoReplaceWords.containsKey(word)) {
tokenizer.replaceWord((String) autoReplaceWords.get(word));
} else {
//JMH Need to somehow capitalise the suggestions if
//ignoreSentanceCapitalisation is not set to true
//Fire the event.
SpellCheckEvent event = new BasicSpellCheckEvent(word, dictionary.getSuggestions(word,
config.getInteger(Configuration.SPELL_THRESHOLD)), tokenizer);
terminated = fireAndHandleEvent(tokenizer, event);
}
}
}
} else {
//This is a correctly spelt word. However perform some extra checks
/*
* JMH TBD //Check for multiple words
* if (!ignoreMultipleWords &&) {
* }
*/
//Check for capitalisation
if ((!config.getBoolean(Configuration.SPELL_IGNORESENTANCECAPITALIZATION)) && (tokenizer.isNewSentance())
&& (Character.isLowerCase(word.charAt(0)))) {
errors++;
StringBuffer buf = new StringBuffer(word);
buf.setCharAt(0, Character.toUpperCase(word.charAt(0)));
List suggestion = new LinkedList();
suggestion.add(new Word(buf.toString(), 0));
SpellCheckEvent event = new BasicSpellCheckEvent(word, suggestion,
tokenizer);
terminated = fireAndHandleEvent(tokenizer, event);
}
}
}
if (terminated)
return SPELLCHECK_CANCEL;
else if (errors == 0)
return SPELLCHECK_OK;
else return errors;
}
/** Added to free up the class memory and resources,
* which otherwise trash the system quickly (code by Steve Birmingham)
*/
public void dispose()
{
eventListeners = null;
ignoredWords = null;
autoReplaceWords = null;
config = null;
dictionary.dispose();
}
}