blob: d5a0674f8e4c1f3aefb0ce47aae5dea42a83b3f9 [file] [log] [blame]
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.spellchecker.engine;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.LevenshteinDistance;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.spellchecker.compress.CompressedDictionary;
import com.intellij.spellchecker.dictionary.Dictionary;
import com.intellij.spellchecker.dictionary.EditableDictionary;
import com.intellij.spellchecker.dictionary.EditableDictionaryLoader;
import com.intellij.spellchecker.dictionary.Loader;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
public class BaseSpellChecker implements SpellCheckerEngine {
static final Logger LOG = Logger.getInstance("#com.intellij.spellchecker.engine.BaseSpellChecker");
private final Transformation transform = new Transformation();
private final Set<EditableDictionary> dictionaries = new HashSet<EditableDictionary>();
private final List<Dictionary> bundledDictionaries = ContainerUtil.createLockFreeCopyOnWriteList();
private final LevenshteinDistance metrics = new LevenshteinDistance();
private final AtomicBoolean myLoadingDictionaries = new AtomicBoolean(false);
private final List<Pair<Loader, Consumer<Dictionary>>> myDictionariesToLoad = ContainerUtil.createLockFreeCopyOnWriteList();
private final Project myProject;
public BaseSpellChecker(final Project project) {
myProject = project;
}
@Override
public void loadDictionary(@NotNull Loader loader) {
if (loader instanceof EditableDictionaryLoader) {
final EditableDictionary dictionary = ((EditableDictionaryLoader)loader).getDictionary();
if (dictionary != null) {
addModifiableDictionary(dictionary);
}
}
else {
loadCompressedDictionary(loader);
}
}
private void loadCompressedDictionary(@NotNull Loader loader) {
if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment()) {
final CompressedDictionary dictionary = CompressedDictionary.create(loader, transform);
addCompressedFixedDictionary(dictionary);
}
else {
loadDictionaryAsync(loader, new Consumer<Dictionary>() {
@Override
public void consume(final Dictionary dictionary) {
addCompressedFixedDictionary(dictionary);
}
});
}
}
private void loadDictionaryAsync(@NotNull final Loader loader, @NotNull final Consumer<Dictionary> consumer) {
if (myLoadingDictionaries.compareAndSet(false, true)) {
LOG.debug("Loading " + loader.getName());
_doLoadDictionaryAsync(loader, consumer);
}
else {
queueDictionaryLoad(loader, consumer);
}
}
private void _doLoadDictionaryAsync(final Loader loader, final Consumer<Dictionary> consumer) {
final Runnable runnable = new Runnable() {
@Override
public void run() {
LOG.debug("Loading " + loader.getName());
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
if (ApplicationManager.getApplication().isDisposed()) return;
final CompressedDictionary dictionary = CompressedDictionary.create(loader, transform);
LOG.debug(loader.getName() + " loaded!");
consumer.consume(dictionary);
while (!myDictionariesToLoad.isEmpty()) {
if (ApplicationManager.getApplication().isDisposed()) return;
final Pair<Loader, Consumer<Dictionary>> nextDictionary = myDictionariesToLoad.remove(0);
Loader nextDictionaryLoader = nextDictionary.getFirst();
CompressedDictionary dictionary1 = CompressedDictionary.create(nextDictionaryLoader, transform);
LOG.debug(nextDictionaryLoader.getName() + " loaded!");
nextDictionary.getSecond().consume(dictionary1);
}
LOG.debug("Loading finished, restarting daemon...");
myLoadingDictionaries.set(false);
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
if (ApplicationManager.getApplication().isDisposed()) return;
for (final Project project : ProjectManager.getInstance().getOpenProjects()) {
if (project.isInitialized() && project.isOpen() && !project.isDefault()) {
final DaemonCodeAnalyzer instance = DaemonCodeAnalyzer.getInstance(project);
if (instance != null) instance.restart();
}
}
}
});
}
});
}
};
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(runnable);
}
private void queueDictionaryLoad(final Loader loader, final Consumer<Dictionary> consumer) {
LOG.debug("Queuing load for: " + loader.getName());
myDictionariesToLoad.add(Pair.create(loader, consumer));
}
private void addModifiableDictionary(@NotNull EditableDictionary dictionary) {
dictionaries.add(dictionary);
}
private void addCompressedFixedDictionary(@NotNull Dictionary dictionary) {
bundledDictionaries.add(dictionary);
}
@Override
public Transformation getTransformation() {
return transform;
}
@NotNull
private static List<String> restore(char startFrom, int i, int j, @NotNull Collection<? extends Dictionary> dictionaries) {
List<String> results = new ArrayList<String>();
for (Dictionary o : dictionaries) {
results.addAll(restore(startFrom, i, j, o));
}
return results;
}
@NotNull
private static List<String> restore(final char first, final int i, final int j, @NotNull Dictionary dictionary) {
final List<String> result = new ArrayList<String>();
if (dictionary instanceof CompressedDictionary) {
result.addAll(((CompressedDictionary)dictionary).getWords(first, i, j));
}
else {
dictionary.traverse(new Consumer<String>() {
@Override
public void consume(String s) {
if (StringUtil.isEmpty(s)) {
return;
}
if (s.charAt(0) == first && s.length() >= i && s.length() <= j) {
result.add(s);
}
}
});
}
return result;
}
/**
* @param transformed
* @param dictionaries
* @return -1 (all)failed / 0 (any) ok / >0 all alien
*/
private static int isCorrect(@NotNull String transformed, @Nullable Collection<? extends Dictionary> dictionaries) {
if (dictionaries == null) {
return -1;
}
//System.out.println("dictionaries = " + dictionaries);
int errors = 0;
for (Dictionary dictionary : dictionaries) {
if (dictionary == null) continue;
//System.out.print("\tBSC.isCorrect " + transformed + " " + dictionary);
Boolean contains = dictionary.contains(transformed);
//System.out.println("\tcontains = " + contains);
if (contains==null) ++errors;
else if (contains) return 0;
}
if (errors == dictionaries.size()) return errors;
return -1;
}
@Override
public boolean isCorrect(@NotNull String word) {
//System.out.println("---\n"+word);
final String transformed = transform.transform(word);
if (myLoadingDictionaries.get() || transformed == null) {
return true;
}
int bundled = isCorrect(transformed, bundledDictionaries);
int user = isCorrect(transformed, dictionaries);
//System.out.println("bundled = " + bundled);
//System.out.println("user = " + user);
return bundled == 0 || user == 0 || bundled > 0 && user > 0;
}
@Override
@NotNull
public List<String> getSuggestions(@NotNull final String word, int threshold, int quality) {
final String transformed = transform.transform(word);
if (transformed == null) {
return Collections.emptyList();
}
final List<Suggestion> suggestions = new ArrayList<Suggestion>();
List<String> rawSuggestions = restore(transformed.charAt(0), 0, Integer.MAX_VALUE, bundledDictionaries);
rawSuggestions.addAll(restore(word.charAt(0), 0, Integer.MAX_VALUE, dictionaries));
for (String rawSuggestion : rawSuggestions) {
final int distance = metrics.calculateMetrics(transformed, rawSuggestion);
suggestions.add(new Suggestion(rawSuggestion, distance));
}
List<String> result = new ArrayList<String>();
if (suggestions.isEmpty()) {
return result;
}
Collections.sort(suggestions);
int bestMetrics = suggestions.get(0).getMetrics();
for (int i = 0; i < threshold; i++) {
if (suggestions.size() <= i || bestMetrics - suggestions.get(i).getMetrics() > quality) {
break;
}
result.add(i, suggestions.get(i).getWord());
}
return result;
}
@Override
@NotNull
public List<String> getVariants(@NotNull String prefix) {
//if (StringUtil.isEmpty(prefix)) {
return Collections.emptyList();
//}
}
@Override
public void reset() {
bundledDictionaries.clear();
dictionaries.clear();
}
@Override
public boolean isDictionaryLoad(@NotNull String name) {
return getBundledDictionaryByName(name) != null;
}
@Override
public void removeDictionary(@NotNull String name) {
final Dictionary dictionaryByName = getBundledDictionaryByName(name);
if (dictionaryByName != null) {
bundledDictionaries.remove(dictionaryByName);
}
}
@Nullable
public Dictionary getBundledDictionaryByName(@NotNull String name) {
if (bundledDictionaries == null) {
return null;
}
for (Dictionary dictionary : bundledDictionaries) {
if (name.equals(dictionary.getName())) {
return dictionary;
}
}
return null;
}
}