#305 Modularize logic / separate from data classes (wip – doesn't compile)

- Remove permission logic on command side; make PermissionsManager handle checks for all CommandSender objects (not only Player), cf. #314
- Remove unnecessary redundancies in passed arguments ("command references" that can be inferred from the FoundResult)
- Extend FoundCommandResult to represent all possible error cases
This commit is contained in:
ljacqu 2015-12-12 00:43:55 +01:00
parent ec9009d776
commit 2f153fb85c
8 changed files with 178 additions and 245 deletions

View File

@ -214,11 +214,9 @@ public class AuthMe extends JavaPlugin {
plugin = this;
setupConstants();
// Set up the permissions manager
setupPermissionsManager();
// Set up and initialize the command handler
setupCommandHandler();
// Set up the permissions manager and command handler
permsMan = initializePermissionsManager();
commandHandler = new CommandHandler(CommandInitializer.getBaseCommands(), permsMan);
// Set up the module manager
setupModuleManager();
@ -433,8 +431,8 @@ public class AuthMe extends JavaPlugin {
/**
* Set up the command handler.
*/
private void setupCommandHandler() {
this.commandHandler = new CommandHandler(CommandInitializer.getBaseCommands());
private void setupCommandHandler(PermissionsManager permissionsManager) {
this.commandHandler = new CommandHandler(CommandInitializer.getBaseCommands(), permissionsManager);
}
/**
@ -606,9 +604,10 @@ public class AuthMe extends JavaPlugin {
/**
* Set up the permissions manager.
*/
public void setupPermissionsManager() {
this.permsMan = new PermissionsManager(Bukkit.getServer(), this, getLogger());
this.permsMan.setup();
private PermissionsManager initializePermissionsManager() {
PermissionsManager manager = new PermissionsManager(Bukkit.getServer(), this, getLogger());
manager.setup();
return manager;
}
/**

View File

@ -14,6 +14,7 @@ import java.util.Comparator;
import java.util.List;
import static com.google.common.base.Objects.firstNonNull;
import static java.util.Arrays.asList;
/**
* Command description - defines which labels ("names") will lead to a command and points to the
@ -31,15 +32,15 @@ public class CommandDescription {
*/
private List<String> labels;
/**
* Command description.
* Short description of the command.
*/
private String description;
/**
* Detailed description of the command.
* Detailed description of what the command does.
*/
private String detailedDescription;
/**
* The executable command instance.
* The executable command instance described by this object.
*/
private ExecutableCommand executableCommand;
/**
@ -55,7 +56,7 @@ public class CommandDescription {
*/
private List<CommandArgumentDescription> arguments;
/**
* Defines the command permissions.
* Command permissions required to execute this command.
*/
private CommandPermissions permissions;
@ -106,9 +107,10 @@ public class CommandDescription {
}
/**
* Get all relative command labels.
* Get all relative labels of this command. For example, if this object describes "/authme register" and
* "/authme r", then "r" and "register" are the relative labels, whereas "authme" is the label of the parent.
*
* @return All relative labels labels.
* @return All relative labels.
*/
public List<String> getLabels() {
return this.labels;
@ -117,9 +119,9 @@ public class CommandDescription {
/**
* Check whether this command description has a specific command.
*
* @param commandLabel Command to check for.
* @param commandLabel The label to check for.
*
* @return True if this command label equals to the param command.
* @return {@code true} if this command contains the given label, {@code false} otherwise.
*/
public boolean hasLabel(String commandLabel) {
for (String label : labels) {
@ -131,25 +133,25 @@ public class CommandDescription {
}
/**
* Get the executable command.
* Return the {@link ExecutableCommand} instance defined by the command description.
*
* @return The executable command.
* @return The executable command object.
*/
public ExecutableCommand getExecutableCommand() {
return this.executableCommand;
return executableCommand;
}
/**
* Get the parent command if this command description has a parent.
* Return the parent.
*
* @return Parent command, or null
* @return The parent command, or null for base commands.
*/
public CommandDescription getParent() {
return this.parent;
return parent;
}
/**
* Get all command children.
* Return all command children.
*
* @return Command children.
*/
@ -159,7 +161,7 @@ public class CommandDescription {
/**
* Get all command arguments.
* Return all arguments the command takes.
*
* @return Command arguments.
*/
@ -168,16 +170,7 @@ public class CommandDescription {
}
/**
* Check whether this command has any arguments.
*
* @return True if this command has any arguments.
*/
public boolean hasArguments() {
return !getArguments().isEmpty();
}
/**
* Get the command description.
* Return a short description of the command.
*
* @return Command description.
*/
@ -186,9 +179,9 @@ public class CommandDescription {
}
/**
* Get the command detailed description.
* Return a detailed description of the command.
*
* @return Command detailed description.
* @return Detailed description.
*/
public String getDetailedDescription() {
return detailedDescription;
@ -196,14 +189,19 @@ public class CommandDescription {
/**
* Get the command permissions. Return null if the command doesn't require any permission.
* Return the permissions required to execute the command.
*
* @return The command permissions.
* @return The command permissions, or null if none are required to execute the command.
*/
public CommandPermissions getCommandPermissions() {
return this.permissions;
}
/**
* Return a builder instance to create a new command description.
*
* @return The builder
*/
public static CommandBuilder builder() {
return new CommandBuilder();
}
@ -221,12 +219,11 @@ public class CommandDescription {
private CommandPermissions permissions;
/**
* Build a CommandDescription from the builder or throw an exception if mandatory
* fields have not been set.
* Build a CommandDescription from the builder or throw an exception if a mandatory
* field has not been set.
*
* @return The generated CommandDescription object
*/
// TODO ljacqu 20151206 Move validation to the create instance method
public CommandDescription build() {
return createInstance(
getOrThrow(labels, "labels"),
@ -245,7 +242,7 @@ public class CommandDescription {
}
public CommandBuilder labels(String... labels) {
return labels(asMutableList(labels));
return labels(asList(labels));
}
public CommandBuilder description(String description) {
@ -274,7 +271,7 @@ public class CommandDescription {
*
* @param label The label of the argument (single word name of the argument)
* @param description The description of the argument
* @param isOptional True if the argument is option, false if it is mandatory
* @param isOptional True if the argument is optional, false if it is mandatory
*
* @return The builder
*/
@ -291,7 +288,7 @@ public class CommandDescription {
@SafeVarargs
private static <T> List<T> asMutableList(T... items) {
return new ArrayList<>(Arrays.asList(items));
return new ArrayList<>(asList(items));
}
private static <T> T getOrThrow(T element, String elementName) {

View File

@ -6,6 +6,7 @@ import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
@ -56,16 +57,16 @@ public class CommandHandler {
FoundCommandResult result = mapPartsToCommand(parts);
switch (result.getResultStatus()) {
case SUCCESS:
// Check perms + process
executeCommandIfAllowed(sender, result.getCommandDescription(), result.getArguments());
break;
case MISSING_BASE_COMMAND:
sender.sendMessage(ChatColor.DARK_RED + "Failed to parse " + AuthMe.getPluginName() + " command!");
return false;
case INCORRECT_ARGUMENTS:
// sendImproperArgumentsMessage(sender, result);
sendImproperArgumentsMessage(sender, result);
break;
case UNKNOWN_LABEL:
// sendUnknownCommandMessage(sender);
sendUnknownCommandMessage(sender, result);
break;
default:
throw new RuntimeException("Unknown result '" + result.getResultStatus() + "'");
@ -74,6 +75,14 @@ public class CommandHandler {
return true;
}
private void executeCommandIfAllowed(CommandSender sender, CommandDescription command, List<String> arguments) {
if (permissionsManager.hasPermission(sender, command)) {
command.getExecutableCommand().executeCommand(sender, arguments);
} else {
sendPermissionDeniedError(sender);
}
}
/**
* Skip all entries of the given array that are simply whitespace.
*
@ -81,7 +90,7 @@ public class CommandHandler {
* @return List of the items that are not empty
*/
private static List<String> skipEmptyArguments(String[] args) {
List<String> cleanArguments = new ArrayList<>(args.length);
List<String> cleanArguments = new ArrayList<>();
for (String argument : args) {
if (!StringUtils.isEmpty(argument)) {
cleanArguments.add(argument);
@ -90,16 +99,14 @@ public class CommandHandler {
return cleanArguments;
}
/**
* Show an "unknown command" to the user and suggests an existing command if its similarity is within
* the defined threshold.
*
* @param sender The command sender
* @param result The command that was found during the mapping process
* @param baseCommand The base command
*/
private static void sendUnknownCommandMessage(CommandSender sender, FoundCommandResult result, String baseCommand) {
private static void sendUnknownCommandMessage(CommandSender sender, FoundCommandResult result) {
sender.sendMessage(ChatColor.DARK_RED + "Unknown command!");
// Show a command suggestion if available and the difference isn't too big
@ -109,27 +116,29 @@ public class CommandHandler {
// TODO: Define a proper string representation of command description
}
sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + baseCommand + " help"
+ ChatColor.YELLOW + " to view help.");
sender.sendMessage(ChatColor.YELLOW + "Use the command " + ChatColor.GOLD + "/" + result.getLabels().get(0)
+ " help" + ChatColor.YELLOW + " to view help.");
}
private static void sendImproperArgumentsMessage(CommandSender sender, FoundCommandResult result,
CommandParts commandReference, String baseCommand) {
// Get the command and the suggested command reference
// FIXME List<String> suggestedCommandReference =
// result.getCommandDescription().getCommandReference(commandReference).getList();
// List<String> helpCommandReference = CollectionUtils.getRange(suggestedCommandReference, 1);
// Show the invalid arguments warning
sender.sendMessage(ChatColor.DARK_RED + "Incorrect command arguments!");
private void sendImproperArgumentsMessage(CommandSender sender, FoundCommandResult result) {
CommandDescription command = result.getCommandDescription();
if (!permissionsManager.hasPermission(sender, command)) {
sendPermissionDeniedError(sender);
return;
}
// Show the command argument help
// HelpProvider.showHelp(sender, commandReference, new CommandParts(suggestedCommandReference),
// true, false, true, false, false, false);
sender.sendMessage(ChatColor.DARK_RED + "Incorrect command arguments!");
// TODO: Define showHelp(CommandSender, CommandDescription, List<String>, boolean, boolean, ...)
List<String> labels = result.getLabels();
HelpProvider.showHelp(sender, command, labels, true, false, true, false, false, false);
sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE + "/" + labels.get(0)
+ " help " + CommandUtils.labelsToString(labels.subList(1, labels.size())));
}
// Show the command to use for detailed help
// sender.sendMessage(ChatColor.GOLD + "Detailed help: " + ChatColor.WHITE + "/" + baseCommand
// + " help " + CommandUtils.labelsToString(helpCommandReference));
// TODO ljacqu 20151212: Remove me once I am a MessageKey
private void sendPermissionDeniedError(CommandSender sender) {
sender.sendMessage(ChatColor.DARK_RED + "You don't have permission to use this command!");
}
public FoundCommandResult mapPartsToCommand(final List<String> parts) {
@ -144,64 +153,50 @@ public class CommandHandler {
// Prefer labels: /register help goes to "Help command", not "Register command" with argument 'help'
List<String> remaining = parts.subList(1, parts.size());
CommandDescription childCommand = returnSuitableChild(base, remaining);
CommandDescription childCommand = getSuitableChild(base, remaining);
if (childCommand != null) {
return new FoundCommandResult(childCommand, parts.subList(2, parts.size()), parts.subList(0, 2));
} else if (isSuitableArgumentCount(base, remaining.size())) {
return new FoundCommandResult(base, parts.subList(1, parts.size()), parts.subList(0, 1));
}
// TODO: return getCommandWithSmallestDifference()
return null;
return getCommandWithSmallestDifference(base, parts);
}
// TODO: Return FoundCommandDescription immediately
private CommandDescription getCommandWithSmallestDifference(CommandDescription base, List<String> parts) {
private FoundCommandResult getCommandWithSmallestDifference(CommandDescription base, List<String> parts) {
final String label = parts.get(0);
final int argumentCount = parts.size() - 1;
double minDifference = Double.POSITIVE_INFINITY;
CommandDescription closestCommand = null;
for (CommandDescription child : base.getChildren()) {
double argumentDifference = getArgumentCountDifference(child, argumentCount);
double labelDifference = getLabelDifference(child, label);
double argumentDifference = getArgumentCountDifference(child, argumentCount);
// Weigh argument difference less
double difference = labelDifference + argumentCount / 2;
double difference = labelDifference + argumentDifference / 2;
if (difference < minDifference) {
minDifference = difference;
closestCommand = child;
}
}
return closestCommand;
// TODO: Return the full list of labels and arguments
// TODO: Also compare the base command and suggest it if it's the most similar
return new FoundCommandResult(
closestCommand, null, null, minDifference, FoundCommandResult.ResultStatus.UNKNOWN_LABEL);
}
private static boolean isSuitableArgumentCount(CommandDescription command, int argumentCount) {
int minArgs = CommandUtils.getMinNumberOfArguments(command);
int maxArgs = CommandUtils.getMaxNumberOfArguments(command);
return argumentCount >= minArgs && argumentCount <= maxArgs;
}
private static double getLabelDifference(CommandDescription command, String givenLabel) {
double minDifference = Double.POSITIVE_INFINITY;
for (String commandLabel : command.getLabels()) {
double difference = StringUtils.getDifference(commandLabel, givenLabel);
if (difference < minDifference) {
minDifference = difference;
private CommandDescription getBaseCommand(String label) {
String baseLabel = label.toLowerCase();
for (CommandDescription command : baseCommands) {
if (command.hasLabel(baseLabel)) {
return command;
}
}
return minDifference;
}
private static int getArgumentCountDifference(CommandDescription commandDescription, int givenArgumentsCount) {
return Math.min(
Math.abs(givenArgumentsCount - CommandUtils.getMinNumberOfArguments(commandDescription)),
Math.abs(givenArgumentsCount - CommandUtils.getMaxNumberOfArguments(commandDescription)));
return null;
}
// Is the given command a suitable match for the given parts? parts is for example [changepassword, newpw, newpw]
public CommandDescription returnSuitableChild(CommandDescription baseCommand, List<String> parts) {
private CommandDescription getSuitableChild(CommandDescription baseCommand, List<String> parts) {
if (CollectionUtils.isEmpty(parts)) {
return null;
}
@ -217,14 +212,28 @@ public class CommandHandler {
return null;
}
private CommandDescription getBaseCommand(String label) {
String baseLabel = label.toLowerCase();
for (CommandDescription command : baseCommands) {
if (command.hasLabel(baseLabel)) {
return command;
private static boolean isSuitableArgumentCount(CommandDescription command, int argumentCount) {
int minArgs = CommandUtils.getMinNumberOfArguments(command);
int maxArgs = CommandUtils.getMaxNumberOfArguments(command);
return argumentCount >= minArgs && argumentCount <= maxArgs;
}
private static int getArgumentCountDifference(CommandDescription commandDescription, int givenArgumentsCount) {
return Math.min(
Math.abs(givenArgumentsCount - CommandUtils.getMinNumberOfArguments(commandDescription)),
Math.abs(givenArgumentsCount - CommandUtils.getMaxNumberOfArguments(commandDescription)));
}
private static double getLabelDifference(CommandDescription command, String givenLabel) {
double minDifference = Double.POSITIVE_INFINITY;
for (String commandLabel : command.getLabels()) {
double difference = StringUtils.getDifference(commandLabel, givenLabel);
if (difference < minDifference) {
minDifference = difference;
}
}
return null;
return minDifference;
}
}

View File

@ -1,8 +1,5 @@
package fr.xephi.authme.command;
import java.util.List;
import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.StringUtils;
public final class CommandUtils {
@ -32,26 +29,4 @@ public final class CommandUtils {
return StringUtils.join(" ", labels);
}
public static double getDifference(List<String> labels1, List<String> labels2, boolean fullCompare) {
// Make sure the other reference is correct
if (labels1 == null || labels2 == null) {
return -1;
}
// Get the range to use
int range = Math.min(labels1.size(), labels2.size());
// Get and the difference
if (fullCompare) {
return StringUtils.getDifference(CommandUtils.labelsToString(labels1), CommandUtils.labelsToString(labels2));
}
return StringUtils.getDifference(
labelsToString(CollectionUtils.getRange(labels1, range - 1, 1)),
labelsToString(CollectionUtils.getRange(labels2, range - 1, 1)));
}
}

View File

@ -1,6 +1,6 @@
package fr.xephi.authme.command;
import org.bukkit.command.CommandSender;
import java.util.List;
/**
*/
@ -9,47 +9,39 @@ public class FoundCommandResult {
/**
* The command description instance.
*/
private CommandDescription commandDescription;
/**
* The command reference.
*/
private final CommandParts commandReference;
private final CommandDescription commandDescription;
/**
* The command arguments.
*/
private final CommandParts commandArguments;
private final List<String> arguments;
/**
* The original search query reference.
* The labels used to invoke the command. This may be different for the same {@link ExecutableCommand} instance
* if multiple labels have been defined, e.g. "/authme register" and "/authme reg".
*/
private final CommandParts queryReference;
private final List<String> labels;
private final double difference;
private final ResultStatus resultStatus;
/**
* Constructor.
*
* @param commandDescription The command description.
* @param commandReference The command reference.
* @param commandArguments The command arguments.
* @param queryReference The original query reference.
* @param arguments The command arguments.
* @param labels The original query reference.
*/
public FoundCommandResult(CommandDescription commandDescription, CommandParts commandReference, CommandParts commandArguments, CommandParts queryReference) {
public FoundCommandResult(CommandDescription commandDescription, List<String> arguments, List<String> labels,
double difference, ResultStatus resultStatus) {
this.commandDescription = commandDescription;
this.commandReference = commandReference;
this.commandArguments = commandArguments;
this.queryReference = queryReference;
this.arguments = arguments;
this.labels = labels;
this.difference = difference;
this.resultStatus = resultStatus;
}
/**
* Check whether the command was suitable.
*
* @return True if the command was suitable, false otherwise.
*/
public boolean hasProperArguments() {
// Make sure the command description is set
if (this.commandDescription == null)
return false;
// Get and return the result
return getCommandDescription().getSuitableArgumentsDifference(this.queryReference) == 0;
public FoundCommandResult(CommandDescription commandDescription, List<String> arguments, List<String> labels) {
this(commandDescription, arguments, labels, 0.0, ResultStatus.SUCCESS);
}
/**
@ -61,66 +53,14 @@ public class FoundCommandResult {
return this.commandDescription;
}
/**
* Check whether the command is executable.
*
* @return True if the command is executable, false otherwise.
*/
public boolean isExecutable() {
return commandDescription != null;
}
/**
* Execute the command.
*
* @param sender The command sender that executed the command.
*
* @return True on success, false on failure.
*/
public boolean executeCommand(CommandSender sender) {
// Make sure the command description is valid
if (this.commandDescription == null)
return false;
// Execute the command
return this.commandDescription.execute(sender, this.commandReference, this.commandArguments);
}
/**
* Check whether a command sender has permission to execute the command.
*
* @param sender The command sender.
*
* @return True if the command sender has permission, false otherwise.
*/
public boolean hasPermission(CommandSender sender) {
if (commandDescription == null) {
return false;
} else if (commandDescription.getCommandPermissions() == null) {
return true;
}
// TODO: Move permissions check to the permission package; command package should not define permission-checking
// API
return commandDescription.getCommandPermissions().hasPermission(sender);
}
/**
* Get the command reference.
*
* @return The command reference.
*/
public CommandParts getCommandReference() {
return this.commandReference;
}
/**
* Get the command arguments.
*
* @return The command arguments.
*/
public CommandParts getCommandArguments() {
return this.commandArguments;
public List<String> getArguments() {
return this.arguments;
}
/**
@ -128,22 +68,26 @@ public class FoundCommandResult {
*
* @return Original query reference.
*/
public CommandParts getQueryReference() {
return this.queryReference;
public List<String> getLabels() {
return this.labels;
}
/**
* Get the difference value between the original query and the result reference.
*
* @return The difference value.
*/
public double getDifference() {
// Get the difference through the command found
if (this.commandDescription != null) {
return this.commandDescription.getCommandDifference(this.queryReference);
}
return difference;
}
// Get the difference from the query reference
return CommandUtils.getDifference(queryReference.getList(), commandReference.getList(), true);
public ResultStatus getResultStatus() {
return resultStatus;
}
public enum ResultStatus {
SUCCESS,
INCORRECT_ARGUMENTS,
UNKNOWN_LABEL,
MISSING_BASE_COMMAND
}
}

View File

@ -301,19 +301,24 @@ public class PermissionsManager implements PermissionsService {
/**
* Check if the player has permission for the given permissions node. If no permissions system is used,
* the player has to be OP in order to have the permission.
* Check if the command sender has permission for the given permissions node. If no permissions system is used or
* if the sender is not a player (e.g. console user), the player has to be OP in order to have the permission.
*
* @param player The player.
* @param sender The command sender.
* @param permissionNode The permissions node to verify.
*
* @return True if the player has the permission, false otherwise.
* @return True if the sender has the permission, false otherwise.
*/
public boolean hasPermission(Player player, PermissionNode permissionNode) {
return hasPermission(player, permissionNode, player.isOp());
public boolean hasPermission(CommandSender sender, PermissionNode permissionNode) {
return hasPermission(sender, permissionNode, sender.isOp());
}
public boolean hasPermission(Player player, PermissionNode permissionNode, boolean def) {
public boolean hasPermission(CommandSender sender, PermissionNode permissionNode, boolean def) {
if (!(sender instanceof Player)) {
return def;
}
Player player = (Player) sender;
return hasPermission(player, permissionNode.getNode(), def)
|| hasPermission(player, permissionNode.getWildcardNode().getNode(), def);
}
@ -327,15 +332,17 @@ public class PermissionsManager implements PermissionsService {
return true;
}
public boolean hasPermission(Player player, CommandDescription command) {
public boolean hasPermission(CommandSender sender, CommandDescription command) {
if (command.getCommandPermissions() == null
|| CollectionUtils.isEmpty(command.getCommandPermissions().getPermissionNodes())) {
return true;
}
DefaultPermission defaultPermission = command.getCommandPermissions().getDefaultPermission();
boolean def = evaluateDefaultPermission(defaultPermission, player);
return hasPermission(player, command.getCommandPermissions().getPermissionNodes(), def);
boolean def = evaluateDefaultPermission(defaultPermission, sender);
return (sender instanceof Player)
? hasPermission((Player) sender, command.getCommandPermissions().getPermissionNodes(), def)
: def;
}
public static boolean evaluateDefaultPermission(DefaultPermission defaultPermission, CommandSender sender) {

View File

@ -1,6 +1,7 @@
package fr.xephi.authme.permission;
import fr.xephi.authme.command.CommandDescription;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
/**
@ -11,23 +12,23 @@ public interface PermissionsService {
/**
* Check if the player has the given permission.
*
* @param player The player
* @param sender The command sender
* @param permission The permission node to check
* @param def Default returned if no permissions system is used
*
* @return True if the player has permission
*/
boolean hasPermission(Player player, PermissionNode permission, boolean def);
boolean hasPermission(CommandSender sender, PermissionNode permission, boolean def);
/**
* Check if the player has the permissions for the given command.
*
* @param player The player
* @param sender The command sender
* @param command The command whose permissions should be checked
*
* @return True if the player may execute the command
*/
boolean hasPermission(Player player, CommandDescription command);
boolean hasPermission(CommandSender sender, CommandDescription command);
/**
* Return the permission system the service is working with.

View File

@ -4,22 +4,23 @@ import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Test for {@link CommandParts}.
* Test for {@link CommandUtils}.
*/
public class CommandPartsTest {
public class CommandUtilsTest {
@Test
public void shouldPrintPartsForStringRepresentation() {
// given
CommandParts parts = new CommandParts(Arrays.asList("some", "parts", "for", "test"));
Iterable<String> parts = Arrays.asList("some", "parts", "for", "test");
// when
String str = parts.toString();
String str = CommandUtils.labelsToString(parts);
// then
assertThat(str, equalTo("some parts for test"));
@ -28,10 +29,10 @@ public class CommandPartsTest {
@Test
public void shouldPrintEmptyStringForNoArguments() {
// given
CommandParts parts = new CommandParts(Collections.EMPTY_LIST);
List<String> parts = Collections.EMPTY_LIST;
// when
String str = parts.toString();
String str = CommandUtils.labelsToString(parts);
// then
assertThat(str, equalTo(""));