Skip to content

Commit 6448e58

Browse files
committed
feat: introduce ArgumentContext to replace ArgumentTiming
The commit introduces the ArgumentContext class, deprecating the ArgumentTiming functionality in the CommandContext.java file, providing a comprehensive log of consumed inputs and parsing duration. This change enhances the precision and maintainability by providing more details about the argument parsing stage. CommandTree.java is also updated to adopt ArgumentContext in place of ArgumentTiming during parsing, while ArgumentContext.java is a new file introduced to store specific details about each argument's consumption of input and its parse times.
1 parent b4b0b9c commit 6448e58

5 files changed

Lines changed: 267 additions & 19 deletions

File tree

cloud-core/src/main/java/cloud/commandframework/CommandTree.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import cloud.commandframework.arguments.compound.CompoundArgument;
2929
import cloud.commandframework.arguments.compound.FlagArgument;
3030
import cloud.commandframework.arguments.parser.ArgumentParseResult;
31+
import cloud.commandframework.context.ArgumentContext;
3132
import cloud.commandframework.context.CommandContext;
3233
import cloud.commandframework.exceptions.AmbiguousNodeException;
3334
import cloud.commandframework.exceptions.ArgumentParseException;
@@ -236,12 +237,20 @@ private CommandTree(final @NonNull CommandManager<C> commandManager) {
236237
final Node<CommandArgument<C, ?>> child = childIterator.next();
237238
if (child.getValue() != null) {
238239
final CommandArgument<C, ?> argument = child.getValue();
239-
final CommandContext.ArgumentTiming argumentTiming = commandContext.createTiming(argument);
240+
final ArgumentContext<C, ?> argumentContext = commandContext.createArgumentContext(argument);
240241

241-
argumentTiming.setStart(System.nanoTime());
242+
// Copy the current queue so that we can deduce the captured input.
243+
final List<String> currentQueue = new LinkedList<>(commandQueue);
244+
245+
argumentContext.markStart();
242246
commandContext.setCurrentArgument(argument);
247+
243248
final ArgumentParseResult<?> result = argument.getParser().parse(commandContext, commandQueue);
244-
argumentTiming.setEnd(System.nanoTime(), result.getFailure().isPresent());
249+
argumentContext.markEnd();
250+
argumentContext.success(!result.getFailure().isPresent());
251+
252+
currentQueue.removeAll(commandQueue);
253+
argumentContext.consumedInput(currentQueue);
245254

246255
if (result.getParsedValue().isPresent()) {
247256
parsedArguments.add(child.getValue());
@@ -425,22 +434,31 @@ private CommandTree(final @NonNull CommandManager<C> commandManager) {
425434
}
426435

427436
final CommandArgument<C, ?> argument = child.getValue();
428-
final CommandContext.ArgumentTiming argumentTiming = commandContext.createTiming(argument);
437+
final ArgumentContext<C, ?> argumentContext = commandContext.createArgumentContext(argument);
429438

430439
// START: Parsing
431-
argumentTiming.setStart(System.nanoTime());
440+
argumentContext.markStart();
432441
final ArgumentParseResult<?> result;
433442
final ArgumentParseResult<Boolean> preParseResult = child.getValue().preprocess(
434443
commandContext,
435444
commandQueue
436445
);
437446
if (!preParseResult.getFailure().isPresent() && preParseResult.getParsedValue().orElse(false)) {
438447
commandContext.setCurrentArgument(argument);
448+
449+
// Copy the current queue so that we can deduce the captured input.
450+
final List<String> currentQueue = new LinkedList<>(commandQueue);
451+
439452
result = argument.getParser().parse(commandContext, commandQueue);
453+
454+
// We remove all remaining queue, and then we'll have a list of the captured input.
455+
currentQueue.removeAll(commandQueue);
456+
argumentContext.consumedInput(currentQueue);
440457
} else {
441458
result = preParseResult;
442459
}
443-
argumentTiming.setEnd(System.nanoTime(), result.getFailure().isPresent());
460+
argumentContext.markEnd();
461+
argumentContext.success(!result.getFailure().isPresent());
444462
// END: Parsing
445463

446464
if (result.getParsedValue().isPresent()) {
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
//
2+
// MIT License
3+
//
4+
// Copyright (c) 2022 Alexander Söderberg & Contributors
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files (the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be included in all
14+
// copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
// SOFTWARE.
23+
//
24+
package cloud.commandframework.context;
25+
26+
import cloud.commandframework.arguments.CommandArgument;
27+
import cloud.commandframework.arguments.StaticArgument;
28+
import java.time.Duration;
29+
import java.util.LinkedList;
30+
import java.util.List;
31+
import org.apiguardian.api.API;
32+
import org.checkerframework.checker.nullness.qual.NonNull;
33+
import org.checkerframework.checker.nullness.qual.Nullable;
34+
35+
@API(status = API.Status.MAINTAINED, since = "1.9.0")
36+
public final class ArgumentContext<C, T> {
37+
38+
private final CommandArgument<@NonNull C, @NonNull T> argument;
39+
private final List<String> consumedInput = new LinkedList<>();
40+
41+
/**
42+
* Construct an ArgumentContext object with the given argument.
43+
*
44+
* @param argument the command argument to be assigned to the ArgumentContext
45+
*/
46+
public ArgumentContext(final @NonNull CommandArgument<@NonNull C, @NonNull T> argument) {
47+
this.argument = argument;
48+
}
49+
50+
private long startTime = -1;
51+
private long endTime = -1;
52+
53+
private boolean success;
54+
55+
/**
56+
* Return the associated argument.
57+
*
58+
* @return the argument
59+
*/
60+
public @NonNull CommandArgument<@NonNull C, @NonNull T> argument() {
61+
return this.argument;
62+
}
63+
64+
/**
65+
* Return the duration taken to parse the argument.
66+
*
67+
* @return the argument parse duration
68+
*/
69+
public @NonNull Duration parseDuration() {
70+
if (this.startTime < 0) {
71+
throw new IllegalStateException("No start time has been registered");
72+
} else if (this.endTime < 0) {
73+
throw new IllegalStateException("No end time has been registered");
74+
}
75+
return Duration.ofNanos(this.endTime - this.startTime);
76+
}
77+
78+
/**
79+
* Set the start time.
80+
*/
81+
public void markStart() {
82+
this.startTime = System.nanoTime();
83+
}
84+
85+
/**
86+
* Set the end time.
87+
*/
88+
public void markEnd() {
89+
this.endTime = System.nanoTime();
90+
}
91+
92+
long startTime() {
93+
return this.startTime;
94+
}
95+
96+
long endTime() {
97+
return this.endTime;
98+
}
99+
100+
/**
101+
* Return whether the argument was parsed successfully.
102+
*
103+
* @return {@code true} if the value was parsed successfully, {@code false} if not
104+
*/
105+
public boolean success() {
106+
return this.success;
107+
}
108+
109+
/**
110+
* Set whether the argument was parsed successfully.
111+
*
112+
* @param success {@code true} if the value was parsed successfully, {@code false} if not
113+
*/
114+
public void success(final boolean success) {
115+
this.success = success;
116+
}
117+
118+
/**
119+
* Add the given input to the list of consumed input.
120+
*
121+
* @param consumedInput the consumed input
122+
*/
123+
public void consumedInput(final @NonNull List<@NonNull String> consumedInput) {
124+
this.consumedInput.addAll(consumedInput);
125+
}
126+
127+
/**
128+
* Returns the exact alias used, if the argument was static. If no alias was consumed
129+
* then {@code null} is returned.
130+
*
131+
* @return the exact alias, or {@code null}
132+
*/
133+
public @Nullable String exactAlias() {
134+
if (!this.success || !(this.argument instanceof StaticArgument)) {
135+
return null;
136+
}
137+
return this.consumedInput.get(0);
138+
}
139+
}

cloud-core/src/main/java/cloud/commandframework/context/CommandContext.java

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,17 @@
3636
import cloud.commandframework.keys.CloudKeyHolder;
3737
import cloud.commandframework.keys.SimpleCloudKey;
3838
import cloud.commandframework.permission.CommandPermission;
39+
import cloud.commandframework.types.tuples.Pair;
3940
import java.util.Collections;
4041
import java.util.HashMap;
4142
import java.util.LinkedList;
43+
import java.util.List;
4244
import java.util.Map;
45+
import java.util.NoSuchElementException;
4346
import java.util.Optional;
4447
import java.util.function.Function;
4548
import java.util.function.Supplier;
49+
import java.util.stream.Collectors;
4650
import org.apiguardian.api.API;
4751
import org.checkerframework.checker.nullness.qual.NonNull;
4852
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -56,7 +60,7 @@
5660
public class CommandContext<C> {
5761

5862
private final CaptionVariableReplacementHandler captionVariableReplacementHandler;
59-
private final Map<CommandArgument<C, ?>, ArgumentTiming> argumentTimings = new HashMap<>();
63+
private final List<ArgumentContext<C, ?>> argumentContexts = new LinkedList<>();
6064
private final FlagContext flagContext = FlagContext.create();
6165
private final Map<CloudKey<?>, Object> internalStorage = new HashMap<>();
6266
private final C commandSender;
@@ -603,20 +607,103 @@ public <T> T computeIfAbsent(
603607
*
604608
* @param argument Argument
605609
* @return Created timing instance
610+
*
611+
* @deprecated This has been replaced by {@link #createArgumentContext(CommandArgument)}
606612
*/
613+
@API(status = API.Status.DEPRECATED, since = "1.9.0")
614+
@Deprecated
607615
public @NonNull ArgumentTiming createTiming(final @NonNull CommandArgument<C, ?> argument) {
608-
final ArgumentTiming argumentTiming = new ArgumentTiming();
609-
this.argumentTimings.put(argument, argumentTiming);
610-
return argumentTiming;
616+
return new ArgumentTiming();
611617
}
612618

613619
/**
614620
* Get an immutable view of the argument timings map
615621
*
616622
* @return Argument timings
623+
* @deprecated Replaced with {@link #argumentContexts()}
617624
*/
625+
@API(status = API.Status.DEPRECATED, since = "1.9.0")
626+
@Deprecated
618627
public @NonNull Map<CommandArgument<@NonNull C, @NonNull ?>, ArgumentTiming> getArgumentTimings() {
619-
return Collections.unmodifiableMap(this.argumentTimings);
628+
return this.argumentContexts.stream()
629+
.map(context -> Pair.of(
630+
context.argument(),
631+
new ArgumentTiming(
632+
context.startTime(),
633+
context.endTime(),
634+
context.success()
635+
)
636+
)
637+
).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
638+
}
639+
640+
/**
641+
* Create an argument context instance for the given argument
642+
*
643+
* @param argument the argument
644+
* @return the created context
645+
* @param <T> the type of the argument
646+
* @since 1.9.0
647+
*/
648+
@API(status = API.Status.MAINTAINED, since = "1.9.0")
649+
public <T> @NonNull ArgumentContext<C, T> createArgumentContext(final @NonNull CommandArgument<C, T> argument) {
650+
final ArgumentContext<C, T> argumentContext = new ArgumentContext<>(argument);
651+
this.argumentContexts.add(argumentContext);
652+
return argumentContext;
653+
}
654+
655+
/**
656+
* Returns the context for the given argument
657+
*
658+
* @param argument the argument
659+
* @return the context
660+
* @param <T> the type of the argument
661+
* @since 1.9.0
662+
*/
663+
@API(status = API.Status.MAINTAINED, since = "1.9.0")
664+
@SuppressWarnings("unchecked")
665+
public <T> @NonNull ArgumentContext<C, T> argumentContext(final @NonNull CommandArgument<C, T> argument) {
666+
return this.argumentContexts.stream().filter(context -> context.argument().equals(argument))
667+
.findFirst()
668+
.map(context -> (ArgumentContext<C, T>) context)
669+
.orElseThrow(NoSuchElementException::new);
670+
}
671+
672+
/**
673+
* Returns the context for the argument at the given position
674+
*
675+
* @param position the position
676+
* @return the context
677+
* @since 1.9.0
678+
*/
679+
@API(status = API.Status.MAINTAINED, since = "1.9.0")
680+
public @NonNull ArgumentContext<C, ?> argumentContext(final int position) {
681+
return this.argumentContexts.get(position);
682+
}
683+
684+
/**
685+
* Return the context for the argument with the given name.
686+
*
687+
* @param name the name
688+
* @return the context
689+
* @since 1.9.0
690+
*/
691+
@API(status = API.Status.MAINTAINED, since = "1.9.0")
692+
public @NonNull ArgumentContext<C, ?> argumentContext(final String name) {
693+
return this.argumentContexts.stream().filter(context -> context.argument().getName().equals(name))
694+
.findFirst()
695+
.orElseThrow(NoSuchElementException::new);
696+
}
697+
698+
/**
699+
* Return an unmodifiable view of the stored argument contexts
700+
*
701+
* @return the contexts
702+
* @since 1.9.0
703+
*/
704+
@API(status = API.Status.MAINTAINED, since = "1.9.0")
705+
public @NonNull List<@NonNull ArgumentContext<@NonNull C, @NonNull ?>> argumentContexts() {
706+
return Collections.unmodifiableList(this.argumentContexts);
620707
}
621708

622709
/**
@@ -680,16 +767,19 @@ public void setCurrentArgument(final @Nullable CommandArgument<C, ?> argument) {
680767
* parsed.
681768
* <p>
682769
* The times are measured in nanoseconds.
770+
*
771+
* @deprecated Superseded by {@link ArgumentContext}
683772
*/
684-
@API(status = API.Status.STABLE)
773+
@Deprecated
774+
@API(status = API.Status.DEPRECATED, since = "1.9.0")
685775
public static final class ArgumentTiming {
686776

687777
private long start;
688778
private long end;
689779
private boolean success;
690780

691781
/**
692-
* Created a new argument timing instance
782+
* Creates a new argument timing instance
693783
*
694784
* @param start Start time (in nanoseconds)
695785
* @param end End time (in nanoseconds)
@@ -702,7 +792,7 @@ public ArgumentTiming(final long start, final long end, final boolean success) {
702792
}
703793

704794
/**
705-
* Created a new argument timing instance without an end time
795+
* Creates a new argument timing instance without an end time
706796
*
707797
* @param start Start time (in nanoseconds)
708798
*/
@@ -712,7 +802,7 @@ public ArgumentTiming(final long start) {
712802
}
713803

714804
/**
715-
* Created a new argument timing instance
805+
* Creates a new argument timing instance
716806
*/
717807
public ArgumentTiming() {
718808
this(-1, -1, false);

0 commit comments

Comments
 (0)