blob: 77c17387402b6e1fdd6b0731522c03ff0de29c50 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2011 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;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.core.variables.IStringVariableManager;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.texteditor.spelling.SpellingService;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.text.spelling.engine.DefaultSpellChecker;
import org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellCheckEngine;
import org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellChecker;
import org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellDictionary;
import org.eclipse.jdt.internal.ui.text.spelling.engine.LocaleSensitiveSpellDictionary;
import org.eclipse.jdt.internal.ui.text.spelling.engine.PersistentSpellDictionary;
/**
* Spell check engine for Java source spell checking.
*
* @since 3.0
*/
public class SpellCheckEngine implements ISpellCheckEngine, IPropertyChangeListener {
/** The dictionary location */
public static final String DICTIONARY_LOCATION= "dictionaries/"; //$NON-NLS-1$
/** The singleton engine instance */
private static ISpellCheckEngine fgEngine= null;
/**
* Caches the locales of installed dictionaries.
*
* @since 3.3
*/
private static Set<Locale> fgLocalesWithInstalledDictionaries;
/**
* Returns the locales for which this
* spell check engine has dictionaries in certain location.
*
* @param location dictionaries location
* @return The available locales for this engine
*/
private static Set<Locale> getLocalesWithInstalledDictionaries(URL location) {
String[] fileNames;
try {
URL url= FileLocator.toFileURL(location);
File file= new File(url.getFile());
if (!file.isDirectory())
return Collections.emptySet();
fileNames= file.list();
if (fileNames == null)
return Collections.emptySet();
} catch (IOException ex) {
JavaPlugin.log(ex);
return Collections.emptySet();
}
Set<Locale> localesWithInstalledDictionaries= new HashSet<Locale>();
int fileNameCount= fileNames.length;
for (int i= 0; i < fileNameCount; i++) {
String fileName= fileNames[i];
int localeEnd= fileName.indexOf(".dictionary"); //$NON-NLS-1$
if (localeEnd > 1) {
String localeName= fileName.substring(0, localeEnd);
int languageEnd=localeName.indexOf('_');
if (languageEnd == -1)
localesWithInstalledDictionaries.add(new Locale(localeName));
else if (languageEnd == 2 && localeName.length() == 5)
localesWithInstalledDictionaries.add(new Locale(localeName.substring(0, 2), localeName.substring(3)));
else if (localeName.length() > 6 && localeName.charAt(5) == '_')
localesWithInstalledDictionaries.add(new Locale(localeName.substring(0, 2), localeName.substring(3, 5), localeName.substring(6)));
}
}
return localesWithInstalledDictionaries;
}
/**
* Returns the locales for which this
* spell check engine has dictionaries.
*
* @return The available locales for this engine
*/
public static Set<Locale> getLocalesWithInstalledDictionaries() {
if (fgLocalesWithInstalledDictionaries != null)
return fgLocalesWithInstalledDictionaries;
Enumeration<URL> locations;
try {
locations= getDictionaryLocations();
if (locations == null)
return fgLocalesWithInstalledDictionaries= Collections.emptySet();
} catch (IOException ex) {
JavaPlugin.log(ex);
return fgLocalesWithInstalledDictionaries= Collections.emptySet();
}
fgLocalesWithInstalledDictionaries= new HashSet<Locale>();
while (locations.hasMoreElements()) {
URL location= locations.nextElement();
Set<Locale> locales= getLocalesWithInstalledDictionaries(location);
fgLocalesWithInstalledDictionaries.addAll(locales);
}
return fgLocalesWithInstalledDictionaries;
}
/**
* Returns the default locale for this engine.
*
* @return The default locale
*/
public static Locale getDefaultLocale() {
return Locale.getDefault();
}
/**
* Returns the dictionary closest to the given locale.
*
* @param locale the locale
* @return the dictionary or <code>null</code> if none is suitable
* @since 3.3
*/
public ISpellDictionary findDictionary(Locale locale) {
ISpellDictionary dictionary= fLocaleDictionaries.get(locale);
if (dictionary != null)
return dictionary;
// Try same language
String language= locale.getLanguage();
Iterator<Entry<Locale, ISpellDictionary>> iter= fLocaleDictionaries.entrySet().iterator();
while (iter.hasNext()) {
Entry<Locale, ISpellDictionary> entry= iter.next();
Locale dictLocale= entry.getKey();
if (dictLocale.getLanguage().equals(language))
return entry.getValue();
}
return null;
}
/*
* @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellCheckEngine#findDictionary(java.util.Locale)
* @since 3.3
*/
public static Locale findClosestLocale(Locale locale) {
if (locale == null || locale.toString().length() == 0)
return locale;
if (getLocalesWithInstalledDictionaries().contains(locale))
return locale;
// Try same language
String language= locale.getLanguage();
Iterator<Locale> iter= getLocalesWithInstalledDictionaries().iterator();
while (iter.hasNext()) {
Locale dictLocale= iter.next();
if (dictLocale.getLanguage().equals(language))
return dictLocale;
}
// Try whether American English is present
Locale defaultLocale= Locale.US;
if (getLocalesWithInstalledDictionaries().contains(defaultLocale))
return defaultLocale;
return null;
}
/**
* Returns the enumeration of URLs for the dictionary locations where
* the Platform dictionaries are located.
* <p>
* This is in <code>org.eclipse.jdt.ui/dictionaries/</code>
* which can also be populated via fragments.
* </p>
*
* @throws IOException if there is an I/O error
* @return The dictionary locations, or <code>null</code> iff the locations are not known
*/
public static Enumeration<URL> getDictionaryLocations() throws IOException {
final JavaPlugin plugin= JavaPlugin.getDefault();
if (plugin != null)
return plugin.getBundle().getResources("/" + DICTIONARY_LOCATION); //$NON-NLS-1$
return null;
}
/**
* Returns the singleton instance of the spell check engine.
*
* @return The singleton instance of the spell check engine
*/
public static synchronized final ISpellCheckEngine getInstance() {
if (fgEngine == null)
fgEngine= new SpellCheckEngine();
return fgEngine;
}
/**
* Shuts down the singleton instance of the spell check engine.
*/
public static synchronized final void shutdownInstance() {
if (fgEngine != null) {
fgEngine.shutdown();
fgEngine= null;
}
}
/** The registered locale insensitive dictionaries */
private Set<ISpellDictionary> fGlobalDictionaries= new HashSet<ISpellDictionary>();
/** The spell checker for fLocale */
private ISpellChecker fChecker= null;
/** The registered locale sensitive dictionaries */
private Map<Locale, ISpellDictionary> fLocaleDictionaries= new HashMap<Locale, ISpellDictionary>();
/** The user dictionary */
private ISpellDictionary fUserDictionary= null;
/**
* Creates a new spell check manager.
*/
private SpellCheckEngine() {
fGlobalDictionaries.add(new TaskTagDictionary());
fGlobalDictionaries.add(new HtmlTagDictionary());
fGlobalDictionaries.add(new JavaDocTagDictionary());
try {
Locale locale= null;
final Enumeration<URL> locations= getDictionaryLocations();
while (locations != null && locations.hasMoreElements()) {
URL location= locations.nextElement();
for (final Iterator<Locale> iterator= getLocalesWithInstalledDictionaries(location).iterator(); iterator.hasNext();) {
locale= iterator.next();
fLocaleDictionaries.put(locale, new LocaleSensitiveSpellDictionary(locale, location));
}
}
} catch (IOException exception) {
// Do nothing
}
JavaPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
EditorsUI.getPreferenceStore().addPropertyChangeListener(this);
}
/*
* @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellCheckEngine#getSpellChecker()
*/
public synchronized final ISpellChecker getSpellChecker() throws IllegalStateException {
if (fGlobalDictionaries == null)
throw new IllegalStateException("spell checker has been shut down"); //$NON-NLS-1$
IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore();
Locale locale= getCurrentLocale(store);
if (fUserDictionary == null && "".equals(locale.toString())) //$NON-NLS-1$
return null;
if (fChecker != null && fChecker.getLocale().equals(locale))
return fChecker;
resetSpellChecker();
fChecker= new DefaultSpellChecker(store, locale);
resetUserDictionary();
for (Iterator<ISpellDictionary> iterator= fGlobalDictionaries.iterator(); iterator.hasNext();) {
ISpellDictionary dictionary= iterator.next();
fChecker.addDictionary(dictionary);
}
ISpellDictionary dictionary= findDictionary(fChecker.getLocale());
if (dictionary != null)
fChecker.addDictionary(dictionary);
return fChecker;
}
/**
* Returns the current locale of the spelling preferences.
*
* @param store the preference store
* @return The current locale of the spelling preferences
*/
private Locale getCurrentLocale(IPreferenceStore store) {
return convertToLocale(store.getString(PreferenceConstants.SPELLING_LOCALE));
}
public static Locale convertToLocale(String locale) {
Locale defaultLocale= SpellCheckEngine.getDefaultLocale();
if (locale.equals(defaultLocale.toString()))
return defaultLocale;
int length= locale.length();
if (length >= 5)
return new Locale(locale.substring(0, 2), locale.substring(3, 5));
if (length == 2 && locale.indexOf('_') == -1)
return new Locale(locale);
if (length == 3 && locale.charAt(0) == '_')
return new Locale("", locale.substring(1)); //$NON-NLS-1$
return new Locale(""); //$NON-NLS-1$
}
/*
* @see org.eclipse.jdt.ui.text.spelling.engine.ISpellCheckEngine#getLocale()
*/
public synchronized final Locale getLocale() {
if (fChecker == null)
return null;
return fChecker.getLocale();
}
/*
* @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
*/
public final void propertyChange(final PropertyChangeEvent event) {
if (event.getProperty().equals(PreferenceConstants.SPELLING_LOCALE)) {
resetSpellChecker();
return;
}
if (event.getProperty().equals(PreferenceConstants.SPELLING_USER_DICTIONARY)) {
resetUserDictionary();
return;
}
if (event.getProperty().equals(PreferenceConstants.SPELLING_USER_DICTIONARY_ENCODING)) {
resetUserDictionary();
return;
}
if (event.getProperty().equals(SpellingService.PREFERENCE_SPELLING_ENABLED) && !EditorsUI.getPreferenceStore().getBoolean(SpellingService.PREFERENCE_SPELLING_ENABLED)) {
if (this == fgEngine)
SpellCheckEngine.shutdownInstance();
else
shutdown();
}
}
/**
* Resets the current checker's user dictionary.
*/
private synchronized void resetUserDictionary() {
if (fChecker == null)
return;
// Update user dictionary
if (fUserDictionary != null) {
fChecker.removeDictionary(fUserDictionary);
fUserDictionary.unload();
fUserDictionary= null;
}
IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore();
String filePath= store.getString(PreferenceConstants.SPELLING_USER_DICTIONARY);
VariablesPlugin variablesPlugin= VariablesPlugin.getDefault();
if (variablesPlugin == null)
return;
IStringVariableManager variableManager= variablesPlugin.getStringVariableManager();
try {
filePath= variableManager.performStringSubstitution(filePath);
} catch (CoreException e) {
JavaPlugin.log(e);
return;
}
if (filePath.length() > 0) {
try {
File file= new File(filePath);
if (!file.exists() && !file.createNewFile())
return;
final URL url= new URL("file", null, filePath); //$NON-NLS-1$
InputStream stream= url.openStream();
if (stream != null) {
try {
fUserDictionary= new PersistentSpellDictionary(url);
fChecker.addDictionary(fUserDictionary);
} finally {
stream.close();
}
}
} catch (MalformedURLException exception) {
// Do nothing
} catch (IOException exception) {
// Do nothing
}
}
}
/*
* @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellCheckEngine#registerDictionary(org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellDictionary)
*/
public synchronized final void registerGlobalDictionary(final ISpellDictionary dictionary) {
fGlobalDictionaries.add(dictionary);
resetSpellChecker();
}
/*
* @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellCheckEngine#registerDictionary(java.util.Locale, org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellDictionary)
*/
public synchronized final void registerDictionary(final Locale locale, final ISpellDictionary dictionary) {
fLocaleDictionaries.put(locale, dictionary);
resetSpellChecker();
}
/*
* @see org.eclipse.jdt.internal.ui.text.spelling.engine.ISpellCheckEngine#unload()
*/
public synchronized final void shutdown() {
JavaPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this);
EditorsUI.getPreferenceStore().removePropertyChangeListener(this);
ISpellDictionary dictionary= null;
for (final Iterator<ISpellDictionary> iterator= fGlobalDictionaries.iterator(); iterator.hasNext();) {
dictionary= iterator.next();
dictionary.unload();
}
fGlobalDictionaries= null;
for (final Iterator<ISpellDictionary> iterator= fLocaleDictionaries.values().iterator(); iterator.hasNext();) {
dictionary= iterator.next();
dictionary.unload();
}
fLocaleDictionaries= null;
fUserDictionary= null;
fChecker= null;
}
private synchronized void resetSpellChecker() {
if (fChecker != null) {
ISpellDictionary dictionary= fLocaleDictionaries.get(fChecker.getLocale());
if (dictionary != null)
dictionary.unload();
}
fChecker= null;
}
/*
* @see org.eclipse.jdt.ui.text.spelling.engine.ISpellCheckEngine#unregisterDictionary(org.eclipse.jdt.ui.text.spelling.engine.ISpellDictionary)
*/
public synchronized final void unregisterDictionary(final ISpellDictionary dictionary) {
fGlobalDictionaries.remove(dictionary);
fLocaleDictionaries.values().remove(dictionary);
dictionary.unload();
resetSpellChecker();
}
}