Tweak some command stuff, clear/swap entities
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user