Rewrite Bukkit entity selector arguments using WrappedBrigadierParser instead of Bukkit API

- Proper handling of spaces
- Possible to use built-in minecraft translations for brigadier exceptions
- Fixes suggestions on Paper in combination with their option to fix tag selector suggestions
- Added option to fail parse when the result collection is empty
This commit is contained in:
Jason Penilla 2022-10-26 10:45:30 -07:00 committed by Jason
parent 09a66cef95
commit 2f34437398
12 changed files with 908 additions and 290 deletions

View file

@ -39,7 +39,9 @@ import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
@ -56,6 +58,7 @@ public final class WrappedBrigadierParser<C, T> implements ArgumentParser<C, T>
private final Supplier<ArgumentType<T>> nativeType; private final Supplier<ArgumentType<T>> nativeType;
private final int expectedArgumentCount; private final int expectedArgumentCount;
private final @Nullable ParseFunction<T> parse;
/** /**
* Create an argument parser based on a brigadier command. * Create an argument parser based on a brigadier command.
@ -101,10 +104,28 @@ public final class WrappedBrigadierParser<C, T> implements ArgumentParser<C, T>
public WrappedBrigadierParser( public WrappedBrigadierParser(
final Supplier<ArgumentType<T>> nativeType, final Supplier<ArgumentType<T>> nativeType,
final int expectedArgumentCount final int expectedArgumentCount
) {
this(nativeType, expectedArgumentCount, null);
}
/**
* Create an argument parser based on a brigadier command.
*
* @param nativeType the native command type provider, calculated lazily
* @param expectedArgumentCount the number of arguments the brigadier type is expected to consume
* @param parse special function to replace {@link ArgumentType#parse(StringReader)} (for CraftBukkit weirdness)
* @since 1.8.0
*/
@API(status = API.Status.STABLE, since = "1.8.0")
public WrappedBrigadierParser(
final Supplier<ArgumentType<T>> nativeType,
final int expectedArgumentCount,
final @Nullable ParseFunction<T> parse
) { ) {
requireNonNull(nativeType, "brigadierType"); requireNonNull(nativeType, "brigadierType");
this.nativeType = nativeType; this.nativeType = nativeType;
this.expectedArgumentCount = expectedArgumentCount; this.expectedArgumentCount = expectedArgumentCount;
this.parse = parse;
} }
/** /**
@ -135,7 +156,10 @@ public final class WrappedBrigadierParser<C, T> implements ArgumentParser<C, T>
// Then try to parse // Then try to parse
try { try {
return ArgumentParseResult.success(this.nativeType.get().parse(reader)); final T result = this.parse != null
? this.parse.apply(this.nativeType.get(), reader)
: this.nativeType.get().parse(reader);
return ArgumentParseResult.success(result);
} catch (final CommandSyntaxException ex) { } catch (final CommandSyntaxException ex) {
return ArgumentParseResult.failure(ex); return ArgumentParseResult.failure(ex);
} finally { } finally {
@ -191,4 +215,24 @@ public final class WrappedBrigadierParser<C, T> implements ArgumentParser<C, T>
public int getRequestedArgumentCount() { public int getRequestedArgumentCount() {
return this.expectedArgumentCount; return this.expectedArgumentCount;
} }
/**
* Function which can call {@link ArgumentType#parse(StringReader)} or another method.
*
* @param <T> result type
* @since 1.8.0
*/
@API(status = API.Status.STABLE, since = "1.8.0")
@FunctionalInterface
public interface ParseFunction<T> {
/**
* Apply the parse function.
*
* @param type argument type
* @param reader string reader
* @return result
* @throws CommandSyntaxException on failure
*/
T apply(ArgumentType<T> type, StringReader reader) throws CommandSyntaxException;
}
} }

View file

@ -27,6 +27,7 @@ import cloud.commandframework.captions.Caption;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
/** /**
@ -58,7 +59,11 @@ public final class BukkitCaptionKeys {
public static final Caption ARGUMENT_PARSE_FAILURE_WORLD = of("argument.parse.failure.world"); public static final Caption ARGUMENT_PARSE_FAILURE_WORLD = of("argument.parse.failure.world");
/** /**
* Variables: {input} * Variables: {input}
*
* @deprecated parsing is now handled by Brigadier and will throw {@link com.mojang.brigadier.exceptions.CommandSyntaxException} instead.
*/ */
@API(status = API.Status.DEPRECATED, since = "1.8.0")
@Deprecated
public static final Caption ARGUMENT_PARSE_FAILURE_SELECTOR_MALFORMED = of("argument.parse.failure.selector.malformed"); public static final Caption ARGUMENT_PARSE_FAILURE_SELECTOR_MALFORMED = of("argument.parse.failure.selector.malformed");
/** /**
* Variables: None * Variables: None
@ -66,17 +71,29 @@ public final class BukkitCaptionKeys {
public static final Caption ARGUMENT_PARSE_FAILURE_SELECTOR_UNSUPPORTED = of("argument.parse.failure.selector.unsupported"); public static final Caption ARGUMENT_PARSE_FAILURE_SELECTOR_UNSUPPORTED = of("argument.parse.failure.selector.unsupported");
/** /**
* Variables: None * Variables: None
*
* @deprecated parsing is now handled by Brigadier and will throw {@link com.mojang.brigadier.exceptions.CommandSyntaxException} instead.
*/ */
@API(status = API.Status.DEPRECATED, since = "1.8.0")
@Deprecated
public static final Caption ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_PLAYERS = of( public static final Caption ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_PLAYERS = of(
"argument.parse.failure.selector.too_many_players"); "argument.parse.failure.selector.too_many_players");
/** /**
* Variables: None * Variables: None
*
* @deprecated parsing is now handled by Brigadier and will throw {@link com.mojang.brigadier.exceptions.CommandSyntaxException} instead.
*/ */
@API(status = API.Status.DEPRECATED, since = "1.8.0")
@Deprecated
public static final Caption ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_ENTITIES = of( public static final Caption ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_ENTITIES = of(
"argument.parse.failure.selector.too_many_entities"); "argument.parse.failure.selector.too_many_entities");
/** /**
* Variables: None * Variables: None
*
* @deprecated parsing is now handled by Brigadier and will throw {@link com.mojang.brigadier.exceptions.CommandSyntaxException} instead.
*/ */
@API(status = API.Status.DEPRECATED, since = "1.8.0")
@Deprecated
public static final Caption ARGUMENT_PARSE_FAILURE_SELECTOR_NON_PLAYER = of( public static final Caption ARGUMENT_PARSE_FAILURE_SELECTOR_NON_PLAYER = of(
"argument.parse.failure.selector.non_player_in_player_selector"); "argument.parse.failure.selector.non_player_in_player_selector");
/** /**

View file

@ -24,6 +24,7 @@
package cloud.commandframework.bukkit; package cloud.commandframework.bukkit;
import cloud.commandframework.captions.SimpleCaptionRegistry; import cloud.commandframework.captions.SimpleCaptionRegistry;
import org.apiguardian.api.API;
/** /**
* Caption registry that uses bi-functions to produce messages * Caption registry that uses bi-functions to produce messages
@ -54,7 +55,11 @@ public class BukkitCaptionRegistry<C> extends SimpleCaptionRegistry<C> {
public static final String ARGUMENT_PARSE_FAILURE_WORLD = "'{input}' is not a valid Minecraft world"; public static final String ARGUMENT_PARSE_FAILURE_WORLD = "'{input}' is not a valid Minecraft world";
/** /**
* Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_SELECTOR_MALFORMED} * Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_SELECTOR_MALFORMED}
*
* @deprecated parsing is now handled by Brigadier and will throw {@link com.mojang.brigadier.exceptions.CommandSyntaxException} instead.
*/ */
@API(status = API.Status.DEPRECATED, since = "1.8.0")
@Deprecated
public static final String ARGUMENT_PARSE_FAILURE_SELECTOR_MALFORMED = "Selector '{input}' is malformed."; public static final String ARGUMENT_PARSE_FAILURE_SELECTOR_MALFORMED = "Selector '{input}' is malformed.";
/** /**
* Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_SELECTOR_UNSUPPORTED} * Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_SELECTOR_UNSUPPORTED}
@ -63,15 +68,27 @@ public class BukkitCaptionRegistry<C> extends SimpleCaptionRegistry<C> {
"Entity selector argument type not supported below Minecraft 1.13."; "Entity selector argument type not supported below Minecraft 1.13.";
/** /**
* Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_PLAYERS} * Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_PLAYERS}
*
* @deprecated parsing is now handled by Brigadier and will throw {@link com.mojang.brigadier.exceptions.CommandSyntaxException} instead.
*/ */
@API(status = API.Status.DEPRECATED, since = "1.8.0")
@Deprecated
public static final String ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_PLAYERS = "More than 1 player selected in single player selector"; public static final String ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_PLAYERS = "More than 1 player selected in single player selector";
/** /**
* Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_ENTITIES} * Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_ENTITIES}
*
* @deprecated parsing is now handled by Brigadier and will throw {@link com.mojang.brigadier.exceptions.CommandSyntaxException} instead.
*/ */
@API(status = API.Status.DEPRECATED, since = "1.8.0")
@Deprecated
public static final String ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_ENTITIES = "More than 1 entity selected in single entity selector."; public static final String ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_ENTITIES = "More than 1 entity selected in single entity selector.";
/** /**
* Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_SELECTOR_NON_PLAYER} * Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_SELECTOR_NON_PLAYER}
*
* @deprecated parsing is now handled by Brigadier and will throw {@link com.mojang.brigadier.exceptions.CommandSyntaxException} instead.
*/ */
@API(status = API.Status.DEPRECATED, since = "1.8.0")
@Deprecated
public static final String ARGUMENT_PARSE_FAILURE_SELECTOR_NON_PLAYER = "Non-player(s) selected in player selector."; public static final String ARGUMENT_PARSE_FAILURE_SELECTOR_NON_PLAYER = "Non-player(s) selected in player selector.";
/** /**
* Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_LOCATION_INVALID_FORMAT} * Default caption for {@link BukkitCaptionKeys#ARGUMENT_PARSE_FAILURE_LOCATION_INVALID_FORMAT}
@ -106,6 +123,7 @@ public class BukkitCaptionRegistry<C> extends SimpleCaptionRegistry<C> {
"Invalid input '{input}', requires an explicit namespace."; "Invalid input '{input}', requires an explicit namespace.";
@SuppressWarnings("deprecation")
protected BukkitCaptionRegistry() { protected BukkitCaptionRegistry() {
super(); super();
this.registerMessageFactory( this.registerMessageFactory(

View file

@ -29,6 +29,7 @@ import cloud.commandframework.CommandTree;
import cloud.commandframework.arguments.parser.ParserParameters; import cloud.commandframework.arguments.parser.ParserParameters;
import cloud.commandframework.brigadier.BrigadierManagerHolder; import cloud.commandframework.brigadier.BrigadierManagerHolder;
import cloud.commandframework.brigadier.CloudBrigadierManager; import cloud.commandframework.brigadier.CloudBrigadierManager;
import cloud.commandframework.bukkit.annotation.specifier.AllowEmptySelection;
import cloud.commandframework.bukkit.annotation.specifier.DefaultNamespace; import cloud.commandframework.bukkit.annotation.specifier.DefaultNamespace;
import cloud.commandframework.bukkit.annotation.specifier.RequireExplicitNamespace; import cloud.commandframework.bukkit.annotation.specifier.RequireExplicitNamespace;
import cloud.commandframework.bukkit.argument.NamespacedKeyArgument; import cloud.commandframework.bukkit.argument.NamespacedKeyArgument;
@ -157,15 +158,28 @@ public class BukkitCommandManager<C> extends CommandManager<C> implements Brigad
new Location2DArgument.Location2DParser<>()); new Location2DArgument.Location2DParser<>());
this.parserRegistry().registerParserSupplier(TypeToken.get(ProtoItemStack.class), parserParameters -> this.parserRegistry().registerParserSupplier(TypeToken.get(ProtoItemStack.class), parserParameters ->
new ItemStackArgument.Parser<>()); new ItemStackArgument.Parser<>());
/* Register Entity Selector Parsers */ /* Register Entity Selector Parsers */
this.parserRegistry().registerParserSupplier(TypeToken.get(SingleEntitySelector.class), parserParameters -> this.parserRegistry().registerParserSupplier(TypeToken.get(SingleEntitySelector.class), parserParameters ->
new SingleEntitySelectorArgument.SingleEntitySelectorParser<>()); new SingleEntitySelectorArgument.SingleEntitySelectorParser<>());
this.parserRegistry().registerParserSupplier(TypeToken.get(SinglePlayerSelector.class), parserParameters -> this.parserRegistry().registerParserSupplier(TypeToken.get(SinglePlayerSelector.class), parserParameters ->
new SinglePlayerSelectorArgument.SinglePlayerSelectorParser<>()); new SinglePlayerSelectorArgument.SinglePlayerSelectorParser<>());
this.parserRegistry().registerParserSupplier(TypeToken.get(MultipleEntitySelector.class), parserParameters -> this.parserRegistry().registerAnnotationMapper(
new MultipleEntitySelectorArgument.MultipleEntitySelectorParser<>()); AllowEmptySelection.class,
this.parserRegistry().registerParserSupplier(TypeToken.get(MultiplePlayerSelector.class), parserParameters -> (annotation, type) -> ParserParameters.single(BukkitParserParameters.ALLOW_EMPTY_SELECTOR_RESULT, annotation.value())
new MultiplePlayerSelectorArgument.MultiplePlayerSelectorParser<>()); );
this.parserRegistry().registerParserSupplier(
TypeToken.get(MultipleEntitySelector.class),
parserParameters -> new MultipleEntitySelectorArgument.MultipleEntitySelectorParser<>(
parserParameters.get(BukkitParserParameters.ALLOW_EMPTY_SELECTOR_RESULT, true)
)
);
this.parserRegistry().registerParserSupplier(
TypeToken.get(MultiplePlayerSelector.class),
parserParameters -> new MultiplePlayerSelectorArgument.MultiplePlayerSelectorParser<>(
parserParameters.get(BukkitParserParameters.ALLOW_EMPTY_SELECTOR_RESULT, true)
)
);
if (CraftBukkitReflection.classExists("org.bukkit.NamespacedKey")) { if (CraftBukkitReflection.classExists("org.bukkit.NamespacedKey")) {
this.registerParserSupplierFor(NamespacedKeyArgument.class); this.registerParserSupplierFor(NamespacedKeyArgument.class);

View file

@ -25,6 +25,7 @@ package cloud.commandframework.bukkit;
import cloud.commandframework.arguments.parser.ParserParameter; import cloud.commandframework.arguments.parser.ParserParameter;
import io.leangen.geantyref.TypeToken; import io.leangen.geantyref.TypeToken;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
/** /**
@ -32,11 +33,23 @@ import org.checkerframework.checker.nullness.qual.NonNull;
* *
* @since 1.7.0 * @since 1.7.0
*/ */
@API(status = API.Status.STABLE, since = "1.7.0")
public final class BukkitParserParameters { public final class BukkitParserParameters {
private BukkitParserParameters() { private BukkitParserParameters() {
} }
/**
* Used to specify if an empty result is allowed for
* {@link cloud.commandframework.bukkit.parsers.selector.MultiplePlayerSelectorArgument} and
* {@link cloud.commandframework.bukkit.parsers.selector.MultipleEntitySelectorArgument}.
*
* @since 1.8.0
*/
@API(status = API.Status.STABLE, since = "1.8.0")
public static final ParserParameter<Boolean> ALLOW_EMPTY_SELECTOR_RESULT =
create("allow_empty_selector_result", TypeToken.get(Boolean.class));
/** /**
* Sets to require explicit namespaces for {@link cloud.commandframework.bukkit.argument.NamespacedKeyArgument} * Sets to require explicit namespaces for {@link cloud.commandframework.bukkit.argument.NamespacedKeyArgument}
* (i.e. 'test' will be rejected but 'test:test' will pass). * (i.e. 'test' will be rejected but 'test:test' will pass).

View file

@ -0,0 +1,28 @@
package cloud.commandframework.bukkit.annotation.specifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.apiguardian.api.API;
/**
* Annotation used to specify if an empty result is allowed for
* {@link cloud.commandframework.bukkit.parsers.selector.MultiplePlayerSelectorArgument} and
* {@link cloud.commandframework.bukkit.parsers.selector.MultipleEntitySelectorArgument}.
*
* @since 1.8.0
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@API(status = API.Status.STABLE, since = "1.8.0")
public @interface AllowEmptySelection {
/**
* Whether to allow empty results.
*
* @return value
*/
boolean value() default true;
}

View file

@ -25,17 +25,12 @@ package cloud.commandframework.bukkit.parsers.selector;
import cloud.commandframework.ArgumentDescription; import cloud.commandframework.ArgumentDescription;
import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.bukkit.BukkitCommandContextKeys;
import cloud.commandframework.bukkit.CloudBukkitCapabilities;
import cloud.commandframework.bukkit.arguments.selector.MultipleEntitySelector; import cloud.commandframework.bukkit.arguments.selector.MultipleEntitySelector;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import cloud.commandframework.exceptions.parsing.NoInputProvidedException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import java.util.List; import java.util.List;
import java.util.Queue;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.bukkit.Bukkit; import org.apiguardian.api.API;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -43,6 +38,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public final class MultipleEntitySelectorArgument<C> extends CommandArgument<C, MultipleEntitySelector> { public final class MultipleEntitySelectorArgument<C> extends CommandArgument<C, MultipleEntitySelector> {
private MultipleEntitySelectorArgument( private MultipleEntitySelectorArgument(
final boolean allowEmpty,
final boolean required, final boolean required,
final @NonNull String name, final @NonNull String name,
final @NonNull String defaultValue, final @NonNull String defaultValue,
@ -50,8 +46,8 @@ public final class MultipleEntitySelectorArgument<C> extends CommandArgument<C,
@NonNull List<@NonNull String>> suggestionsProvider, @NonNull List<@NonNull String>> suggestionsProvider,
final @NonNull ArgumentDescription defaultDescription final @NonNull ArgumentDescription defaultDescription
) { ) {
super(required, name, new MultipleEntitySelectorParser<>(), defaultValue, MultipleEntitySelector.class, super(required, name, new MultipleEntitySelectorParser<>(allowEmpty), defaultValue,
suggestionsProvider, defaultDescription MultipleEntitySelector.class, suggestionsProvider, defaultDescription
); );
} }
@ -61,9 +57,25 @@ public final class MultipleEntitySelectorArgument<C> extends CommandArgument<C,
* @param name Name of the argument * @param name Name of the argument
* @param <C> Command sender type * @param <C> Command sender type
* @return Created builder * @return Created builder
* @deprecated prefer {@link #builder(String)}
*/ */
public static <C> MultipleEntitySelectorArgument.@NonNull Builder<C> newBuilder(final @NonNull String name) { @API(status = API.Status.DEPRECATED, since = "1.8.0")
return new MultipleEntitySelectorArgument.Builder<>(name); @Deprecated
public static <C> Builder<C> newBuilder(final @NonNull String name) {
return builder(name);
}
/**
* Create a new {@link Builder}.
*
* @param name argument name
* @param <C> sender type
* @return new builder
* @since 1.8.0
*/
@API(status = API.Status.STABLE, since = "1.8.0")
public static <C> @NonNull Builder<C> builder(final @NonNull String name) {
return new Builder<>(name);
} }
/** /**
@ -73,8 +85,8 @@ public final class MultipleEntitySelectorArgument<C> extends CommandArgument<C,
* @param <C> Command sender type * @param <C> Command sender type
* @return Created argument * @return Created argument
*/ */
public static <C> @NonNull CommandArgument<C, MultipleEntitySelector> of(final @NonNull String name) { public static <C> @NonNull MultipleEntitySelectorArgument<C> of(final @NonNull String name) {
return MultipleEntitySelectorArgument.<C>newBuilder(name).asRequired().build(); return MultipleEntitySelectorArgument.<C>builder(name).asRequired().build();
} }
/** /**
@ -84,8 +96,8 @@ public final class MultipleEntitySelectorArgument<C> extends CommandArgument<C,
* @param <C> Command sender type * @param <C> Command sender type
* @return Created argument * @return Created argument
*/ */
public static <C> @NonNull CommandArgument<C, MultipleEntitySelector> optional(final @NonNull String name) { public static <C> @NonNull MultipleEntitySelectorArgument<C> optional(final @NonNull String name) {
return MultipleEntitySelectorArgument.<C>newBuilder(name).asOptional().build(); return MultipleEntitySelectorArgument.<C>builder(name).asOptional().build();
} }
/** /**
@ -96,20 +108,35 @@ public final class MultipleEntitySelectorArgument<C> extends CommandArgument<C,
* @param <C> Command sender type * @param <C> Command sender type
* @return Created argument * @return Created argument
*/ */
public static <C> @NonNull CommandArgument<C, MultipleEntitySelector> optional( public static <C> @NonNull MultipleEntitySelectorArgument<C> optional(
final @NonNull String name, final @NonNull String name,
final @NonNull String defaultEntitySelector final @NonNull String defaultEntitySelector
) { ) {
return MultipleEntitySelectorArgument.<C>newBuilder(name).asOptionalWithDefault(defaultEntitySelector).build(); return MultipleEntitySelectorArgument.<C>builder(name).asOptionalWithDefault(defaultEntitySelector).build();
} }
public static final class Builder<C> extends CommandArgument.Builder<C, MultipleEntitySelector> { public static final class Builder<C> extends CommandArgument.TypedBuilder<C, MultipleEntitySelector, Builder<C>> {
private boolean allowEmpty = true;
private Builder(final @NonNull String name) { private Builder(final @NonNull String name) {
super(MultipleEntitySelector.class, name); super(MultipleEntitySelector.class, name);
} }
/**
* Set whether to allow empty results.
*
* @param allowEmpty whether to allow empty results
* @return builder instance
* @since 1.8.0
*/
@API(status = API.Status.STABLE, since = "1.8.0")
public @NonNull Builder<C> allowEmpty(final boolean allowEmpty) {
this.allowEmpty = allowEmpty;
return this;
}
/** /**
* Builder a new argument * Builder a new argument
* *
@ -117,51 +144,51 @@ public final class MultipleEntitySelectorArgument<C> extends CommandArgument<C,
*/ */
@Override @Override
public @NonNull MultipleEntitySelectorArgument<C> build() { public @NonNull MultipleEntitySelectorArgument<C> build() {
return new MultipleEntitySelectorArgument<>(this.isRequired(), this.getName(), this.getDefaultValue(), return new MultipleEntitySelectorArgument<>(
this.getSuggestionsProvider(), this.getDefaultDescription() this.allowEmpty,
this.isRequired(),
this.getName(),
this.getDefaultValue(),
this.getSuggestionsProvider(),
this.getDefaultDescription()
); );
} }
} }
public static final class MultipleEntitySelectorParser<C> implements ArgumentParser<C, MultipleEntitySelector> { public static final class MultipleEntitySelectorParser<C> extends SelectorUtils.EntitySelectorParser<C, MultipleEntitySelector> {
private final boolean allowEmpty;
/**
* Creates a new {@link MultipleEntitySelectorParser}.
*
* @param allowEmpty Whether to allow an empty result
* @since 1.8.0
*/
@API(status = API.Status.STABLE, since = "1.8.0")
public MultipleEntitySelectorParser(final boolean allowEmpty) {
super(false);
this.allowEmpty = allowEmpty;
}
/**
* Creates a new {@link MultipleEntitySelectorParser}.
*/
public MultipleEntitySelectorParser() {
this(true);
}
@Override @Override
public @NonNull ArgumentParseResult<MultipleEntitySelector> parse( public MultipleEntitySelector mapResult(
final @NonNull CommandContext<C> commandContext, final @NonNull String input,
final @NonNull Queue<@NonNull String> inputQueue final SelectorUtils.@NonNull EntitySelectorWrapper wrapper
) { ) throws Exception {
if (!commandContext.get(BukkitCommandContextKeys.CLOUD_BUKKIT_CAPABILITIES).contains( final List<Entity> entities = wrapper.entities();
CloudBukkitCapabilities.BRIGADIER)) { if (entities.isEmpty() && !this.allowEmpty) {
return ArgumentParseResult.failure(new SelectorParseException( throw ((SimpleCommandExceptionType) NO_ENTITIES_EXCEPTION_TYPE.get()).create();
"",
commandContext,
SelectorParseException.FailureReason.UNSUPPORTED_VERSION,
MultipleEntitySelectorParser.class
));
} }
final String input = inputQueue.peek(); return new MultipleEntitySelector(input, entities);
if (input == null) {
return ArgumentParseResult.failure(new NoInputProvidedException(
MultipleEntitySelectorParser.class,
commandContext
));
}
List<Entity> entities;
try {
entities = Bukkit.selectEntities(commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER), input);
} catch (IllegalArgumentException e) {
return ArgumentParseResult.failure(new SelectorParseException(
input,
commandContext,
SelectorParseException.FailureReason.MALFORMED_SELECTOR,
MultipleEntitySelectorParser.class
));
}
inputQueue.remove();
return ArgumentParseResult.success(new MultipleEntitySelector(input, entities));
} }
} }
} }

View file

@ -26,21 +26,17 @@ package cloud.commandframework.bukkit.parsers.selector;
import cloud.commandframework.ArgumentDescription; import cloud.commandframework.ArgumentDescription;
import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.bukkit.BukkitCommandContextKeys;
import cloud.commandframework.bukkit.CloudBukkitCapabilities;
import cloud.commandframework.bukkit.arguments.selector.MultiplePlayerSelector; import cloud.commandframework.bukkit.arguments.selector.MultiplePlayerSelector;
import cloud.commandframework.bukkit.parsers.PlayerArgument; import cloud.commandframework.bukkit.parsers.PlayerArgument;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import cloud.commandframework.exceptions.parsing.NoInputProvidedException;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.apiguardian.api.API;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -48,6 +44,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public final class MultiplePlayerSelectorArgument<C> extends CommandArgument<C, MultiplePlayerSelector> { public final class MultiplePlayerSelectorArgument<C> extends CommandArgument<C, MultiplePlayerSelector> {
private MultiplePlayerSelectorArgument( private MultiplePlayerSelectorArgument(
final boolean allowEmpty,
final boolean required, final boolean required,
final @NonNull String name, final @NonNull String name,
final @NonNull String defaultValue, final @NonNull String defaultValue,
@ -55,7 +52,7 @@ public final class MultiplePlayerSelectorArgument<C> extends CommandArgument<C,
@NonNull List<@NonNull String>> suggestionsProvider, @NonNull List<@NonNull String>> suggestionsProvider,
final @NonNull ArgumentDescription defaultDescription final @NonNull ArgumentDescription defaultDescription
) { ) {
super(required, name, new MultiplePlayerSelectorParser<>(), defaultValue, MultiplePlayerSelector.class, super(required, name, new MultiplePlayerSelectorParser<>(allowEmpty), defaultValue, MultiplePlayerSelector.class,
suggestionsProvider, defaultDescription suggestionsProvider, defaultDescription
); );
} }
@ -66,9 +63,25 @@ public final class MultiplePlayerSelectorArgument<C> extends CommandArgument<C,
* @param name Name of the argument * @param name Name of the argument
* @param <C> Command sender type * @param <C> Command sender type
* @return Created builder * @return Created builder
* @deprecated prefer {@link #builder(String)}
*/ */
public static <C> MultiplePlayerSelectorArgument.Builder<C> newBuilder(final @NonNull String name) { @API(status = API.Status.DEPRECATED, since = "1.8.0")
return new MultiplePlayerSelectorArgument.Builder<>(name); @Deprecated
public static <C> Builder<C> newBuilder(final @NonNull String name) {
return builder(name);
}
/**
* Create a new {@link Builder}.
*
* @param name argument name
* @param <C> sender type
* @return new builder
* @since 1.8.0
*/
@API(status = API.Status.STABLE, since = "1.8.0")
public static <C> @NonNull Builder<C> builder(final @NonNull String name) {
return new Builder<>(name);
} }
/** /**
@ -78,8 +91,8 @@ public final class MultiplePlayerSelectorArgument<C> extends CommandArgument<C,
* @param <C> Command sender type * @param <C> Command sender type
* @return Created argument * @return Created argument
*/ */
public static <C> @NonNull CommandArgument<C, MultiplePlayerSelector> of(final @NonNull String name) { public static <C> @NonNull MultiplePlayerSelectorArgument<C> of(final @NonNull String name) {
return MultiplePlayerSelectorArgument.<C>newBuilder(name).asRequired().build(); return MultiplePlayerSelectorArgument.<C>builder(name).asRequired().build();
} }
/** /**
@ -89,8 +102,8 @@ public final class MultiplePlayerSelectorArgument<C> extends CommandArgument<C,
* @param <C> Command sender type * @param <C> Command sender type
* @return Created argument * @return Created argument
*/ */
public static <C> @NonNull CommandArgument<C, MultiplePlayerSelector> optional(final @NonNull String name) { public static <C> @NonNull MultiplePlayerSelectorArgument<C> optional(final @NonNull String name) {
return MultiplePlayerSelectorArgument.<C>newBuilder(name).asOptional().build(); return MultiplePlayerSelectorArgument.<C>builder(name).asOptional().build();
} }
/** /**
@ -101,20 +114,35 @@ public final class MultiplePlayerSelectorArgument<C> extends CommandArgument<C,
* @param <C> Command sender type * @param <C> Command sender type
* @return Created argument * @return Created argument
*/ */
public static <C> @NonNull CommandArgument<C, MultiplePlayerSelector> optional( public static <C> @NonNull MultiplePlayerSelectorArgument<C> optional(
final @NonNull String name, final @NonNull String name,
final @NonNull String defaultEntitySelector final @NonNull String defaultEntitySelector
) { ) {
return MultiplePlayerSelectorArgument.<C>newBuilder(name).asOptionalWithDefault(defaultEntitySelector).build(); return MultiplePlayerSelectorArgument.<C>builder(name).asOptionalWithDefault(defaultEntitySelector).build();
} }
public static final class Builder<C> extends CommandArgument.Builder<C, MultiplePlayerSelector> { public static final class Builder<C> extends CommandArgument.TypedBuilder<C, MultiplePlayerSelector, Builder<C>> {
private boolean allowEmpty = true;
private Builder(final @NonNull String name) { private Builder(final @NonNull String name) {
super(MultiplePlayerSelector.class, name); super(MultiplePlayerSelector.class, name);
} }
/**
* Set whether to allow empty results.
*
* @param allowEmpty whether to allow empty results
* @return builder instance
* @since 1.8.0
*/
@API(status = API.Status.STABLE, since = "1.8.0")
public @NonNull Builder<C> allowEmpty(final boolean allowEmpty) {
this.allowEmpty = allowEmpty;
return this;
}
/** /**
* Builder a new argument * Builder a new argument
* *
@ -122,32 +150,60 @@ public final class MultiplePlayerSelectorArgument<C> extends CommandArgument<C,
*/ */
@Override @Override
public @NonNull MultiplePlayerSelectorArgument<C> build() { public @NonNull MultiplePlayerSelectorArgument<C> build() {
return new MultiplePlayerSelectorArgument<>(this.isRequired(), this.getName(), this.getDefaultValue(), return new MultiplePlayerSelectorArgument<>(
this.getSuggestionsProvider(), this.getDefaultDescription() this.allowEmpty,
this.isRequired(),
this.getName(),
this.getDefaultValue(),
this.getSuggestionsProvider(),
this.getDefaultDescription()
); );
} }
} }
public static final class MultiplePlayerSelectorParser<C> implements ArgumentParser<C, MultiplePlayerSelector> { public static final class MultiplePlayerSelectorParser<C> extends SelectorUtils.PlayerSelectorParser<C, MultiplePlayerSelector> {
private final boolean allowEmpty;
/**
* Creates a new {@link MultiplePlayerSelectorParser}.
*
* @param allowEmpty Whether to allow an empty result
* @since 1.8.0
*/
@API(status = API.Status.STABLE, since = "1.8.0")
public MultiplePlayerSelectorParser(final boolean allowEmpty) {
super(false);
this.allowEmpty = allowEmpty;
}
/**
* Creates a new {@link MultiplePlayerSelectorParser}.
*/
public MultiplePlayerSelectorParser() {
this(true);
}
@Override @Override
public @NonNull ArgumentParseResult<MultiplePlayerSelector> parse( public MultiplePlayerSelector mapResult(
final @NonNull String input,
final SelectorUtils.@NonNull EntitySelectorWrapper wrapper
) throws Exception {
final List<Player> players = wrapper.players();
if (players.isEmpty() && !this.allowEmpty) {
throw ((SimpleCommandExceptionType) NO_PLAYERS_EXCEPTION_TYPE.get()).create();
}
return new MultiplePlayerSelector(input, new ArrayList<>(players));
}
@Override
protected @NonNull ArgumentParseResult<MultiplePlayerSelector> legacyParse(
final @NonNull CommandContext<C> commandContext, final @NonNull CommandContext<C> commandContext,
final @NonNull Queue<@NonNull String> inputQueue final @NonNull Queue<@NonNull String> inputQueue
) { ) {
final String input = inputQueue.peek(); final String input = inputQueue.peek();
if (input == null) { @SuppressWarnings("deprecation") final @Nullable Player player = Bukkit.getPlayer(input);
return ArgumentParseResult.failure(new NoInputProvidedException(
MultiplePlayerSelectorParser.class,
commandContext
));
}
if (!commandContext.get(BukkitCommandContextKeys.CLOUD_BUKKIT_CAPABILITIES).contains(
CloudBukkitCapabilities.BRIGADIER)) {
@SuppressWarnings("deprecation")
Player player = Bukkit.getPlayer(input);
if (player == null) { if (player == null) {
return ArgumentParseResult.failure(new PlayerArgument.PlayerParseException(input, commandContext)); return ArgumentParseResult.failure(new PlayerArgument.PlayerParseException(input, commandContext));
@ -155,50 +211,5 @@ public final class MultiplePlayerSelectorArgument<C> extends CommandArgument<C,
inputQueue.remove(); inputQueue.remove();
return ArgumentParseResult.success(new MultiplePlayerSelector(input, ImmutableList.of(player))); return ArgumentParseResult.success(new MultiplePlayerSelector(input, ImmutableList.of(player)));
} }
List<Entity> entities;
try {
entities = Bukkit.selectEntities(commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER), input);
} catch (IllegalArgumentException e) {
return ArgumentParseResult.failure(new SelectorParseException(
input,
commandContext,
SelectorParseException.FailureReason.MALFORMED_SELECTOR,
MultiplePlayerSelectorParser.class
));
}
for (Entity e : entities) {
if (!(e instanceof Player)) {
return ArgumentParseResult.failure(new SelectorParseException(
input,
commandContext,
SelectorParseException.FailureReason.NON_PLAYER_IN_PLAYER_SELECTOR,
MultiplePlayerSelectorParser.class
));
}
}
inputQueue.remove();
return ArgumentParseResult.success(new MultiplePlayerSelector(input, entities));
}
@Override
public @NonNull List<@NonNull String> suggestions(
final @NonNull CommandContext<C> commandContext,
final @NonNull String input
) {
List<String> output = new ArrayList<>();
for (Player player : Bukkit.getOnlinePlayers()) {
final CommandSender bukkit = commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER);
if (bukkit instanceof Player && !((Player) bukkit).canSee(player)) {
continue;
}
output.add(player.getName());
}
return output;
}
} }
} }

View file

@ -28,6 +28,7 @@ import cloud.commandframework.captions.Caption;
import cloud.commandframework.captions.CaptionVariable; import cloud.commandframework.captions.CaptionVariable;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import cloud.commandframework.exceptions.parsing.ParserException; import cloud.commandframework.exceptions.parsing.ParserException;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
/** /**
@ -73,7 +74,10 @@ public final class SelectorParseException extends ParserException {
} }
/** /**
* Get the reason of failure for the selector parser * Get the reason of failure for the selector parser.
*
* <p>Note: The only type currently used is {@link FailureReason#UNSUPPORTED_VERSION}, other exceptions
* are now handled by Brigadier in the form of {@link com.mojang.brigadier.exceptions.CommandSyntaxException}.</p>
* *
* @return Failure reason * @return Failure reason
* @since 1.2.0 * @since 1.2.0
@ -90,9 +94,29 @@ public final class SelectorParseException extends ParserException {
public enum FailureReason { public enum FailureReason {
UNSUPPORTED_VERSION(BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_SELECTOR_UNSUPPORTED), UNSUPPORTED_VERSION(BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_SELECTOR_UNSUPPORTED),
/**
* @deprecated parsing is now handled by Brigadier and will throw {@link com.mojang.brigadier.exceptions.CommandSyntaxException} instead.
*/
@API(status = API.Status.DEPRECATED, since = "1.8.0")
@Deprecated
MALFORMED_SELECTOR(BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_SELECTOR_MALFORMED), MALFORMED_SELECTOR(BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_SELECTOR_MALFORMED),
/**
* @deprecated parsing is now handled by Brigadier and will throw {@link com.mojang.brigadier.exceptions.CommandSyntaxException} instead.
*/
@API(status = API.Status.DEPRECATED, since = "1.8.0")
@Deprecated
TOO_MANY_PLAYERS(BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_PLAYERS), TOO_MANY_PLAYERS(BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_PLAYERS),
/**
* @deprecated parsing is now handled by Brigadier and will throw {@link com.mojang.brigadier.exceptions.CommandSyntaxException} instead.
*/
@API(status = API.Status.DEPRECATED, since = "1.8.0")
@Deprecated
TOO_MANY_ENTITIES(BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_ENTITIES), TOO_MANY_ENTITIES(BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_SELECTOR_TOO_MANY_ENTITIES),
/**
* @deprecated parsing is now handled by Brigadier and will throw {@link com.mojang.brigadier.exceptions.CommandSyntaxException} instead.
*/
@API(status = API.Status.DEPRECATED, since = "1.8.0")
@Deprecated
NON_PLAYER_IN_PLAYER_SELECTOR(BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_SELECTOR_NON_PLAYER); NON_PLAYER_IN_PLAYER_SELECTOR(BukkitCaptionKeys.ARGUMENT_PARSE_FAILURE_SELECTOR_NON_PLAYER);

View file

@ -0,0 +1,472 @@
//
// MIT License
//
// Copyright (c) 2021 Alexander Söderberg & Contributors
//
// 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 cloud.commandframework.bukkit.parsers.selector;
import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.brigadier.argument.WrappedBrigadierParser;
import cloud.commandframework.bukkit.BukkitCommandContextKeys;
import cloud.commandframework.bukkit.internal.CraftBukkitReflection;
import cloud.commandframework.bukkit.internal.MinecraftArgumentTypes;
import cloud.commandframework.context.CommandContext;
import com.google.common.base.Suppliers;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import io.leangen.geantyref.GenericTypeReflector;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;
@DefaultQualifier(NonNull.class)
final class SelectorUtils {
private SelectorUtils() {
}
private static <C, T> @Nullable ArgumentParser<C, T> createModernParser(
final boolean single,
final boolean playersOnly,
final SelectorMapper<T> mapper
) {
if (CraftBukkitReflection.MAJOR_REVISION < 13) {
return null;
}
final ArgumentParser<C, Object> wrappedBrigParser = new WrappedBrigadierParser<>(
() -> createEntityArgument(single, playersOnly),
ArgumentParser.DEFAULT_ARGUMENT_COUNT,
EntityArgumentParseFunction.INSTANCE
);
return new ModernSelectorParser<>(wrappedBrigParser, mapper);
}
@SuppressWarnings("unchecked")
private static ArgumentType<Object> createEntityArgument(final boolean single, final boolean playersOnly) {
final Constructor<?> constructor =
MinecraftArgumentTypes.getClassByKey(NamespacedKey.minecraft("entity")).getDeclaredConstructors()[0];
constructor.setAccessible(true);
try {
return (ArgumentType<Object>) constructor.newInstance(single, playersOnly);
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
private static final class EntityArgumentParseFunction implements WrappedBrigadierParser.ParseFunction<Object> {
static final EntityArgumentParseFunction INSTANCE = new EntityArgumentParseFunction();
@Override
public Object apply(
final ArgumentType<Object> type,
final StringReader reader
) throws CommandSyntaxException {
final @Nullable Method specialParse = CraftBukkitReflection.findMethod(
type.getClass(),
"parse",
StringReader.class,
boolean.class
);
if (specialParse == null) {
return type.parse(reader);
}
try {
return specialParse.invoke(
type,
reader,
true // CraftBukkit overridePermissions param
);
} catch (final InvocationTargetException ex) {
final Throwable cause = ex.getCause();
if (cause instanceof CommandSyntaxException) {
throw (CommandSyntaxException) cause;
}
throw new RuntimeException(ex);
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
}
private abstract static class SelectorParser<C, T> implements ArgumentParser<C, T>, SelectorMapper<T> {
protected static final Supplier<Object> NO_PLAYERS_EXCEPTION_TYPE =
Suppliers.memoize(() -> findExceptionType("argument.entity.notfound.player"));
protected static final Supplier<Object> NO_ENTITIES_EXCEPTION_TYPE =
Suppliers.memoize(() -> findExceptionType("argument.entity.notfound.entity"));
private final @Nullable ArgumentParser<C, T> modernParser;
protected SelectorParser(
final boolean single,
final boolean playersOnly
) {
this.modernParser = createModernParser(single, playersOnly, this);
}
protected ArgumentParseResult<T> legacyParse(
final CommandContext<C> commandContext,
final Queue<String> inputQueue
) {
return ArgumentParseResult.failure(new SelectorParseException(
"",
commandContext,
SelectorParseException.FailureReason.UNSUPPORTED_VERSION,
this.getClass()
));
}
protected List<String> legacySuggestions(
final CommandContext<C> commandContext,
final String input
) {
return Collections.emptyList();
}
@Override
public ArgumentParseResult<T> parse(
final CommandContext<C> commandContext,
final Queue<String> inputQueue
) {
if (this.modernParser != null) {
return this.modernParser.parse(commandContext, inputQueue);
}
return this.legacyParse(commandContext, inputQueue);
}
@Override
public List<String> suggestions(
final CommandContext<C> commandContext,
final String input
) {
if (this.modernParser != null) {
return this.modernParser.suggestions(commandContext, input);
}
return this.legacySuggestions(commandContext, input);
}
// returns SimpleCommandExceptionType, does not reference in signature for ABI with pre-1.13
private static Object findExceptionType(final String type) {
final Field[] fields = MinecraftArgumentTypes.getClassByKey(NamespacedKey.minecraft("entity")).getDeclaredFields();
return Arrays.stream(fields)
.filter(field -> Modifier.isStatic(field.getModifiers()) && field.getType() == SimpleCommandExceptionType.class)
.map(field -> {
try {
final @Nullable Object fieldValue = field.get(null);
if (fieldValue == null) {
return null;
}
final Field messageField = SimpleCommandExceptionType.class.getDeclaredField("message");
messageField.setAccessible(true);
if (messageField.get(fieldValue).toString().contains(type)) {
return fieldValue;
}
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
return null;
})
.filter(Objects::nonNull)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Could not find exception type '" + type + "'"));
}
}
abstract static class EntitySelectorParser<C, T> extends SelectorParser<C, T> {
protected EntitySelectorParser(final boolean single) {
super(single, false);
}
}
abstract static class PlayerSelectorParser<C, T> extends SelectorParser<C, T> {
protected PlayerSelectorParser(final boolean single) {
super(single, true);
}
@Override
protected List<String> legacySuggestions(
final CommandContext<C> commandContext,
final String input
) {
final List<String> suggestions = new ArrayList<>();
for (final Player player : Bukkit.getOnlinePlayers()) {
final CommandSender bukkit = commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER);
if (bukkit instanceof Player && !((Player) bukkit).canSee(player)) {
continue;
}
suggestions.add(player.getName());
}
return suggestions;
}
}
private static class ModernSelectorParser<C, T> implements ArgumentParser<C, T> {
private final ArgumentParser<C, Object> wrappedBrigadierParser;
private final SelectorMapper<T> mapper;
ModernSelectorParser(
final ArgumentParser<C, Object> wrapperBrigParser,
final SelectorMapper<T> mapper
) {
this.wrappedBrigadierParser = wrapperBrigParser;
this.mapper = mapper;
}
@Override
public ArgumentParseResult<T> parse(
final CommandContext<C> commandContext,
final Queue<String> inputQueue
) {
final List<String> originalInputQueue = new ArrayList<>(inputQueue);
final ArgumentParseResult<Object> result = this.wrappedBrigadierParser.parse(commandContext, inputQueue);
if (result.getFailure().isPresent()) {
return ArgumentParseResult.failure(result.getFailure().get());
} else if (result.getParsedValue().isPresent()) {
try {
final int consumed = originalInputQueue.size() - inputQueue.size();
final String input = String.join(" ", originalInputQueue.subList(0, consumed));
return ArgumentParseResult.success(this.mapper.mapResult(
input,
new EntitySelectorWrapper(commandContext, result.getParsedValue().get())
));
} catch (final CommandSyntaxException ex) {
inputQueue.clear();
inputQueue.addAll(originalInputQueue);
return ArgumentParseResult.failure(ex);
} catch (final Exception ex) {
throw rethrow(ex);
}
}
throw new IllegalStateException();
}
@Override
public List<String> suggestions(
final CommandContext<C> commandContext,
final String input
) {
return this.wrappedBrigadierParser.suggestions(commandContext, input);
}
}
static final class EntitySelectorWrapper {
private static volatile @Nullable Methods methods;
private final CommandContext<?> commandContext;
private final Object selector;
private static final class Methods {
private Method getBukkitEntity;
private Method entity;
private Method player;
private Method entities;
private Method players;
Methods(final CommandContext<?> commandContext, final Object selector) {
final Object nativeSender = commandContext.get(WrappedBrigadierParser.COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER);
final Class<?> nativeSenderClass = nativeSender.getClass();
for (final Method method : selector.getClass().getDeclaredMethods()) {
if (method.getParameterCount() != 1 || !method.getParameterTypes()[0].equals(nativeSenderClass)) {
continue;
}
final Class<?> returnType = method.getReturnType();
if (List.class.isAssignableFrom(returnType)) {
final ParameterizedType stringListType = (ParameterizedType) method.getGenericReturnType();
Type listType = stringListType.getActualTypeArguments()[0];
while (listType instanceof WildcardType) {
listType = ((WildcardType) listType).getUpperBounds()[0];
}
final Class<?> clazz = listType instanceof Class
? (Class<?>) listType
: GenericTypeReflector.erase(listType);
final @Nullable Method getBukkitEntity = findGetBukkitEntityMethod(clazz);
if (getBukkitEntity == null) {
continue;
}
final Class<?> bukkitType = getBukkitEntity.getReturnType();
if (Player.class.isAssignableFrom(bukkitType)) {
if (this.players != null) {
throw new IllegalStateException();
}
this.players = method;
} else {
if (this.entities != null) {
throw new IllegalStateException();
}
this.entities = method;
}
} else if (returnType != Void.TYPE) {
final @Nullable Method getBukkitEntity = findGetBukkitEntityMethod(returnType);
if (getBukkitEntity == null) {
continue;
}
final Class<?> bukkitType = getBukkitEntity.getReturnType();
if (Player.class.isAssignableFrom(bukkitType)) {
if (this.player != null) {
throw new IllegalStateException();
}
this.player = method;
} else {
if (this.entity != null || this.getBukkitEntity != null) {
throw new IllegalStateException();
}
this.entity = method;
this.getBukkitEntity = getBukkitEntity;
}
}
}
Objects.requireNonNull(this.getBukkitEntity);
Objects.requireNonNull(this.player);
Objects.requireNonNull(this.entity);
Objects.requireNonNull(this.players);
Objects.requireNonNull(this.entities);
}
private static @Nullable Method findGetBukkitEntityMethod(final Class<?> returnType) {
@Nullable Method getBukkitEntity;
try {
getBukkitEntity = returnType.getDeclaredMethod("getBukkitEntity");
} catch (final ReflectiveOperationException ex) {
try {
getBukkitEntity = returnType.getMethod("getBukkitEntity");
} catch (final ReflectiveOperationException ex0) {
getBukkitEntity = null;
}
}
return getBukkitEntity;
}
}
EntitySelectorWrapper(
final CommandContext<?> commandContext,
final Object selector
) {
this.commandContext = commandContext;
this.selector = selector;
}
private static Methods methods(final CommandContext<?> commandContext, final Object selector) {
if (methods == null) {
synchronized (Methods.class) {
if (methods == null) {
methods = new Methods(commandContext, selector);
}
}
}
return methods;
}
private Methods methods() {
return methods(this.commandContext, this.selector);
}
Entity singleEntity() {
return reflectiveOperation(() -> (Entity) this.methods().getBukkitEntity.invoke(this.methods().entity.invoke(
this.selector,
this.commandContext.<Object>get(WrappedBrigadierParser.COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER)
)));
}
Player singlePlayer() {
return reflectiveOperation(() -> (Player) this.methods().getBukkitEntity.invoke(this.methods().player.invoke(
this.selector,
this.commandContext.<Object>get(WrappedBrigadierParser.COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER)
)));
}
@SuppressWarnings("unchecked")
List<Entity> entities() {
return reflectiveOperation(() -> ((List<Object>) this.methods().entities.invoke(
this.selector,
this.commandContext.<Object>get(WrappedBrigadierParser.COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER)
))
.stream()
.map(o -> reflectiveOperation(() -> (Entity) this.methods().getBukkitEntity.invoke(o)))
.collect(Collectors.toList()));
}
@SuppressWarnings("unchecked")
List<Player> players() {
return reflectiveOperation(() -> ((List<Object>) this.methods().players.invoke(
this.selector,
this.commandContext.<Object>get(WrappedBrigadierParser.COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER)
))
.stream()
.map(o -> reflectiveOperation(() -> (Player) this.methods().getBukkitEntity.invoke(o)))
.collect(Collectors.toList()));
}
@FunctionalInterface
interface ReflectiveOperation<T> {
T run() throws ReflectiveOperationException;
}
private static <T> T reflectiveOperation(final ReflectiveOperation<T> op) {
try {
return op.run();
} catch (final InvocationTargetException ex) {
if (ex.getCause() instanceof CommandSyntaxException) {
throw rethrow(ex.getCause());
}
throw new RuntimeException(ex);
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
}
@FunctionalInterface
interface SelectorMapper<T> {
T mapResult(String input, EntitySelectorWrapper wrapper) throws Exception; // throws CommandSyntaxException
}
@SuppressWarnings("unchecked")
private static <X extends Throwable> RuntimeException rethrow(final Throwable t) throws X {
throw (X) t;
}
}

View file

@ -25,18 +25,12 @@ package cloud.commandframework.bukkit.parsers.selector;
import cloud.commandframework.ArgumentDescription; import cloud.commandframework.ArgumentDescription;
import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.bukkit.BukkitCommandContextKeys;
import cloud.commandframework.bukkit.CloudBukkitCapabilities;
import cloud.commandframework.bukkit.arguments.selector.SingleEntitySelector; import cloud.commandframework.bukkit.arguments.selector.SingleEntitySelector;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import cloud.commandframework.exceptions.parsing.NoInputProvidedException; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Queue;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.bukkit.Bukkit; import org.apiguardian.api.API;
import org.bukkit.entity.Entity;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -67,9 +61,25 @@ public final class SingleEntitySelectorArgument<C> extends CommandArgument<C, Si
* @param name Name of the argument * @param name Name of the argument
* @param <C> Command sender type * @param <C> Command sender type
* @return Created builder * @return Created builder
* @deprecated prefer {@link #builder(String)}
*/ */
public static <C> SingleEntitySelectorArgument.@NonNull Builder<C> newBuilder(final @NonNull String name) { @API(status = API.Status.DEPRECATED, since = "1.8.0")
return new SingleEntitySelectorArgument.Builder<>(name); @Deprecated
public static <C> @NonNull Builder<C> newBuilder(final @NonNull String name) {
return builder(name);
}
/**
* Create a new {@link Builder}.
*
* @param name argument name
* @param <C> sender type
* @return new builder
* @since 1.8.0
*/
@API(status = API.Status.STABLE, since = "1.8.0")
public static <C> @NonNull Builder<C> builder(final @NonNull String name) {
return new Builder<>(name);
} }
/** /**
@ -79,8 +89,8 @@ public final class SingleEntitySelectorArgument<C> extends CommandArgument<C, Si
* @param <C> Command sender type * @param <C> Command sender type
* @return Created argument * @return Created argument
*/ */
public static <C> @NonNull CommandArgument<C, SingleEntitySelector> of(final @NonNull String name) { public static <C> @NonNull SingleEntitySelectorArgument<C> of(final @NonNull String name) {
return SingleEntitySelectorArgument.<C>newBuilder(name).asRequired().build(); return SingleEntitySelectorArgument.<C>builder(name).asRequired().build();
} }
/** /**
@ -90,8 +100,8 @@ public final class SingleEntitySelectorArgument<C> extends CommandArgument<C, Si
* @param <C> Command sender type * @param <C> Command sender type
* @return Created argument * @return Created argument
*/ */
public static <C> @NonNull CommandArgument<C, SingleEntitySelector> optional(final @NonNull String name) { public static <C> @NonNull SingleEntitySelectorArgument<C> optional(final @NonNull String name) {
return SingleEntitySelectorArgument.<C>newBuilder(name).asOptional().build(); return SingleEntitySelectorArgument.<C>builder(name).asOptional().build();
} }
/** /**
@ -102,15 +112,15 @@ public final class SingleEntitySelectorArgument<C> extends CommandArgument<C, Si
* @param <C> Command sender type * @param <C> Command sender type
* @return Created argument * @return Created argument
*/ */
public static <C> @NonNull CommandArgument<C, SingleEntitySelector> optional( public static <C> @NonNull SingleEntitySelectorArgument<C> optional(
final @NonNull String name, final @NonNull String name,
final @NonNull String defaultEntitySelector final @NonNull String defaultEntitySelector
) { ) {
return SingleEntitySelectorArgument.<C>newBuilder(name).asOptionalWithDefault(defaultEntitySelector).build(); return SingleEntitySelectorArgument.<C>builder(name).asOptionalWithDefault(defaultEntitySelector).build();
} }
public static final class Builder<C> extends CommandArgument.Builder<C, SingleEntitySelector> { public static final class Builder<C> extends CommandArgument.TypedBuilder<C, SingleEntitySelector, Builder<C>> {
private Builder(final @NonNull String name) { private Builder(final @NonNull String name) {
super(SingleEntitySelector.class, name); super(SingleEntitySelector.class, name);
@ -123,60 +133,32 @@ public final class SingleEntitySelectorArgument<C> extends CommandArgument<C, Si
*/ */
@Override @Override
public @NonNull SingleEntitySelectorArgument<C> build() { public @NonNull SingleEntitySelectorArgument<C> build() {
return new SingleEntitySelectorArgument<>(this.isRequired(), this.getName(), this.getDefaultValue(), return new SingleEntitySelectorArgument<>(
this.getSuggestionsProvider(), this.getDefaultDescription() this.isRequired(),
this.getName(),
this.getDefaultValue(),
this.getSuggestionsProvider(),
this.getDefaultDescription()
); );
} }
} }
public static final class SingleEntitySelectorParser<C> implements ArgumentParser<C, SingleEntitySelector> { public static final class SingleEntitySelectorParser<C> extends SelectorUtils.EntitySelectorParser<C, SingleEntitySelector> {
/**
* Creates a new {@link SingleEntitySelectorParser}.
*/
public SingleEntitySelectorParser() {
super(true);
}
@Override @Override
public @NonNull ArgumentParseResult<SingleEntitySelector> parse( public SingleEntitySelector mapResult(
final @NonNull CommandContext<C> commandContext, final @NonNull String input,
final @NonNull Queue<@NonNull String> inputQueue final SelectorUtils.@NonNull EntitySelectorWrapper wrapper
) { ) {
if (!commandContext.get(BukkitCommandContextKeys.CLOUD_BUKKIT_CAPABILITIES).contains( return new SingleEntitySelector(input, Collections.singletonList(wrapper.singleEntity()));
CloudBukkitCapabilities.BRIGADIER)) {
return ArgumentParseResult.failure(new SelectorParseException(
"",
commandContext,
SelectorParseException.FailureReason.UNSUPPORTED_VERSION,
SingleEntitySelectorParser.class
));
}
final String input = inputQueue.peek();
if (input == null) {
return ArgumentParseResult.failure(new NoInputProvidedException(
SingleEntitySelectorParser.class,
commandContext
));
}
List<Entity> entities;
try {
entities = Bukkit.selectEntities(commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER), input);
} catch (IllegalArgumentException e) {
return ArgumentParseResult.failure(new SelectorParseException(
input,
commandContext,
SelectorParseException.FailureReason.MALFORMED_SELECTOR,
SingleEntitySelectorParser.class
));
}
if (entities.size() > 1) {
return ArgumentParseResult.failure(new SelectorParseException(
input,
commandContext,
SelectorParseException.FailureReason.TOO_MANY_ENTITIES,
SingleEntitySelectorParser.class
));
}
inputQueue.remove();
return ArgumentParseResult.success(new SingleEntitySelector(input, entities));
} }
} }
} }

View file

@ -26,21 +26,16 @@ package cloud.commandframework.bukkit.parsers.selector;
import cloud.commandframework.ArgumentDescription; import cloud.commandframework.ArgumentDescription;
import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.bukkit.BukkitCommandContextKeys;
import cloud.commandframework.bukkit.CloudBukkitCapabilities;
import cloud.commandframework.bukkit.arguments.selector.SinglePlayerSelector; import cloud.commandframework.bukkit.arguments.selector.SinglePlayerSelector;
import cloud.commandframework.bukkit.parsers.PlayerArgument; import cloud.commandframework.bukkit.parsers.PlayerArgument;
import cloud.commandframework.context.CommandContext; import cloud.commandframework.context.CommandContext;
import cloud.commandframework.exceptions.parsing.NoInputProvidedException;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.ArrayList; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.apiguardian.api.API;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -72,9 +67,25 @@ public final class SinglePlayerSelectorArgument<C> extends CommandArgument<C, Si
* @param name Name of the argument * @param name Name of the argument
* @param <C> Command sender type * @param <C> Command sender type
* @return Created builder * @return Created builder
* @deprecated prefer {@link #builder(String)}
*/ */
public static <C> SinglePlayerSelectorArgument.@NonNull Builder<C> newBuilder(final @NonNull String name) { @API(status = API.Status.DEPRECATED, since = "1.8.0")
return new SinglePlayerSelectorArgument.Builder<>(name); @Deprecated
public static <C> @NonNull Builder<C> newBuilder(final @NonNull String name) {
return builder(name);
}
/**
* Create a new {@link Builder}.
*
* @param name argument name
* @param <C> sender type
* @return new builder
* @since 1.8.0
*/
@API(status = API.Status.STABLE, since = "1.8.0")
public static <C> @NonNull Builder<C> builder(final @NonNull String name) {
return new Builder<>(name);
} }
/** /**
@ -84,8 +95,8 @@ public final class SinglePlayerSelectorArgument<C> extends CommandArgument<C, Si
* @param <C> Command sender type * @param <C> Command sender type
* @return Created argument * @return Created argument
*/ */
public static <C> @NonNull CommandArgument<C, SinglePlayerSelector> of(final @NonNull String name) { public static <C> @NonNull SinglePlayerSelectorArgument<C> of(final @NonNull String name) {
return SinglePlayerSelectorArgument.<C>newBuilder(name).asRequired().build(); return SinglePlayerSelectorArgument.<C>builder(name).asRequired().build();
} }
/** /**
@ -95,8 +106,8 @@ public final class SinglePlayerSelectorArgument<C> extends CommandArgument<C, Si
* @param <C> Command sender type * @param <C> Command sender type
* @return Created argument * @return Created argument
*/ */
public static <C> @NonNull CommandArgument<C, SinglePlayerSelector> optional(final @NonNull String name) { public static <C> @NonNull SinglePlayerSelectorArgument<C> optional(final @NonNull String name) {
return SinglePlayerSelectorArgument.<C>newBuilder(name).asOptional().build(); return SinglePlayerSelectorArgument.<C>builder(name).asOptional().build();
} }
/** /**
@ -107,15 +118,15 @@ public final class SinglePlayerSelectorArgument<C> extends CommandArgument<C, Si
* @param <C> Command sender type * @param <C> Command sender type
* @return Created argument * @return Created argument
*/ */
public static <C> @NonNull CommandArgument<C, SinglePlayerSelector> optional( public static <C> @NonNull SinglePlayerSelectorArgument<C> optional(
final @NonNull String name, final @NonNull String name,
final @NonNull String defaultEntitySelector final @NonNull String defaultEntitySelector
) { ) {
return SinglePlayerSelectorArgument.<C>newBuilder(name).asOptionalWithDefault(defaultEntitySelector).build(); return SinglePlayerSelectorArgument.<C>builder(name).asOptionalWithDefault(defaultEntitySelector).build();
} }
public static final class Builder<C> extends CommandArgument.Builder<C, SinglePlayerSelector> { public static final class Builder<C> extends CommandArgument.TypedBuilder<C, SinglePlayerSelector, Builder<C>> {
private Builder(final @NonNull String name) { private Builder(final @NonNull String name) {
super(SinglePlayerSelector.class, name); super(SinglePlayerSelector.class, name);
@ -128,32 +139,41 @@ public final class SinglePlayerSelectorArgument<C> extends CommandArgument<C, Si
*/ */
@Override @Override
public @NonNull SinglePlayerSelectorArgument<C> build() { public @NonNull SinglePlayerSelectorArgument<C> build() {
return new SinglePlayerSelectorArgument<>(this.isRequired(), this.getName(), this.getDefaultValue(), return new SinglePlayerSelectorArgument<>(
this.getSuggestionsProvider(), this.getDefaultDescription() this.isRequired(),
this.getName(),
this.getDefaultValue(),
this.getSuggestionsProvider(),
this.getDefaultDescription()
); );
} }
} }
public static final class SinglePlayerSelectorParser<C> implements ArgumentParser<C, SinglePlayerSelector> { public static final class SinglePlayerSelectorParser<C> extends SelectorUtils.PlayerSelectorParser<C, SinglePlayerSelector> {
/**
* Creates a new {@link SinglePlayerSelectorParser}.
*/
public SinglePlayerSelectorParser() {
super(true);
}
@Override @Override
public @NonNull ArgumentParseResult<SinglePlayerSelector> parse( public SinglePlayerSelector mapResult(
final @NonNull String input,
final SelectorUtils.@NonNull EntitySelectorWrapper wrapper
) {
return new SinglePlayerSelector(input, Collections.singletonList(wrapper.singlePlayer()));
}
@Override
protected @NonNull ArgumentParseResult<SinglePlayerSelector> legacyParse(
final @NonNull CommandContext<C> commandContext, final @NonNull CommandContext<C> commandContext,
final @NonNull Queue<@NonNull String> inputQueue final @NonNull Queue<@NonNull String> inputQueue
) { ) {
final String input = inputQueue.peek(); final String input = inputQueue.peek();
if (input == null) { @SuppressWarnings("deprecation") final @Nullable Player player = Bukkit.getPlayer(input);
return ArgumentParseResult.failure(new NoInputProvidedException(
SinglePlayerSelectorParser.class,
commandContext
));
}
if (!commandContext.get(BukkitCommandContextKeys.CLOUD_BUKKIT_CAPABILITIES).contains(
CloudBukkitCapabilities.BRIGADIER)) {
@SuppressWarnings("deprecation")
Player player = Bukkit.getPlayer(input);
if (player == null) { if (player == null) {
return ArgumentParseResult.failure(new PlayerArgument.PlayerParseException(input, commandContext)); return ArgumentParseResult.failure(new PlayerArgument.PlayerParseException(input, commandContext));
@ -162,57 +182,5 @@ public final class SinglePlayerSelectorArgument<C> extends CommandArgument<C, Si
return ArgumentParseResult.success(new SinglePlayerSelector(input, ImmutableList.of(player))); return ArgumentParseResult.success(new SinglePlayerSelector(input, ImmutableList.of(player)));
} }
List<Entity> entities;
try {
entities = Bukkit.selectEntities(commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER), input);
} catch (IllegalArgumentException e) {
return ArgumentParseResult.failure(new SelectorParseException(
input,
commandContext,
SelectorParseException.FailureReason.MALFORMED_SELECTOR,
SinglePlayerSelectorParser.class
));
}
for (Entity e : entities) {
if (!(e instanceof Player)) {
return ArgumentParseResult.failure(new SelectorParseException(
input,
commandContext,
SelectorParseException.FailureReason.NON_PLAYER_IN_PLAYER_SELECTOR,
SinglePlayerSelectorParser.class
));
}
}
if (entities.size() > 1) {
return ArgumentParseResult.failure(new SelectorParseException(
input,
commandContext,
SelectorParseException.FailureReason.TOO_MANY_PLAYERS,
SinglePlayerSelectorParser.class
));
}
inputQueue.remove();
return ArgumentParseResult.success(new SinglePlayerSelector(input, entities));
}
@Override
public @NonNull List<@NonNull String> suggestions(
final @NonNull CommandContext<C> commandContext,
final @NonNull String input
) {
List<String> output = new ArrayList<>();
for (Player player : Bukkit.getOnlinePlayers()) {
final CommandSender bukkit = commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER);
if (bukkit instanceof Player && !((Player) bukkit).canSee(player)) {
continue;
}
output.add(player.getName());
}
return output;
}
} }
} }