1 package dev.sympho.modular_commands.api.command.context;
2
3 import java.util.Objects;
4
5 import org.checkerframework.checker.nullness.qual.NonNull;
6 import org.checkerframework.checker.nullness.qual.Nullable;
7 import org.checkerframework.dataflow.qual.Pure;
8
9 import dev.sympho.bot_utils.event.RepliableContext;
10 import dev.sympho.modular_commands.api.command.Command;
11 import dev.sympho.modular_commands.api.command.Invocation;
12 import dev.sympho.modular_commands.api.command.parameter.Parameter;
13 import discord4j.common.util.Snowflake;
14 import discord4j.core.object.entity.Member;
15 import discord4j.core.object.entity.User;
16 import discord4j.core.object.entity.channel.MessageChannel;
17 import reactor.core.publisher.Mono;
18
19 /**
20 * The execution context of an invoked command.
21 *
22 * @version 1.0
23 * @since 1.0
24 */
25 public interface CommandContext extends RepliableContext {
26
27 /**
28 * Retrieves the invocation that triggered the command.
29 *
30 * <p>Unlike {@link #commandInvocation()}, the returned value may be
31 * different from the command's declared {@link Command#invocation()} if it
32 * was invoked using an alias (when supported).
33 *
34 * <p>If the command has no aliases, or was invoked through a method that does not
35 * support aliases, the return of this method is the same as the return of
36 * {@link #commandInvocation()}.
37 *
38 * @return The trigger invocation.
39 */
40 @Pure
41 Invocation invocation();
42
43 /**
44 * Retrieves the canonical invocation of the triggered command, that is, the value
45 * of {@link Command#invocation()}. This is equivalent to the
46 * {@link #invocation() triggering invocation} after resolving any aliases.
47 *
48 * <p>If the command has no aliases, or was invoked through a method that does not
49 * support aliases, the return of this method is the same as the return of
50 * {@link #invocation()}.
51 *
52 * @return The normalized trigger invocation.
53 */
54 @Pure
55 Invocation commandInvocation();
56
57 /**
58 * Retrieves the user that called the command.
59 *
60 * @return The calling user.
61 */
62 @Pure
63 default User caller() {
64 return user();
65 }
66
67 /**
68 * Retrieves the user that called the command as a guild
69 * member as provided by the triggering event, if
70 * present.
71 *
72 * @return The calling user as a guild member, or {@code null}
73 * if the command was invoked in a private channel.
74 */
75 @Pure
76 @Nullable Member callerMember();
77
78 /**
79 * Retrieves the user that called the command as a guild
80 * member of the given guild.
81 *
82 * @param guildId The ID of the target guild.
83 * @return The calling user as a guild member of the given guild.
84 * @implNote The default implementation will re-use the member instance
85 * {@link #callerMember() provided by the event} if appropriate (that is, if the
86 * given guild is the same that the command was invoked from) to avoid making an
87 * API request.
88 */
89 @Pure
90 default Mono<Member> callerMember( final Snowflake guildId ) {
91
92 return Objects.requireNonNullElse( callerMember(), caller() )
93 .asMember( guildId );
94
95 }
96
97 /**
98 * @see #callerMember()
99 */
100 @Override
101 default Mono<Member> member() {
102 return Mono.justOrEmpty( callerMember() );
103 }
104
105 /**
106 * @see #callerMember(Snowflake)
107 */
108 @Override
109 default Mono<Member> member( final Snowflake guildId ) {
110 return callerMember( guildId );
111 }
112
113 @Override
114 Mono<MessageChannel> channel();
115
116 /**
117 * Retrieves one of the arguments to the command.
118 *
119 * @param <T> The type of the argument.
120 * @param name The name of the corresponding parameter.
121 * @param argumentType The type of the argument.
122 * @return The argument value, or {@code null} if the received argument is empty
123 * (omitted by the caller or an empty parsing result) and does not have
124 * a default value.
125 * @throws IllegalArgumentException if there is no parameter with the given name.
126 * @throws ClassCastException if the given argument type does not match the type of the
127 * argument with the given name.
128 * @see #getArgument(Parameter, Class)
129 * @apiNote This method will never return {@code null} if the parameter provides a default
130 * value. If it is marked as required, it will return {@code null} if and only if
131 * the parser result was empty (which implies that it will never return {@code null}
132 * if the parser is guaranteed to never give an empty result).
133 */
134 @Pure
135 <T extends @NonNull Object> @Nullable T getArgument( String name, Class<T> argumentType )
136 throws IllegalArgumentException, ClassCastException;
137
138 /**
139 * Retrieves one of the arguments to the command.
140 *
141 * @param <T> The type of the argument.
142 * @param parameter The corresponding parameter.
143 * @param argumentType The type of the argument.
144 * @return The argument value, or {@code null} if the received argument is empty
145 * (omitted by the caller or an empty parsing result) and does not have
146 * a default value.
147 * @throws IllegalArgumentException if there is no parameter with a matching name.
148 * @throws ClassCastException if the given argument type does not match the type of the
149 * argument with the given name.
150 * @see #getArgument(String, Class)
151 * @apiNote This is functionally equivalent to {@link #getArgument(String, Class)},
152 * but allows access directly from the parameter instance and provides
153 * compile-time type checking.
154 */
155 @Pure
156 default <T extends @NonNull Object> @Nullable T getArgument(
157 Parameter<? extends T> parameter, Class<T> argumentType )
158 throws IllegalArgumentException, ClassCastException {
159
160 return getArgument( parameter.name(), argumentType );
161
162 }
163
164 /**
165 * Retrieves one of the arguments to the command.
166 *
167 * @param <T> The type of the argument.
168 * @param parameter The corresponding parameter.
169 * @return The argument value, or {@code null} if the received argument is empty
170 * (omitted by the caller or an empty parsing result) and does not have
171 * a default value.
172 * @throws IllegalArgumentException if the given parameter is not present in the
173 * invoked command.
174 * @apiNote This is functionally equivalent to {@link #getArgument(Parameter, Class)}.
175 * However, it has a stronger requirement on the {@code parameter} argument
176 * in that it must be the <i>same instance</i> (i.e., according to {@code ==})
177 * that was used to define the parameter in the original command, instead of
178 * just needing to match the name. This is necessary as the lack of the class
179 * parameter means there is no other way to ensure type safety. On the other
180 * hand, this variant makes it possible to use arguments that have their own
181 * type parameters in a type-safe manner.
182 */
183 @Pure
184 <T extends @NonNull Object> @Nullable T getArgument( Parameter<? extends T> parameter )
185 throws IllegalArgumentException;
186
187 /**
188 * Retrieves one of the arguments to the command expecting that it is non-null,
189 * i.e. that it either has a default value or is never empty (is required and
190 * the parser never has an empty result).
191 *
192 * @param <T> The type of the argument.
193 * @param name The name of the corresponding parameter.
194 * @param argumentType The type of the argument.
195 * @return The argument value.
196 * @throws IllegalArgumentException if there is no parameter with the given name.
197 * @throws ClassCastException if the given argument type does not match the type of the
198 * argument with the given name.
199 * @throws NullPointerException if the argument was empty (not received or empty parsing
200 * result) and does not have a default value.
201 * @see #requireArgument(Parameter, Class)
202 * @apiNote An NPE thrown by this method indicates either a mismatched configuration
203 * (code expects the parameter to be required or default but it was not
204 * configured as such) or that the parser function unexpectedly returned
205 * an empty result.
206 */
207 @Pure
208 default <T extends @NonNull Object> T requireArgument( String name, Class<T> argumentType )
209 throws IllegalArgumentException, ClassCastException, NullPointerException {
210
211 return Objects.requireNonNull( getArgument( name, argumentType ) );
212
213 }
214
215 /**
216 * Retrieves one of the arguments to the command expecting that it is non-null,
217 * i.e. that it either has a default value or is never empty (is required and
218 * the parser never has an empty result).
219 *
220 * @param <T> The type of the argument.
221 * @param parameter The name of the corresponding parameter.
222 * @param argumentType The type of the argument.
223 * @return The argument value.
224 * @throws IllegalArgumentException if there is no parameter with the given name.
225 * @throws ClassCastException if the given argument type does not match the type of the
226 * argument with the given name.
227 * @throws NullPointerException if the argument was empty (not received or empty parsing
228 * result) and does not have a default value.
229 * @see #requireArgument(String, Class)
230 * @apiNote This is functionally equivalent to {@link #requireArgument(String, Class)},
231 * but allows access directly from the parameter instance and provides
232 * compile-time type checking.
233 */
234 @Pure
235 default <T extends @NonNull Object> T requireArgument(
236 Parameter<? extends T> parameter, Class<T> argumentType )
237 throws IllegalArgumentException, ClassCastException, NullPointerException {
238
239 return requireArgument( parameter.name(), argumentType );
240
241 }
242
243 /**
244 * Retrieves one of the arguments to the command expecting that it is non-null,
245 * i.e. that it either has a default value or is never empty (is required and
246 * the parser never has an empty result).
247 *
248 * @param <T> The type of the argument.
249 * @param parameter The name of the corresponding parameter.
250 * @return The argument value.
251 * @throws IllegalArgumentException if the given parameter is not present in the
252 * invoked command.
253 * @throws NullPointerException if the argument was not received and does not have a
254 * default value.
255 * @apiNote This is functionally equivalent to {@link #requireArgument(Parameter, Class)}.
256 * However, it has a stronger requirement on the {@code parameter} argument
257 * in that it must be the <i>same instance</i> (i.e., according to {@code ==})
258 * that was used to define the parameter in the original command, instead of
259 * just needing to match the name. This is necessary as the lack of the class
260 * parameter means there is no other way to ensure type safety. On the other
261 * hand, this variant makes it possible to use arguments that have their own
262 * type parameters in a type-safe manner.
263 */
264 @Pure
265 default <T extends @NonNull Object> T requireArgument( final Parameter<? extends T> parameter )
266 throws IllegalArgumentException, NullPointerException {
267
268 return Objects.requireNonNull( getArgument( parameter ) );
269
270 }
271
272 /**
273 * Places a context object for subsequent handlers, optionally replacing any existing
274 * values under the same key.
275 *
276 * @param key The object key.
277 * @param obj The object to store.
278 * @param replace If {@code true}, the object will be placed unconditionally, replacing
279 * any existing value in that key. Otherwise, it will only be placed if there
280 * are no values with the given key.
281 * @return {@code true} if the given object was placed in the context. If {@code replace}
282 * is {@code false} and there is already an object at the given key, returns
283 * {@code false}.
284 * @apiNote This method is <i>not</i> thread-safe.
285 */
286 boolean setContext( String key, @Nullable Object obj, boolean replace );
287
288 /**
289 * Unconditionally places a context object for subsequent handlers.
290 *
291 * @param key The object key.
292 * @param obj The object to store.
293 * @see #setContext(String, Object, boolean)
294 * @apiNote This method is equivalent to
295 * {@link #setContext(String, Object, boolean) setContext(key, obj, true)}.
296 */
297 default void setContext( String key, @Nullable Object obj ) {
298 setContext( key, obj, true );
299 }
300
301 /**
302 * Retrieves a context object set by {@link #setContext(String, Object, boolean)}.
303 *
304 * @param <T> The type of the object.
305 * @param key The object key.
306 * @param type The object class.
307 * @return The context object.
308 * @throws IllegalArgumentException if there is no context object with the given key.
309 * @throws ClassCastException if the context object with the given key is not compatible
310 * with the given type (not the same or a subtype).
311 */
312 @Pure
313 <T> @Nullable T getContext( String key, Class<? extends T> type )
314 throws IllegalArgumentException, ClassCastException;
315
316 /**
317 * Retrieves a non-null context object set by {@link #setContext(String, Object, boolean)}.
318 *
319 * @param <T> The type of the object.
320 * @param key The object key.
321 * @param type The object class.
322 * @return The context object.
323 * @throws IllegalArgumentException If there is no context object with the given key.
324 * @throws ClassCastException if the context object with the given key is not compatible
325 * with the given type (not the same or a subtype).
326 * @throws NullPointerException if the context object was {@code null}.
327 */
328 @Pure
329 default <T> T requireContext( final String key, final Class<? extends T> type )
330 throws IllegalArgumentException, ClassCastException, NullPointerException {
331
332 return Objects.requireNonNull( getContext( key, type ) );
333
334 }
335
336 }