Skip to content

Commit 1d06ab4

Browse files
Citymonstretjpenilla
authored andcommitted
feat: introduce ArgumentContext to replace ArgumentTiming (#461)
1 parent 358dc6c commit 1d06ab4

File tree

6 files changed

+370
-19
lines changed

6 files changed

+370
-19
lines changed

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;
@@ -235,12 +236,20 @@ private CommandTree(final @NonNull CommandManager<C> commandManager) {
235236
final Node<CommandArgument<C, ?>> child = childIterator.next();
236237
if (child.getValue() != null) {
237238
final CommandArgument<C, ?> argument = child.getValue();
238-
final CommandContext.ArgumentTiming argumentTiming = commandContext.createTiming(argument);
239+
final ArgumentContext<C, ?> argumentContext = commandContext.createArgumentContext(argument);
239240

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

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

426435
final CommandArgument<C, ?> argument = child.getValue();
427-
final CommandContext.ArgumentTiming argumentTiming = commandContext.createTiming(argument);
436+
final ArgumentContext<C, ?> argumentContext = commandContext.createArgumentContext(argument);
428437

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

445463
if (result.getParsedValue().isPresent()) {
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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.Collections;
30+
import java.util.LinkedList;
31+
import java.util.List;
32+
import org.apiguardian.api.API;
33+
import org.checkerframework.checker.nullness.qual.NonNull;
34+
import org.checkerframework.checker.nullness.qual.Nullable;
35+
36+
@API(status = API.Status.MAINTAINED, since = "1.9.0")
37+
public final class ArgumentContext<C, T> {
38+
39+
private final CommandArgument<@NonNull C, @NonNull T> argument;
40+
private final List<String> consumedInput = new LinkedList<>();
41+
42+
/**
43+
* Construct an ArgumentContext object with the given argument.
44+
*
45+
* @param argument the command argument to be assigned to the ArgumentContext
46+
*/
47+
public ArgumentContext(final @NonNull CommandArgument<@NonNull C, @NonNull T> argument) {
48+
this.argument = argument;
49+
}
50+
51+
private long startTime = -1;
52+
private long endTime = -1;
53+
54+
private boolean success;
55+
56+
/**
57+
* Return the associated argument.
58+
*
59+
* @return the argument
60+
*/
61+
public @NonNull CommandArgument<@NonNull C, @NonNull T> argument() {
62+
return this.argument;
63+
}
64+
65+
/**
66+
* Return the duration taken to parse the argument.
67+
*
68+
* @return the argument parse duration
69+
*/
70+
public @NonNull Duration parseDuration() {
71+
if (this.startTime < 0) {
72+
throw new IllegalStateException("No start time has been registered");
73+
} else if (this.endTime < 0) {
74+
throw new IllegalStateException("No end time has been registered");
75+
}
76+
return Duration.ofNanos(this.endTime - this.startTime);
77+
}
78+
79+
/**
80+
* Set the start time.
81+
*/
82+
public void markStart() {
83+
this.startTime = System.nanoTime();
84+
}
85+
86+
/**
87+
* Set the end time.
88+
*/
89+
public void markEnd() {
90+
this.endTime = System.nanoTime();
91+
}
92+
93+
long startTime() {
94+
return this.startTime;
95+
}
96+
97+
long endTime() {
98+
return this.endTime;
99+
}
100+
101+
/**
102+
* Return whether the argument was parsed successfully.
103+
*
104+
* @return {@code true} if the value was parsed successfully, {@code false} if not
105+
*/
106+
public boolean success() {
107+
return this.success;
108+
}
109+
110+
/**
111+
* Set whether the argument was parsed successfully.
112+
*
113+
* @param success {@code true} if the value was parsed successfully, {@code false} if not
114+
*/
115+
public void success(final boolean success) {
116+
this.success = success;
117+
}
118+
119+
/**
120+
* Add the given input to the list of consumed input.
121+
*
122+
* @param consumedInput the consumed input
123+
*/
124+
public void consumedInput(final @NonNull List<@NonNull String> consumedInput) {
125+
this.consumedInput.addAll(consumedInput);
126+
}
127+
128+
/**
129+
* Return the list of consumed input.
130+
*
131+
* @return the list of consumed input
132+
*/
133+
public @NonNull List<@NonNull String> consumedInput() {
134+
return Collections.unmodifiableList(this.consumedInput);
135+
}
136+
137+
/**
138+
* Return the exact alias used, if the argument was static. If no alias was consumed
139+
* then {@code null} is returned.
140+
*
141+
* @return the exact alias, or {@code null}
142+
*/
143+
public @Nullable String exactAlias() {
144+
if (!this.success || !(this.argument instanceof StaticArgument)) {
145+
return null;
146+
}
147+
return this.consumedInput.get(0);
148+
}
149+
}

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)