Initial commit
This commit is contained in:
commit
0303da531e
12 changed files with 1004 additions and 0 deletions
85
src/main/java/com/intellectualsites/commands/Command.java
Normal file
85
src/main/java/com/intellectualsites/commands/Command.java
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2020 IntellectualSites
|
||||
//
|
||||
// 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.intellectualsites.commands;
|
||||
|
||||
import com.intellectualsites.commands.components.CommandComponent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A command consists out of a chain of {@link com.intellectualsites.commands.components.CommandComponent command components}.
|
||||
*/
|
||||
public class Command {
|
||||
|
||||
private final CommandComponent<?>[] components;
|
||||
|
||||
private Command(@Nonnull final CommandComponent<?>[] commandComponents) {
|
||||
this.components = Objects.requireNonNull(commandComponents, "Command components may not be null");
|
||||
if (this.components.length == 0){
|
||||
throw new IllegalArgumentException("At least one command component is required");
|
||||
}
|
||||
// Enforce ordering of command components
|
||||
boolean foundOptional = false;
|
||||
for (final CommandComponent<?> component : this.components) {
|
||||
if (foundOptional && component.isRequired()) {
|
||||
throw new IllegalArgumentException(String.format("Command component '%s' cannot be placed after an optional component", component.getName()));
|
||||
} else if (!component.isRequired()) {
|
||||
foundOptional = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of the command component array
|
||||
*
|
||||
* @return Copy of the command component array
|
||||
*/
|
||||
@Nonnull public CommandComponent<?>[] getComponents() {
|
||||
final CommandComponent<?>[] commandComponents = new CommandComponent<?>[this.components.length];
|
||||
System.arraycopy(this.components, 0, commandComponents, 0, this.components.length);
|
||||
return commandComponents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the longest chain of similar components for
|
||||
* two commands
|
||||
*
|
||||
* @return List containing the longest shared component chain
|
||||
*/
|
||||
public List<CommandComponent<?>> getSharedComponentChain(@Nonnull final Command other) {
|
||||
final List<CommandComponent<?>> commandComponents = new LinkedList<>();
|
||||
for (int i = 0; i < this.components.length && i < other.components.length; i++) {
|
||||
if (this.components[i].equals(other.components[i])) {
|
||||
commandComponents.add(this.components[i]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return commandComponents;
|
||||
}
|
||||
|
||||
}
|
||||
155
src/main/java/com/intellectualsites/commands/CommandTree.java
Normal file
155
src/main/java/com/intellectualsites/commands/CommandTree.java
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
package com.intellectualsites.commands;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.intellectualsites.commands.components.CommandComponent;
|
||||
import com.intellectualsites.commands.components.StaticComponent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tree containing all commands and command paths
|
||||
*/
|
||||
public class CommandTree {
|
||||
|
||||
private final Node<CommandComponent<?>> internalTree = new Node<>(null);
|
||||
|
||||
private CommandTree() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new command tree instance
|
||||
*
|
||||
* @return New command tree
|
||||
*/
|
||||
@Nonnull public static CommandTree newTree() {
|
||||
return new CommandTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new command into the command tree
|
||||
*
|
||||
* @param command Command to insert
|
||||
*/
|
||||
public void insertCommand(@Nonnull final Command command) {
|
||||
Node<CommandComponent<?>> node = this.internalTree;
|
||||
for (final CommandComponent<?> component : command.getComponents()) {
|
||||
Node<CommandComponent<?>> tempNode = node.getChild(component);
|
||||
if (tempNode == null) {
|
||||
tempNode = node.addChild(component);
|
||||
}
|
||||
node = tempNode;
|
||||
}
|
||||
if (node.getValue() != null) {
|
||||
node.getValue().setOwningCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through all commands and register them, and verify the
|
||||
* command tree contracts
|
||||
*/
|
||||
public void verifyAndRegister() {
|
||||
// All top level commands are supposed to be registered in the command manager
|
||||
this.internalTree.children.stream().map(Node::getValue).forEach(commandComponent -> {
|
||||
if (!(commandComponent instanceof StaticComponent)) {
|
||||
throw new IllegalStateException("Top level command component cannot be a variable");
|
||||
}
|
||||
// TODO: Register in the command handler
|
||||
});
|
||||
this.checkAmbiguity(this.internalTree);
|
||||
// Verify that all leaf nodes have command registered
|
||||
this.getLeaves(this.internalTree).forEach(leaf -> {
|
||||
if (leaf.getOwningCommand() == null) {
|
||||
// TODO: Custom exception type
|
||||
throw new IllegalStateException("Leaf node does not have associated owning command");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void checkAmbiguity(@Nonnull final Node<CommandComponent<?>> node) {
|
||||
if (node.isLeaf()) {
|
||||
return;
|
||||
}
|
||||
final int size = node.children.size();
|
||||
for (final Node<CommandComponent<?>> child : node.children) {
|
||||
if (child.getValue() != null && !child.getValue().isRequired() && size > 1) {
|
||||
// TODO: Use a custom exception type here
|
||||
throw new IllegalStateException("Ambiguous command node found: " + node.getValue());
|
||||
}
|
||||
}
|
||||
node.children.forEach(this::checkAmbiguity);
|
||||
}
|
||||
|
||||
private List<CommandComponent<?>> getLeaves(@Nonnull final Node<CommandComponent<?>> node) {
|
||||
final List<CommandComponent<?>> leaves = new LinkedList<>();
|
||||
if (node.isLeaf()) {
|
||||
if (node.getValue() != null) {
|
||||
leaves.add(node.getValue());
|
||||
}
|
||||
} else {
|
||||
node.children.forEach(child -> leaves.addAll(getLeaves(child)));
|
||||
}
|
||||
return leaves;
|
||||
}
|
||||
|
||||
|
||||
private static final class Node<T> {
|
||||
|
||||
private final List<Node<T>> children = new LinkedList<>();
|
||||
private final T value;
|
||||
|
||||
private Node(@Nullable final T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private List<Node<T>> getChildren() {
|
||||
return Collections.unmodifiableList(this.children);
|
||||
}
|
||||
|
||||
@Nonnull private Node<T> addChild(@Nonnull final T child) {
|
||||
final Node<T> node = new Node<>(child);
|
||||
this.children.add(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
@Nullable private Node<T> getChild(@Nonnull final T type) {
|
||||
for (final Node<T> child : this.children) {
|
||||
if (type.equals(child.getValue())) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isLeaf() {
|
||||
return this.children.isEmpty();
|
||||
}
|
||||
|
||||
@Nullable public T getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final Node<?> node = (Node<?>) o;
|
||||
return Objects.equal(getChildren(), node.getChildren()) &&
|
||||
Objects.equal(getValue(), node.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(getChildren(), getValue());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2020 IntellectualSites
|
||||
//
|
||||
// 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.intellectualsites.commands.components;
|
||||
|
||||
import com.intellectualsites.commands.Command;
|
||||
import com.intellectualsites.commands.parser.ComponentParser;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A component that belongs to a command
|
||||
*
|
||||
* @param <T> The type that the component parses into
|
||||
*/
|
||||
public class CommandComponent<T> {
|
||||
|
||||
private static final Pattern NAME_PATTERN = Pattern.compile("[A-Za-z0-9]+");
|
||||
|
||||
/**
|
||||
* Indicates whether or not the component is required
|
||||
* or not. All components prior to any other required
|
||||
* component must also be required, such that the predicate
|
||||
* (∀ c_i ∈ required)({c_0, ..., c_i-1} ⊂ required) holds true,
|
||||
* where {c_0, ..., c_n-1} is the set of command components.
|
||||
*/
|
||||
private final boolean required;
|
||||
/**
|
||||
* The command component name. This might be exposed
|
||||
* to command senders and so should be choosen carefully.
|
||||
*/
|
||||
private final String name;
|
||||
/**
|
||||
* The parser that is used to parse the command input
|
||||
* into the corresponding command type
|
||||
*/
|
||||
private final ComponentParser<T> parser;
|
||||
|
||||
private Command owningCommand;
|
||||
|
||||
CommandComponent(final boolean required, @Nonnull final String name,
|
||||
@Nonnull final ComponentParser<T> parser) {
|
||||
this.required = required;
|
||||
this.name = Objects.requireNonNull(name, "Name may not be null");
|
||||
if (!NAME_PATTERN.asPredicate().test(name)) {
|
||||
throw new IllegalArgumentException("Name must be alphanumeric");
|
||||
}
|
||||
this.parser = Objects.requireNonNull(parser, "Parser may not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new command component
|
||||
*
|
||||
* @param clazz Argument class
|
||||
* @param <T> Argument Type
|
||||
* @return Component builder
|
||||
*/
|
||||
@Nonnull public static <T> CommandComponent.Builder<T> ofType(@Nonnull final Class<T> clazz) {
|
||||
return new Builder<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether or not the command component is required
|
||||
*
|
||||
* @return {@code true} if the component is required, {@code false} if not
|
||||
*/
|
||||
public boolean isRequired() {
|
||||
return this.required;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command component name;
|
||||
*
|
||||
* @return Component name
|
||||
*/
|
||||
@Nonnull public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parser that is used to parse the command input
|
||||
* into the corresponding command type
|
||||
*
|
||||
* @return Command parser
|
||||
*/
|
||||
@Nonnull public ComponentParser<T> getParser() {
|
||||
return this.parser;
|
||||
}
|
||||
|
||||
@Nonnull @Override public String toString() {
|
||||
return String.format("CommandComponent{name=%s}", this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the owning command
|
||||
*
|
||||
* @return Owning command
|
||||
*/
|
||||
@Nullable public Command getOwningCommand() {
|
||||
return this.owningCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the owning command
|
||||
*
|
||||
* @param owningCommand Owning command
|
||||
*/
|
||||
public void setOwningCommand(@Nonnull final Command owningCommand) {
|
||||
if (this.owningCommand != null) {
|
||||
throw new IllegalStateException("Cannot replace owning command");
|
||||
}
|
||||
this.owningCommand = owningCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final CommandComponent<?> that = (CommandComponent<?>) o;
|
||||
return isRequired() == that.isRequired() && com.google.common.base.Objects.equal(getName(), that.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return com.google.common.base.Objects.hashCode(isRequired(), getName());
|
||||
}
|
||||
|
||||
|
||||
public static class Builder<T> {
|
||||
|
||||
private String name;
|
||||
private boolean required = true;
|
||||
private ComponentParser<T> parser;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the component name. Must be alphanumeric
|
||||
*
|
||||
* @param name Alphanumeric component name
|
||||
* @return Builder instance
|
||||
*/
|
||||
@Nonnull public Builder<T> named(@Nonnull final String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the component is required.
|
||||
* All components prior to any other required
|
||||
* component must also be required, such that the predicate
|
||||
* (∀ c_i ∈ required)({c_0, ..., c_i-1} ⊂ required) holds true,
|
||||
* where {c_0, ..., c_n-1} is the set of command components.
|
||||
*
|
||||
* @return Builder instance
|
||||
*/
|
||||
@Nonnull public Builder<T> asRequired() {
|
||||
this.required = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the component is optional.
|
||||
* All components prior to any other required
|
||||
* component must also be required, such that the predicate
|
||||
* (∀ c_i ∈ required)({c_0, ..., c_i-1} ⊂ required) holds true,
|
||||
* where {c_0, ..., c_n-1} is the set of command components.
|
||||
*
|
||||
* @return Builder instance
|
||||
*/
|
||||
@Nonnull public Builder<T> asOptional() {
|
||||
this.required = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the component parser
|
||||
*
|
||||
* @param parser Component parser
|
||||
* @return Builder instance
|
||||
*/
|
||||
@Nonnull public Builder<T> withParser(@Nonnull final ComponentParser<T> parser) {
|
||||
this.parser = Objects.requireNonNull(parser, "Parser may not be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a command component from the builder settings
|
||||
*
|
||||
* @return Constructed component
|
||||
*/
|
||||
@Nonnull public CommandComponent<T> build() {
|
||||
return new CommandComponent<>(this.required, this.name, this.parser);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2020 IntellectualSites
|
||||
//
|
||||
// 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.intellectualsites.commands.components;
|
||||
|
||||
import com.intellectualsites.commands.parser.ComponentParser;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public final class StaticComponent extends CommandComponent<String> {
|
||||
|
||||
private StaticComponent(final boolean required, @Nonnull final String name, @Nonnull final String ... aliases) {
|
||||
super(required, name, new StaticComponentParser(name, aliases));
|
||||
}
|
||||
|
||||
|
||||
private static final class StaticComponentParser implements ComponentParser<String> {
|
||||
|
||||
private StaticComponentParser(@Nonnull final String name, @Nonnull final String ... aliases) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2020 IntellectualSites
|
||||
//
|
||||
// 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.intellectualsites.commands.parser;
|
||||
|
||||
public interface ComponentParser<T> {
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue