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[] { "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$
32 * Does this word contain digits?
36 * @return <code>true</code> iff this word contains digits, <code>false></code>
39 protected static boolean isDigits(final String word) {
41 for (int index= 0; index < word.length(); index++) {
43 if (Character.isDigit(word.charAt(index)))
50 * Does this word contain mixed-case letters?
55 * <code>true</code> iff the specified word starts a new
56 * sentence, <code>false</code> otherwise
57 * @return <code>true</code> iff the contains mixed-case letters, <code>false</code>
60 protected static boolean isMixedCase(final String word, final boolean sentence) {
62 final int length= word.length();
63 boolean upper= Character.isUpperCase(word.charAt(0));
65 if (sentence && upper && (length > 1))
66 upper= Character.isUpperCase(word.charAt(1));
70 for (int index= length - 1; index > 0; index--) {
71 if (Character.isLowerCase(word.charAt(index)))
76 for (int index= length - 1; index > 0; index--) {
77 if (Character.isUpperCase(word.charAt(index)))
85 * Does this word contain upper-case letters only?
89 * @return <code>true</code> iff this word only contains upper-case
90 * letters, <code>false</code> otherwise
92 protected static boolean isUpperCase(final String word) {
94 for (int index= word.length() - 1; index >= 0; index--) {
96 if (Character.isLowerCase(word.charAt(index)))
103 * Does this word look like an URL?
107 * @return <code>true</code> iff this word looks like an URL, <code>false</code>
110 protected static boolean isUrl(final String word) {
112 for (int index= 0; index < URL_PREFIXES.length; index++) {
114 if (word.startsWith(URL_PREFIXES[index]))
121 * The dictionaries to use for spell-checking. Synchronized to avoid
122 * concurrent modifications.
124 private final Set fDictionaries= Collections.synchronizedSet(new HashSet());
127 * The words to be ignored. Synchronized to avoid concurrent modifications.
129 private final Set fIgnored= Collections.synchronizedSet(new HashSet());
132 * The spell event listeners. Synchronized to avoid concurrent
135 private final Set fListeners= Collections.synchronizedSet(new HashSet());
138 * The preference store. Assumes the <code>IPreferenceStore</code>
139 * implementation is thread safe.
141 private final IPreferenceStore fPreferences;
144 * Creates a new default spell-checker.
147 * The preference store for this spell-checker
149 public DefaultSpellChecker(final IPreferenceStore store) {
154 * @see org.eclipse.spelling.done.ISpellChecker#addDictionary(org.eclipse.spelling.done.ISpellDictionary)
156 public final void addDictionary(final ISpellDictionary dictionary) {
157 // synchronizing is necessary as this is a write access
158 fDictionaries.add(dictionary);
162 * @see org.eclipse.spelling.done.ISpellChecker#addListener(org.eclipse.spelling.done.ISpellEventListener)
164 public final void addListener(final ISpellEventListener listener) {
165 // synchronizing is necessary as this is a write access
166 fListeners.add(listener);
170 * @see org.eclipse.jdt.ui.text.spelling.engine.ISpellChecker#acceptsWords()
172 public boolean acceptsWords() {
173 // synchronizing might not be needed here since acceptWords is
174 // a read-only access and only called in the same thread as
175 // the modifing methods add/checkWord (?)
177 synchronized (fDictionaries) {
178 copy= new HashSet(fDictionaries);
181 ISpellDictionary dictionary= null;
182 for (final Iterator iterator= copy.iterator(); iterator.hasNext();) {
184 dictionary= (ISpellDictionary)iterator.next();
185 if (dictionary.acceptsWords())
192 * @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellChecker#addWord(java.lang.String)
194 public void addWord(final String word) {
195 // synchronizing is necessary as this is a write access
197 synchronized (fDictionaries) {
198 copy= new HashSet(fDictionaries);
201 final String addable= word.toLowerCase();
202 fIgnored.add(addable);
204 ISpellDictionary dictionary= null;
205 for (final Iterator iterator= copy.iterator(); iterator.hasNext();) {
207 dictionary= (ISpellDictionary)iterator.next();
208 dictionary.addWord(addable);
213 * @see org.eclipse.jdt.ui.text.spelling.engine.ISpellChecker#checkWord(java.lang.String)
215 public final void checkWord(final String word) {
216 // synchronizing is necessary as this is a write access
217 fIgnored.remove(word.toLowerCase());
221 * @see org.eclipse.spelling.done.ISpellChecker#execute(org.eclipse.spelling.ISpellCheckTokenizer)
223 public void execute(final ISpellCheckIterator iterator) {
225 final boolean ignoreDigits= fPreferences.getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_DIGITS);
226 final boolean ignoreMixed= fPreferences.getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_MIXED);
227 final boolean ignoreSentence= fPreferences.getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_SENTENCE);
228 final boolean ignoreUpper= fPreferences.getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_UPPER);
229 final boolean ignoreURLS= fPreferences.getBoolean(ISpellCheckPreferenceKeys.SPELLING_IGNORE_URLS);
232 boolean starts= false;
234 while (iterator.hasNext()) {
236 word= (String)iterator.next();
239 // synchronizing is necessary as this is called inside the reconciler
240 if (!fIgnored.contains(word)) {
242 starts= iterator.startsSentence();
243 if (!isCorrect(word)) {
245 boolean isMixed= isMixedCase(word, true);
246 boolean isUpper= isUpperCase(word);
247 boolean isDigits= isDigits(word);
248 boolean isURL= isUrl(word);
250 if ( !ignoreMixed && isMixed || !ignoreUpper && isUpper || !ignoreDigits && isDigits || !ignoreURLS && isURL || !(isMixed || isUpper || isDigits || isURL))
251 fireEvent(new SpellEvent(this, word, iterator.getBegin(), iterator.getEnd(), starts, false));
255 if (!ignoreSentence && starts && Character.isLowerCase(word.charAt(0)))
256 fireEvent(new SpellEvent(this, word, iterator.getBegin(), iterator.getEnd(), true, true));
264 * Fires the specified event.
269 protected final void fireEvent(final ISpellEvent event) {
270 // synchronizing is necessary as this is called from execute
272 synchronized (fListeners) {
273 copy= new HashSet(fListeners);
275 for (final Iterator iterator= copy.iterator(); iterator.hasNext();) {
276 ((ISpellEventListener)iterator.next()).handle(event);
281 * @see org.eclipse.spelling.done.ISpellChecker#getProposals(java.lang.String,boolean)
283 public Set getProposals(final String word, final boolean sentence) {
285 // synchronizing might not be needed here since getProposals is
286 // a read-only access and only called in the same thread as
287 // the modifing methods add/removeDictionary (?)
289 synchronized (fDictionaries) {
290 copy= new HashSet(fDictionaries);
293 ISpellDictionary dictionary= null;
294 final HashSet proposals= new HashSet();
296 for (final Iterator iterator= copy.iterator(); iterator.hasNext();) {
298 dictionary= (ISpellDictionary)iterator.next();
299 proposals.addAll(dictionary.getProposals(word, sentence));
305 * @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellChecker#ignoreWord(java.lang.String)
307 public final void ignoreWord(final String word) {
308 // synchronizing is necessary as this is a write access
309 fIgnored.add(word.toLowerCase());
313 * @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellChecker#isCorrect(java.lang.String)
315 public final boolean isCorrect(final String word) {
316 // synchronizing is necessary as this is called from execute
318 synchronized (fDictionaries) {
319 copy= new HashSet(fDictionaries);
322 if (fIgnored.contains(word.toLowerCase()))
325 ISpellDictionary dictionary= null;
326 for (final Iterator iterator= copy.iterator(); iterator.hasNext();) {
328 dictionary= (ISpellDictionary)iterator.next();
329 if (dictionary.isCorrect(word))
336 * @see org.eclipse.spelling.done.ISpellChecker#removeDictionary(org.eclipse.spelling.done.ISpellDictionary)
338 public final void removeDictionary(final ISpellDictionary dictionary) {
339 // synchronizing is necessary as this is a write access
340 fDictionaries.remove(dictionary);
344 * @see org.eclipse.spelling.done.ISpellChecker#removeListener(org.eclipse.spelling.done.ISpellEventListener)
346 public final void removeListener(final ISpellEventListener listener) {
347 // synchronizing is necessary as this is a write access
348 fListeners.remove(listener);