--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package net.sourceforge.phpdt.internal.ui.text.spelling.engine;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+
+/**
+ * Default spell checker for standard text.
+ *
+ * @since 3.0
+ */
+public class DefaultSpellChecker implements ISpellChecker {
+
+ /** Array of url prefixes */
+ public static final String[] URL_PREFIXES = new String[] {
+ "http://", "https://", "www.", "ftp://", "ftps://", "news://", "mailto://" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
+
+ /**
+ * Does this word contain digits?
+ *
+ * @param word
+ * The word to check
+ * @return <code>true</code> iff this word contains digits,
+ * <code>false></code> otherwise
+ */
+ protected static boolean isDigits(final String word) {
+
+ for (int index = 0; index < word.length(); index++) {
+
+ if (Character.isDigit(word.charAt(index)))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Does this word contain mixed-case letters?
+ *
+ * @param word
+ * The word to check
+ * @param sentence
+ * <code>true</code> iff the specified word starts a new
+ * sentence, <code>false</code> otherwise
+ * @return <code>true</code> iff the contains mixed-case letters,
+ * <code>false</code> otherwise
+ */
+ protected static boolean isMixedCase(final String word,
+ final boolean sentence) {
+
+ final int length = word.length();
+ boolean upper = Character.isUpperCase(word.charAt(0));
+
+ if (sentence && upper && (length > 1))
+ upper = Character.isUpperCase(word.charAt(1));
+
+ if (upper) {
+
+ for (int index = length - 1; index > 0; index--) {
+ if (Character.isLowerCase(word.charAt(index)))
+ return true;
+ }
+ } else {
+
+ for (int index = length - 1; index > 0; index--) {
+ if (Character.isUpperCase(word.charAt(index)))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Does this word contain upper-case letters only?
+ *
+ * @param word
+ * The word to check
+ * @return <code>true</code> iff this word only contains upper-case
+ * letters, <code>false</code> otherwise
+ */
+ protected static boolean isUpperCase(final String word) {
+
+ for (int index = word.length() - 1; index >= 0; index--) {
+
+ if (Character.isLowerCase(word.charAt(index)))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Does this word look like an URL?
+ *
+ * @param word
+ * The word to check
+ * @return <code>true</code> iff this word looks like an URL,
+ * <code>false</code> otherwise
+ */
+ protected static boolean isUrl(final String word) {
+
+ for (int index = 0; index < URL_PREFIXES.length; index++) {
+
+ if (word.startsWith(URL_PREFIXES[index]))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * The dictionaries to use for spell-checking. Synchronized to avoid
+ * concurrent modifications.
+ */
+ private final Set fDictionaries = Collections
+ .synchronizedSet(new HashSet());
+
+ /**
+ * The words to be ignored. Synchronized to avoid concurrent modifications.
+ */
+ private final Set fIgnored = Collections.synchronizedSet(new HashSet());
+
+ /**
+ * The spell event listeners. Synchronized to avoid concurrent
+ * modifications.
+ */
+ private final Set fListeners = Collections.synchronizedSet(new HashSet());
+
+ /**
+ * The preference store. Assumes the <code>IPreferenceStore</code>
+ * implementation is thread safe.
+ */
+ private final IPreferenceStore fPreferences;
+
+ /**
+ * Creates a new default spell-checker.
+ *
+ * @param store
+ * The preference store for this spell-checker
+ */
+ public DefaultSpellChecker(final IPreferenceStore store) {
+ fPreferences = store;
+ }
+
+ /*
+ * @see org.eclipse.spelling.done.ISpellChecker#addDictionary(org.eclipse.spelling.done.ISpellDictionary)
+ */
+ public final void addDictionary(final ISpellDictionary dictionary) {
+ // synchronizing is necessary as this is a write access
+ fDictionaries.add(dictionary);
+ }
+
+ /*
+ * @see org.eclipse.spelling.done.ISpellChecker#addListener(org.eclipse.spelling.done.ISpellEventListener)
+ */
+ public final void addListener(final ISpellEventListener listener) {
+ // synchronizing is necessary as this is a write access
+ fListeners.add(listener);
+ }
+
+ /*
+ * @see net.sourceforge.phpdt.ui.text.spelling.engine.ISpellChecker#acceptsWords()
+ */
+ public boolean acceptsWords() {
+ // synchronizing might not be needed here since acceptWords is
+ // a read-only access and only called in the same thread as
+ // the modifing methods add/checkWord (?)
+ Set copy;
+ synchronized (fDictionaries) {
+ copy = new HashSet(fDictionaries);
+ }
+
+ ISpellDictionary dictionary = null;
+ for (final Iterator iterator = copy.iterator(); iterator.hasNext();) {
+
+ dictionary = (ISpellDictionary) iterator.next();
+ if (dictionary.acceptsWords())
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * @see net.sourceforge.phpdt.internal.ui.text.spelling.engine.ISpellChecker#addWord(java.lang.String)
+ */
+ public void addWord(final String word) {
+ // synchronizing is necessary as this is a write access
+ Set copy;
+ synchronized (fDictionaries) {
+ copy = new HashSet(fDictionaries);
+ }
+
+ final String addable = word.toLowerCase();
+ fIgnored.add(addable);
+
+ ISpellDictionary dictionary = null;
+ for (final Iterator iterator = copy.iterator(); iterator.hasNext();) {
+
+ dictionary = (ISpellDictionary) iterator.next();
+ dictionary.addWord(addable);
+ }
+ }
+
+ /*
+ * @see net.sourceforge.phpdt.ui.text.spelling.engine.ISpellChecker#checkWord(java.lang.String)
+ */
+ public final void checkWord(final String word) {
+ // synchronizing is necessary as this is a write access
+ fIgnored.remove(word.toLowerCase());
+ }
+
+ /*
+ * @see org.eclipse.spelling.done.ISpellChecker#execute(org.eclipse.spelling.ISpellCheckTokenizer)
+ */
+ public void execute(final ISpellCheckIterator iterator) {
+
+ final boolean ignoreDigits = fPreferences
+ .getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_DIGITS);
+ final boolean ignoreMixed = fPreferences
+ .getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_MIXED);
+ final boolean ignoreSentence = fPreferences
+ .getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_SENTENCE);
+ final boolean ignoreUpper = fPreferences
+ .getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_UPPER);
+ final boolean ignoreURLS = fPreferences
+ .getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_URLS);
+
+ String word = null;
+ boolean starts = false;
+
+ while (iterator.hasNext()) {
+
+ word = (String) iterator.next();
+ if (word != null) {
+
+ // synchronizing is necessary as this is called inside the
+ // reconciler
+ if (!fIgnored.contains(word)) {
+
+ starts = iterator.startsSentence();
+ if (!isCorrect(word)) {
+
+ boolean isMixed = isMixedCase(word, true);
+ boolean isUpper = isUpperCase(word);
+ boolean isDigits = isDigits(word);
+ boolean isURL = isUrl(word);
+
+ if (!ignoreMixed && isMixed || !ignoreUpper && isUpper
+ || !ignoreDigits && isDigits || !ignoreURLS
+ && isURL
+ || !(isMixed || isUpper || isDigits || isURL))
+ fireEvent(new SpellEvent(this, word, iterator
+ .getBegin(), iterator.getEnd(), starts,
+ false));
+
+ } else {
+
+ if (!ignoreSentence && starts
+ && Character.isLowerCase(word.charAt(0)))
+ fireEvent(new SpellEvent(this, word, iterator
+ .getBegin(), iterator.getEnd(), true, true));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Fires the specified event.
+ *
+ * @param event
+ * Event to fire
+ */
+ protected final void fireEvent(final ISpellEvent event) {
+ // synchronizing is necessary as this is called from execute
+ Set copy;
+ synchronized (fListeners) {
+ copy = new HashSet(fListeners);
+ }
+ for (final Iterator iterator = copy.iterator(); iterator.hasNext();) {
+ ((ISpellEventListener) iterator.next()).handle(event);
+ }
+ }
+
+ /*
+ * @see org.eclipse.spelling.done.ISpellChecker#getProposals(java.lang.String,boolean)
+ */
+ public Set getProposals(final String word, final boolean sentence) {
+
+ // synchronizing might not be needed here since getProposals is
+ // a read-only access and only called in the same thread as
+ // the modifing methods add/removeDictionary (?)
+ Set copy;
+ synchronized (fDictionaries) {
+ copy = new HashSet(fDictionaries);
+ }
+
+ ISpellDictionary dictionary = null;
+ final HashSet proposals = new HashSet();
+
+ for (final Iterator iterator = copy.iterator(); iterator.hasNext();) {
+
+ dictionary = (ISpellDictionary) iterator.next();
+ proposals.addAll(dictionary.getProposals(word, sentence));
+ }
+ return proposals;
+ }
+
+ /*
+ * @see net.sourceforge.phpdt.internal.ui.text.spelling.engine.ISpellChecker#ignoreWord(java.lang.String)
+ */
+ public final void ignoreWord(final String word) {
+ // synchronizing is necessary as this is a write access
+ fIgnored.add(word.toLowerCase());
+ }
+
+ /*
+ * @see net.sourceforge.phpdt.internal.ui.text.spelling.engine.ISpellChecker#isCorrect(java.lang.String)
+ */
+ public final boolean isCorrect(final String word) {
+ // synchronizing is necessary as this is called from execute
+ Set copy;
+ synchronized (fDictionaries) {
+ copy = new HashSet(fDictionaries);
+ }
+
+ if (fIgnored.contains(word.toLowerCase()))
+ return true;
+
+ ISpellDictionary dictionary = null;
+ for (final Iterator iterator = copy.iterator(); iterator.hasNext();) {
+
+ dictionary = (ISpellDictionary) iterator.next();
+ if (dictionary.isCorrect(word))
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * @see org.eclipse.spelling.done.ISpellChecker#removeDictionary(org.eclipse.spelling.done.ISpellDictionary)
+ */
+ public final void removeDictionary(final ISpellDictionary dictionary) {
+ // synchronizing is necessary as this is a write access
+ fDictionaries.remove(dictionary);
+ }
+
+ /*
+ * @see org.eclipse.spelling.done.ISpellChecker#removeListener(org.eclipse.spelling.done.ISpellEventListener)
+ */
+ public final void removeListener(final ISpellEventListener listener) {
+ // synchronizing is necessary as this is a write access
+ fListeners.remove(listener);
+ }
+}