Initial revision
[old-projects.git] / ekit / com / swabunga / spell / event / SpellChecker.java
diff --git a/ekit/com/swabunga/spell/event/SpellChecker.java b/ekit/com/swabunga/spell/event/SpellChecker.java
new file mode 100644 (file)
index 0000000..ccd4c58
--- /dev/null
@@ -0,0 +1,304 @@
+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