net.lenni0451.commandlib.CommandExecutor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of CommandLib Show documentation
Show all versions of CommandLib Show documentation
A command lib with a simple and powerful API
The newest version!
package net.lenni0451.commandlib;
import net.lenni0451.commandlib.contexts.CompletionContext;
import net.lenni0451.commandlib.contexts.ExecutionContext;
import net.lenni0451.commandlib.exceptions.ChainExecutionException;
import net.lenni0451.commandlib.exceptions.CommandExecutionException;
import net.lenni0451.commandlib.nodes.ArgumentNode;
import net.lenni0451.commandlib.nodes.RedirectNode;
import net.lenni0451.commandlib.nodes.StringNode;
import net.lenni0451.commandlib.utils.StringReader;
import net.lenni0451.commandlib.utils.Util;
import net.lenni0451.commandlib.utils.comparator.ArgumentComparator;
import net.lenni0451.commandlib.utils.comparator.CloseChainsComparator;
import net.lenni0451.commandlib.utils.comparator.CompletionsComparator;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;
/**
* The handler for all registered commands.
*
* @param The type of the executor
*/
public class CommandExecutor {
private final ArgumentComparator argumentComparator;
private final Map, List>> chains;
public CommandExecutor() {
this(ArgumentComparator.CASE_INSENSITIVE);
}
public CommandExecutor(final ArgumentComparator argumentComparator) {
this.argumentComparator = argumentComparator;
this.chains = new HashMap<>();
}
/**
* Register an argument node.
* The argument node must be a {@link StringNode}.
* This method just exists for convenience. See {@link #register(StringNode)}.
*
* @param argumentNode The argument node
*/
public void register(final ArgumentNode argumentNode) {
if (!(argumentNode instanceof StringNode)) throw new IllegalArgumentException("Register argument node must be a StringNode");
this.register((StringNode) argumentNode);
}
/**
* Register a string argument node.
*
* @param stringNode The string argument node
*/
public void register(final StringNode stringNode) {
this.chains.entrySet().removeIf(entry -> this.argumentComparator.compare(entry.getKey().name(), stringNode.name()));
this.chains.put(stringNode, ArgumentChain.buildChains(stringNode));
}
/**
* Get completions for the given command input.
*
* @param executor The executor
* @param command The command input
* @return The sorted completions
*/
public Set completions(@Nonnull final E executor, @Nonnull final String command) {
return this.completions(executor, new StringReader(command));
}
/**
* Get completions for the given command input.
*
* @param executor The executor
* @param reader The string reader
* @return The sorted completions
*/
public Set completions(@Nonnull final E executor, @Nonnull final StringReader reader) {
Set completions = new HashSet<>();
if (!reader.canRead()) {
completions.addAll(this.chains.keySet().stream().map(StringNode::name).map(n -> new Completion(0, n)).collect(Collectors.toList()));
} else {
ExecutionContext executionContext = new ExecutionContext<>(this.argumentComparator, executor, false);
ParseResult parseResult = this.parseChains(executionContext, reader);
for (ParseResult.ParsedChain parsedChain : parseResult.getParsedChains()) {
if (parsedChain.getMatchedArguments().isEmpty()) continue;
ArgumentChain chain = parsedChain.getArgumentChain();
List matchedArguments = parsedChain.getMatchedArguments();
CompletionContext completionContext = new CompletionContext();
ArgumentChain.MatchedArgument match = matchedArguments.get(matchedArguments.size() - 1);
ArgumentNode argument = chain.getArgument(matchedArguments.size() - 1);
reader.setCursor(match.getCursor());
String check = reader.peekRemaining();
Set argumentCompletions = argument.parseCompletions(completionContext, executionContext, reader);
for (String completion : argumentCompletions) {
int trim = completionContext.getCompletionsTrim();
if (this.argumentComparator.startsWith(completion, check.substring(trim))) completions.add(new Completion(match.getCursor() + trim, completion));
}
}
for (ParseResult.FailedChain failedChain : parseResult.getFailedChains()) {
ArgumentChain chain = failedChain.getArgumentChain();
ChainExecutionException exception = failedChain.getExecutionException();
if (ChainExecutionException.Reason.REQUIREMENT_FAILED.equals(exception.getReason())) continue;
CompletionContext completionContext = new CompletionContext();
reader.setCursor(exception.getReaderCursor());
ArgumentNode argument = chain.getArgument(exception.getExecutionIndex());
while (argument instanceof RedirectNode) argument = ((RedirectNode) argument).getTargetNode();
String check = reader.peekRemaining();
Set argumentCompletions = argument.parseCompletions(completionContext, executionContext, reader);
for (String completion : argumentCompletions) {
int trim = completionContext.getCompletionsTrim();
if (this.argumentComparator.startsWith(completion, check.substring(trim))) completions.add(new Completion(exception.getReaderCursor() + trim, completion));
}
}
}
return completions
.stream()
.sorted(new CompletionsComparator(this.argumentComparator))
.map(s -> {
if (s.getCompletion().contains(" ")) return new Completion(s.getStart(), "\"" + s.getCompletion().replace("\\", "\\\\").replace("\"", "\\\"") + "\"");
else return s;
})
.collect(Collectors.toCollection(LinkedHashSet::new));
}
/**
* Execute the given command input.
*
* @param executor The executor
* @param command The command input
* @param The return type of the executed command
* @return The return value of the executed command
* @throws CommandExecutionException If the command execution failed
*/
@Nullable
public T execute(@Nonnull final E executor, @Nonnull final String command) throws CommandExecutionException {
return this.execute(executor, new StringReader(command));
}
/**
* Execute the given command input.
*
* @param executor The executor
* @param reader The string reader
* @param The return type of the executed command
* @return The return value of the executed command
* @throws CommandExecutionException If the command execution failed
*/
@Nullable
public T execute(@Nonnull final E executor, @Nonnull final StringReader reader) throws CommandExecutionException {
if (!reader.canRead()) throw new CommandExecutionException("");
ExecutionContext executionContext = new ExecutionContext<>(this.argumentComparator, executor, true);
ParseResult parseResult = this.parseChains(executionContext, reader);
try {
return this.executeChain(parseResult, executionContext, reader);
} catch (CommandExecutionException e) {
if (parseResult.getFailedChains().isEmpty()) throw e;
List> closeChains = CloseChainsComparator.getClosest(parseResult.getFailedChains());
closeChains.sort((f1, f2) -> this.compareChains(f1.getArgumentChain(), f2.getArgumentChain()));
throw new CommandExecutionException(e.getCommand(), Util.cast(closeChains));
}
}
private ParseResult parseChains(final ExecutionContext executionContext, final StringReader reader) {
List> chains = new ArrayList<>();
for (List> nodeChains : this.chains.values()) chains.addAll(nodeChains);
return this.parseChains(chains, executionContext, reader);
}
private ParseResult parseChains(final List> chains, final ExecutionContext executionContext, final StringReader reader) {
List> parsedChains = new ArrayList<>();
List> failedChains = new ArrayList<>();
int cursor = reader.getCursor();
for (ArgumentChain chain : chains) {
reader.setCursor(cursor);
try {
List matchedArguments = chain.parse(executionContext, reader);
if (chain.getArgument(chain.getLength() - 1) instanceof RedirectNode) {
RedirectNode redirectNode = (RedirectNode) chain.getArgument(chain.getLength() - 1);
ParseResult redirectResult = this.parseChains(redirectNode.getTargetChains(), executionContext, reader);
for (ParseResult.ParsedChain parsedChain : redirectResult.getParsedChains()) {
matchedArguments.addAll(parsedChain.getMatchedArguments());
parsedChains.add(new ParseResult.ParsedChain<>(ArgumentChain.merge(chain, parsedChain.getArgumentChain()), matchedArguments));
}
for (ParseResult.FailedChain failedChain : redirectResult.getFailedChains()) {
ChainExecutionException mergedException = new ChainExecutionException(failedChain.getExecutionException(), chain.getLength());
failedChains.add(new ParseResult.FailedChain<>(ArgumentChain.merge(chain, failedChain.getArgumentChain()), mergedException));
}
} else {
parsedChains.add(new ParseResult.ParsedChain<>(chain, matchedArguments));
}
} catch (ChainExecutionException e) {
if (e.getExecutionIndex() == 0) {
reader.setCursor(e.getReaderCursor());
String word = reader.readWordOrString();
if (!this.argumentComparator.startsWith(chain.getArgument(0).name(), word)) continue;
}
failedChains.add(new ParseResult.FailedChain<>(chain, e));
}
}
reader.setCursor(cursor);
return new ParseResult<>(parsedChains, failedChains);
}
private T executeChain(final ParseResult parseResult, final ExecutionContext executionContext, final StringReader reader) throws CommandExecutionException {
if (parseResult.getParsedChains().isEmpty()) {
throw new CommandExecutionException(reader.readWordOrString());
} else if (parseResult.getParsedChains().size() == 1) {
ParseResult.ParsedChain chain = parseResult.getParsedChains().get(0);
List arguments = chain.getMatchedArguments();
chain.getArgumentChain().populateArguments(executionContext, arguments);
return (T) chain.getArgumentChain().getExecutor().apply(executionContext);
} else {
List> parsedChains = new ArrayList<>();
ParseResult.ParsedChain bestChain = this.findBestChain(parseResult.getParsedChains());
parsedChains.add(bestChain);
return this.executeChain(new ParseResult<>(parsedChains, parseResult.getFailedChains()), executionContext, reader);
}
}
private ParseResult.ParsedChain findBestChain(final List> chains) {
return chains.stream().max((p1, p2) -> this.compareChains(p1.getArgumentChain(), p2.getArgumentChain())).orElseThrow(IllegalStateException::new);
}
private int compareChains(final ArgumentChain chain1, final ArgumentChain chain2) {
if (chain1.getLength() > chain2.getLength()) return 1;
if (chain1.getLength() < chain2.getLength()) return -1;
int[] weights1 = chain1.getWeights();
int[] weights2 = chain2.getWeights();
for (int i = 0; i < weights1.length; i++) {
if (weights1[i] > weights2[i]) return 1;
if (weights1[i] < weights2[i]) return -1;
}
return 0;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy