Skip to content

Commit

Permalink
feat: introduce ArgumentContext to replace ArgumentTiming (#461)
Browse files Browse the repository at this point in the history
  • Loading branch information
Citymonstret authored and jpenilla committed Nov 9, 2023
1 parent 5d2db23 commit 9bdd50b
Show file tree
Hide file tree
Showing 6 changed files with 370 additions and 19 deletions.
30 changes: 24 additions & 6 deletions cloud-core/src/main/java/cloud/commandframework/CommandTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import cloud.commandframework.arguments.compound.CompoundArgument;
import cloud.commandframework.arguments.compound.FlagArgument;
import cloud.commandframework.arguments.parser.ArgumentParseResult;
import cloud.commandframework.context.ArgumentContext;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.exceptions.AmbiguousNodeException;
import cloud.commandframework.exceptions.ArgumentParseException;
Expand Down Expand Up @@ -235,12 +236,20 @@ private CommandTree(final @NonNull CommandManager<C> commandManager) {
final Node<CommandArgument<C, ?>> child = childIterator.next();
if (child.getValue() != null) {
final CommandArgument<C, ?> argument = child.getValue();
final CommandContext.ArgumentTiming argumentTiming = commandContext.createTiming(argument);
final ArgumentContext<C, ?> argumentContext = commandContext.createArgumentContext(argument);

argumentTiming.setStart(System.nanoTime());
// Copy the current queue so that we can deduce the captured input.
final List<String> currentQueue = new LinkedList<>(commandQueue);

argumentContext.markStart();
commandContext.setCurrentArgument(argument);

final ArgumentParseResult<?> result = argument.getParser().parse(commandContext, commandQueue);
argumentTiming.setEnd(System.nanoTime(), result.getFailure().isPresent());
argumentContext.markEnd();
argumentContext.success(!result.getFailure().isPresent());

currentQueue.removeAll(commandQueue);
argumentContext.consumedInput(currentQueue);

if (result.getParsedValue().isPresent()) {
parsedArguments.add(child.getValue());
Expand Down Expand Up @@ -424,22 +433,31 @@ private CommandTree(final @NonNull CommandManager<C> commandManager) {
}

final CommandArgument<C, ?> argument = child.getValue();
final CommandContext.ArgumentTiming argumentTiming = commandContext.createTiming(argument);
final ArgumentContext<C, ?> argumentContext = commandContext.createArgumentContext(argument);

// START: Parsing
argumentTiming.setStart(System.nanoTime());
argumentContext.markStart();
final ArgumentParseResult<?> result;
final ArgumentParseResult<Boolean> preParseResult = child.getValue().preprocess(
commandContext,
commandQueue
);
if (!preParseResult.getFailure().isPresent() && preParseResult.getParsedValue().orElse(false)) {
commandContext.setCurrentArgument(argument);

// Copy the current queue so that we can deduce the captured input.
final List<String> currentQueue = new LinkedList<>(commandQueue);

result = argument.getParser().parse(commandContext, commandQueue);

// We remove all remaining queue, and then we'll have a list of the captured input.
currentQueue.removeAll(commandQueue);
argumentContext.consumedInput(currentQueue);
} else {
result = preParseResult;
}
argumentTiming.setEnd(System.nanoTime(), result.getFailure().isPresent());
argumentContext.markEnd();
argumentContext.success(!result.getFailure().isPresent());
// END: Parsing

if (result.getParsedValue().isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//
// MIT License
//
// Copyright (c) 2022 Alexander Söderberg & Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package cloud.commandframework.context;

import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.arguments.StaticArgument;
import java.time.Duration;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

@API(status = API.Status.MAINTAINED, since = "1.9.0")
public final class ArgumentContext<C, T> {

private final CommandArgument<@NonNull C, @NonNull T> argument;
private final List<String> consumedInput = new LinkedList<>();

/**
* Construct an ArgumentContext object with the given argument.
*
* @param argument the command argument to be assigned to the ArgumentContext
*/
public ArgumentContext(final @NonNull CommandArgument<@NonNull C, @NonNull T> argument) {
this.argument = argument;
}

private long startTime = -1;
private long endTime = -1;

private boolean success;

/**
* Return the associated argument.
*
* @return the argument
*/
public @NonNull CommandArgument<@NonNull C, @NonNull T> argument() {
return this.argument;
}

/**
* Return the duration taken to parse the argument.
*
* @return the argument parse duration
*/
public @NonNull Duration parseDuration() {
if (this.startTime < 0) {
throw new IllegalStateException("No start time has been registered");
} else if (this.endTime < 0) {
throw new IllegalStateException("No end time has been registered");
}
return Duration.ofNanos(this.endTime - this.startTime);
}

/**
* Set the start time.
*/
public void markStart() {
this.startTime = System.nanoTime();
}

/**
* Set the end time.
*/
public void markEnd() {
this.endTime = System.nanoTime();
}

long startTime() {
return this.startTime;
}

long endTime() {
return this.endTime;
}

/**
* Return whether the argument was parsed successfully.
*
* @return {@code true} if the value was parsed successfully, {@code false} if not
*/
public boolean success() {
return this.success;
}

/**
* Set whether the argument was parsed successfully.
*
* @param success {@code true} if the value was parsed successfully, {@code false} if not
*/
public void success(final boolean success) {
this.success = success;
}

/**
* Add the given input to the list of consumed input.
*
* @param consumedInput the consumed input
*/
public void consumedInput(final @NonNull List<@NonNull String> consumedInput) {
this.consumedInput.addAll(consumedInput);
}

/**
* Return the list of consumed input.
*
* @return the list of consumed input
*/
public @NonNull List<@NonNull String> consumedInput() {
return Collections.unmodifiableList(this.consumedInput);
}

/**
* Return the exact alias used, if the argument was static. If no alias was consumed
* then {@code null} is returned.
*
* @return the exact alias, or {@code null}
*/
public @Nullable String exactAlias() {
if (!this.success || !(this.argument instanceof StaticArgument)) {
return null;
}
return this.consumedInput.get(0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,17 @@
import cloud.commandframework.keys.CloudKeyHolder;
import cloud.commandframework.keys.SimpleCloudKey;
import cloud.commandframework.permission.CommandPermission;
import cloud.commandframework.types.tuples.Pair;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand All @@ -56,7 +60,7 @@
public class CommandContext<C> {

private final CaptionVariableReplacementHandler captionVariableReplacementHandler;
private final Map<CommandArgument<C, ?>, ArgumentTiming> argumentTimings = new HashMap<>();
private final List<ArgumentContext<C, ?>> argumentContexts = new LinkedList<>();
private final FlagContext flagContext = FlagContext.create();
private final Map<CloudKey<?>, Object> internalStorage = new HashMap<>();
private final C commandSender;
Expand Down Expand Up @@ -603,20 +607,103 @@ public <T> T computeIfAbsent(
*
* @param argument Argument
* @return Created timing instance
*
* @deprecated This has been replaced by {@link #createArgumentContext(CommandArgument)}
*/
@API(status = API.Status.DEPRECATED, since = "1.9.0")
@Deprecated
public @NonNull ArgumentTiming createTiming(final @NonNull CommandArgument<C, ?> argument) {
final ArgumentTiming argumentTiming = new ArgumentTiming();
this.argumentTimings.put(argument, argumentTiming);
return argumentTiming;
return new ArgumentTiming();
}

/**
* Get an immutable view of the argument timings map
*
* @return Argument timings
* @deprecated Replaced with {@link #argumentContexts()}
*/
@API(status = API.Status.DEPRECATED, since = "1.9.0")
@Deprecated
public @NonNull Map<CommandArgument<@NonNull C, @NonNull ?>, ArgumentTiming> getArgumentTimings() {
return Collections.unmodifiableMap(this.argumentTimings);
return this.argumentContexts.stream()
.map(context -> Pair.of(
context.argument(),
new ArgumentTiming(
context.startTime(),
context.endTime(),
context.success()
)
)
).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
}

/**
* Create an argument context instance for the given argument
*
* @param argument the argument
* @return the created context
* @param <T> the type of the argument
* @since 1.9.0
*/
@API(status = API.Status.MAINTAINED, since = "1.9.0")
public <T> @NonNull ArgumentContext<C, T> createArgumentContext(final @NonNull CommandArgument<C, T> argument) {
final ArgumentContext<C, T> argumentContext = new ArgumentContext<>(argument);
this.argumentContexts.add(argumentContext);
return argumentContext;
}

/**
* Returns the context for the given argument
*
* @param argument the argument
* @return the context
* @param <T> the type of the argument
* @since 1.9.0
*/
@API(status = API.Status.MAINTAINED, since = "1.9.0")
@SuppressWarnings("unchecked")
public <T> @NonNull ArgumentContext<C, T> argumentContext(final @NonNull CommandArgument<C, T> argument) {
return this.argumentContexts.stream().filter(context -> context.argument().equals(argument))
.findFirst()
.map(context -> (ArgumentContext<C, T>) context)
.orElseThrow(NoSuchElementException::new);
}

/**
* Returns the context for the argument at the given position
*
* @param position the position
* @return the context
* @since 1.9.0
*/
@API(status = API.Status.MAINTAINED, since = "1.9.0")
public @NonNull ArgumentContext<C, ?> argumentContext(final int position) {
return this.argumentContexts.get(position);
}

/**
* Return the context for the argument with the given name.
*
* @param name the name
* @return the context
* @since 1.9.0
*/
@API(status = API.Status.MAINTAINED, since = "1.9.0")
public @NonNull ArgumentContext<C, ?> argumentContext(final String name) {
return this.argumentContexts.stream().filter(context -> context.argument().getName().equals(name))
.findFirst()
.orElseThrow(NoSuchElementException::new);
}

/**
* Return an unmodifiable view of the stored argument contexts
*
* @return the contexts
* @since 1.9.0
*/
@API(status = API.Status.MAINTAINED, since = "1.9.0")
public @NonNull List<@NonNull ArgumentContext<@NonNull C, @NonNull ?>> argumentContexts() {
return Collections.unmodifiableList(this.argumentContexts);
}

/**
Expand Down Expand Up @@ -680,16 +767,19 @@ public void setCurrentArgument(final @Nullable CommandArgument<C, ?> argument) {
* parsed.
* <p>
* The times are measured in nanoseconds.
*
* @deprecated Superseded by {@link ArgumentContext}
*/
@API(status = API.Status.STABLE)
@Deprecated
@API(status = API.Status.DEPRECATED, since = "1.9.0")
public static final class ArgumentTiming {

private long start;
private long end;
private boolean success;

/**
* Created a new argument timing instance
* Creates a new argument timing instance
*
* @param start Start time (in nanoseconds)
* @param end End time (in nanoseconds)
Expand All @@ -702,7 +792,7 @@ public ArgumentTiming(final long start, final long end, final boolean success) {
}

/**
* Created a new argument timing instance without an end time
* Creates a new argument timing instance without an end time
*
* @param start Start time (in nanoseconds)
*/
Expand All @@ -712,7 +802,7 @@ public ArgumentTiming(final long start) {
}

/**
* Created a new argument timing instance
* Creates a new argument timing instance
*/
public ArgumentTiming() {
this(-1, -1, false);
Expand Down
Loading

0 comments on commit 9bdd50b

Please sign in to comment.