Mercurial > hg > release > thermostat-1.4
changeset 1817:1695efcce99d
Add TreeCompleter to complete logLevels
Reviewed-by: aazores, omajid, jkang, jerboaa
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2015-September/016483.html
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2015-May/013657.html
PR2646
author | Lukasz Dracz <ldracz@redhat.com> |
---|---|
date | Tue, 23 Jun 2015 10:46:29 -0400 |
parents | e255ee1b2f90 |
children | 912b35b5bedd |
files | common/core/src/main/java/com/redhat/thermostat/common/utils/LoggingUtils.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/ShellCommand.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/TabCompletion.java launcher/src/main/java/com/redhat/thermostat/launcher/internal/TreeCompleter.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/ShellCommandTest.java launcher/src/test/java/com/redhat/thermostat/launcher/internal/TreeCompleterTest.java |
diffstat | 6 files changed, 934 insertions(+), 46 deletions(-) [+] |
line wrap: on
line diff
--- a/common/core/src/main/java/com/redhat/thermostat/common/utils/LoggingUtils.java Tue Sep 29 10:20:19 2015 +0200 +++ b/common/core/src/main/java/com/redhat/thermostat/common/utils/LoggingUtils.java Tue Jun 23 10:46:29 2015 -0400 @@ -62,14 +62,42 @@ private static final String JUL_CONFIG_PROP_FILE = "java.util.logging.config.file"; + public enum LogLevel { + /* + * Custom log level, intended for use with Thermostat's internal performance + * analysis framework. Log messages at this level should be formatted using + * {@link com.redhat.thermostat.shared.perflog.PerformanceLogFormatter}. + */ + PERFLOG(new Level("PERFLOG", 50) { + private static final long serialVersionUID = 1L; + }), + ALL(Level.ALL), + CONFIG(Level.CONFIG), + FINE(Level.FINE), + FINER(Level.FINER), + FINEST(Level.FINEST), + INFO(Level.INFO), + OFF(Level.OFF), + SEVERE(Level.SEVERE), + WARNING(Level.WARNING); + + private Level level; + + LogLevel(Level level) { + this.level = level; + } + + public Level getLevel() { + return level; + } + } + /* * Custom log level, intended for use with Thermostat's internal performance * analysis framework. Log messages at this level should be formatted using * {@link com.redhat.thermostat.shared.perflog.PerformanceLogFormatter}. */ - public static final Level PERFLOG = new Level("PERFLOG", 50) { - private static final long serialVersionUID = 1L; - }; + public static final Level PERFLOG = LogLevel.PERFLOG.getLevel(); // package private for testing static final String ROOTNAME = "com.redhat.thermostat";
--- a/launcher/src/main/java/com/redhat/thermostat/launcher/internal/ShellCommand.java Tue Sep 29 10:20:19 2015 +0200 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/ShellCommand.java Tue Jun 23 10:46:29 2015 -0400 @@ -37,17 +37,14 @@ package com.redhat.thermostat.launcher.internal; import java.io.IOException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Map; -import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; import jline.Terminal; import jline.TerminalFactory; import jline.console.ConsoleReader; -import jline.console.completer.FileNameCompleter; import jline.console.history.FileHistory; import jline.console.history.History; import jline.console.history.PersistentHistory; @@ -68,11 +65,6 @@ import com.redhat.thermostat.shared.config.InvalidConfigurationException; import com.redhat.thermostat.shared.locale.Translate; import com.redhat.thermostat.storage.core.DbService; -import jline.console.completer.AggregateCompleter; -import jline.console.completer.ArgumentCompleter; -import jline.console.completer.Completer; -import jline.console.completer.StringsCompleter; -import org.apache.commons.cli.Option; public class ShellCommand extends AbstractCommand { @@ -163,7 +155,7 @@ private void shellMainLoop(CommandContext ctx, History history, Terminal term) throws IOException, CommandException { ConsoleReader reader = new ConsoleReader(ctx.getConsole().getInput(), ctx.getConsole().getOutput(), term); if (reader.getCompleters().isEmpty() && commandInfoSource != null) { - setupTabCompletion(reader); + TabCompletion.setupTabCompletion(reader, commandInfoSource); } if (history != null) { reader.setHistory(history); @@ -242,38 +234,5 @@ this.commandInfoSource = source; } - private void setupTabCompletion(ConsoleReader reader) { - Collection<Completer> commands = new ArrayList<>(); - Collection<String> options = new ArrayList<>(); - Collection<Completer> completers = new ArrayList<>(); - for (CommandInfo info : commandInfoSource.getCommandInfos()) { - - if (info.getEnvironments().contains(Environment.SHELL)) { - commands.clear(); - options.clear(); - commands.add(new StringsCompleter(info.getName())); - for (Option option : (Collection<Option>) info.getOptions().getOptions()) { - if (option.getLongOpt() != null) { - options.add("--" + option.getLongOpt()); - } - if (option.getOpt() != null) { - options.add("-" + option.getOpt()); - } - } - - if (info.needsFileTabCompletions()) { - AggregateCompleter optionsAndFiles = new AggregateCompleter(new StringsCompleter(options), new FileNameCompleter()); - commands.add(optionsAndFiles); - } else { - commands.add(new StringsCompleter(options)); - } - - completers.add(new ArgumentCompleter(new ArrayList<>(commands))); - } - } - AggregateCompleter aggregateCompleter = new AggregateCompleter(completers); - reader.addCompleter(aggregateCompleter); - } - }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/TabCompletion.java Tue Jun 23 10:46:29 2015 -0400 @@ -0,0 +1,124 @@ +/* + * Copyright 2012-2015 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.redhat.thermostat.common.utils.LoggingUtils; +import jline.console.ConsoleReader; +import jline.console.completer.FileNameCompleter; +import org.apache.commons.cli.Option; + +import static com.redhat.thermostat.launcher.internal.TreeCompleter.createStringNode; + +public class TabCompletion { + + private static final String LONG_OPTION_PREFIX = "--"; + private static final String SHORT_OPTION_PREFIX = "-"; + + public static void setupTabCompletion(ConsoleReader reader, CommandInfoSource commandInfoSource) { + List<String> logLevels = new ArrayList<>(); + + for (LoggingUtils.LogLevel level : LoggingUtils.LogLevel.values()) { + logLevels.add(level.getLevel().getName()); + } + + TreeCompleter treeCompleter = new TreeCompleter(); + for (CommandInfo info : commandInfoSource.getCommandInfos()) { + + if (info.getEnvironments().contains(Environment.SHELL)) { + String commandName = info.getName(); + TreeCompleter.Node command = createStringNode(commandName); + + for (Option option : (Collection<Option>) info.getOptions().getOptions()) { + if (option.getLongOpt().equals("logLevel")) { + setupLogLevelCompletion(logLevels, command, option); + } else { + setupDefaultCompletion(command, option); + } + + } + + if (info.needsFileTabCompletions()) { + TreeCompleter.Node files = new TreeCompleter.Node(new FileNameCompleter()); + files.setRestartNode(command); + command.addBranch(files); + } + treeCompleter.addBranch(command); + } + } + + reader.addCompleter(treeCompleter); + } + + private static void setupDefaultCompletion(final TreeCompleter.Node command, final Option option) { + if (option.getLongOpt() != null) { + String optionLongName = LONG_OPTION_PREFIX + option.getLongOpt(); + TreeCompleter.Node defaultNode = createStringNode(optionLongName); + defaultNode.setRestartNode(command); + command.addBranch(defaultNode); + } + if (option.getOpt() != null) { + String optionShortName = SHORT_OPTION_PREFIX + option.getOpt(); + TreeCompleter.Node defaultNode = createStringNode(optionShortName); + defaultNode.setRestartNode(command); + command.addBranch(defaultNode); + } + } + + private static void setupLogLevelCompletion( final List<String> logLevels, final TreeCompleter.Node command, final Option option) { + if (option.getLongOpt() != null) { + String optionLongName = LONG_OPTION_PREFIX + option.getLongOpt(); + TreeCompleter.Node logLevelOption = createStringNode(optionLongName); + TreeCompleter.Node logLevelChoices = createStringNode(logLevels); + logLevelChoices.setRestartNode(command); + logLevelOption.addBranch(logLevelChoices); + command.addBranch(logLevelOption); + } + if (option.getOpt() != null) { + String optionShortName = SHORT_OPTION_PREFIX + option.getOpt(); + TreeCompleter.Node logLevelOption = createStringNode(optionShortName); + TreeCompleter.Node logLevelChoices = createStringNode(logLevels); + logLevelChoices.setRestartNode(command); + logLevelOption.addBranch(logLevelChoices); + command.addBranch(logLevelOption); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/main/java/com/redhat/thermostat/launcher/internal/TreeCompleter.java Tue Jun 23 10:46:29 2015 -0400 @@ -0,0 +1,295 @@ +/* + * Copyright 2012-2015 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import static java.util.Objects.requireNonNull; +import static jline.console.completer.ArgumentCompleter.ArgumentDelimiter; +import static jline.console.completer.ArgumentCompleter.ArgumentList; +import static jline.console.completer.ArgumentCompleter.WhitespaceArgumentDelimiter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import jline.console.completer.Completer; +import jline.console.completer.StringsCompleter; + +public class TreeCompleter implements Completer { + + private final ArgumentDelimiter delimiter; + + private final List<Node> branches; + + private Node currentNode; + private ArgumentList list; + + public static final int NOT_FOUND = -1; + private static final String EMPTY_SPACE = " "; + private static final Node START_NODE = null; + + /** + * This method adds a child branch to the start node + * @param child a node containing a completer + */ + public void addBranch(final Node child) { + branches.add(child); + } + + /** + * This method adds child branches to the start node + * @param branchList a list of nodes containing a completer each + */ + public void addBranches(final List<Node> branchList) { + for (final Node branch : branchList) { + addBranch(branch); + } + } + + /** + * @return the start node branches + */ + public List<Node> getBranches() { + return branches; + } + + public TreeCompleter() { + delimiter = new WhitespaceArgumentDelimiter(); + branches = new ArrayList<>(); + } + + /** + * This method is called when attempting to tab complete + * @param buffer the input that will be tab completed + * @param cursorPosition the position of the cursorPosition within the buffer + * @param candidates the list of possible completions will get filled when found + * @return the new position of the cursorPosition, a return of NOT_FOUND means no completion + * was found or completion is finished, resulting in no change of the cursorPosition position + */ + @Override + public int complete(final String buffer, final int cursorPosition, final List<CharSequence> candidates) { + requireNonNull(candidates); + + if (cursorPosition > buffer.length()) { + return NOT_FOUND; + } + final String currentBuffer = buffer.substring(0, cursorPosition); + refreshCompleter(); + + list = delimiter.delimit(currentBuffer, cursorPosition); + if (list.getCursorArgumentIndex() < 0) { + return NOT_FOUND; + } + + int position = cursorPosition; + currentNode = traverseBranches(currentBuffer, Arrays.asList(list.getArguments())); + final List<Completer> completers = getAllCompleters(currentNode); + + //Complete possible arguments off a space or the current word up to the cursor + if (currentBuffer.endsWith(EMPTY_SPACE)) { + completeList(candidates, completers); + } else { + if (currentNode != START_NODE && currentNode.getBranches().isEmpty()) { + currentNode = currentNode.getRestartNode(); + } + final List<Completer> relevantCompleters = filterRelevantCompleters(completers, list.getCursorArgument()); + for (final Completer completer : relevantCompleters) { + position = getInlinedCursorPosition(completer, candidates); + } + } + + return position; + } + + private int getInlinedCursorPosition(final Completer completer, final List<CharSequence> candidates) { + int cursor = completer.complete(list.getCursorArgument(), list.getArgumentPosition(), candidates); + return cursor + list.getBufferPosition() - list.getArgumentPosition(); + } + + private void refreshCompleter() { + currentNode = START_NODE; + } + + private Node traverseBranches(final String currentBuffer, final List<String> arguments) { + Node resultNode = START_NODE; + for (final Iterator<String> it = arguments.iterator(); it.hasNext();) { + final String arg = it.next(); + if (!it.hasNext() && !currentBuffer.endsWith(" ")) { + //inline completion detected + break; + } + final int branchIndex = getBranchIndex(arg, getPossibleMatches(resultNode)); + if (branchIndex != NOT_FOUND) { + resultNode = findBranches(resultNode).get(branchIndex); + } + } + return resultNode; + } + + private void completeList(final List<CharSequence> candidates, final List<Completer> completerList) { + for (final Completer completer : completerList) { + completer.complete(null, 0, candidates); + } + } + + private List<Completer> getAllCompleters(final Node currentNode) { + final List<Completer> completersFromBranches = new ArrayList<>(); + for (final Node node : findBranches(currentNode)) { + completersFromBranches.add(node.getCompleter()); + } + return completersFromBranches; + } + + private List<Node> getChildNodesFromRestartNode(final Node node) { + final List<Node> childrenNodeList; + if (node.getRestartNode() == START_NODE) { + childrenNodeList = getBranches(); + } else { + childrenNodeList = node.getRestartNode().getBranches(); + } + currentNode = node.getRestartNode(); + return childrenNodeList; + } + + private List<Completer> filterRelevantCompleters(final List<Completer> completersFromBranches, final String cursorArgument) { + final List<Completer> completers = new ArrayList<>(); + for (final Completer branchCompleter : completersFromBranches) { + final List<CharSequence> candidates = new LinkedList<>(); + branchCompleter.complete(cursorArgument, 0, candidates); + if (!candidates.isEmpty()) { + completers.add(branchCompleter); + } + } + return completers; + } + + private List<CharSequence> findCompletions(final Completer branchCompleter) { + final List<CharSequence> candidates = new LinkedList<>(); + branchCompleter.complete(null, 0, candidates); + return candidates; + } + + private int getBranchIndex(final String argument, final List<List<CharSequence>> listOfPossibleMatches) { + for (final List<CharSequence> possibleMatches : listOfPossibleMatches) { + for (final CharSequence word : possibleMatches) { + if (word.toString().trim().equals(argument.trim())) { + return listOfPossibleMatches.indexOf(possibleMatches); + } + } + } + return NOT_FOUND; + } + + private List<List<CharSequence>> getPossibleMatches(final Node node) { + final List<List<CharSequence>> possibleMatches = new LinkedList<>(); + for (final Node branch : findBranches(node)) { + possibleMatches.add(findCompletions(branch.getCompleter())); + } + return possibleMatches; + } + + private List<Node> findBranches(final Node node) { + if (node == START_NODE) { + return getBranches(); + } else { + if (node.getBranches().isEmpty()) { + return getChildNodesFromRestartNode(node); + } + return node.getBranches(); + } + } + + /** + * A class to be used with the TreeCompleter + * Each node contains a completer that will be used by the TreeCompleter + * to check completion. Each node contains branches that are possible + * paths for the completion to follow. Once completion has reached a node + * with no branches it will use the restartNode to continue to find + * any further completions. + */ + public static class Node { + private final Completer completer; + private final List<Node> branches; + private Node restartNode = START_NODE; + + public Node(final Completer data) { + requireNonNull(data); + this.completer = data; + branches = new ArrayList<>(); + } + + public void addBranch(final Node branch) { + branches.add(branch); + } + + public Completer getCompleter() { + return completer; + } + + public List<Node> getBranches() { + return branches; + } + + public void setRestartNode(final Node restartNode) { + this.restartNode = restartNode; + } + + public Node getRestartNode() { + return restartNode; + } + + } + + /** + * A helper method to quickly create a node containing a strings completer + * @param strings the strings to be completed by the strings completer + * @return the node containing the string completer + */ + public static Node createStringNode(String... strings) { + return new Node(new StringsCompleter(strings)); + } + + /** + * A helper method to quickly create a node containing a strings completer + * @param strings the strings to be completed by the strings completer + * @return the node containing the string completer + */ + public static Node createStringNode(List<String> strings) { + return new Node(new StringsCompleter(strings)); + } +}
--- a/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ShellCommandTest.java Tue Sep 29 10:20:19 2015 +0200 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/ShellCommandTest.java Tue Jun 23 10:46:29 2015 -0400 @@ -577,7 +577,7 @@ createTempFile(filename + "12345678"); TestCommandContextFactory ctxFactory = new TestCommandContextFactory(bundleContext); - ctxFactory.setInput("validate --dbUrl -a -l -d " + dir.getAbsolutePath() + File.separator + "testFil\t\nexit\n"); + ctxFactory.setInput("validate --dbUrl -d " + dir.getAbsolutePath() + File.separator + "testFil\t\nexit\n"); Arguments args = new SimpleArguments(); CommandContext ctx = ctxFactory.createContext(args); cmd.run(ctx);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/launcher/src/test/java/com/redhat/thermostat/launcher/internal/TreeCompleterTest.java Tue Jun 23 10:46:29 2015 -0400 @@ -0,0 +1,482 @@ +/* + * Copyright 2012-2015 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.launcher.internal; + +import static com.redhat.thermostat.launcher.internal.TreeCompleter.createStringNode; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import com.redhat.thermostat.common.cli.CommandException; +import jline.console.completer.FileNameCompleter; +import org.junit.Before; +import org.junit.Test; + +public class TreeCompleterTest { + + private File testDir; + private TreeCompleter tree; + + @Before + public void setUp() throws IOException { + tree = new TreeCompleter(); + + testDir = new File(System.getProperty("java.io.tmpdir") + File.separator + "treecompleter"); + testDir.deleteOnExit(); + testDir.mkdirs(); + setupTreeCompleter(); + } + + @Test + public void testInlineCompletion() throws IOException, CommandException { + List<String> candidates = completeBuffer("other"); + + assertTrue(candidates.contains("otherCommand")); + } + + @Test + public void testBaseIndexCompletion() throws IOException, CommandException { + List<String> candidates = completeBuffer(""); + + assertTrue(candidates.contains("otherCommand")); + assertTrue(candidates.contains("command1")); + assertTrue(candidates.contains("command2")); + assertTrue(candidates.contains("anotherCommand")); + } + + @Test + public void testVeryLongCompletion() throws IOException, CommandException { + String[] longChainOfWords = { "otherCommand", "create", "a", "long", "chain", "of", "tab", "completing", "words"}; + List<String> chain = new ArrayList<String>(); + Collections.addAll(chain, longChainOfWords); + String firstPart = ""; + for (String word : chain) { + firstPart += word + " "; + List<String> tabOutput = completeBuffer(firstPart + " "); + + if (chain.indexOf(word) == 0) { + assertTrue(tabOutput.contains("list")); + assertTrue(tabOutput.contains("create")); + assertTrue(tabOutput.contains("start")); + } else if (chain.indexOf(word) == chain.size() - 1) { + assertTrue(tabOutput.contains("otherCommand")); + assertTrue(tabOutput.contains("anotherCommand")); + assertTrue(tabOutput.contains("command1")); + assertTrue(tabOutput.contains("command2")); + } else { + assertTrue(tabOutput.contains(chain.get(chain.indexOf(word) + 1))); + } + + } + } + + @Test + public void testVeryLongCompletionRestartedTwice() throws IOException, CommandException { + String[] longChainOfWords = { "otherCommand", "create", "a", "long", "chain", "of", "tab", "completing", "words"}; + List<String> chain = new ArrayList<String>(); + Collections.addAll(chain, longChainOfWords); + Collections.addAll(chain, longChainOfWords); + Collections.addAll(chain, longChainOfWords); + String firstPart = ""; + for (String word : chain) { + firstPart += word + " "; + List<String> tabOutput = completeBuffer(firstPart + " "); + + if (chain.indexOf(word) == 0) { + assertTrue(tabOutput.contains("list")); + assertTrue(tabOutput.contains("create")); + assertTrue(tabOutput.contains("start")); + } else if (chain.indexOf(word) == chain.size() - 1) { + assertTrue(tabOutput.contains("otherCommand")); + assertTrue(tabOutput.contains("anotherCommand")); + assertTrue(tabOutput.contains("command1")); + assertTrue(tabOutput.contains("command2")); + } else { + assertTrue(tabOutput.contains(chain.get(chain.indexOf(word) + 1))); + } + + } + } + + @Test + public void testRestartIndexOtherThanZero() throws CommandException { + String[] input = { "command1", "list", "nothing"}; + List<String> chain = new ArrayList<String>(); + Collections.addAll(chain, input); + String firstPart = ""; + for (String word : chain) { + firstPart += word + " "; + List<String> tabOutput = completeBuffer(firstPart + " "); + + if (chain.indexOf(word) == 0) { + assertTrue(tabOutput.contains("list")); + assertTrue(tabOutput.contains("create")); + assertTrue(tabOutput.contains("delete")); + } else if (chain.indexOf(word) == chain.size() - 1) { + assertTrue(tabOutput.contains("list")); + assertTrue(tabOutput.contains("create")); + assertTrue(tabOutput.contains("delete")); + } else { + assertTrue(tabOutput.contains(chain.get(chain.indexOf(word) + 1))); + } + + } + } + + @Test + public void testCompletionOfSameTextInDifferentBranchesReturnsDifferentResults() throws CommandException { + List<String> firstOutput = completeBuffer("otherCommand list "); + List<String> secondOutput = completeBuffer("command1 list "); + + assertTrue(firstOutput.contains("parts")); + assertTrue(firstOutput.contains("assemblies")); + assertTrue(firstOutput.contains("degreesOfFreedom")); + assertTrue(firstOutput.contains("bolts")); + assertTrue(firstOutput.contains("tools")); + + assertFalse(firstOutput.contains("everything")); + assertFalse(firstOutput.contains("nothing")); + assertFalse(firstOutput.contains("firstHalf")); + assertFalse(firstOutput.contains("secondHalf")); + + assertTrue(secondOutput.contains("everything")); + assertTrue(secondOutput.contains("nothing")); + assertTrue(secondOutput.contains("firstHalf")); + assertTrue(secondOutput.contains("secondHalf")); + + assertFalse(secondOutput.contains("parts")); + assertFalse(secondOutput.contains("assemblies")); + assertFalse(secondOutput.contains("degreesOfFreedom")); + assertFalse(secondOutput.contains("bolts")); + assertFalse(secondOutput.contains("tools")); + } + + @Test + public void testRestartIndexOne() throws CommandException { + List<String> output = completeBuffer("command2 stop yes "); + + assertTrue(output.contains("find")); + assertTrue(output.contains("climb")); + assertTrue(output.contains("stop")); + } + + @Test + public void testRestartIndexTwo() throws CommandException { + List<String> output = completeBuffer("command1 list firstHalf "); + + assertTrue(output.contains("everything")); + assertTrue(output.contains("nothing")); + assertTrue(output.contains("firstHalf")); + assertTrue(output.contains("secondHalf")); + } + + @Test + public void testParametersDoNotInterfereWithInlineCompletion() throws CommandException { + List<String> inlineOutput = completeBuffer("command1 blah list blue red green notValid second"); + + assertTrue(inlineOutput.contains("secondHalf")); + } + + @Test + public void testParametersDoNotInterfereWithCompletion() throws CommandException { + List<String> output = completeBuffer("otherCommand blue green list red purple "); + + assertTrue(output.contains("parts")); + assertTrue(output.contains("assemblies")); + assertTrue(output.contains("degreesOfFreedom")); + assertTrue(output.contains("bolts")); + assertTrue(output.contains("tools")); + } + + @Test + public void testFileCompletion() throws CommandException, IOException { + String filename1 = "testFilesNow"; + String filename2 = "document"; + String filename3 = "musicFile"; + String filename4 = "slideshow"; + + createTempFile(filename1); + createTempFile(filename2); + createTempFile(filename3); + createTempFile(filename4); + + List<String> output = completeBuffer("command2 find " + testDir.getAbsolutePath() + File.separator); + + assertTrue(output.contains(filename1)); + assertTrue(output.contains(filename2)); + assertTrue(output.contains(filename3)); + assertTrue(output.contains(filename4)); + } + + @Test + public void testFileCompletionInline() throws CommandException, IOException { + String filename1 = "testFilesNow"; + String filename2 = "document"; + String filename3 = "musicFile"; + String filename4 = "slideshow"; + + createTempFile(filename1); + createTempFile(filename2); + createTempFile(filename3); + createTempFile(filename4); + + List<String> output = completeBuffer("command2 find " + testDir.getAbsolutePath() + File.separator); + + assertTrue(output.contains(filename2)); + } + + @Test + public void testCursorInMiddleOfWord() { + List<String> output = completeBuffer("command1 ", 3); + + assertTrue(output.contains("command1")); + assertTrue(output.contains("command2")); + assertFalse(output.contains("otherCommand")); + assertFalse(output.contains("anotherCommand")); + } + + @Test + public void testCursorBeforeWord() { + List<String> output = completeBuffer("command1 ", 0); + + assertTrue(output.contains("command1")); + assertTrue(output.contains("command2")); + assertTrue(output.contains("otherCommand")); + assertTrue(output.contains("anotherCommand")); + } + + @Test + public void testCursorBeforeSpace() { + List<String> output = completeBuffer("command1 ", 8); + + assertTrue(output.contains("command1")); + assertFalse(output.contains("command2")); + assertFalse(output.contains("otherCommand")); + assertFalse(output.contains("anotherCommand")); + } + + @Test + public void testCursorAtSpace() { + List<String> output = completeBuffer("command1 ", 9); + + assertTrue(output.contains("list")); + assertTrue(output.contains("create")); + assertTrue(output.contains("delete")); + } + + @Test + public void testCursorAtSpaceBeforeAnotherWord() { + List<String> output = completeBuffer("command1 list", 9); + + assertTrue(output.contains("list")); + assertTrue(output.contains("create")); + assertTrue(output.contains("delete")); + } + + @Test + public void testCursorInMiddleOfSecondWord() { + List<String> output = completeBuffer("command1 list", 11); + + assertTrue(output.contains("list")); + assertFalse(output.contains("create")); + assertFalse(output.contains("delete")); + } + + @Test + public void testCursorLongerThanBufferLength() { + List<CharSequence> candidates = new LinkedList<>(); + + int cursor = tree.complete("command1 ", 503, candidates); + List<String> convertedCandidates = convertToStringList(candidates); + assertEquals(TreeCompleter.NOT_FOUND, cursor); + assertTrue(convertedCandidates.isEmpty()); + } + + private List<String> completeBuffer(String buffer) { + return completeBuffer(buffer, buffer.length()); + } + + private List<String> completeBuffer(String buffer, int cursor) { + List<CharSequence> candidates = new LinkedList<>(); + + tree.complete(buffer, cursor, candidates); + List<String> convertedCandidates = convertToStringList(candidates); + return convertedCandidates; + } + + private List<String> convertToStringList(List<CharSequence> list) { + List<String> stringsList = new ArrayList<>(); + for (CharSequence chars : list) { + stringsList.add(chars.toString().trim()); + } + return stringsList; + } + + private void createTempFile(String name) throws IOException { + File file = new File(testDir, name); + file.deleteOnExit(); + file.createNewFile(); + } + + public void setupTreeCompleter() throws IOException { + + List<TreeCompleter.Node> commands = new ArrayList<>(); + TreeCompleter.Node command1 = createStringNode("command1"); + { + TreeCompleter.Node create = createStringNode("create"); + TreeCompleter.Node delete = createStringNode("delete"); + TreeCompleter.Node list = createStringNode("list"); + { + TreeCompleter.Node everything = createStringNode("everything"); + TreeCompleter.Node nothing = createStringNode("nothing"); + nothing.setRestartNode(command1); + TreeCompleter.Node firstHalf = createStringNode("firstHalf"); + firstHalf.setRestartNode(list); + TreeCompleter.Node secondHalf = createStringNode("secondHalf"); + secondHalf.setRestartNode(list); // maybe secondHalf + list.addBranch(everything); + list.addBranch(nothing); + list.addBranch(firstHalf); + list.addBranch(secondHalf); + } + command1.addBranch(list); + command1.addBranch(create); + command1.addBranch(delete); + } + + TreeCompleter.Node command2 = createStringNode("command2"); + { + TreeCompleter.Node find = createStringNode("find"); + { + TreeCompleter.Node files = new TreeCompleter.Node(new FileNameCompleter()); + find.addBranch(files); + } + TreeCompleter.Node climb = createStringNode("climb"); + TreeCompleter.Node stop = createStringNode("stop"); + { + TreeCompleter.Node yes = createStringNode("yes"); + yes.setRestartNode(command2); + TreeCompleter.Node no = createStringNode("no"); + stop.addBranch(yes); + stop.addBranch(no); + } + command2.addBranch(find); + command2.addBranch(climb); + command2.addBranch(stop); + } + TreeCompleter.Node otherCommand = createStringNode("otherCommand"); + { + TreeCompleter.Node list = createStringNode("list"); + { + TreeCompleter.Node parts = createStringNode("parts"); + TreeCompleter.Node assemblies = createStringNode("assemblies"); + assemblies.setRestartNode(otherCommand); + TreeCompleter.Node degreesOfFreedom = createStringNode("degreesOfFreedom"); + degreesOfFreedom.setRestartNode(list); + TreeCompleter.Node bolts = createStringNode("bolts"); + bolts.setRestartNode(list); + TreeCompleter.Node tools = createStringNode("tools"); + tools.setRestartNode(list); + list.addBranch(parts); + list.addBranch(assemblies); + list.addBranch(degreesOfFreedom); + list.addBranch(bolts); + list.addBranch(tools); + } + TreeCompleter.Node create = createStringNode("create"); + { + TreeCompleter.Node a = createStringNode("a"); + TreeCompleter.Node longNode = createStringNode("long"); + TreeCompleter.Node chain = createStringNode("chain"); + TreeCompleter.Node of = createStringNode("of"); + TreeCompleter.Node tab = createStringNode("tab"); + TreeCompleter.Node completing = createStringNode("completing"); + TreeCompleter.Node words = createStringNode("words"); + completing.addBranch(words); + tab.addBranch(completing); + of.addBranch(tab); + chain.addBranch(of); + longNode.addBranch(chain); + a.addBranch(longNode); + create.addBranch(a); + } + TreeCompleter.Node start = createStringNode("start"); + { + TreeCompleter.Node yes = createStringNode("yes"); + yes.setRestartNode(otherCommand); + TreeCompleter.Node no = createStringNode("no"); + start.addBranch(yes); + start.addBranch(no); + } + otherCommand.addBranch(list); + otherCommand.addBranch(create); + otherCommand.addBranch(start); + } + TreeCompleter.Node anotherCommand = createStringNode("anotherCommand"); + { + TreeCompleter.Node list = createStringNode("list"); + { + TreeCompleter.Node everything = createStringNode("everything"); + TreeCompleter.Node nothing = createStringNode("nothing"); + TreeCompleter.Node firstHalf = createStringNode("firstHalf"); + TreeCompleter.Node secondHalf = createStringNode("secondHalf"); + TreeCompleter.Node twentyFifthToSeventyFifthPart = createStringNode("25to75part"); + list.addBranch(everything); + list.addBranch(nothing); + list.addBranch(firstHalf); + list.addBranch(secondHalf); + list.addBranch(twentyFifthToSeventyFifthPart); + } + anotherCommand.addBranch(list); + } + + commands.add(command1); + commands.add(command2); + commands.add(otherCommand); + commands.add(anotherCommand); + + tree.addBranches(commands); + } + +}