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 }