CMD #141
@ -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 {
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
package de.steamwar.command;
|
||||
|
||||
public class CommandParseException extends Exception {
|
||||
public class CommandParseException extends RuntimeException {
|
||||
|
||||
public CommandParseException() {
|
||||
}
|
||||
|
217
SpigotCore_Main/src/de/steamwar/command/CommandPart.java
Normale Datei
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.command;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@ToString
|
||||
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;
|
||||
private String optional;
|
||||
private GuardCheckType guardCheckType;
|
||||
|
||||
private CommandPart next = null;
|
||||
|
||||
@Setter
|
||||
private boolean ignoreAsArgument = false;
|
||||
|
||||
public CommandPart(TypeMapper<?> typeMapper, GuardChecker guard, Class<?> varArgType, String optional, GuardCheckType guardCheckType) {
|
||||
this.typeMapper = typeMapper;
|
||||
this.guard = guard;
|
||||
this.varArgType = varArgType;
|
||||
this.optional = optional;
|
||||
this.guardCheckType = guardCheckType;
|
||||
|
||||
validatePart();
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
|
||||
private void validatePart() {
|
||||
if (guardCheckType == GuardCheckType.TAB_COMPLETE) {
|
||||
throw new IllegalArgumentException("Tab complete is not allowed as a guard check type!");
|
||||
}
|
||||
if (optional != null && varArgType != null) {
|
||||
throw new IllegalArgumentException("A vararg part can't have an optional part!");
|
||||
}
|
||||
|
||||
if (optional != null) {
|
||||
try {
|
||||
typeMapper.map(null, EMPTY_ARRAY, optional);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("The optional part is not valid!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void generateArgumentArray(List<Object> current, CommandSender commandSender, String[] args, int startIndex) {
|
||||
if (startIndex >= args.length && varArgType == null) {
|
||||
throw new CommandParseException();
|
||||
}
|
||||
|
||||
if (varArgType != null) {
|
||||
Object array = Array.newInstance(varArgType, args.length - startIndex);
|
||||
for (int i = startIndex; i < args.length; i++) {
|
||||
CheckArgumentResult validArgument = checkArgument(null, commandSender, args, i);
|
||||
if (!validArgument.success) {
|
||||
throw new CommandParseException();
|
||||
}
|
||||
Array.set(array, i - startIndex, validArgument.value);
|
||||
}
|
||||
current.add(array);
|
||||
return;
|
||||
}
|
||||
|
||||
CheckArgumentResult validArgument = checkArgument(null, commandSender, args, startIndex);
|
||||
if (!validArgument.success && optional == null) {
|
||||
throw new CommandParseException();
|
||||
}
|
||||
if (!validArgument.success) {
|
||||
if (!ignoreAsArgument) {
|
||||
current.add(typeMapper.map(commandSender, EMPTY_ARRAY, optional));
|
||||
}
|
||||
if (next != null) {
|
||||
next.generateArgumentArray(current, commandSender, args, startIndex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!ignoreAsArgument) {
|
||||
current.add(validArgument.value);
|
||||
}
|
||||
if (next != null) {
|
||||
next.generateArgumentArray(current, commandSender, args, startIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean guardCheck(CommandSender commandSender, String[] args, int startIndex) {
|
||||
if (varArgType != null) {
|
||||
for (int i = startIndex; i < args.length; i++) {
|
||||
GuardResult guardResult = checkGuard(guardCheckType, commandSender, args, i);
|
||||
if (guardResult == GuardResult.DENIED) {
|
||||
throw new CommandNoHelpException();
|
||||
}
|
||||
if (guardResult == GuardResult.DENIED_WITH_HELP) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
GuardResult guardResult = checkGuard(guardCheckType, commandSender, args, startIndex);
|
||||
if (guardResult == GuardResult.DENIED) {
|
||||
if (optional != null && next != null) {
|
||||
return next.guardCheck(commandSender, args, startIndex);
|
||||
}
|
||||
throw new CommandNoHelpException();
|
||||
}
|
||||
if (guardResult == GuardResult.DENIED_WITH_HELP) {
|
||||
if (optional != null && next != null) {
|
||||
return next.guardCheck(commandSender, args, startIndex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (next != null) {
|
||||
return next.guardCheck(commandSender, args, startIndex + 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void generateTabComplete(List<String> current, CommandSender commandSender, String[] args, int startIndex) {
|
||||
if (varArgType != null) {
|
||||
for (int i = startIndex; i < args.length - 1; i++) {
|
||||
CheckArgumentResult validArgument = checkArgument(null, commandSender, args, i);
|
||||
if (!validArgument.success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
List<String> strings = typeMapper.tabCompletes(commandSender, Arrays.copyOf(args, args.length - 1), args[args.length - 1]);
|
||||
if (strings != null) {
|
||||
current.addAll(strings);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.length - 1 > startIndex) {
|
||||
CheckArgumentResult checkArgumentResult = checkArgument(GuardCheckType.TAB_COMPLETE, commandSender, args, startIndex);
|
||||
if (checkArgumentResult.success && next != null) {
|
||||
next.generateTabComplete(current, commandSender, args, startIndex + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> strings = typeMapper.tabCompletes(commandSender, Arrays.copyOf(args, startIndex), args[startIndex]);
|
||||
if (strings != null) {
|
||||
current.addAll(strings);
|
||||
}
|
||||
if (optional != null && next != null) {
|
||||
next.generateTabComplete(current, commandSender, args, startIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private CheckArgumentResult checkArgument(GuardCheckType guardCheckType, CommandSender commandSender, String[] args, int index) {
|
||||
try {
|
||||
Object value = typeMapper.map(commandSender, Arrays.copyOf(args, index), args[index]);
|
||||
if (value == null) {
|
||||
return new CheckArgumentResult(false, null);
|
||||
}
|
||||
GuardResult guardResult = checkGuard(guardCheckType, commandSender, 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);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return new CheckArgumentResult(false, null);
|
||||
}
|
||||
}
|
||||
|
||||
private GuardResult checkGuard(GuardCheckType guardCheckType, CommandSender commandSender, String[] args, int index) {
|
||||
if (guard != null && guardCheckType != null) {
|
||||
return guard.guard(commandSender, guardCheckType, Arrays.copyOf(args, index), args[index]);
|
||||
}
|
||||
return GuardResult.ALLOWED;
|
||||
}
|
||||
}
|
65
SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java
Normale Datei
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.command;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
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;
|
||||
|
||||
@UtilityClass
|
||||
class CommandRegistering {
|
||||
|
||||
private static final CommandMap commandMap;
|
||||
private static final Map<String, Command> 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<String, Command>) 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);
|
||||
}
|
||||
}
|
@ -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<String> 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<String> 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<Method> methods = methods();
|
||||
for (Method method : methods) {
|
||||
@ -149,8 +174,7 @@ public abstract class SWCommand {
|
||||
if (compare != 0) {
|
||||
return compare;
|
||||
} else {
|
||||
return Integer.compare(o1.varArgType != null ? Integer.MAX_VALUE : o1.arguments.length,
|
||||
o2.varArgType != null ? Integer.MAX_VALUE : o2.arguments.length);
|
||||
return Integer.compare(o1.comparableValue, o2.comparableValue);
|
||||
}
|
||||
});
|
||||
commandHelpList.sort((o1, o2) -> {
|
||||
@ -219,13 +243,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 +354,15 @@ 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 {
|
||||
/**
|
||||
* Will pe parsed against the TypeMapper specified by the parameter or annotation.
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
}
|
||||
|
@ -19,20 +19,17 @@
|
||||
|
||||
package de.steamwar.command;
|
||||
|
||||
import de.steamwar.sql.BauweltMember;
|
||||
import de.steamwar.sql.SchematicNode;
|
||||
import de.steamwar.sql.SteamwarUser;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandMap;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.SimpleCommandMap;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
@ -47,15 +44,6 @@ public class SWCommandUtils {
|
||||
static final Map<String, TypeMapper<?>> MAPPER_FUNCTIONS = new HashMap<>();
|
||||
static final Map<String, GuardChecker> GUARD_FUNCTIONS = new HashMap<>();
|
||||
|
||||
static final TypeMapper<?> ERROR_FUNCTION = createMapper(s -> {
|
||||
throw new SecurityException();
|
||||
}, s -> Collections.emptyList());
|
||||
|
||||
static final BiFunction<Class<Enum<?>>, String, Enum<?>> ENUM_MAPPER = (enumClass, s) -> {
|
||||
Enum<?>[] enums = enumClass.getEnumConstants();
|
||||
return Arrays.stream(enums).filter(e -> e.name().equalsIgnoreCase(s)).findFirst().orElse(null);
|
||||
};
|
||||
|
||||
static {
|
||||
addMapper(boolean.class, Boolean.class, createMapper(Boolean::parseBoolean, s -> Arrays.asList("true", "false")));
|
||||
addMapper(float.class, Float.class, createMapper(numberMapper(Float::parseFloat), numberCompleter(Float::parseFloat)));
|
||||
@ -75,13 +63,39 @@ public class SWCommandUtils {
|
||||
MAPPER_FUNCTIONS.put(SteamwarUser.class.getTypeName(), createMapper(SteamwarUser::get, s -> Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList())));
|
||||
MAPPER_FUNCTIONS.put(SchematicNode.class.getTypeName(), new TypeMapper<SchematicNode>() {
|
||||
@Override
|
||||
public List<String> tabCompletes(CommandSender commandSender, String[] strings, String s) {
|
||||
return SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s);
|
||||
public SchematicNode map(CommandSender commandSender, String[] previousArguments, String s) {
|
||||
return SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchematicNode map(CommandSender commandSender, String[] previousArguments, String s) {
|
||||
return SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s);
|
||||
public List<String> tabCompletes(CommandSender commandSender, String[] strings, String s) {
|
||||
return SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s);
|
||||
}
|
||||
});
|
||||
MAPPER_FUNCTIONS.put(BauweltMember.class.getTypeName(), new TypeMapper<BauweltMember>() {
|
||||
@Override
|
||||
public BauweltMember map(CommandSender commandSender, String[] previousArguments, String s) {
|
||||
if (!(commandSender instanceof Player)) {
|
||||
return null;
|
||||
}
|
||||
Player player = (Player) commandSender;
|
||||
return BauweltMember.getMembers(player.getUniqueId())
|
||||
.stream()
|
||||
.filter(member -> SteamwarUser.get(member.getMemberID()).getUserName().equalsIgnoreCase(s))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tabCompletes(CommandSender commandSender, String[] previousArguments, String s) {
|
||||
if (!(commandSender instanceof Player)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Player player = (Player) commandSender;
|
||||
return BauweltMember.getMembers(player.getUniqueId())
|
||||
.stream()
|
||||
.map(m -> SteamwarUser.get(m.getMemberID()).getUserName())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -91,60 +105,99 @@ public class SWCommandUtils {
|
||||
MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper);
|
||||
}
|
||||
|
||||
static final CommandMap commandMap;
|
||||
static final Map<String, Command> 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<String, Command>) 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;
|
||||
while (index < subCommand.length) {
|
||||
if (!args[index].equalsIgnoreCase(subCommand[index])) throw new CommandParseException();
|
||||
index++;
|
||||
}
|
||||
|
||||
int length = 0;
|
||||
if (varArgType != null) {
|
||||
length = args.length - parameters.length - subCommand.length + 1;
|
||||
arguments[arguments.length - 1] = Array.newInstance(varArgType, length);
|
||||
if (index > args.length - 1) return arguments;
|
||||
}
|
||||
|
||||
for (int i = 0; i < parameters.length - (varArgType != null ? 1 : 0); i++) {
|
||||
arguments[i + 1] = parameters[i].map(commandSender, Arrays.copyOf(args, index), args[index]);
|
||||
index++;
|
||||
if (arguments[i + 1] == null) throw new CommandParseException();
|
||||
}
|
||||
|
||||
if (varArgType != null) {
|
||||
Object varArgument = arguments[arguments.length - 1];
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
Object value = parameters[parameters.length - 1].map(commandSender, Arrays.copyOf(args, index), args[index]);
|
||||
if (value == null) throw new CommandParseException();
|
||||
Array.set(varArgument, i, value);
|
||||
index++;
|
||||
static CommandPart generateCommandPart(boolean help, String[] subCommand, Parameter[] parameters, Map<String, TypeMapper<?>> localTypeMapper, Map<String, GuardChecker> localGuardChecker) {
|
||||
CommandPart first = null;
|
||||
CommandPart current = null;
|
||||
for (String s : subCommand) {
|
||||
CommandPart commandPart = new CommandPart(createMapper(s), null, null, null, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND);
|
||||
commandPart.setIgnoreAsArgument(true);
|
||||
if (current != null) {
|
||||
current.setNext(commandPart);
|
||||
}
|
||||
current = commandPart;
|
||||
if (first == null) {
|
||||
first = current;
|
||||
}
|
||||
}
|
||||
return arguments;
|
||||
for (int i = 1; i < parameters.length; i++) {
|
||||
Parameter parameter = parameters[i];
|
||||
TypeMapper<?> typeMapper = getTypeMapper(parameter, localTypeMapper);
|
||||
GuardChecker guardChecker = getGuardChecker(parameter, localGuardChecker);
|
||||
Class<?> varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null;
|
||||
SWCommand.OptionalValue optionalValue = parameter.getAnnotation(SWCommand.OptionalValue.class);
|
||||
|
||||
CommandPart commandPart = new CommandPart(typeMapper, guardChecker, varArgType, optionalValue != null ? optionalValue.value() : null, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND);
|
||||
if (current != null) {
|
||||
current.setNext(commandPart);
|
||||
}
|
||||
current = commandPart;
|
||||
if (first == null) {
|
||||
first = current;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
static TypeMapper<?> getTypeMapper(Parameter parameter, Map<String, TypeMapper<?>> localTypeMapper) {
|
||||
Class<?> clazz = parameter.getType();
|
||||
if (parameter.isVarArgs()) {
|
||||
clazz = clazz.getComponentType();
|
||||
}
|
||||
|
||||
SWCommand.ClassMapper classMapper = parameter.getAnnotation(SWCommand.ClassMapper.class);
|
||||
SWCommand.Mapper mapper = parameter.getAnnotation(SWCommand.Mapper.class);
|
||||
if (clazz.isEnum() && classMapper == null && mapper == null && !MAPPER_FUNCTIONS.containsKey(clazz.getTypeName()) && !localTypeMapper.containsKey(clazz.getTypeName())) {
|
||||
return createEnumMapper((Class<Enum<?>>) clazz);
|
||||
}
|
||||
|
||||
String name = clazz.getTypeName();
|
||||
if (classMapper != null) {
|
||||
name = classMapper.value().getTypeName();
|
||||
} else if (mapper != null) {
|
||||
name = mapper.value();
|
||||
} else {
|
||||
SWCommand.StaticValue staticValue = parameter.getAnnotation(SWCommand.StaticValue.class);
|
||||
if (staticValue != null && parameter.getType() == String.class) {
|
||||
return createMapper(staticValue.value());
|
||||
}
|
||||
}
|
||||
TypeMapper<?> typeMapper = localTypeMapper.getOrDefault(name, MAPPER_FUNCTIONS.getOrDefault(name, null));
|
||||
if (typeMapper == null) {
|
||||
throw new IllegalArgumentException("No mapper found for " + name);
|
||||
}
|
||||
return typeMapper;
|
||||
}
|
||||
|
||||
static GuardChecker getGuardChecker(Parameter parameter, Map<String, GuardChecker> localGuardChecker) {
|
||||
Class<?> clazz = parameter.getType();
|
||||
if (parameter.isVarArgs()) {
|
||||
clazz = clazz.getComponentType();
|
||||
}
|
||||
|
||||
SWCommand.ClassGuard classGuard = parameter.getAnnotation(SWCommand.ClassGuard.class);
|
||||
if (classGuard != null) {
|
||||
if (classGuard.value() != null) {
|
||||
return getGuardChecker(classGuard.value().getTypeName(), localGuardChecker);
|
||||
}
|
||||
return getGuardChecker(clazz.getTypeName(), localGuardChecker);
|
||||
}
|
||||
|
||||
SWCommand.Guard guard = parameter.getAnnotation(SWCommand.Guard.class);
|
||||
if (guard != null) {
|
||||
if (guard.value() != null && !guard.value().isEmpty()) {
|
||||
return getGuardChecker(guard.value(), localGuardChecker);
|
||||
}
|
||||
return getGuardChecker(clazz.getTypeName(), localGuardChecker);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static GuardChecker getGuardChecker(String s, Map<String, GuardChecker> localGuardChecker) {
|
||||
GuardChecker guardChecker = localGuardChecker.getOrDefault(s, GUARD_FUNCTIONS.getOrDefault(s, null));
|
||||
if (guardChecker == null) {
|
||||
throw new IllegalArgumentException("No guard found for " + s);
|
||||
}
|
||||
return guardChecker;
|
||||
}
|
||||
|
||||
public static <T> void addMapper(Class<T> clazz, TypeMapper<T> mapper) {
|
||||
@ -186,6 +239,25 @@ public class SWCommandUtils {
|
||||
};
|
||||
}
|
||||
|
||||
public static TypeMapper<Enum<?>> createEnumMapper(Class<Enum<?>> enumClass) {
|
||||
Enum<?>[] enums = enumClass.getEnumConstants();
|
||||
List<String> strings = Arrays.stream(enums).map(Enum::name).map(String::toLowerCase).collect(Collectors.toList());
|
||||
return new TypeMapper<Enum<?>>() {
|
||||
@Override
|
||||
public Enum<?> map(CommandSender commandSender, String[] previousArguments, String s) {
|
||||
for (Enum<?> e : enums) {
|
||||
if (e.name().equalsIgnoreCase(s)) return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tabCompletes(CommandSender commandSender, String[] previousArguments, String s) {
|
||||
return strings;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static <T> Function<String, T> numberMapper(Function<String, T> mapper) {
|
||||
return s -> {
|
||||
try {
|
||||
|
@ -19,18 +19,16 @@
|
||||
|
||||
package de.steamwar.command;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static de.steamwar.command.SWCommandUtils.*;
|
||||
|
||||
class SubCommand {
|
||||
|
||||
@ -38,214 +36,82 @@ class SubCommand {
|
||||
Method method;
|
||||
String[] description;
|
||||
String[] subCommand;
|
||||
TypeMapper<?>[] arguments;
|
||||
GuardChecker[] guards;
|
||||
private Predicate<CommandSender> commandSenderPredicate;
|
||||
private Function<CommandSender, ?> commandSenderFunction;
|
||||
GuardChecker guardChecker;
|
||||
Class<?> varArgType = null;
|
||||
private boolean help;
|
||||
boolean noTabComplete;
|
||||
int comparableValue;
|
||||
|
||||
private CommandPart commandPart;
|
||||
|
||||
SubCommand(SWCommand swCommand, Method method, String[] subCommand, Map<String, TypeMapper<?>> localTypeMapper, Map<String, GuardChecker> localGuardChecker, boolean help, String[] description, boolean noTabComplete) {
|
||||
this.swCommand = swCommand;
|
||||
this.method = method;
|
||||
this.help = help;
|
||||
this.description = description;
|
||||
this.noTabComplete = noTabComplete;
|
||||
this.subCommand = subCommand;
|
||||
|
||||
Parameter[] parameters = method.getParameters();
|
||||
comparableValue = parameters[parameters.length - 1].isVarArgs() ? Integer.MAX_VALUE : subCommand.length;
|
||||
|
||||
guardChecker = SWCommandUtils.getGuardChecker(parameters[0], localGuardChecker);
|
||||
|
||||
commandPart = SWCommandUtils.generateCommandPart(help, subCommand, parameters, localTypeMapper, localGuardChecker);
|
||||
commandSenderPredicate = sender -> parameters[0].getType().isAssignableFrom(sender.getClass());
|
||||
commandSenderFunction = sender -> parameters[0].getType().cast(sender);
|
||||
this.subCommand = subCommand;
|
||||
guardChecker = getGuardChecker(parameters[0], localGuardChecker);
|
||||
|
||||
arguments = new TypeMapper[parameters.length - 1];
|
||||
guards = new GuardChecker[parameters.length - 1];
|
||||
for (int i = 1; i < parameters.length; i++) {
|
||||
Parameter parameter = parameters[i];
|
||||
Class<?> clazz = parameter.getType();
|
||||
if (parameter.isVarArgs()) {
|
||||
clazz = clazz.getComponentType();
|
||||
varArgType = clazz;
|
||||
}
|
||||
|
||||
SWCommand.Mapper mapper = parameter.getAnnotation(SWCommand.Mapper.class);
|
||||
SWCommand.ClassMapper classMapper = parameter.getAnnotation(SWCommand.ClassMapper.class);
|
||||
if (clazz.isEnum() && mapper == null && classMapper == null && !MAPPER_FUNCTIONS.containsKey(clazz.getTypeName()) && !localTypeMapper.containsKey(clazz.getTypeName())) {
|
||||
Class<Enum<?>> enumClass = (Class<Enum<?>>) clazz;
|
||||
List<String> tabCompletes = new ArrayList<>();
|
||||
for (Enum<?> enumConstant : enumClass.getEnumConstants()) {
|
||||
tabCompletes.add(enumConstant.name().toLowerCase());
|
||||
}
|
||||
arguments[i - 1] = SWCommandUtils.createMapper(s -> ENUM_MAPPER.apply(enumClass, s), s -> tabCompletes);
|
||||
continue;
|
||||
}
|
||||
|
||||
String name = clazz.getTypeName();
|
||||
if (classMapper != null) {
|
||||
name = classMapper.value().getTypeName();
|
||||
} else if (mapper != null) {
|
||||
name = mapper.value();
|
||||
} else {
|
||||
SWCommand.StaticValue staticValue = parameter.getAnnotation(SWCommand.StaticValue.class);
|
||||
if (staticValue != null && parameter.getType() == String.class) {
|
||||
arguments[i - 1] = SWCommandUtils.createMapper(staticValue.value());
|
||||
guards[i - 1] = getGuardChecker(parameter, localGuardChecker);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
arguments[i - 1] = localTypeMapper.containsKey(name)
|
||||
? localTypeMapper.get(name)
|
||||
: MAPPER_FUNCTIONS.getOrDefault(name, ERROR_FUNCTION);
|
||||
guards[i - 1] = getGuardChecker(parameter, localGuardChecker);
|
||||
}
|
||||
}
|
||||
|
||||
private GuardChecker getGuardChecker(Parameter parameter, Map<String, GuardChecker> localGuardChecker) {
|
||||
SWCommand.ClassGuard classGuard = parameter.getAnnotation(SWCommand.ClassGuard.class);
|
||||
if (classGuard != null) {
|
||||
if (classGuard.value() == null) {
|
||||
String s = parameter.getType().getTypeName();
|
||||
if (parameter.isVarArgs()) {
|
||||
s = parameter.getType().getComponentType().getTypeName();
|
||||
}
|
||||
return localGuardChecker.getOrDefault(s, GUARD_FUNCTIONS.getOrDefault(s, null));
|
||||
}
|
||||
GuardChecker current = localGuardChecker.getOrDefault(classGuard.value().getTypeName(), GUARD_FUNCTIONS.getOrDefault(classGuard.value().getTypeName(), null));
|
||||
if (current == null) {
|
||||
Bukkit.getLogger().log(Level.WARNING, () -> "The guard checker with name '" + classGuard.value().getTypeName() + "' is neither a local guard checker nor a global one");
|
||||
}
|
||||
return current;
|
||||
}
|
||||
SWCommand.Guard guard = parameter.getAnnotation(SWCommand.Guard.class);
|
||||
if (guard != null) {
|
||||
if (guard.value() == null || guard.value().isEmpty()) {
|
||||
String s = parameter.getType().getTypeName();
|
||||
if (parameter.isVarArgs()) {
|
||||
s = parameter.getType().getComponentType().getTypeName();
|
||||
}
|
||||
return localGuardChecker.getOrDefault(s, GUARD_FUNCTIONS.getOrDefault(s, null));
|
||||
}
|
||||
GuardChecker current = localGuardChecker.getOrDefault(guard.value(), GUARD_FUNCTIONS.getOrDefault(guard.value(), null));
|
||||
if (current == null) {
|
||||
Bukkit.getLogger().log(Level.WARNING, () -> "The guard checker with name '" + guard.value() + "' is neither a local guard checker nor a global one");
|
||||
}
|
||||
return current;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean invoke(CommandSender commandSender, String[] args) {
|
||||
if (args.length < arguments.length + subCommand.length - (varArgType != null ? 1 : 0)) {
|
||||
return false;
|
||||
}
|
||||
if (varArgType == null && args.length > arguments.length + subCommand.length) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (!commandSenderPredicate.test(commandSender)) {
|
||||
return false;
|
||||
}
|
||||
Object[] objects = SWCommandUtils.generateArgumentArray(commandSender, arguments, guards, args, varArgType, subCommand);
|
||||
objects[0] = commandSenderFunction.apply(commandSender);
|
||||
for (int i = subCommand.length; i < args.length; i++) {
|
||||
GuardChecker current;
|
||||
if (i == subCommand.length) {
|
||||
current = guardChecker;
|
||||
} else {
|
||||
if (i >= objects.length + subCommand.length) {
|
||||
current = guards[guards.length - 1];
|
||||
} else {
|
||||
current = guards[i - 1 - subCommand.length];
|
||||
}
|
||||
|
||||
if (commandPart == null) {
|
||||
if (args.length != 0) {
|
||||
return false;
|
||||
}
|
||||
if (current != null) {
|
||||
GuardResult guardResult;
|
||||
if (i == 0) {
|
||||
guardResult = current.guard(commandSender, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND, new String[0], null);
|
||||
} else {
|
||||
guardResult = current.guard(commandSender, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND, Arrays.copyOf(args, i - 1), args[i - 1]);
|
||||
}
|
||||
if (guardResult != GuardResult.ALLOWED) {
|
||||
if (guardResult == GuardResult.DENIED) {
|
||||
method.setAccessible(true);
|
||||
method.invoke(swCommand, commandSenderFunction.apply(commandSender));
|
||||
} else {
|
||||
List<Object> objects = new ArrayList<>();
|
||||
commandPart.generateArgumentArray(objects, commandSender, args, 0);
|
||||
if (guardChecker != null) {
|
||||
GuardResult guardResult = guardChecker.guard(commandSender, GuardCheckType.COMMAND, new String[0], null);
|
||||
switch (guardResult) {
|
||||
case ALLOWED:
|
||||
break;
|
||||
case DENIED:
|
||||
throw new CommandNoHelpException();
|
||||
}
|
||||
return false;
|
||||
case DENIED_WITH_HELP:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
commandPart.guardCheck(commandSender, args, 0);
|
||||
objects.add(0, commandSenderFunction.apply(commandSender));
|
||||
method.setAccessible(true);
|
||||
method.invoke(swCommand, objects.toArray());
|
||||
}
|
||||
method.setAccessible(true);
|
||||
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;
|
||||
}
|
||||
|
||||
List<String> tabComplete(CommandSender commandSender, String[] args) {
|
||||
if (varArgType == null && args.length > arguments.length + subCommand.length) {
|
||||
return null;
|
||||
}
|
||||
if (guardChecker != null && guardChecker.guard(commandSender, GuardCheckType.TAB_COMPLETE, new String[0], null) != GuardResult.ALLOWED) {
|
||||
return null;
|
||||
}
|
||||
int index = 0;
|
||||
List<String> argsList = new LinkedList<>(Arrays.asList(args));
|
||||
for (String value : subCommand) {
|
||||
String s = argsList.remove(0);
|
||||
if (argsList.isEmpty()) return Collections.singletonList(value);
|
||||
if (!value.equalsIgnoreCase(s)) return null;
|
||||
index++;
|
||||
if (commandPart == null) {
|
||||
return null;
|
||||
}
|
||||
int guardIndex = 0;
|
||||
for (TypeMapper<?> argument : arguments) {
|
||||
String s = argsList.remove(0);
|
||||
if (argsList.isEmpty()) {
|
||||
if (guards[guardIndex] != null && guards[guardIndex].guard(commandSender, GuardCheckType.TAB_COMPLETE, Arrays.copyOf(args, args.length - 1), s) != GuardResult.ALLOWED) {
|
||||
return null;
|
||||
}
|
||||
return argument.tabCompletes(commandSender, Arrays.copyOf(args, args.length - 1), s);
|
||||
}
|
||||
try {
|
||||
if (guards[guardIndex] != null && guards[guardIndex].guard(commandSender, GuardCheckType.TAB_COMPLETE, Arrays.copyOf(args, index), s) != GuardResult.ALLOWED) {
|
||||
return null;
|
||||
}
|
||||
if (argument.map(commandSender, Arrays.copyOf(args, index), s) == null) {
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
index++;
|
||||
guardIndex++;
|
||||
}
|
||||
if (varArgType != null && !argsList.isEmpty()) {
|
||||
while (!argsList.isEmpty()) {
|
||||
String s = argsList.remove(0);
|
||||
if (argsList.isEmpty()) {
|
||||
if (guards[guards.length - 1] != null && guards[guards.length - 1].guard(commandSender, GuardCheckType.TAB_COMPLETE, Arrays.copyOf(args, args.length - 1), s) != GuardResult.ALLOWED) {
|
||||
return null;
|
||||
}
|
||||
return arguments[arguments.length - 1].tabCompletes(commandSender, Arrays.copyOf(args, args.length - 1), s);
|
||||
}
|
||||
try {
|
||||
if (guards[guards.length - 1] != null && guards[guards.length - 1].guard(commandSender, GuardCheckType.TAB_COMPLETE, Arrays.copyOf(args, index), s) != GuardResult.ALLOWED) {
|
||||
return null;
|
||||
}
|
||||
if (arguments[arguments.length - 1].map(commandSender, Arrays.copyOf(args, index), s) == null) {
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
List<String> list = new ArrayList<>();
|
||||
commandPart.generateTabComplete(list, commandSender, args, 0);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,9 @@ import org.bukkit.command.CommandSender;
|
||||
import java.util.List;
|
||||
|
||||
public interface TypeMapper<T> {
|
||||
/**
|
||||
* The CommandSender can be null!
|
||||
*/
|
||||
default T map(CommandSender commandSender, String[] previousArguments, String s) {
|
||||
return map(previousArguments, s);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -25,7 +25,9 @@ import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
YoyoNow markierte diese Unterhaltung als gelöst
Veraltet
|
||||
import java.util.UUID;
|
||||
|
||||
public class SteamwarUser {
|
||||
|
||||
|
122
SpigotCore_Main/testsrc/de/steamwar/TestCommandSender.java
Normale Datei
@ -0,0 +1,122 @@
|
||||
/*
|
||||
YoyoNow markierte diese Unterhaltung als gelöst
Lixfel
hat
Die testsrc/.gitkeep kann doch mittlerweile eigentlich weg, oder? Die testsrc/.gitkeep kann doch mittlerweile eigentlich weg, oder?
YoyoNow
hat
Da hast du recht. Da hast du recht.
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<PermissionAttachmentInfo> getEffectivePermissions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOp() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOp(boolean b) {
|
||||
|
||||
}
|
||||
}
|
41
SpigotCore_Main/testsrc/de/steamwar/command/ExecutionIdentifier.java
Normale Datei
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.command;
|
||||
|
||||
public class ExecutionIdentifier extends RuntimeException {
|
||||
YoyoNow markierte diese Unterhaltung als gelöst
Lixfel
hat
Brauchst du den wirklich, oder hast du andernfalls silent-exceptions die irgendwo gecatcht und verworfen werden? Das wäre dann nämlich eher ein Zeichen davon, dass die Codearchitektur bescheiden ist. Eigentlich sollte man eine korrekte Ausführung daran erkennen, dass der Code ordnungsgemäß terminiert und Werte, welche sich ändern sollten, auf den korrekten Wert geändert wurden. Brauchst du den wirklich, oder hast du andernfalls silent-exceptions die irgendwo gecatcht und verworfen werden? Das wäre dann nämlich eher ein Zeichen davon, dass die Codearchitektur bescheiden ist. Eigentlich sollte man eine korrekte Ausführung daran erkennen, dass der Code ordnungsgemäß terminiert und Werte, welche sich ändern sollten, auf den korrekten Wert geändert wurden.
YoyoNow
hat
Naja ich will im test wissen, ob die methode wirklich ausgeführt wird, und das geht am besten indem ich wenn es ausgeführt wird eine Exception werfe und diese überprüfe. Naja ich will im test wissen, ob die methode wirklich ausgeführt wird, und das geht am besten indem ich wenn es ausgeführt wird eine Exception werfe und diese überprüfe.
Lixfel
hat
Dann weißt du aber nie, ob der Code, welcher danach noch ausgeführt wird, nicht den Wert wieder zurücksetzen würde/fehler werfen würde. Dann weißt du aber nie, ob der Code, welcher danach noch ausgeführt wird, nicht den Wert wieder zurücksetzen würde/fehler werfen würde.
YoyoNow
hat
Das brauche ich mit dem Test nicht prüfen, ich will dort erstmal nur prüfen ob an sich die methode ausgeführt wird, andere Tests werden noch folgen. Aber soweit tut das Framework Das brauche ich mit dem Test nicht prüfen, ich will dort erstmal nur prüfen ob an sich die methode ausgeführt wird, andere Tests werden noch folgen. Aber soweit tut das Framework
|
||||
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);
|
||||
}
|
||||
}
|
34
SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommand.java
Normale Datei
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
243
SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandPartTest.java
Normale Datei
@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.command;
|
||||
|
||||
import de.steamwar.TestCommandSender;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
public class SimpleCommandPartTest {
|
||||
|
||||
private CommandPart stringCommandPart;
|
||||
private CommandPart intCommandPart;
|
||||
private CommandPart chainedCommandPart;
|
||||
private CommandPart varArgCommandPart;
|
||||
|
||||
private CommandPart simpleGuardPart;
|
||||
|
||||
private CommandPart optionalCommandPart;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
stringCommandPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), null, null, null, GuardCheckType.COMMAND);
|
||||
intCommandPart = new CommandPart(SWCommandUtils.MAPPER_FUNCTIONS.get("int"), null, null, null, GuardCheckType.COMMAND);
|
||||
|
||||
chainedCommandPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), null, null, null, GuardCheckType.COMMAND);
|
||||
chainedCommandPart.setNext(new CommandPart(SWCommandUtils.MAPPER_FUNCTIONS.get("int"), null, null, null, GuardCheckType.COMMAND));
|
||||
|
||||
varArgCommandPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), null, String.class, null, GuardCheckType.COMMAND);
|
||||
|
||||
simpleGuardPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), (commandSender, guardCheckType, previousArguments, s) -> s.equals("hello") ? GuardResult.DENIED : GuardResult.ALLOWED, null, null, GuardCheckType.COMMAND);
|
||||
|
||||
optionalCommandPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), null, null, "hello", GuardCheckType.COMMAND);
|
||||
optionalCommandPart.setNext(new CommandPart(SWCommandUtils.createMapper("hello2", "world2"), null, null, null, GuardCheckType.COMMAND));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandPartTabCompleteNoArguments() {
|
||||
List<String> tabComplete = new ArrayList<>();
|
||||
stringCommandPart.generateTabComplete(tabComplete, new TestCommandSender(), new String[]{""}, 0);
|
||||
assertThat(tabComplete.size(), is(2));
|
||||
assertThat(tabComplete.get(0), is("hello"));
|
||||
assertThat(tabComplete.get(1), is("world"));
|
||||
}
|
||||
|
||||
@Test(expected = CommandParseException.class)
|
||||
public void testCommandExecuteInvalidArgument() {
|
||||
stringCommandPart.generateArgumentArray(new ArrayList<>(), new TestCommandSender(), new String[]{""}, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandExecuteValidArgument() {
|
||||
List<Object> argumentArray = new ArrayList<>();
|
||||
stringCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello"}, 0);
|
||||
assertThat(argumentArray.size(), is(1));
|
||||
assertThat(argumentArray.get(0), instanceOf(String.class));
|
||||
assertThat(argumentArray.get(0), is("hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandExecuteValidOtherArgument() {
|
||||
List<Object> argumentArray = new ArrayList<>();
|
||||
stringCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"world"}, 0);
|
||||
assertThat(argumentArray.size(), is(1));
|
||||
assertThat(argumentArray.get(0), instanceOf(String.class));
|
||||
assertThat(argumentArray.get(0), is("world"));
|
||||
}
|
||||
|
||||
@Test(expected = CommandParseException.class)
|
||||
public void testCommandExecuteNonNumberArgument() {
|
||||
intCommandPart.generateArgumentArray(new ArrayList<>(), new TestCommandSender(), new String[]{"world"}, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandExecuteValidNumberArgument() {
|
||||
List<Object> argumentArray = new ArrayList<>();
|
||||
intCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"0"}, 0);
|
||||
assertThat(argumentArray.size(), is(1));
|
||||
assertThat(argumentArray.get(0), instanceOf(int.class));
|
||||
assertThat(argumentArray.get(0), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChainedCommandExecuteValidArgument() {
|
||||
List<Object> argumentArray = new ArrayList<>();
|
||||
chainedCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello", "0"}, 0);
|
||||
assertThat(argumentArray.size(), is(2));
|
||||
assertThat(argumentArray.get(0), instanceOf(String.class));
|
||||
assertThat(argumentArray.get(0), is("hello"));
|
||||
assertThat(argumentArray.get(1), instanceOf(int.class));
|
||||
assertThat(argumentArray.get(1), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChainedCommandTabComplete() {
|
||||
List<String> tabCompletes = new ArrayList<>();
|
||||
chainedCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{""}, 0);
|
||||
assertThat(tabCompletes.size(), is(2));
|
||||
assertThat(tabCompletes.get(0), is("hello"));
|
||||
assertThat(tabCompletes.get(1), is("world"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChainedCommandTabCompleteOther() {
|
||||
List<String> tabCompletes = new ArrayList<>();
|
||||
chainedCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello", ""}, 0);
|
||||
assertThat(tabCompletes.size(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVarArgsCommandTabComplete() {
|
||||
List<String> tabCompletes = new ArrayList<>();
|
||||
varArgCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello"}, 0);
|
||||
assertThat(tabCompletes.size(), is(2));
|
||||
assertThat(tabCompletes.get(0), is("hello"));
|
||||
assertThat(tabCompletes.get(1), is("world"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVarArgsCommandTabCompleteDeeper() {
|
||||
List<String> tabCompletes = new ArrayList<>();
|
||||
varArgCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello", "world", "hello", "world"}, 0);
|
||||
System.out.println(tabCompletes);
|
||||
assertThat(tabCompletes.size(), is(2));
|
||||
assertThat(tabCompletes.get(0), is("hello"));
|
||||
assertThat(tabCompletes.get(1), is("world"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVarArgsCommandArgumentParsing() {
|
||||
List<Object> argumentArray = new ArrayList<>();
|
||||
varArgCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello"}, 0);
|
||||
assertThat(argumentArray.size(), is(1));
|
||||
assertThat(argumentArray.get(0), instanceOf(String[].class));
|
||||
assertThat((String[]) argumentArray.get(0), arrayWithSize(1));
|
||||
assertThat((String[]) argumentArray.get(0), is(new String[]{"hello"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVarArgsCommandArgumentParsingDeeper() {
|
||||
List<Object> argumentArray = new ArrayList<>();
|
||||
varArgCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello", "world", "hello", "world"}, 0);
|
||||
assertThat(argumentArray.size(), is(1));
|
||||
assertThat(argumentArray.get(0), instanceOf(String[].class));
|
||||
assertThat((String[]) argumentArray.get(0), arrayWithSize(4));
|
||||
assertThat((String[]) argumentArray.get(0), is(new String[]{"hello", "world", "hello", "world"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardCommandExecute() {
|
||||
List<Object> argumentArray = new ArrayList<>();
|
||||
simpleGuardPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello"}, 0);
|
||||
assertThat(argumentArray.size(), is(1));
|
||||
}
|
||||
|
||||
@Test(expected = CommandNoHelpException.class)
|
||||
public void testGuardGuardCheck() {
|
||||
simpleGuardPart.guardCheck(new TestCommandSender(), new String[]{"hello"}, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardCommandExecuteValid() {
|
||||
List<Object> argumentArray = new ArrayList<>();
|
||||
simpleGuardPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"world"}, 0);
|
||||
assertThat(argumentArray.size(), is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuardGuardCheckValid() {
|
||||
boolean guardResult = simpleGuardPart.guardCheck(new TestCommandSender(), new String[]{"world"}, 0);
|
||||
assertThat(guardResult, is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalCommandPartTabComplete() {
|
||||
List<String> tabCompletes = new ArrayList<>();
|
||||
optionalCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{""}, 0);
|
||||
assertThat(tabCompletes.size(), is(4));
|
||||
assertThat(tabCompletes.get(0), is("hello"));
|
||||
assertThat(tabCompletes.get(1), is("world"));
|
||||
assertThat(tabCompletes.get(2), is("hello2"));
|
||||
assertThat(tabCompletes.get(3), is("world2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalCommandPartTabCompleteSecond() {
|
||||
List<String> tabCompletes = new ArrayList<>();
|
||||
optionalCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello", ""}, 0);
|
||||
assertThat(tabCompletes.size(), is(2));
|
||||
assertThat(tabCompletes.get(0), is("hello2"));
|
||||
assertThat(tabCompletes.get(1), is("world2"));
|
||||
}
|
||||
|
||||
@Test(expected = CommandParseException.class)
|
||||
public void testOptionalCommandPartExecution() {
|
||||
optionalCommandPart.generateArgumentArray(new ArrayList<>(), new TestCommandSender(), new String[]{""}, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalCommandPartExecutionValid() {
|
||||
List<Object> argumentArray = new ArrayList<>();
|
||||
optionalCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello2"}, 0);
|
||||
assertThat(argumentArray.size(), is(2));
|
||||
assertThat(argumentArray.get(0), is("hello"));
|
||||
assertThat(argumentArray.get(1), is("hello2"));
|
||||
}
|
||||
|
||||
@Test(expected = CommandParseException.class)
|
||||
public void testOptionalCommandPartExecutionInvalid() {
|
||||
optionalCommandPart.generateArgumentArray(new ArrayList<>(), new TestCommandSender(), new String[]{"hello"}, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalCommandPartExecutionFullyValid() {
|
||||
List<Object> argumentArray = new ArrayList<>();
|
||||
optionalCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"world", "hello2"}, 0);
|
||||
assertThat(argumentArray.size(), is(2));
|
||||
assertThat(argumentArray.get(0), is("world"));
|
||||
assertThat(argumentArray.get(1), is("hello2"));
|
||||
}
|
||||
}
|
61
SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java
Normale Datei
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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) {
|
||||
securityException.printStackTrace();
|
||||
assert false;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ build:
|
||||
- "cp ~/gradle.properties ."
|
||||
- "chmod u+x build.gradle"
|
||||
- "./gradlew buildProject"
|
||||
- "./gradlew test"
|
||||
|
||||
artifacts:
|
||||
"/binarys/spigotcore.jar": "build/libs/spigotcore.jar"
|
||||
|
Du und deine Import-Sauberkeit ;).