diff --git a/core/src/main/java/dev/velix/imperat/ResolverRegistrar.java b/core/src/main/java/dev/velix/imperat/ResolverRegistrar.java index 7566baa2..70fd3b08 100644 --- a/core/src/main/java/dev/velix/imperat/ResolverRegistrar.java +++ b/core/src/main/java/dev/velix/imperat/ResolverRegistrar.java @@ -3,16 +3,21 @@ import dev.velix.imperat.annotations.base.element.ParameterElement; import dev.velix.imperat.command.ContextResolverFactory; import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.command.parameters.type.ParameterType; +import dev.velix.imperat.context.ExecutionContext; import dev.velix.imperat.context.Source; +import dev.velix.imperat.context.internal.CommandInputStream; import dev.velix.imperat.placeholders.Placeholder; import dev.velix.imperat.placeholders.PlaceholderResolver; -import dev.velix.imperat.resolvers.*; +import dev.velix.imperat.resolvers.ContextResolver; +import dev.velix.imperat.resolvers.PermissionResolver; +import dev.velix.imperat.resolvers.SourceResolver; +import dev.velix.imperat.resolvers.SuggestionResolver; import dev.velix.imperat.util.TypeWrap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.reflect.Type; -import java.util.Collection; import java.util.Collections; import java.util.Optional; @@ -72,7 +77,7 @@ public sealed interface ResolverRegistrar permits Imperat { * Fetches the {@link ContextResolver} suitable for the {@link CommandParameter} * * @param commandParameter the parameter of a command's usage - * @param the valueType of value that will be resolved by {@link ValueResolver} + * @param the valueType of value that will be resolved by {@link ParameterType#resolve(ExecutionContext, CommandInputStream)} * @return the context resolver for this parameter's value valueType */ default ContextResolver getContextResolver(CommandParameter commandParameter) { @@ -89,37 +94,23 @@ default ContextResolver getContextResolver(CommandParameter command void registerContextResolver(Type type, @NotNull ContextResolver resolver); /** - * Fetches {@link ValueResolver} for a certain value + * Fetches {@link ParameterType} for a certain value * * @param resolvingValueType the value that the resolver ends providing it from the context * @return the value resolver of a certain valueType */ @Nullable - ValueResolver getValueResolver(Type resolvingValueType); + ParameterType getParameterType(Type resolvingValueType); /** - * Fetches the {@link ValueResolver} suitable for the {@link CommandParameter} - * - * @param commandParameter the parameter of a command's usage - * @return the value resolver for this parameter's value valueType - */ - default ValueResolver getValueResolver(CommandParameter commandParameter) { - return getValueResolver(commandParameter.valueType()); - } - - /** - * Registers {@link ValueResolver} + * Registers {@link ParameterType} * * @param type the class-valueType of value being resolved from context * @param resolver the resolver for this value * @param the valueType of value being resolved from context */ - void registerValueResolver(Type type, @NotNull ValueResolver resolver); + void registerParamType(Type type, @NotNull ParameterType resolver); - /** - * @return all currently registered {@link ValueResolver} - */ - Collection> getRegisteredValueResolvers(); /** * Fetches the suggestion provider/resolver for a specific valueType of @@ -158,21 +149,6 @@ default ContextResolver getContextResolver(CommandParameter command @Nullable SuggestionResolver getNamedSuggestionResolver(String name); - /** - * Registers a suggestion resolver - * - * @param suggestionResolver the suggestion resolver to register - * @param the valueType of value that the suggestion resolver will work with. - */ - void registerSuggestionResolver(TypeSuggestionResolver suggestionResolver); - - /** - * Registers a suggestion resolver to a valueType - * - * @param type the valueType - * @param suggestionResolver the suggestion resolver. - */ - void registerSuggestionResolver(Type type, SuggestionResolver suggestionResolver); /** * Registers a suggestion resolver diff --git a/core/src/main/java/dev/velix/imperat/annotations/base/element/SimpleCommandClassVisitor.java b/core/src/main/java/dev/velix/imperat/annotations/base/element/SimpleCommandClassVisitor.java index f2ad6764..d02fd3e5 100644 --- a/core/src/main/java/dev/velix/imperat/annotations/base/element/SimpleCommandClassVisitor.java +++ b/core/src/main/java/dev/velix/imperat/annotations/base/element/SimpleCommandClassVisitor.java @@ -15,7 +15,7 @@ import dev.velix.imperat.command.parameters.CommandParameter; import dev.velix.imperat.command.parameters.NumericRange; import dev.velix.imperat.command.parameters.StrictParameterList; -import dev.velix.imperat.command.parameters.type.ParameterTypes; +import dev.velix.imperat.command.parameters.type.ParameterType; import dev.velix.imperat.command.processors.CommandPostProcessor; import dev.velix.imperat.command.processors.CommandPreProcessor; import dev.velix.imperat.context.Source; @@ -422,7 +422,11 @@ private CommandParameter loadParameter( throw new IllegalStateException("both @Flag and @Switch at the same time !"); } - TypeWrap parameterType = TypeWrap.of((Class) parameter.getParameterizedType()); + TypeWrap parameterTypeWrap = TypeWrap.of((Class) parameter.getParameterizedType()); + var type = (ParameterType) imperat.getParameterType(parameterTypeWrap.getType()); + if (type == null) { + throw new IllegalArgumentException("Unknown type detected '" + parameterTypeWrap.getType().getTypeName() + "'"); + } String name = AnnotationHelper.getParamName(imperat, parameter, named, flag, switchAnnotation); boolean optional = flag != null || switchAnnotation != null @@ -440,7 +444,7 @@ private CommandParameter loadParameter( if (suggestAnnotation != null) { suggestionResolver = SuggestionResolver.type( - parameterType, + parameterTypeWrap, imperat.replacePlaceholders(suggestAnnotation.value()) ); } else if (suggestionProvider != null) { @@ -479,7 +483,7 @@ else if (namedResolver != null) permission = imperat.replacePlaceholders(permAnn.value()); } - OptionalValueSupplier optionalValueSupplier = OptionalValueSupplier.empty(parameterType); + OptionalValueSupplier optionalValueSupplier = OptionalValueSupplier.empty(parameterTypeWrap); if (optional) { Default defaultAnnotation = parameter.getAnnotation(Default.class); DefaultProvider provider = parameter.getAnnotation(DefaultProvider.class); @@ -491,8 +495,9 @@ else if (namedResolver != null) if (suggestAnnotation != null) { suggestionResolver = SuggestionResolver.type(TypeWrap.of(parameter.getParameterizedType()), imperat.replacePlaceholders(suggestAnnotation.value())); } + return AnnotationParameterDecorator.decorate( - CommandParameter.flag(name, (Class) parameterType.getType()) + CommandParameter.flag(name, type) .suggestForInputValue((TypeSuggestionResolver) suggestionResolver) .aliases(getAllExceptFirst(flagAliases)) .flagDefaultInputValue(optionalValueSupplier) @@ -516,7 +521,7 @@ else if (namedResolver != null) CommandParameter param = AnnotationParameterDecorator.decorate( CommandParameter.of( - name, ParameterTypes.from(parameterType), permission, desc, + name, type, permission, desc, optional, greedy, optionalValueSupplier, suggestionResolver ), element ); diff --git a/core/src/main/java/dev/velix/imperat/annotations/parameters/NumericParameterDecorator.java b/core/src/main/java/dev/velix/imperat/annotations/parameters/NumericParameterDecorator.java index 99924b5a..b0c3950d 100644 --- a/core/src/main/java/dev/velix/imperat/annotations/parameters/NumericParameterDecorator.java +++ b/core/src/main/java/dev/velix/imperat/annotations/parameters/NumericParameterDecorator.java @@ -14,7 +14,7 @@ public final class NumericParameterDecorator extends InputPara NumericParameterDecorator(CommandParameter parameter, NumericRange range) { super( - parameter.name(), parameter.wrappedType(), parameter.permission(), + parameter.name(), parameter.type(), parameter.permission(), parameter.description(), parameter.isOptional(), parameter.isFlag(), parameter.isFlag(), parameter.getDefaultValueSupplier(), parameter.getSuggestionResolver() diff --git a/core/src/main/java/dev/velix/imperat/command/BaseImperat.java b/core/src/main/java/dev/velix/imperat/command/BaseImperat.java index 1f96f85e..8594f495 100644 --- a/core/src/main/java/dev/velix/imperat/command/BaseImperat.java +++ b/core/src/main/java/dev/velix/imperat/command/BaseImperat.java @@ -6,6 +6,7 @@ import dev.velix.imperat.annotations.base.AnnotationReplacer; import dev.velix.imperat.annotations.base.element.ParameterElement; import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.command.parameters.type.ParameterType; import dev.velix.imperat.command.processors.CommandPostProcessor; import dev.velix.imperat.command.processors.CommandPreProcessor; import dev.velix.imperat.command.processors.CommandProcessor; @@ -20,7 +21,10 @@ import dev.velix.imperat.placeholders.Placeholder; import dev.velix.imperat.placeholders.PlaceholderRegistry; import dev.velix.imperat.placeholders.PlaceholderResolver; -import dev.velix.imperat.resolvers.*; +import dev.velix.imperat.resolvers.ContextResolver; +import dev.velix.imperat.resolvers.PermissionResolver; +import dev.velix.imperat.resolvers.SourceResolver; +import dev.velix.imperat.resolvers.SuggestionResolver; import dev.velix.imperat.util.ImperatDebugger; import dev.velix.imperat.util.Preconditions; import dev.velix.imperat.util.TypeWrap; @@ -37,7 +41,7 @@ public abstract class BaseImperat implements Imperat { private final ContextResolverRegistry contextResolverRegistry; - private final ValueResolverRegistry valueResolverRegistry; + private final ParamTypeRegistry paramTypeRegistry; private final SuggestionResolverRegistry suggestionResolverRegistry; private final PlaceholderRegistry placeholderRegistry; private final SourceResolverRegistry sourceResolverRegistry; @@ -61,8 +65,8 @@ protected BaseImperat(@NotNull PermissionResolver permissionResolver) { contextFactory = ContextFactory.defaultFactory(this); contextResolverRegistry = ContextResolverRegistry.createDefault(this); - valueResolverRegistry = ValueResolverRegistry.createDefault(); - suggestionResolverRegistry = SuggestionResolverRegistry.createDefault(); + paramTypeRegistry = ParamTypeRegistry.createDefault(); + suggestionResolverRegistry = SuggestionResolverRegistry.createDefault(this); sourceResolverRegistry = SourceResolverRegistry.createDefault(); placeholderRegistry = PlaceholderRegistry.createDefault(this); verifier = UsageVerifier.typeTolerantVerifier(); @@ -390,33 +394,26 @@ public void registerContextResolver(Type type, } /** - * Registers {@link ValueResolver} + * Registers {@link dev.velix.imperat.command.parameters.type.ParameterType} * * @param type the class-valueType of value being resolved from context * @param resolver the resolver for this value */ @Override - public void registerValueResolver(Type type, @NotNull ValueResolver resolver) { - valueResolverRegistry.registerResolver(type, resolver); + public void registerParamType(Type type, @NotNull ParameterType resolver) { + paramTypeRegistry.registerResolver(type, resolver); } - /** - * @return all currently registered {@link ValueResolver} - */ - @Override - public Collection> getRegisteredValueResolvers() { - return valueResolverRegistry.getAll(); - } /** - * Fetches {@link ValueResolver} for a certain value + * Fetches {@link ParameterType} for a certain value * * @param resolvingValueType the value that the resolver ends providing it from the context * @return the context resolver of a certain valueType */ @Override - public @Nullable ValueResolver getValueResolver(Type resolvingValueType) { - return valueResolverRegistry.getResolver(resolvingValueType); + public @Nullable ParameterType getParameterType(Type resolvingValueType) { + return paramTypeRegistry.getResolver(resolvingValueType).orElse(null); } /** @@ -428,7 +425,9 @@ public void registerValueResolver(Type type, @NotNull ValueResolver re */ @Override public @Nullable SuggestionResolver getSuggestionResolverByType(Type type) { - return suggestionResolverRegistry.getResolver(type); + return paramTypeRegistry.getResolver(type) + .map(ParameterType::getSuggestionResolver) + .orElse(null); } /** @@ -442,27 +441,6 @@ public boolean canBeSender(Type type) { return TypeWrap.of(Source.class).isSupertypeOf(type); } - /** - * Registers a suggestion resolver - * - * @param suggestionResolver the suggestion resolver to register - */ - @Override - public void registerSuggestionResolver(TypeSuggestionResolver suggestionResolver) { - suggestionResolverRegistry.registerResolverForType(suggestionResolver); - } - - /** - * Registers a suggestion resolver to a valueType - * - * @param type the valueType - * @param suggestionResolver the suggestion resolver. - */ - @Override - public void registerSuggestionResolver(Type type, SuggestionResolver suggestionResolver) { - suggestionResolverRegistry.registerResolverForType(type, suggestionResolver); - } - /** * Fetches the suggestion provider/resolver for a specific argument * diff --git a/core/src/main/java/dev/velix/imperat/command/Command.java b/core/src/main/java/dev/velix/imperat/command/Command.java index 56a01c3e..570e0317 100644 --- a/core/src/main/java/dev/velix/imperat/command/Command.java +++ b/core/src/main/java/dev/velix/imperat/command/Command.java @@ -2,6 +2,8 @@ import dev.velix.imperat.Imperat; import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.command.parameters.type.ParameterType; +import dev.velix.imperat.command.parameters.type.ParameterTypes; import dev.velix.imperat.command.processors.CommandPostProcessor; import dev.velix.imperat.command.processors.CommandPreProcessor; import dev.velix.imperat.command.suggestions.AutoCompleter; @@ -82,6 +84,11 @@ default void addAliases(String... aliases) { addAliases(List.of(aliases)); } + @Override + default @NotNull ParameterType type() { + return ParameterTypes.command(); + } + /** * Sets the position of this command in a syntax * DO NOT USE THIS FOR ANY REASON unless it's necessary to do so @@ -93,6 +100,7 @@ default void position(int position) { throw new UnsupportedOperationException("You can't modify the position of a command"); } + /** * @return the default value if it's input is not present * in case of the parameter being optional diff --git a/core/src/main/java/dev/velix/imperat/command/CommandUsage.java b/core/src/main/java/dev/velix/imperat/command/CommandUsage.java index 80bcd5db..301a4cc9 100644 --- a/core/src/main/java/dev/velix/imperat/command/CommandUsage.java +++ b/core/src/main/java/dev/velix/imperat/command/CommandUsage.java @@ -5,8 +5,8 @@ import dev.velix.imperat.command.cooldown.UsageCooldown; import dev.velix.imperat.command.parameters.CommandParameter; import dev.velix.imperat.command.parameters.ParameterBuilder; -import dev.velix.imperat.context.CommandFlag; import dev.velix.imperat.context.ExecutionContext; +import dev.velix.imperat.context.FlagData; import dev.velix.imperat.context.Source; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -58,7 +58,7 @@ static Builder builder() { * @return the flag from the raw input, null if it cannot be a flag */ @Nullable - CommandFlag getFlagFromRaw(String rawInput); + FlagData getFlagFromRaw(String rawInput); /** * Adds parameters to the usage diff --git a/core/src/main/java/dev/velix/imperat/command/CommandUsageImpl.java b/core/src/main/java/dev/velix/imperat/command/CommandUsageImpl.java index 6c83437e..bbe461c4 100644 --- a/core/src/main/java/dev/velix/imperat/command/CommandUsageImpl.java +++ b/core/src/main/java/dev/velix/imperat/command/CommandUsageImpl.java @@ -5,8 +5,8 @@ import dev.velix.imperat.command.cooldown.DefaultCooldownHandler; import dev.velix.imperat.command.cooldown.UsageCooldown; import dev.velix.imperat.command.parameters.CommandParameter; -import dev.velix.imperat.context.CommandFlag; import dev.velix.imperat.context.ExecutionContext; +import dev.velix.imperat.context.FlagData; import dev.velix.imperat.context.Source; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -96,7 +96,7 @@ public boolean hasFlag(String input) { * @return the flag from the raw input, null if it cannot be a flag */ @Override - public @Nullable CommandFlag getFlagFromRaw(String rawInput) { + public @Nullable FlagData getFlagFromRaw(String rawInput) { boolean isSingle = SINGLE_FLAG.matcher(rawInput).matches(); boolean isDouble = DOUBLE_FLAG.matcher(rawInput).matches(); @@ -108,7 +108,7 @@ public boolean hasFlag(String input) { for (var param : parameters) { if (!param.isFlag()) continue; - CommandFlag flag = param.asFlagParameter().flagData(); + FlagData flag = param.asFlagParameter().flagData(); if (flag.acceptsInput(inputFlagAlias)) { return flag; } diff --git a/core/src/main/java/dev/velix/imperat/command/parameters/CommandParameter.java b/core/src/main/java/dev/velix/imperat/command/parameters/CommandParameter.java index 7d6ac055..5c8c77dd 100644 --- a/core/src/main/java/dev/velix/imperat/command/parameters/CommandParameter.java +++ b/core/src/main/java/dev/velix/imperat/command/parameters/CommandParameter.java @@ -115,7 +115,7 @@ static ParameterBuilder optionalGreedy(String name static FlagBuilder flag( String name, - Class inputType + ParameterType inputType ) { return FlagBuilder.ofFlag(name, inputType); } @@ -268,4 +268,7 @@ default NumericParameter asNumeric() { */ boolean similarTo(CommandParameter parameter); + default boolean isRequired() { + return !isOptional(); + } } diff --git a/core/src/main/java/dev/velix/imperat/command/parameters/FlagBuilder.java b/core/src/main/java/dev/velix/imperat/command/parameters/FlagBuilder.java index b8352f41..8bec0d6b 100644 --- a/core/src/main/java/dev/velix/imperat/command/parameters/FlagBuilder.java +++ b/core/src/main/java/dev/velix/imperat/command/parameters/FlagBuilder.java @@ -1,26 +1,25 @@ package dev.velix.imperat.command.parameters; +import dev.velix.imperat.command.parameters.type.ParameterType; import dev.velix.imperat.command.parameters.type.ParameterTypes; -import dev.velix.imperat.context.CommandFlag; -import dev.velix.imperat.context.CommandSwitch; +import dev.velix.imperat.context.FlagData; import dev.velix.imperat.context.Source; +import dev.velix.imperat.context.internal.CommandFlag; import dev.velix.imperat.resolvers.TypeSuggestionResolver; import dev.velix.imperat.supplier.OptionalValueSupplier; -import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public final class FlagBuilder extends ParameterBuilder { - private final Type inputType; + private final ParameterType inputType; private final List aliases = new ArrayList<>(); private OptionalValueSupplier defaultValueSupplier = null; private TypeSuggestionResolver suggestionResolver; - private FlagBuilder(String name, Class inputType) { - super(name, ParameterTypes.flag(CommandFlag.create(name, Collections.emptyList(), inputType)), true, false); + private FlagBuilder(String name, ParameterType inputType) { + super(name, ParameterTypes.flag(), true, false); this.inputType = inputType; } @@ -29,7 +28,7 @@ private FlagBuilder(String name) { this(name, null); } - public static FlagBuilder ofFlag(String name, Class inputType) { + public static FlagBuilder ofFlag(String name, ParameterType inputType) { return new FlagBuilder<>(name, inputType); } @@ -64,14 +63,13 @@ public FlagBuilder suggestForInputValue(TypeSuggestionResolver sugge } @Override + @SuppressWarnings("unchecked") public FlagParameter build() { - if (inputType != null) { - CommandFlag flag = CommandFlag.create(name, aliases, inputType); - return new FlagCommandParameter<>(flag, permission, description, defaultValueSupplier, suggestionResolver); - } else { - CommandSwitch commandSwitch = CommandSwitch.create(name, aliases); - return new FlagCommandParameter<>(commandSwitch, permission, OptionalValueSupplier.of(false), suggestionResolver); + FlagData flag = FlagData.create(name, aliases, inputType); + if (inputType == null) { + defaultValueSupplier = (OptionalValueSupplier) OptionalValueSupplier.of(false); } + return new FlagCommandParameter<>(flag, permission, description, defaultValueSupplier, suggestionResolver); } } diff --git a/core/src/main/java/dev/velix/imperat/command/parameters/FlagCommandParameter.java b/core/src/main/java/dev/velix/imperat/command/parameters/FlagCommandParameter.java index ceabd3c7..db4e9110 100644 --- a/core/src/main/java/dev/velix/imperat/command/parameters/FlagCommandParameter.java +++ b/core/src/main/java/dev/velix/imperat/command/parameters/FlagCommandParameter.java @@ -1,9 +1,10 @@ package dev.velix.imperat.command.parameters; import dev.velix.imperat.command.Description; -import dev.velix.imperat.context.CommandFlag; -import dev.velix.imperat.context.CommandSwitch; +import dev.velix.imperat.command.parameters.type.ParameterTypes; +import dev.velix.imperat.context.FlagData; import dev.velix.imperat.context.Source; +import dev.velix.imperat.context.internal.CommandFlag; import dev.velix.imperat.resolvers.TypeSuggestionResolver; import dev.velix.imperat.supplier.OptionalValueSupplier; import dev.velix.imperat.util.TypeWrap; @@ -11,61 +12,32 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.Type; -import java.util.List; - @ApiStatus.Internal public final class FlagCommandParameter extends InputParameter implements FlagParameter { - private final CommandFlag flag; - private final OptionalValueSupplier supplier; + private final FlagData flag; + private final OptionalValueSupplier inputValueSupplier; private final TypeSuggestionResolver inputValueSuggestionResolver; FlagCommandParameter( - CommandFlag flag, + FlagData flag, String permission, Description description, - OptionalValueSupplier valueSupplier, - TypeSuggestionResolver inputValueSuggestionResolver - ) { - this(flag.name(), permission, flag.aliases(), description, flag.inputType(), valueSupplier, inputValueSuggestionResolver); - } - - FlagCommandParameter( - String flagName, - @Nullable String permission, - List aliases, - Description description, - Type inputType, - OptionalValueSupplier supplier, + OptionalValueSupplier inputValueSupplier, TypeSuggestionResolver inputValueSuggestionResolver ) { - super(flagName, TypeWrap.of(CommandFlag.class), permission, description, - true, true, false, OptionalValueSupplier.empty(TypeWrap.of(CommandFlag.class)), null); - flag = CommandFlag.create(flagName, aliases, inputType); - this.supplier = supplier; - this.inputValueSuggestionResolver = inputValueSuggestionResolver; - } - - FlagCommandParameter(CommandSwitch commandSwitch, @Nullable String permission, - Description description, OptionalValueSupplier supplier, TypeSuggestionResolver inputValueSuggestionResolver) { - super(commandSwitch.name(), TypeWrap.of(CommandSwitch.class), permission, description, + super( + flag.name(), ParameterTypes.flag(), + permission, description, true, true, false, - OptionalValueSupplier.empty(TypeWrap.of(CommandFlag.class)), null); - this.flag = commandSwitch; - this.supplier = supplier; + OptionalValueSupplier.empty(TypeWrap.of(CommandFlag.class)), + inputValueSuggestionResolver + ); + this.flag = flag; + this.inputValueSupplier = inputValueSupplier; this.inputValueSuggestionResolver = inputValueSuggestionResolver; } - FlagCommandParameter( - CommandSwitch commandSwitch, - @Nullable String permission, - OptionalValueSupplier supplier, - TypeSuggestionResolver suggestionResolver - ) { - this(commandSwitch, permission, Description.EMPTY, supplier, suggestionResolver); - } - @Override public String format() { return flag.format(); @@ -75,7 +47,7 @@ public String format() { * @return The flag's data */ @Override - public @NotNull CommandFlag flagData() { + public @NotNull FlagData flagData() { return flag; } @@ -86,7 +58,7 @@ public String format() { @Override @SuppressWarnings("unchecked") public @NotNull OptionalValueSupplier getDefaultValueSupplier() { - return (OptionalValueSupplier) supplier; + return (OptionalValueSupplier) inputValueSupplier; } @Override diff --git a/core/src/main/java/dev/velix/imperat/command/parameters/FlagParameter.java b/core/src/main/java/dev/velix/imperat/command/parameters/FlagParameter.java index cf018b20..a063b609 100644 --- a/core/src/main/java/dev/velix/imperat/command/parameters/FlagParameter.java +++ b/core/src/main/java/dev/velix/imperat/command/parameters/FlagParameter.java @@ -1,7 +1,6 @@ package dev.velix.imperat.command.parameters; -import dev.velix.imperat.context.CommandFlag; -import dev.velix.imperat.context.CommandSwitch; +import dev.velix.imperat.context.FlagData; import dev.velix.imperat.context.Source; import dev.velix.imperat.resolvers.TypeSuggestionResolver; import org.jetbrains.annotations.NotNull; @@ -15,19 +14,22 @@ public interface FlagParameter extends CommandParameter { * @return The flag's data */ @NotNull - CommandFlag flagData(); + FlagData flagData(); /** * @return The valueType of input value */ default Type inputValueType() { - return flagData().inputType(); + var type = flagData().inputType(); + if (type == null) + return Boolean.class; + return type.type(); } /** * @param the valueType of flag input value * @return the {@link TypeSuggestionResolver} for input value of this flag - * null if the flag is {@link CommandSwitch}, check using {@link FlagParameter#isSwitch()} + * null if the flag is switch, check using {@link FlagParameter#isSwitch()} */ @Nullable TypeSuggestionResolver inputSuggestionResolver(); @@ -41,6 +43,6 @@ default boolean isFlag() { } default boolean isSwitch() { - return flagData() instanceof CommandSwitch; + return flagData().inputType() == null; } } diff --git a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterBoolean.java b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterBoolean.java index 09712f7e..1f86ae2e 100644 --- a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterBoolean.java +++ b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterBoolean.java @@ -1,14 +1,16 @@ package dev.velix.imperat.command.parameters.type; -import dev.velix.imperat.context.ResolvedContext; +import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.context.ExecutionContext; import dev.velix.imperat.context.Source; -import dev.velix.imperat.context.internal.sur.CommandInputStream; +import dev.velix.imperat.context.internal.CommandInputStream; import dev.velix.imperat.exception.ImperatException; import dev.velix.imperat.exception.SourceException; import dev.velix.imperat.util.TypeWrap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.Locale; import java.util.Map; @@ -26,10 +28,11 @@ public final class ParameterBoolean extends BaseParameterType< ParameterBoolean() { super(TypeWrap.of(Boolean.class)); + withSuggestions("true", "false"); } @Override - public @Nullable Boolean resolve(ResolvedContext context, @NotNull CommandInputStream commandInputStream) throws ImperatException { + public @Nullable Boolean resolve(ExecutionContext context, @NotNull CommandInputStream commandInputStream) throws ImperatException { var raw = commandInputStream.currentRaw(); if (raw.equalsIgnoreCase("true") || raw.equalsIgnoreCase("false")) { @@ -44,7 +47,7 @@ public final class ParameterBoolean extends BaseParameterType< } @Override - public boolean matchesInput(String input) { + public boolean matchesInput(String input, CommandParameter parameter) { if (!allowVariants && (input.equalsIgnoreCase("true") || input.equalsIgnoreCase("false"))) return true; @@ -55,8 +58,18 @@ else if (allowVariants) { return Boolean.parseBoolean(input); } + @Override + public Collection suggestions() { + return super.suggestions(); + } + public ParameterBoolean setAllowVariants(boolean allowVariants) { this.allowVariants = allowVariants; + if (allowVariants) { + suggestions.addAll(VARIANTS.keySet()); + } else { + suggestions.removeAll(VARIANTS.keySet()); + } return this; } diff --git a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterCommand.java b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterCommand.java new file mode 100644 index 00000000..3b8a6b0b --- /dev/null +++ b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterCommand.java @@ -0,0 +1,33 @@ +package dev.velix.imperat.command.parameters.type; + +import dev.velix.imperat.command.Command; +import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.context.ExecutionContext; +import dev.velix.imperat.context.Source; +import dev.velix.imperat.context.internal.CommandInputStream; +import dev.velix.imperat.exception.ImperatException; +import dev.velix.imperat.util.TypeWrap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class ParameterCommand extends BaseParameterType> { + ParameterCommand() { + super(new TypeWrap<>() { + }); + } + + @Override + public @Nullable Command resolve(ExecutionContext context, @NotNull CommandInputStream commandInputStream) throws ImperatException { + var currentParameter = commandInputStream.currentParameter(); + if (currentParameter == null) + return null; + return currentParameter.asCommand(); + } + + @Override + public boolean matchesInput(String input, CommandParameter parameter) { + return parameter.isCommand() && + parameter.asCommand().hasName(input.toLowerCase()); + } + +} diff --git a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterEnum.java b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterEnum.java new file mode 100644 index 00000000..f034b950 --- /dev/null +++ b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterEnum.java @@ -0,0 +1,55 @@ +package dev.velix.imperat.command.parameters.type; + +import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.context.ExecutionContext; +import dev.velix.imperat.context.Source; +import dev.velix.imperat.context.internal.CommandInputStream; +import dev.velix.imperat.exception.ImperatException; +import dev.velix.imperat.exception.SourceException; +import dev.velix.imperat.util.TypeUtility; +import dev.velix.imperat.util.TypeWrap; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Type; +import java.util.Objects; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public final class ParameterEnum extends BaseParameterType> { + + ParameterEnum(TypeWrap> typeWrap) { + super(typeWrap); + } + + public ParameterEnum() { + super(new TypeWrap<>() { + }); + } + + @Override + public @NotNull Enum resolve(ExecutionContext context, @NotNull CommandInputStream commandInputStream) throws ImperatException { + + Type enumType = TypeUtility.matches(typeWrap.getType(), Enum.class) + ? Objects.requireNonNull(commandInputStream.currentParameter()).valueType() : typeWrap.getType(); + + var raw = commandInputStream.currentRaw(); + try { + assert raw != null; + return Enum.valueOf((Class) enumType, raw.toUpperCase()); + } catch (EnumConstantNotPresentException ex) { + throw new SourceException("Invalid " + enumType.getTypeName() + " '" + raw + "'"); + } + } + + @Override + public boolean matchesInput(String input, CommandParameter parameter) { + try { + if (!typeWrap.isSubtypeOf(Enum.class)) { + return true; + } + Enum.valueOf((Class) typeWrap.getType(), input); + return true; + } catch (EnumConstantNotPresentException ex) { + return false; + } + } +} diff --git a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterFlag.java b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterFlag.java index b9fe0943..a58f26af 100644 --- a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterFlag.java +++ b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterFlag.java @@ -1,9 +1,11 @@ package dev.velix.imperat.command.parameters.type; -import dev.velix.imperat.context.CommandFlag; -import dev.velix.imperat.context.ResolvedContext; +import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.command.parameters.FlagParameter; +import dev.velix.imperat.context.ExecutionContext; import dev.velix.imperat.context.Source; -import dev.velix.imperat.context.internal.sur.CommandInputStream; +import dev.velix.imperat.context.internal.CommandFlag; +import dev.velix.imperat.context.internal.CommandInputStream; import dev.velix.imperat.exception.ImperatException; import dev.velix.imperat.util.Patterns; import dev.velix.imperat.util.TypeWrap; @@ -11,22 +13,36 @@ import org.jetbrains.annotations.Nullable; public class ParameterFlag extends BaseParameterType { - private final CommandFlag flag; - protected ParameterFlag(CommandFlag flag) { + protected ParameterFlag() { super(TypeWrap.of(CommandFlag.class)); - this.flag = flag; } - - //TODO fix fucked up flags structure next week @Override - public @Nullable CommandFlag resolve(ResolvedContext context, @NotNull CommandInputStream commandInputStream) throws ImperatException { - return null; + public @Nullable CommandFlag resolve(ExecutionContext context, @NotNull CommandInputStream commandInputStream) throws ImperatException { + CommandParameter currentParameter = commandInputStream.currentParameter(); + assert currentParameter != null; + if (!currentParameter.isFlag()) { + throw new IllegalArgumentException(); + } + + FlagParameter flagParameter = currentParameter.asFlagParameter(); + + String rawFlag = commandInputStream.currentRaw(); + + String rawInput = null; + Object input = null; + + if (!flagParameter.isSwitch()) { + ParameterType inputType = flagParameter.flagData().inputType(); + rawInput = commandInputStream.popRaw().orElseThrow(); + input = inputType.resolve(context, commandInputStream); + } + return new CommandFlag(flagParameter.flagData(), rawFlag, rawInput, input); } @Override - public boolean matchesInput(String input) { + public boolean matchesInput(String input, CommandParameter parameter) { int subStringIndex; if (Patterns.SINGLE_FLAG.matcher(input).matches()) { subStringIndex = 1; @@ -36,6 +52,7 @@ public boolean matchesInput(String input) { subStringIndex = 0; } String flagInput = input.substring(subStringIndex); - return flag.acceptsInput(flagInput); + return parameter.asFlagParameter().flagData() + .acceptsInput(flagInput); } } diff --git a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterNumber.java b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterNumber.java index b3f289ef..43a05ae2 100644 --- a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterNumber.java +++ b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterNumber.java @@ -1,8 +1,9 @@ package dev.velix.imperat.command.parameters.type; -import dev.velix.imperat.context.ResolvedContext; +import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.context.ExecutionContext; import dev.velix.imperat.context.Source; -import dev.velix.imperat.context.internal.sur.CommandInputStream; +import dev.velix.imperat.context.internal.CommandInputStream; import dev.velix.imperat.exception.ImperatException; import dev.velix.imperat.exception.SourceException; import dev.velix.imperat.util.TypeUtility; @@ -32,7 +33,7 @@ static ParameterNumber from(Class } @Override - public @Nullable N resolve(ResolvedContext context, @NotNull CommandInputStream commandInputStream) throws ImperatException { + public @Nullable N resolve(ExecutionContext context, @NotNull CommandInputStream commandInputStream) throws ImperatException { String input = commandInputStream.currentRaw(); try { @@ -43,7 +44,7 @@ static ParameterNumber from(Class } @Override - public boolean matchesInput(String input) { + public boolean matchesInput(String input, CommandParameter parameter) { try { parse(input); return true; diff --git a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterString.java b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterString.java index 04d16ecb..271b7ad4 100644 --- a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterString.java +++ b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterString.java @@ -1,8 +1,9 @@ package dev.velix.imperat.command.parameters.type; -import dev.velix.imperat.context.ResolvedContext; +import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.context.ExecutionContext; import dev.velix.imperat.context.Source; -import dev.velix.imperat.context.internal.sur.CommandInputStream; +import dev.velix.imperat.context.internal.CommandInputStream; import dev.velix.imperat.exception.ImperatException; import dev.velix.imperat.util.TypeWrap; import org.jetbrains.annotations.NotNull; @@ -17,9 +18,10 @@ public final class ParameterString extends BaseParameterType context, @NotNull CommandInputStream inputStream) throws ImperatException { + public @Nullable String resolve(ExecutionContext context, @NotNull CommandInputStream inputStream) throws ImperatException { StringBuilder builder = new StringBuilder(); final Character current = inputStream.currentLetter(); + if (current == null) return null; if (!isQuoteChar(current)) { return inputStream.currentRaw(); @@ -31,12 +33,7 @@ public final class ParameterString extends BaseParameterType parameter) { return true; } } diff --git a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterType.java b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterType.java index 07fbc84e..e840e565 100644 --- a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterType.java +++ b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterType.java @@ -1,8 +1,9 @@ package dev.velix.imperat.command.parameters.type; -import dev.velix.imperat.context.ResolvedContext; +import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.context.ExecutionContext; import dev.velix.imperat.context.Source; -import dev.velix.imperat.context.internal.sur.CommandInputStream; +import dev.velix.imperat.context.internal.CommandInputStream; import dev.velix.imperat.exception.ImperatException; import dev.velix.imperat.resolvers.TypeSuggestionResolver; import dev.velix.imperat.util.TypeUtility; @@ -17,13 +18,13 @@ public interface ParameterType { Type type(); - @Nullable T resolve(ResolvedContext context, @NotNull CommandInputStream commandInputStream) throws ImperatException; + @Nullable T resolve(ExecutionContext context, @NotNull CommandInputStream commandInputStream) throws ImperatException; Collection suggestions(); TypeSuggestionResolver getSuggestionResolver(); - boolean matchesInput(String input); + boolean matchesInput(String input, CommandParameter parameter); default boolean isRelatedToType(Type type) { return TypeUtility.areRelatedTypes(type, this.type()); diff --git a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterTypes.java b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterTypes.java index 455edcbf..d6eb78c3 100644 --- a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterTypes.java +++ b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterTypes.java @@ -1,19 +1,12 @@ package dev.velix.imperat.command.parameters.type; -import dev.velix.imperat.context.CommandFlag; import dev.velix.imperat.context.Source; -import dev.velix.imperat.util.Registry; -import dev.velix.imperat.util.TypeUtility; -import dev.velix.imperat.util.TypeWrap; - -import java.lang.reflect.Type; +import org.jetbrains.annotations.NotNull; public final class ParameterTypes { - private final static Registry> types = new Registry<>(); private ParameterTypes() { - throw new AssertionError(); } public static ParameterWord word() { @@ -32,28 +25,12 @@ public static ParameterBoolean bool() { return new ParameterBoolean<>(); } - public static ParameterFlag flag(CommandFlag flag) { - return new ParameterFlag<>(flag); + public static ParameterFlag flag() { + return new ParameterFlag<>(); } - @SuppressWarnings("unchecked") - public static ParameterType from(TypeWrap typeWrap) { - Class type = (Class) typeWrap.getType(); - if (TypeUtility.matches(type, String.class)) { - return (ParameterType) string(); - } else if (TypeUtility.matches(type, Boolean.class)) { - return (ParameterType) bool(); - } else if (TypeUtility.matches(type, Integer.class)) { - return (ParameterType) numeric(Integer.class); - } else if (TypeUtility.matches(type, Long.class)) { - return (ParameterType) numeric(Long.class); - } else if (TypeUtility.matches(type, Float.class)) { - return (ParameterType) numeric(Float.class); - } else if (TypeUtility.matches(type, Double.class)) { - return (ParameterType) numeric(Double.class); - } else { - throw new IllegalArgumentException("Unsupported parameter type: " + type.getName()); - } - } + public static @NotNull ParameterCommand command() { + return new ParameterCommand<>(); + } } diff --git a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterWord.java b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterWord.java index 27b79ae0..fc6a3550 100644 --- a/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterWord.java +++ b/core/src/main/java/dev/velix/imperat/command/parameters/type/ParameterWord.java @@ -1,8 +1,9 @@ package dev.velix.imperat.command.parameters.type; -import dev.velix.imperat.context.ResolvedContext; +import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.context.ExecutionContext; import dev.velix.imperat.context.Source; -import dev.velix.imperat.context.internal.sur.CommandInputStream; +import dev.velix.imperat.context.internal.CommandInputStream; import dev.velix.imperat.exception.ImperatException; import dev.velix.imperat.exception.SourceException; import dev.velix.imperat.util.Preconditions; @@ -24,11 +25,12 @@ public final class ParameterWord extends BaseParameterType context, @NotNull CommandInputStream commandInputStream) throws ImperatException { + public @Nullable String resolve(ExecutionContext context, @NotNull CommandInputStream commandInputStream) throws ImperatException { var nextRaw = commandInputStream.currentRaw(); if (restrictions.isEmpty()) { return nextRaw; } + assert nextRaw != null; return Optional.of(nextRaw).filter(restrictions::contains) .orElseThrow(() -> new SourceException("Word '%s' is not within the given restrictions=%s", nextRaw, restrictions.toString())); } @@ -39,7 +41,7 @@ public Collection suggestions() { } @Override - public boolean matchesInput(String input) { + public boolean matchesInput(String input, CommandParameter parameter) { if (!restrictions.isEmpty()) { return restrictions.contains(input); } diff --git a/core/src/main/java/dev/velix/imperat/command/suggestions/SuggestionResolverRegistry.java b/core/src/main/java/dev/velix/imperat/command/suggestions/SuggestionResolverRegistry.java index a9520fd7..ec47a68e 100644 --- a/core/src/main/java/dev/velix/imperat/command/suggestions/SuggestionResolverRegistry.java +++ b/core/src/main/java/dev/velix/imperat/command/suggestions/SuggestionResolverRegistry.java @@ -1,14 +1,13 @@ package dev.velix.imperat.command.suggestions; +import dev.velix.imperat.Imperat; import dev.velix.imperat.command.parameters.CommandParameter; import dev.velix.imperat.command.parameters.FlagParameter; -import dev.velix.imperat.context.CommandFlag; +import dev.velix.imperat.context.FlagData; import dev.velix.imperat.context.Source; import dev.velix.imperat.context.SuggestionContext; import dev.velix.imperat.resolvers.SuggestionResolver; import dev.velix.imperat.resolvers.TypeSuggestionResolver; -import dev.velix.imperat.util.Registry; -import dev.velix.imperat.util.TypeUtility; import dev.velix.imperat.util.TypeWrap; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -18,34 +17,31 @@ import java.util.*; @ApiStatus.Internal -public final class SuggestionResolverRegistry extends Registry> { +public final class SuggestionResolverRegistry { private final Map> resolversPerName; private final EnumSuggestionResolver enumSuggestionResolver = new EnumSuggestionResolver(); private final FlagSuggestionResolver flagSuggestionResolver = new FlagSuggestionResolver(); - private SuggestionResolverRegistry() { + private final Imperat imperat; + + private SuggestionResolverRegistry(Imperat imperat) { super(); - registerResolverForType(SuggestionResolver.type(Boolean.class, "true", "false")); + this.imperat = imperat; resolversPerName = new HashMap<>(); } - public static SuggestionResolverRegistry createDefault() { - return new SuggestionResolverRegistry<>(); + public static SuggestionResolverRegistry createDefault(Imperat imperat) { + return new SuggestionResolverRegistry<>(imperat); } - public void registerResolverForType(TypeSuggestionResolver suggestionResolver) { - this.registerResolverForType(suggestionResolver.getType().getType(), suggestionResolver); + public FlagSuggestionResolver getFlagSuggestionResolver() { + return flagSuggestionResolver; } - public void registerResolverForType(Type type, SuggestionResolver suggestionResolver) { - if (TypeUtility.areRelatedTypes(type, Enum.class)) { - //we preload the enum he registers - enumSuggestionResolver.registerEnumResolver(type); - } - - setData(type, suggestionResolver); + public EnumSuggestionResolver getEnumSuggestionResolver() { + return enumSuggestionResolver; } public void registerNamedResolver(String name, @@ -53,34 +49,12 @@ public void registerNamedResolver(String name, resolversPerName.put(name, suggestionResolver); } - public @Nullable SuggestionResolver getResolver(Type type) { - - return getData(type).orElseGet(() -> { - - if (TypeUtility.areRelatedTypes(type, Enum.class)) { - return enumSuggestionResolver; - } - - if (TypeUtility.areRelatedTypes(type, CommandFlag.class)) { - return flagSuggestionResolver; - } - - for (var resolverByType : this.getAll()) { - if (resolverByType instanceof TypeSuggestionResolver typeSuggestionResolver - && typeSuggestionResolver.getType().isSupertypeOf(type)) { - return resolverByType; - } - } - return null; - }); - } - public @Nullable SuggestionResolver getResolverByName(String name) { return resolversPerName.get(name); } @SuppressWarnings({"rawtypes", "unchecked"}) - final class EnumSuggestionResolver implements TypeSuggestionResolver { + public final class EnumSuggestionResolver implements TypeSuggestionResolver { private final Map> PRE_LOADED_ENUMS = new HashMap<>(); public void registerEnumResolver(Type raw) { @@ -109,11 +83,11 @@ public List autoComplete(SuggestionContext context, CommandParameter< } } - final class FlagSuggestionResolver implements TypeSuggestionResolver { + public final class FlagSuggestionResolver implements TypeSuggestionResolver { @Override - public @NotNull TypeWrap getType() { - return TypeWrap.of(CommandFlag.class); + public @NotNull TypeWrap getType() { + return TypeWrap.of(FlagData.class); } @Override @@ -121,7 +95,7 @@ public Collection autoComplete(SuggestionContext context, CommandPara assert parameter.isFlag(); FlagParameter flagParameter = parameter.asFlagParameter(); CompletionArg arg = context.getArgToComplete(); - CommandFlag data = flagParameter.flagData(); + FlagData data = flagParameter.flagData(); if (flagParameter.isSwitch()) { //normal one arg @@ -133,8 +107,8 @@ public Collection autoComplete(SuggestionContext context, CommandPara int argPos = arg.index(); if (argPos > paramPos) { //auto-complete the value for the flag - SuggestionResolver flagInputResolver = getResolver(TypeWrap.of(data.inputType()).getType()); - + var inputType = imperat.getParameterType(data.inputType().type()); + SuggestionResolver flagInputResolver = inputType == null ? null : inputType.getSuggestionResolver(); //flag parameter's suggestion resolver is the same resolver for its data input. if (flagInputResolver == null) flagInputResolver = flagParameter.inputSuggestionResolver(); @@ -148,7 +122,7 @@ public Collection autoComplete(SuggestionContext context, CommandPara } - private List autoCompleteFlagNames(CommandFlag data) { + private List autoCompleteFlagNames(FlagData data) { List results = new ArrayList<>(); results.add("-" + data.name()); for (var alias : data.aliases()) { diff --git a/core/src/main/java/dev/velix/imperat/command/tree/ArgumentNode.java b/core/src/main/java/dev/velix/imperat/command/tree/ArgumentNode.java index 870d8084..f3e76d4d 100644 --- a/core/src/main/java/dev/velix/imperat/command/tree/ArgumentNode.java +++ b/core/src/main/java/dev/velix/imperat/command/tree/ArgumentNode.java @@ -29,7 +29,7 @@ public boolean matchesInput(String input) { .flagData().acceptsInput(flagInput); } var type = data.type(); - return type.matchesInput(input); + return type.matchesInput(input, data); //return valueType == null || valueType.matchesInput(input); } diff --git a/core/src/main/java/dev/velix/imperat/context/CommandSwitch.java b/core/src/main/java/dev/velix/imperat/context/CommandSwitch.java deleted file mode 100644 index 9ad4db94..00000000 --- a/core/src/main/java/dev/velix/imperat/context/CommandSwitch.java +++ /dev/null @@ -1,51 +0,0 @@ -package dev.velix.imperat.context; - -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -/** - * Represents a flag that returns a value of valueType boolean - * which represents merely whether its present in the syntax input or - * so they have no input value unlike true flags - * - * @see CommandFlag - */ -public interface CommandSwitch extends CommandFlag { - - static CommandSwitch create(String name, List aliases) { - return new CommandSwitch.CommandSwitchImpl(CommandFlag.create(name, aliases, null)); - } - - /** - * @return the valueType of input - * from the flag - */ - @Override - default Class inputType() { - throw new UnsupportedOperationException("Command Switches are declared " + - "by their presence merely no input types"); - } - - record CommandSwitchImpl(CommandFlag flag) implements CommandSwitch { - - /** - * The main name of the flag - * - * @return the name(unique) of the flag - */ - @Override - public @NotNull String name() { - return flag.name(); - } - - /** - * @return the alias of the flag - */ - @Override - public @NotNull List aliases() { - return flag.aliases(); - } - - } -} diff --git a/core/src/main/java/dev/velix/imperat/context/ExecutionContext.java b/core/src/main/java/dev/velix/imperat/context/ExecutionContext.java index 1dc4f936..b7c8b87c 100644 --- a/core/src/main/java/dev/velix/imperat/context/ExecutionContext.java +++ b/core/src/main/java/dev/velix/imperat/context/ExecutionContext.java @@ -1,8 +1,8 @@ package dev.velix.imperat.context; import dev.velix.imperat.Imperat; -import dev.velix.imperat.context.internal.ResolvedArgument; -import dev.velix.imperat.context.internal.ResolvedFlag; +import dev.velix.imperat.context.internal.Argument; +import dev.velix.imperat.context.internal.CommandFlag; import dev.velix.imperat.exception.ImperatException; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -23,11 +23,11 @@ public interface ExecutionContext extends Context { * @param flagName the name of the flag to check if it's used or not * @return The flag whether it has been used or not in this command context */ - Optional getFlag(String flagName); + Optional getFlag(String flagName); /** * Fetches the flag input value - * returns null if the flag is a {@link CommandSwitch} + * returns null if the flag is a switch * OR if the value hasn't been resolved somehow * * @param flagName the flag name @@ -43,7 +43,7 @@ public interface ExecutionContext extends Context { * @param name the name of the command * @param the valueType of this value * @return the value of the resolved argument - * @see ResolvedArgument + * @see Argument */ @Nullable T getArgument(String name); @@ -80,5 +80,5 @@ default String getRawArgument(int index) { /** * @return the resolved flag arguments */ - Collection getResolvedFlags(); + Collection getResolvedFlags(); } diff --git a/core/src/main/java/dev/velix/imperat/context/CommandFlag.java b/core/src/main/java/dev/velix/imperat/context/FlagData.java similarity index 57% rename from core/src/main/java/dev/velix/imperat/context/CommandFlag.java rename to core/src/main/java/dev/velix/imperat/context/FlagData.java index 87fe2b97..59644e32 100644 --- a/core/src/main/java/dev/velix/imperat/context/CommandFlag.java +++ b/core/src/main/java/dev/velix/imperat/context/FlagData.java @@ -1,10 +1,10 @@ package dev.velix.imperat.context; +import dev.velix.imperat.command.parameters.type.ParameterType; import dev.velix.imperat.util.StringUtils; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import java.lang.reflect.Type; import java.util.List; /** @@ -13,13 +13,19 @@ *

* A flag has merely 2 types: * 1) A true flag is a flag that has an input value next to it in the raw input - * 2) A {@link CommandSwitch} + * 2) A switch (flag that it's value input is represented by its mere presence */ @ApiStatus.AvailableSince("1.0.0") -public interface CommandFlag { +public interface FlagData { - static CommandFlag create(String name, List alias, Type inputType) { - return new CommandFlagImpl(name, alias, inputType); + //TODO fix FLAGS STRUCTURE + static FlagData create(String name, List alias, ParameterType inputType) { + return new FlagDataImpl<>(name, alias, inputType); + } + + //TODO fix FLAGS STRUCTURE + static FlagData createSwitch(String name, List aliases) { + return create(name, aliases, null); } /** @@ -40,7 +46,7 @@ static CommandFlag create(String name, List alias, Type inputType) { * @return the valueType of input * from the flag */ - Type inputType(); + ParameterType inputType(); default boolean hasAlias(String alias) { return aliases().contains(alias.toLowerCase()); @@ -49,7 +55,7 @@ default boolean hasAlias(String alias) { default String format() { String display = this.name(); - String valueFormat = (this instanceof CommandSwitch) ? "" : " "; + String valueFormat = this.inputType() == null ? "" : " "; return StringUtils.normalizedParameterFormatting("-" + display + valueFormat, true); } @@ -59,9 +65,12 @@ default boolean acceptsInput(String input) { } - record CommandFlagImpl(String name, List aliases, Type inputType) - implements CommandFlag { + record FlagDataImpl(String name, List aliases, ParameterType inputType) + implements FlagData { } + default boolean isSwitch() { + return inputType() == null; + } } diff --git a/core/src/main/java/dev/velix/imperat/context/ParamTypeRegistry.java b/core/src/main/java/dev/velix/imperat/context/ParamTypeRegistry.java new file mode 100644 index 00000000..9e4aabf4 --- /dev/null +++ b/core/src/main/java/dev/velix/imperat/context/ParamTypeRegistry.java @@ -0,0 +1,64 @@ +package dev.velix.imperat.context; + +import dev.velix.imperat.command.parameters.type.ParameterEnum; +import dev.velix.imperat.command.parameters.type.ParameterType; +import dev.velix.imperat.command.parameters.type.ParameterTypes; +import dev.velix.imperat.context.internal.CommandFlag; +import dev.velix.imperat.exception.SourceException; +import dev.velix.imperat.util.Registry; +import dev.velix.imperat.util.TypeUtility; +import dev.velix.imperat.util.TypeWrap; +import org.jetbrains.annotations.ApiStatus; + +import java.lang.reflect.Type; +import java.util.Optional; + +@ApiStatus.Internal +public final class ParamTypeRegistry extends Registry> { + + private final ParameterEnum genericEnumType = new ParameterEnum<>(); + + private ParamTypeRegistry() { + super(); + registerResolver(Boolean.class, ParameterTypes.bool()); + registerResolver(String.class, ParameterTypes.string()); + registerResolver(CommandFlag.class, ParameterTypes.flag()); + } + + public static ParamTypeRegistry createDefault() { + return new ParamTypeRegistry<>(); + } + + private SourceException exception(String raw, + Class clazzRequired) { + return new SourceException( + "Error while parsing argument '%s', It's not a valid %s", raw, clazzRequired.getSimpleName() + ); + } + + public void registerResolver(Type type, ParameterType resolver) { + if (TypeUtility.areRelatedTypes(type, Enum.class)) return; + setData(type, resolver); + } + + @SuppressWarnings("unchecked") + public Optional> getResolver(Type type) { + if (TypeWrap.of(type).isSubtypeOf(Number.class)) + return Optional.of(ParameterTypes.numeric((Class) type)); + + //TODO make check for enum + return Optional.ofNullable(getData(TypeUtility.primitiveToBoxed(type)).orElseGet(() -> { + if (TypeUtility.areRelatedTypes(type, Enum.class)) { + return genericEnumType; + } + + for (var registeredType : getKeys()) { + if (TypeUtility.areRelatedTypes(type, registeredType)) { + return getData(registeredType).orElse(null); + } + } + return null; + })); + } + +} diff --git a/core/src/main/java/dev/velix/imperat/context/ResolvedContext.java b/core/src/main/java/dev/velix/imperat/context/ResolvedContext.java index 778304a1..ef430b1e 100644 --- a/core/src/main/java/dev/velix/imperat/context/ResolvedContext.java +++ b/core/src/main/java/dev/velix/imperat/context/ResolvedContext.java @@ -3,9 +3,11 @@ import dev.velix.imperat.command.Command; import dev.velix.imperat.command.CommandUsage; import dev.velix.imperat.command.parameters.CommandParameter; -import dev.velix.imperat.context.internal.ResolvedArgument; +import dev.velix.imperat.command.parameters.type.ParameterType; +import dev.velix.imperat.context.internal.Argument; +import dev.velix.imperat.context.internal.CommandFlag; +import dev.velix.imperat.context.internal.CommandInputStream; import dev.velix.imperat.exception.ImperatException; -import dev.velix.imperat.resolvers.ValueResolver; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,13 +44,13 @@ public interface ResolvedContext extends ExecutionContext { * @return the argument resolved from raw into a value */ @Nullable - ResolvedArgument getResolvedArgument(Command command, String name); + Argument getResolvedArgument(Command command, String name); /** * @param command the command/subcommand with certain args * @return the command/subcommand's resolved args */ - List> getResolvedArguments(Command command); + List> getResolvedArguments(Command command); /** * @return all {@link Command} that have been used in this context @@ -57,14 +59,14 @@ public interface ResolvedContext extends ExecutionContext { Iterable> getCommandsUsed(); /** - * @return an ordered collection of {@link ResolvedArgument} just like how they were entered + * @return an ordered collection of {@link Argument} just like how they were entered * NOTE: the flags are NOT included as a resolved argument, it's treated differently */ - Collection> getResolvedArguments(); + Collection> getResolvedArguments(); /** * Resolves the raw input and - * the parameters into arguments {@link ResolvedArgument} + * the parameters into arguments {@link Argument} * * @param command the command owning the argument * @param raw the raw input @@ -84,17 +86,23 @@ void resolveArgument( /** * Resolves flag the in the context * + * @param flagDetected the optional flag-parameter detected * @param flagRaw the flag itself raw input * @param flagInputRaw the flag's value if present - * @param flagInputValue the flag's input value resolved by {@link ValueResolver} - * @param flagDetected the optional flag-parameter detected + * @param flagInputValue the flag's input value resolved by {@link ParameterType#resolve(ExecutionContext, CommandInputStream)} */ - void resolveFlag( + default void resolveFlag( + FlagData flagDetected, String flagRaw, @Nullable String flagInputRaw, - @Nullable Object flagInputValue, - CommandFlag flagDetected - ); + @Nullable Object flagInputValue + ) { + resolveFlag( + new CommandFlag(flagDetected, flagRaw, flagInputRaw, flagInputValue) + ); + } + + void resolveFlag(CommandFlag flag); /** * Fetches the last used resolved command diff --git a/core/src/main/java/dev/velix/imperat/context/TokenIterator.java b/core/src/main/java/dev/velix/imperat/context/TokenIterator.java deleted file mode 100644 index 976720bc..00000000 --- a/core/src/main/java/dev/velix/imperat/context/TokenIterator.java +++ /dev/null @@ -1,51 +0,0 @@ -package dev.velix.imperat.context; - -import dev.velix.imperat.exception.TokenParseException; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -import java.util.Iterator; - -/** - * Represents a class made specifically to - * tokenize and manage strings - */ -@ApiStatus.Internal -final class TokenIterator implements Iterator { - - private final String source; - private int position = -1; - - TokenIterator(String input) { - this.source = input; - } - - public @Nullable Character peek() { - try { - return source.charAt(position + 1); - } catch (StringIndexOutOfBoundsException ex) { - return null; - } - } - - @Override - public boolean hasNext() { - return peek() != null; - } - - @Override - public Character next() throws TokenParseException { - if (!hasNext()) - throw createException(); - - Character res = peek(); - position++; - return res; - } - - TokenParseException createException() { - return new TokenParseException("Buffer overrun while parsing args"); - } - - -} diff --git a/core/src/main/java/dev/velix/imperat/context/ValueResolverRegistry.java b/core/src/main/java/dev/velix/imperat/context/ValueResolverRegistry.java deleted file mode 100644 index 3d5a5fb7..00000000 --- a/core/src/main/java/dev/velix/imperat/context/ValueResolverRegistry.java +++ /dev/null @@ -1,113 +0,0 @@ -package dev.velix.imperat.context; - -import dev.velix.imperat.command.parameters.CommandParameter; -import dev.velix.imperat.context.internal.sur.Cursor; -import dev.velix.imperat.exception.ImperatException; -import dev.velix.imperat.exception.InvalidUUIDException; -import dev.velix.imperat.exception.SourceException; -import dev.velix.imperat.resolvers.ValueResolver; -import dev.velix.imperat.util.Registry; -import dev.velix.imperat.util.TypeUtility; -import org.jetbrains.annotations.ApiStatus; - -import java.lang.reflect.Type; -import java.util.UUID; - -@ApiStatus.Internal -public final class ValueResolverRegistry extends Registry> { - - private final EnumValueResolver enumValueResolver = new EnumValueResolver(); - - private ValueResolverRegistry() { - super(); - registerResolver(String.class, ((context, parameter, cursor, raw) -> raw)); - registerResolver(Integer.class, (context, parameter, cursor, raw) -> { - if (TypeUtility.isInteger(raw)) { - return Integer.parseInt(raw); - } else { - throw exception(raw, Integer.class); - } - }); - registerResolver(Long.class, (context, parameter, cursor, raw) -> { - if (TypeUtility.isLong(raw)) { - return Long.parseLong(raw); - } else { - throw exception(raw, Long.class); - } - }); - registerResolver(Boolean.class, (context, parameter, cursor, raw) -> { - if (TypeUtility.isBoolean(raw)) { - return Boolean.valueOf(raw); - } else { - throw exception(raw, Boolean.class); - } - }); - registerResolver(Double.class, (context, parameter, cursor, raw) -> { - if (TypeUtility.isDouble(raw)) { - return Double.parseDouble(raw); - } else { - throw exception(raw, Double.class); - } - }); - - registerResolver(UUID.class, (context, parameter, cursor, raw) -> { - try { - return UUID.fromString(raw); - } catch (IllegalArgumentException ex) { - throw new InvalidUUIDException(raw); - } - }); - } - - public static ValueResolverRegistry createDefault() { - return new ValueResolverRegistry<>(); - } - - private SourceException exception(String raw, - Class clazzRequired) { - return new SourceException( - "Error while parsing argument '%s', It's not a valid %s", raw, clazzRequired.getSimpleName() - ); - } - - public void registerResolver(Type type, ValueResolver resolver) { - if (TypeUtility.areRelatedTypes(type, Enum.class)) return; - setData(type, resolver); - } - - public ValueResolver getResolver(Type type) { - - return getData(TypeUtility.primitiveToBoxed(type)).orElseGet(() -> { - if (TypeUtility.areRelatedTypes(type, Enum.class)) { - return enumValueResolver; - } - - for (var registeredType : getKeys()) { - if (TypeUtility.areRelatedTypes(type, registeredType)) { - return getData(registeredType).orElse(null); - } - } - return null; - }); - } - - @ApiStatus.Internal - @SuppressWarnings({"rawtypes", "unchecked"}) - final class EnumValueResolver implements ValueResolver> { - - @Override - public Enum resolve( - ExecutionContext context, - CommandParameter parameter, - Cursor cursor, - String raw - ) throws ImperatException { - var enumType = (Class) parameter.valueType(); - try { - return Enum.valueOf(enumType, raw.toUpperCase()); - } catch (EnumConstantNotPresentException ex) { - throw new SourceException("Invalid " + enumType.getSimpleName() + " '" + raw + "'"); - } - } - } -} diff --git a/core/src/main/java/dev/velix/imperat/context/internal/ResolvedArgument.java b/core/src/main/java/dev/velix/imperat/context/internal/Argument.java similarity index 86% rename from core/src/main/java/dev/velix/imperat/context/internal/ResolvedArgument.java rename to core/src/main/java/dev/velix/imperat/context/internal/Argument.java index 6501c583..5725e8f7 100644 --- a/core/src/main/java/dev/velix/imperat/context/internal/ResolvedArgument.java +++ b/core/src/main/java/dev/velix/imperat/context/internal/Argument.java @@ -6,14 +6,14 @@ import org.jetbrains.annotations.Nullable; @ApiStatus.Internal -public record ResolvedArgument( +public record Argument( @Nullable String raw, CommandParameter parameter, int index, @Nullable Object value ) { @Override public String toString() { - return "ResolvedArgument{" + + return "Argument{" + "raw='" + raw + '\'' + ", parameter=" + parameter.format() + ", index=" + index + diff --git a/core/src/main/java/dev/velix/imperat/context/internal/CommandFlag.java b/core/src/main/java/dev/velix/imperat/context/internal/CommandFlag.java new file mode 100644 index 00000000..5518d6ee --- /dev/null +++ b/core/src/main/java/dev/velix/imperat/context/internal/CommandFlag.java @@ -0,0 +1,12 @@ +package dev.velix.imperat.context.internal; + +import dev.velix.imperat.context.FlagData; + +public record CommandFlag(FlagData flag, String flagRaw, + String flagRawInput, Object value) { + + public boolean isSwitch() { + return flag.inputType() == null; + } + +} diff --git a/core/src/main/java/dev/velix/imperat/context/internal/CommandInputStream.java b/core/src/main/java/dev/velix/imperat/context/internal/CommandInputStream.java new file mode 100644 index 00000000..9fae453f --- /dev/null +++ b/core/src/main/java/dev/velix/imperat/context/internal/CommandInputStream.java @@ -0,0 +1,83 @@ +package dev.velix.imperat.context.internal; + +import dev.velix.imperat.command.CommandUsage; +import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.context.ArgumentQueue; +import dev.velix.imperat.context.Source; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +public interface CommandInputStream { + + @NotNull Cursor cursor(); + + @Nullable CommandParameter currentParameter(); + + Optional> peekParameter(); + + Optional> popParameter(); + + @Nullable Character currentLetter(); + + Optional peekLetter(); + + Optional popLetter(); + + @Nullable String currentRaw(); + + Optional peekRaw(); + + Optional popRaw(); + + boolean hasNextLetter(); + + boolean hasNextRaw(); + + boolean hasNextParameter(); + + @NotNull ArgumentQueue getRawQueue(); + + @NotNull CommandUsage getUsage(); + + default boolean skip() { + final Cursor cursor = cursor(); + int prevRaw = cursor.raw; + cursor.shift(ShiftTarget.ALL, ShiftOperation.RIGHT); + return cursor.raw > prevRaw; + } + + default boolean skipRaw() { + final Cursor cursor = cursor(); + int prevRaw = cursor.raw; + cursor.shift(ShiftTarget.RAW_ONLY, ShiftOperation.RIGHT); + return cursor.raw > prevRaw; + } + + default boolean skipParameter() { + final Cursor cursor = cursor(); + int prevParam = cursor.parameter; + cursor.shift(ShiftTarget.PARAMETER_ONLY, ShiftOperation.RIGHT); + return cursor.parameter > prevParam; + } + + default int currentRawPosition() { + return cursor().raw; + } + + default int currentParameterPosition() { + return cursor().parameter; + } + + + default int rawsLength() { + return getRawQueue().size(); + } + + default int parametersLength() { + return getUsage().size(); + } + + +} diff --git a/core/src/main/java/dev/velix/imperat/context/internal/CommandInputStreamImpl.java b/core/src/main/java/dev/velix/imperat/context/internal/CommandInputStreamImpl.java new file mode 100644 index 00000000..300259e0 --- /dev/null +++ b/core/src/main/java/dev/velix/imperat/context/internal/CommandInputStreamImpl.java @@ -0,0 +1,148 @@ +package dev.velix.imperat.context.internal; + +import dev.velix.imperat.command.CommandUsage; +import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.context.ArgumentQueue; +import dev.velix.imperat.context.Source; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.Optional; + +final class CommandInputStreamImpl implements CommandInputStream { + + private final static char WHITE_SPACE = ' '; + + private int letterPos = 0; + + private final Cursor cursor; + + private final ArgumentQueue queue; + private final CommandUsage usage; + + CommandInputStreamImpl(ArgumentQueue queue, CommandUsage usage) { + this.queue = queue; + this.usage = usage; + this.cursor = new Cursor<>(this, 0, 0); + } + + @Override + public @NotNull Cursor cursor() { + return cursor; + } + + @Override + public @Nullable CommandParameter currentParameter() { + return usage.getParameter(cursor.parameter); + } + + @Override + public Optional> peekParameter() { + return Optional.ofNullable( + usage.getParameter(cursor.parameter + 1) + ); + } + + @Override + public Optional> popParameter() { + cursor.shift(ShiftTarget.PARAMETER_ONLY, ShiftOperation.RIGHT); + return Optional.ofNullable(currentParameter()); + } + + @Override + public String currentRaw() { + if (cursor.raw >= queue.size()) return null; + return queue.get(cursor.raw); + } + + @Override + public Character currentLetter() { + if (cursor.raw >= queue.size()) { + return null; + } + @NotNull String raw = Objects.requireNonNull(currentRaw()); + return raw.charAt(letterPos); + } + + @Override + public Optional peekLetter() { + int nextLetterPos = letterPos + 1; + String currentRaw = Objects.requireNonNull(currentRaw()); + if (nextLetterPos == currentRaw.length()) { + return Optional.of(WHITE_SPACE); + } else if (nextLetterPos > currentRaw.length()) { + //next raw + return peekRaw().map((nextRaw) -> nextRaw.charAt(0)); + } else { + //nextLetterPos < currentRaw.length() + return Optional.of(currentRaw.charAt(nextLetterPos)); + } + } + + @Override + public Optional popLetter() { + letterPos++; + String currentRaw = Objects.requireNonNull(currentRaw()); + if (letterPos == currentRaw.length()) { + return Optional.of(WHITE_SPACE); + } else if (letterPos > currentRaw.length()) { + //next raw + letterPos = 0; + return popRaw().map((nextRaw) -> nextRaw.charAt(0)); + } else { + //nextLetterPos < currentRaw.length() + return Optional.of(currentRaw.charAt(letterPos)); + } + } + + @Override + public Optional peekRaw() { + int next = cursor.raw + 1; + return Optional.ofNullable( + queue.getOr(next, null) + ); + } + + @Override + public Optional popRaw() { + cursor.shift(ShiftTarget.RAW_ONLY, ShiftOperation.RIGHT); + return Optional.ofNullable(currentRaw()); + } + + @Override + public boolean hasNextLetter() { + return Optional.ofNullable(currentRaw()) + .map((raw) -> (letterPos < raw.length())).orElse(false); + } + + @Override + public boolean hasNextRaw() { + return cursor.raw < queue.size(); + } + + @Override + public boolean hasNextParameter() { + return cursor.parameter < usage.size(); + } + + @Override + public @NotNull ArgumentQueue getRawQueue() { + return queue; + } + + @Override + public @NotNull CommandUsage getUsage() { + return usage; + } + + @Override + public int rawsLength() { + return queue.size(); + } + + @Override + public int parametersLength() { + return usage.size(); + } +} diff --git a/core/src/main/java/dev/velix/imperat/context/internal/Cursor.java b/core/src/main/java/dev/velix/imperat/context/internal/Cursor.java new file mode 100644 index 00000000..f342c671 --- /dev/null +++ b/core/src/main/java/dev/velix/imperat/context/internal/Cursor.java @@ -0,0 +1,77 @@ +package dev.velix.imperat.context.internal; + +import dev.velix.imperat.context.Source; + +import java.util.function.IntUnaryOperator; + +public final class Cursor { + + CommandInputStream stream; + int parameter, raw; + + Cursor(CommandInputStream stream, int parameter, int raw) { + this.stream = stream; + this.parameter = parameter; + this.raw = raw; + } + + void shift(ShiftTarget shift, IntUnaryOperator operator) { + switch (shift) { + case RAW_ONLY -> this.raw = operator.applyAsInt(raw); + case PARAMETER_ONLY -> this.parameter = operator.applyAsInt(parameter); + default -> { + this.raw = operator.applyAsInt(raw); + this.parameter = operator.applyAsInt(parameter); + } + } + } + + void shift(ShiftTarget target, ShiftOperation operation) { + shift(target, operation.operator); + } + + boolean canContinue( + ShiftTarget target + ) { + return target.canContinue(this); + } + + boolean isLast(ShiftTarget shiftTarget, int maxParams, int maxRaws) { + if (shiftTarget == ShiftTarget.PARAMETER_ONLY) + return parameter == maxParams - 1; + else if (shiftTarget == ShiftTarget.RAW_ONLY) + return raw == maxRaws - 1; + else + return parameter == maxParams - 1 && raw == maxRaws - 1; + } + + boolean isLast(ShiftTarget shiftTarget) { + return isLast(shiftTarget, stream.parametersLength(), stream.rawsLength()); + } + + int maxRaws() { + return stream.rawsLength(); + } + + int maxParameters() { + return stream.parametersLength(); + } + + @Override + public boolean equals(final Object o) { + if (o == this) return true; + if (!(o instanceof Cursor other)) return false; + if (this.parameter != other.parameter) return false; + return this.raw == other.raw; + } + + @Override + public int hashCode() { + final int PRIME = 59; + int result = 1; + result = result * PRIME + parameter; + result = result * PRIME + raw; + return result; + } + +} diff --git a/core/src/main/java/dev/velix/imperat/context/internal/PositionShiftCondition.java b/core/src/main/java/dev/velix/imperat/context/internal/PositionShiftCondition.java new file mode 100644 index 00000000..fc73a3f2 --- /dev/null +++ b/core/src/main/java/dev/velix/imperat/context/internal/PositionShiftCondition.java @@ -0,0 +1,8 @@ +package dev.velix.imperat.context.internal; + +import org.jetbrains.annotations.NotNull; + +@FunctionalInterface +interface PositionShiftCondition { + boolean canContinue(@NotNull Cursor cursor); +} \ No newline at end of file diff --git a/core/src/main/java/dev/velix/imperat/context/internal/ResolvedContextImpl.java b/core/src/main/java/dev/velix/imperat/context/internal/ResolvedContextImpl.java index 00ad4e53..1d2185bc 100644 --- a/core/src/main/java/dev/velix/imperat/context/internal/ResolvedContextImpl.java +++ b/core/src/main/java/dev/velix/imperat/context/internal/ResolvedContextImpl.java @@ -6,12 +6,12 @@ import dev.velix.imperat.command.parameters.CommandParameter; import dev.velix.imperat.command.parameters.NumericParameter; import dev.velix.imperat.command.parameters.NumericRange; -import dev.velix.imperat.context.*; -import dev.velix.imperat.context.internal.sur.SmartUsageResolve; +import dev.velix.imperat.context.Context; +import dev.velix.imperat.context.ResolvedContext; +import dev.velix.imperat.context.Source; import dev.velix.imperat.exception.ImperatException; import dev.velix.imperat.exception.NumberOutOfRangeException; import dev.velix.imperat.resolvers.ContextResolver; -import dev.velix.imperat.resolvers.ValueResolver; import dev.velix.imperat.util.Registry; import dev.velix.imperat.util.TypeUtility; import org.jetbrains.annotations.ApiStatus; @@ -35,11 +35,11 @@ final class ResolvedContextImpl extends ContextImpl implements ResolvedContext { private final CommandUsage usage; - private final Registry flagRegistry = new Registry<>(); + private final Registry flagRegistry = new Registry<>(); //per command/subcommand because the class 'Command' can be also treated as a sub command - private final Registry, Registry>> resolvedArgumentsPerCommand = new Registry<>(LinkedHashMap::new); + private final Registry, Registry>> resolvedArgumentsPerCommand = new Registry<>(LinkedHashMap::new); //all resolved arguments EXCEPT for subcommands and flags. - private final Registry> allResolvedArgs = new Registry<>(LinkedHashMap::new); + private final Registry> allResolvedArgs = new Registry<>(LinkedHashMap::new); //last command used private Command lastCommand; @@ -63,7 +63,7 @@ final class ResolvedContextImpl extends ContextImpl impleme * @return the argument resolved from raw into a value */ @Override - public @Nullable ResolvedArgument getResolvedArgument(Command command, String name) { + public @Nullable Argument getResolvedArgument(Command command, String name) { return resolvedArgumentsPerCommand.getData(command) .flatMap((resolvedArgs) -> resolvedArgs.getData(name)) .orElse(null); @@ -74,9 +74,9 @@ final class ResolvedContextImpl extends ContextImpl impleme * @return the command/subcommand's resolved args in as a new array-list */ @Override - public List> getResolvedArguments(Command command) { + public List> getResolvedArguments(Command command) { return resolvedArgumentsPerCommand.getData(command) - .map((argMap) -> (List>) new ArrayList>(argMap.getAll())) + .map((argMap) -> (List>) new ArrayList>(argMap.getAll())) .orElse(Collections.emptyList()); } @@ -89,11 +89,11 @@ public List> getResolvedArguments(Command command) { } /** - * @return an ordered collection of {@link ResolvedArgument} just like how they were entered + * @return an ordered collection of {@link Argument} just like how they were entered * NOTE: the flags are NOT included as a resolved argument, it's treated differently */ @Override - public Collection> getResolvedArguments() { + public Collection> getResolvedArguments() { return allResolvedArgs.getAll(); } @@ -102,12 +102,12 @@ public Collection> getResolvedArguments() { * * @param name the name of the command * @return the value of the resolved argument - * @see ResolvedArgument + * @see Argument */ @Override @SuppressWarnings("unchecked") public @Nullable T getArgument(String name) { - return (T) allResolvedArgs.getData(name).map(ResolvedArgument::value) + return (T) allResolvedArgs.getData(name).map(Argument::value) .orElse(null); } @@ -141,19 +141,19 @@ public Collection> getResolvedArguments() { * @return the resolved flag arguments */ @Override - public Collection getResolvedFlags() { + public Collection getResolvedFlags() { return flagRegistry.getAll(); } @Override - public Optional getFlag(String flagName) { + public Optional getFlag(String flagName) { return flagRegistry.getData(flagName); } /** * Fetches the flag input value - * returns null if the flag is a {@link CommandSwitch} + * returns null if the flag is a switch * OR if the value hasn't been resolved somehow * * @param flagName the flag name @@ -163,7 +163,7 @@ public Optional getFlag(String flagName) { @SuppressWarnings("unchecked") public @Nullable T getFlagValue(String flagName) { return (T) getFlag(flagName) - .map(ResolvedFlag::value) + .map(CommandFlag::value) .orElse(null); } @@ -176,9 +176,9 @@ public void resolve() throws ImperatException { if (arguments().isEmpty()) return; - SmartUsageResolve handler = SmartUsageResolve.create(command(), usage); - handler.resolve(dispatcher, this); - this.lastCommand = handler.getCommand(); + SmartUsageResolve sur = SmartUsageResolve.create(command(), this, usage); + sur.resolve(); + this.lastCommand = sur.getCommand(); } @@ -199,7 +199,7 @@ public void resolveArgument( NumericRange range = numericParameter.getRange(); throw new NumberOutOfRangeException(numericParameter, (Number) value, range); } - final ResolvedArgument argument = new ResolvedArgument<>(raw, parameter, index, value); + final Argument argument = new Argument<>(raw, parameter, index, value); resolvedArgumentsPerCommand.update(command, (existingResolvedArgs) -> { if (existingResolvedArgs != null) { return existingResolvedArgs.setData(parameter.name(), argument); @@ -209,23 +209,9 @@ public void resolveArgument( allResolvedArgs.setData(parameter.name(), argument); } - /** - * Resolves flag the in the context - * - * @param flagRaw the flag itself raw input - * @param flagInputRaw the flag's value if present - * @param flagInputValue the input value resolved using {@link ValueResolver} - * @param flagDetected the optional flag-parameter detected - */ @Override - public void resolveFlag( - String flagRaw, - @Nullable String flagInputRaw, - @Nullable Object flagInputValue, - CommandFlag flagDetected - ) { - flagRegistry.setData(flagDetected.name(), - new ResolvedFlag(flagDetected, flagRaw, flagInputRaw, flagInputValue)); + public void resolveFlag(CommandFlag flag) { + flagRegistry.setData(flag.flag().name(), flag); } /** diff --git a/core/src/main/java/dev/velix/imperat/context/internal/ResolvedFlag.java b/core/src/main/java/dev/velix/imperat/context/internal/ResolvedFlag.java deleted file mode 100644 index 304332ce..00000000 --- a/core/src/main/java/dev/velix/imperat/context/internal/ResolvedFlag.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.velix.imperat.context.internal; - -import dev.velix.imperat.context.CommandFlag; -import dev.velix.imperat.context.CommandSwitch; - -public record ResolvedFlag(CommandFlag flag, String flagRaw, - String flagRawInput, Object value) { - - public boolean isSwitch() { - return flag instanceof CommandSwitch; - } - -} diff --git a/core/src/main/java/dev/velix/imperat/context/internal/sur/ShiftOperation.java b/core/src/main/java/dev/velix/imperat/context/internal/ShiftOperation.java similarity index 75% rename from core/src/main/java/dev/velix/imperat/context/internal/sur/ShiftOperation.java rename to core/src/main/java/dev/velix/imperat/context/internal/ShiftOperation.java index 11aa2581..096c05fb 100644 --- a/core/src/main/java/dev/velix/imperat/context/internal/sur/ShiftOperation.java +++ b/core/src/main/java/dev/velix/imperat/context/internal/ShiftOperation.java @@ -1,8 +1,8 @@ -package dev.velix.imperat.context.internal.sur; +package dev.velix.imperat.context.internal; import java.util.function.IntUnaryOperator; -public enum ShiftOperation { +enum ShiftOperation { RIGHT(value -> value + 1), LEFT(value -> value - 1); diff --git a/core/src/main/java/dev/velix/imperat/context/internal/ShiftTarget.java b/core/src/main/java/dev/velix/imperat/context/internal/ShiftTarget.java new file mode 100644 index 00000000..52e9b8a9 --- /dev/null +++ b/core/src/main/java/dev/velix/imperat/context/internal/ShiftTarget.java @@ -0,0 +1,22 @@ +package dev.velix.imperat.context.internal; + +enum ShiftTarget { + + RAW_ONLY((pos) -> pos.raw < pos.maxRaws()), + + PARAMETER_ONLY((pos) -> pos.parameter < pos.maxParameters()), + + ALL((pos) -> + pos.raw < pos.maxRaws() && pos.parameter < pos.maxParameters()); + + private final PositionShiftCondition canContinueCheck; + + ShiftTarget(PositionShiftCondition canContinueCheck) { + this.canContinueCheck = canContinueCheck; + } + + boolean canContinue(Cursor cursor) { + return canContinueCheck.canContinue(cursor); + } + +} \ No newline at end of file diff --git a/core/src/main/java/dev/velix/imperat/context/internal/SmartUsageResolve.java b/core/src/main/java/dev/velix/imperat/context/internal/SmartUsageResolve.java new file mode 100644 index 00000000..9bdf4a0e --- /dev/null +++ b/core/src/main/java/dev/velix/imperat/context/internal/SmartUsageResolve.java @@ -0,0 +1,233 @@ +package dev.velix.imperat.context.internal; + +import dev.velix.imperat.command.Command; +import dev.velix.imperat.command.CommandUsage; +import dev.velix.imperat.command.parameters.CommandParameter; +import dev.velix.imperat.command.parameters.FlagParameter; +import dev.velix.imperat.context.Context; +import dev.velix.imperat.context.FlagData; +import dev.velix.imperat.context.ResolvedContext; +import dev.velix.imperat.context.Source; +import dev.velix.imperat.exception.ImperatException; +import dev.velix.imperat.exception.SourceException; +import dev.velix.imperat.supplier.OptionalValueSupplier; +import org.jetbrains.annotations.Nullable; + +final class SmartUsageResolve { + + private final CommandUsage usage; + private final ResolvedContext context; + private final CommandInputStream stream; + private Command command; + + SmartUsageResolve(Command command, ResolvedContext context, CommandUsage usage) { + this.command = command; + this.context = context; + this.usage = usage; + this.stream = new CommandInputStreamImpl<>(context.arguments(), usage); + } + + public static SmartUsageResolve create( + Command command, + ResolvedContext context, + CommandUsage usage + ) { + return new SmartUsageResolve<>(command, context, usage); + } + + private void handleEmptyOptional(CommandParameter optionalEmptyParameter) throws ImperatException { + if (optionalEmptyParameter.isFlag()) { + FlagParameter flagParameter = optionalEmptyParameter.asFlagParameter(); + FlagData flag = flagParameter.flagData(); + Object value; + if (flag.isSwitch()) + value = false; + else { + value = flagParameter.getDefaultValueSupplier() + .supply(context.source()); + } + + context.resolveFlag(flag, null, null, value); + } else { + context.resolveArgument(command, null, stream.cursor() + .parameter, optionalEmptyParameter, getDefaultValue(context, optionalEmptyParameter)); + } + } + + public void resolve() throws ImperatException { + + final int lengthWithoutFlags = usage.getParametersWithoutFlags().size(); + while (stream.hasNextParameter()) { + + CommandParameter currentParameter = stream.currentParameter(); + assert currentParameter != null; + + String currentRaw = stream.currentRaw(); + if (currentRaw == null) { + //ImperatDebugger.visualize("Filling empty optional args"); + if (currentParameter.isOptional()) { + handleEmptyOptional(currentParameter); + } + while (stream.hasNextParameter()) { + var param = stream.popParameter() + .filter(CommandParameter::isOptional) + .orElse(null); + if (param == null) break; + handleEmptyOptional( + param + ); + } + //System.out.println("Closed at position= " + position); + break; + } + + if (currentParameter.isCommand()) { + Command parameterSubCmd = (Command) currentParameter; + if (parameterSubCmd.hasName(currentRaw)) this.command = parameterSubCmd; + else throw new SourceException("Unknown sub-command '" + currentRaw + "'"); + stream.skip(); + continue; + } + + FlagData flag = usage.getFlagFromRaw(currentRaw); + if (flag == null && currentParameter.isFlag()) { + assert currentParameter.isFlag(); + //non identified + //TODO write Free-flags logic + //TODO check if it's free flag + + context.resolveFlag( + null, + null, + getDefaultValue(context, currentParameter), + currentParameter.asFlagParameter().flagData() + ); + stream.skipParameter(); + continue; + } + //TODO fix the infinity error + var value = currentParameter.type().resolve(context, stream); + if (value instanceof CommandFlag commandFlag) { + context.resolveFlag(commandFlag); + stream.skip(); + } else if (currentParameter.isOptional()) { + resolveOptional( + currentRaw, + currentParameter, + lengthWithoutFlags, + value + ); + } else { + //required + resolveRequired(currentRaw, currentParameter, value); + } + + + } + + } + + + private void resolveRequired( + String currentRaw, + CommandParameter currentParameter, + Object resolveResult + ) throws ImperatException { + //ImperatDebugger.visualize("Resolving required param '%s' with value '%s'", currentParameter.format(), resolveResult); + context.resolveArgument( + command, + currentRaw, + stream.currentParameterPosition(), + currentParameter, + resolveResult + ); + stream.skip(); + } + + private void resolveOptional( + String currentRaw, + CommandParameter currentParameter, + int lengthWithoutFlags, + Object resolveResult + ) throws ImperatException { + // /cmd [o1] [o2] + // /cmd hi bye + int currentParameterPosition = stream.currentParameterPosition(); + + if (stream.rawsLength() < lengthWithoutFlags) { + int diff = lengthWithoutFlags - stream.rawsLength(); + + + if (!stream.cursor().isLast(ShiftTarget.PARAMETER_ONLY)) { + //[o1] + + if (diff > 1) { + + CommandParameter nextParam = stream.peekParameter().filter(CommandParameter::isRequired).orElse(null); + if (nextParam == null) { + //optional next parameter + stream.skipParameter(); + return; + } + //required NEXT parameter + context.resolveArgument( + command, + currentRaw, + currentParameterPosition, + currentParameter, + getDefaultValue(context, currentParameter) + ); + + context.resolveArgument( + command, currentRaw, + currentParameterPosition + 1, + nextParam, resolveResult + ); + + stream.skipParameter(); + } else { + context.resolveArgument( + command, + currentRaw, + currentParameterPosition, + currentParameter, + resolveResult + ); + stream.skip(); + } + + } else { + + context.resolveArgument( + command, + currentRaw, + currentParameterPosition, + currentParameter, + getDefaultValue(context, currentParameter) + ); + + //shifting the parameters && raw again, so it can start after the new shift + stream.skipParameter(); + } + return; + } + + //raw.size >= parameterSize + context.resolveArgument( + command, currentRaw, + currentParameterPosition, + currentParameter, + resolveResult + ); + stream.skip(); + } + + private @Nullable T getDefaultValue(Context context, CommandParameter parameter) { + OptionalValueSupplier optionalSupplier = parameter.getDefaultValueSupplier(); + return optionalSupplier.supply(context.source()); + } + + public Command getCommand() { + return command; + } +} diff --git a/core/src/main/java/dev/velix/imperat/context/internal/sur/CommandInputStream.java b/core/src/main/java/dev/velix/imperat/context/internal/sur/CommandInputStream.java deleted file mode 100644 index 11b84040..00000000 --- a/core/src/main/java/dev/velix/imperat/context/internal/sur/CommandInputStream.java +++ /dev/null @@ -1,47 +0,0 @@ -package dev.velix.imperat.context.internal.sur; - -import dev.velix.imperat.command.parameters.CommandParameter; -import dev.velix.imperat.context.Source; -import org.jetbrains.annotations.NotNull; - -import java.util.Optional; - -public interface CommandInputStream { - - @NotNull Cursor cursor(); - - CommandParameter currentParameter(); - - String currentRaw(); - - Character currentLetter(); - - Optional peekLetter(); - - Optional popLetter(); - - Optional peekRaw(); - - Optional popRaw(); - - boolean hasNextLetter(); - - boolean hasNextRaw(); - - boolean hasNextParameter(); - - default boolean skip() { - final Cursor cursor = cursor(); - int prevRaw = cursor.raw; - cursor.shift(ShiftTarget.ALL, ShiftOperation.RIGHT); - return cursor.raw > prevRaw; - } - - default boolean skipRaw() { - final Cursor cursor = cursor(); - int prevRaw = cursor.raw; - cursor.shift(ShiftTarget.RAW_ONLY, ShiftOperation.RIGHT); - return cursor.raw > prevRaw; - } - -} diff --git a/core/src/main/java/dev/velix/imperat/context/internal/sur/Cursor.java b/core/src/main/java/dev/velix/imperat/context/internal/sur/Cursor.java deleted file mode 100644 index 75969efb..00000000 --- a/core/src/main/java/dev/velix/imperat/context/internal/sur/Cursor.java +++ /dev/null @@ -1,113 +0,0 @@ -package dev.velix.imperat.context.internal.sur; - -import dev.velix.imperat.command.parameters.CommandParameter; -import dev.velix.imperat.context.ArgumentQueue; -import dev.velix.imperat.context.Context; -import dev.velix.imperat.context.Source; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.function.IntUnaryOperator; - -public final class Cursor { - - int parameter, raw; - - public Cursor(int parameter, int raw) { - this.parameter = parameter; - this.raw = raw; - } - - public void shift(ShiftTarget shift, IntUnaryOperator operator) { - switch (shift) { - case RAW_ONLY -> this.raw = operator.applyAsInt(raw); - case PARAMETER_ONLY -> this.parameter = operator.applyAsInt(parameter); - default -> { - this.raw = operator.applyAsInt(raw); - this.parameter = operator.applyAsInt(parameter); - } - } - } - - public void shift(ShiftTarget target, ShiftOperation operation) { - shift(target, operation.operator); - } - - public boolean canContinue( - ShiftTarget target, - List> parameters, - ArgumentQueue queue - ) { - return target.canContinue(this, parameters.size(), queue.size()); - } - - public boolean isLast(ShiftTarget shiftTarget, int maxParams, int maxRaws) { - if (shiftTarget == ShiftTarget.PARAMETER_ONLY) - return parameter == maxParams - 1; - else if (shiftTarget == ShiftTarget.RAW_ONLY) - return raw == maxRaws - 1; - else - return parameter == maxParams - 1 && raw == maxRaws - 1; - } - - public boolean isLast(ShiftTarget shiftTarget, List> params, ArgumentQueue raws) { - return isLast(shiftTarget, params.size(), raws.size()); - } - - - public @Nullable CommandParameter peekParameter(List> parameters) { - return parameters.get(this.parameter); - } - - public @Nullable String peekRaw(ArgumentQueue raws) { - try { - return raws.get(raw); - } catch (Exception ex) { - return null; - } - } - - public @Nullable String nextRaw(ArgumentQueue queue) { - shift(ShiftTarget.RAW_ONLY, ShiftOperation.RIGHT); - return peekRaw(queue); - } - - public @Nullable String nextRaw(Context context) { - return nextRaw(context.arguments()); - } - - public int getParameter() { - return this.parameter; - } - - public int getRaw() { - return this.raw; - } - - public void setParameter(int parameter) { - this.parameter = parameter; - } - - public void setRaw(int raw) { - this.raw = raw; - } - - public boolean equals(final Object o) { - if (o == this) return true; - if (!(o instanceof Cursor other)) return false; - if (this.getParameter() != other.getParameter()) return false; - return this.getRaw() == other.getRaw(); - } - - public int hashCode() { - final int PRIME = 59; - int result = 1; - result = result * PRIME + this.getParameter(); - result = result * PRIME + this.getRaw(); - return result; - } - - public String toString() { - return "Cursor(parameter=" + this.getParameter() + ", raw=" + this.getRaw() + ")"; - } -} diff --git a/core/src/main/java/dev/velix/imperat/context/internal/sur/PositionShiftCondition.java b/core/src/main/java/dev/velix/imperat/context/internal/sur/PositionShiftCondition.java deleted file mode 100644 index 69fc1144..00000000 --- a/core/src/main/java/dev/velix/imperat/context/internal/sur/PositionShiftCondition.java +++ /dev/null @@ -1,6 +0,0 @@ -package dev.velix.imperat.context.internal.sur; - -@FunctionalInterface -interface PositionShiftCondition { - boolean canContinue(Cursor cursor, int maxRaw, int maxParameter); -} \ No newline at end of file diff --git a/core/src/main/java/dev/velix/imperat/context/internal/sur/ShiftTarget.java b/core/src/main/java/dev/velix/imperat/context/internal/sur/ShiftTarget.java deleted file mode 100644 index 1894765e..00000000 --- a/core/src/main/java/dev/velix/imperat/context/internal/sur/ShiftTarget.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.velix.imperat.context.internal.sur; - -public enum ShiftTarget { - - RAW_ONLY((pos, maxParam, maxRaw) -> pos.getRaw() < maxRaw), - - PARAMETER_ONLY((pos, maxParam, maxRaw) -> pos.getParameter() < maxParam), - - ALL((pos, maxRaw, maxParameter) -> - pos.getRaw() < maxRaw && pos.getParameter() < maxParameter); - - private final PositionShiftCondition canContinueCheck; - - ShiftTarget(PositionShiftCondition canContinueCheck) { - this.canContinueCheck = canContinueCheck; - } - - boolean canContinue(Cursor cursor, int maxParam, int maxRaw) { - return canContinueCheck.canContinue(cursor, maxParam, maxRaw); - } - -} \ No newline at end of file diff --git a/core/src/main/java/dev/velix/imperat/context/internal/sur/SmartUsageResolve.java b/core/src/main/java/dev/velix/imperat/context/internal/sur/SmartUsageResolve.java deleted file mode 100644 index 65f17df5..00000000 --- a/core/src/main/java/dev/velix/imperat/context/internal/sur/SmartUsageResolve.java +++ /dev/null @@ -1,330 +0,0 @@ -package dev.velix.imperat.context.internal.sur; - -import dev.velix.imperat.Imperat; -import dev.velix.imperat.command.Command; -import dev.velix.imperat.command.CommandUsage; -import dev.velix.imperat.command.parameters.CommandParameter; -import dev.velix.imperat.context.*; -import dev.velix.imperat.exception.ImperatException; -import dev.velix.imperat.exception.SourceException; -import dev.velix.imperat.exception.TokenParseException; -import dev.velix.imperat.resolvers.ValueResolver; -import dev.velix.imperat.supplier.OptionalValueSupplier; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; - -public final class SmartUsageResolve { - - private final CommandUsage usage; - private final Cursor cursor = new Cursor<>(0, 0); - private Command command; - - SmartUsageResolve(Command command, - CommandUsage usage) { - - this.command = command; - this.usage = usage; - } - - public static SmartUsageResolve create( - Command command, - CommandUsage usage - ) { - return new SmartUsageResolve<>(command, usage); - } - - public void resolve(Imperat dispatcher, ResolvedContext context) throws ImperatException { - final List> parameterList = new ArrayList<>(usage.getParameters()); - final ArgumentQueue raws = context.arguments().copy(); - - final int lengthWithoutFlags = usage.getParametersWithoutFlags().size(); - - while (cursor.canContinue(ShiftTarget.PARAMETER_ONLY, parameterList, raws)) { - CommandParameter currentParameter = cursor.peekParameter(parameterList); - assert currentParameter != null; - - String currentRaw = cursor.peekRaw(raws); - //ImperatDebugger.visualize("Current raw= '%s' at %s" , currentRaw, position.raw); - if (currentRaw == null) { - //ImperatDebugger.visualize("Filling empty optional args"); - for (int i = cursor.parameter; i < parameterList.size(); i++) { - final CommandParameter optionalEmptyParameter = getNextParameter(parameterList); - //all parameters from here must be optional - //adding the absent optional args with their default values - - if (optionalEmptyParameter.isFlag()) { - CommandFlag flag = optionalEmptyParameter.asFlagParameter().flagData(); - Object value; - if (flag instanceof CommandSwitch) value = false; - else { - optionalEmptyParameter.asFlagParameter().getDefaultValueSupplier(); - value = optionalEmptyParameter.asFlagParameter() - .getDefaultValueSupplier().supply(context.source()); - } - - context.resolveFlag(null, null, value, flag); - } else { - context.resolveArgument(command, null, cursor.parameter, optionalEmptyParameter, getDefaultValue(context, optionalEmptyParameter)); - } - cursor.shift(ShiftTarget.PARAMETER_ONLY, ShiftOperation.RIGHT); - } - //System.out.println("Closed at position= " + position); - break; - } - - if (currentParameter.isCommand()) { - - //visualize("Found command %s at %s", currentParameter.getName(), position.parameter); - Command parameterSubCmd = (Command) currentParameter; - if (parameterSubCmd.hasName(currentRaw)) { - this.command = parameterSubCmd; - } else { - throw new SourceException("Unknown sub-command '" + currentRaw + "'"); - } - - cursor.shift(ShiftTarget.ALL, ShiftOperation.RIGHT); - continue; - } - - CommandFlag flag = usage.getFlagFromRaw(currentRaw); - if (flag != null && currentParameter.isFlag()) { - //ImperatDebugger.visualize("Found flag raw '%s' at %s", currentRaw, position.raw); - //shifting raw only - //check if it's switch - if (flag instanceof CommandSwitch) { - //input-value is true because the flag is present - context.resolveFlag(currentRaw, null, true, flag); - } else { - // [-g ] - //shifting again to get the expected value - cursor.shift(ShiftTarget.RAW_ONLY, ShiftOperation.RIGHT); - String flagValueRawInput = cursor.peekRaw(raws); - Object flagDefaultValue = getDefaultValue(context, currentParameter); - if (flagValueRawInput == null) { - - if (flagDefaultValue == null) { - throw new SourceException(String.format( - "Missing required flag value-input to be filled '%s'", flag.format()) - ); - } - - context.resolveFlag(currentRaw, null, flagDefaultValue, flag); - cursor.shift(ShiftTarget.PARAMETER_ONLY, ShiftOperation.RIGHT); - continue; - } - - //flag value raw input is NOT NULL, resolving the value input - ValueResolver valueResolver = dispatcher.getValueResolver(flag.inputType()); - if (valueResolver == null) { - throw new SourceException("Cannot find resolver for flag with input valueType '" + flag.name() + "'"); - } - context.resolveFlag( - currentRaw, - flagValueRawInput, - getResult(valueResolver, context, flagValueRawInput, currentParameter), - flag - ); - } - - cursor.shift(ShiftTarget.ALL, ShiftOperation.RIGHT); - continue; - } else if (flag == null && currentParameter.isFlag()) { - assert currentParameter.isFlag(); - //non identified - //TODO write Free-flags logic - //TODO check if it's free flag - - context.resolveFlag( - null, - null, - getDefaultValue(context, currentParameter), - currentParameter.asFlagParameter().flagData() - ); - - cursor.shift(ShiftTarget.PARAMETER_ONLY, ShiftOperation.RIGHT); - continue; - } - - //argument input - ValueResolver resolver = dispatcher.getValueResolver(currentParameter); - if (resolver == null) - throw new SourceException("Cannot find resolver for valueType '" + currentParameter.valueType().getTypeName() + "'"); - - if (currentParameter.isOptional()) { - //visualize("Optional parameter '%s' at position %s", currentParameter.getName(), position.parameter); - //visualize("raws-size= %s, usageMaxWithoutFlags= %s", raws.size() , (lengthWithoutFlags)); - //optional argument handling - resolveOptional(context, resolver, raws, - parameterList, currentRaw, currentParameter, - lengthWithoutFlags); - - } else { - //visualize("Required parameter '%s' at position %s", currentParameter.getName(), position.parameter); - resolveRequired(context, resolver, - raws, currentRaw, currentParameter); - } - - } - - } - - private @NotNull CommandParameter getNextParameter(List> parameterList) throws SourceException { - final CommandParameter optionalEmptyParameter = cursor.peekParameter(parameterList); - assert optionalEmptyParameter != null; - //visualize("Parameter at %s = %s", i, parameter.format(command)); - if (!optionalEmptyParameter.isOptional()) { - //cannot happen if no bugs, but just in case - throw new SourceException("Missing required parameters to be filled '%s'", optionalEmptyParameter.format()); - } - return optionalEmptyParameter; - } - - private void resolveRequired( - ResolvedContext context, - ValueResolver resolver, - ArgumentQueue raws, - String currentRaw, - CommandParameter currentParameter - ) throws ImperatException { - Object resolveResult; - if (currentParameter.isGreedy()) { - - StringBuilder builder = new StringBuilder(); - final int maxRaws = raws.size(); - for (int i = cursor.raw; i < maxRaws; i++) { - builder.append(cursor.peekRaw(raws)); - if (i != maxRaws - 1) { - builder.append(' '); - } - cursor.shift(ShiftTarget.RAW_ONLY, ShiftOperation.RIGHT); - } - - if (builder.isEmpty()) { - throw new TokenParseException("Failed to parse greedy argument '" - + currentParameter.format() + "'"); - } - resolveResult = builder.toString(); - - cursor.shift(ShiftTarget.PARAMETER_ONLY, ShiftOperation.RIGHT); - } else { - resolveResult = this.getResult(resolver, context, currentRaw, currentParameter); - cursor.shift(ShiftTarget.ALL, ShiftOperation.RIGHT); - } - //ImperatDebugger.visualize("Resolving required param '%s' with value '%s'", currentParameter.format(), resolveResult); - context.resolveArgument(command, currentRaw, cursor.parameter, - currentParameter, resolveResult); - } - - private void resolveOptional( - ResolvedContext context, - ValueResolver resolver, - ArgumentQueue raws, - List> parameterList, - String currentRaw, - CommandParameter currentParameter, - int lengthWithoutFlags - ) throws ImperatException { - // /cmd [o1] [o2] - // /cmd hi bye - if (raws.size() < lengthWithoutFlags) { - int diff = lengthWithoutFlags - raws.size(); - - Object resolveResult = getResult(resolver, context, currentRaw, currentParameter); - - if (!cursor.isLast(ShiftTarget.PARAMETER_ONLY, parameterList, raws)) { - //[o1] - if (diff > 1) { - CommandParameter nextParam = getNextParam(cursor.parameter + 1, parameterList, (param) -> !param.isOptional()); - if (nextParam == null) { - //optional next parameter - cursor.shift(ShiftTarget.PARAMETER_ONLY, ShiftOperation.RIGHT); - return; - } - - //required NEXT parameter - context.resolveArgument(command, currentRaw, cursor.parameter, - currentParameter, getDefaultValue(context, currentParameter)); - - context.resolveArgument(command, currentRaw, cursor.parameter + 1, - nextParam, resolveResult); - - cursor.shift(ShiftTarget.PARAMETER_ONLY, ShiftOperation.RIGHT); - } else { - context.resolveArgument(command, currentRaw, cursor.parameter, - currentParameter, resolveResult); - cursor.shift(ShiftTarget.ALL, ShiftOperation.RIGHT); - } - - } else { - - context.resolveArgument(command, currentRaw, cursor.parameter, - currentParameter, getDefaultValue(context, currentParameter)); - - //shifting the parameters && raw again, so it can start after the new shift - cursor.shift(ShiftTarget.PARAMETER_ONLY, ShiftOperation.RIGHT); - } - return; - } - - //raw.size >= parameterSize - Object resolveResult; - if (currentParameter.isGreedy()) { - - StringBuilder builder = new StringBuilder(); - final int maxRaws = raws.size(); - - for (int i = cursor.raw; i < maxRaws; i++) { - builder.append(cursor.peekRaw(raws)); - if (i != maxRaws - 1) { - builder.append(' '); - } - cursor.shift(ShiftTarget.RAW_ONLY, ShiftOperation.RIGHT); - } - - if (builder.isEmpty()) { - throw new TokenParseException("Failed to parse greedy argument '" - + currentParameter.format() + "'"); - } - - resolveResult = builder.toString(); - - cursor.shift(ShiftTarget.PARAMETER_ONLY, ShiftOperation.RIGHT); - context.resolveArgument(command, currentRaw, cursor.parameter, - currentParameter, resolveResult); - } else { - resolveResult = getResult(resolver, context, currentRaw, currentParameter); - context.resolveArgument(command, currentRaw, cursor.parameter, currentParameter, resolveResult); - cursor.shift(ShiftTarget.ALL, ShiftOperation.RIGHT); - } - - } - - private T getResult(ValueResolver resolver, ExecutionContext context, String raw, CommandParameter currentParameter) throws ImperatException { - return resolver.resolve(context, currentParameter, cursor, raw); - } - - - private @Nullable CommandParameter getNextParam(int start, List> parameters, - Predicate> parameterCondition) { - if (start >= parameters.size()) return null; - for (int i = start; i < parameters.size(); i++) { - if (parameterCondition.test(parameters.get(i))) - return parameters.get(i); - - } - return null; - } - - private @Nullable T getDefaultValue(Context context, CommandParameter parameter) { - OptionalValueSupplier optionalSupplier = parameter.getDefaultValueSupplier(); - return optionalSupplier.supply(context.source()); - } - - public Command getCommand() { - return command; - } -} diff --git a/core/src/main/java/dev/velix/imperat/resolvers/ValueResolver.java b/core/src/main/java/dev/velix/imperat/resolvers/ValueResolver.java deleted file mode 100644 index 3b687122..00000000 --- a/core/src/main/java/dev/velix/imperat/resolvers/ValueResolver.java +++ /dev/null @@ -1,34 +0,0 @@ -package dev.velix.imperat.resolvers; - -import dev.velix.imperat.command.parameters.CommandParameter; -import dev.velix.imperat.context.ExecutionContext; -import dev.velix.imperat.context.Source; -import dev.velix.imperat.context.internal.sur.Cursor; -import dev.velix.imperat.exception.ImperatException; -import org.jetbrains.annotations.ApiStatus; - -/** - * Resolves the argument of a certain parameter - * converting the parameter into a value for - * the argument input, just using the context, parameter required and the raw input. - * - * @param the valueType of the command sender/source - * @param the valueType of value that the parameter requires - */ -@ApiStatus.AvailableSince("1.0.0") -public interface ValueResolver { - - /** - * @param context the context for the command - * @param parameter the parameter corresponding to the raw - * @param cursor the cursor for controlling the position of raws and parameters - * @param raw the required raw of the command. - * @return the resolved output from the input object - */ - T resolve( - ExecutionContext context, - CommandParameter parameter, - Cursor cursor, - String raw - ) throws ImperatException; -} diff --git a/core/src/test/java/dev/velix/imperat/TestRun.java b/core/src/test/java/dev/velix/imperat/TestRun.java index 55aaa21e..cc045bd3 100644 --- a/core/src/test/java/dev/velix/imperat/TestRun.java +++ b/core/src/test/java/dev/velix/imperat/TestRun.java @@ -4,8 +4,7 @@ import dev.velix.imperat.command.CommandUsage; import dev.velix.imperat.command.parameters.CommandParameter; import dev.velix.imperat.command.tree.CommandDispatch; -import dev.velix.imperat.commands.annotations.TestCommand; -import dev.velix.imperat.commands.annotations.examples.*; +import dev.velix.imperat.commands.annotations.examples.BanCommand; import dev.velix.imperat.context.ArgumentQueue; import dev.velix.imperat.util.TypeWrap; import dev.velix.imperat.verification.UsageVerifier; @@ -34,15 +33,13 @@ public class TestRun { static { IMPERAT.setUsageVerifier(UsageVerifier.typeTolerantVerifier()); - IMPERAT.registerValueResolver(Group.class, new GroupValueResolver()); - IMPERAT.registerSuggestionResolver(new GroupSuggestionResolver()); //IMPERAT.registerCommand(GROUP_CMD); IMPERAT.registerCommand(MULTIPLE_OPTIONAL_CMD); //IMPERAT.registerCommand(CHAINED_SUBCOMMANDS_CMD); - IMPERAT.registerCommand(new AnnotatedGroupCommand()); - IMPERAT.registerCommand(new TestCommand()); - IMPERAT.registerCommand(new OptionalArgCommand()); + //IMPERAT.registerCommand(new AnnotatedGroupCommand()); + //IMPERAT.registerCommand(new TestCommand()); + //IMPERAT.registerCommand(new OptionalArgCommand()); IMPERAT.registerCommand(new BanCommand()); } diff --git a/core/src/test/java/dev/velix/imperat/TestSmartUsageResolve.java b/core/src/test/java/dev/velix/imperat/TestSmartUsageResolve.java index 5009677e..de3954bc 100644 --- a/core/src/test/java/dev/velix/imperat/TestSmartUsageResolve.java +++ b/core/src/test/java/dev/velix/imperat/TestSmartUsageResolve.java @@ -3,6 +3,7 @@ import dev.velix.imperat.command.Command; import dev.velix.imperat.command.CommandUsage; +import dev.velix.imperat.commands.annotations.examples.BanCommand; import dev.velix.imperat.context.ArgumentQueue; import dev.velix.imperat.context.Context; import dev.velix.imperat.context.ResolvedContext; @@ -20,7 +21,6 @@ public class TestSmartUsageResolve { TestSmartUsageResolve() { - } private final static ContextFactory FACTORY = IMPERAT.getContextFactory(); @@ -57,6 +57,7 @@ private static void test(String cmdLine, ResolvedArgsData expected) { @Test public void banWithSwitchFlag() { + IMPERAT.registerCommand(new BanCommand()); test( "ban mqzen", diff --git a/core/src/test/java/dev/velix/imperat/commands/annotations/examples/GroupValueResolver.java b/core/src/test/java/dev/velix/imperat/commands/annotations/examples/GroupValueResolver.java deleted file mode 100644 index 93ad5fae..00000000 --- a/core/src/test/java/dev/velix/imperat/commands/annotations/examples/GroupValueResolver.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.velix.imperat.commands.annotations.examples; - -import dev.velix.imperat.TestSource; -import dev.velix.imperat.command.parameters.CommandParameter; -import dev.velix.imperat.context.ExecutionContext; -import dev.velix.imperat.context.internal.sur.Cursor; -import dev.velix.imperat.exception.ImperatException; -import dev.velix.imperat.exception.SourceException; -import dev.velix.imperat.resolvers.ValueResolver; - -public final class GroupValueResolver implements ValueResolver { - - @Override - public Group resolve( - ExecutionContext context, - CommandParameter parameter, - Cursor cursor, - String raw - ) throws ImperatException { - /*if (sender.isConsole()) { - throw new SenderErrorException("Invalid group '%s'", raw); - }*/ - var group = GroupRegistry.getInstance() - .getData(raw); - if (group.isEmpty()) { - throw new SourceException("Invalid group '%s'", raw); - } - return group.get(); - } -}