diff --git a/SpigotCore_Main/build.gradle b/SpigotCore_Main/build.gradle index 746d01e..8e751bf 100644 --- a/SpigotCore_Main/build.gradle +++ b/SpigotCore_Main/build.gradle @@ -40,6 +40,16 @@ sourceSets { exclude '**/*.java', '**/*.kt' } } + + test { + java { + srcDirs = ['testsrc'] + } + resources { + srcDirs = ['testsrc'] + exclude '**/*.java', '**/*.kt' + } + } } dependencies { @@ -51,6 +61,12 @@ dependencies { testCompileOnly 'org.projectlombok:lombok:1.18.22' annotationProcessor 'org.projectlombok:lombok:1.18.22' testAnnotationProcessor 'org.projectlombok:lombok:1.18.22' + + testImplementation files("${project.rootDir}/lib/Spigot-1.15.jar") + testImplementation files("${project.rootDir}/lib/WorldEdit-1.12.jar") + + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.hamcrest:hamcrest:2.2' } processResources { diff --git a/SpigotCore_Main/src/de/steamwar/command/CommandParseException.java b/SpigotCore_Main/src/de/steamwar/command/CommandParseException.java index 21e68bb..3d81ea6 100644 --- a/SpigotCore_Main/src/de/steamwar/command/CommandParseException.java +++ b/SpigotCore_Main/src/de/steamwar/command/CommandParseException.java @@ -19,7 +19,7 @@ package de.steamwar.command; -public class CommandParseException extends Exception { +public class CommandParseException extends RuntimeException { public CommandParseException() { } diff --git a/SpigotCore_Main/src/de/steamwar/command/CommandPart.java b/SpigotCore_Main/src/de/steamwar/command/CommandPart.java new file mode 100644 index 0000000..bcb6c3f --- /dev/null +++ b/SpigotCore_Main/src/de/steamwar/command/CommandPart.java @@ -0,0 +1,119 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import lombok.AllArgsConstructor; +import org.bukkit.command.CommandSender; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.List; + +public class CommandPart { + private static final String[] EMPTY_ARRAY = new String[0]; + + @AllArgsConstructor + private static class CheckArgumentResult { + private final boolean success; + private final Object value; + } + + private TypeMapper typeMapper; + private GuardChecker guard; + private Class varArgType = null; + private String optional = null; + private boolean help; + + private CommandPart next = null; + + CommandPart(TypeMapper typeMapper, GuardChecker guard, Class varArgType, String optional, boolean help) { + this.typeMapper = typeMapper; + this.guard = guard; + if (optional != null && varArgType != null) { + throw new IllegalArgumentException("A vararg part can't have an optional part!"); + } + this.varArgType = varArgType; + this.optional = optional; + this.help = help; + } + + void setNext(CommandPart next) { + if (varArgType != null) { + throw new IllegalArgumentException("There can't be a next part if this is a vararg part!"); + } + this.next = next; + } + + void generateArgumentArray(List current, CommandSender commandSender, String[] args, int startIndex) { + if (startIndex >= args.length) { + return; + } + + if (varArgType != null) { + Object array = Array.newInstance(varArgType, args.length - startIndex); + for (int i = startIndex; i < args.length; i++) { + CheckArgumentResult validArgument = checkArgument(help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND, commandSender, args, i); + if (!validArgument.success) { + throw new CommandParseException(); + } + Array.set(array, i - startIndex, validArgument.value); + } + current.add(array); + return; + } + + CheckArgumentResult validArgument = checkArgument(help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND, commandSender, args, startIndex); + if (!validArgument.success && optional == null) { + throw new CommandParseException(); + } + if (optional != null) { + current.add(typeMapper.map(commandSender, EMPTY_ARRAY, optional)); + next.generateArgumentArray(current, commandSender, args, startIndex); + return; + } + current.add(validArgument.value); + next.generateArgumentArray(current, commandSender, args, startIndex + 1); + } + + void generateTabComplete(List current, CommandSender commandSender, String[] args, int startIndex) { + + } + + private CheckArgumentResult checkArgument(GuardCheckType guardCheckType, CommandSender commandSender, String[] args, int index) { + try { + Object value = typeMapper.map(commandSender, Arrays.copyOf(args, index - 1), args[index]); + if (guard != null) { + GuardResult guardResult = guard.guard(commandSender, GuardCheckType.TAB_COMPLETE, Arrays.copyOf(args, index - 1), args[index]); + switch (guardResult) { + case ALLOWED: + return new CheckArgumentResult(true, value); + case DENIED: + throw new CommandNoHelpException(); + case DENIED_WITH_HELP: + default: + return new CheckArgumentResult(false, null); + } + } + return new CheckArgumentResult(true, value); + } catch (Exception e) { + return new CheckArgumentResult(false, null); + } + } +} diff --git a/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java b/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java new file mode 100644 index 0000000..8cbf59f --- /dev/null +++ b/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java @@ -0,0 +1,63 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.bukkit.command.SimpleCommandMap; + +import java.lang.reflect.Field; +import java.util.Map; + +class CommandRegistering { + + private static final CommandMap commandMap; + private static final Map knownCommandMap; + + static { + try { + final Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); + commandMapField.setAccessible(true); + commandMap = (CommandMap) commandMapField.get(Bukkit.getServer()); + } catch (NoSuchFieldException | IllegalAccessException exception) { + Bukkit.shutdown(); + throw new SecurityException("Oh shit. Commands cannot be registered.", exception); + } + try { + final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); + knownCommandsField.setAccessible(true); + knownCommandMap = (Map) knownCommandsField.get(commandMap); + } catch (NoSuchFieldException | IllegalAccessException exception) { + Bukkit.shutdown(); + throw new SecurityException("Oh shit. Commands cannot be registered.", exception); + } + } + + static void unregister(Command command) { + knownCommandMap.remove(command.getName()); + command.getAliases().forEach(knownCommandMap::remove); + command.unregister(commandMap); + } + + static void register(Command command) { + commandMap.register("steamwar", command); + } +} diff --git a/SpigotCore_Main/src/de/steamwar/command/SWCommand.java b/SpigotCore_Main/src/de/steamwar/command/SWCommand.java index 1783303..7126fe8 100644 --- a/SpigotCore_Main/src/de/steamwar/command/SWCommand.java +++ b/SpigotCore_Main/src/de/steamwar/command/SWCommand.java @@ -61,13 +61,7 @@ public abstract class SWCommand { if (!initialized) { createMapping(); } - try { - if (!commandList.stream().anyMatch(s -> s.invoke(sender, args))) { - commandHelpList.stream().anyMatch(s -> s.invoke(sender, args)); - } - } catch (CommandNoHelpException e) { - // Ignored - } + SWCommand.this.execute(sender, alias, args); return false; } @@ -76,21 +70,52 @@ public abstract class SWCommand { if (!initialized) { createMapping(); } - String string = args[args.length - 1].toLowerCase(); - return commandList.stream() - .filter(s -> !s.noTabComplete) - .map(s -> s.tabComplete(sender, args)) - .filter(Objects::nonNull) - .flatMap(Collection::stream) - .filter(s -> !s.isEmpty()) - .filter(s -> s.toLowerCase().startsWith(string)) - .collect(Collectors.toList()); + return SWCommand.this.tabComplete(sender, alias, args); } }; unregister(); register(); } + // This is used for the tests! + SWCommand(boolean noRegister, String command, String... aliases) { + this.command = new Command(command, "", "/" + command, Arrays.asList(aliases)) { + @Override + public boolean execute(CommandSender sender, String alias, String[] args) { + SWCommand.this.execute(sender, alias, args); + return false; + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + return SWCommand.this.tabComplete(sender, alias, args); + } + }; + createMapping(); + } + + void execute(CommandSender sender, String alias, String[] args) { + try { + if (!commandList.stream().anyMatch(s -> s.invoke(sender, args))) { + commandHelpList.stream().anyMatch(s -> s.invoke(sender, args)); + } + } catch (CommandNoHelpException e) { + // Ignored + } + } + + List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + String string = args[args.length - 1].toLowerCase(); + return commandList.stream() + .filter(s -> !s.noTabComplete) + .map(s -> s.tabComplete(sender, args)) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(s -> !s.isEmpty()) + .filter(s -> s.toLowerCase().startsWith(string)) + .collect(Collectors.toList()); + } + private synchronized void createMapping() { List methods = methods(); for (Method method : methods) { @@ -219,13 +244,11 @@ public abstract class SWCommand { } public void unregister() { - SWCommandUtils.knownCommandMap.remove(command.getName()); - command.getAliases().forEach(SWCommandUtils.knownCommandMap::remove); - command.unregister(SWCommandUtils.commandMap); + CommandRegistering.unregister(command); } public void register() { - SWCommandUtils.commandMap.register("steamwar", this.command); + CommandRegistering.register(command); } @Register(help = true) @@ -332,6 +355,12 @@ public abstract class SWCommand { @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) protected @interface StaticValue { - String[] value() default {}; + String[] value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + protected @interface OptionalValue { + String defaultValue(); // Will pe parsed against the TypeMapper specified by the parameter or annotation } } diff --git a/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java b/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java index dbe616a..0ee8562 100644 --- a/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java +++ b/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java @@ -91,28 +91,6 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper); } - static final CommandMap commandMap; - static final Map knownCommandMap; - - static { - try { - final Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - commandMapField.setAccessible(true); - commandMap = (CommandMap) commandMapField.get(Bukkit.getServer()); - } catch (NoSuchFieldException | IllegalAccessException exception) { - Bukkit.shutdown(); - throw new SecurityException("Oh shit. Commands cannot be registered.", exception); - } - try { - final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); - knownCommandsField.setAccessible(true); - knownCommandMap = (Map) knownCommandsField.get(commandMap); - } catch (NoSuchFieldException | IllegalAccessException exception) { - Bukkit.shutdown(); - throw new SecurityException("Oh shit. Commands cannot be registered.", exception); - } - } - static Object[] generateArgumentArray(CommandSender commandSender, TypeMapper[] parameters, GuardChecker[] guards, String[] args, Class varArgType, String[] subCommand) throws CommandParseException { Object[] arguments = new Object[parameters.length + 1]; int index = 0; diff --git a/SpigotCore_Main/src/de/steamwar/command/SubCommand.java b/SpigotCore_Main/src/de/steamwar/command/SubCommand.java index 997cdea..07f05d8 100644 --- a/SpigotCore_Main/src/de/steamwar/command/SubCommand.java +++ b/SpigotCore_Main/src/de/steamwar/command/SubCommand.java @@ -179,10 +179,10 @@ class SubCommand { method.invoke(swCommand, objects); } catch (CommandNoHelpException e) { throw e; - } catch (IllegalAccessException | RuntimeException | InvocationTargetException e) { - throw new SecurityException(e.getMessage(), e); } catch (CommandParseException e) { return false; + } catch (IllegalAccessException | RuntimeException | InvocationTargetException e) { + throw new SecurityException(e.getMessage(), e); } return true; } diff --git a/SpigotCore_Main/src/de/steamwar/core/Core.java b/SpigotCore_Main/src/de/steamwar/core/Core.java index 82f0705..b0fc18f 100644 --- a/SpigotCore_Main/src/de/steamwar/core/Core.java +++ b/SpigotCore_Main/src/de/steamwar/core/Core.java @@ -38,7 +38,6 @@ import java.util.logging.Level; public class Core extends JavaPlugin{ - private static Core instance; private static final int version; public static Message MESSAGE; diff --git a/SpigotCore_Main/testsrc/.gitkeep b/SpigotCore_Main/testsrc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/SpigotCore_Main/testsrc/de/steamwar/TestCommandSender.java b/SpigotCore_Main/testsrc/de/steamwar/TestCommandSender.java new file mode 100644 index 0000000..02760e4 --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/TestCommandSender.java @@ -0,0 +1,122 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar; + +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; + +import java.util.Set; + +public class TestCommandSender implements CommandSender { + + @Override + public void sendMessage(String s) { + + } + + @Override + public void sendMessage(String[] strings) { + + } + + @Override + public Server getServer() { + return null; + } + + @Override + public String getName() { + return null; + } + + @Override + public Spigot spigot() { + return null; + } + + @Override + public boolean isPermissionSet(String s) { + return false; + } + + @Override + public boolean isPermissionSet(Permission permission) { + return false; + } + + @Override + public boolean hasPermission(String s) { + return false; + } + + @Override + public boolean hasPermission(Permission permission) { + return false; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b) { + return null; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin) { + return null; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b, int i) { + return null; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, int i) { + return null; + } + + @Override + public void removeAttachment(PermissionAttachment permissionAttachment) { + + } + + @Override + public void recalculatePermissions() { + + } + + @Override + public Set getEffectivePermissions() { + return null; + } + + @Override + public boolean isOp() { + return false; + } + + @Override + public void setOp(boolean b) { + + } +} diff --git a/SpigotCore_Main/testsrc/de/steamwar/command/ExecutionIdentifier.java b/SpigotCore_Main/testsrc/de/steamwar/command/ExecutionIdentifier.java new file mode 100644 index 0000000..e4ed591 --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/command/ExecutionIdentifier.java @@ -0,0 +1,41 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +public class ExecutionIdentifier extends RuntimeException { + public ExecutionIdentifier() { + } + + public ExecutionIdentifier(String message) { + super(message); + } + + public ExecutionIdentifier(String message, Throwable cause) { + super(message, cause); + } + + public ExecutionIdentifier(Throwable cause) { + super(cause); + } + + public ExecutionIdentifier(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommand.java b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommand.java new file mode 100644 index 0000000..79f2844 --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommand.java @@ -0,0 +1,34 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import org.bukkit.command.CommandSender; + +public class SimpleCommand extends SWCommand { + + public SimpleCommand() { + super(true, "simple"); + } + + @Register + public void execute(CommandSender sender) { + throw new ExecutionIdentifier("Simple execute without any parameters"); + } +} diff --git a/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java new file mode 100644 index 0000000..f8ba72a --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java @@ -0,0 +1,60 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2020 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.command; + +import de.steamwar.TestCommandSender; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SimpleCommandTest { + + private SimpleCommand simpleCommand; + + @Before + public void setUp() throws Exception { + simpleCommand = new SimpleCommand(); + } + + @Test + public void testCommandParsing() { + try { + simpleCommand.execute(new TestCommandSender(), "", new String[]{}); + } catch (SecurityException securityException) { + if (securityException.getCause().getCause() instanceof ExecutionIdentifier) { + ExecutionIdentifier executionIdentifier = (ExecutionIdentifier) securityException.getCause().getCause(); + assertThat(executionIdentifier.getMessage(), is("Simple execute without any parameters")); + return; + } + } + assert false; + } + + @Test + public void testUnknownCommandParsing() { + try { + simpleCommand.execute(new TestCommandSender(), "", new String[]{"unknown"}); + } catch (SecurityException securityException) { + assert false; + } + } +}