Commands - fix child not being registered in parent via Builder

- Create test to ensure that commands don't define the same binding
- Create stricter attribute validation in builder: throw an error if required field was not initialized
This commit is contained in:
ljacqu 2015-11-29 10:24:32 +01:00
parent a124e8f283
commit 6a94135f64
2 changed files with 90 additions and 48 deletions

View File

@ -6,6 +6,7 @@ import org.bukkit.command.CommandSender;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
@ -144,6 +145,11 @@ public class CommandDescription {
this.arguments = arguments; this.arguments = arguments;
this.noArgumentMaximum = noArgumentMaximum; this.noArgumentMaximum = noArgumentMaximum;
this.permissions = permissions; this.permissions = permissions;
if (parent != null) {
// Passing `this` in constructor is not very nice; consider creating a "static create()" method instead
parent.addChild(this);
}
} }
/** /**
@ -246,9 +252,9 @@ public class CommandDescription {
} }
/** /**
* Get the absolute command label, without a slash. * Get the absolute command label, without a starting slash.
* *
* @return the absolute label * @return The absolute label
*/ */
public String getAbsoluteLabel() { public String getAbsoluteLabel() {
return getAbsoluteLabel(false); return getAbsoluteLabel(false);
@ -445,20 +451,6 @@ public class CommandDescription {
return this.children; return this.children;
} }
/**
* Set the children of this command.
*
* @param children New command children. Null to remove all children.
*/
public void setChildren(List<CommandDescription> children) {
// Check whether the children list should be cleared
if (children == null)
this.children.clear();
else
this.children = children;
}
/** /**
* Add a child to the command description. * Add a child to the command description.
* *
@ -757,7 +749,7 @@ public class CommandDescription {
} }
/** /**
* Get the command permissions. * Get the command permissions. Return null if the command doesn't require any permission.
* *
* @return The command permissions. * @return The command permissions.
*/ */
@ -796,10 +788,10 @@ public class CommandDescription {
*/ */
public CommandDescription build() { public CommandDescription build() {
return new CommandDescription( return new CommandDescription(
valueOrEmptyList(labels), getOrThrow(labels, "labels"),
firstNonNull(description, ""), firstNonNull(description, ""),
firstNonNull(detailedDescription, ""), firstNonNull(detailedDescription, ""),
firstNonNull(executableCommand, null), // TODO ljacqu 20151128: May `executableCommand` be null? getOrThrow(executableCommand, "executableCommand"),
firstNonNull(parent, null), firstNonNull(parent, null),
arguments, arguments,
noArgumentMaximum, noArgumentMaximum,
@ -857,16 +849,28 @@ public class CommandDescription {
return new ArrayList<>(Arrays.asList(items)); return new ArrayList<>(Arrays.asList(items));
} }
private static <T> List<T> valueOrEmptyList(List<T> givenList) {
if (givenList != null) {
return givenList;
}
return new ArrayList<>();
}
private static <T> T firstNonNull(T first, T second) { private static <T> T firstNonNull(T first, T second) {
return first != null ? first : second; return first != null ? first : second;
} }
private static <T> T getOrThrow(T element, String elementName) {
if (!isEmpty(element)) {
return element;
}
throw new RuntimeException("The element '" + elementName + "' may not be empty in CommandDescription");
}
private static <T> boolean isEmpty(T element) {
if (element == null) {
return true;
} else if (element instanceof Collection<?>) {
return ((Collection<?>) element).isEmpty();
} else if (element instanceof String) {
return StringUtils.isEmpty((String) element);
}
return false;
}
} }
} }

View File

@ -1,9 +1,11 @@
package fr.xephi.authme.command; package fr.xephi.authme.command;
import fr.xephi.authme.util.StringUtils; import fr.xephi.authme.util.StringUtils;
import org.junit.BeforeClass;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -24,12 +26,18 @@ public class CommandManagerTest {
* Defines the maximum allowed depths for nesting CommandDescription instances. * Defines the maximum allowed depths for nesting CommandDescription instances.
* Note that the depth starts at 0 (e.g. /authme), so a depth of 2 is something like /authme hello world * Note that the depth starts at 0 (e.g. /authme), so a depth of 2 is something like /authme hello world
*/ */
private static int MAX_ALLOWED_DEPTH = 2; private static int MAX_ALLOWED_DEPTH = 1;
private static CommandManager manager;
@BeforeClass
public static void initializeCommandManager() {
manager = new CommandManager(true);
}
@Test @Test
public void shouldInitializeCommands() { public void shouldInitializeCommands() {
// given/when // given/when
CommandManager manager = new CommandManager(true);
int commandCount = manager.getCommandDescriptionCount(); int commandCount = manager.getCommandDescriptionCount();
List<CommandDescription> commands = manager.getCommandDescriptions(); List<CommandDescription> commands = manager.getCommandDescriptions();
@ -46,8 +54,7 @@ public class CommandManagerTest {
@Test @Test
public void shouldNotBeNestedExcessively() { public void shouldNotBeNestedExcessively() {
// given // given
CommandManager manager = new CommandManager(true); BiConsumer descriptionTester = new BiConsumer() {
Consumer descriptionTester = new Consumer() {
@Override @Override
public void accept(CommandDescription command, int depth) { public void accept(CommandDescription command, int depth) {
assertThat(depth <= MAX_ALLOWED_DEPTH, equalTo(true)); assertThat(depth <= MAX_ALLOWED_DEPTH, equalTo(true));
@ -55,25 +62,28 @@ public class CommandManagerTest {
}; };
// when/then // when/then
List<CommandDescription> commands = manager.getCommandDescriptions(); walkThroughCommands(manager.getCommandDescriptions(), descriptionTester);
walkThroughCommands(commands, descriptionTester);
} }
@Test @Test
@Ignore
public void shouldNotDefineSameLabelTwice() { public void shouldNotDefineSameLabelTwice() {
// given // given
CommandManager manager = new CommandManager(true); final Set<String> commandMappings = new HashSet<>();
Set<String> commandMappings = new HashSet<>(); BiConsumer uniqueMappingTester = new BiConsumer() {
Consumer uniqueMappingTester = new Consumer() {
@Override @Override
public void accept(CommandDescription command, int depth) { public void accept(CommandDescription command, int depth) {
int initialSize = commandMappings.size();
List<String> newMappings = getAbsoluteLabels(command);
commandMappings.addAll(newMappings);
// Set only contains unique entries, so we just check after adding all new mappings that the size
// of the Set corresponds to our expectation
assertThat("All bindings are unique for command with bindings '" + command.getLabels() + "'",
commandMappings.size() == initialSize + newMappings.size(), equalTo(true));
} }
}; };
// when // when/then
// TODO finish this walkThroughCommands(manager.getCommandDescriptions(), uniqueMappingTester);
} }
/** /**
@ -83,8 +93,7 @@ public class CommandManagerTest {
@Test @Test
public void shouldHaveDescription() { public void shouldHaveDescription() {
// given // given
CommandManager manager = new CommandManager(true); BiConsumer descriptionTester = new BiConsumer() {
Consumer descriptionTester = new Consumer() {
@Override @Override
public void accept(CommandDescription command, int depth) { public void accept(CommandDescription command, int depth) {
assertThat("has description", StringUtils.isEmpty(command.getDescription()), equalTo(false)); assertThat("has description", StringUtils.isEmpty(command.getDescription()), equalTo(false));
@ -97,17 +106,20 @@ public class CommandManagerTest {
} }
}; };
// when // when/then
List<CommandDescription> commands = manager.getCommandDescriptions(); walkThroughCommands(manager.getCommandDescriptions(), descriptionTester);
walkThroughCommands(commands, descriptionTester);
} }
/**
* Check that the implementation of {@link ExecutableCommand} a command points to is the same for each type:
* it is inefficient to instantiate the same type multiple times.
*/
@Test @Test
public void shouldNotHaveMultipleInstancesOfSameExecutableCommandSubType() { public void shouldNotHaveMultipleInstancesOfSameExecutableCommandSubType() {
// given // given
final Map<Class<? extends ExecutableCommand>, ExecutableCommand> implementations = new HashMap<>(); final Map<Class<? extends ExecutableCommand>, ExecutableCommand> implementations = new HashMap<>();
CommandManager manager = new CommandManager(true); CommandManager manager = new CommandManager(true);
Consumer descriptionTester = new Consumer() { BiConsumer descriptionTester = new BiConsumer() {
@Override @Override
public void accept(CommandDescription command, int depth) { public void accept(CommandDescription command, int depth) {
assertThat(command.getExecutableCommand(), not(nullValue())); assertThat(command.getExecutableCommand(), not(nullValue()));
@ -125,6 +137,8 @@ public class CommandManagerTest {
// when // when
List<CommandDescription> commands = manager.getCommandDescriptions(); List<CommandDescription> commands = manager.getCommandDescriptions();
// then
walkThroughCommands(commands, descriptionTester); walkThroughCommands(commands, descriptionTester);
} }
@ -132,11 +146,11 @@ public class CommandManagerTest {
// ------------ // ------------
// Helper methods // Helper methods
// ------------ // ------------
private static void walkThroughCommands(List<CommandDescription> commands, Consumer consumer) { private static void walkThroughCommands(List<CommandDescription> commands, BiConsumer consumer) {
walkThroughCommands(commands, consumer, 0); walkThroughCommands(commands, consumer, 0);
} }
private static void walkThroughCommands(List<CommandDescription> commands, Consumer consumer, int depth) { private static void walkThroughCommands(List<CommandDescription> commands, BiConsumer consumer, int depth) {
for (CommandDescription command : commands) { for (CommandDescription command : commands) {
consumer.accept(command, depth); consumer.accept(command, depth);
if (command.hasChildren()) { if (command.hasChildren()) {
@ -154,8 +168,32 @@ public class CommandManagerTest {
return false; return false;
} }
private interface Consumer { private interface BiConsumer {
void accept(CommandDescription command, int depth); void accept(CommandDescription command, int depth);
} }
/**
* Get the absolute label that a command defines. Note: Assumes that only the passed command might have
* multiple labels; only considering the first label for all of the command's parents.
*
* @param command The command to verify
*
* @return The full command binding
*/
private static List<String> getAbsoluteLabels(CommandDescription command) {
String parentPath = "";
CommandDescription elem = command.getParent();
while (elem != null) {
parentPath = elem.getLabels().get(0) + " " + parentPath;
elem = elem.getParent();
}
parentPath = parentPath.trim();
List<String> bindings = new ArrayList<>(command.getLabels().size());
for (String label : command.getLabels()) {
bindings.add(StringUtils.join(" ", parentPath, label));
}
return bindings;
}
} }