| /******************************************************************************* |
| * Copyright (c) 2000, 2005 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.jdt.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 org.eclipse.jdt.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 org.eclipse.jdt.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 org.eclipse.jdt.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 org.eclipse.jdt.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 org.eclipse.jdt.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); |
| } |
| } |