]>
Commit | Line | Data |
---|---|---|
1b2798f6 EK |
1 | /******************************************************************************* |
2 | * Copyright (c) 2000, 2011 IBM Corporation and others. | |
3 | * All rights reserved. This program and the accompanying materials | |
4 | * are made available under the terms of the Eclipse Public License v1.0 | |
5 | * which accompanies this distribution, and is available at | |
6 | * http://www.eclipse.org/legal/epl-v10.html | |
7 | * | |
8 | * Contributors: | |
9 | * IBM Corporation - initial API and implementation | |
10 | *******************************************************************************/ | |
11 | package org.eclipse.jdt.internal.ui.text.spelling.engine; | |
12 | ||
13 | import java.util.Collections; | |
14 | import java.util.HashSet; | |
15 | import java.util.Iterator; | |
16 | import java.util.Locale; | |
17 | import java.util.Set; | |
18 | ||
19 | import org.eclipse.core.runtime.Assert; | |
20 | ||
21 | import org.eclipse.jface.preference.IPreferenceStore; | |
22 | ||
23 | import org.eclipse.jdt.ui.PreferenceConstants; | |
24 | ||
25 | ||
26 | /** | |
27 | * Default spell checker for standard text. | |
28 | * | |
29 | * @since 3.0 | |
30 | */ | |
31 | public class DefaultSpellChecker implements ISpellChecker { | |
32 | ||
33 | /** Array of URL prefixes */ | |
34 | 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$ | |
35 | ||
36 | /** | |
37 | * Does this word contain digits? | |
38 | * | |
39 | * @param word the word to check | |
40 | * @return <code>true</code> iff this word contains digits, <code>false></code> otherwise | |
41 | */ | |
42 | protected static boolean isDigits(final String word) { | |
43 | ||
44 | for (int index= 0; index < word.length(); index++) { | |
45 | ||
46 | if (Character.isDigit(word.charAt(index))) | |
47 | return true; | |
48 | } | |
49 | return false; | |
50 | } | |
51 | ||
52 | /** | |
53 | * Does this word contain mixed-case letters? | |
54 | * | |
55 | * @param word | |
56 | * The word to check | |
57 | * @param sentence | |
58 | * <code>true</code> iff the specified word starts a new | |
59 | * sentence, <code>false</code> otherwise | |
60 | * @return <code>true</code> iff the contains mixed-case letters, <code>false</code> | |
61 | * otherwise | |
62 | */ | |
63 | protected static boolean isMixedCase(final String word, final boolean sentence) { | |
64 | ||
65 | final int length= word.length(); | |
66 | boolean upper= Character.isUpperCase(word.charAt(0)); | |
67 | ||
68 | if (sentence && upper && (length > 1)) | |
69 | upper= Character.isUpperCase(word.charAt(1)); | |
70 | ||
71 | if (upper) { | |
72 | ||
73 | for (int index= length - 1; index > 0; index--) { | |
74 | if (Character.isLowerCase(word.charAt(index))) | |
75 | return true; | |
76 | } | |
77 | } else { | |
78 | ||
79 | for (int index= length - 1; index > 0; index--) { | |
80 | if (Character.isUpperCase(word.charAt(index))) | |
81 | return true; | |
82 | } | |
83 | } | |
84 | return false; | |
85 | } | |
86 | ||
87 | /** | |
88 | * Does this word contain upper-case letters only? | |
89 | * | |
90 | * @param word | |
91 | * The word to check | |
92 | * @return <code>true</code> iff this word only contains upper-case | |
93 | * letters, <code>false</code> otherwise | |
94 | */ | |
95 | protected static boolean isUpperCase(final String word) { | |
96 | ||
97 | for (int index= word.length() - 1; index >= 0; index--) { | |
98 | ||
99 | if (Character.isLowerCase(word.charAt(index))) | |
100 | return false; | |
101 | } | |
102 | return true; | |
103 | } | |
104 | ||
105 | /** | |
106 | * Does this word look like an URL? | |
107 | * | |
108 | * @param word | |
109 | * The word to check | |
110 | * @return <code>true</code> iff this word looks like an URL, <code>false</code> | |
111 | * otherwise | |
112 | */ | |
113 | protected static boolean isUrl(final String word) { | |
114 | ||
115 | for (int index= 0; index < URL_PREFIXES.length; index++) { | |
116 | ||
117 | if (word.startsWith(URL_PREFIXES[index])) | |
118 | return true; | |
119 | } | |
120 | return false; | |
121 | } | |
122 | ||
123 | /** | |
124 | * The dictionaries to use for spell checking. Synchronized to avoid | |
125 | * concurrent modifications. | |
126 | */ | |
127 | private final Set<ISpellDictionary> fDictionaries= Collections.synchronizedSet(new HashSet<ISpellDictionary>()); | |
128 | ||
129 | /** | |
130 | * The words to be ignored. Synchronized to avoid concurrent modifications. | |
131 | */ | |
132 | private final Set<String> fIgnored= Collections.synchronizedSet(new HashSet<String>()); | |
133 | ||
134 | /** | |
135 | * The preference store. Assumes the <code>IPreferenceStore</code> | |
136 | * implementation is thread safe. | |
137 | */ | |
138 | private final IPreferenceStore fPreferences; | |
139 | ||
140 | /** | |
141 | * The locale of this checker. | |
142 | * @since 3.3 | |
143 | */ | |
144 | private Locale fLocale; | |
145 | ||
146 | /** | |
147 | * Creates a new default spell checker. | |
148 | * | |
149 | * @param store the preference store for this spell checker | |
150 | * @param locale the locale | |
151 | */ | |
152 | public DefaultSpellChecker(IPreferenceStore store, Locale locale) { | |
153 | Assert.isLegal(store != null); | |
154 | Assert.isLegal(locale != null); | |
155 | ||
156 | fPreferences= store; | |
157 | fLocale= locale; | |
158 | } | |
159 | ||
160 | /* | |
161 | * @see org.eclipse.spelling.done.ISpellChecker#addDictionary(org.eclipse.spelling.done.ISpellDictionary) | |
162 | */ | |
163 | public final void addDictionary(final ISpellDictionary dictionary) { | |
164 | // synchronizing is necessary as this is a write access | |
165 | fDictionaries.add(dictionary); | |
166 | } | |
167 | ||
168 | /* | |
169 | * @see org.eclipse.jdt.ui.text.spelling.engine.ISpellChecker#acceptsWords() | |
170 | */ | |
171 | public boolean acceptsWords() { | |
172 | // synchronizing might not be needed here since acceptWords is | |
173 | // a read-only access and only called in the same thread as | |
174 | // the modifying methods add/checkWord (?) | |
175 | Set<ISpellDictionary> copy; | |
176 | synchronized (fDictionaries) { | |
177 | copy= new HashSet<ISpellDictionary>(fDictionaries); | |
178 | } | |
179 | ||
180 | ISpellDictionary dictionary= null; | |
181 | for (final Iterator<ISpellDictionary> iterator= copy.iterator(); iterator.hasNext();) { | |
182 | ||
183 | dictionary= iterator.next(); | |
184 | if (dictionary.acceptsWords()) | |
185 | return true; | |
186 | } | |
187 | return false; | |
188 | } | |
189 | ||
190 | /* | |
191 | * @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellChecker#addWord(java.lang.String) | |
192 | */ | |
193 | public void addWord(final String word) { | |
194 | // synchronizing is necessary as this is a write access | |
195 | Set<ISpellDictionary> copy; | |
196 | synchronized (fDictionaries) { | |
197 | copy= new HashSet<ISpellDictionary>(fDictionaries); | |
198 | } | |
199 | ||
200 | final String addable= word.toLowerCase(); | |
201 | for (final Iterator<ISpellDictionary> iterator= copy.iterator(); iterator.hasNext();) { | |
202 | ISpellDictionary dictionary= iterator.next(); | |
203 | if (dictionary.acceptsWords()) | |
204 | dictionary.addWord(addable); | |
205 | } | |
206 | ||
207 | } | |
208 | ||
209 | /* | |
210 | * @see org.eclipse.jdt.ui.text.spelling.engine.ISpellChecker#checkWord(java.lang.String) | |
211 | */ | |
212 | public final void checkWord(final String word) { | |
213 | // synchronizing is necessary as this is a write access | |
214 | fIgnored.remove(word.toLowerCase()); | |
215 | } | |
216 | ||
217 | /* | |
218 | * @see org.eclipse.spelling.done.ISpellChecker#execute(org.eclipse.spelling.ISpellCheckTokenizer) | |
219 | */ | |
220 | public void execute(final ISpellEventListener listener, final ISpellCheckIterator iterator) { | |
221 | ||
222 | final boolean ignoreDigits= fPreferences.getBoolean(PreferenceConstants.SPELLING_IGNORE_DIGITS); | |
223 | final boolean ignoreMixed= fPreferences.getBoolean(PreferenceConstants.SPELLING_IGNORE_MIXED); | |
224 | final boolean ignoreSentence= fPreferences.getBoolean(PreferenceConstants.SPELLING_IGNORE_SENTENCE); | |
225 | final boolean ignoreUpper= fPreferences.getBoolean(PreferenceConstants.SPELLING_IGNORE_UPPER); | |
226 | final boolean ignoreURLS= fPreferences.getBoolean(PreferenceConstants.SPELLING_IGNORE_URLS); | |
227 | final boolean ignoreNonLetters= fPreferences.getBoolean(PreferenceConstants.SPELLING_IGNORE_NON_LETTERS); | |
228 | final boolean ignoreSingleLetters= fPreferences.getBoolean(PreferenceConstants.SPELLING_IGNORE_SINGLE_LETTERS); | |
229 | final int problemsThreshold= PreferenceConstants.getPreferenceStore().getInt(PreferenceConstants.SPELLING_PROBLEMS_THRESHOLD); | |
230 | ||
231 | iterator.setIgnoreSingleLetters(ignoreSingleLetters); | |
232 | ||
233 | Iterator<ISpellDictionary> iter= fDictionaries.iterator(); | |
234 | while (iter.hasNext()) | |
235 | iter.next().setStripNonLetters(ignoreNonLetters); | |
236 | ||
237 | String word= null; | |
238 | boolean starts= false; | |
239 | int problemCount= 0; | |
240 | ||
241 | while (problemCount <= problemsThreshold && iterator.hasNext()) { | |
242 | ||
243 | word= iterator.next(); | |
244 | if (word != null) { | |
245 | ||
246 | // synchronizing is necessary as this is called inside the reconciler | |
247 | if (!fIgnored.contains(word)) { | |
248 | ||
249 | starts= iterator.startsSentence(); | |
250 | if (!isCorrect(word)) { | |
251 | ||
252 | boolean isMixed= isMixedCase(word, true); | |
253 | boolean isUpper= isUpperCase(word); | |
254 | boolean isDigits= isDigits(word); | |
255 | boolean isURL= isUrl(word); | |
256 | ||
257 | if ( !ignoreMixed && isMixed || !ignoreUpper && isUpper || !ignoreDigits && isDigits || !ignoreURLS && isURL || !(isMixed || isUpper || isDigits || isURL)) { | |
258 | listener.handle(new SpellEvent(this, word, iterator.getBegin(), iterator.getEnd(), starts, false)); | |
259 | problemCount++; | |
260 | } | |
261 | ||
262 | } else { | |
263 | ||
264 | if (!ignoreSentence && starts && Character.isLowerCase(word.charAt(0))) { | |
265 | listener.handle(new SpellEvent(this, word, iterator.getBegin(), iterator.getEnd(), true, true)); | |
266 | problemCount++; | |
267 | } | |
268 | } | |
269 | } | |
270 | } | |
271 | } | |
272 | } | |
273 | ||
274 | /* | |
275 | * @see org.eclipse.spelling.done.ISpellChecker#getProposals(java.lang.String,boolean) | |
276 | */ | |
277 | public Set<RankedWordProposal> getProposals(final String word, final boolean sentence) { | |
278 | ||
279 | // synchronizing might not be needed here since getProposals is | |
280 | // a read-only access and only called in the same thread as | |
281 | // the modifing methods add/removeDictionary (?) | |
282 | Set<ISpellDictionary> copy; | |
283 | synchronized (fDictionaries) { | |
284 | copy= new HashSet<ISpellDictionary>(fDictionaries); | |
285 | } | |
286 | ||
287 | ISpellDictionary dictionary= null; | |
288 | final HashSet<RankedWordProposal> proposals= new HashSet<RankedWordProposal>(); | |
289 | ||
290 | for (final Iterator<ISpellDictionary> iterator= copy.iterator(); iterator.hasNext();) { | |
291 | ||
292 | dictionary= iterator.next(); | |
293 | proposals.addAll(dictionary.getProposals(word, sentence)); | |
294 | } | |
295 | return proposals; | |
296 | } | |
297 | ||
298 | /* | |
299 | * @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellChecker#ignoreWord(java.lang.String) | |
300 | */ | |
301 | public final void ignoreWord(final String word) { | |
302 | // synchronizing is necessary as this is a write access | |
303 | fIgnored.add(word.toLowerCase()); | |
304 | } | |
305 | ||
306 | /* | |
307 | * @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellChecker#isCorrect(java.lang.String) | |
308 | */ | |
309 | public final boolean isCorrect(final String word) { | |
310 | // synchronizing is necessary as this is called from execute | |
311 | Set<ISpellDictionary> copy; | |
312 | synchronized (fDictionaries) { | |
313 | copy= new HashSet<ISpellDictionary>(fDictionaries); | |
314 | } | |
315 | ||
316 | if (fIgnored.contains(word.toLowerCase())) | |
317 | return true; | |
318 | ||
319 | ISpellDictionary dictionary= null; | |
320 | for (final Iterator<ISpellDictionary> iterator= copy.iterator(); iterator.hasNext();) { | |
321 | ||
322 | dictionary= iterator.next(); | |
323 | if (dictionary.isCorrect(word)) | |
324 | return true; | |
325 | } | |
326 | return false; | |
327 | } | |
328 | ||
329 | /* | |
330 | * @see org.eclipse.spelling.done.ISpellChecker#removeDictionary(org.eclipse.spelling.done.ISpellDictionary) | |
331 | */ | |
332 | public final void removeDictionary(final ISpellDictionary dictionary) { | |
333 | // synchronizing is necessary as this is a write access | |
334 | fDictionaries.remove(dictionary); | |
335 | } | |
336 | ||
337 | /* | |
338 | * @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellChecker#getLocale() | |
339 | * @since 3.3 | |
340 | */ | |
341 | public Locale getLocale() { | |
342 | return fLocale; | |
343 | } | |
344 | } |