Archived
0

Tweak some command stuff, clear/swap entities

This commit is contained in:
Dico Karssiens
2018-11-17 21:32:43 +00:00
parent 0f196f59c6
commit 5ef2584fdb
25 changed files with 3953 additions and 3554 deletions

View File

@@ -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));
}
}
}
}

View File

@@ -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");
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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)
}