/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.internal.managed.commands; import com.jcwhatever.nucleus.managed.commands.ICommandOwner; import com.jcwhatever.nucleus.utils.ArrayUtils; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.text.TextUtils; import com.jcwhatever.nucleus.utils.validate.IValidator; import org.bukkit.command.CommandSender; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.annotation.Nullable; /** * Parses command string arrays to find the intended command and arguments. */ class CommandParser { private final RegisteredCommand _rootCommand; /** * Constructor. * * @param rootCommand The root command. */ public CommandParser(RegisteredCommand rootCommand) { PreCon.notNull(rootCommand); _rootCommand = rootCommand; } /** * Parse for a command and its arguments. * * @param commandCollection The collection of command candidates. * @param components The components of the text command. * * @return Null if a command is not found and no base command is set. */ @Nullable public ParsedCommand parseCommand(CommandCollection commandCollection, String[] components) { return parse(commandCollection, components, false); } /** * Get a command instance using a string path. * * <p>The format of the path is the command names separated by periods. * i.e. "command.subcommand1.subcommand2"</p> * * <p>The command collection provided should be the commands at the root * of the intended command path. Sub commands are found by traversing the * root (master) command hierarchy.</p> * * @param commandCollection The master command candidates. * @param commandPath The path of the command. */ @Nullable public RegisteredCommand parsePath(CommandCollection commandCollection, String commandPath) { PreCon.notNull(commandPath); String[] pathComp = TextUtils.PATTERN_DOT.split(commandPath); ParsedCommand parsed = parseCommand(commandCollection, pathComp); if (parsed == null) return null; // there shouldn't be any left over command path components if (parsed.getArguments().length != 0) return null; return parsed.getCommand(); } /** * Get a list of commands for tab complete. * * @param commandOwner The command the args are for. * @param sender The command sender. * @param args The base command arguments. */ public ParsedTabComplete parseTabComplete(ICommandOwner commandOwner, CommandSender sender, String[] args) { PreCon.notNull(commandOwner); PreCon.notNull(sender); PreCon.notNull(args); // get the primary command from the first argument ParsedCommand parsed = parse(commandOwner, args, true); if (parsed == null || args.length == 1) { List<String> result; if (args[0].isEmpty()) { result = args.length == 1 // get all commands (that can be seen) ? filterCommandNames(sender, "", commandOwner) : new ArrayList<String>(5); } else { // get possible command matches result = filterCommandNames(sender, args[0], commandOwner); } return new ParsedTabComplete(result, null, ArrayUtils.EMPTY_STRING_ARRAY); } final RegisteredCommand command = parsed.getCommand(); final String[] arguments = parsed.getArguments(); ParsedTabComplete tabComplete; if (arguments.length == 1 && command.getCommands().size() > 0) { // generate list of sub command names the player has permission to use List<String> names = filterCommandNames(sender, arguments[0], parsed.getCommand()); tabComplete = new ParsedTabComplete(names, command, arguments); } else if (arguments.length == 0 && parsed.getCommand().getParent() != null) { // generate list of command names the player has permission to use List<String> names = filterCommandNames(sender, "", parsed.getCommand().getParent()); tabComplete = new ParsedTabComplete(names, command, arguments); } else { tabComplete = new ParsedTabComplete(new ArrayList<String>(5), command, arguments); } return tabComplete; } /* * Filter a command owners command names based on a search name and if the * sub command is help visible. */ private List<String> filterCommandNames(final CommandSender sender, String searchName, final ICommandOwner commandOwner) { final String caseSearchName = searchName.toLowerCase(); Collection<String> commandNames = commandOwner.getCommandNames(); return TextUtils.search(commandNames, new IValidator<String>() { @Override public boolean isValid(String element) { RegisteredCommand subCommand = (RegisteredCommand)commandOwner.getCommand(element); return subCommand != null && subCommand.isHelpVisible(sender) && element.toLowerCase().startsWith(caseSearchName); } }); } /* * internal command parser */ private ParsedCommand parse(ICommandOwner commandCollection, String[] components, boolean isStrict) { PreCon.notNull(commandCollection); PreCon.notNull(components); // get the primary command from the first argument RegisteredCommand command = null; if (components.length > 0 && !components[0].isEmpty()) { command = (RegisteredCommand)commandCollection.getCommand(components[0]); } // primary command not found if (command == null) { if (!isStrict) { return new ParsedCommand(_rootCommand, components, 0); } return null; } // trim the first element from the array String[] args = ArrayUtils.reduceStart(1, components); // parse arguments and get command and command specific arguments return getCommand(command, args, 1); } /* * Recursively parses a String[] of arguments for the specified * parent command and return a ParsedCommand containing the * {@link AbstractCommand} implementation that should be used to execute the command * as well as the arguments to be used for the returned command. */ private ParsedCommand getCommand(RegisteredCommand parentCommand, @Nullable String[] args, int depth) { PreCon.notNull(parentCommand); if (args == null || args.length == 0) return new ParsedCommand(parentCommand, new String[0], depth); String subCmd = args[0].toLowerCase(); String[] params = ArrayUtils.reduceStart(1, args); RegisteredCommand subCommand = subCmd.isEmpty() ? null : parentCommand.getCommand(subCmd); if (subCommand == null) return new ParsedCommand(parentCommand, args, depth); else { ParsedCommand p = getCommand(subCommand, params, depth + 1); if (p == null) return new ParsedCommand(parentCommand, args, depth); return p; } } /** * A data object that holds results of parsing for a command. */ public final static class ParsedCommand { private final RegisteredCommand _command; private final String[] _arguments; private final int _depth; ParsedCommand(RegisteredCommand command, String[] arguments, int depth) { _command = command; _arguments = arguments; _depth = depth; } /** * Get the command. */ public RegisteredCommand getCommand() { return _command; } /** * Get the arguments intended for the command. */ public String[] getArguments() { return _arguments; } /** * Get the command path depth. */ public int getDepth() { return _depth; } } /** * A data object that holds the results of parsing for * tab completions. */ public final static class ParsedTabComplete { private final RegisteredCommand _command; private final String[] _arguments; private final List<String> _matches; ParsedTabComplete(List<String> matches, @Nullable RegisteredCommand command, String[] arguments) { _command = command; _matches = matches; _arguments = arguments; } /** * Get the list of command name matches for * a tab completion. */ public List<String> getMatches() { return _matches; } /** * Get the command that was parsed, if any. */ @Nullable public RegisteredCommand getCommand() { return _command; } /** * Get the arguments for the command. */ public String[] getArguments() { return _arguments; } } }