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 index a5ee8413..12345b1b 100644 --- a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/AnnotationParser.java +++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/AnnotationParser.java @@ -37,19 +37,14 @@ import com.intellectualsites.commands.meta.CommandMeta; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationTargetException; 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.LinkedHashMap; -import java.util.List; import java.util.Map; -import java.util.StringTokenizer; import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Pattern; /** * Parser that parses class instances {@link com.intellectualsites.commands.Command commands} @@ -58,18 +53,13 @@ import java.util.regex.Pattern; */ public final 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 SyntaxParser syntaxParser = new SyntaxParser(); + private final ArgumentExtractor argumentExtractor = new ArgumentExtractor(); - - private final Function metaMapper; private final CommandManager manager; private final Map, Function> annotationMappers; private final Class commandSenderClass; + private final MetaFactory metaFactory; /** * Construct a new annotation parser @@ -86,7 +76,7 @@ public final class AnnotationParser { @Nonnull final Function metaMapper) { this.commandSenderClass = commandSenderClass; this.manager = manager; - this.metaMapper = metaMapper; + this.metaFactory = new MetaFactory(this, metaMapper); this.annotationMappers = Maps.newHashMap(); this.registerAnnotationMapper(Description.class, d -> ParserParameters.single(StandardParameters.DESCRIPTION, d.value())); } @@ -103,20 +93,6 @@ public final class AnnotationParser { this.annotationMappers.put(annotation, mapper); } - @Nonnull - private CommandMeta createMeta(@Nonnull final Annotation[] annotations) { - final ParserParameters parameters = ParserParameters.empty(); - for (final Annotation annotation : annotations) { - @SuppressWarnings("ALL") final Function function = this.annotationMappers.get(annotation.annotationType()); - if (function == null) { - continue; - } - //noinspection unchecked - parameters.merge((ParserParameters) function.apply(annotation)); - } - return this.metaMapper.apply(parameters); - } - /** * Scan a class instance of {@link CommandMethod} annotations and attempt to * compile them into {@link Command} instances @@ -160,15 +136,15 @@ public final class AnnotationParser { for (final CommandMethodPair commandMethodPair : methodPairs) { final CommandMethod commandMethod = commandMethodPair.getCommandMethod(); final Method method = commandMethodPair.getMethod(); - final LinkedHashMap tokens = this.parseSyntax(commandMethod.value()); + final LinkedHashMap tokens = this.syntaxParser.apply(commandMethod.value()); /* Determine command name */ final String commandToken = commandMethod.value().split(" ")[0].split("\\|")[0]; @SuppressWarnings("ALL") final CommandManager manager = this.manager; @SuppressWarnings("ALL") Command.Builder builder = manager.commandBuilder(commandToken, tokens.get(commandToken).getMinor(), - this.createMeta(method.getAnnotations())); - final Collection arguments = this.getArguments(method); + this.metaFactory.apply(method.getAnnotations())); + final Collection arguments = this.argumentExtractor.apply(method); final Map> commandArguments = Maps.newHashMap(); final Map, String> argumentDescriptions = Maps.newHashMap(); /* Go through all annotated parameters and build up the argument tree */ @@ -198,7 +174,6 @@ public final class AnnotationParser { } final String description = argumentDescriptions.getOrDefault(argument, ""); - builder = builder.argument(argument, description); } } @@ -220,37 +195,14 @@ public final class AnnotationParser { } else if (senderType != null) { builder = builder.withSenderType(senderType); } - /* Construct the handler */ - final CommandExecutionHandler commandExecutionHandler = commandContext -> { - final List parameters = new ArrayList<>(method.getParameterCount()); - for (final Parameter parameter : method.getParameters()) { - if (parameter.isAnnotationPresent(Argument.class)) { - final Argument argument = parameter.getAnnotation(Argument.class); - final CommandArgument commandArgument = commandArguments.get(argument.value()); - if (commandArgument.isRequired()) { - parameters.add(commandContext.getRequired(argument.value())); - } else { - final Object optional = commandContext.get(argument.value()).orElse(null); - parameters.add(optional); - } - } else { - if (parameter.getType().isAssignableFrom(commandContext.getSender().getClass())) { - parameters.add(commandContext.getSender()); - } else { - throw new IllegalArgumentException(String.format( - "Unknown command parameter '%s' in method '%s'", - parameter.getName(), method.getName() - )); - } - } - } - try { - method.invoke(instance, parameters.toArray()); - } catch (final IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); - } - }; - builder = builder.handler(commandExecutionHandler); + try { + /* Construct the handler */ + final CommandExecutionHandler commandExecutionHandler + = new MethodCommandExecutionHandler<>(instance, commandArguments, method); + builder = builder.handler(commandExecutionHandler); + } catch (final Exception e) { + throw new RuntimeException("Failed to construct command execution handler", e); + } commands.add(builder.build()); } return commands; @@ -309,126 +261,8 @@ public final class AnnotationParser { } @Nonnull - 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(); - String major; - List minor = new ArrayList<>(); - ArgumentMode mode; - if (PATTERN_ARGUMENT_REQUIRED.test(token)) { - major = token.substring(1, token.length() - 1); - mode = ArgumentMode.REQUIRED; - } else if (PATTERN_ARGUMENT_OPTIONAL.test(token)) { - major = token.substring(1, token.length() - 1); - mode = ArgumentMode.OPTIONAL; - } else if (PATTERN_ARGUMENT_LITERAL.test(token)) { - final String[] literals = token.split("\\|"); - /* Actually use the other literals as well */ - major = literals[0]; - minor.addAll(Arrays.asList(literals).subList(1, literals.length)); - mode = ArgumentMode.LITERAL; - } else { - throw new IllegalArgumentException(String.format("Unrecognizable syntax token '%s'", syntax)); - } - map.put(major, new SyntaxFragment(major, minor, mode)); - } - 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; - } - - - enum ArgumentMode { - LITERAL, - OPTIONAL, - REQUIRED - } - - 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 static final class SyntaxFragment { - - private final String major; - private final List minor; - private final ArgumentMode argumentMode; - - private SyntaxFragment(@Nonnull final String major, - @Nonnull final List minor, - @Nonnull final ArgumentMode argumentMode) { - this.major = major; - this.minor = minor; - this.argumentMode = argumentMode; - } - - @Nonnull - private String getMajor() { - return this.major; - } - - @Nonnull - private List getMinor() { - return this.minor; - } - - @Nonnull - private ArgumentMode getArgumentMode() { - return this.argumentMode; - } - + Map, Function> getAnnotationMappers() { + return this.annotationMappers; } } diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/ArgumentExtractor.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/ArgumentExtractor.java new file mode 100644 index 00000000..822d4678 --- /dev/null +++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/ArgumentExtractor.java @@ -0,0 +1,51 @@ +// +// 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 javax.annotation.Nonnull; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.Function; + +/** + * Utility that extract {@link Argument arguments} from + * {@link java.lang.reflect.Method method} {@link java.lang.reflect.Parameter parameters} + */ +class ArgumentExtractor implements Function> { + + @Override + public Collection apply(@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; + } + +} diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/ArgumentMode.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/ArgumentMode.java new file mode 100644 index 00000000..cef5d6b4 --- /dev/null +++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/ArgumentMode.java @@ -0,0 +1,30 @@ +// +// 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; + +enum ArgumentMode { + LITERAL, + OPTIONAL, + REQUIRED +} diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/ArgumentParameterPair.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/ArgumentParameterPair.java new file mode 100644 index 00000000..91e4ea57 --- /dev/null +++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/ArgumentParameterPair.java @@ -0,0 +1,49 @@ +// +// 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 javax.annotation.Nonnull; +import java.lang.reflect.Parameter; + +final class ArgumentParameterPair { + + private final Parameter parameter; + private final Argument argument; + + ArgumentParameterPair(@Nonnull final Parameter parameter, @Nonnull final Argument argument) { + this.parameter = parameter; + this.argument = argument; + } + + @Nonnull + Parameter getParameter() { + return this.parameter; + } + + @Nonnull + Argument getArgument() { + return this.argument; + } + +} diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/CommandMethodPair.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/CommandMethodPair.java new file mode 100644 index 00000000..4f12e3f2 --- /dev/null +++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/CommandMethodPair.java @@ -0,0 +1,49 @@ +// +// 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 javax.annotation.Nonnull; +import java.lang.reflect.Method; + +final class CommandMethodPair { + + private final Method method; + private final CommandMethod commandMethod; + + CommandMethodPair(@Nonnull final Method method, @Nonnull final CommandMethod commandMethod) { + this.method = method; + this.commandMethod = commandMethod; + } + + @Nonnull + Method getMethod() { + return this.method; + } + + @Nonnull + CommandMethod getCommandMethod() { + return this.commandMethod; + } + +} diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/MetaFactory.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/MetaFactory.java new file mode 100644 index 00000000..4ce46591 --- /dev/null +++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/MetaFactory.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 com.intellectualsites.commands.arguments.parser.ParserParameters; +import com.intellectualsites.commands.meta.CommandMeta; + +import javax.annotation.Nonnull; +import java.lang.annotation.Annotation; +import java.util.function.Function; + +class MetaFactory implements Function { + + private final AnnotationParser annotationParser; + private final Function metaMapper; + + MetaFactory(@Nonnull final AnnotationParser annotationParser, + @Nonnull final Function metaMapper) { + this.annotationParser = annotationParser; + this.metaMapper = metaMapper; + } + + @Override + public CommandMeta apply(@Nonnull final Annotation[] annotations) { + final ParserParameters parameters = ParserParameters.empty(); + for (final Annotation annotation : annotations) { + @SuppressWarnings("ALL") final Function function = this.annotationParser.getAnnotationMappers() + .get(annotation.annotationType()); + if (function == null) { + continue; + } + //noinspection unchecked + parameters.merge((ParserParameters) function.apply(annotation)); + } + return this.metaMapper.apply(parameters); + } + +} diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/MethodCommandExecutionHandler.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/MethodCommandExecutionHandler.java new file mode 100644 index 00000000..6866bdb6 --- /dev/null +++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/MethodCommandExecutionHandler.java @@ -0,0 +1,89 @@ +// +// 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.intellectualsites.commands.arguments.CommandArgument; +import com.intellectualsites.commands.context.CommandContext; +import com.intellectualsites.commands.execution.CommandExecutionHandler; + +import javax.annotation.Nonnull; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +class MethodCommandExecutionHandler implements CommandExecutionHandler { + + private final Parameter[] parameters; + private final MethodHandle methodHandle; + private final Map> commandArguments; + + MethodCommandExecutionHandler(@Nonnull final Object instance, + @Nonnull final Map> commandArguments, + @Nonnull final Method method) throws Exception { + this.commandArguments = commandArguments; + method.setAccessible(true); + this.methodHandle = MethodHandles.lookup().unreflect(method).bindTo(instance); + this.parameters = method.getParameters(); + } + + @Override + public void execute(@Nonnull final CommandContext commandContext) { + final List arguments = new ArrayList<>(this.parameters.length); + + /* Bind parameters to context */ + for (final Parameter parameter : this.parameters) { + if (parameter.isAnnotationPresent(Argument.class)) { + final Argument argument = parameter.getAnnotation(Argument.class); + final CommandArgument commandArgument = this.commandArguments.get(argument.value()); + if (commandArgument.isRequired()) { + arguments.add(commandContext.getRequired(argument.value())); + } else { + final Object optional = commandContext.get(argument.value()).orElse(null); + arguments.add(optional); + } + } else { + if (parameter.getType().isAssignableFrom(commandContext.getSender().getClass())) { + arguments.add(commandContext.getSender()); + } else { + throw new IllegalArgumentException(String.format( + "Unknown command parameter '%s' in method '%s'", + parameter.getName(), this.methodHandle.toString() + )); + } + } + } + + /* Invoke the command method */ + try { + this.methodHandle.invokeWithArguments(arguments); + } catch (final Throwable e) { + e.printStackTrace(); + } + } + +} diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/SyntaxFragment.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/SyntaxFragment.java new file mode 100644 index 00000000..b5678a6b --- /dev/null +++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/SyntaxFragment.java @@ -0,0 +1,58 @@ +// +// 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 javax.annotation.Nonnull; +import java.util.List; + +final class SyntaxFragment { + + private final String major; + private final List minor; + private final ArgumentMode argumentMode; + + SyntaxFragment(@Nonnull final String major, + @Nonnull final List minor, + @Nonnull final ArgumentMode argumentMode) { + this.major = major; + this.minor = minor; + this.argumentMode = argumentMode; + } + + @Nonnull + String getMajor() { + return this.major; + } + + @Nonnull + List getMinor() { + return this.minor; + } + + @Nonnull + ArgumentMode getArgumentMode() { + return this.argumentMode; + } + +} diff --git a/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/SyntaxParser.java b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/SyntaxParser.java new file mode 100644 index 00000000..6726c20e --- /dev/null +++ b/cloud-annotations/src/main/java/com/intellectualsites/commands/annotations/SyntaxParser.java @@ -0,0 +1,77 @@ +// +// 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 javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.StringTokenizer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +/** + * Parses command syntax into syntax fragments + */ +final class SyntaxParser implements Function> { + + 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(); + + @Override + public LinkedHashMap apply(@Nonnull final String syntax) { + final StringTokenizer stringTokenizer = new StringTokenizer(syntax, " "); + final LinkedHashMap map = new LinkedHashMap<>(); + while (stringTokenizer.hasMoreTokens()) { + final String token = stringTokenizer.nextToken(); + String major; + List minor = new ArrayList<>(); + ArgumentMode mode; + if (PATTERN_ARGUMENT_REQUIRED.test(token)) { + major = token.substring(1, token.length() - 1); + mode = ArgumentMode.REQUIRED; + } else if (PATTERN_ARGUMENT_OPTIONAL.test(token)) { + major = token.substring(1, token.length() - 1); + mode = ArgumentMode.OPTIONAL; + } else if (PATTERN_ARGUMENT_LITERAL.test(token)) { + final String[] literals = token.split("\\|"); + /* Actually use the other literals as well */ + major = literals[0]; + minor.addAll(Arrays.asList(literals).subList(1, literals.length)); + mode = ArgumentMode.LITERAL; + } else { + throw new IllegalArgumentException(String.format("Unrecognizable syntax token '%s'", syntax)); + } + map.put(major, new SyntaxFragment(major, minor, mode)); + } + return map; + } + +} diff --git a/cloud-annotations/src/test/java/com/intellectualsites/commands/annotations/AnnotationParserTest.java b/cloud-annotations/src/test/java/com/intellectualsites/commands/annotations/AnnotationParserTest.java index c0f246e6..8060d116 100644 --- a/cloud-annotations/src/test/java/com/intellectualsites/commands/annotations/AnnotationParserTest.java +++ b/cloud-annotations/src/test/java/com/intellectualsites/commands/annotations/AnnotationParserTest.java @@ -55,7 +55,7 @@ class AnnotationParserTest { final Collection> commands = annotationParser.parse(this); Assertions.assertFalse(commands.isEmpty()); manager.executeCommand(new TestCommandSender(), "test 10").join(); - manager.executeCommand(new TestCommandSender(), "t 10").join(); + manager.executeCommand(new TestCommandSender(), "t 10 o").join(); Assertions.assertThrows(CompletionException.class, () -> manager.executeCommand(new TestCommandSender(), "test 101").join()); }