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