diff --git a/cloud-annotations/pom.xml b/cloud-annotations/pom.xml
new file mode 100644
index 00000000..fb988a60
--- /dev/null
+++ b/cloud-annotations/pom.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+ cloud
+ com.intellectualsites
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ cloud-annotations
+
+
+ com.intellectualsites
+ cloud-core
+ 1.0-SNAPSHOT
+
+
+
+
diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/AnnotationParser.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/AnnotationParser.java
new file mode 100644
index 00000000..12af8e52
--- /dev/null
+++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/AnnotationParser.java
@@ -0,0 +1,210 @@
+//
+// MIT License
+//
+// Copyright (c) 2020 Alexander Söderberg
+//
+// 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.annotations;
+
+import com.google.common.reflect.TypeToken;
+import com.intellectualsites.commands.Command;
+import com.intellectualsites.commands.CommandManager;
+import com.intellectualsites.commands.arguments.parser.ArgumentParser;
+import com.intellectualsites.commands.arguments.parser.ParserParameters;
+import com.intellectualsites.commands.meta.CommandMeta;
+
+import javax.annotation.Nonnull;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+/**
+ * Parser that parses class instances {@link Command commands}
+ *
+ * @param Command sender type
+ * @param Command meta type
+ */
+public class AnnotationParser {
+
+ private static final Predicate PATTERN_ARGUMENT_LITERAL = Pattern.compile("([A-Za-z0-9]+)(|([A-Za-z0-9]+))*")
+ .asPredicate();
+ private static final Predicate PATTERN_ARGUMENT_REQUIRED = Pattern.compile("<(A-Za-z0-9]+)>").asPredicate();
+ private static final Predicate PATTERN_ARGUMENT_OPTIONAL = Pattern.compile("\\[([A-Za-z0-9]+)\\]").asPredicate();
+
+ private final CommandManager manager;
+
+ /**
+ * Construct a new annotation parser
+ *
+ * @param manager Command manager instance
+ */
+ public AnnotationParser(@Nonnull final CommandManager manager) {
+ this.manager = manager;
+ }
+
+ /**
+ * Scan a class instance of {@link CommandMethod} annotations and attempt to
+ * compile them into {@link Command} instances
+ *
+ * @param instance Instance to scan
+ * @param Type of the instance
+ * @return Collection of parsed annotations
+ */
+ @Nonnull
+ public Collection> parse(@Nonnull final T instance) {
+ final Method[] methods = instance.getClass().getMethods();
+ final Collection commandMethodPairs = new ArrayList<>();
+ for (final Method method : methods) {
+ final CommandMethod commandMethod = method.getAnnotation(CommandMethod.class);
+ if (commandMethod == null) {
+ continue;
+ }
+ if (method.getReturnType() != Void.TYPE) {
+ throw new IllegalArgumentException(String.format("@CommandMethod annotated method '%s' has non-void return type",
+ method.getName()));
+ }
+ commandMethodPairs.add(new CommandMethodPair(method, commandMethod));
+ }
+ return this.construct(commandMethodPairs);
+ }
+
+ @Nonnull
+ private Collection> construct(@Nonnull final Collection methodPairs) {
+ final Collection> commands = new ArrayList<>();
+ for (final CommandMethodPair commandMethodPair : methodPairs) {
+ final CommandMethod commandMethod = commandMethodPair.getCommandMethod();
+ final Method method = commandMethodPair.getMethod();
+ final Map tokens = this.parseSyntax(commandMethod.value());
+ Command.Builder builder = this.manager.commandBuilder(commandMethod.value(),
+ Collections.emptyList(),
+ manager.createDefaultCommandMeta());
+ final Collection arguments = this.getArguments(method);
+ for (final ArgumentParameterPair argumentPair : arguments) {
+ final Parameter parameter = argumentPair.getParameter();
+ final Collection annotations = Arrays.asList(parameter.getAnnotations());
+ final TypeToken> token = TypeToken.of(parameter.getParameterizedType());
+ final ParserParameters parameters = this.manager.getParserRegistry()
+ .parseAnnotations(token, annotations);
+ final ArgumentParser parser = this.manager.getParserRegistry()
+ .createParser(token, parameters)
+ .orElseThrow(() -> new IllegalArgumentException(
+ String.format("Parameter '%s' in method '%s' "
+ + "has parser '%s' but no parser exists "
+ + "for that type",
+ parameter.getName(), method.getName(),
+ token.toString())));
+ }
+
+ }
+ return commands;
+ }
+
+ @Nonnull
+ private LinkedHashMap parseSyntax(@Nonnull final String syntax) {
+ final StringTokenizer stringTokenizer = new StringTokenizer(syntax, " ");
+ final LinkedHashMap map = new LinkedHashMap<>();
+ while (stringTokenizer.hasMoreTokens()) {
+ final String token = stringTokenizer.nextToken();
+ if (PATTERN_ARGUMENT_REQUIRED.test(token)) {
+ map.put(token.substring(1, token.length() - 1), ArgumentMode.REQUIRED);
+ } else if (PATTERN_ARGUMENT_OPTIONAL.test(token)) {
+ map.put(token.substring(1, token.length() - 1), ArgumentMode.OPTIONAL);
+ } else {
+ final String[] literals = token.split("\\|");
+ /* Actually use the other literals as well */
+ map.put(literals[0], ArgumentMode.LITERAL);
+ }
+ }
+ return map;
+ }
+
+ @Nonnull
+ private Collection getArguments(@Nonnull final Method method) {
+ final Collection arguments = new ArrayList<>();
+ for (final Parameter parameter : method.getParameters()) {
+ if (!parameter.isAnnotationPresent(Argument.class)) {
+ continue;
+ }
+ arguments.add(new ArgumentParameterPair(parameter, parameter.getAnnotation(Argument.class)));
+ }
+ return arguments;
+ }
+
+
+ private static final class CommandMethodPair {
+
+ private final Method method;
+ private final CommandMethod commandMethod;
+
+ private CommandMethodPair(@Nonnull final Method method, @Nonnull final CommandMethod commandMethod) {
+ this.method = method;
+ this.commandMethod = commandMethod;
+ }
+
+ @Nonnull
+ private Method getMethod() {
+ return this.method;
+ }
+
+ @Nonnull
+ private CommandMethod getCommandMethod() {
+ return this.commandMethod;
+ }
+
+ }
+
+
+ private static final class ArgumentParameterPair {
+
+ private final Parameter parameter;
+ private final Argument argument;
+
+ private ArgumentParameterPair(@Nonnull final Parameter parameter, @Nonnull final Argument argument) {
+ this.parameter = parameter;
+ this.argument = argument;
+ }
+
+ @Nonnull
+ private Parameter getParameter() {
+ return this.parameter;
+ }
+
+ @Nonnull
+ private Argument getArgument() {
+ return this.argument;
+ }
+
+ }
+
+
+ private enum ArgumentMode {
+ LITERAL, OPTIONAL, REQUIRED
+ }
+
+}
diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/Argument.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/Argument.java
new file mode 100644
index 00000000..8fb2fc4d
--- /dev/null
+++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/Argument.java
@@ -0,0 +1,59 @@
+//
+// MIT License
+//
+// Copyright (c) 2020 Alexander Söderberg
+//
+// 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to indicate that a method parameter is a command argument
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface Argument {
+
+ /**
+ * Argument name
+ *
+ * @return Argument name
+ */
+ String value();
+
+ /**
+ * Whether or not the argument is required
+ *
+ * @return {@code true} if the argument is required, else {@code false}
+ */
+ boolean required() default true;
+
+ /**
+ * Name of the argument parser
+ *
+ * @return Argument name
+ */
+ String parserName() default "";
+
+}
diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/CommandMethod.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/CommandMethod.java
new file mode 100644
index 00000000..b9746b24
--- /dev/null
+++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/CommandMethod.java
@@ -0,0 +1,59 @@
+//
+// MIT License
+//
+// Copyright (c) 2020 Alexander Söderberg
+//
+// 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Used to declare a class method as a command method
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface CommandMethod {
+
+ /**
+ * Command syntax
+ *
+ * @return Command syntax
+ */
+ String value();
+
+ /**
+ * The command permission node
+ *
+ * @return Command permission node
+ */
+ String permission() default "";
+
+ /**
+ * The required sender
+ *
+ * @return Required sender
+ */
+ Class> requiredSender() default Object.class;
+
+}
diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/package-info.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/package-info.java
new file mode 100644
index 00000000..fb11c77f
--- /dev/null
+++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/package-info.java
@@ -0,0 +1,28 @@
+//
+// MIT License
+//
+// Copyright (c) 2020 Alexander Söderberg
+//
+// 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.
+//
+
+/**
+ * Annotation parsing related classes
+ */
+package com.intellectualsites.commands.annotations;
diff --git a/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java b/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java
index c19bb20e..9bcd5cce 100644
--- a/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java
+++ b/cloud-core/src/main/java/com/intellectualsites/commands/CommandManager.java
@@ -45,10 +45,10 @@ import com.intellectualsites.services.ServicePipeline;
import com.intellectualsites.services.State;
import javax.annotation.Nonnull;
+import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
-import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
@@ -212,7 +212,7 @@ public abstract class CommandManager {
*/
@Nonnull
public Command.Builder commandBuilder(@Nonnull final String name,
- @Nonnull final Set aliases,
+ @Nonnull final Collection aliases,
@Nonnull final M meta) {
return Command.newBuilder(name, meta, aliases.toArray(new String[0]));
}
diff --git a/pom.xml b/pom.xml
index 89243148..8a6ef31e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,6 +16,7 @@
cloud-minecraft/cloud-bukkit
cloud-minecraft/cloud-paper
cloud-minecraft/cloud-brigadier
+ cloud-annotations
pom
2020