/*******************************************************************************
* Copyright (c) 2000, 2008 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
* Aptana, Inc. - modifications
*******************************************************************************/
package com.aptana.internal.ui.text.spelling.engine;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.preference.IPreferenceStore;
import com.aptana.internal.ui.text.spelling.PreferenceConstants;
/**
* 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 preference store. Assumes the <code>IPreferenceStore</code>
* implementation is thread safe.
*/
private final IPreferenceStore fPreferences;
/**
* The locale of this checker.
*
* @since 3.3
*/
private final Locale fLocale;
/**
* Creates a new default spell checker.
*
* @param store
* the preference store for this spell checker
* @param locale
* the locale
*/
public DefaultSpellChecker(IPreferenceStore store, Locale locale) {
Assert.isLegal(store != null);
Assert.isLegal(locale != null);
this.fPreferences = store;
this.fLocale = locale;
}
/*
* @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
this.fDictionaries.add(dictionary);
}
/*
* @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 modifying methods add/checkWord (?)
Set copy;
synchronized (this.fDictionaries) {
copy = new HashSet(this.fDictionaries);
}
ISpellDictionary dictionary = null;
for (final Iterator iterator = copy.iterator(); iterator.hasNext();) {
dictionary = (ISpellDictionary) iterator.next();
if (dictionary.acceptsWords()) {
return true;
}
}
return false;
}
/*
* @see
* com.onpositive.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 (this.fDictionaries) {
copy = new HashSet(this.fDictionaries);
}
final String addable = word.toLowerCase();
for (final Iterator iterator = copy.iterator(); iterator.hasNext();) {
final ISpellDictionary dictionary = (ISpellDictionary) iterator
.next();
if (dictionary.acceptsWords()) {
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
this.fIgnored.remove(word.toLowerCase());
}
/*
* @see
* org.eclipse.spelling.done.ISpellChecker#execute(org.eclipse.spelling.
* ISpellCheckTokenizer)
*/
public void execute(final ISpellEventListener listener,
final ISpellCheckIterator iterator) {
final boolean ignoreDigits = fPreferences
.getBoolean(PreferenceConstants.SPELLING_IGNORE_DIGITS);
final boolean ignoreMixed = fPreferences
.getBoolean(PreferenceConstants.SPELLING_IGNORE_MIXED);
final boolean ignoreSentence = fPreferences
.getBoolean(PreferenceConstants.SPELLING_IGNORE_SENTENCE);
final boolean ignoreUpper = fPreferences
.getBoolean(PreferenceConstants.SPELLING_IGNORE_UPPER);
final boolean ignoreURLS = fPreferences
.getBoolean(PreferenceConstants.SPELLING_IGNORE_URLS);
final boolean ignoreNonLetters = fPreferences
.getBoolean(PreferenceConstants.SPELLING_IGNORE_NON_LETTERS);
final boolean ignoreSingleLetters = fPreferences
.getBoolean(PreferenceConstants.SPELLING_IGNORE_SINGLE_LETTERS);
final int problemsThreshold = PreferenceConstants.getPreferenceStore()
.getInt(PreferenceConstants.SPELLING_PROBLEMS_THRESHOLD);
iterator.setIgnoreSingleLetters(ignoreSingleLetters);
final Iterator iter = this.fDictionaries.iterator();
while (iter.hasNext()) {
((ISpellDictionary) iter.next())
.setStripNonLetters(ignoreNonLetters);
}
String word = null;
boolean starts = false;
int problemCount = 0;
while ((problemCount <= problemsThreshold) && iterator.hasNext()) {
word = (String) iterator.next();
if (word != null) {
// synchronizing is necessary as this is called inside the
// reconciler
if (!this.fIgnored.contains(word)) {
starts = iterator.startsSentence();
if (!this.isCorrect(word)) {
final boolean isMixed = isMixedCase(word, true);
final boolean isUpper = isUpperCase(word);
final boolean isDigits = isDigits(word);
final boolean isURL = isUrl(word);
if ((!ignoreMixed && isMixed)
|| (!ignoreUpper && isUpper)
|| (!ignoreDigits && isDigits)
|| (!ignoreURLS && isURL)
|| !(isMixed || isUpper || isDigits || isURL)) {
listener.handle(new SpellEvent(this, word, iterator
.getBegin(), iterator.getEnd(), starts,
false));
problemCount++;
}
} else {
if (!ignoreSentence && starts
&& Character.isLowerCase(word.charAt(0))) {
listener
.handle(new SpellEvent(this, word, iterator
.getBegin(), iterator.getEnd(),
true, true));
problemCount++;
}
}
}
}
}
}
/*
* @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 (this.fDictionaries) {
copy = new HashSet(this.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;
}
public Set getCompletionProposals(final String word, 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 (this.fDictionaries) {
copy = new HashSet(this.fDictionaries);
}
ISpellDictionary dictionary = null;
final HashSet proposals = new HashSet();
for (final Iterator iterator = copy.iterator(); iterator.hasNext();) {
dictionary = (ISpellDictionary) iterator.next();
if (dictionary instanceof AbstractSpellDictionary) {
final AbstractSpellDictionary sp = (AbstractSpellDictionary) dictionary;
proposals.addAll(sp.getCompletionProposals(word, sentence));
} else {
proposals.addAll(dictionary.getProposals(word, sentence));
}
}
return proposals;
}
/*
* @see
* com.onpositive.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
this.fIgnored.add(word.toLowerCase());
}
/*
* @see
* com.onpositive.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 (this.fDictionaries) {
copy = new HashSet(this.fDictionaries);
}
if (this.fIgnored.contains(word.toLowerCase())) {
return true;
}
if (word.indexOf('@')!=-1){
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
this.fDictionaries.remove(dictionary);
}
/*
* @see
* com.onpositive.internal.ui.text.spelling.engine.ISpellChecker#getLocale()
*
* @since 3.3
*/
public Locale getLocale() {
return this.fLocale;
}
}