1 /*******************************************************************************
2 * Copyright (c) 2000, 2003 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
12 package net.sourceforge.phpdt.internal.ui.text.spelling.engine;
14 import java.util.Collections;
15 import java.util.HashSet;
16 import java.util.Iterator;
19 import org.eclipse.jface.preference.IPreferenceStore;
22 * Default spell checker for standard text.
26 public class DefaultSpellChecker implements ISpellChecker {
28 /** Array of url prefixes */
29 public static final String[] URL_PREFIXES = new String[] {
30 "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$
33 * Does this word contain digits?
37 * @return <code>true</code> iff this word contains digits,
38 * <code>false></code> otherwise
40 protected static boolean isDigits(final String word) {
42 for (int index = 0; index < word.length(); index++) {
44 if (Character.isDigit(word.charAt(index)))
51 * Does this word contain mixed-case letters?
56 * <code>true</code> iff the specified word starts a new
57 * sentence, <code>false</code> otherwise
58 * @return <code>true</code> iff the contains mixed-case letters,
59 * <code>false</code> otherwise
61 protected static boolean isMixedCase(final String word,
62 final boolean sentence) {
64 final int length = word.length();
65 boolean upper = Character.isUpperCase(word.charAt(0));
67 if (sentence && upper && (length > 1))
68 upper = Character.isUpperCase(word.charAt(1));
72 for (int index = length - 1; index > 0; index--) {
73 if (Character.isLowerCase(word.charAt(index)))
78 for (int index = length - 1; index > 0; index--) {
79 if (Character.isUpperCase(word.charAt(index)))
87 * Does this word contain upper-case letters only?
91 * @return <code>true</code> iff this word only contains upper-case
92 * letters, <code>false</code> otherwise
94 protected static boolean isUpperCase(final String word) {
96 for (int index = word.length() - 1; index >= 0; index--) {
98 if (Character.isLowerCase(word.charAt(index)))
105 * Does this word look like an URL?
109 * @return <code>true</code> iff this word looks like an URL,
110 * <code>false</code> otherwise
112 protected static boolean isUrl(final String word) {
114 for (int index = 0; index < URL_PREFIXES.length; index++) {
116 if (word.startsWith(URL_PREFIXES[index]))
123 * The dictionaries to use for spell-checking. Synchronized to avoid
124 * concurrent modifications.
126 private final Set fDictionaries = Collections
127 .synchronizedSet(new HashSet());
130 * The words to be ignored. Synchronized to avoid concurrent modifications.
132 private final Set fIgnored = Collections.synchronizedSet(new HashSet());
135 * The spell event listeners. Synchronized to avoid concurrent
138 private final Set fListeners = Collections.synchronizedSet(new HashSet());
141 * The preference store. Assumes the <code>IPreferenceStore</code>
142 * implementation is thread safe.
144 private final IPreferenceStore fPreferences;
147 * Creates a new default spell-checker.
150 * The preference store for this spell-checker
152 public DefaultSpellChecker(final IPreferenceStore store) {
153 fPreferences = store;
157 * @see org.eclipse.spelling.done.ISpellChecker#addDictionary(org.eclipse.spelling.done.ISpellDictionary)
159 public final void addDictionary(final ISpellDictionary dictionary) {
160 // synchronizing is necessary as this is a write access
161 fDictionaries.add(dictionary);
165 * @see org.eclipse.spelling.done.ISpellChecker#addListener(org.eclipse.spelling.done.ISpellEventListener)
167 public final void addListener(final ISpellEventListener listener) {
168 // synchronizing is necessary as this is a write access
169 fListeners.add(listener);
173 * @see net.sourceforge.phpdt.ui.text.spelling.engine.ISpellChecker#acceptsWords()
175 public boolean acceptsWords() {
176 // synchronizing might not be needed here since acceptWords is
177 // a read-only access and only called in the same thread as
178 // the modifing methods add/checkWord (?)
180 synchronized (fDictionaries) {
181 copy = new HashSet(fDictionaries);
184 ISpellDictionary dictionary = null;
185 for (final Iterator iterator = copy.iterator(); iterator.hasNext();) {
187 dictionary = (ISpellDictionary) iterator.next();
188 if (dictionary.acceptsWords())
195 * @see net.sourceforge.phpdt.internal.ui.text.spelling.engine.ISpellChecker#addWord(java.lang.String)
197 public void addWord(final String word) {
198 // synchronizing is necessary as this is a write access
200 synchronized (fDictionaries) {
201 copy = new HashSet(fDictionaries);
204 final String addable = word.toLowerCase();
205 fIgnored.add(addable);
207 ISpellDictionary dictionary = null;
208 for (final Iterator iterator = copy.iterator(); iterator.hasNext();) {
210 dictionary = (ISpellDictionary) iterator.next();
211 dictionary.addWord(addable);
216 * @see net.sourceforge.phpdt.ui.text.spelling.engine.ISpellChecker#checkWord(java.lang.String)
218 public final void checkWord(final String word) {
219 // synchronizing is necessary as this is a write access
220 fIgnored.remove(word.toLowerCase());
224 * @see org.eclipse.spelling.done.ISpellChecker#execute(org.eclipse.spelling.ISpellCheckTokenizer)
226 public void execute(final ISpellCheckIterator iterator) {
228 final boolean ignoreDigits = fPreferences
229 .getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_DIGITS);
230 final boolean ignoreMixed = fPreferences
231 .getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_MIXED);
232 final boolean ignoreSentence = fPreferences
233 .getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_SENTENCE);
234 final boolean ignoreUpper = fPreferences
235 .getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_UPPER);
236 final boolean ignoreURLS = fPreferences
237 .getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_URLS);
240 boolean starts = false;
242 while (iterator.hasNext()) {
244 word = (String) iterator.next();
247 // synchronizing is necessary as this is called inside the
249 if (!fIgnored.contains(word)) {
251 starts = iterator.startsSentence();
252 if (!isCorrect(word)) {
254 boolean isMixed = isMixedCase(word, true);
255 boolean isUpper = isUpperCase(word);
256 boolean isDigits = isDigits(word);
257 boolean isURL = isUrl(word);
259 if (!ignoreMixed && isMixed || !ignoreUpper && isUpper
260 || !ignoreDigits && isDigits || !ignoreURLS
262 || !(isMixed || isUpper || isDigits || isURL))
263 fireEvent(new SpellEvent(this, word, iterator
264 .getBegin(), iterator.getEnd(), starts,
269 if (!ignoreSentence && starts
270 && Character.isLowerCase(word.charAt(0)))
271 fireEvent(new SpellEvent(this, word, iterator
272 .getBegin(), iterator.getEnd(), true, true));
280 * Fires the specified event.
285 protected final void fireEvent(final ISpellEvent event) {
286 // synchronizing is necessary as this is called from execute
288 synchronized (fListeners) {
289 copy = new HashSet(fListeners);
291 for (final Iterator iterator = copy.iterator(); iterator.hasNext();) {
292 ((ISpellEventListener) iterator.next()).handle(event);
297 * @see org.eclipse.spelling.done.ISpellChecker#getProposals(java.lang.String,boolean)
299 public Set getProposals(final String word, final boolean sentence) {
301 // synchronizing might not be needed here since getProposals is
302 // a read-only access and only called in the same thread as
303 // the modifing methods add/removeDictionary (?)
305 synchronized (fDictionaries) {
306 copy = new HashSet(fDictionaries);
309 ISpellDictionary dictionary = null;
310 final HashSet proposals = new HashSet();
312 for (final Iterator iterator = copy.iterator(); iterator.hasNext();) {
314 dictionary = (ISpellDictionary) iterator.next();
315 proposals.addAll(dictionary.getProposals(word, sentence));
321 * @see net.sourceforge.phpdt.internal.ui.text.spelling.engine.ISpellChecker#ignoreWord(java.lang.String)
323 public final void ignoreWord(final String word) {
324 // synchronizing is necessary as this is a write access
325 fIgnored.add(word.toLowerCase());
329 * @see net.sourceforge.phpdt.internal.ui.text.spelling.engine.ISpellChecker#isCorrect(java.lang.String)
331 public final boolean isCorrect(final String word) {
332 // synchronizing is necessary as this is called from execute
334 synchronized (fDictionaries) {
335 copy = new HashSet(fDictionaries);
338 if (fIgnored.contains(word.toLowerCase()))
341 ISpellDictionary dictionary = null;
342 for (final Iterator iterator = copy.iterator(); iterator.hasNext();) {
344 dictionary = (ISpellDictionary) iterator.next();
345 if (dictionary.isCorrect(word))
352 * @see org.eclipse.spelling.done.ISpellChecker#removeDictionary(org.eclipse.spelling.done.ISpellDictionary)
354 public final void removeDictionary(final ISpellDictionary dictionary) {
355 // synchronizing is necessary as this is a write access
356 fDictionaries.remove(dictionary);
360 * @see org.eclipse.spelling.done.ISpellChecker#removeListener(org.eclipse.spelling.done.ISpellEventListener)
362 public final void removeListener(final ISpellEventListener listener) {
363 // synchronizing is necessary as this is a write access
364 fListeners.remove(listener);