View Javadoc
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 }