Initial support for compound arguments

This allows for grouping and mappings of multiple command arguments by using product types.
This commit is contained in:
Alexander Söderberg 2020-09-27 22:39:56 +02:00 committed by Alexander Söderberg
parent e033ee88db
commit 94710c5174
22 changed files with 1032 additions and 28 deletions

View file

@ -133,7 +133,6 @@
<!-- Checks for Size Violations. -->
<!-- See https://checkstyle.org/config_sizes.html -->
<module name="MethodLength"/>
<module name="ParameterNumber"/>
<!-- Checks for whitespace -->
<!-- See https://checkstyle.org/config_whitespace.html -->
@ -166,7 +165,6 @@
<module name="EqualsHashCode"/>
<module name="IllegalInstantiation"/>
<module name="InnerAssignment"/>
<module name="MagicNumber"/>
<module name="MissingSwitchDefault"/>
<module name="MultipleVariableDeclarations"/>
<module name="SimplifyBooleanExpression"/>

View file

@ -41,6 +41,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* A command consists out of a chain of {@link CommandArgument command arguments}.
@ -359,7 +360,6 @@ public class Command<C> {
this.commandExecutionHandler, this.commandPermission);
}
/**
* Add a new command argument by interacting with a constructed command argument builder
*
@ -381,6 +381,138 @@ public class Command<C> {
return this.argument(builder.build());
}
// Compound helper methods
/**
* Create a new argument pair that maps to {@link Pair}
* <p>
* For this to work, there must be a {@link CommandManager}
* attached to the command builder. To guarantee this, it is recommended to get the command builder instance
* using {@link CommandManager#commandBuilder(String, String...)}
*
* @param name Name of the argument
* @param names Pair containing the names of the sub-arguments
* @param parserPair Pair containing the types of the sub-arguments. There must be parsers for these types registered
* in the {@link com.intellectualsites.commands.arguments.parser.ParserRegistry} used by the
* {@link CommandManager} attached to this command
* @param description Description of the argument
* @param <U> First type
* @param <V> Second type
* @return Builder instance with the argument inserted
*/
@Nonnull
public <U, V> Builder<C> argumentPair(@Nonnull final String name,
@Nonnull final Pair<String, String> names,
@Nonnull final Pair<Class<U>, Class<V>> parserPair,
@Nonnull final Description description) {
if (this.commandManager == null) {
throw new IllegalStateException("This cannot be called from a command that has no command manager attached");
}
return this.argument(ArgumentPair.required(this.commandManager, name, names, parserPair).simple(), description);
}
/**
* Create a new argument pair that maps to a custom type.
* <p>
* For this to work, there must be a {@link CommandManager}
* attached to the command builder. To guarantee this, it is recommended to get the command builder instance
* using {@link CommandManager#commandBuilder(String, String...)}
*
* @param name Name of the argument
* @param outputType The output type
* @param names Pair containing the names of the sub-arguments
* @param parserPair Pair containing the types of the sub-arguments. There must be parsers for these types registered
* in the {@link com.intellectualsites.commands.arguments.parser.ParserRegistry} used by the
* {@link CommandManager} attached to this command
* @param mapper Mapper that maps from {@link Pair} to the custom type
* @param description Description of the argument
* @param <U> First type
* @param <V> Second type
* @param <O> Output type
* @return Builder instance with the argument inserted
*/
@Nonnull
public <U, V, O> Builder<C> argumentPair(@Nonnull final String name,
@Nonnull final TypeToken<O> outputType,
@Nonnull final Pair<String, String> names,
@Nonnull final Pair<Class<U>, Class<V>> parserPair,
@Nonnull final Function<Pair<U, V>, O> mapper,
@Nonnull final Description description) {
if (this.commandManager == null) {
throw new IllegalStateException("This cannot be called from a command that has no command manager attached");
}
return this.argument(
ArgumentPair.required(this.commandManager, name, names, parserPair).withMapper(outputType, mapper),
description);
}
/**
* Create a new argument pair that maps to {@link com.intellectualsites.commands.types.tuples.Triplet}
* <p>
* For this to work, there must be a {@link CommandManager}
* attached to the command builder. To guarantee this, it is recommended to get the command builder instance
* using {@link CommandManager#commandBuilder(String, String...)}
*
* @param name Name of the argument
* @param names Triplet containing the names of the sub-arguments
* @param parserTriplet Triplet containing the types of the sub-arguments. There must be parsers for these types
* registered in the {@link com.intellectualsites.commands.arguments.parser.ParserRegistry}
* used by the {@link CommandManager} attached to this command
* @param description Description of the argument
* @param <U> First type
* @param <V> Second type
* @param <W> Third type
* @return Builder instance with the argument inserted
*/
@Nonnull
public <U, V, W> Builder<C> argumentTriplet(@Nonnull final String name,
@Nonnull final Triplet<String, String, String> names,
@Nonnull final Triplet<Class<U>, Class<V>, Class<W>> parserTriplet,
@Nonnull final Description description) {
if (this.commandManager == null) {
throw new IllegalStateException("This cannot be called from a command that has no command manager attached");
}
return this.argument(ArgumentTriplet.required(this.commandManager, name, names, parserTriplet).simple(), description);
}
/**
* Create a new argument triplet that maps to a custom type.
* <p>
* For this to work, there must be a {@link CommandManager}
* attached to the command builder. To guarantee this, it is recommended to get the command builder instance
* using {@link CommandManager#commandBuilder(String, String...)}
*
* @param name Name of the argument
* @param outputType The output type
* @param names Triplet containing the names of the sub-arguments
* @param parserTriplet Triplet containing the types of the sub-arguments. There must be parsers for these types
* registered in the {@link com.intellectualsites.commands.arguments.parser.ParserRegistry} used by
* the {@link CommandManager} attached to this command
* @param mapper Mapper that maps from {@link Triplet} to the custom type
* @param description Description of the argument
* @param <U> First type
* @param <V> Second type
* @param <W> Third type
* @param <O> Output type
* @return Builder instance with the argument inserted
*/
@Nonnull
public <U, V, W, O> Builder<C> argumentTriplet(@Nonnull final String name,
@Nonnull final TypeToken<O> outputType,
@Nonnull final Triplet<String, String, String> names,
@Nonnull final Triplet<Class<U>, Class<V>, Class<W>> parserTriplet,
@Nonnull final Function<Triplet<U, V, W>, O> mapper,
@Nonnull final Description description) {
if (this.commandManager == null) {
throw new IllegalStateException("This cannot be called from a command that has no command manager attached");
}
return this.argument(
ArgumentTriplet.required(this.commandManager, name, names, parserTriplet).withMapper(outputType, mapper),
description);
}
// End of compound helper methods
/**
* Specify the command execution handler
*

View file

@ -360,6 +360,25 @@ public final class CommandTree<C> {
if (children.size() == 1 && !(children.get(0).getValue() instanceof StaticArgument)) {
// The value has to be a variable
final Node<CommandArgument<C, ?>> child = children.get(0);
// START: Compound arguments
/* When we get in here, we need to treat compound arguments a little differently */
if (child.getValue() instanceof CompoundArgument) {
@SuppressWarnings("unchecked")
final CompoundArgument<?, C, ?> compoundArgument = (CompoundArgument<?, C, ?>) child.getValue();
/* See how many arguments it requires */
final int requiredArguments = compoundArgument.getParserTuple().getSize();
/* Figure out whether we even need to care about this */
if (commandQueue.size() <= requiredArguments) {
/* Attempt to pop as many arguments from the stack as possible */
for (int i = 0; i < requiredArguments - 1 && commandQueue.size() > 1; i++) {
commandQueue.remove();
commandContext.store("__parsing_argument__", i + 2);
}
}
}
// END: Compound arguments
if (child.getValue() != null) {
if (commandQueue.isEmpty()) {
return Collections.emptyList();
@ -367,6 +386,10 @@ public final class CommandTree<C> {
} else if (child.isLeaf() && commandQueue.size() < 2) {
return child.getValue().getSuggestionsProvider().apply(commandContext, commandQueue.peek());
} else if (child.isLeaf()) {
if (child.getValue() instanceof CompoundArgument) {
final String last = ((LinkedList<String>) commandQueue).getLast();
return child.getValue().getSuggestionsProvider().apply(commandContext, last);
}
return Collections.emptyList();
} else if (commandQueue.peek().isEmpty()) {
return child.getValue().getSuggestionsProvider().apply(commandContext, commandQueue.remove());

View file

@ -77,7 +77,7 @@ public class CommandArgument<C, T> implements Comparable<CommandArgument<?, ?>>
/**
* The type that is produces by the argument's parser
*/
private final Class<T> valueType;
private final TypeToken<T> valueType;
/**
* Suggestion provider
*/
@ -99,7 +99,7 @@ public class CommandArgument<C, T> implements Comparable<CommandArgument<?, ?>>
@Nonnull final String name,
@Nonnull final ArgumentParser<C, T> parser,
@Nonnull final String defaultValue,
@Nonnull final Class<T> valueType,
@Nonnull final TypeToken<T> valueType,
@Nullable final BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider) {
this.required = required;
this.name = Objects.requireNonNull(name, "Name may not be null");
@ -114,6 +114,25 @@ public class CommandArgument<C, T> implements Comparable<CommandArgument<?, ?>>
: suggestionsProvider;
}
/**
* Construct a new command argument
*
* @param required Whether or not the argument is required
* @param name The argument name
* @param parser The argument parser
* @param defaultValue Default value used when no value is provided by the command sender
* @param valueType Type produced by the parser
* @param suggestionsProvider Suggestions provider
*/
public CommandArgument(final boolean required,
@Nonnull final String name,
@Nonnull final ArgumentParser<C, T> parser,
@Nonnull final String defaultValue,
@Nonnull final Class<T> valueType,
@Nullable final BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider) {
this(required, name, parser, defaultValue, TypeToken.of(valueType), suggestionsProvider);
}
/**
* Construct a new command argument
*
@ -144,11 +163,26 @@ public class CommandArgument<C, T> implements Comparable<CommandArgument<?, ?>>
* @return Argument builder
*/
@Nonnull
public static <C, T> CommandArgument.Builder<C, T> ofType(@Nonnull final Class<T> clazz,
public static <C, T> CommandArgument.Builder<C, T> ofType(@Nonnull final TypeToken<T> clazz,
@Nonnull final String name) {
return new Builder<>(clazz, name);
}
/**
* Create a new command argument
*
* @param clazz Argument class
* @param name Argument name
* @param <C> Command sender type
* @param <T> Argument Type. Used to make the compiler happy.
* @return Argument builder
*/
@Nonnull
public static <C, T> CommandArgument.Builder<C, T> ofType(@Nonnull final Class<T> clazz,
@Nonnull final String name) {
return new Builder<>(TypeToken.of(clazz), name);
}
/**
* Check whether or not the command argument is required
*
@ -277,7 +311,7 @@ public class CommandArgument<C, T> implements Comparable<CommandArgument<?, ?>>
* @return Value type
*/
@Nonnull
public Class<T> getValueType() {
public TypeToken<T> getValueType() {
return this.valueType;
}
@ -310,7 +344,7 @@ public class CommandArgument<C, T> implements Comparable<CommandArgument<?, ?>>
*/
public static class Builder<C, T> {
private final Class<T> valueType;
private final TypeToken<T> valueType;
private final String name;
private CommandManager<C> manager;
@ -319,12 +353,17 @@ public class CommandArgument<C, T> implements Comparable<CommandArgument<?, ?>>
private String defaultValue = "";
private BiFunction<CommandContext<C>, String, List<String>> suggestionsProvider;
protected Builder(@Nonnull final Class<T> valueType,
protected Builder(@Nonnull final TypeToken<T> valueType,
@Nonnull final String name) {
this.valueType = valueType;
this.name = name;
}
protected Builder(@Nonnull final Class<T> valueType,
@Nonnull final String name) {
this(TypeToken.of(valueType), name);
}
/**
* Set the command manager. Will be used to create a default parser
* if none was provided
@ -418,7 +457,7 @@ public class CommandArgument<C, T> implements Comparable<CommandArgument<?, ?>>
@Nonnull
public CommandArgument<C, T> build() {
if (this.parser == null && this.manager != null) {
this.parser = this.manager.getParserRegistry().createParser(TypeToken.of(valueType), ParserParameters.empty())
this.parser = this.manager.getParserRegistry().createParser(valueType, ParserParameters.empty())
.orElse(null);
}
if (this.parser == null) {

View file

@ -41,8 +41,8 @@ final class DelegatingSuggestionsProvider<C> implements BiFunction<CommandContex
}
@Override
public List<String> apply(final CommandContext<C> context, final String s) {
return this.parser.suggestions(context, s);
public List<String> apply(final CommandContext<C> context, final String string) {
return this.parser.suggestions(context, string);
}
@Override

View file

@ -53,7 +53,20 @@ public class StandardCommandSyntaxFormatter<C> implements CommandSyntaxFormatter
if (commandArgument instanceof StaticArgument) {
stringBuilder.append(commandArgument.getName());
} else {
if (commandArgument.isRequired()) {
if (commandArgument instanceof CompoundArgument) {
final char prefix = commandArgument.isRequired() ? '<' : '[';
final char suffix = commandArgument.isRequired() ? '>' : ']';
stringBuilder.append(prefix);
// noinspection all
final Object[] names = ((CompoundArgument<?, C, ?>) commandArgument).getNames().toArray();
for (int i = 0; i < names.length; i++) {
stringBuilder.append(prefix).append(names[i]).append(suffix);
if ((i + 1) < names.length) {
stringBuilder.append(' ');
}
}
stringBuilder.append(suffix);
} else if (commandArgument.isRequired()) {
stringBuilder.append("<").append(commandArgument.getName()).append(">");
} else {
stringBuilder.append("[").append(commandArgument.getName()).append("]");
@ -90,10 +103,24 @@ public class StandardCommandSyntaxFormatter<C> implements CommandSyntaxFormatter
prefix = "[";
suffix = "]";
}
if (argument instanceof CompoundArgument) {
stringBuilder.append(" ").append(prefix);
// noinspection all
final Object[] names = ((CompoundArgument<?, C, ?>) argument).getNames().toArray();
for (int i = 0; i < names.length; i++) {
stringBuilder.append(prefix).append(names[i]).append(suffix);
if ((i + 1) < names.length) {
stringBuilder.append(' ');
}
}
stringBuilder.append(suffix);
} else {
stringBuilder.append(" ")
.append(prefix)
.append(argument.getName())
.append(suffix);
}
tail = tail.getChildren().get(0);
}
return stringBuilder.toString();

View file

@ -33,7 +33,7 @@ import javax.annotation.Nonnull;
* @param <U> First type
* @param <V> Second type
*/
public class Pair<U, V> {
public class Pair<U, V> implements Tuple {
@Nonnull
private final U first;
@ -104,4 +104,18 @@ public class Pair<U, V> {
return String.format("(%s, %s)", this.first, this.second);
}
@Override
public final int getSize() {
return Tuples.SIZE_PAIR;
}
@Nonnull
@Override
public final Object[] toArray() {
final Object[] array = new Object[2];
array[0] = this.first;
array[1] = this.second;
return array;
}
}

View file

@ -35,7 +35,7 @@ import javax.annotation.Nonnull;
* @param <W> Third type
* @param <X> Fourth type
*/
public class Quartet<U, V, W, X> {
public class Quartet<U, V, W, X> implements Tuple {
@Nonnull
private final U first;
@ -142,4 +142,20 @@ public class Quartet<U, V, W, X> {
return String.format("(%s, %s, %s, %s)", this.first, this.second, this.third, this.fourth);
}
@Override
public final int getSize() {
return Tuples.SIZE_QUARTET;
}
@Nonnull
@Override
public final Object[] toArray() {
final Object[] array = new Object[4];
array[0] = this.first;
array[1] = this.second;
array[3] = this.third;
array[4] = this.fourth;
return array;
}
}

View file

@ -36,7 +36,7 @@ import javax.annotation.Nonnull;
* @param <X> Fourth type
* @param <Y> Fifth type
*/
public class Quintet<U, V, W, X, Y> {
public class Quintet<U, V, W, X, Y> implements Tuple {
@Nonnull
private final U first;
@ -161,4 +161,21 @@ public class Quintet<U, V, W, X, Y> {
return String.format("(%s, %s, %s, %s, %s)", this.first, this.second, this.third, this.fourth, this.fifth);
}
@Override
public final int getSize() {
return Tuples.SIZE_QUINTET;
}
@Nonnull
@Override
public final Object[] toArray() {
final Object[] array = new Object[5];
array[0] = this.first;
array[1] = this.second;
array[3] = this.third;
array[4] = this.fourth;
array[5] = this.fifth;
return array;
}
}

View file

@ -37,7 +37,7 @@ import javax.annotation.Nonnull;
* @param <Y> Fifth type
* @param <Z> Sixth type
*/
public class Sextet<U, V, W, X, Y, Z> {
public class Sextet<U, V, W, X, Y, Z> implements Tuple {
@Nonnull
private final U first;
@ -181,4 +181,22 @@ public class Sextet<U, V, W, X, Y, Z> {
this.fourth, this.fifth, this.sixth);
}
@Override
public final int getSize() {
return Tuples.SIZE_SEXTET;
}
@Nonnull
@Override
public final Object[] toArray() {
final Object[] array = new Object[6];
array[0] = this.first;
array[1] = this.second;
array[3] = this.third;
array[4] = this.fourth;
array[5] = this.fifth;
array[6] = this.sixth;
return array;
}
}

View file

@ -34,7 +34,7 @@ import javax.annotation.Nonnull;
* @param <V> Second type
* @param <W> Third type
*/
public class Triplet<U, V, W> {
public class Triplet<U, V, W> implements Tuple {
@Nonnull
private final U first;
@ -123,4 +123,19 @@ public class Triplet<U, V, W> {
return String.format("(%s, %s, %s)", this.first, this.second, this.third);
}
@Override
public final int getSize() {
return Tuples.SIZE_TRIPLET;
}
@Nonnull
@Override
public final Object[] toArray() {
final Object[] array = new Object[3];
array[0] = this.first;
array[1] = this.second;
array[2] = this.third;
return array;
}
}

View file

@ -30,6 +30,12 @@ import javax.annotation.Nonnull;
*/
public final class Tuples {
static final int SIZE_PAIR = 2;
static final int SIZE_TRIPLET = 3;
static final int SIZE_QUARTET = 4;
static final int SIZE_QUINTET = 5;
static final int SIZE_SEXTET = 6;
private Tuples() {
}

View file

@ -0,0 +1,155 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package com.intellectualsites.commands.arguments.compound;
import com.google.common.reflect.TypeToken;
import com.intellectualsites.commands.CommandManager;
import com.intellectualsites.commands.arguments.parser.ArgumentParser;
import com.intellectualsites.commands.arguments.parser.ParserParameters;
import com.intellectualsites.commands.arguments.parser.ParserRegistry;
import com.intellectualsites.commands.types.tuples.Pair;
import javax.annotation.Nonnull;
import java.util.function.Function;
/**
* A compound argument consisting of two inner arguments
*
* @param <C> Command sender type
* @param <U> First argument type
* @param <V> Second argument type
* @param <O> Output type
*/
public final class ArgumentPair<C, U, V, O> extends CompoundArgument<Pair<U, V>, C, O> {
private ArgumentPair(final boolean required,
@Nonnull final String name,
@Nonnull final Pair<String, String> names,
@Nonnull final Pair<Class<U>, Class<V>> types,
@Nonnull final Pair<ArgumentParser<C, U>, ArgumentParser<C, V>> parserPair,
@Nonnull final Function<Pair<U, V>, O> mapper,
@Nonnull final TypeToken<O> valueType) {
super(required, name, names, parserPair, types, mapper, o -> Pair.of((U) o[0], (V) o[1]), valueType);
}
/**
* Construct a builder for an argument pair
*
* @param manager Command manager
* @param name Argument name
* @param names Sub-argument names
* @param types Pair containing the types of the sub-arguments. There must be parsers for these types registered
* in the {@link com.intellectualsites.commands.arguments.parser.ParserRegistry} used by the
* {@link CommandManager}
* @param <C> Command sender type
* @param <U> First parsed type
* @param <V> Second parsed type
* @return Intermediary builder
*/
@Nonnull
public static <C, U, V> ArgumentPairIntermediaryBuilder<C, U, V> required(@Nonnull final CommandManager<C> manager,
@Nonnull final String name,
@Nonnull final Pair<String, String> names,
@Nonnull final Pair<Class<U>, Class<V>> types) {
final ParserRegistry<C> parserRegistry = manager.getParserRegistry();
final ArgumentParser<C, U> firstParser = parserRegistry.createParser(TypeToken.of(types.getFirst()),
ParserParameters.empty()).orElseThrow(() ->
new IllegalArgumentException(
"Could not create parser for primary type"));
final ArgumentParser<C, V> secondaryParser = parserRegistry.createParser(TypeToken.of(types.getSecond()),
ParserParameters.empty()).orElseThrow(() ->
new IllegalArgumentException(
"Could not create parser for secondary type"));
return new ArgumentPairIntermediaryBuilder<>(true, name, names, Pair.of(firstParser, secondaryParser), types);
}
@SuppressWarnings("ALL")
public static final class ArgumentPairIntermediaryBuilder<C, U, V> {
private final boolean required;
private final String name;
private final Pair<ArgumentParser<C, U>, ArgumentParser<C, V>> parserPair;
private final Pair<String, String> names;
private final Pair<Class<U>, Class<V>> types;
private ArgumentPairIntermediaryBuilder(final boolean required,
@Nonnull final String name,
@Nonnull final Pair<String, String> names,
@Nonnull final Pair<ArgumentParser<C, U>, ArgumentParser<C, V>> parserPair,
@Nonnull final Pair<Class<U>, Class<V>> types) {
this.required = required;
this.name = name;
this.names = names;
this.parserPair = parserPair;
this.types = types;
}
/**
* Create a simple argument pair that maps to a pair
*
* @return Argument pair
*/
@Nonnull
public ArgumentPair<C, U, V, Pair<U, V>> simple() {
return new ArgumentPair<C, U, V, Pair<U, V>>(this.required,
this.name,
this.names,
this.types,
this.parserPair,
Function.identity(),
new TypeToken<Pair<U, V>>() {
});
}
/**
* Create an argument pair that maps to a specific type
*
* @param clazz Output class
* @param mapper Output mapper
* @param <O> Output type
* @return Created pair
*/
@Nonnull
public <O> ArgumentPair<C, U, V, O> withMapper(@Nonnull final TypeToken<O> clazz,
@Nonnull final Function<Pair<U, V>, O> mapper) {
return new ArgumentPair<C, U, V, O>(this.required, this.name, this.names, this.types, this.parserPair, mapper, clazz);
}
/**
* Create an argument pair that maps to a specific type
*
* @param clazz Output class
* @param mapper Output mapper
* @param <O> Output type
* @return Created pair
*/
@Nonnull
public <O> ArgumentPair<C, U, V, O> withMapper(@Nonnull final Class<O> clazz,
@Nonnull final Function<Pair<U, V>, O> mapper) {
return this.withMapper(TypeToken.of(clazz), mapper);
}
}
}

View file

@ -0,0 +1,166 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package com.intellectualsites.commands.arguments.compound;
import com.google.common.reflect.TypeToken;
import com.intellectualsites.commands.CommandManager;
import com.intellectualsites.commands.arguments.parser.ArgumentParser;
import com.intellectualsites.commands.arguments.parser.ParserParameters;
import com.intellectualsites.commands.arguments.parser.ParserRegistry;
import com.intellectualsites.commands.types.tuples.Triplet;
import javax.annotation.Nonnull;
import java.util.function.Function;
/**
* A compound argument consisting of three inner arguments
*
* @param <C> Command sender type
* @param <U> First argument type
* @param <V> Second argument type
* @param <W> Third argument type
* @param <O> Output type
*/
public final class ArgumentTriplet<C, U, V, W, O> extends CompoundArgument<Triplet<U, V, W>, C, O> {
private ArgumentTriplet(final boolean required,
@Nonnull final String name,
@Nonnull final Triplet<String, String, String> names,
@Nonnull final Triplet<Class<U>, Class<V>, Class<W>> types,
@Nonnull final Triplet<ArgumentParser<C, U>, ArgumentParser<C, V>,
ArgumentParser<C, W>> parserTriplet,
@Nonnull final Function<Triplet<U, V, W>, O> mapper,
@Nonnull final TypeToken<O> valueType) {
super(required, name, names, parserTriplet, types, mapper, o -> Triplet.of((U) o[0], (V) o[1], (W) o[2]), valueType);
}
/**
* Construct a builder for an argument triplet
*
* @param manager Command manager
* @param name Argument name
* @param names Sub-argument names
* @param types Triplet containing the types of the sub-arguments. There must be parsers for these types registered
* in the {@link com.intellectualsites.commands.arguments.parser.ParserRegistry} used by the
* {@link CommandManager}
* @param <C> Command sender type
* @param <U> First parsed type
* @param <V> Second parsed type
* @param <W> Third type
* @return Intermediary builder
*/
@Nonnull
public static <C, U, V, W> ArgumentTripletIntermediaryBuilder<C, U, V, W>
required(@Nonnull final CommandManager<C> manager,
@Nonnull final String name,
@Nonnull final Triplet<String, String, String> names,
@Nonnull final Triplet<Class<U>, Class<V>, Class<W>> types) {
final ParserRegistry<C> parserRegistry = manager.getParserRegistry();
final ArgumentParser<C, U> firstParser = parserRegistry.createParser(TypeToken.of(types.getFirst()),
ParserParameters.empty()).orElseThrow(() ->
new IllegalArgumentException(
"Could not create parser for primary type"));
final ArgumentParser<C, V> secondaryParser = parserRegistry.createParser(TypeToken.of(types.getSecond()),
ParserParameters.empty()).orElseThrow(() ->
new IllegalArgumentException(
"Could not create parser for secondary type"));
final ArgumentParser<C, W> tertiaryParser = parserRegistry.createParser(TypeToken.of(types.getThird()),
ParserParameters.empty()).orElseThrow(() ->
new IllegalArgumentException(
"Could not create parser for tertiary type"));
return new ArgumentTripletIntermediaryBuilder<>(true, name, names,
Triplet.of(firstParser, secondaryParser, tertiaryParser), types);
}
@SuppressWarnings("ALL")
public static final class ArgumentTripletIntermediaryBuilder<C, U, V, W> {
private final boolean required;
private final String name;
private final Triplet<ArgumentParser<C, U>, ArgumentParser<C, V>, ArgumentParser<C, W>> parserTriplet;
private final Triplet<String, String, String> names;
private final Triplet<Class<U>, Class<V>, Class<W>> types;
private ArgumentTripletIntermediaryBuilder(final boolean required,
@Nonnull final String name,
@Nonnull final Triplet<String, String, String> names,
@Nonnull final Triplet<ArgumentParser<C, U>,
ArgumentParser<C, V>, ArgumentParser<C, W>> parserTriplet,
@Nonnull final Triplet<Class<U>, Class<V>, Class<W>> types) {
this.required = required;
this.name = name;
this.names = names;
this.parserTriplet = parserTriplet;
this.types = types;
}
/**
* Create a simple argument triplet that maps to a triplet
*
* @return Argument triplet
*/
@Nonnull
public ArgumentTriplet<C, U, V, W, Triplet<U, V, W>> simple() {
return new ArgumentTriplet<>(this.required,
this.name,
this.names,
this.types,
this.parserTriplet,
Function.identity(),
new TypeToken<Triplet<U, V, W>>() {
});
}
/**
* Create an argument triplet that maps to a specific type
*
* @param clazz Output class
* @param mapper Output mapper
* @param <O> Output type
* @return Created triplet
*/
@Nonnull
public <O> ArgumentTriplet<C, U, V, W, O> withMapper(@Nonnull final TypeToken<O> clazz,
@Nonnull final Function<Triplet<U, V, W>, O> mapper) {
return new ArgumentTriplet<>(this.required, this.name, this.names, this.types, this.parserTriplet, mapper, clazz);
}
/**
* Create an argument triplet that maps to a specific type
*
* @param clazz Output class
* @param mapper Output mapper
* @param <O> Output type
* @return Created triplet
*/
@Nonnull
public <O> ArgumentTriplet<C, U, V, W, O> withMapper(@Nonnull final Class<O> clazz,
@Nonnull final Function<Triplet<U, V, W>, O> mapper) {
return new ArgumentTriplet<>(this.required, this.name, this.names, this.types,
this.parserTriplet, mapper, TypeToken.of(clazz));
}
}
}

View file

@ -0,0 +1,153 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package com.intellectualsites.commands.arguments.compound;
import com.google.common.reflect.TypeToken;
import com.intellectualsites.commands.arguments.CommandArgument;
import com.intellectualsites.commands.arguments.parser.ArgumentParseResult;
import com.intellectualsites.commands.arguments.parser.ArgumentParser;
import com.intellectualsites.commands.context.CommandContext;
import com.intellectualsites.commands.types.tuples.Tuple;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Queue;
import java.util.function.Function;
/**
* Compound argument
*
* @param <T> Tuple type
* @param <C> Command sender type
* @param <O> Output type
*/
public class CompoundArgument<T extends Tuple, C, O> extends CommandArgument<C, O> {
private final Tuple types;
private final Tuple names;
private final Tuple parserTuple;
CompoundArgument(final boolean required,
@Nonnull final String name,
@Nonnull final Tuple names,
@Nonnull final Tuple parserTuple,
@Nonnull final Tuple types,
@Nonnull final Function<T, O> mapper,
@Nonnull final Function<Object[], T> tupleFactory,
@Nonnull final TypeToken<O> valueType) {
super(required,
name,
new CompoundParser<>(parserTuple, mapper, tupleFactory),
"",
valueType,
null);
this.parserTuple = parserTuple;
this.names = names;
this.types = types;
}
/**
* Get the tuple containing the internal parsers
*
* @return Internal parsers
*/
@Nonnull
public Tuple getParserTuple() {
return this.parserTuple;
}
/**
* Get the argument names
*
* @return Argument names
*/
@Nonnull
public Tuple getNames() {
return this.names;
}
/**
* Get the parser types
*
* @return Parser types
*/
@Nonnull
public Tuple getTypes() {
return this.types;
}
private static final class CompoundParser<T extends Tuple, C, O> implements ArgumentParser<C, O> {
private final Object[] parsers;
private final Function<T, O> mapper;
private final Function<Object[], T> tupleFactory;
private CompoundParser(@Nonnull final Tuple parserTuple,
@Nonnull final Function<T, O> mapper,
@Nonnull final Function<Object[], T> tupleFactory) {
this.parsers = parserTuple.toArray();
this.mapper = mapper;
this.tupleFactory = tupleFactory;
}
@Nonnull
@Override
public ArgumentParseResult<O> parse(@Nonnull final CommandContext<C> commandContext,
@Nonnull final Queue<String> inputQueue) {
final Object[] output = new Object[this.parsers.length];
for (int i = 0; i < this.parsers.length; i++) {
@SuppressWarnings("unchecked")
final ArgumentParser<C, ?> parser = (ArgumentParser<C, ?>) this.parsers[i];
final ArgumentParseResult<?> result = parser.parse(commandContext, inputQueue);
if (result.getFailure().isPresent()) {
/* Return the failure */
return ArgumentParseResult.failure(result.getFailure().get());
}
/* Store the parsed value */
output[i] = result.getParsedValue().orElse(null);
}
/* We now know that we have complete output, as none of the parsers returned a failure */
return ArgumentParseResult.success(this.mapper.apply(this.tupleFactory.apply(output)));
}
@Nonnull
@Override
public List<String> suggestions(@Nonnull final CommandContext<C> commandContext,
@Nonnull final String input) {
/*
This method will be called n times, each time for each of the internal types.
The problem is that we need to then know which of the parsers to forward the
suggestion request to. This is done by storing the number of parsed subtypes
in the context, so we can then extract that number and forward the request
*/
final int argument = commandContext.getOrDefault("__parsing_argument__", 1) - 1;
System.out.printf("Compound argument suggestions: %d | %s\n", argument, input);
//noinspection all
return ((ArgumentParser<C, ?>) this.parsers[argument]).suggestions(commandContext, input);
}
}
}

View file

@ -0,0 +1,28 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
/**
* Argument types that consists of 2 or more sub-types
*/
package com.intellectualsites.commands.arguments.compound;

View file

@ -0,0 +1,48 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package com.intellectualsites.commands.types.tuples;
import javax.annotation.Nonnull;
/**
* Tuple type
*/
public interface Tuple {
/**
* Get the tuple size
*
* @return Tuple size
*/
int getSize();
/**
* Turn the tuple into a type erased array
*
* @return Created array
*/
@Nonnull
Object[] toArray();
}

View file

@ -48,6 +48,12 @@ class CommandHelpHandlerTest {
final SimpleCommandMeta meta2 = SimpleCommandMeta.builder().with("description", "Command with variables").build();
manager.command(manager.commandBuilder("test", meta2).literal("int").
argument(IntegerArgument.required("int"), Description.of("A number")).build());
manager.command(manager.commandBuilder("vec")
.meta("description", "Takes in a vector")
.argumentPair("vec", Pair.of("x", "y"),
Pair.of(Double.class, Double.class), Description.of("Vector"))
.build());
}
@Test
@ -63,7 +69,7 @@ class CommandHelpHandlerTest {
@Test
void testLongestChains() {
final List<String> longestChains = manager.getCommandHelpHandler().getLongestSharedChains();
Assertions.assertEquals(Arrays.asList("test int|this"), longestChains);
Assertions.assertEquals(Arrays.asList("test int|this", "vec <<x> <y>>"), longestChains);
}
@Test
@ -77,6 +83,9 @@ class CommandHelpHandlerTest {
final CommandHelpHandler.HelpTopic<TestCommandSender> query3 = manager.getCommandHelpHandler().queryHelp("test int");
Assertions.assertTrue(query3 instanceof CommandHelpHandler.VerboseHelpTopic);
this.printTopic("test int", query3);
final CommandHelpHandler.HelpTopic<TestCommandSender> query4 = manager.getCommandHelpHandler().queryHelp("vec");
Assertions.assertTrue(query4 instanceof CommandHelpHandler.VerboseHelpTopic);
this.printTopic("vec", query4);
}
private void printTopic(@Nonnull final String query,

View file

@ -63,6 +63,17 @@ public class CommandSuggestionsTest {
.argument(IntegerArgument.<TestCommandSender>newBuilder("num")
.withSuggestionsProvider((c, s) -> Arrays.asList("3", "33", "333")).build())
.build());
manager.command(manager.commandBuilder("com")
.argumentPair("com", Pair.of("x", "y"), Pair.of(Integer.class, TestEnum.class),
Description.empty())
.argument(IntegerArgument.required("int"))
.build());
manager.command(manager.commandBuilder("com2")
.argumentPair("com", Pair.of("x", "enum"),
Pair.of(Integer.class, TestEnum.class), Description.empty())
.build());
}
@Test
@ -121,6 +132,21 @@ public class CommandSuggestionsTest {
Assertions.assertEquals(Arrays.asList("3", "33", "333"), suggestions);
}
@Test
void testCompound() {
final String input = "com ";
final List<String> suggestions = manager.suggest(new TestCommandSender(), input);
Assertions.assertEquals(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"), suggestions);
final String input2 = "com 1 ";
final List<String> suggestions2 = manager.suggest(new TestCommandSender(), input2);
Assertions.assertEquals(Arrays.asList("foo", "bar"), suggestions2);
final String input3 = "com 1 foo ";
final List<String> suggestions3 = manager.suggest(new TestCommandSender(), input3);
Assertions.assertEquals(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"), suggestions3);
final String input4 = "com2 1 ";
final List<String> suggestions4 = manager.suggest(new TestCommandSender(), input4);
Assertions.assertEquals(Arrays.asList("foo", "bar"), suggestions4);
}
public enum TestEnum {
FOO,

View file

@ -81,6 +81,28 @@ class CommandTreeTest {
.withPermission("command.outer")
.handler(c -> System.out.println("Using outer command"))
.build());
/* Build command for testing compound types */
manager.command(manager.commandBuilder("pos")
.argument(ArgumentPair.required(manager, "pos", Pair.of("x", "y"),
Pair.of(Integer.class, Integer.class))
.simple())
.handler(c -> {
final Pair<Integer, Integer> pair = c.getRequired("pos");
System.out.printf("X: %d | Y: %d\n", pair.getFirst(), pair.getSecond());
})
.build());
manager.command(manager.commandBuilder("vec")
.argument(ArgumentPair.required(manager, "vec", Pair.of("x", "y"),
Pair.of(Double.class, Double.class))
.withMapper(Vector2.class,
pair -> new Vector2(pair.getFirst(), pair.getSecond()))
)
.handler(c -> {
final Vector2 vector2 = c.getRequired("vec");
System.out.printf("X: %f | Y: %f\n", vector2.getX(), vector2.getY());
})
.build());
}
@Test
@ -157,8 +179,35 @@ class CommandTreeTest {
manager.executeCommand(new TestCommandSender(), "command").join();
}
@Test
void testCompound() {
manager.executeCommand(new TestCommandSender(), "pos -3 2").join();
manager.executeCommand(new TestCommandSender(), "vec 1 1").join();
}
public static final class SpecificCommandSender extends TestCommandSender {
}
public static final class Vector2 {
private final double x;
private final double y;
private Vector2(final double x, final double y) {
this.x = x;
this.y = y;
}
private double getX() {
return this.x;
}
private double getY() {
return this.y;
}
}
}

View file

@ -212,7 +212,7 @@ public final class CloudBrigadierManager<C, S> {
@Nullable
@SuppressWarnings("all")
private <T, K extends ArgumentParser<?, ?>> Pair<ArgumentType<?>, Boolean> getArgument(
@Nonnull final Class<?> valueType,
@Nonnull final TypeToken<?> valueType,
@Nonnull final TypeToken<T> argumentType,
@Nonnull final K argument) {
final ArgumentParser<C, ?> commandArgument = (ArgumentParser<C, ?>) argument;
@ -225,9 +225,9 @@ public final class CloudBrigadierManager<C, S> {
@Nonnull
private <T, K extends ArgumentParser<C, T>> Pair<ArgumentType<?>, Boolean> createDefaultMapper(
@Nonnull final Class<?> clazz,
@Nonnull final TypeToken<?> clazz,
@Nonnull final ArgumentParser<C, T> argument) {
final Supplier<ArgumentType<?>> argumentTypeSupplier = this.defaultArgumentTypeSuppliers.get(clazz);
final Supplier<ArgumentType<?>> argumentTypeSupplier = this.defaultArgumentTypeSuppliers.get(clazz.getRawType());
if (argumentTypeSupplier != null) {
return new Pair<>(argumentTypeSupplier.get(), true);
}
@ -298,12 +298,54 @@ public final class CloudBrigadierManager<C, S> {
@Nonnull final BiPredicate<S, CommandPermission> permissionChecker,
@Nonnull final com.mojang.brigadier.Command<S> executor,
@Nonnull final SuggestionProvider<S> suggestionProvider) {
if (root.getValue() instanceof CompoundArgument) {
@SuppressWarnings("unchecked")
final CompoundArgument<?, C, ?> compoundArgument = (CompoundArgument<?, C, ?>) root.getValue();
final Object[] parsers = compoundArgument.getParserTuple().toArray();
final Object[] types = compoundArgument.getTypes().toArray();
final Object[] names = compoundArgument.getNames().toArray();
/* Build nodes backwards */
final ArgumentBuilder<S, ?>[] argumentBuilders = new ArgumentBuilder[parsers.length];
for (int i = parsers.length - 1; i >= 0; i--) {
@SuppressWarnings("unchecked")
final ArgumentParser<C, ?> parser = (ArgumentParser<C, ?>) parsers[i];
final Pair<ArgumentType<?>, Boolean> pair = this.getArgument(TypeToken.of((Class<?>) types[i]),
TypeToken.of(parser.getClass()),
parser);
final SuggestionProvider<S> provider = pair.getRight() ? null : suggestionProvider;
final ArgumentBuilder<S, ?> fragmentBuilder = RequiredArgumentBuilder
.<S, Object>argument((String) names[i], (ArgumentType<Object>) pair.getLeft())
.suggests(provider)
.requires(sender -> permissionChecker.test(sender,
(CommandPermission) root.getNodeMeta()
.getOrDefault("permission", Permission.empty())));
argumentBuilders[i] = fragmentBuilder;
if (forceExecutor || (i == parsers.length - 1) && (root.isLeaf() || !root.getValue().isRequired())) {
fragmentBuilder.executes(executor);
}
/* Link all previous builder to this one */
if ((i + 1) < parsers.length) {
fragmentBuilder.then(argumentBuilders[i + 1]);
}
}
for (final CommandTree.Node<CommandArgument<C, ?>> node : root.getChildren()) {
argumentBuilders[parsers.length - 1]
.then(constructCommandNode(forceExecutor, node, permissionChecker, executor, suggestionProvider));
}
return argumentBuilders[0];
}
ArgumentBuilder<S, ?> argumentBuilder;
if (root.getValue() instanceof StaticArgument) {
argumentBuilder = LiteralArgumentBuilder.<S>literal(root.getValue().getName())
.requires(sender -> permissionChecker.test(sender, (CommandPermission) root.getNodeMeta()
.getOrDefault("permission", Permission.empty())))
.getOrDefault("permission",
Permission.empty())))
.executes(executor);
} else {
final Pair<ArgumentType<?>, Boolean> pair = this.getArgument(root.getValue().getValueType(),

View file

@ -58,6 +58,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.util.Vector;
import javax.annotation.Nonnull;
import java.util.ArrayList;
@ -210,13 +211,35 @@ public final class BukkitTest extends JavaPlugin {
.build(), Description.of("Help Query"))
.handler(c -> minecraftHelp.queryCommands(c.<String>get("query").orElse(""),
c.getSender())).build());
this.registerTeleportCommand(mgr);
mgr.registerExceptionHandler(InvalidSyntaxException.class, (c, e) -> e.printStackTrace());
} catch (final Exception e) {
e.printStackTrace();
}
}
private void registerTeleportCommand(@Nonnull final BukkitCommandManager<CommandSender> manager) {
manager.command(mgr.commandBuilder("teleport")
.meta("description", "Takes in a location and teleports the player there")
.withSenderType(Player.class)
.argument(WorldArgument.required("world"), Description.of("World name"))
.argumentTriplet("coords",
TypeToken.of(Vector.class),
Triplet.of("x", "y", "z"),
Triplet.of(Double.class, Double.class, Double.class),
triplet -> new Vector(triplet.getFirst(), triplet.getSecond(), triplet.getThird()),
Description.of("Coordinates"))
.handler(context -> {
context.getSender().sendMessage(ChatColor.GOLD + "Teleporting!");
Bukkit.getScheduler().runTask(this, () -> {
final World world = context.getRequired("world");
final Vector vector = context.getRequired("coords");
((Player) context.getSender()).teleport(vector.toLocation(world));
});
})
.build());
}
@CommandDescription("Test cloud command using @CommandMethod")
@CommandMethod(value = "annotation|a <input> [number]", permission = "some.permission.node")
private void annotatedCommand(@Nonnull final Player player,