/*************************************************************************** * Copyright 2006-2016 by Christian Ihle * * contact@kouchat.net * * * * This file is part of KouChat. * * * * KouChat is free software; you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation, either version 3 of * * the License, or (at your option) any later version. * * * * KouChat is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with KouChat. * * If not, see <http://www.gnu.org/licenses/>. * ***************************************************************************/ package net.usikkert.kouchat.autocomplete; import java.util.ArrayList; import java.util.List; /** * This class can give suggestions for autocompleting words. * * At least one {@link AutoCompleteList} with some words is needed * to get results. * * @author Christian Ihle */ public class AutoCompleter { /** * The word that was used to build autocomplete suggestions in the * previous search. */ private String lastWord; /** * The suggested autocompleted word from previous search. */ private String lastCompletedWord; /** * The full line of text from the previous search, with the autocompleted * word replacing the original word. */ private String lastCompletedLine; /** * The position in the {@link #lastCompletedLine} that marks the end * of the {@link #lastCompletedWord}. Useful for setting the caret * at the end of the autocompleted word. */ private int newCaretPosition; /** * A list of {@link AutoCompleteList}s with support for different * kinds of words to autocomplete. */ private final List<AutoCompleteList> autoCompleteLists; /** * Constructor. Initializes variables. */ public AutoCompleter() { lastCompletedLine = ""; lastCompletedWord = ""; lastWord = ""; autoCompleteLists = new ArrayList<AutoCompleteList>(); } /** * Extracts the word at the given position from the line, * and looks for suggestions for autocompleting the word. * <br><br> * The previous search is saved, so if there are more than one * suggestion, repeated calls to this method will give the next * result in the suggestion list. * * @param line The line of text where the word to autocomplete is. * @param caretPosition The position in the line to look for the word. * @return The complete line, where the original word is replaced by * the suggested autocompleted word. * Use {@link #getNewCaretPosition()} to get the new caret position. */ public String completeWord(final String line, final int caretPosition) { String completedLine = ""; if (autoCompleteLists.size() > 0) { final int stop = findStopPosition(line, caretPosition); final int start = findStartPosition(line, caretPosition); final String word = line.substring(start, stop); if (word.trim().length() > 0) { final boolean continueLastSearch = continueLastSearch(word, line); String checkword = ""; if (continueLastSearch) { checkword = lastWord; } else { checkword = word; } final AutoCompleteList autoCompleteList = getAutoCompleteList(checkword); if (autoCompleteList != null) { final List<String> suggestions = getAutoCompleteSuggestions( autoCompleteList.getWordList(), checkword); if (suggestions.size() > 0) { final int nextSuggestionPosition = findNextSuggestionPosition( continueLastSearch, suggestions, word); final String newWord = suggestions.get(nextSuggestionPosition); completedLine = line.substring(0, start) + newWord; newCaretPosition = completedLine.length(); completedLine += line.substring(stop); lastCompletedLine = completedLine; lastCompletedWord = newWord; if (!continueLastSearch) { lastWord = word; } } } } } return completedLine; } /** * Finds where in the list of suggestions to get the next suggestion. * * @param continueLastSearch If the previous search should be continued. * @param suggestions The list of suggested words. * @param word The word that is going to be autocompleted by the suggestion * this method finds. If this search continues from the previous * search, the word will be the same as the suggestion from that search. * @return The position in the list where the next suggestion can be found. */ private int findNextSuggestionPosition(final boolean continueLastSearch, final List<String> suggestions, final String word) { int nextSuggestionPosition = -1; if (continueLastSearch) { // Locate the position of the previous suggestion in the list for (int i = 0; i < suggestions.size(); i++) { if (suggestions.get(i).equals(word)) { nextSuggestionPosition = i; break; } } /* If more suggestions are available, increase position, * or else start from the beginning again. */ if (nextSuggestionPosition > -1 && nextSuggestionPosition < suggestions.size() - 1) { nextSuggestionPosition++; } else { nextSuggestionPosition = 0; } } // New search, start with first suggestion if (nextSuggestionPosition == -1) { nextSuggestionPosition = 0; } return nextSuggestionPosition; } /** * Checks if the new search should continue where the last left off. * The reason to continue is to see if there are more matches to the * previous search. * <br><br> * To find this, a check is done to see if the previous autocompleted * word and line is the same as the new word and line. If that's the case, * no changes to the text has been done since last autocomplete attempt. * * @param word The word to compare against the previous autocompleted word. * @param line The line to compare against the previous autocompleted line. * @return True if the search should be continued instead of restarted. */ private boolean continueLastSearch(final String word, final String line) { return lastCompletedWord.equals(word) && lastCompletedLine.equals(line); } /** * Locates the position in the line where the word ends. * * @param line The line of text where the word is. * @param caretPosition The position in the line where the word is. * @return The position where the word ends. */ private int findStopPosition(final String line, final int caretPosition) { int stop = line.indexOf(' ', caretPosition); if (stop == -1) { stop = line.length(); } return stop; } /** * Locates the position in the line where the word starts. * * @param line The line of text where the word is. * @param caretPosition The position in the line where the word is. * @return The position where the word starts. */ private int findStartPosition(final String line, final int caretPosition) { int start = line.lastIndexOf(' ', caretPosition - 1); if (start == -1) { start = 0; } else { start++; } return start; } /** * Asks the {@link AutoCompleteList}s available if any of them supports * this kind of word, and returns the match, if found. * * @param word The word to ask if any {@link AutoCompleteList} supports. * @return The first {@link AutoCompleteList} to support that word, * or <em>null</em> if none. */ private AutoCompleteList getAutoCompleteList(final String word) { for (final AutoCompleteList acl : autoCompleteLists) { if (acl.acceptsWord(word)) { return acl; } } return null; } /** * Compares the word with the word list to find suggestions for * autocompleting the word. * * @param wordList A list of words to compare the word with. * @param word The word to get suggestions for. * @return A list of suggestions. */ private List<String> getAutoCompleteSuggestions(final String[] wordList, final String word) { final List<String> suggestions = new ArrayList<String>(); for (int i = 0; i < wordList.length; i++) { if (wordList[i].toLowerCase().startsWith(word.toLowerCase())) { suggestions.add(wordList[i]); } } return suggestions; } /** * Returns the new caret position for the last completed search. * * @return The new caret position. */ public int getNewCaretPosition() { return newCaretPosition; } /** * Adds a new {@link AutoCompleteList} to use for autocompletion. * * @param acl The list to add. */ public void addAutoCompleteList(final AutoCompleteList acl) { autoCompleteLists.add(acl); } }