CommandFramework #107
@ -19,9 +19,11 @@
|
||||
|
||||
package de.steamwar.command;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import de.steamwar.message.Message;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.reflect.Method;
|
||||
@ -29,21 +31,33 @@ import java.lang.reflect.Parameter;
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.IntPredicate;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class SWCommand {
|
||||
|
||||
private List<String> help = null;
|
||||
|
||||
private final Command command;
|
||||
private final List<SubCommand> commandList = new ArrayList<>();
|
||||
private final List<SubCommand> commandHelpList = new ArrayList<>();
|
||||
private final Map<String, TypeMapper<?>> localTypeMapper = new HashMap<>();
|
||||
|
||||
private final Message message;
|
||||
|
||||
protected SWCommand(String command) {
|
||||
this(command, new String[0]);
|
||||
}
|
||||
|
||||
protected SWCommand(String command, Message message) {
|
||||
this(command, message, new String[0]);
|
||||
}
|
||||
|
||||
protected SWCommand(String command, String... aliases) {
|
||||
this(command, null, aliases);
|
||||
}
|
||||
|
||||
protected SWCommand(String command, Message message, String... aliases) {
|
||||
this.message = message;
|
||||
this.command = new Command(command, "", "/" + command, Arrays.asList(aliases)) {
|
||||
@Override
|
||||
public boolean execute(CommandSender sender, String alias, String[] args) {
|
||||
@ -67,7 +81,7 @@ public abstract class SWCommand {
|
||||
unregister();
|
||||
register();
|
||||
|
||||
Method[] methods = getClass().getDeclaredMethods();
|
||||
List<Method> methods = methods();
|
||||
for (Method method : methods) {
|
||||
addMapper(Mapper.class, method, i -> i == 0, false, TypeMapper.class, (anno, typeMapper) -> {
|
||||
(anno.local() ? localTypeMapper : SWCommandUtils.MAPPER_FUNCTIONS).putIfAbsent(anno.value(), typeMapper);
|
||||
@ -78,16 +92,15 @@ public abstract class SWCommand {
|
||||
add(Register.class, method, i -> i > 0, true, null, (anno, parameters) -> {
|
||||
if (!anno.help()) return;
|
||||
if (parameters.length != 2) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "The method '" + method.toString() + "' is lacking parameters or has too many");
|
||||
throw new SecurityException("The method '" + method.toString() + "' is lacking parameters or has too many");
|
||||
}
|
||||
if (!parameters[parameters.length - 1].isVarArgs()) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "The method '" + method.toString() + "' is lacking the varArgs parameters as last Argument");
|
||||
throw new SecurityException("The method '" + method.toString() + "' is lacking the varArgs parameters as last Argument");
|
||||
}
|
||||
if (parameters[parameters.length - 1].getType().getComponentType() != String.class) {
|
||||
YoyoNow markierte diese Unterhaltung als gelöst
Veraltet
|
||||
Bukkit.getLogger().log(Level.WARNING, "The method '" + method.toString() + "' is lacking the varArgs parameters of type '" + String.class.getTypeName() + "' as last Argument");
|
||||
return;
|
||||
throw new SecurityException("The method '" + method.toString() + "' is lacking the varArgs parameters of type '" + String.class.getTypeName() + "' as last Argument");
|
||||
}
|
||||
commandHelpList.add(new SubCommand(this, method, anno.value(), new HashMap<>()));
|
||||
commandHelpList.add(new SubCommand(this, method, anno.description(), anno.value(), new HashMap<>()));
|
||||
});
|
||||
}
|
||||
for (Method method : methods) {
|
||||
@ -105,11 +118,10 @@ public abstract class SWCommand {
|
||||
}
|
||||
String name = mapper != null ? mapper.value() : clazz.getTypeName();
|
||||
if (!SWCommandUtils.MAPPER_FUNCTIONS.containsKey(name) && !localTypeMapper.containsKey(name)) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "The parameter '" + parameter.toString() + "' is using an unsupported Mapper of type '" + name + "'");
|
||||
return;
|
||||
throw new SecurityException("The parameter '" + parameter.toString() + "' is using an unsupported Mapper of type '" + name + "'");
|
||||
}
|
||||
}
|
||||
commandList.add(new SubCommand(this, method, anno.value(), localTypeMapper));
|
||||
commandList.add(new SubCommand(this, method, anno.description(), anno.value(), localTypeMapper));
|
||||
});
|
||||
|
||||
this.commandList.sort((o1, o2) -> {
|
||||
@ -121,7 +133,15 @@ public abstract class SWCommand {
|
||||
o2.varArgType != null ? Integer.MAX_VALUE : o2.arguments.length);
|
||||
}
|
||||
});
|
||||
commandHelpList.sort(Comparator.comparingInt(o -> -o.subCommand.length));
|
||||
commandHelpList.sort((o1, o2) -> {
|
||||
int compare = Integer.compare(-o1.subCommand.length, -o2.subCommand.length);
|
||||
if (compare != 0) {
|
||||
return compare;
|
||||
} else {
|
||||
return Integer.compare(o1.method.getDeclaringClass() == SWCommand.class ? 1 : 0,
|
||||
o2.method.getDeclaringClass() == SWCommand.class ? 1 : 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,16 +151,13 @@ public abstract class SWCommand {
|
||||
|
||||
YoyoNow markierte diese Unterhaltung als gelöst
Veraltet
Lixfel
hat
Nope nope nope. Lass dir beim Erstellen des Commands ein Message-Objekt übergeben. Nope nope nope. Lass dir beim Erstellen des Commands ein Message-Objekt übergeben.
YoyoNow
hat
Doch, Doch, Doch, weil ich will kein Sache übergeben bekommen! Ich will es auch optional halten usw usw! Doch, Doch, Doch, weil ich will kein Sache übergeben bekommen! Ich will es auch optional halten usw usw!
Lixfel
hat
Dann nutze einen Setter. Dann nutze einen Setter.
YoyoNow
hat
Ist nicht einfach so da, ich will das ich nichts dafür aufrufen oder sonst machen muss! So wie das Register und Mapper und so. Ist nicht einfach so da, ich will das ich nichts dafür aufrufen oder sonst machen muss! So wie das Register und Mapper und so.
Lixfel
hat
Der Einsatz von Reflections zu diesem Grund ist einfach nur unangemessen. Ein Objekt MESSAGE zur Verfügung zu stellen, ist genauso. Warum nicht einfach message als protected Parameter im SWCommand haben, dann kann das auch einfach problemlos gesetzt werden.... Der Einsatz von Reflections zu diesem Grund ist einfach nur unangemessen. Ein Objekt MESSAGE zur Verfügung zu stellen, ist genauso. Warum nicht einfach message als protected Parameter im SWCommand haben, dann kann das auch einfach problemlos gesetzt werden....
YoyoNow
hat
Lixfel dieses System kann man so noch einfach aus dem SpigotCore kopieren und woanders verwenden, wenn ich das verdrahte nicht mehr! Lixfel dieses System kann man so noch einfach aus dem SpigotCore kopieren und woanders verwenden, wenn ich das verdrahte nicht mehr!
Lixfel
hat
Wohin hast du denn vor, dieses System eins zu eins zu kopieren? Wohin hast du denn vor, dieses System eins zu eins zu kopieren?
YoyoNow
hat
Zeanon verwendet es in einem seiner Projekte. Zeanon verwendet es in einem seiner Projekte.
Lixfel
hat
Njoa, Also das Message-System ist exakt eine Klasse mehr. Ich glaube, es ist komplexer, Support für mit und ohne Message-System vorzuhalten, als einfach das Message-System mit dranzuhängen. Njoa, Also das Message-System ist exakt eine Klasse mehr. Ich glaube, es ist komplexer, Support für mit und ohne Message-System vorzuhalten, als einfach das Message-System mit dranzuhängen.
|
||||
Parameter[] parameters = method.getParameters();
|
||||
if (!parameterTester.test(parameters.length)) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "The method '" + method.toString() + "' is lacking parameters or has too many");
|
||||
return;
|
||||
throw new SecurityException("The method '" + method.toString() + "' is lacking parameters or has too many");
|
||||
}
|
||||
if (firstParameter && !CommandSender.class.isAssignableFrom(parameters[0].getType())) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "The method '" + method.toString() + "' is lacking the first parameter of type '" + CommandSender.class.getTypeName() + "'");
|
||||
return;
|
||||
throw new SecurityException("The method '" + method.toString() + "' is lacking the first parameter of type '" + CommandSender.class.getTypeName() + "'");
|
||||
}
|
||||
if (returnType != null && method.getReturnType() != returnType) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "The method '" + method.toString() + "' is lacking the desired return type '" + returnType.getTypeName() + "'");
|
||||
return;
|
||||
throw new SecurityException("The method '" + method.toString() + "' is lacking the desired return type '" + returnType.getTypeName() + "'");
|
||||
}
|
||||
Arrays.stream(anno).forEach(t -> consumer.accept(t, parameters));
|
||||
}
|
||||
@ -156,6 +173,12 @@ public abstract class SWCommand {
|
||||
});
|
||||
}
|
||||
|
||||
private List<Method> methods() {
|
||||
List<Method> methods = new ArrayList<>(Arrays.asList(getClass().getDeclaredMethods()));
|
||||
methods.addAll(Arrays.asList(SWCommand.class.getDeclaredMethods()));
|
||||
return methods;
|
||||
}
|
||||
|
||||
public void unregister() {
|
||||
SWCommandUtils.knownCommandMap.remove(command.getName());
|
||||
command.getAliases().forEach(SWCommandUtils.knownCommandMap::remove);
|
||||
@ -166,12 +189,68 @@ public abstract class SWCommand {
|
||||
SWCommandUtils.commandMap.register("steamwar", this.command);
|
||||
}
|
||||
|
||||
public void inject(Plugin plugin) {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SWCommand.this.unregister();
|
||||
SWCommand.this.register();
|
||||
}
|
||||
}.runTask(plugin);
|
||||
}
|
||||
|
||||
@Register(help = true)
|
||||
private void internalHelp(CommandSender sender, String... args) {
|
||||
if (help == null) {
|
||||
help = new ArrayList<>();
|
||||
commandList.forEach(subCommand -> {
|
||||
StringBuilder st = new StringBuilder();
|
||||
st.append("§8/§7").append(command.getName()).append(" ");
|
||||
st.append("§7").append(String.join(" ", subCommand.subCommand));
|
||||
String cmd = Arrays.stream(subCommand.parameters)
|
||||
.skip(1)
|
||||
.map(parameter -> {
|
||||
Name name = parameter.getAnnotation(Name.class);
|
||||
if (name != null) {
|
||||
return name.name();
|
||||
} else {
|
||||
return parameter.getName();
|
||||
}
|
||||
})
|
||||
.map(param -> " §8[§e" + param + "§8]")
|
||||
.collect(Collectors.joining(""));
|
||||
YoyoNow markierte diese Unterhaltung als gelöst
Veraltet
Lixfel
hat
Warum muss das jetzt wieder plötzlich injected werden? Warum muss das jetzt wieder plötzlich injected werden?
YoyoNow
hat
Weil wichtig und so und API, welche uns bei zeiten helfen kann. Weil wichtig und so und API, welche uns bei zeiten helfen kann.
|
||||
st.append(cmd);
|
||||
if (subCommand.varArgType != null) {
|
||||
st.append("§7...");
|
||||
}
|
||||
if (!subCommand.description.isEmpty()) {
|
||||
st.append("§8 - §7").append(message != null ? message.parse(subCommand.description, sender) : subCommand.description);
|
||||
}
|
||||
help.add(st.toString());
|
||||
});
|
||||
}
|
||||
String string = "/" + command.getName() + " " + String.join(" ", args);
|
||||
sender.sendMessage("§7----==== §e" + command.getName() + " §7====----");
|
||||
sender.sendMessage("§7" + (message != null ? message.parse("COMMAND_ALIASES", sender) : "Aliases") + "§8:§e " + String.join("§8,§e ", command.getAliases()));
|
||||
help.forEach(s -> {
|
||||
if (s.replaceAll("§[0-9A-Z]", "").startsWith(string)) {
|
||||
sender.sendMessage(s);
|
||||
Lixfel
hat
Sämtliche Color-Codes sind auch Teil des Message-Systems. Farben haben in anderen Sprach- und Kulturkreisen andere Bedeutungen. Sämtliche Color-Codes sind auch Teil des Message-Systems. Farben haben in anderen Sprach- und Kulturkreisen andere Bedeutungen.
YoyoNow
hat
Haben nicht die SteamwarFarben aus einem Grund? Haben nicht die SteamwarFarben aus einem Grund?
Lixfel
hat
? Deutsch? Wir haben die Farben aus dem Grund, dass wir Stil haben und nicht wie nahezu jeder andere Server einfach einen kunterbunten flashy Colorstyle haben. ? Deutsch? Wir haben die Farben aus dem Grund, dass wir Stil haben und nicht wie nahezu jeder andere Server einfach einen kunterbunten flashy Colorstyle haben.
YoyoNow
hat
*wir \*wir
YoyoNow
hat
Können wir nicht dann trotzdem unser Style in jede Message setzten? Können wir nicht dann trotzdem unser Style in jede Message setzten?
Lixfel
hat
Ja genau, in die Message, und nicht in den Code. Ja genau, in die Message, und nicht in den Code.
YoyoNow
hat
Ok und wie soll ich dies deiner Meinung dann machen? Ok und wie soll ich dies deiner Meinung dann machen?
Also wie soll ich die keys nennen, wo soll ich dies dokumentieren und was soll ich machen, wenn diese nicht existieren?
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String translate(String description, CommandSender sender) {
|
||||
return message != null ? message.parse(description, sender) : description;
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD})
|
||||
@Repeatable(Register.Registeres.class)
|
||||
protected @interface Register {
|
||||
String[] value() default {};
|
||||
|
||||
String description() default "";
|
||||
|
||||
boolean help() default false;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ -196,4 +275,10 @@ public abstract class SWCommand {
|
||||
|
||||
boolean local() default false;
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
protected @interface Name {
|
||||
String name();
|
||||
}
|
||||
}
|
||||
|
@ -33,18 +33,21 @@ import static de.steamwar.command.SWCommandUtils.*;
|
||||
class SubCommand {
|
||||
|
||||
private SWCommand swCommand;
|
||||
private Method method;
|
||||
Method method;
|
||||
String description;
|
||||
Parameter[] parameters;
|
||||
String[] subCommand;
|
||||
TypeMapper<?>[] arguments;
|
||||
private Predicate<CommandSender> commandSenderPredicate;
|
||||
private Function<CommandSender, ?> commandSenderFunction;
|
||||
Class<?> varArgType = null;
|
||||
|
||||
SubCommand(SWCommand swCommand, Method method, String[] subCommand, Map<String, TypeMapper<?>> localTypeMapper) {
|
||||
SubCommand(SWCommand swCommand, Method method, String description, String[] subCommand, Map<String, TypeMapper<?>> localTypeMapper) {
|
||||
this.swCommand = swCommand;
|
||||
this.method = method;
|
||||
this.description = description;
|
||||
|
||||
Parameter[] parameters = method.getParameters();
|
||||
parameters = method.getParameters();
|
||||
commandSenderPredicate = sender -> parameters[0].getType().isAssignableFrom(sender.getClass());
|
||||
commandSenderFunction = sender -> parameters[0].getType().cast(sender);
|
||||
this.subCommand = subCommand;
|
||||
|
Warum nicht direkt die WARNINGS printen bzw. sind das nicht Programmierfehler, also müssten nicht Exceptions fliegen?
Kann ich gerne als Exception fliegen lassen, aber ich will es gesamt halt sagen.
Programmierfehler sollst du nicht mehr oder weniger silent verstecken, sondern korrekt werfen, was wiederum zum einen Logeinträge hinterlässt, Stacktraces und ggf. den Nutzer über die fehlgeschlagene Operation informiert. Exceptions sind sprichwörtlich dafür gemacht.