diff --git a/application/src/main/java/org/togetherjava/tjbot/config/JShellConfig.java b/application/src/main/java/org/togetherjava/tjbot/config/JShellConfig.java index 2337c6e8d4..850ca9f4c3 100644 --- a/application/src/main/java/org/togetherjava/tjbot/config/JShellConfig.java +++ b/application/src/main/java/org/togetherjava/tjbot/config/JShellConfig.java @@ -2,21 +2,43 @@ import com.linkedin.urls.Url; +import org.togetherjava.tjbot.features.utils.RateLimiter; + import java.net.MalformedURLException; +/** + * JShell config. + * + * @param baseUrl the base url of the JShell REST API + * @param rateLimitWindowSeconds the number of seconds of the {@link RateLimiter rate limiter} for + * jshell commands and code actions + * @param rateLimitRequestsInWindow the number of requests of the {@link RateLimiter rate limiter} + * for jshell commands and code actions + */ public record JShellConfig(String baseUrl, int rateLimitWindowSeconds, int rateLimitRequestsInWindow) { + /** + * Creates a JShell config. + * + * @param baseUrl the base url of the JShell REST API, must be valid + * @param rateLimitWindowSeconds the number of seconds of the {@link RateLimiter rate limiter} + * for jshell commands and code actions, must be higher than 0 + * @param rateLimitRequestsInWindow the number of requests of the {@link RateLimiter rate + * limiter} for jshell commands and code actions, must be higher than 0 + */ public JShellConfig { try { Url.create(baseUrl); } catch (MalformedURLException e) { throw new IllegalArgumentException(e); } - if (rateLimitWindowSeconds <= 0) + if (rateLimitWindowSeconds < 0) { throw new IllegalArgumentException( "Illegal rateLimitWindowSeconds : " + rateLimitWindowSeconds); - if (rateLimitRequestsInWindow <= 0) + } + if (rateLimitRequestsInWindow < 0) { throw new IllegalArgumentException( "Illegal rateLimitRequestsInWindow : " + rateLimitRequestsInWindow); + } } } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/code/CodeMessageHandler.java b/application/src/main/java/org/togetherjava/tjbot/features/code/CodeMessageHandler.java index cd849926fd..30ae6bdfc2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/code/CodeMessageHandler.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/code/CodeMessageHandler.java @@ -63,6 +63,8 @@ public final class CodeMessageHandler extends MessageReceiverAdapter implements /** * Creates a new instance. + * + * @param jshellEval the jshell evaluation instance used in the code actions */ public CodeMessageHandler(JShellEval jshellEval) { componentIdInteractor = new ComponentIdInteractor(getInteractionType(), getName()); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/code/EvalCodeCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/code/EvalCodeCommand.java index 23aae33824..1dfd6a7f8c 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/code/EvalCodeCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/code/EvalCodeCommand.java @@ -4,12 +4,14 @@ import net.dv8tion.jda.api.entities.MessageEmbed; import org.togetherjava.tjbot.features.jshell.JShellEval; -import org.togetherjava.tjbot.features.jshell.render.Colors; import org.togetherjava.tjbot.features.utils.CodeFence; +import org.togetherjava.tjbot.features.utils.Colors; import org.togetherjava.tjbot.features.utils.RequestFailedException; /** - * Evaluates the given code. + * Evaluates the given code with jshell. + *

+ * It will not work of the code isn't valid java or jshell compatible code. */ final class EvalCodeCommand implements CodeAction { private final JShellEval jshellEval; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellCommand.java index dbf13f5176..92905b6756 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellCommand.java @@ -20,7 +20,8 @@ import org.togetherjava.tjbot.features.CommandVisibility; import org.togetherjava.tjbot.features.SlashCommandAdapter; import org.togetherjava.tjbot.features.jshell.backend.JShellApi; -import org.togetherjava.tjbot.features.jshell.render.Colors; +import org.togetherjava.tjbot.features.utils.Colors; +import org.togetherjava.tjbot.features.utils.MessageUtils; import org.togetherjava.tjbot.features.utils.RequestFailedException; import javax.annotation.Nullable; @@ -28,18 +29,21 @@ import java.util.List; import java.util.Objects; +/** + * The JShell command, provide functionalities to create JShell sessions, evaluate code, etc. + */ public class JShellCommand extends SlashCommandAdapter { - private static final String JSHELL_TEXT_INPUT_ID = "jshell"; + private static final String TEXT_INPUT_PART_ID = "jshell"; private static final String JSHELL_COMMAND = "jshell"; - private static final String JSHELL_VERSION_SUBCOMMAND = "version"; - private static final String JSHELL_EVAL_SUBCOMMAND = "eval"; - private static final String JSHELL_SNIPPETS_SUBCOMMAND = "snippets"; - private static final String JSHELL_CLOSE_SUBCOMMAND = "shutdown"; - private static final String JSHELL_STARTUP_SCRIPT_SUBCOMMAND = "startup-script"; - private static final String JSHELL_CODE_PARAMETER = "code"; - private static final String JSHELL_STARTUP_SCRIPT_PARAMETER = "startup-script"; - private static final String JSHELL_USER_PARAMETER = "user"; - private static final String JSHELL_INCLUDE_STARTUP_SCRIPT_PARAMETER = "include-startup-script"; + private static final String VERSION_SUBCOMMAND = "version"; + private static final String EVAL_SUBCOMMAND = "eval"; + private static final String SNIPPETS_SUBCOMMAND = "snippets"; + private static final String CLOSE_SUBCOMMAND = "shutdown"; + private static final String STARTUP_SCRIPT_SUBCOMMAND = "startup-script"; + private static final String CODE_PARAMETER = "code"; + private static final String STARTUP_SCRIPT_PARAMETER = "startup-script"; + private static final String USER_PARAMETER = "user"; + private static final String INCLUDE_STARTUP_SCRIPT_PARAMETER = "include-startup-script"; private static final int MIN_MESSAGE_INPUT_LENGTH = 0; private static final int MAX_MESSAGE_INPUT_LENGTH = TextInput.MAX_VALUE_LENGTH; @@ -48,39 +52,42 @@ public class JShellCommand extends SlashCommandAdapter { /** * Creates an instance of the command. + * + * @param jshellEval the jshell evaluation instance used in the jshell command */ public JShellCommand(JShellEval jshellEval) { - super(JSHELL_COMMAND, "JShell as a command.", CommandVisibility.GUILD); + super(JSHELL_COMMAND, + "JShell as a command, a Read–eval–print loop which allows to rapidly and simply execute java code.", + CommandVisibility.GUILD); this.jshellEval = jshellEval; getData().addSubcommands( - new SubcommandData(JSHELL_VERSION_SUBCOMMAND, "Get the version of JShell"), - new SubcommandData(JSHELL_EVAL_SUBCOMMAND, - "Evaluate java code in JShell, don't fill the optional parameter to access a bigger input box.") - .addOption(OptionType.STRING, JSHELL_CODE_PARAMETER, - "Code to evaluate. If not supplied, open an inout box.") - .addOption(OptionType.BOOLEAN, JSHELL_STARTUP_SCRIPT_PARAMETER, + new SubcommandData(VERSION_SUBCOMMAND, "Get the version of JShell"), + new SubcommandData(EVAL_SUBCOMMAND, + "Evaluate java code in JShell, submit the command without code for inputting longer, multi-line code.") + .addOption(OptionType.STRING, CODE_PARAMETER, + "Code to evaluate. Leave empty to input longer, multi-line code.") + .addOption(OptionType.BOOLEAN, STARTUP_SCRIPT_PARAMETER, "If the startup script should be loaded, true by default."), - new SubcommandData(JSHELL_SNIPPETS_SUBCOMMAND, - "Get the evaluated snippets of the user who sent the command, or the user specified user if any.") - .addOption(OptionType.USER, JSHELL_USER_PARAMETER, - "User to get the snippets from. If null, get the snippets of the user who sent the command.") - .addOption(OptionType.BOOLEAN, JSHELL_INCLUDE_STARTUP_SCRIPT_PARAMETER, + new SubcommandData(SNIPPETS_SUBCOMMAND, + "Display your snippets, or the snippets of the specified user if any.") + .addOption(OptionType.USER, USER_PARAMETER, + "User to get the snippets from. If null, get your snippets.") + .addOption(OptionType.BOOLEAN, INCLUDE_STARTUP_SCRIPT_PARAMETER, "if the startup script should be included, false by default."), - new SubcommandData(JSHELL_CLOSE_SUBCOMMAND, "Close your session."), - new SubcommandData(JSHELL_STARTUP_SCRIPT_SUBCOMMAND, - "Display the startup script.")); + new SubcommandData(CLOSE_SUBCOMMAND, "Close your session."), + new SubcommandData(STARTUP_SCRIPT_SUBCOMMAND, "Display the startup script.")); } @Override public void onSlashCommand(SlashCommandInteractionEvent event) { switch (Objects.requireNonNull(event.getSubcommandName())) { - case JSHELL_VERSION_SUBCOMMAND -> handleVersionCommand(event); - case JSHELL_EVAL_SUBCOMMAND -> handleEvalCommand(event); - case JSHELL_SNIPPETS_SUBCOMMAND -> handleSnippetsCommand(event); - case JSHELL_CLOSE_SUBCOMMAND -> handleCloseCommand(event); - case JSHELL_STARTUP_SCRIPT_SUBCOMMAND -> handleStartupScriptCommand(event); + case VERSION_SUBCOMMAND -> handleVersionCommand(event); + case EVAL_SUBCOMMAND -> handleEvalCommand(event); + case SNIPPETS_SUBCOMMAND -> handleSnippetsCommand(event); + case CLOSE_SUBCOMMAND -> handleCloseCommand(event); + case STARTUP_SCRIPT_SUBCOMMAND -> handleStartupScriptCommand(event); default -> throw new AssertionError( "Unexpected Subcommand: " + event.getSubcommandName()); } @@ -88,11 +95,10 @@ public void onSlashCommand(SlashCommandInteractionEvent event) { @Override public void onModalSubmitted(ModalInteractionEvent event, List args) { - ModalMapping mapping = - event.getValue(JSHELL_TEXT_INPUT_ID + "|" + JSHELL_STARTUP_SCRIPT_PARAMETER); + ModalMapping mapping = event.getValue(TEXT_INPUT_PART_ID + "|" + STARTUP_SCRIPT_PARAMETER); boolean startupScript = mapping != null; if (mapping == null) { - mapping = event.getValue(JSHELL_TEXT_INPUT_ID); + mapping = event.getValue(TEXT_INPUT_PART_ID); } if (mapping != null) { handleEval(event, event.getUser(), true, mapping.getAsString(), startupScript); @@ -106,15 +112,14 @@ private void handleVersionCommand(SlashCommandInteractionEvent event) { System.out.println("Vendor: " + System.getProperty("java.vendor")); System.out.println("OS: " + System.getProperty("os.name")); System.out.println("Arch: " + System.getProperty("os.arch")); - System.out.println("```");"""; + System.out.println("```");"""; handleEval(event, null, false, code, false); } private void handleEvalCommand(SlashCommandInteractionEvent event) { - OptionMapping code = event.getOption(JSHELL_CODE_PARAMETER); - boolean startupScript = event.getOption(JSHELL_STARTUP_SCRIPT_PARAMETER) == null - || Objects.requireNonNull(event.getOption(JSHELL_STARTUP_SCRIPT_PARAMETER)) - .getAsBoolean(); + OptionMapping code = event.getOption(CODE_PARAMETER); + boolean startupScript = event.getOption(STARTUP_SCRIPT_PARAMETER) == null + || Objects.requireNonNull(event.getOption(STARTUP_SCRIPT_PARAMETER)).getAsBoolean(); if (code == null) { sendEvalModal(event, startupScript); } else { @@ -124,8 +129,7 @@ private void handleEvalCommand(SlashCommandInteractionEvent event) { private void sendEvalModal(SlashCommandInteractionEvent event, boolean startupScript) { TextInput body = TextInput - .create(JSHELL_TEXT_INPUT_ID - + (startupScript ? "|" + JSHELL_STARTUP_SCRIPT_PARAMETER : ""), + .create(TEXT_INPUT_PART_ID + (startupScript ? "|" + STARTUP_SCRIPT_PARAMETER : ""), "Enter code to evaluate.", TextInputStyle.PARAGRAPH) .setPlaceholder("Put your code here.") .setRequiredRange(MIN_MESSAGE_INPUT_LENGTH, MAX_MESSAGE_INPUT_LENGTH) @@ -160,10 +164,10 @@ private void handleEval(IReplyCallback replyCallback, @Nullable User user, boole private void handleSnippetsCommand(SlashCommandInteractionEvent event) { event.deferReply().queue(interactionHook -> { - OptionMapping userOption = event.getOption(JSHELL_USER_PARAMETER); + OptionMapping userOption = event.getOption(USER_PARAMETER); User user = userOption == null ? event.getUser() : userOption.getAsUser(); OptionMapping includeStartupScriptOption = - event.getOption(JSHELL_INCLUDE_STARTUP_SCRIPT_PARAMETER); + event.getOption(INCLUDE_STARTUP_SCRIPT_PARAMETER); boolean includeStartupScript = includeStartupScriptOption != null && includeStartupScriptOption.getAsBoolean(); List snippets; @@ -185,10 +189,7 @@ private void handleSnippetsCommand(SlashCommandInteractionEvent event) { && snippets.stream() .mapToInt(s -> (s + "Snippet 10```java\n```").length()) .sum() < MessageEmbed.EMBED_MAX_LENGTH_BOT - 100 - && snippets.size() <= 25/* - * Max visible embed fields in an embed TODO replace - * with constant - */) { + && snippets.size() <= MessageUtils.MAXIMUM_VISIBLE_EMBEDS) { sendSnippetsAsEmbed(interactionHook, user, snippets); } else if (snippets.stream() .mapToInt(s -> (s + "// Snippet 10").getBytes().length) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellEval.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellEval.java index c54a2739d0..91d46feb14 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellEval.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/JShellEval.java @@ -9,8 +9,7 @@ import org.togetherjava.tjbot.config.JShellConfig; import org.togetherjava.tjbot.features.jshell.backend.JShellApi; import org.togetherjava.tjbot.features.jshell.backend.dto.JShellResult; -import org.togetherjava.tjbot.features.jshell.render.Colors; -import org.togetherjava.tjbot.features.jshell.render.ResultRenderer; +import org.togetherjava.tjbot.features.utils.Colors; import org.togetherjava.tjbot.features.utils.RateLimiter; import org.togetherjava.tjbot.features.utils.RequestFailedException; @@ -19,12 +18,21 @@ import java.time.Duration; import java.time.Instant; +/** + * Provides a mid-ground between JDA and JShell API which can be used from many places in the bot, + * including JShell commands and JShell code actions. + */ public class JShellEval { private final JShellApi api; private final ResultRenderer renderer; private final RateLimiter rateLimiter; + /** + * Creates a JShell evaluation instance + * + * @param config the JShell configuration to use + */ public JShellEval(JShellConfig config) { this.api = new JShellApi(new ObjectMapper(), config.baseUrl()); this.renderer = new ResultRenderer(); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/render/ResultRenderer.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/ResultRenderer.java similarity index 94% rename from application/src/main/java/org/togetherjava/tjbot/features/jshell/render/ResultRenderer.java rename to application/src/main/java/org/togetherjava/tjbot/features/jshell/ResultRenderer.java index 2bbb0f5897..f15dc3f973 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/render/ResultRenderer.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/ResultRenderer.java @@ -1,4 +1,4 @@ -package org.togetherjava.tjbot.features.jshell.render; +package org.togetherjava.tjbot.features.jshell; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.MessageEmbed; @@ -12,9 +12,9 @@ import java.awt.Color; -import static org.togetherjava.tjbot.features.jshell.render.Colors.*; +import static org.togetherjava.tjbot.features.utils.Colors.*; -public class ResultRenderer { +class ResultRenderer { public EmbedBuilder renderToEmbed(@Nullable User originator, @Nullable String originalCode, boolean partOfSession, JShellResult result, EmbedBuilder builder) { diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/JShellApi.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/JShellApi.java index 89e88b44a5..b0f6e7e073 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/JShellApi.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/JShellApi.java @@ -19,14 +19,35 @@ import java.net.http.HttpResponse.BodyHandler; import java.net.http.HttpResponse.BodyHandlers; +/** + * Allows to interact with the unofficial JShell REST API of the Together-Java JShell backend + * project. + *

+ * Each method may do a blocking HTTP request and may throw a RequestFailedException if status code + * isn't 200 or 204. + *

+ * When startup script boolean argument is asked, true means {@link JShellApi#STARTUP_SCRIPT_ID} and + * false means Together-Java JShell backend's default startup script. + *

+ * For more information, check the Together-Java JShell backend project. + */ public class JShellApi { public static final int SESSION_NOT_FOUND = 404; + /** + * The startup script to use when startup script boolean argument is true. + */ private static final String STARTUP_SCRIPT_ID = "CUSTOM_DEFAULT"; private final ObjectMapper objectMapper; private final HttpClient httpClient; private final String baseUrl; + /** + * Creates a JShellAPI + * + * @param objectMapper the json mapper to use + * @param baseUrl the base url of the JShell REST API + */ public JShellApi(ObjectMapper objectMapper, String baseUrl) { this.objectMapper = objectMapper; this.baseUrl = baseUrl; @@ -34,6 +55,15 @@ public JShellApi(ObjectMapper objectMapper, String baseUrl) { this.httpClient = HttpClient.newBuilder().build(); } + /** + * Evaluates the code in a one time only session, will block until the request is over. + * + * @param code the code to evaluate + * @param startupScript if the {@link JShellApi#STARTUP_SCRIPT_ID startup script} should be + * executed at the start of the session + * @return the result of the evaluation + * @throws RequestFailedException if the status code is not 200 or 204 + */ public JShellResult evalOnce(String code, boolean startupScript) throws RequestFailedException { return send( baseUrl + "single-eval" @@ -42,6 +72,15 @@ public JShellResult evalOnce(String code, boolean startupScript) throws RequestF ResponseUtils.ofJson(JShellResult.class, objectMapper)).body(); } + /** + * Evaluates the code in a regular session, will block until the request is over. + * + * @param code the code to evaluate + * @param startupScript if the {@link JShellApi#STARTUP_SCRIPT_ID startup script} should be + * executed at the start of the session + * @return the result of the evaluation + * @throws RequestFailedException if the status code is not 200 or 204 + */ public JShellResult evalSession(String code, String sessionId, boolean startupScript) throws RequestFailedException { return send( @@ -51,6 +90,14 @@ public JShellResult evalSession(String code, String sessionId, boolean startupSc ResponseUtils.ofJson(JShellResult.class, objectMapper)).body(); } + /** + * Gets and return the snippets for the given session id, will block until the request is over. + * + * @param sessionId the id of the session to get the snippets from + * @param includeStartupScript if the startup script should be included in the returned snippets + * @return the snippets of the session + * @throws RequestFailedException if the status code is not 200 or 204 + */ public SnippetList snippetsSession(String sessionId, boolean includeStartupScript) throws RequestFailedException { return send( @@ -59,11 +106,24 @@ public SnippetList snippetsSession(String sessionId, boolean includeStartupScrip ResponseUtils.ofJson(SnippetList.class, objectMapper)).body(); } + /** + * Closes the given session. + * + * @param sessionId the id of the session to close + * @throws RequestFailedException if the status code is not 200 or 204 + */ public void closeSession(String sessionId) throws RequestFailedException { send(baseUrl + sessionId, HttpRequest.newBuilder().DELETE(), BodyHandlers.discarding()) .body(); } + /** + * Gets and return the {@link JShellApi#STARTUP_SCRIPT_ID startup script}, will block until the + * request is over. + * + * @return the startup script + * @throws RequestFailedException if the status code is not 200 or 204 + */ public String startupScript() throws RequestFailedException { return send(baseUrl + "startup_script/" + STARTUP_SCRIPT_ID, HttpRequest.newBuilder().GET(), BodyHandlers.ofString()).body(); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/JShellExceptionResult.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/JShellExceptionResult.java index 1cbb548d6e..dbffefda69 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/JShellExceptionResult.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/JShellExceptionResult.java @@ -1,4 +1,10 @@ package org.togetherjava.tjbot.features.jshell.backend.dto; +/** + * The thrown exception. + * + * @param exceptionClass the class of the exception + * @param exceptionMessage the message of the exception + */ public record JShellExceptionResult(String exceptionClass, String exceptionMessage) { } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/JShellResult.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/JShellResult.java index afa83ed1b3..830eb17429 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/JShellResult.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/JShellResult.java @@ -1,10 +1,25 @@ package org.togetherjava.tjbot.features.jshell.backend.dto; +import javax.annotation.Nullable; + import java.util.List; +/** + * Result of a JShell eval. + * + * @param status status of the snippet + * @param type type of the snippet + * @param id id of the snippet + * @param source source code of the snippet + * @param result result of the snippet + * @param exception thrown exception + * @param stdoutOverflow if stdout has overflowed and was truncated + * @param stdout what was printed by the snippet + * @param errors the compilations errors of the snippet + */ public record JShellResult(SnippetStatus status, SnippetType type, String id, String source, - String result, JShellExceptionResult exception, boolean stdoutOverflow, String stdout, - List errors) { + @Nullable String result, @Nullable JShellExceptionResult exception, boolean stdoutOverflow, + String stdout, List errors) { public JShellResult { errors = List.copyOf(errors); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/JShellResultWithId.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/JShellResultWithId.java index 4c1b1ea970..2cd862e9b7 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/JShellResultWithId.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/JShellResultWithId.java @@ -1,4 +1,10 @@ package org.togetherjava.tjbot.features.jshell.backend.dto; +/** + * Result of a JShell eval plus the session id. + * + * @param id the session of the id + * @param result the JShell eval result + */ public record JShellResultWithId(String id, JShellResult result) { } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/SnippetList.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/SnippetList.java index 980b1aa211..dbed64f37d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/SnippetList.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/SnippetList.java @@ -5,7 +5,17 @@ import java.util.List; import java.util.Objects; +/** + * List of snippets returned by snippets endpoint. + * + * @param snippets the list of snippets + */ public record SnippetList(List snippets) { + /** + * List of snippets returned by snippets endpoint. + * + * @param snippets the list of snippets + */ @JsonCreator(mode = JsonCreator.Mode.DELEGATING) public SnippetList { Objects.requireNonNull(snippets); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/SnippetStatus.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/SnippetStatus.java index c734acad34..747604a9b1 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/SnippetStatus.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/SnippetStatus.java @@ -1,9 +1,28 @@ package org.togetherjava.tjbot.features.jshell.backend.dto; +/** + * The status of the snippet, see {@link jdk.jshell.Snippet.Status} for most of them, and evaluation + * timeout of the JShell REST API for ABORTED. + */ public enum SnippetStatus { + /** + * See {@link jdk.jshell.Snippet.Status#VALID}. + */ VALID, + /** + * See {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED}. + */ RECOVERABLE_DEFINED, + /** + * See {@link jdk.jshell.Snippet.Status#RECOVERABLE_NOT_DEFINED}. + */ RECOVERABLE_NOT_DEFINED, + /** + * See {@link jdk.jshell.Snippet.Status#REJECTED}. + */ REJECTED, + /** + * Used when the timeout of an evaluation is reached. + */ ABORTED } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/SnippetType.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/SnippetType.java index 0de54ff44e..54c737dca5 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/SnippetType.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/SnippetType.java @@ -1,5 +1,8 @@ package org.togetherjava.tjbot.features.jshell.backend.dto; +/** + * Type of the snippet, if it was added or modified. + */ public enum SnippetType { ADDITION, MODIFICATION diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/package-info.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/package-info.java new file mode 100644 index 0000000000..c506b34feb --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/dto/package-info.java @@ -0,0 +1,10 @@ +/** + * This packages offers value classes to encapsulate information of JShell REST API. + */ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package org.togetherjava.tjbot.features.jshell.backend.dto; + +import org.togetherjava.tjbot.annotations.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/package-info.java b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/package-info.java new file mode 100644 index 0000000000..67a345f285 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/jshell/backend/package-info.java @@ -0,0 +1,11 @@ +/** + * This packages offers a class to interact with JShell REST API. The core class is + * {@link org.togetherjava.tjbot.features.jshell.backend.JShellApi}. + */ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package org.togetherjava.tjbot.features.jshell.backend; + +import org.togetherjava.tjbot.annotations.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/jshell/render/Colors.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/Colors.java similarity index 77% rename from application/src/main/java/org/togetherjava/tjbot/features/jshell/render/Colors.java rename to application/src/main/java/org/togetherjava/tjbot/features/utils/Colors.java index 928668e15d..8222bf3c52 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/jshell/render/Colors.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/Colors.java @@ -1,7 +1,10 @@ -package org.togetherjava.tjbot.features.jshell.render; +package org.togetherjava.tjbot.features.utils; import java.awt.Color; +/** + * Provides the color of different things. + */ public class Colors { private Colors() { throw new UnsupportedOperationException(); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/MessageUtils.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/MessageUtils.java index 025bf37da7..661f5acd64 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/MessageUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/MessageUtils.java @@ -20,6 +20,7 @@ * other commands to avoid similar methods appearing everywhere. */ public class MessageUtils { + public static final int MAXIMUM_VISIBLE_EMBEDS = 25; public static final String ABBREVIATION = "..."; private static final String CODE_FENCE_SYMBOL = "```"; diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/RateLimiter.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/RateLimiter.java index 9ac4761480..8dda5c8c91 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/RateLimiter.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/RateLimiter.java @@ -7,7 +7,8 @@ import java.util.stream.Collectors; /** - * Custom rate limiter. + * Rate limiter, register when requests are done and tells if a request can be done or need to be + * canceled. */ public class RateLimiter { @@ -16,6 +17,16 @@ public class RateLimiter { private final Duration duration; private final int allowedRequests; + /** + * Creates a rate limiter. + *

+ * Defines a window and a number of request, for example, if 10 requests should be allowed per 5 + * seconds, so 10/5s, the following should be called: {@snippet java: new + * RateLimit(Duration.of(5, TimeUnit.SECONDS), 10) } + * + * @param duration the duration of window + * @param allowedRequests the number of requests to allow in the window + */ public RateLimiter(Duration duration, int allowedRequests) { this.duration = duration; this.allowedRequests = allowedRequests; @@ -23,6 +34,12 @@ public RateLimiter(Duration duration, int allowedRequests) { this.lastUses = List.of(); } + /** + * Tries to allow the request. If it is allowed, the time is registered. + * + * @param time the time of the request + * @return if the request was allowed + */ public boolean allowRequest(Instant time) { synchronized (this) { List usesInWindow = getEffectiveUses(time); @@ -44,6 +61,12 @@ private List getEffectiveUses(Instant time) { .collect(Collectors.toCollection(ArrayList::new)); } + /** + * Returns next time a request can be allowed. + * + * @param time the time of the request + * @return when the next request will be allowed + */ public Instant nextAllowedRequestTime(Instant time) { synchronized (this) { List currentUses = getEffectiveUses(time); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/utils/ResponseUtils.java b/application/src/main/java/org/togetherjava/tjbot/features/utils/ResponseUtils.java index 676eaf46cb..0b84a6cf52 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/utils/ResponseUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/utils/ResponseUtils.java @@ -14,10 +14,21 @@ public class ResponseUtils { private ResponseUtils() {} - public static BodyHandler ofJson(Class t, ObjectMapper mapper) { + /** + * Creates a body handler which will parse the body of the request. If the parsing fails, an + * UncheckedIOException exception is thrown and may be wrapped in an IOException.//TODO If the + * request status code is not 200 or 204, a UncheckedRequestFailedException is thrown and + * wrapped in an IOException. + * + * @param type the class to parse the json into + * @param mapper the json mapper + * @return the body handler + * @param the type of the class to parse the json into + */ + public static BodyHandler ofJson(Class type, ObjectMapper mapper) { return responseInfo -> BodySubscribers.mapping(BodySubscribers.ofByteArray(), bytes -> { if (responseInfo.statusCode() == 200 || responseInfo.statusCode() == 204) { - return uncheckedParseJson(t, mapper, bytes); + return uncheckedParseJson(type, mapper, bytes); } String errorMessage = tryParseError(bytes, mapper) .orElse("Request failed with status: " + responseInfo.statusCode()); @@ -25,9 +36,9 @@ public static BodyHandler ofJson(Class t, ObjectMapper mapper) { }); } - private static T uncheckedParseJson(Class t, ObjectMapper mapper, byte[] value) { + private static T uncheckedParseJson(Class type, ObjectMapper mapper, byte[] value) { try { - return mapper.readValue(value, t); + return mapper.readValue(value, type); } catch (IOException e) { throw new UncheckedIOException("Error parsing json", e); }