diff options
Diffstat (limited to 'dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java')
-rw-r--r-- | dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java | 821 |
1 files changed, 406 insertions, 415 deletions
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java index 93ac0ee..ddd5420 100644 --- a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java +++ b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java @@ -1,415 +1,406 @@ -package io.dico.dicore.command.registration.reflect;
-
-import io.dico.dicore.command.*;
-import io.dico.dicore.command.annotation.*;
-import io.dico.dicore.command.annotation.GroupMatchedCommands.GroupEntry;
-import io.dico.dicore.command.parameter.Parameter;
-import io.dico.dicore.command.parameter.ParameterList;
-import io.dico.dicore.command.parameter.type.IParameterTypeSelector;
-import io.dico.dicore.command.parameter.type.MapBasedParameterTypeSelector;
-import io.dico.dicore.command.parameter.type.ParameterType;
-import io.dico.dicore.command.parameter.type.ParameterTypes;
-import io.dico.dicore.command.predef.PredefinedCommand;
-import org.bukkit.command.CommandSender;
-import org.bukkit.command.ConsoleCommandSender;
-import org.bukkit.entity.Player;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.*;
-import java.util.function.Consumer;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-/**
- * Takes care of turning a reflection {@link Method} into a command and more.
- */
-public class ReflectiveRegistration {
- /**
- * This object provides names of the parameters.
- * Oddly, the AnnotationParanamer extensions require a 'fallback' paranamer to function properly without
- * requiring ALL parameters to have that flag. This is weird because it should just use the AdaptiveParanamer on an upper level to
- * determine the name of each individual flag. Oddly this isn't how it works, so the fallback works the same way as the AdaptiveParanamer does.
- * It's just linked instead of using an array for that part. Then we can use an AdaptiveParanamer for the latest fallback, to get bytecode names
- * or, finally, to get the Jvm-provided parameter names.
- */
- //private static final Paranamer paranamer = new CachingParanamer(new BytecodeReadingParanamer());
- @SuppressWarnings("StatementWithEmptyBody")
- private static String[] lookupParameterNames(Method method, java.lang.reflect.Parameter[] parameters, int start) {
- int n = parameters.length;
- String[] out = new String[n - start];
-
- //String[] bytecode;
- //try {
- // bytecode = paranamer.lookupParameterNames(method, false);
- //} catch (Exception ex) {
- // bytecode = new String[0];
- // System.err.println("ReflectiveRegistration.lookupParameterNames failed to read bytecode");
- // //ex.printStackTrace();
- //}
- //int bn = bytecode.length;
-
- for (int i = start; i < n; i++) {
- java.lang.reflect.Parameter parameter = parameters[i];
- Flag flag = parameter.getAnnotation(Flag.class);
- NamedArg namedArg = parameter.getAnnotation(NamedArg.class);
-
- boolean isFlag = flag != null;
- String name;
- if (namedArg != null && !(name = namedArg.value()).isEmpty()) {
- } else if (isFlag && !(name = flag.value()).isEmpty()) {
- //} else if (i < bn && (name = bytecode[i]) != null && !name.isEmpty()) {
- } else {
- name = parameter.getName();
- }
-
- if (isFlag) {
- name = '-' + name;
- } else {
- int idx = 0;
- while (name.startsWith("-", idx)) {
- idx++;
- }
- name = name.substring(idx);
- }
-
- out[i - start] = name;
- }
-
- return out;
- }
-
- public static void parseCommandGroup(ICommandAddress address, Class<?> clazz, Object instance) throws CommandParseException {
- parseCommandGroup(address, ParameterTypes.getSelector(), clazz, instance);
- }
-
- public static void parseCommandGroup(ICommandAddress address, IParameterTypeSelector selector, Class<?> clazz, Object instance) throws CommandParseException {
- boolean requireStatic = instance == null;
- if (!requireStatic && !clazz.isInstance(instance)) {
- throw new CommandParseException();
- }
-
- List<Method> methods = new LinkedList<>(Arrays.asList(clazz.getDeclaredMethods()));
-
- Iterator<Method> it = methods.iterator();
- for (Method method; it.hasNext(); ) {
- method = it.next();
-
- if (requireStatic && !Modifier.isStatic(method.getModifiers())) {
- it.remove();
- continue;
- }
-
- if (method.isAnnotationPresent(CmdParamType.class)) {
- it.remove();
-
- if (method.getReturnType() != ParameterType.class || method.getParameterCount() != 0) {
- throw new CommandParseException("Invalid CmdParamType method: must return ParameterType and take no arguments");
- }
-
- ParameterType<?, ?> type;
- try {
- Object inst = Modifier.isStatic(method.getModifiers()) ? null : instance;
- type = (ParameterType<?, ?>) method.invoke(inst);
- Objects.requireNonNull(type, "ParameterType returned is null");
- } catch (Exception ex) {
- throw new CommandParseException("Error occurred whilst getting ParameterType from CmdParamType method '" + method.toGenericString() + "'", ex);
- }
-
- if (selector == ParameterTypes.getSelector()) {
- selector = new MapBasedParameterTypeSelector(true);
- }
-
- selector.addType(method.getAnnotation(CmdParamType.class).infolessAlias(), type);
- }
- }
-
- GroupMatcherCache groupMatcherCache = new GroupMatcherCache(clazz, address);
- for (Method method : methods) {
- if (method.isAnnotationPresent(Cmd.class)) {
- ICommandAddress parsed = parseCommandMethod(selector, method, instance);
- groupMatcherCache.getGroupFor(method).addChild(parsed);
- }
- }
-
- }
-
- private static final class GroupMatcherCache {
- private ModifiableCommandAddress groupRootAddress;
- private GroupEntry[] matchEntries;
- private Pattern[] patterns;
- private ModifiableCommandAddress[] addresses;
-
- GroupMatcherCache(Class<?> clazz, ICommandAddress groupRootAddress) throws CommandParseException {
- this.groupRootAddress = (ModifiableCommandAddress) groupRootAddress;
-
- GroupMatchedCommands groupMatchedCommands = clazz.getAnnotation(GroupMatchedCommands.class);
- GroupEntry[] matchEntries = groupMatchedCommands == null ? new GroupEntry[0] : groupMatchedCommands.value();
-
- Pattern[] patterns = new Pattern[matchEntries.length];
- for (int i = 0; i < matchEntries.length; i++) {
- GroupEntry matchEntry = matchEntries[i];
- if (matchEntry.group().isEmpty() || matchEntry.regex().isEmpty()) {
- throw new CommandParseException("Empty group or regex in GroupMatchedCommands entry");
- }
- try {
- patterns[i] = Pattern.compile(matchEntry.regex());
- } catch (PatternSyntaxException ex) {
- throw new CommandParseException(ex);
- }
- }
-
- this.matchEntries = matchEntries;
- this.patterns = patterns;
- this.addresses = new ModifiableCommandAddress[this.matchEntries.length];
- }
-
- ModifiableCommandAddress getGroupFor(Method method) {
- String name = method.getName();
-
- GroupEntry[] matchEntries = this.matchEntries;
- Pattern[] patterns = this.patterns;
- ModifiableCommandAddress[] addresses = this.addresses;
-
- for (int i = 0; i < matchEntries.length; i++) {
- GroupEntry matchEntry = matchEntries[i];
- if (patterns[i].matcher(name).matches()) {
- if (addresses[i] == null) {
- ChildCommandAddress placeholder = new ChildCommandAddress();
- placeholder.setupAsPlaceholder(matchEntry.group(), matchEntry.groupAliases());
- addresses[i] = placeholder;
- groupRootAddress.addChild(placeholder);
- generateCommands(placeholder, matchEntry.generatedCommands());
- setDescription(placeholder, matchEntry.description(), matchEntry.shortDescription());
- }
- return addresses[i];
- }
- }
-
- return groupRootAddress;
- }
-
- }
-
- public static ICommandAddress parseCommandMethod(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException {
- return new ReflectiveCommand(selector, method, instance).getAddress();
- }
-
- static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] parameters) throws CommandParseException {
- ParameterList list = command.getParameterList();
-
- boolean hasReceiverParameter = false;
- boolean hasSenderParameter = false;
- boolean hasContextParameter = false;
- boolean hasContinuationParameter = false;
-
- int start = 0;
- int end = parameters.length;
-
- Class<?> senderParameterType = null;
-
- if (parameters.length > start
- && command.getInstance() instanceof ICommandInterceptor
- && ICommandReceiver.class.isAssignableFrom(parameters[start].getType())) {
- hasReceiverParameter = true;
- start++;
- }
-
- if (parameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = parameters[start].getType())) {
- hasSenderParameter = true;
- start++;
- }
-
- if (parameters.length > start && parameters[start].getType() == ExecutionContext.class) {
- hasContextParameter = true;
- start++;
- }
-
- if (parameters.length > start && parameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) {
- hasContinuationParameter = true;
- end--;
- }
-
- String[] parameterNames = lookupParameterNames(method, parameters, start);
- for (int i = start, n = end; i < n; i++) {
- Parameter<?, ?> parameter = parseParameter(selector, method, parameters[i], parameterNames[i - start]);
- list.addParameter(parameter);
- }
- command.setParameterOrder(hasContinuationParameter ? Arrays.copyOfRange(parameterNames, 0, parameterNames.length - 1) : parameterNames);
-
- RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class);
- if (cmdPermissions != null) {
- for (String permission : cmdPermissions.value()) {
- command.addContextFilter(IContextFilter.permission(permission));
- }
-
- if (cmdPermissions.inherit()) {
- command.addContextFilter(IContextFilter.INHERIT_PERMISSIONS);
- }
- } else {
- command.addContextFilter(IContextFilter.INHERIT_PERMISSIONS);
- }
-
- RequireParameters reqPar = method.getAnnotation(RequireParameters.class);
- if (reqPar != null) {
- list.setRequiredCount(reqPar.value() < 0 ? Integer.MAX_VALUE : reqPar.value());
- } else {
- list.setRequiredCount(list.getIndexedParameters().size());
- }
-
- /*
- PreprocessArgs preprocessArgs = method.getAnnotation(PreprocessArgs.class);
- if (preprocessArgs != null) {
- IArgumentPreProcessor preProcessor = IArgumentPreProcessor.mergeOnTokens(preprocessArgs.tokens(), preprocessArgs.escapeChar());
- list.setArgumentPreProcessor(preProcessor);
- }*/
-
- Desc desc = method.getAnnotation(Desc.class);
- if (desc != null) {
- String[] array = desc.value();
- if (array.length == 0) {
- command.setDescription(desc.shortVersion());
- } else {
- command.setDescription(array);
- }
- } else {
- command.setDescription();
- }
-
- if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) {
- command.addContextFilter(IContextFilter.PLAYER_ONLY);
- } else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) {
- command.addContextFilter(IContextFilter.CONSOLE_ONLY);
- } else if (method.isAnnotationPresent(RequirePlayer.class)) {
- command.addContextFilter(IContextFilter.PLAYER_ONLY);
- } else if (method.isAnnotationPresent(RequireConsole.class)) {
- command.addContextFilter(IContextFilter.CONSOLE_ONLY);
- }
-
- list.setRepeatFinalParameter(parameters.length > start && parameters[parameters.length - 1].isVarArgs());
- list.setFinalParameterMayBeFlag(true);
-
- int flags = 0;
- if (hasContinuationParameter) flags |= 1;
- flags <<= 1;
- if (hasContextParameter) flags |= 1;
- flags <<= 1;
- if (hasSenderParameter) flags |= 1;
- flags <<= 1;
- if (hasReceiverParameter) flags |= 1;
- return flags;
- }
-
- public static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command) throws CommandParseException {
- return parseCommandAttributes(selector, method, command, method.getParameters());
- }
-
- public static Parameter<?, ?> parseParameter(IParameterTypeSelector selector, Method method, java.lang.reflect.Parameter parameter, String name) throws CommandParseException {
- Class<?> type = parameter.getType();
- if (parameter.isVarArgs()) {
- type = type.getComponentType();
- }
-
- Annotation[] annotations = parameter.getAnnotations();
- Flag flag = null;
- Annotation typeAnnotation = null;
- Desc desc = null;
-
- for (Annotation annotation : annotations) {
- //noinspection StatementWithEmptyBody
- if (annotation instanceof NamedArg) {
- // do nothing
- } else if (annotation instanceof Flag) {
- if (flag != null) {
- throw new CommandParseException("Multiple flags for the same parameter");
- }
- flag = (Flag) annotation;
- } else if (annotation instanceof Desc) {
- if (desc != null) {
- throw new CommandParseException("Multiple descriptions for the same parameter");
- }
- desc = (Desc) annotation;
- } else {
- if (typeAnnotation != null) {
- throw new CommandParseException("Multiple parameter type annotations for the same parameter");
- }
- typeAnnotation = annotation;
- }
- }
-
- if (flag == null && name.startsWith("-")) {
- throw new CommandParseException("Non-flag parameter's name starts with -");
- } else if (flag != null && !name.startsWith("-")) {
- throw new CommandParseException("Flag parameter's name doesn't start with -");
- }
-
- ParameterType<Object, Object> parameterType = selector.selectAny(type, typeAnnotation == null ? null : typeAnnotation.getClass());
- if (parameterType == null) {
- throw new CommandParseException("IParameter type not found for parameter " + name + " in method " + method.toString());
- }
-
- Object parameterInfo;
- if (typeAnnotation == null) {
- parameterInfo = null;
- } else try {
- parameterInfo = parameterType.getParameterConfig() == null ? null : parameterType.getParameterConfig().getParameterInfo(typeAnnotation);
- } catch (Exception ex) {
- throw new CommandParseException("Invalid parameter config", ex);
- }
-
- String descString = desc == null ? null : CommandAnnotationUtils.getShortDescription(desc);
-
- try {
- //noinspection unchecked
- String flagPermission = flag == null || flag.permission().isEmpty() ? null : flag.permission();
- return new Parameter<>(name, descString, parameterType, parameterInfo, type.isPrimitive(), name.startsWith("-"), flagPermission);
- } catch (Exception ex) {
- throw new CommandParseException("Invalid parameter", ex);
- }
- }
-
- public static void generateCommands(ICommandAddress address, String[] input) {
- for (String value : input) {
- Consumer<ICommandAddress> consumer = PredefinedCommand.getPredefinedCommandGenerator(value);
- if (consumer == null) {
- System.out.println("[Command Warning] generated command '" + value + "' could not be found");
- } else {
- consumer.accept(address);
- }
- }
- }
-
- /*
- Desired format
-
- @Cmd({"tp", "tpto"})
- @RequirePermissions("teleport.self")
- public (static) String|void onCommand(Player sender, Player target, @Flag("force", permission = "teleport.self.force") boolean force) {
- Validate.isTrue(force || !hasTpToggledOff(target), "Target has teleportation disabled. Use -force to ignore");
- sender.teleport(target);
- //return
- }
-
- parser needs to:
- - see the @Cmd and create a CommandTree for it
- - see that it must be a Player executing the command
- - add an indexed IParameter for a Player type
- - add a flag parameter named force, that consumes no arguments.
- - see that setting the force flag requires a permission
- */
-
- private static void setDescription(ICommandAddress address, String[] array, String shortVersion) {
- if (!address.hasCommand()) {
- return;
- }
-
- if (array.length == 0) {
- address.getCommand().setDescription(shortVersion);
- } else {
- address.getCommand().setDescription(array);
- }
-
- }
-
-}
+package io.dico.dicore.command.registration.reflect; + +import io.dico.dicore.command.*; +import io.dico.dicore.command.annotation.*; +import io.dico.dicore.command.annotation.GroupMatchedCommands.GroupEntry; +import io.dico.dicore.command.parameter.Parameter; +import io.dico.dicore.command.parameter.ParameterList; +import io.dico.dicore.command.parameter.type.IParameterTypeSelector; +import io.dico.dicore.command.parameter.type.MapBasedParameterTypeSelector; +import io.dico.dicore.command.parameter.type.ParameterType; +import io.dico.dicore.command.parameter.type.ParameterTypes; +import io.dico.dicore.command.predef.PredefinedCommand; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.function.Consumer; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import static io.dico.dicore.command.registration.reflect.ReflectiveCallFlags.*; + +/** + * Takes care of turning a reflection {@link Method} into a command and more. + */ +public class ReflectiveRegistration { + /** + * This object provides names of the parameters. + * Oddly, the AnnotationParanamer extensions require a 'fallback' paranamer to function properly without + * requiring ALL parameters to have that flag. This is weird because it should just use the AdaptiveParanamer on an upper level to + * determine the name of each individual flag. Oddly this isn't how it works, so the fallback works the same way as the AdaptiveParanamer does. + * It's just linked instead of using an array for that part. Then we can use an AdaptiveParanamer for the latest fallback, to get bytecode names + * or, finally, to get the Jvm-provided parameter names. + */ + //private static final Paranamer paranamer = new CachingParanamer(new BytecodeReadingParanamer()); + @SuppressWarnings("StatementWithEmptyBody") + private static String[] lookupParameterNames(Method method, java.lang.reflect.Parameter[] parameters, int start) { + int n = parameters.length; + String[] out = new String[n - start]; + + //String[] bytecode; + //try { + // bytecode = paranamer.lookupParameterNames(method, false); + //} catch (Exception ex) { + // bytecode = new String[0]; + // System.err.println("ReflectiveRegistration.lookupParameterNames failed to read bytecode"); + // //ex.printStackTrace(); + //} + //int bn = bytecode.length; + + for (int i = start; i < n; i++) { + java.lang.reflect.Parameter parameter = parameters[i]; + Flag flag = parameter.getAnnotation(Flag.class); + NamedArg namedArg = parameter.getAnnotation(NamedArg.class); + + boolean isFlag = flag != null; + String name; + if (namedArg != null && !(name = namedArg.value()).isEmpty()) { + } else if (isFlag && !(name = flag.value()).isEmpty()) { + //} else if (i < bn && (name = bytecode[i]) != null && !name.isEmpty()) { + } else { + name = parameter.getName(); + } + + if (isFlag) { + name = '-' + name; + } else { + int idx = 0; + while (name.startsWith("-", idx)) { + idx++; + } + name = name.substring(idx); + } + + out[i - start] = name; + } + + return out; + } + + public static void parseCommandGroup(ICommandAddress address, Class<?> clazz, Object instance) throws CommandParseException { + parseCommandGroup(address, ParameterTypes.getSelector(), clazz, instance); + } + + public static void parseCommandGroup(ICommandAddress address, IParameterTypeSelector selector, Class<?> clazz, Object instance) throws CommandParseException { + boolean requireStatic = instance == null; + if (!requireStatic && !clazz.isInstance(instance)) { + throw new CommandParseException(); + } + + List<Method> methods = new LinkedList<>(Arrays.asList(clazz.getDeclaredMethods())); + + Iterator<Method> it = methods.iterator(); + for (Method method; it.hasNext(); ) { + method = it.next(); + + if (requireStatic && !Modifier.isStatic(method.getModifiers())) { + it.remove(); + continue; + } + + if (method.isAnnotationPresent(CmdParamType.class)) { + it.remove(); + + if (method.getReturnType() != ParameterType.class || method.getParameterCount() != 0) { + throw new CommandParseException("Invalid CmdParamType method: must return ParameterType and take no arguments"); + } + + ParameterType<?, ?> type; + try { + Object inst = Modifier.isStatic(method.getModifiers()) ? null : instance; + type = (ParameterType<?, ?>) method.invoke(inst); + Objects.requireNonNull(type, "ParameterType returned is null"); + } catch (Exception ex) { + throw new CommandParseException("Error occurred whilst getting ParameterType from CmdParamType method '" + method.toGenericString() + "'", ex); + } + + if (selector == ParameterTypes.getSelector()) { + selector = new MapBasedParameterTypeSelector(true); + } + + selector.addType(method.getAnnotation(CmdParamType.class).infolessAlias(), type); + } + } + + GroupMatcherCache groupMatcherCache = new GroupMatcherCache(clazz, address); + for (Method method : methods) { + if (method.isAnnotationPresent(Cmd.class)) { + ICommandAddress parsed = parseCommandMethod(selector, method, instance); + groupMatcherCache.getGroupFor(method).addChild(parsed); + } + } + + } + + private static final class GroupMatcherCache { + private ModifiableCommandAddress groupRootAddress; + private GroupEntry[] matchEntries; + private Pattern[] patterns; + private ModifiableCommandAddress[] addresses; + + GroupMatcherCache(Class<?> clazz, ICommandAddress groupRootAddress) throws CommandParseException { + this.groupRootAddress = (ModifiableCommandAddress) groupRootAddress; + + GroupMatchedCommands groupMatchedCommands = clazz.getAnnotation(GroupMatchedCommands.class); + GroupEntry[] matchEntries = groupMatchedCommands == null ? new GroupEntry[0] : groupMatchedCommands.value(); + + Pattern[] patterns = new Pattern[matchEntries.length]; + for (int i = 0; i < matchEntries.length; i++) { + GroupEntry matchEntry = matchEntries[i]; + if (matchEntry.group().isEmpty() || matchEntry.regex().isEmpty()) { + throw new CommandParseException("Empty group or regex in GroupMatchedCommands entry"); + } + try { + patterns[i] = Pattern.compile(matchEntry.regex()); + } catch (PatternSyntaxException ex) { + throw new CommandParseException(ex); + } + } + + this.matchEntries = matchEntries; + this.patterns = patterns; + this.addresses = new ModifiableCommandAddress[this.matchEntries.length]; + } + + ModifiableCommandAddress getGroupFor(Method method) { + String name = method.getName(); + + GroupEntry[] matchEntries = this.matchEntries; + Pattern[] patterns = this.patterns; + ModifiableCommandAddress[] addresses = this.addresses; + + for (int i = 0; i < matchEntries.length; i++) { + GroupEntry matchEntry = matchEntries[i]; + if (patterns[i].matcher(name).matches()) { + if (addresses[i] == null) { + ChildCommandAddress placeholder = new ChildCommandAddress(); + placeholder.setupAsPlaceholder(matchEntry.group(), matchEntry.groupAliases()); + addresses[i] = placeholder; + groupRootAddress.addChild(placeholder); + generateCommands(placeholder, matchEntry.generatedCommands()); + setDescription(placeholder, matchEntry.description(), matchEntry.shortDescription()); + } + return addresses[i]; + } + } + + return groupRootAddress; + } + + } + + public static ICommandAddress parseCommandMethod(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException { + return new ReflectiveCommand(selector, method, instance).getAddress(); + } + + static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] callParameters) throws CommandParseException { + ParameterList list = command.getParameterList(); + + Class<?> senderParameterType = null; + int flags = 0; + int start = 0; + int end = callParameters.length; + + if (callParameters.length > start + && command.getInstance() instanceof ICommandInterceptor + && ICommandReceiver.class.isAssignableFrom(callParameters[start].getType())) { + flags |= RECEIVER_BIT; + ++start; + } + + if (callParameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = callParameters[start].getType())) { + flags |= SENDER_BIT; + ++start; + } + + if (callParameters.length > start && callParameters[start].getType() == ExecutionContext.class) { + flags |= CONTEXT_BIT; + ++start; + } + + if (callParameters.length > start && callParameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) { + flags |= CONTINUATION_BIT; + --end; + } + + String[] parameterNames = lookupParameterNames(method, callParameters, start); + for (int i = start, n = end; i < n; i++) { + Parameter<?, ?> parameter = parseParameter(selector, method, callParameters[i], parameterNames[i - start]); + list.addParameter(parameter); + } + + command.setParameterOrder(hasCallArg(flags, CONTINUATION_BIT) ? Arrays.copyOfRange(parameterNames, 0, parameterNames.length - 1) : parameterNames); + + RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class); + if (cmdPermissions != null) { + for (String permission : cmdPermissions.value()) { + command.addContextFilter(IContextFilter.permission(permission)); + } + + if (cmdPermissions.inherit()) { + command.addContextFilter(IContextFilter.INHERIT_PERMISSIONS); + } + } else { + command.addContextFilter(IContextFilter.INHERIT_PERMISSIONS); + } + + RequireParameters reqPar = method.getAnnotation(RequireParameters.class); + if (reqPar != null) { + list.setRequiredCount(reqPar.value() < 0 ? Integer.MAX_VALUE : reqPar.value()); + } else { + list.setRequiredCount(list.getIndexedParameters().size()); + } + + /* + PreprocessArgs preprocessArgs = method.getAnnotation(PreprocessArgs.class); + if (preprocessArgs != null) { + IArgumentPreProcessor preProcessor = IArgumentPreProcessor.mergeOnTokens(preprocessArgs.tokens(), preprocessArgs.escapeChar()); + list.setArgumentPreProcessor(preProcessor); + }*/ + + Desc desc = method.getAnnotation(Desc.class); + if (desc != null) { + String[] array = desc.value(); + if (array.length == 0) { + command.setDescription(desc.shortVersion()); + } else { + command.setDescription(array); + } + } else { + command.setDescription(); + } + + boolean hasSenderParameter = hasCallArg(flags, SENDER_BIT); + if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) { + command.addContextFilter(IContextFilter.PLAYER_ONLY); + } else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) { + command.addContextFilter(IContextFilter.CONSOLE_ONLY); + } else if (method.isAnnotationPresent(RequirePlayer.class)) { + command.addContextFilter(IContextFilter.PLAYER_ONLY); + } else if (method.isAnnotationPresent(RequireConsole.class)) { + command.addContextFilter(IContextFilter.CONSOLE_ONLY); + } + + list.setRepeatFinalParameter(callParameters.length > start && callParameters[callParameters.length - 1].isVarArgs()); + list.setFinalParameterMayBeFlag(true); + + return flags; + } + + public static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command) throws CommandParseException { + return parseCommandAttributes(selector, method, command, method.getParameters()); + } + + public static Parameter<?, ?> parseParameter(IParameterTypeSelector selector, Method method, java.lang.reflect.Parameter parameter, String name) throws CommandParseException { + Class<?> type = parameter.getType(); + if (parameter.isVarArgs()) { + type = type.getComponentType(); + } + + Annotation[] annotations = parameter.getAnnotations(); + Flag flag = null; + Annotation typeAnnotation = null; + Desc desc = null; + + for (Annotation annotation : annotations) { + //noinspection StatementWithEmptyBody + if (annotation instanceof NamedArg) { + // do nothing + } else if (annotation instanceof Flag) { + if (flag != null) { + throw new CommandParseException("Multiple flags for the same parameter"); + } + flag = (Flag) annotation; + } else if (annotation instanceof Desc) { + if (desc != null) { + throw new CommandParseException("Multiple descriptions for the same parameter"); + } + desc = (Desc) annotation; + } else { + if (typeAnnotation != null) { + throw new CommandParseException("Multiple parameter type annotations for the same parameter"); + } + typeAnnotation = annotation; + } + } + + if (flag == null && name.startsWith("-")) { + throw new CommandParseException("Non-flag parameter's name starts with -"); + } else if (flag != null && !name.startsWith("-")) { + throw new CommandParseException("Flag parameter's name doesn't start with -"); + } + + ParameterType<Object, Object> parameterType = selector.selectAny(type, typeAnnotation == null ? null : typeAnnotation.getClass()); + if (parameterType == null) { + throw new CommandParseException("IParameter type not found for parameter " + name + " in method " + method.toString()); + } + + Object parameterInfo; + if (typeAnnotation == null) { + parameterInfo = null; + } else try { + parameterInfo = parameterType.getParameterConfig() == null ? null : parameterType.getParameterConfig().getParameterInfo(typeAnnotation); + } catch (Exception ex) { + throw new CommandParseException("Invalid parameter config", ex); + } + + String descString = desc == null ? null : CommandAnnotationUtils.getShortDescription(desc); + + try { + //noinspection unchecked + String flagPermission = flag == null || flag.permission().isEmpty() ? null : flag.permission(); + return new Parameter<>(name, descString, parameterType, parameterInfo, type.isPrimitive(), name.startsWith("-"), flagPermission); + } catch (Exception ex) { + throw new CommandParseException("Invalid parameter", ex); + } + } + + public static void generateCommands(ICommandAddress address, String[] input) { + for (String value : input) { + Consumer<ICommandAddress> consumer = PredefinedCommand.getPredefinedCommandGenerator(value); + if (consumer == null) { + System.out.println("[Command Warning] generated command '" + value + "' could not be found"); + } else { + consumer.accept(address); + } + } + } + + /* + Desired format + + @Cmd({"tp", "tpto"}) + @RequirePermissions("teleport.self") + public (static) String|void onCommand(Player sender, Player target, @Flag("force", permission = "teleport.self.force") boolean force) { + Validate.isTrue(force || !hasTpToggledOff(target), "Target has teleportation disabled. Use -force to ignore"); + sender.teleport(target); + //return + } + + parser needs to: + - see the @Cmd and create a CommandTree for it + - see that it must be a Player executing the command + - add an indexed IParameter for a Player type + - add a flag parameter named force, that consumes no arguments. + - see that setting the force flag requires a permission + */ + + private static void setDescription(ICommandAddress address, String[] array, String shortVersion) { + if (!address.hasCommand()) { + return; + } + + if (array.length == 0) { + address.getCommand().setDescription(shortVersion); + } else { + address.getCommand().setDescription(array); + } + + } + +} |