diff options
Diffstat (limited to 'dicore3/command/src')
6 files changed, 1389 insertions, 1219 deletions
diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java b/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java index 2b70eaf..4df8c49 100644 --- a/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java +++ b/dicore3/command/src/main/java/io/dico/dicore/command/RootCommandAddress.java @@ -1,275 +1,281 @@ -package io.dico.dicore.command;
-
-import io.dico.dicore.command.parameter.ArgumentBuffer;
-import io.dico.dicore.command.registration.BukkitCommand;
-import org.bukkit.Location;
-import org.bukkit.command.CommandSender;
-
-import java.util.*;
-
-public class RootCommandAddress extends ModifiableCommandAddress implements ICommandDispatcher {
- @Deprecated
- public static final RootCommandAddress INSTANCE = new RootCommandAddress();
-
- public RootCommandAddress() {
- }
-
- @Override
- public Command getCommand() {
- return null;
- }
-
- @Override
- public boolean isRoot() {
- return true;
- }
-
- @Override
- public List<String> getNames() {
- return Collections.emptyList();
- }
-
- @Override
- public ModifiableCommandAddress getParent() {
- return null;
- }
-
- @Override
- public String getMainKey() {
- return null;
- }
-
- @Override
- public String getAddress() {
- return "";
- }
-
- @Override
- public void registerToCommandMap(String fallbackPrefix, Map<String, org.bukkit.command.Command> map, EOverridePolicy overridePolicy) {
- Objects.requireNonNull(overridePolicy);
- //debugChildren(this);
- Map<String, ChildCommandAddress> children = this.children;
- Map<ChildCommandAddress, BukkitCommand> wrappers = new IdentityHashMap<>();
-
- for (ChildCommandAddress address : children.values()) {
- if (!wrappers.containsKey(address)) {
- wrappers.put(address, new BukkitCommand(address));
- }
- }
-
- for (Map.Entry<String, ChildCommandAddress> entry : children.entrySet()) {
- String key = entry.getKey();
- ChildCommandAddress address = entry.getValue();
- boolean override = overridePolicy == EOverridePolicy.OVERRIDE_ALL;
- if (!override && key.equals(address.getMainKey())) {
- override = overridePolicy == EOverridePolicy.MAIN_KEY_ONLY || overridePolicy == EOverridePolicy.MAIN_AND_FALLBACK;
- }
-
- registerMember(map, key, wrappers.get(address), override);
-
- if (fallbackPrefix != null) {
- key = fallbackPrefix + key;
- override = overridePolicy != EOverridePolicy.OVERRIDE_NONE && overridePolicy != EOverridePolicy.MAIN_KEY_ONLY;
- registerMember(map, key, wrappers.get(address), override);
- }
- }
-
- }
-
- private static void debugChildren(ModifiableCommandAddress address) {
- Collection<String> keys = address.getChildrenMainKeys();
- for (String key : keys) {
- ChildCommandAddress child = address.getChild(key);
- System.out.println(child.getAddress());
- debugChildren(child);
- }
- }
-
- private static void registerMember(Map<String, org.bukkit.command.Command> map,
- String key, org.bukkit.command.Command value, boolean override) {
- if (override) {
- map.put(key, value);
- } else {
- map.putIfAbsent(key, value);
- }
- }
-
- @Override
- public void unregisterFromCommandMap(Map<String, org.bukkit.command.Command> map) {
- Set<ICommandAddress> children = new HashSet<>(this.children.values());
- Iterator<Map.Entry<String, org.bukkit.command.Command>> iterator = map.entrySet().iterator();
- while (iterator.hasNext()) {
- Map.Entry<String, org.bukkit.command.Command> entry = iterator.next();
- org.bukkit.command.Command cmd = entry.getValue();
- if (cmd instanceof BukkitCommand && children.contains(((BukkitCommand) cmd).getOrigin())) {
- iterator.remove();
- }
- }
- }
-
- @Override
- public ModifiableCommandAddress getDeepChild(ArgumentBuffer buffer) {
- ModifiableCommandAddress cur = this;
- ChildCommandAddress child;
- while (buffer.hasNext()) {
- child = cur.getChild(buffer.next());
- if (child == null) {
- buffer.rewind();
- return cur;
- }
-
- cur = child;
- }
- return cur;
- }
-
- @Override
- public ModifiableCommandAddress getCommandTarget(CommandSender sender, ArgumentBuffer buffer) {
- ModifiableCommandAddress cur = this;
- ChildCommandAddress child;
- while (buffer.hasNext()) {
- child = cur.getChild(buffer.next());
- if (child == null
- || (child.hasCommand() && !child.getCommand().isVisibleTo(sender))
- || (cur.hasCommand() && cur.getCommand().takePrecedenceOverSubcommand(buffer.peekPrevious(), buffer.getUnaffectingCopy()))) {
- buffer.rewind();
- break;
- }
-
- cur = child;
- }
-
- return cur;
- }
-
- @Override
- public ModifiableCommandAddress getCommandTarget(ExecutionContext context, ArgumentBuffer buffer) throws CommandException {
- CommandSender sender = context.getSender();
- ModifiableCommandAddress cur = this;
- ChildCommandAddress child;
- while (buffer.hasNext()) {
- int cursor = buffer.getCursor();
-
- child = cur.getChild(context, buffer);
-
- if (child == null
- || (context.isTabComplete() && !buffer.hasNext())
- || (child.hasCommand() && !child.getCommand().isVisibleTo(sender))
- || (cur.hasCommand() && cur.getCommand().takePrecedenceOverSubcommand(buffer.peekPrevious(), buffer.getUnaffectingCopy()))) {
- buffer.setCursor(cursor);
- break;
- }
-
- cur = child;
-
- context.setAddress(child);
- if (child.hasCommand() && child.isCommandTrailing()) {
- child.getCommand().initializeAndFilterContext(context);
- child.getCommand().execute(context.getSender(), context);
- }
- }
-
- return cur;
- }
-
- @Override
- public boolean dispatchCommand(CommandSender sender, String[] command) {
- return dispatchCommand(sender, new ArgumentBuffer(command));
- }
-
- @Override
- public boolean dispatchCommand(CommandSender sender, String usedLabel, String[] args) {
- return dispatchCommand(sender, new ArgumentBuffer(usedLabel, args));
- }
-
- @Override
- public boolean dispatchCommand(CommandSender sender, ArgumentBuffer buffer) {
- ExecutionContext context = new ExecutionContext(sender, buffer, false);
-
- ModifiableCommandAddress targetAddress = null;
-
- try {
- targetAddress = getCommandTarget(context, buffer);
- Command target = targetAddress.getCommand();
-
- if (target == null) {
- if (targetAddress.hasHelpCommand()) {
- target = targetAddress.getHelpCommand().getCommand();
- } else {
- return false;
- }
- }
-
- context.setCommand(target);
-
- if (!targetAddress.isCommandTrailing()) {
- target.initializeAndFilterContext(context);
- String message = target.execute(sender, context);
- if (message != null && !message.isEmpty()) {
- context.sendMessage(EMessageType.RESULT, message);
- }
- }
-
- } catch (Throwable t) {
- if (targetAddress == null) {
- targetAddress = this;
- }
- targetAddress.getChatHandler().handleException(sender, context, t);
- }
-
- return true;
- }
-
- @Override
- public List<String> getTabCompletions(CommandSender sender, Location location, String[] args) {
- return getTabCompletions(sender, location, new ArgumentBuffer(args));
- }
-
- @Override
- public List<String> getTabCompletions(CommandSender sender, String usedLabel, Location location, String[] args) {
- return getTabCompletions(sender, location, new ArgumentBuffer(usedLabel, args));
- }
-
- @Override
- public List<String> getTabCompletions(CommandSender sender, Location location, ArgumentBuffer buffer) {
- ExecutionContext context = new ExecutionContext(sender, buffer, true);
-
- try {
- ICommandAddress target = getCommandTarget(context, buffer);
-
- List<String> out;
- if (target.hasCommand()) {
- context.setCommand(target.getCommand());
- target.getCommand().initializeAndFilterContext(context);
- out = target.getCommand().tabComplete(sender, context, location);
- } else {
- out = Collections.emptyList();
- }
-
- int cursor = buffer.getCursor();
- String input;
- if (cursor >= buffer.size()) {
- input = "";
- } else {
- input = buffer.get(cursor).toLowerCase();
- }
-
- boolean wrapped = false;
- for (String child : target.getChildrenMainKeys()) {
- if (child.toLowerCase().startsWith(input)) {
- if (!wrapped) {
- out = new ArrayList<>(out);
- wrapped = true;
- }
- out.add(child);
- }
- }
-
- return out;
-
- } catch (CommandException ex) {
- return Collections.emptyList();
- }
-
- }
-}
+package io.dico.dicore.command; + +import io.dico.dicore.command.parameter.ArgumentBuffer; +import io.dico.dicore.command.registration.BukkitCommand; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; + +import java.util.*; + +public class RootCommandAddress extends ModifiableCommandAddress implements ICommandDispatcher { + @Deprecated + public static final RootCommandAddress INSTANCE = new RootCommandAddress(); + + public RootCommandAddress() { + } + + @Override + public Command getCommand() { + return null; + } + + @Override + public boolean isRoot() { + return true; + } + + @Override + public List<String> getNames() { + return Collections.emptyList(); + } + + @Override + public ModifiableCommandAddress getParent() { + return null; + } + + @Override + public String getMainKey() { + return null; + } + + @Override + public String getAddress() { + return ""; + } + + @Override + public void registerToCommandMap(String fallbackPrefix, Map<String, org.bukkit.command.Command> map, EOverridePolicy overridePolicy) { + Objects.requireNonNull(overridePolicy); + //debugChildren(this); + Map<String, ChildCommandAddress> children = this.children; + Map<ChildCommandAddress, BukkitCommand> wrappers = new IdentityHashMap<>(); + + for (ChildCommandAddress address : children.values()) { + if (!wrappers.containsKey(address)) { + wrappers.put(address, new BukkitCommand(address)); + } + } + + for (Map.Entry<String, ChildCommandAddress> entry : children.entrySet()) { + String key = entry.getKey(); + ChildCommandAddress address = entry.getValue(); + boolean override = overridePolicy == EOverridePolicy.OVERRIDE_ALL; + if (!override && key.equals(address.getMainKey())) { + override = overridePolicy == EOverridePolicy.MAIN_KEY_ONLY || overridePolicy == EOverridePolicy.MAIN_AND_FALLBACK; + } + + registerMember(map, key, wrappers.get(address), override); + + if (fallbackPrefix != null) { + key = fallbackPrefix + key; + override = overridePolicy != EOverridePolicy.OVERRIDE_NONE && overridePolicy != EOverridePolicy.MAIN_KEY_ONLY; + registerMember(map, key, wrappers.get(address), override); + } + } + + } + + private static void debugChildren(ModifiableCommandAddress address) { + Collection<String> keys = address.getChildrenMainKeys(); + for (String key : keys) { + ChildCommandAddress child = address.getChild(key); + System.out.println(child.getAddress()); + debugChildren(child); + } + } + + private static void registerMember(Map<String, org.bukkit.command.Command> map, + String key, org.bukkit.command.Command value, boolean override) { + if (override) { + map.put(key, value); + } else { + map.putIfAbsent(key, value); + } + } + + @Override + public void unregisterFromCommandMap(Map<String, org.bukkit.command.Command> map) { + Set<ICommandAddress> children = new HashSet<>(this.children.values()); + Iterator<Map.Entry<String, org.bukkit.command.Command>> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry<String, org.bukkit.command.Command> entry = iterator.next(); + org.bukkit.command.Command cmd = entry.getValue(); + if (cmd instanceof BukkitCommand && children.contains(((BukkitCommand) cmd).getOrigin())) { + iterator.remove(); + } + } + } + + @Override + public ModifiableCommandAddress getDeepChild(ArgumentBuffer buffer) { + ModifiableCommandAddress cur = this; + ChildCommandAddress child; + while (buffer.hasNext()) { + child = cur.getChild(buffer.next()); + if (child == null) { + buffer.rewind(); + return cur; + } + + cur = child; + } + return cur; + } + + @Override + public ModifiableCommandAddress getCommandTarget(CommandSender sender, ArgumentBuffer buffer) { + ModifiableCommandAddress cur = this; + ChildCommandAddress child; + while (buffer.hasNext()) { + child = cur.getChild(buffer.next()); + if (child == null + || (child.hasCommand() && !child.getCommand().isVisibleTo(sender)) + || (cur.hasCommand() && cur.getCommand().takePrecedenceOverSubcommand(buffer.peekPrevious(), buffer.getUnaffectingCopy()))) { + buffer.rewind(); + break; + } + + cur = child; + } + + return cur; + } + + @Override + public ModifiableCommandAddress getCommandTarget(ExecutionContext context, ArgumentBuffer buffer) throws CommandException { + CommandSender sender = context.getSender(); + ModifiableCommandAddress cur = this; + ChildCommandAddress child; + while (buffer.hasNext()) { + int cursor = buffer.getCursor(); + + child = cur.getChild(context, buffer); + + if (child == null + || (context.isTabComplete() && !buffer.hasNext()) + || (child.hasCommand() && !child.getCommand().isVisibleTo(sender)) + || (cur.hasCommand() && cur.getCommand().takePrecedenceOverSubcommand(buffer.peekPrevious(), buffer.getUnaffectingCopy()))) { + buffer.setCursor(cursor); + break; + } + + cur = child; + + context.setAddress(child); + if (child.hasCommand() && child.isCommandTrailing()) { + child.getCommand().initializeAndFilterContext(context); + child.getCommand().execute(context.getSender(), context); + } + } + + return cur; + } + + @Override + public boolean dispatchCommand(CommandSender sender, String[] command) { + return dispatchCommand(sender, new ArgumentBuffer(command)); + } + + @Override + public boolean dispatchCommand(CommandSender sender, String usedLabel, String[] args) { + return dispatchCommand(sender, new ArgumentBuffer(usedLabel, args)); + } + + @Override + public boolean dispatchCommand(CommandSender sender, ArgumentBuffer buffer) { + ExecutionContext context = new ExecutionContext(sender, buffer, false); + + ModifiableCommandAddress targetAddress = null; + + try { + targetAddress = getCommandTarget(context, buffer); + Command target = targetAddress.getCommand(); + + if (target == null) { + if (targetAddress.hasHelpCommand()) { + target = targetAddress.getHelpCommand().getCommand(); + } else { + return false; + } + } + + context.setCommand(target); + + if (!targetAddress.isCommandTrailing()) { + target.initializeAndFilterContext(context); + String message = target.execute(sender, context); + if (message != null && !message.isEmpty()) { + context.sendMessage(EMessageType.RESULT, message); + } + } + + } catch (Throwable t) { + if (targetAddress == null) { + targetAddress = this; + } + targetAddress.getChatHandler().handleException(sender, context, t); + } + + return true; + } + + @Override + public List<String> getTabCompletions(CommandSender sender, Location location, String[] args) { + return getTabCompletions(sender, location, new ArgumentBuffer(args)); + } + + @Override + public List<String> getTabCompletions(CommandSender sender, String usedLabel, Location location, String[] args) { + return getTabCompletions(sender, location, new ArgumentBuffer(usedLabel, args)); + } + + @Override + public List<String> getTabCompletions(CommandSender sender, Location location, ArgumentBuffer buffer) { + ExecutionContext context = new ExecutionContext(sender, buffer, true); + long start = System.currentTimeMillis(); + + try { + ICommandAddress target = getCommandTarget(context, buffer); + + List<String> out; + if (target.hasCommand()) { + context.setCommand(target.getCommand()); + target.getCommand().initializeAndFilterContext(context); + out = target.getCommand().tabComplete(sender, context, location); + } else { + out = Collections.emptyList(); + } + + int cursor = buffer.getCursor(); + String input; + if (cursor >= buffer.size()) { + input = ""; + } else { + input = buffer.get(cursor).toLowerCase(); + } + + boolean wrapped = false; + for (String child : target.getChildrenMainKeys()) { + if (child.toLowerCase().startsWith(input)) { + if (!wrapped) { + out = new ArrayList<>(out); + wrapped = true; + } + out.add(child); + } + } + + return out; + + } catch (CommandException ex) { + return Collections.emptyList(); + } finally { + long duration = System.currentTimeMillis() - start; + if (duration > 2) { + System.out.println(String.format("Complete took %.3f seconds", duration / 1000.0)); + } + } + + } +} diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/parameter/ContextParser.java b/dicore3/command/src/main/java/io/dico/dicore/command/parameter/ContextParser.java index f486f52..7418c4a 100644 --- a/dicore3/command/src/main/java/io/dico/dicore/command/parameter/ContextParser.java +++ b/dicore3/command/src/main/java/io/dico/dicore/command/parameter/ContextParser.java @@ -1,276 +1,278 @@ -package io.dico.dicore.command.parameter;
-
-import io.dico.dicore.command.CommandException;
-import io.dico.dicore.command.ExecutionContext;
-
-import java.lang.reflect.Array;
-import java.util.*;
-
-public class ContextParser {
- private final ExecutionContext m_context;
- private final ArgumentBuffer m_buffer;
- private final ParameterList m_paramList;
- private final Parameter<?, ?> m_repeatedParam;
- private final List<Parameter<?, ?>> m_indexedParams;
- private final int m_maxIndex;
- private final int m_maxRequiredIndex;
-
- private Map<String, Object> m_valueMap;
- private Set<String> m_parsedKeys;
- private int m_completionCursor = -1;
- private Parameter<?, ?> m_completionTarget = null;
-
- public ContextParser(ExecutionContext context,
- ParameterList parameterList,
- Map<String, Object> valueMap,
- Set<String> keySet) {
- m_context = context;
- m_paramList = parameterList;
- m_valueMap = valueMap;
- m_parsedKeys = keySet;
-
- m_buffer = context.getBuffer();
- m_repeatedParam = m_paramList.getRepeatedParameter();
- m_indexedParams = m_paramList.getIndexedParameters();
- m_maxIndex = m_indexedParams.size() - 1;
- m_maxRequiredIndex = m_paramList.getRequiredCount() - 1;
- }
-
- public ExecutionContext getContext() {
- return m_context;
- }
-
- public Map<String, Object> getValueMap() {
- return m_valueMap;
- }
-
- public Set<String> getParsedKeys() {
- return m_parsedKeys;
- }
-
- public void parse() throws CommandException {
- parseAllParameters();
- }
-
- public int getCompletionCursor() {
- if (!m_done) {
- throw new IllegalStateException();
- }
- return m_completionCursor;
- }
-
- public Parameter<?, ?> getCompletionTarget() {
- if (!m_done) {
- throw new IllegalStateException();
- }
- return m_completionTarget;
- }
-
- // ################################
- // # PARSING METHODS #
- // ################################
-
- private boolean m_repeating = false;
- private boolean m_done = false;
- private int m_curParamIndex = -1;
- private Parameter<?, ?> m_curParam = null;
- private List<Object> m_curRepeatingList = null;
-
- private void parseAllParameters() throws CommandException {
- try {
- do {
- prepareStateToParseParam();
- if (m_done) break;
- parseCurParam();
- } while (!m_done);
-
- } finally {
- m_curParam = null;
- m_curRepeatingList = null;
- assignDefaultValuesToUncomputedParams();
- arrayifyRepeatedParamValue();
- }
- }
-
- private void prepareStateToParseParam() throws CommandException {
-
- boolean requireInput;
- if (identifyFlag()) {
- m_buffer.advance();
- prepareRepeatedParameterIfSet();
- requireInput = false;
-
- } else if (m_repeating) {
- m_curParam = m_repeatedParam;
- requireInput = false;
-
- } else if (m_curParamIndex < m_maxIndex) {
- m_curParamIndex++;
- m_curParam = m_indexedParams.get(m_curParamIndex);
- prepareRepeatedParameterIfSet();
- requireInput = m_curParamIndex <= m_maxRequiredIndex;
-
- } else if (m_buffer.hasNext()) {
- throw new CommandException("Too many arguments for /" + m_context.getAddress().getAddress());
-
- } else {
- m_done = true;
- return;
- }
-
- if (!m_buffer.hasNext()) {
- if (requireInput) {
- reportParameterRequired(m_curParam);
- }
-
- if (m_repeating) {
- m_done = true;
- }
- }
-
- }
-
- private boolean identifyFlag() {
- String potentialFlag = m_buffer.peekNext();
- Parameter<?, ?> target;
- if (potentialFlag != null
- && potentialFlag.startsWith("-")
- && (target = m_paramList.getParameterByName(potentialFlag)) != null
- && target.isFlag()
- && !m_valueMap.containsKey(potentialFlag)
-
-// Disabled because it's checked by {@link Parameter#parse(ExecutionContext, ArgumentBuffer)}
-// && (target.getFlagPermission() == null || m_context.getSender().hasPermission(target.getFlagPermission()))
- ) {
- m_curParam = target;
- return true;
- }
-
- return false;
- }
-
- private void prepareRepeatedParameterIfSet() throws CommandException {
- if (m_curParam != null && m_curParam == m_repeatedParam) {
-
- if (m_curParam.isFlag() && m_curParamIndex < m_maxRequiredIndex) {
- Parameter<?, ?> requiredParam = m_indexedParams.get(m_curParamIndex + 1);
- reportParameterRequired(requiredParam);
- }
-
- m_curRepeatingList = new ArrayList<>();
- assignValue(m_curRepeatingList);
- m_repeating = true;
- }
- }
-
- private void reportParameterRequired(Parameter<?, ?> param) throws CommandException {
- throw new CommandException("The argument '" + param.getName() + "' is required");
- }
-
- private void parseCurParam() throws CommandException {
- if (!m_buffer.hasNext() && !m_curParam.isFlag()) {
- assignDefaultValue();
- return;
- }
-
- int cursorStart = m_buffer.getCursor();
-
- if (m_context.isTabComplete() && "".equals(m_buffer.peekNext())) {
- assignAsCompletionTarget(cursorStart);
- return;
- }
-
- Object parseResult;
- try {
- parseResult = m_curParam.parse(m_context, m_buffer);
- } catch (CommandException e) {
- assignAsCompletionTarget(cursorStart);
- throw e;
- }
-
- assignValue(parseResult);
- m_parsedKeys.add(m_curParam.getName());
- }
-
- private void assignDefaultValue() throws CommandException {
- assignValue(m_curParam.getDefaultValue(m_context, m_buffer));
- }
-
- private void assignAsCompletionTarget(int cursor) {
- m_completionCursor = cursor;
- m_completionTarget = m_curParam;
- m_done = true;
- }
-
- private void assignValue(Object value) {
- if (m_repeating) {
- m_curRepeatingList.add(value);
- } else {
- m_valueMap.put(m_curParam.getName(), value);
- }
- }
-
- private void assignDefaultValuesToUncomputedParams() throws CommandException {
- // add default values for unset parameters
- for (Map.Entry<String, Parameter<?, ?>> entry : m_paramList.getParametersByName().entrySet()) {
- String name = entry.getKey();
- if (!m_valueMap.containsKey(name)) {
- if (m_repeatedParam == entry.getValue()) {
- // below value will be turned into an array later
- m_valueMap.put(name, Collections.emptyList());
- } else {
- m_valueMap.put(name, entry.getValue().getDefaultValue(m_context, m_buffer));
- }
- }
- }
- }
-
- private void arrayifyRepeatedParamValue() {
- if (m_repeatedParam != null) {
- m_valueMap.computeIfPresent(m_repeatedParam.getName(), (k, v) -> {
- List list = (List) v;
- Class<?> returnType = m_repeatedParam.getType().getReturnType();
- Object array = Array.newInstance(returnType, list.size());
- ArraySetter setter = ArraySetter.getSetter(returnType);
- for (int i = 0, n = list.size(); i < n; i++) {
- setter.set(array, i, list.get(i));
- }
-
- return array;
- });
- }
- }
-
- private interface ArraySetter {
- void set(Object array, int index, Object value);
-
- static ArraySetter getSetter(Class<?> clazz) {
- if (!clazz.isPrimitive()) {
- return (array, index, value) -> ((Object[]) array)[index] = value;
- }
-
- switch (clazz.getSimpleName()) {
- case "boolean":
- return (array, index, value) -> ((boolean[]) array)[index] = (boolean) value;
- case "int":
- return (array, index, value) -> ((int[]) array)[index] = (int) value;
- case "double":
- return (array, index, value) -> ((double[]) array)[index] = (double) value;
- case "long":
- return (array, index, value) -> ((long[]) array)[index] = (long) value;
- case "short":
- return (array, index, value) -> ((short[]) array)[index] = (short) value;
- case "byte":
- return (array, index, value) -> ((byte[]) array)[index] = (byte) value;
- case "float":
- return (array, index, value) -> ((float[]) array)[index] = (float) value;
- case "char":
- return (array, index, value) -> ((char[]) array)[index] = (char) value;
- case "void":
- default:
- throw new InternalError("This should not happen");
- }
- }
- }
-
-}
+package io.dico.dicore.command.parameter; + +import io.dico.dicore.command.CommandException; +import io.dico.dicore.command.ExecutionContext; + +import java.lang.reflect.Array; +import java.util.*; + +public class ContextParser { + private final ExecutionContext m_context; + private final ArgumentBuffer m_buffer; + private final ParameterList m_paramList; + private final Parameter<?, ?> m_repeatedParam; + private final List<Parameter<?, ?>> m_indexedParams; + private final int m_maxIndex; + private final int m_maxRequiredIndex; + + private Map<String, Object> m_valueMap; + private Set<String> m_parsedKeys; + private int m_completionCursor = -1; + private Parameter<?, ?> m_completionTarget = null; + + public ContextParser(ExecutionContext context, + ParameterList parameterList, + Map<String, Object> valueMap, + Set<String> keySet) { + m_context = context; + m_paramList = parameterList; + m_valueMap = valueMap; + m_parsedKeys = keySet; + + m_buffer = context.getBuffer(); + m_repeatedParam = m_paramList.getRepeatedParameter(); + m_indexedParams = m_paramList.getIndexedParameters(); + m_maxIndex = m_indexedParams.size() - 1; + m_maxRequiredIndex = m_paramList.getRequiredCount() - 1; + } + + public ExecutionContext getContext() { + return m_context; + } + + public Map<String, Object> getValueMap() { + return m_valueMap; + } + + public Set<String> getParsedKeys() { + return m_parsedKeys; + } + + public void parse() throws CommandException { + parseAllParameters(); + } + + public int getCompletionCursor() { + if (!m_done) { + throw new IllegalStateException(); + } + return m_completionCursor; + } + + public Parameter<?, ?> getCompletionTarget() { + if (!m_done) { + throw new IllegalStateException(); + } + return m_completionTarget; + } + + // ################################ + // # PARSING METHODS # + // ################################ + + private boolean m_repeating = false; + private boolean m_done = false; + private int m_curParamIndex = -1; + private Parameter<?, ?> m_curParam = null; + private List<Object> m_curRepeatingList = null; + + private void parseAllParameters() throws CommandException { + try { + do { + prepareStateToParseParam(); + if (m_done) break; + parseCurParam(); + } while (!m_done); + + } finally { + m_curParam = null; + m_curRepeatingList = null; + assignDefaultValuesToUncomputedParams(); + arrayifyRepeatedParamValue(); + + m_done = true; + } + } + + private void prepareStateToParseParam() throws CommandException { + + boolean requireInput; + if (identifyFlag()) { + m_buffer.advance(); + prepareRepeatedParameterIfSet(); + requireInput = false; + + } else if (m_repeating) { + m_curParam = m_repeatedParam; + requireInput = false; + + } else if (m_curParamIndex < m_maxIndex) { + m_curParamIndex++; + m_curParam = m_indexedParams.get(m_curParamIndex); + prepareRepeatedParameterIfSet(); + requireInput = m_curParamIndex <= m_maxRequiredIndex; + + } else if (m_buffer.hasNext()) { + throw new CommandException("Too many arguments for /" + m_context.getAddress().getAddress()); + + } else { + m_done = true; + return; + } + + if (!m_buffer.hasNext()) { + if (requireInput) { + reportParameterRequired(m_curParam); + } + + if (m_repeating) { + m_done = true; + } + } + + } + + private boolean identifyFlag() { + String potentialFlag = m_buffer.peekNext(); + Parameter<?, ?> target; + if (potentialFlag != null + && potentialFlag.startsWith("-") + && (target = m_paramList.getParameterByName(potentialFlag)) != null + && target.isFlag() + && !m_valueMap.containsKey(potentialFlag) + +// Disabled because it's checked by {@link Parameter#parse(ExecutionContext, ArgumentBuffer)} +// && (target.getFlagPermission() == null || m_context.getSender().hasPermission(target.getFlagPermission())) + ) { + m_curParam = target; + return true; + } + + return false; + } + + private void prepareRepeatedParameterIfSet() throws CommandException { + if (m_curParam != null && m_curParam == m_repeatedParam) { + + if (m_curParam.isFlag() && m_curParamIndex < m_maxRequiredIndex) { + Parameter<?, ?> requiredParam = m_indexedParams.get(m_curParamIndex + 1); + reportParameterRequired(requiredParam); + } + + m_curRepeatingList = new ArrayList<>(); + assignValue(m_curRepeatingList); + m_repeating = true; + } + } + + private void reportParameterRequired(Parameter<?, ?> param) throws CommandException { + throw new CommandException("The argument '" + param.getName() + "' is required"); + } + + private void parseCurParam() throws CommandException { + if (!m_buffer.hasNext() && !m_curParam.isFlag()) { + assignDefaultValue(); + return; + } + + int cursorStart = m_buffer.getCursor(); + + if (m_context.isTabComplete() && "".equals(m_buffer.peekNext())) { + assignAsCompletionTarget(cursorStart); + return; + } + + Object parseResult; + try { + parseResult = m_curParam.parse(m_context, m_buffer); + } catch (CommandException e) { + assignAsCompletionTarget(cursorStart); + throw e; + } + + assignValue(parseResult); + m_parsedKeys.add(m_curParam.getName()); + } + + private void assignDefaultValue() throws CommandException { + assignValue(m_curParam.getDefaultValue(m_context, m_buffer)); + } + + private void assignAsCompletionTarget(int cursor) { + m_completionCursor = cursor; + m_completionTarget = m_curParam; + m_done = true; + } + + private void assignValue(Object value) { + if (m_repeating) { + m_curRepeatingList.add(value); + } else { + m_valueMap.put(m_curParam.getName(), value); + } + } + + private void assignDefaultValuesToUncomputedParams() throws CommandException { + // add default values for unset parameters + for (Map.Entry<String, Parameter<?, ?>> entry : m_paramList.getParametersByName().entrySet()) { + String name = entry.getKey(); + if (!m_valueMap.containsKey(name)) { + if (m_repeatedParam == entry.getValue()) { + // below value will be turned into an array later + m_valueMap.put(name, Collections.emptyList()); + } else { + m_valueMap.put(name, entry.getValue().getDefaultValue(m_context, m_buffer)); + } + } + } + } + + private void arrayifyRepeatedParamValue() { + if (m_repeatedParam != null) { + m_valueMap.computeIfPresent(m_repeatedParam.getName(), (k, v) -> { + List list = (List) v; + Class<?> returnType = m_repeatedParam.getType().getReturnType(); + Object array = Array.newInstance(returnType, list.size()); + ArraySetter setter = ArraySetter.getSetter(returnType); + for (int i = 0, n = list.size(); i < n; i++) { + setter.set(array, i, list.get(i)); + } + + return array; + }); + } + } + + private interface ArraySetter { + void set(Object array, int index, Object value); + + static ArraySetter getSetter(Class<?> clazz) { + if (!clazz.isPrimitive()) { + return (array, index, value) -> ((Object[]) array)[index] = value; + } + + switch (clazz.getSimpleName()) { + case "boolean": + return (array, index, value) -> ((boolean[]) array)[index] = (boolean) value; + case "int": + return (array, index, value) -> ((int[]) array)[index] = (int) value; + case "double": + return (array, index, value) -> ((double[]) array)[index] = (double) value; + case "long": + return (array, index, value) -> ((long[]) array)[index] = (long) value; + case "short": + return (array, index, value) -> ((short[]) array)[index] = (short) value; + case "byte": + return (array, index, value) -> ((byte[]) array)[index] = (byte) value; + case "float": + return (array, index, value) -> ((float[]) array)[index] = (float) value; + case "char": + return (array, index, value) -> ((char[]) array)[index] = (char) value; + case "void": + default: + throw new InternalError("This should not happen"); + } + } + } + +} diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCallFlags.java b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCallFlags.java new file mode 100644 index 0000000..fa198c2 --- /dev/null +++ b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCallFlags.java @@ -0,0 +1,186 @@ +package io.dico.dicore.command.registration.reflect; + +import io.dico.dicore.command.CommandException; +import io.dico.dicore.command.ExecutionContext; +import io.dico.dicore.exceptions.checkedfunctions.CheckedSupplier; + +/** + * Call flags store which extra parameters the target function expects on top of command parameters. + * All 4 possible extra parameters are listed below. + * <p> + * Extra parameters are ordered by the bit that represents them in the call flags. + * They can either be leading or trailing the command's parameters. + */ +public class ReflectiveCallFlags { + + /** + * Receiver ({@code this} in some kotlin functions - always first parameter) + * + * @see ICommandInterceptor#getReceiver(io.dico.dicore.command.ExecutionContext, java.lang.reflect.Method, String) + */ + public static final int RECEIVER_BIT = 1 << 0; + + /** + * CommandSender + * + * @see org.bukkit.command.CommandSender + */ + public static final int SENDER_BIT = 1 << 1; + + /** + * ExecutionContext + * + * @see io.dico.dicore.command.ExecutionContext + */ + public static final int CONTEXT_BIT = 1 << 2; + + /** + * Continuation (trailing parameters of kotlin suspended functions) + * + * @see kotlin.coroutines.Continuation + */ + public static final int CONTINUATION_BIT = 1 << 3; + + /** + * Mask of extra parameters that trail the command's parameters, instead of leading. + */ + public static final int TRAILING_MASK = CONTINUATION_BIT; + + /** + * Check if the call arg is trailing the command's parameters. + * + * @param bit the bit used for the call flag + * @return true if the call arg is trailing the command's parameters + */ + public static boolean isTrailingCallArg(int bit) { + return (bit & TRAILING_MASK) != 0; + } + + /** + * Number of call arguments leading the command parameters. + * + * @param flags the call flags + * @return the number of call arguments leading the command parameters + */ + public static int getLeadingCallArgNum(int flags) { + return Integer.bitCount(flags & ~TRAILING_MASK); + } + + /** + * Number of call arguments trailing the command parameters. + * + * @param flags the call flags + * @return the number of call arguments trailing the command parameters + */ + public static int getTrailingCallArgNum(int flags) { + return Integer.bitCount(flags & TRAILING_MASK); + } + + /** + * Check if the flags contain the call arg. + * + * @param flags the call flags + * @param bit the bit used for the call flag + * @return true if the flags contain the call arg + */ + public static boolean hasCallArg(int flags, int bit) { + return (flags & bit) != 0; + } + + /** + * Get the index used for the call arg when calling the reflective function + * + * @param flags the call flags + * @param bit the bit used for the call flag + * @param cmdParameterNum the number of parameters of the command + * @return the index used for the call arg + */ + public static int getCallArgIndex(int flags, int bit, int cmdParameterNum) { + if ((bit & TRAILING_MASK) == 0) { + // Leading. + + int preceding = precedingMaskFrom(bit); + int mask = flags & precedingMaskFrom(bit) & ~TRAILING_MASK; + + // Count the number of present call args that are leading and precede the given bit + return Integer.bitCount(flags & precedingMaskFrom(bit) & ~TRAILING_MASK); + } else { + // Trailing. + + // Count the number of present call args that are leading + // plus the number of present call args that are trailing and precede the given bit + // plus the command's parameters + + return Integer.bitCount(flags & ~TRAILING_MASK) + + Integer.bitCount(flags & precedingMaskFrom(bit) & TRAILING_MASK) + + cmdParameterNum; + } + } + + /** + * Get the mask for all bits trailing the given fromBit + * + * <p> + * For example, if the bit is 00010000 + * This function returns 00001111 + * <p> + * + * @param fromBit number with the bit set there the ones should stop. + * @return the mask for all bits trailing the given fromBit + */ + private static int precedingMaskFrom(int fromBit) { + int trailingZeros = Integer.numberOfTrailingZeros(fromBit); + if (trailingZeros == 0) return 0; + return -1 >>> -trailingZeros; + } + + /** + * Get the object array used to call the function. + * + * @param callFlags the call flags + * @param context the context + * @param parameterOrder the order of parameters in the function + * @param receiverFunction the function that will create the receiver for this call, if applicable + * @return the call args + */ + public static Object[] getCallArgs( + int callFlags, + ExecutionContext context, + String[] parameterOrder, + CheckedSupplier<Object, CommandException> receiverFunction + ) throws CommandException { + int leadingParameterNum = getLeadingCallArgNum(callFlags); + int cmdParameterNum = parameterOrder.length; + int trailingParameterNum = getTrailingCallArgNum(callFlags); + + Object[] result = new Object[leadingParameterNum + cmdParameterNum + trailingParameterNum]; + + if (hasCallArg(callFlags, RECEIVER_BIT)) { + int index = getCallArgIndex(callFlags, RECEIVER_BIT, cmdParameterNum); + result[index] = receiverFunction.get(); + } + + if (hasCallArg(callFlags, SENDER_BIT)) { + int index = getCallArgIndex(callFlags, SENDER_BIT, cmdParameterNum); + result[index] = context.getSender(); + } + + if (hasCallArg(callFlags, CONTEXT_BIT)) { + int index = getCallArgIndex(callFlags, CONTEXT_BIT, cmdParameterNum); + result[index] = context; + } + + if (hasCallArg(callFlags, CONTINUATION_BIT)) { + int index = getCallArgIndex(callFlags, CONTINUATION_BIT, cmdParameterNum); + result[index] = null; // filled in later. + } + + for (int i = 0; i < parameterOrder.length; i++) { + String parameterName = parameterOrder[i]; + result[leadingParameterNum + i] = context.get(parameterName); + } + + return result; + } + +} diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCommand.java b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCommand.java index 34ea8de..f4ddc70 100644 --- a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCommand.java +++ b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCommand.java @@ -1,187 +1,169 @@ -package io.dico.dicore.command.registration.reflect;
-
-import io.dico.dicore.command.*;
-import io.dico.dicore.command.annotation.Cmd;
-import io.dico.dicore.command.annotation.GenerateCommands;
-import io.dico.dicore.command.parameter.type.IParameterTypeSelector;
-import org.bukkit.command.CommandSender;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-
-public final class ReflectiveCommand extends Command {
- private static final int continuationMask = 1 << 3;
- private final Cmd cmdAnnotation;
- private final Method method;
- private final Object instance;
- private String[] parameterOrder;
-
- // hasContinuation | hasContext | hasSender | hasReceiver
- private final int flags;
-
- ReflectiveCommand(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException {
- if (!method.isAnnotationPresent(Cmd.class)) {
- throw new CommandParseException("No @Cmd present for the method " + method.toGenericString());
- }
- cmdAnnotation = method.getAnnotation(Cmd.class);
-
- java.lang.reflect.Parameter[] parameters = method.getParameters();
-
- if (!method.isAccessible()) try {
- method.setAccessible(true);
- } catch (Exception ex) {
- throw new CommandParseException("Failed to make method accessible");
- }
-
- if (!Modifier.isStatic(method.getModifiers())) {
- if (instance == null) {
- try {
- instance = method.getDeclaringClass().newInstance();
- } catch (Exception ex) {
- throw new CommandParseException("No instance given for instance method, and failed to create new instance", ex);
- }
- } else if (!method.getDeclaringClass().isInstance(instance)) {
- throw new CommandParseException("Given instance is not an instance of the method's declaring class");
- }
- }
-
- this.method = method;
- this.instance = instance;
- this.flags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
- }
-
- public Method getMethod() {
- return method;
- }
-
- public Object getInstance() {
- return instance;
- }
-
- public String getCmdName() { return cmdAnnotation.value(); }
-
- void setParameterOrder(String[] parameterOrder) {
- this.parameterOrder = parameterOrder;
- }
-
- ICommandAddress getAddress() {
- ChildCommandAddress result = new ChildCommandAddress();
- result.setCommand(this);
-
- Cmd cmd = cmdAnnotation;
- result.getNames().add(cmd.value());
- for (String alias : cmd.aliases()) {
- result.getNames().add(alias);
- }
- result.finalizeNames();
-
- GenerateCommands generateCommands = method.getAnnotation(GenerateCommands.class);
- if (generateCommands != null) {
- ReflectiveRegistration.generateCommands(result, generateCommands.value());
- }
-
- return result;
- }
-
- @Override
- public String execute(CommandSender sender, ExecutionContext context) throws CommandException {
- String[] parameterOrder = this.parameterOrder;
- int extraArgumentCount = Integer.bitCount(flags);
- int parameterStartIndex = Integer.bitCount(flags & ~continuationMask);
-
- Object[] args = new Object[parameterOrder.length + extraArgumentCount];
-
- int i = 0;
-
- int mask = 1;
- if ((flags & mask) != 0) {
- // Has receiver
- try {
- args[i++] = ((ICommandInterceptor) instance).getReceiver(context, method, getCmdName());
- } catch (Exception ex) {
- handleException(ex);
- return null; // unreachable
- }
- }
-
- mask <<= 1;
- if ((flags & mask) != 0) {
- // Has sender
- args[i++] = sender;
- }
-
- mask <<= 1;
- if ((flags & mask) != 0) {
- // Has context
- args[i++] = context;
- }
-
- mask <<= 1;
- if ((flags & mask) != 0) {
- // Has continuation
-
- extraArgumentCount--;
- }
-
- for (int n = args.length; i < n; i++) {
- args[i] = context.get(parameterOrder[i - extraArgumentCount]);
- }
-
- if ((flags & mask) != 0) {
- // Since it has continuation, call as coroutine
- return callAsCoroutine(context, args);
- }
-
- return callSynchronously(args);
- }
-
- private boolean isSuspendFunction() {
- try {
- return KotlinReflectiveRegistrationKt.isSuspendFunction(method);
- } catch (Throwable ex) {
- return false;
- }
- }
-
- public String callSynchronously(Object[] args) throws CommandException {
- try {
- return getResult(method.invoke(instance, args), null);
- } catch (Exception ex) {
- return getResult(null, ex);
- }
- }
-
- public static String getResult(Object returned, Exception ex) throws CommandException {
- if (ex != null) {
- handleException(ex);
- return null; // unreachable
- }
-
- if (returned instanceof String) {
- return (String) returned;
- }
- return null;
- }
-
- public static void handleException(Exception ex) throws CommandException {
- if (ex instanceof InvocationTargetException) {
- if (ex.getCause() instanceof CommandException) {
- throw (CommandException) ex.getCause();
- }
-
- ex.printStackTrace();
- throw new CommandException("An internal error occurred while executing this command.", ex);
- }
- if (ex instanceof CommandException) {
- throw (CommandException) ex;
- }
- ex.printStackTrace();
- throw new CommandException("An internal error occurred while executing this command.", ex);
- }
-
- private String callAsCoroutine(ExecutionContext context, Object[] args) {
- return KotlinReflectiveRegistrationKt.callAsCoroutine(this, (ICommandInterceptor) instance, context, args);
- }
-
-}
+package io.dico.dicore.command.registration.reflect; + +import io.dico.dicore.command.*; +import io.dico.dicore.command.annotation.Cmd; +import io.dico.dicore.command.annotation.GenerateCommands; +import io.dico.dicore.command.parameter.type.IParameterTypeSelector; +import io.dico.dicore.exceptions.checkedfunctions.CheckedSupplier; +import kotlin.coroutines.CoroutineContext; +import org.bukkit.command.CommandSender; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +public final class ReflectiveCommand extends Command { + private final Cmd cmdAnnotation; + private final Method method; + private final Object instance; + private String[] parameterOrder; + private final int callFlags; + + ReflectiveCommand(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException { + if (!method.isAnnotationPresent(Cmd.class)) { + throw new CommandParseException("No @Cmd present for the method " + method.toGenericString()); + } + cmdAnnotation = method.getAnnotation(Cmd.class); + + java.lang.reflect.Parameter[] parameters = method.getParameters(); + + if (!method.isAccessible()) try { + method.setAccessible(true); + } catch (Exception ex) { + throw new CommandParseException("Failed to make method accessible"); + } + + if (!Modifier.isStatic(method.getModifiers())) { + if (instance == null) { + try { + instance = method.getDeclaringClass().newInstance(); + } catch (Exception ex) { + throw new CommandParseException("No instance given for instance method, and failed to create new instance", ex); + } + } else if (!method.getDeclaringClass().isInstance(instance)) { + throw new CommandParseException("Given instance is not an instance of the method's declaring class"); + } + } + + this.method = method; + this.instance = instance; + this.callFlags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters); + } + + public Method getMethod() { + return method; + } + + public Object getInstance() { + return instance; + } + + public String getCmdName() { + return cmdAnnotation.value(); + } + + public int getCallFlags() { + return callFlags; + } + + void setParameterOrder(String[] parameterOrder) { + this.parameterOrder = parameterOrder; + } + + public int getParameterNum() { + return parameterOrder.length; + } + + ICommandAddress getAddress() { + ChildCommandAddress result = new ChildCommandAddress(); + result.setCommand(this); + + Cmd cmd = cmdAnnotation; + result.getNames().add(cmd.value()); + for (String alias : cmd.aliases()) { + result.getNames().add(alias); + } + result.finalizeNames(); + + GenerateCommands generateCommands = method.getAnnotation(GenerateCommands.class); + if (generateCommands != null) { + ReflectiveRegistration.generateCommands(result, generateCommands.value()); + } + + return result; + } + + @Override + public String execute(CommandSender sender, ExecutionContext context) throws CommandException { + + CheckedSupplier<Object, CommandException> receiverFunction = () -> { + try { + return ((ICommandInterceptor) instance).getReceiver(context, method, getCmdName()); + } catch (Exception ex) { + handleException(ex); + return null; // unreachable + } + }; + + Object[] callArgs = ReflectiveCallFlags.getCallArgs(callFlags, context, parameterOrder, receiverFunction); + + if (ReflectiveCallFlags.hasCallArg(callFlags, ReflectiveCallFlags.CONTINUATION_BIT)) { + // If it has a continuation, call as coroutine + return callAsCoroutine(context, callArgs); + } + + return callSynchronously(callArgs); + } + + private boolean isSuspendFunction() { + try { + return KotlinReflectiveRegistrationKt.isSuspendFunction(method); + } catch (Throwable ex) { + return false; + } + } + + public String callSynchronously(Object[] args) throws CommandException { + try { + return getResult(method.invoke(instance, args), null); + } catch (Exception ex) { + return getResult(null, ex); + } + } + + public static String getResult(Object returned, Exception ex) throws CommandException { + if (ex != null) { + handleException(ex); + return null; // unreachable + } + + if (returned instanceof String) { + return (String) returned; + } + return null; + } + + public static void handleException(Exception ex) throws CommandException { + if (ex instanceof InvocationTargetException) { + if (ex.getCause() instanceof CommandException) { + throw (CommandException) ex.getCause(); + } + + ex.printStackTrace(); + throw new CommandException("An internal error occurred while executing this command.", ex); + } + if (ex instanceof CommandException) { + throw (CommandException) ex; + } + ex.printStackTrace(); + throw new CommandException("An internal error occurred while executing this command.", ex); + } + + private String callAsCoroutine(ExecutionContext executionContext, Object[] args) throws CommandException { + ICommandInterceptor factory = (ICommandInterceptor) instance; + CoroutineContext coroutineContext = (CoroutineContext) factory.getCoroutineContext(executionContext, method, getCmdName()); + int continuationIndex = ReflectiveCallFlags.getCallArgIndex(callFlags, ReflectiveCallFlags.CONTINUATION_BIT, parameterOrder.length); + return KotlinReflectiveRegistrationKt.callCommandAsCoroutine(executionContext, coroutineContext, continuationIndex, method, instance, args); + } + +} 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); + } + + } + +} diff --git a/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt b/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt index 1a6692b..273cadc 100644 --- a/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt +++ b/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt @@ -1,66 +1,69 @@ -package io.dico.dicore.command.registration.reflect
-
-import io.dico.dicore.command.*
-import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
-import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.async
-import java.lang.reflect.Method
-import java.util.concurrent.CancellationException
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.intrinsics.intercepted
-import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
-import kotlin.reflect.jvm.kotlinFunction
-
-fun isSuspendFunction(method: Method): Boolean {
- val func = method.kotlinFunction ?: return false
- return func.isSuspend
-}
-
-fun callAsCoroutine(
- command: ReflectiveCommand,
- factory: ICommandInterceptor,
- context: ExecutionContext,
- args: Array<Any?>
-): String? {
- val coroutineContext = factory.getCoroutineContext(context, command.method, command.cmdName) as CoroutineContext
-
- // UNDISPATCHED causes the handler to run until the first suspension point on the current thread,
- // meaning command handlers that don't have suspension points will run completely synchronously.
- // Tasks that take time to compute should suspend the coroutine and resume on another thread.
- val job = GlobalScope.async(context = coroutineContext, start = UNDISPATCHED) {
- suspendCoroutineUninterceptedOrReturn<Any?> { cont ->
- command.method.invoke(command.instance, *args, cont.intercepted())
- }
- }
-
- if (job.isCompleted) {
- return job.getResult()
- }
-
- job.invokeOnCompletion {
- val chatHandler = context.address.chatHandler
- try {
- val result = job.getResult()
- chatHandler.sendMessage(context.sender, EMessageType.RESULT, result)
- } catch (ex: Throwable) {
- chatHandler.handleException(context.sender, context, ex)
- }
- }
-
- return null
-}
-
-@Throws(CommandException::class)
-private fun Deferred<Any?>.getResult(): String? {
- getCompletionExceptionOrNull()?.let { ex ->
- if (ex is CancellationException) {
- System.err.println("An asynchronously dispatched command was cancelled unexpectedly")
- ex.printStackTrace()
- throw CommandException("The command was cancelled unexpectedly (see console)")
- }
- if (ex is Exception) return ReflectiveCommand.getResult(null, ex)
- throw ex
- }
- return ReflectiveCommand.getResult(getCompleted(), null)
-}
+package io.dico.dicore.command.registration.reflect + +import io.dico.dicore.command.* +import kotlinx.coroutines.CoroutineStart.UNDISPATCHED +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import java.lang.reflect.Method +import java.util.concurrent.CancellationException +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.intrinsics.intercepted +import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn +import kotlin.reflect.jvm.kotlinFunction + +fun isSuspendFunction(method: Method): Boolean { + val func = method.kotlinFunction ?: return false + return func.isSuspend +} + +@Throws(CommandException::class) +fun callCommandAsCoroutine( + executionContext: ExecutionContext, + coroutineContext: CoroutineContext, + continuationIndex: Int, + method: Method, + instance: Any?, + args: Array<Any?> +): String? { + + // UNDISPATCHED causes the handler to run until the first suspension point on the current thread, + // meaning command handlers that don't have suspension points will run completely synchronously. + // Tasks that take time to compute should suspend the coroutine and resume on another thread. + val job = GlobalScope.async(context = coroutineContext, start = UNDISPATCHED) { + suspendCoroutineUninterceptedOrReturn<Any?> { cont -> + args[continuationIndex] = cont.intercepted() + method.invoke(instance, *args) + } + } + + if (job.isCompleted) { + return job.getResult() + } + + job.invokeOnCompletion { + val chatHandler = executionContext.address.chatHandler + try { + val result = job.getResult() + chatHandler.sendMessage(executionContext.sender, EMessageType.RESULT, result) + } catch (ex: Throwable) { + chatHandler.handleException(executionContext.sender, executionContext, ex) + } + } + + return null +} + +@Throws(CommandException::class) +private fun Deferred<Any?>.getResult(): String? { + getCompletionExceptionOrNull()?.let { ex -> + if (ex is CancellationException) { + System.err.println("An asynchronously dispatched command was cancelled unexpectedly") + ex.printStackTrace() + throw CommandException("The command was cancelled unexpectedly (see console)") + } + if (ex is Exception) return ReflectiveCommand.getResult(null, ex) + throw ex + } + return ReflectiveCommand.getResult(getCompleted(), null) +} |