package org.jabref.logic.autocompleter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.jabref.logic.layout.format.LatexToUnicodeFormatter;
/**
* Delivers possible completions for a given string.
*
* @author kahlert, cordes, olly98
* @see AutoCompleterFactory
*/
public abstract class AbstractAutoCompleter implements AutoCompleter<String> {
private static final int SHORTEST_WORD_TO_ADD = 4;
private final AutoCompletePreferences preferences;
/**
* Stores the strings as is.
*/
private final TreeSet<String> indexCaseSensitive = new TreeSet<>();
/**
* Stores strings in lowercase.
*/
private final TreeSet<String> indexCaseInsensitive = new TreeSet<>();
/**
* Stores for a lowercase string the possible expanded strings.
*/
private final Map<String, Set<String>> possibleStringsForSearchString = new HashMap<>();
public AbstractAutoCompleter(AutoCompletePreferences preferences) {
this.preferences = Objects.requireNonNull(preferences);
}
/**
* {@inheritDoc}
* The completion is case sensitive if the string contains upper case letters.
* Otherwise the completion is case insensitive.
*/
@Override
public List<String> complete(String toComplete) {
if (toComplete == null) {
return new ArrayList<>();
}
if (isTooShortToComplete(toComplete)) {
return new ArrayList<>();
}
String lowerCase = toComplete.toLowerCase(Locale.ROOT);
if (lowerCase.equals(toComplete)) {
// user typed in lower case word -> we do an case-insensitive search
String ender = AbstractAutoCompleter.incrementLastCharacter(lowerCase);
SortedSet<String> subset = indexCaseInsensitive.subSet(lowerCase, ender);
// As subset only contains lower case strings,
// we have to to determine possible strings for each hit
List<String> result = new ArrayList<>();
for (String s : subset) {
result.addAll(possibleStringsForSearchString.get(s));
}
return result;
} else {
// user typed in a mix of upper case and lower case,
// we assume user wants to have exact search
String ender = AbstractAutoCompleter.incrementLastCharacter(toComplete);
SortedSet<String> subset = indexCaseSensitive.subSet(toComplete, ender);
return new ArrayList<>(subset);
}
}
/**
* Increments the last character of a string.
*
* Example: incrementLastCharacter("abc") returns "abd".
*/
private static String incrementLastCharacter(String toIncrement) {
if (toIncrement.isEmpty()) {
return "";
}
char lastChar = toIncrement.charAt(toIncrement.length() - 1);
return toIncrement.substring(0, toIncrement.length() - 1) + Character.toString((char) (lastChar + 1));
}
/**
* Returns whether the string is to short to be completed.
*/
private boolean isTooShortToComplete(String toCheck) {
return toCheck.length() < preferences.getShortestLengthToComplete();
}
@Override
public void addItemToIndex(String word) {
if (word.length() < getLengthOfShortestWordToAdd()) {
return;
}
word = new LatexToUnicodeFormatter().format(word);
indexCaseSensitive.add(word);
// insensitive treatment
// first, add the lower cased word to search index
// second, add a mapping from the lower cased word to the real word
String lowerCase = word.toLowerCase(Locale.ROOT);
indexCaseInsensitive.add(lowerCase);
Set<String> set = possibleStringsForSearchString.get(lowerCase);
if (set == null) {
set = new TreeSet<>();
}
set.add(word);
possibleStringsForSearchString.put(lowerCase, set);
}
@Override
public String getPrefix() {
return "";
}
@Override
public String getAutoCompleteText(String item) {
return item;
}
protected int getLengthOfShortestWordToAdd() {
return AbstractAutoCompleter.SHORTEST_WORD_TO_ADD;
}
}