package com.psddev.cms.nlp;
import com.google.common.base.Preconditions;
import com.psddev.dari.util.ClassFinder;
import com.psddev.dari.util.Lazy;
import com.psddev.dari.util.TypeDefinition;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Spell checker.
*/
public interface SpellChecker {
/**
* Creates a list of dictionary names based on the given {@code baseName}
* and {@code locales}.
*
* <p>This method should be used by the implementation class to select
* the most appropriate dictionary using the same logic as resource
* bundle loading order.</p>
*
* @param baseName
* Can't be {@code null}.
*
* @param locale
* Can't be {@code null}.
*
* @return Never {@code null}.
*
* @see ResourceBundle.Control#getCandidateLocales(String, Locale)
*/
static List<String> createDictionaryNames(String baseName, Locale locale) {
Preconditions.checkNotNull(baseName);
Preconditions.checkNotNull(locale);
ResourceBundle.Control control = ResourceBundle.Control.getNoFallbackControl(ResourceBundle.Control.FORMAT_CLASS);
Stream<String> names = control
.getCandidateLocales(baseName, locale)
.stream()
.filter(l -> !Locale.ROOT.equals(l))
.map(l -> control.toBundleName(baseName, l));
if (baseName.length() == 0) {
names = names.map(name -> name.substring(1));
}
return names.collect(Collectors.toList());
}
/**
* Returns an instance that supports spell checking words in the given
* {@code locale}.
*
* @param locale
* Can't be {@code null}.
*
* @return {@code null} if no spell checker instance supports spell
* checking words in the given {@code locale}.
*/
static SpellChecker getInstance(Locale locale) {
Preconditions.checkNotNull(locale);
List<SpellChecker> instances = SpellCheckerPrivate.INSTANCES.get();
return instances.stream()
.filter(c -> c.isPreferred(locale))
.findFirst()
.orElseGet(() -> instances.stream()
.filter(c -> c.isSupported(locale))
.findFirst()
.orElse(null));
}
/**
* Returns {@code true} if this spell checker supports the given
* {@code locale}.
*
* @param locale
* Can't be {@code null}.
*/
boolean isSupported(Locale locale);
/**
* Returns {@code true} if this spell checker is preferred for the given
* {@code locale}.
*
* @param locale
* Can't be {@code null}.
*/
boolean isPreferred(Locale locale);
/**
* Suggests possible correct spellings of the given {@code word} in the
* given {@code locale}.
*
* @param locale
* Can't be {@code null}.
*
* @param word
* Can't be {@code null}.
*
* @return {@code null} if the given {@code word} is spelled correctly.
*/
List<String> suggest(Locale locale, String word);
}
class SpellCheckerPrivate {
public static final Lazy<List<SpellChecker>> INSTANCES = new Lazy<List<SpellChecker>>() {
@Override
protected List<SpellChecker> create() throws Exception {
return ClassFinder.findConcreteClasses(SpellChecker.class)
.stream()
.sorted(Comparator.comparing(Class::getName))
.map(c -> TypeDefinition.getInstance(c).newInstance())
.collect(Collectors.toList());
}
};
}