4ca16c5b3af376577f25cdb766a0978fa23f3013
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / ui / text / spelling / engine / DefaultSpellChecker.java
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
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11
12 package net.sourceforge.phpdt.internal.ui.text.spelling.engine;
13
14 import java.util.Collections;
15 import java.util.HashSet;
16 import java.util.Iterator;
17 import java.util.Set;
18
19 import org.eclipse.jface.preference.IPreferenceStore;
20
21 /**
22  * Default spell checker for standard text.
23  * 
24  * @since 3.0
25  */
26 public class DefaultSpellChecker implements ISpellChecker {
27
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$
30
31         /**
32          * Does this word contain digits?
33          * 
34          * @param word
35          *                   The word to check
36          * @return <code>true</code> iff this word contains digits, <code>false></code>
37          *               otherwise
38          */
39         protected static boolean isDigits(final String word) {
40
41                 for (int index= 0; index < word.length(); index++) {
42
43                         if (Character.isDigit(word.charAt(index)))
44                                 return true;
45                 }
46                 return false;
47         }
48
49         /**
50          * Does this word contain mixed-case letters?
51          * 
52          * @param word
53          *                   The word to check
54          * @param sentence
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>
58          *               otherwise
59          */
60         protected static boolean isMixedCase(final String word, final boolean sentence) {
61
62                 final int length= word.length();
63                 boolean upper= Character.isUpperCase(word.charAt(0));
64
65                 if (sentence && upper && (length > 1))
66                         upper= Character.isUpperCase(word.charAt(1));
67
68                 if (upper) {
69
70                         for (int index= length - 1; index > 0; index--) {
71                                 if (Character.isLowerCase(word.charAt(index)))
72                                         return true;
73                         }
74                 } else {
75
76                         for (int index= length - 1; index > 0; index--) {
77                                 if (Character.isUpperCase(word.charAt(index)))
78                                         return true;
79                         }
80                 }
81                 return false;
82         }
83
84         /**
85          * Does this word contain upper-case letters only?
86          * 
87          * @param word
88          *                   The word to check
89          * @return <code>true</code> iff this word only contains upper-case
90          *               letters, <code>false</code> otherwise
91          */
92         protected static boolean isUpperCase(final String word) {
93
94                 for (int index= word.length() - 1; index >= 0; index--) {
95
96                         if (Character.isLowerCase(word.charAt(index)))
97                                 return false;
98                 }
99                 return true;
100         }
101
102         /**
103          * Does this word look like an URL?
104          * 
105          * @param word
106          *                   The word to check
107          * @return <code>true</code> iff this word looks like an URL, <code>false</code>
108          *               otherwise
109          */
110         protected static boolean isUrl(final String word) {
111
112                 for (int index= 0; index < URL_PREFIXES.length; index++) {
113
114                         if (word.startsWith(URL_PREFIXES[index]))
115                                 return true;
116                 }
117                 return false;
118         }
119
120         /**
121          * The dictionaries to use for spell-checking. Synchronized to avoid
122          * concurrent modifications.
123          */
124         private final Set fDictionaries= Collections.synchronizedSet(new HashSet());
125
126         /**
127          * The words to be ignored. Synchronized to avoid concurrent modifications.
128          */
129         private final Set fIgnored= Collections.synchronizedSet(new HashSet());
130
131         /**
132          * The spell event listeners. Synchronized to avoid concurrent
133          * modifications.
134          */
135         private final Set fListeners= Collections.synchronizedSet(new HashSet());
136
137         /**
138          * The preference store. Assumes the <code>IPreferenceStore</code>
139          * implementation is thread safe.
140          */
141         private final IPreferenceStore fPreferences;
142
143         /**
144          * Creates a new default spell-checker.
145          * 
146          * @param store
147          *                   The preference store for this spell-checker
148          */
149         public DefaultSpellChecker(final IPreferenceStore store) {
150                 fPreferences= store;
151         }
152
153         /*
154          * @see org.eclipse.spelling.done.ISpellChecker#addDictionary(org.eclipse.spelling.done.ISpellDictionary)
155          */
156         public final void addDictionary(final ISpellDictionary dictionary) {
157                 // synchronizing is necessary as this is a write access
158                 fDictionaries.add(dictionary);
159         }
160
161         /*
162          * @see org.eclipse.spelling.done.ISpellChecker#addListener(org.eclipse.spelling.done.ISpellEventListener)
163          */
164         public final void addListener(final ISpellEventListener listener) {
165                 // synchronizing is necessary as this is a write access
166                 fListeners.add(listener);
167         }
168         
169         /*
170          * @see org.eclipse.jdt.ui.text.spelling.engine.ISpellChecker#acceptsWords()
171          */
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 (?)
176                 Set copy;
177                 synchronized (fDictionaries) {
178                         copy= new HashSet(fDictionaries);
179                 }
180                 
181                 ISpellDictionary dictionary= null;
182                 for (final Iterator iterator= copy.iterator(); iterator.hasNext();) {
183
184                         dictionary= (ISpellDictionary)iterator.next();
185                         if (dictionary.acceptsWords())
186                                 return true;
187                 }
188                 return false;
189         }
190
191         /*
192          * @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellChecker#addWord(java.lang.String)
193          */
194         public void addWord(final String word) {
195                 // synchronizing is necessary as this is a write access
196                 Set copy;
197                 synchronized (fDictionaries) {
198                         copy= new HashSet(fDictionaries);
199                 }
200
201                 final String addable= word.toLowerCase();
202                 fIgnored.add(addable);
203
204                 ISpellDictionary dictionary= null;
205                 for (final Iterator iterator= copy.iterator(); iterator.hasNext();) {
206
207                         dictionary= (ISpellDictionary)iterator.next();
208                         dictionary.addWord(addable);
209                 }
210         }
211
212         /*
213          * @see org.eclipse.jdt.ui.text.spelling.engine.ISpellChecker#checkWord(java.lang.String)
214          */
215         public final void checkWord(final String word) {
216                 // synchronizing is necessary as this is a write access
217                 fIgnored.remove(word.toLowerCase());
218         }
219
220         /*
221          * @see org.eclipse.spelling.done.ISpellChecker#execute(org.eclipse.spelling.ISpellCheckTokenizer)
222          */
223         public void execute(final ISpellCheckIterator iterator) {
224
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);
230
231                 String word= null;
232                 boolean starts= false;
233
234                 while (iterator.hasNext()) {
235
236                         word= (String)iterator.next();
237                         if (word != null) {
238
239                                 // synchronizing is necessary as this is called inside the reconciler
240                                 if (!fIgnored.contains(word)) {
241
242                                         starts= iterator.startsSentence();
243                                         if (!isCorrect(word)) {
244                                             
245                                             boolean isMixed=  isMixedCase(word, true);
246                                             boolean isUpper= isUpperCase(word);
247                                             boolean isDigits= isDigits(word);
248                                             boolean isURL= isUrl(word);
249                         
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));
252
253                                         } else {
254
255                                                 if (!ignoreSentence && starts && Character.isLowerCase(word.charAt(0)))
256                                                         fireEvent(new SpellEvent(this, word, iterator.getBegin(), iterator.getEnd(), true, true));
257                                         }
258                                 }
259                         }
260                 }
261         }
262
263         /**
264          * Fires the specified event.
265          * 
266          * @param event
267          *                   Event to fire
268          */
269         protected final void fireEvent(final ISpellEvent event) {
270                 // synchronizing is necessary as this is called from execute
271                 Set copy;
272                 synchronized (fListeners) {
273                         copy= new HashSet(fListeners);
274                 }
275                 for (final Iterator iterator= copy.iterator(); iterator.hasNext();) {
276                         ((ISpellEventListener)iterator.next()).handle(event);
277                 }
278         }
279
280         /*
281          * @see org.eclipse.spelling.done.ISpellChecker#getProposals(java.lang.String,boolean)
282          */
283         public Set getProposals(final String word, final boolean sentence) {
284                 
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 (?)
288                 Set copy;
289                 synchronized (fDictionaries) {
290                         copy= new HashSet(fDictionaries);
291                 }
292
293                 ISpellDictionary dictionary= null;
294                 final HashSet proposals= new HashSet();
295
296                 for (final Iterator iterator= copy.iterator(); iterator.hasNext();) {
297
298                         dictionary= (ISpellDictionary)iterator.next();
299                         proposals.addAll(dictionary.getProposals(word, sentence));
300                 }
301                 return proposals;
302         }
303
304         /*
305          * @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellChecker#ignoreWord(java.lang.String)
306          */
307         public final void ignoreWord(final String word) {
308                 // synchronizing is necessary as this is a write access
309                 fIgnored.add(word.toLowerCase());
310         }
311
312         /*
313          * @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellChecker#isCorrect(java.lang.String)
314          */
315         public final boolean isCorrect(final String word) {
316                 // synchronizing is necessary as this is called from execute
317                 Set copy;
318                 synchronized (fDictionaries) {
319                         copy= new HashSet(fDictionaries);
320                 }
321
322                 if (fIgnored.contains(word.toLowerCase()))
323                         return true;
324
325                 ISpellDictionary dictionary= null;
326                 for (final Iterator iterator= copy.iterator(); iterator.hasNext();) {
327
328                         dictionary= (ISpellDictionary)iterator.next();
329                         if (dictionary.isCorrect(word))
330                                 return true;
331                 }
332                 return false;
333         }
334
335         /*
336          * @see org.eclipse.spelling.done.ISpellChecker#removeDictionary(org.eclipse.spelling.done.ISpellDictionary)
337          */
338         public final void removeDictionary(final ISpellDictionary dictionary) {
339                 // synchronizing is necessary as this is a write access
340                 fDictionaries.remove(dictionary);
341         }
342
343         /*
344          * @see org.eclipse.spelling.done.ISpellChecker#removeListener(org.eclipse.spelling.done.ISpellEventListener)
345          */
346         public final void removeListener(final ISpellEventListener listener) {
347                 // synchronizing is necessary as this is a write access
348                 fListeners.remove(listener);
349         }
350 }