diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml
new file mode 100644
index 0000000..d65f0de
--- /dev/null
+++ b/.github/workflows/pre-commit.yaml
@@ -0,0 +1,22 @@
+name: pre-commit
+on:
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ pre-commit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: actions/setup-java@v3
+ with:
+ java-version: '21'
+ distribution: 'corretto'
+
+ - uses: gradle/gradle-build-action@v3
+ with:
+ gradle-version: 8.7
+
+ - name: Build with Gradle
+ run: gradle build
\ No newline at end of file
diff --git a/BuildImage.bat b/BuildImage.bat
deleted file mode 100644
index 9de2b7b..0000000
--- a/BuildImage.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-docker rmi jshellwrapper
-docker build . -t jshellwrapper
\ No newline at end of file
diff --git a/BuildImage.sh b/BuildImage.sh
deleted file mode 100755
index 20981f0..0000000
--- a/BuildImage.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-docker rmi jshellwrapper
-docker build . -t jshellwrapper
\ No newline at end of file
diff --git a/BuildJShellWrapper.bat b/BuildJShellWrapper.bat
deleted file mode 100644
index 9f91998..0000000
--- a/BuildJShellWrapper.bat
+++ /dev/null
@@ -1,6 +0,0 @@
-cd JShellWrapper
-rmdir /s /q out
-javac -d out/production/JShellWrapper src/main/java/*.java
-xcopy src\\main\\resources\\META-INF\\MANIFEST.MF out\\production\\JShellWrapper\\META-INF\\
-cd out/production/JShellWrapper/
-jar -cfm ../../JShellWrapper.jar META-INF/MANIFEST.MF *.class
\ No newline at end of file
diff --git a/BuildJShellWrapper.sh b/BuildJShellWrapper.sh
deleted file mode 100755
index db4eea1..0000000
--- a/BuildJShellWrapper.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-cd JShellWrapper
-rm -rf out
-javac -d out/production/JShellWrapper src/main/java/*.java
-mkdir out/production/JShellWrapper/META-INF
-cp src/main/resources/META-INF/MANIFEST.MF out/production/JShellWrapper/META-INF/
-cd out/production/JShellWrapper/
-jar -cfm ../../JShellWrapper.jar META-INF/MANIFEST.MF *.class
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 5a857da..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,3 +0,0 @@
-FROM eclipse-temurin:19-alpine
-
-COPY JShellWrapper/out/JShellWrapper.jar .
diff --git a/JShellAPI/.gitignore b/JShellAPI/.gitignore
deleted file mode 100644
index 811b0bd..0000000
--- a/JShellAPI/.gitignore
+++ /dev/null
@@ -1,236 +0,0 @@
-# Created by https://www.toptal.com/developers/gitignore/api/java,gradle,intellij,eclipse
-# Edit at https://www.toptal.com/developers/gitignore?templates=java,gradle,intellij,eclipse
-
-### Eclipse ###
-.metadata
-bin/
-tmp/
-*.tmp
-*.bak
-*.swp
-*~.nib
-local.properties
-.settings/
-.loadpath
-.recommenders
-
-# External tool builders
-.externalToolBuilders/
-
-# Locally stored "Eclipse launch configurations"
-*.launch
-
-# PyDev specific (Python IDE for Eclipse)
-*.pydevproject
-
-# CDT-specific (C/C++ Development Tooling)
-.cproject
-
-# CDT- autotools
-.autotools
-
-# Java annotation processor (APT)
-.factorypath
-
-# PDT-specific (PHP Development Tools)
-.buildpath
-
-# sbteclipse plugin
-.target
-
-# Tern plugin
-.tern-project
-
-# TeXlipse plugin
-.texlipse
-
-# STS (Spring Tool Suite)
-.springBeans
-
-# Code Recommenders
-.recommenders/
-
-# Annotation Processing
-.apt_generated/
-.apt_generated_test/
-
-# Scala IDE specific (Scala & Java development for Eclipse)
-.cache-main
-.scala_dependencies
-.worksheet
-
-# Uncomment this line if you wish to ignore the project description file.
-# Typically, this file would be tracked if it contains build/dependency configurations:
-#.project
-
-### Eclipse Patch ###
-# Spring Boot Tooling
-.sts4-cache/
-
-### Intellij ###
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
-
-# AWS User-specific
-.idea/**/aws.xml
-
-# Generated files
-.idea/**/contentModel.xml
-
-# Sensitive or high-churn files
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-.idea/**/dbnavigator.xml
-
-# Gradle
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# Gradle and Maven with auto-import
-# When using Gradle or Maven with auto-import, you should exclude module files,
-# since they will be recreated, and may cause churn. Uncomment if using
-# auto-import.
-.idea/artifacts
-.idea/compiler.xml
-.idea/jarRepositories.xml
-.idea/modules.xml
-.idea/*.iml
-.idea/modules
-*.iml
-*.ipr
-
-# CMake
-cmake-build-*/
-
-# Mongo Explorer plugin
-.idea/**/mongoSettings.xml
-
-# File-based project format
-*.iws
-
-# IntelliJ
-out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# SonarLint plugin
-.idea/sonarlint/
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-# Editor-based Rest Client
-.idea/httpRequests
-
-# Android studio 3.1+ serialized cache file
-.idea/caches/build_file_checksums.ser
-
-### Intellij Patch ###
-# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
-
-*.iml
-modules.xml
-.idea/misc.xml
-*.ipr
-
-# Sonarlint plugin
-# https://plugins.jetbrains.com/plugin/7973-sonarlint
-.idea/**/sonarlint/
-
-# SonarQube Plugin
-# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
-.idea/**/sonarIssues.xml
-
-# Markdown Navigator plugin
-# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
-.idea/**/markdown-navigator.xml
-.idea/**/markdown-navigator-enh.xml
-.idea/**/markdown-navigator/
-
-# Cache file creation bug
-# See https://youtrack.jetbrains.com/issue/JBR-2257
-.idea/$CACHE_FILE$
-
-# CodeStream plugin
-# https://plugins.jetbrains.com/plugin/12206-codestream
-.idea/codestream.xml
-
-# Azure Toolkit for IntelliJ plugin
-# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
-.idea/**/azureSettings.xml
-
-### Java ###
-# Compiled class file
-*.class
-
-# Log file
-*.log
-
-# BlueJ files
-*.ctxt
-
-# Mobile Tools for Java (J2ME)
-.mtj.tmp/
-
-# Package Files #
-*.jar
-*.war
-*.nar
-*.ear
-*.zip
-*.tar.gz
-*.rar
-
-# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
-hs_err_pid*
-replay_pid*
-
-### Gradle ###
-.gradle
-**/build/
-!src/**/build/
-
-# Ignore Gradle GUI config
-gradle-app.setting
-
-# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
-!gradle-wrapper.jar
-
-# Avoid ignore Gradle wrappper properties
-!gradle-wrapper.properties
-
-# Cache of project
-.gradletasknamecache
-
-# Eclipse Gradle plugin generated files
-# Eclipse Core
-.project
-# JDT-specific (Eclipse Java Development Tools)
-.classpath
-
-### Gradle Patch ###
-# Java heap dump
-*.hprof
-
-# End of https://www.toptal.com/developers/gitignore/api/java,gradle,intellij,eclipse
\ No newline at end of file
diff --git a/JShellAPI/.idea/.gitignore b/JShellAPI/.idea/.gitignore
deleted file mode 100644
index 13566b8..0000000
--- a/JShellAPI/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/JShellAPI/.idea/compiler.xml b/JShellAPI/.idea/compiler.xml
deleted file mode 100644
index e58d3e4..0000000
--- a/JShellAPI/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/JShellAPI/.idea/jarRepositories.xml b/JShellAPI/.idea/jarRepositories.xml
deleted file mode 100644
index fdc392f..0000000
--- a/JShellAPI/.idea/jarRepositories.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/JShellAPI/.idea/misc.xml b/JShellAPI/.idea/misc.xml
deleted file mode 100644
index 29f2b8d..0000000
--- a/JShellAPI/.idea/misc.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/JShellAPI/.idea/vcs.xml b/JShellAPI/.idea/vcs.xml
deleted file mode 100644
index 6c0b863..0000000
--- a/JShellAPI/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/JShellAPI/GUIDE.MD b/JShellAPI/README.MD
similarity index 100%
rename from JShellAPI/GUIDE.MD
rename to JShellAPI/README.MD
diff --git a/JShellAPI/RunDocker.bat b/JShellAPI/RunDocker.bat
deleted file mode 100644
index 023a60b..0000000
--- a/JShellAPI/RunDocker.bat
+++ /dev/null
@@ -1 +0,0 @@
-docker run --rm -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock --name jshellapi togetherjava.org:5001/togetherjava/jshellbackend:master
diff --git a/JShellAPI/RunDocker.sh b/JShellAPI/RunDocker.sh
deleted file mode 100644
index 023a60b..0000000
--- a/JShellAPI/RunDocker.sh
+++ /dev/null
@@ -1 +0,0 @@
-docker run --rm -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock --name jshellapi togetherjava.org:5001/togetherjava/jshellbackend:master
diff --git a/JShellAPI/build.gradle b/JShellAPI/build.gradle
index 461bb92..776dd31 100644
--- a/JShellAPI/build.gradle
+++ b/JShellAPI/build.gradle
@@ -1,36 +1,26 @@
-import org.gradle.jvm.toolchain.JavaLanguageVersion
+import java.time.Instant
plugins {
- id 'java'
- id 'org.springframework.boot' version '3.0.1'
+ id 'org.springframework.boot' version '3.2.5'
id 'io.spring.dependency-management' version '1.1.0'
id 'com.google.cloud.tools.jib' version '3.3.2'
id 'com.github.johnrengelman.shadow' version '8.1.1'
}
-group 'org.togetherjava'
-version '1.0-SNAPSHOT'
-
-repositories {
- mavenCentral()
-}
-
dependencies {
implementation project(':JShellWrapper')
implementation 'org.springframework.boot:spring-boot-starter-web'
- implementation 'com.github.docker-java:docker-java-transport-httpclient5:3.3.4'
- implementation 'com.github.docker-java:docker-java-core:3.3.4'
+ implementation 'com.github.docker-java:docker-java-transport-httpclient5:3.3.6'
+ implementation 'com.github.docker-java:docker-java-core:3.3.6'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
}
-var outputImage = 'togetherjava.org:5001/togetherjava/jshellbackend:master' ?: 'latest'
-
jib {
from.image = 'eclipse-temurin:21'
to {
- image = outputImage
+ image = 'togetherjava.org:5001/togetherjava/jshellbackend:master' ?: 'latest'
auth {
username = System.getenv('ORG_REGISTRY_USER') ?: ''
password = System.getenv('ORG_REGISTRY_PASSWORD') ?: ''
@@ -38,7 +28,7 @@ jib {
}
container {
mainClass = 'org.togetherjava.jshellapi.Main'
- setCreationTime(java.time.Instant.now().toString())
+ setCreationTime(Instant.now().toString())
}
}
@@ -46,8 +36,4 @@ shadowJar {
archiveBaseName.set('JShellPlaygroundBackend')
archiveClassifier.set('')
archiveVersion.set('')
-}
-
-tasks.named('test') {
- useJUnitPlatform()
-}
+}
\ No newline at end of file
diff --git a/JShellAPI/settings.gradle b/JShellAPI/settings.gradle
deleted file mode 100644
index 2476bde..0000000
--- a/JShellAPI/settings.gradle
+++ /dev/null
@@ -1,2 +0,0 @@
-rootProject.name = 'JShellAPI'
-
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/Config.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/Config.java
index 3a6942a..3e2558b 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/Config.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/Config.java
@@ -18,18 +18,30 @@ public record Config(
long dockerResponseTimeout,
long dockerConnectionTimeout) {
public Config {
- if(regularSessionTimeoutSeconds <= 0) throw new IllegalArgumentException("Invalid value " + regularSessionTimeoutSeconds);
- if(oneTimeSessionTimeoutSeconds <= 0) throw new IllegalArgumentException("Invalid value " + oneTimeSessionTimeoutSeconds);
- if(evalTimeoutSeconds <= 0) throw new IllegalArgumentException("Invalid value " + evalTimeoutSeconds);
- if(evalTimeoutValidationLeeway <= 0) throw new IllegalArgumentException("Invalid value " + evalTimeoutSeconds);
- if(sysOutCharLimit <= 0) throw new IllegalArgumentException("Invalid value " + sysOutCharLimit);
- if(maxAliveSessions <= 0) throw new IllegalArgumentException("Invalid value " + maxAliveSessions);
- if(dockerMaxRamMegaBytes <= 0) throw new IllegalArgumentException("Invalid value " + dockerMaxRamMegaBytes);
- if(dockerCPUsUsage <= 0) throw new IllegalArgumentException("Invalid value " + dockerCPUsUsage);
- if(dockerCPUSetCPUs != null && !dockerCPUSetCPUs.matches("[1-9]?\\d([-,]\\d?\\d)?"))
+ if (regularSessionTimeoutSeconds <= 0)
+ throw new IllegalArgumentException("Invalid value " + regularSessionTimeoutSeconds);
+ if (oneTimeSessionTimeoutSeconds <= 0)
+ throw new IllegalArgumentException("Invalid value " + oneTimeSessionTimeoutSeconds);
+ if (evalTimeoutSeconds <= 0)
+ throw new IllegalArgumentException("Invalid value " + evalTimeoutSeconds);
+ if (evalTimeoutValidationLeeway <= 0)
+ throw new IllegalArgumentException("Invalid value " + evalTimeoutSeconds);
+ if (sysOutCharLimit <= 0)
+ throw new IllegalArgumentException("Invalid value " + sysOutCharLimit);
+ if (maxAliveSessions <= 0)
+ throw new IllegalArgumentException("Invalid value " + maxAliveSessions);
+ if (dockerMaxRamMegaBytes <= 0)
+ throw new IllegalArgumentException("Invalid value " + dockerMaxRamMegaBytes);
+ if (dockerCPUsUsage <= 0)
+ throw new IllegalArgumentException("Invalid value " + dockerCPUsUsage);
+ if (dockerCPUSetCPUs != null && !dockerCPUSetCPUs.matches("[1-9]?\\d([-,]\\d?\\d)?"))
throw new IllegalArgumentException("Invalid value " + dockerCPUSetCPUs);
- if(schedulerSessionKillScanRateSeconds <= 0) throw new IllegalArgumentException("Invalid value " + schedulerSessionKillScanRateSeconds);
- if(dockerResponseTimeout <= 0) throw new IllegalArgumentException("Invalid value " + dockerResponseTimeout);
- if(dockerConnectionTimeout <= 0) throw new IllegalArgumentException("Invalid value " + dockerConnectionTimeout);
+ if (schedulerSessionKillScanRateSeconds <= 0)
+ throw new IllegalArgumentException(
+ "Invalid value " + schedulerSessionKillScanRateSeconds);
+ if (dockerResponseTimeout <= 0)
+ throw new IllegalArgumentException("Invalid value " + dockerResponseTimeout);
+ if (dockerConnectionTimeout <= 0)
+ throw new IllegalArgumentException("Invalid value " + dockerConnectionTimeout);
}
}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/Main.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/Main.java
index 7012e15..8d37831 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/Main.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/Main.java
@@ -15,4 +15,4 @@ public static void main(String[] args) {
SpringApplication.run(Main.class, args);
LOGGER.info("Running.");
}
-}
\ No newline at end of file
+}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellEvalAbortion.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellEvalAbortion.java
index 48dee78..3a6f93b 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellEvalAbortion.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellEvalAbortion.java
@@ -1,4 +1,4 @@
package org.togetherjava.jshellapi.dto;
-public record JShellEvalAbortion(String sourceCause, String remainingSource, JShellEvalAbortionCause cause) {
-}
+public record JShellEvalAbortion(
+ String sourceCause, String remainingSource, JShellEvalAbortionCause cause) {}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellEvalAbortionCause.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellEvalAbortionCause.java
index 2a50790..2925ae5 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellEvalAbortionCause.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellEvalAbortionCause.java
@@ -9,18 +9,15 @@
public sealed interface JShellEvalAbortionCause {
@JsonTypeName("TIMEOUT")
- record TimeoutAbortionCause() implements JShellEvalAbortionCause {
- }
+ record TimeoutAbortionCause() implements JShellEvalAbortionCause {}
@JsonTypeName("UNCAUGHT_EXCEPTION")
- record UnhandledExceptionAbortionCause(String exceptionClass, String exceptionMessage) implements JShellEvalAbortionCause {
- }
+ record UnhandledExceptionAbortionCause(String exceptionClass, String exceptionMessage)
+ implements JShellEvalAbortionCause {}
@JsonTypeName("COMPILE_TIME_ERROR")
- record CompileTimeErrorAbortionCause(List errors) implements JShellEvalAbortionCause {
- }
+ record CompileTimeErrorAbortionCause(List errors) implements JShellEvalAbortionCause {}
@JsonTypeName("SYNTAX_ERROR")
- record SyntaxErrorAbortionCause() implements JShellEvalAbortionCause {
- }
+ record SyntaxErrorAbortionCause() implements JShellEvalAbortionCause {}
}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellResultWithId.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellResultWithId.java
index be9c7a3..e0e4d6f 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellResultWithId.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellResultWithId.java
@@ -1,4 +1,3 @@
package org.togetherjava.jshellapi.dto;
-public record JShellResultWithId(String id, JShellResult result) {
-}
+public record JShellResultWithId(String id, JShellResult result) {}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellSnippetResult.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellSnippetResult.java
index 78873d8..13c0d3a 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellSnippetResult.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/JShellSnippetResult.java
@@ -3,10 +3,4 @@
import org.springframework.lang.Nullable;
public record JShellSnippetResult(
- SnippetStatus status,
- SnippetType type,
- int id,
- String source,
- @Nullable
- String result) {
-}
+ SnippetStatus status, SnippetType type, int id, String source, @Nullable String result) {}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/SnippetStatus.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/SnippetStatus.java
index 1b83d88..fac958f 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/SnippetStatus.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/SnippetStatus.java
@@ -1,5 +1,8 @@
package org.togetherjava.jshellapi.dto;
public enum SnippetStatus {
- VALID, RECOVERABLE_DEFINED, RECOVERABLE_NOT_DEFINED, REJECTED
+ VALID,
+ RECOVERABLE_DEFINED,
+ RECOVERABLE_NOT_DEFINED,
+ REJECTED
}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/SnippetType.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/SnippetType.java
index 12166ff..0e9d4d4 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/SnippetType.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/dto/SnippetType.java
@@ -1,5 +1,6 @@
package org.togetherjava.jshellapi.dto;
public enum SnippetType {
- ADDITION, MODIFICATION
+ ADDITION,
+ MODIFICATION
}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/exceptions/DockerException.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/exceptions/DockerException.java
index 0241489..3c7e093 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/exceptions/DockerException.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/exceptions/DockerException.java
@@ -3,10 +3,9 @@
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
-@ResponseStatus(value= HttpStatus.CONFLICT, reason="Session may have been forcibly destroyed.")
+@ResponseStatus(value = HttpStatus.CONFLICT, reason = "Session may have been forcibly destroyed.")
public class DockerException extends Exception {
- public DockerException() {
- }
+ public DockerException() {}
public DockerException(String message) {
super(message);
@@ -20,7 +19,11 @@ public DockerException(Throwable cause) {
super(cause);
}
- public DockerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ public DockerException(
+ String message,
+ Throwable cause,
+ boolean enableSuppression,
+ boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/JShellController.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/JShellController.java
index 46a42ec..63d2011 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/JShellController.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/rest/JShellController.java
@@ -21,32 +21,71 @@ public class JShellController {
private StartupScriptsService startupScriptsService;
@PostMapping("/eval/{id}")
- public JShellResult eval(@PathVariable String id, @RequestParam(required = false) StartupScriptId startupScriptId, @RequestBody String code) throws DockerException {
+ public JShellResult eval(
+ @PathVariable String id,
+ @RequestParam(required = false) StartupScriptId startupScriptId,
+ @RequestBody String code)
+ throws DockerException {
validateId(id);
- return service.session(id, startupScriptId).eval(code).orElseThrow(() -> new ResponseStatusException(HttpStatus.CONFLICT, "An operation is already running"));
+ return service.session(id, startupScriptId)
+ .eval(code)
+ .orElseThrow(
+ () ->
+ new ResponseStatusException(
+ HttpStatus.CONFLICT, "An operation is already running"));
}
+
@PostMapping("/eval")
- public JShellResultWithId eval(@RequestParam(required = false) StartupScriptId startupScriptId, @RequestBody String code) throws DockerException {
+ public JShellResultWithId eval(
+ @RequestParam(required = false) StartupScriptId startupScriptId,
+ @RequestBody String code)
+ throws DockerException {
JShellService jShellService = service.session(startupScriptId);
- return new JShellResultWithId(jShellService.id(), jShellService.eval(code).orElseThrow(() -> new ResponseStatusException(HttpStatus.CONFLICT, "An operation is already running")));
+ return new JShellResultWithId(
+ jShellService.id(),
+ jShellService
+ .eval(code)
+ .orElseThrow(
+ () ->
+ new ResponseStatusException(
+ HttpStatus.CONFLICT,
+ "An operation is already running")));
}
+
@PostMapping("/single-eval")
- public JShellResult singleEval(@RequestParam(required = false) StartupScriptId startupScriptId, @RequestBody String code) throws DockerException {
+ public JShellResult singleEval(
+ @RequestParam(required = false) StartupScriptId startupScriptId,
+ @RequestBody String code)
+ throws DockerException {
JShellService jShellService = service.oneTimeSession(startupScriptId);
- return jShellService.eval(code).orElseThrow(() -> new ResponseStatusException(HttpStatus.CONFLICT, "An operation is already running"));
+ return jShellService
+ .eval(code)
+ .orElseThrow(
+ () ->
+ new ResponseStatusException(
+ HttpStatus.CONFLICT, "An operation is already running"));
}
@GetMapping("/snippets/{id}")
- public List snippets(@PathVariable String id, @RequestParam(required = false) boolean includeStartupScript) throws DockerException {
+ public List snippets(
+ @PathVariable String id, @RequestParam(required = false) boolean includeStartupScript)
+ throws DockerException {
validateId(id);
- if(!service.hasSession(id)) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Id " + id + " not found");
- return service.session(id, null).snippets(includeStartupScript).orElseThrow(() -> new ResponseStatusException(HttpStatus.CONFLICT, "An operation is already running"));
+ if (!service.hasSession(id))
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Id " + id + " not found");
+ return service.session(id, null)
+ .snippets(includeStartupScript)
+ .orElseThrow(
+ () ->
+ new ResponseStatusException(
+ HttpStatus.CONFLICT, "An operation is already running"));
}
@DeleteMapping("/{id}")
public void delete(@PathVariable String id) throws DockerException {
validateId(id);
- if(!service.hasSession(id)) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Id " + id + " not found");
+ if (!service.hasSession(id))
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Id " + id + " not found");
service.deleteSession(id);
}
@@ -66,8 +105,10 @@ public void setStartupScriptsService(StartupScriptsService startupScriptsService
}
private static void validateId(String id) throws ResponseStatusException {
- if(!id.matches("[a-zA-Z0-9][a-zA-Z0-9_.-]+")) {
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Id " + id + " doesn't match the regex [a-zA-Z0-9][a-zA-Z0-9_.-]+");
+ if (!id.matches("[a-zA-Z0-9][a-zA-Z0-9_.-]+")) {
+ throw new ResponseStatusException(
+ HttpStatus.BAD_REQUEST,
+ "Id " + id + " doesn't match the regex [a-zA-Z0-9][a-zA-Z0-9_.-]+");
}
}
}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/DockerService.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/DockerService.java
index 8d011ed..e7fb651 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/DockerService.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/DockerService.java
@@ -7,6 +7,7 @@
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
@@ -29,21 +30,25 @@ public class DockerService implements DisposableBean {
private final DockerClient client;
public DockerService(Config config) {
- DefaultDockerClientConfig clientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder().build();
- ApacheDockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
- .dockerHost(clientConfig.getDockerHost())
- .sslConfig(clientConfig.getSSLConfig())
- .responseTimeout(Duration.ofSeconds(config.dockerResponseTimeout()))
- .connectionTimeout(Duration.ofSeconds(config.dockerConnectionTimeout()))
- .build();
+ DefaultDockerClientConfig clientConfig =
+ DefaultDockerClientConfig.createDefaultConfigBuilder().build();
+ ApacheDockerHttpClient httpClient =
+ new ApacheDockerHttpClient.Builder()
+ .dockerHost(clientConfig.getDockerHost())
+ .sslConfig(clientConfig.getSSLConfig())
+ .responseTimeout(Duration.ofSeconds(config.dockerResponseTimeout()))
+ .connectionTimeout(Duration.ofSeconds(config.dockerConnectionTimeout()))
+ .build();
this.client = DockerClientImpl.getInstance(clientConfig, httpClient);
cleanupLeftovers(WORKER_UNIQUE_ID);
}
private void cleanupLeftovers(UUID currentId) {
- for (Container container : client.listContainersCmd().withLabelFilter(Set.of(WORKER_LABEL)).exec()) {
- String containerHumanName = container.getId() + " " + Arrays.toString(container.getNames());
+ for (Container container :
+ client.listContainersCmd().withLabelFilter(Set.of(WORKER_LABEL)).exec()) {
+ String containerHumanName =
+ container.getId() + " " + Arrays.toString(container.getNames());
LOGGER.info("Found worker container '{}'", containerHumanName);
if (!container.getLabels().get(WORKER_LABEL).equals(currentId.toString())) {
LOGGER.info("Killing container '{}'", containerHumanName);
@@ -53,15 +58,18 @@ private void cleanupLeftovers(UUID currentId) {
}
public String spawnContainer(
- long maxMemoryMegs, long cpus, @Nullable String cpuSetCpus, String name, Duration evalTimeout, long sysoutLimit
- ) throws InterruptedException {
+ long maxMemoryMegs,
+ long cpus,
+ @Nullable String cpuSetCpus,
+ String name,
+ Duration evalTimeout,
+ long sysoutLimit)
+ throws InterruptedException {
String imageName = "togetherjava.org:5001/togetherjava/jshellwrapper";
- boolean presentLocally = client.listImagesCmd()
- .withFilter("reference", List.of(imageName))
- .exec()
- .stream()
- .flatMap(it -> Arrays.stream(it.getRepoTags()))
- .anyMatch(it -> it.endsWith(":master"));
+ boolean presentLocally =
+ client.listImagesCmd().withFilter("reference", List.of(imageName)).exec().stream()
+ .flatMap(it -> Arrays.stream(it.getRepoTags()))
+ .anyMatch(it -> it.endsWith(":master"));
if (!presentLocally) {
client.pullImageCmd(imageName)
@@ -70,9 +78,7 @@ public String spawnContainer(
.awaitCompletion(5, TimeUnit.MINUTES);
}
- return client.createContainerCmd(
- imageName + ":master"
- )
+ return client.createContainerCmd(imageName + ":master")
.withHostConfig(
HostConfig.newHostConfig()
.withAutoRemove(true)
@@ -83,20 +89,22 @@ public String spawnContainer(
.withReadonlyRootfs(true)
.withMemory(maxMemoryMegs * 1024 * 1024)
.withCpuCount(cpus)
- .withCpusetCpus(cpuSetCpus)
- )
+ .withCpusetCpus(cpuSetCpus))
.withStdinOpen(true)
.withAttachStdin(true)
.withAttachStderr(true)
.withAttachStdout(true)
- .withEnv("evalTimeoutSeconds=" + evalTimeout.toSeconds(), "sysOutCharLimit=" + sysoutLimit)
+ .withEnv(
+ "evalTimeoutSeconds=" + evalTimeout.toSeconds(),
+ "sysOutCharLimit=" + sysoutLimit)
.withLabels(Map.of(WORKER_LABEL, WORKER_UNIQUE_ID.toString()))
.withName(name)
.exec()
.getId();
}
- public InputStream startAndAttachToContainer(String containerId, InputStream stdin) throws IOException {
+ public InputStream startAndAttachToContainer(String containerId, InputStream stdin)
+ throws IOException {
PipedInputStream pipeIn = new PipedInputStream();
PipedOutputStream pipeOut = new PipedOutputStream(pipeIn);
@@ -106,25 +114,26 @@ public InputStream startAndAttachToContainer(String containerId, InputStream std
.withStdOut(true)
.withStdErr(true)
.withStdIn(stdin)
- .exec(new ResultCallback.Adapter<>() {
- @Override
- public void onNext(Frame object) {
- try {
- String payloadString = new String(object.getPayload(), StandardCharsets.UTF_8);
- if (object.getStreamType() == StreamType.STDOUT) {
- pipeOut.write(object.getPayload());
- } else {
- LOGGER.warn(
- "Received STDERR from container {}: {}",
- containerId,
- payloadString
- );
+ .exec(
+ new ResultCallback.Adapter<>() {
+ @Override
+ public void onNext(Frame object) {
+ try {
+ String payloadString =
+ new String(object.getPayload(), StandardCharsets.UTF_8);
+ if (object.getStreamType() == StreamType.STDOUT) {
+ pipeOut.write(object.getPayload());
+ } else {
+ LOGGER.warn(
+ "Received STDERR from container {}: {}",
+ containerId,
+ payloadString);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
}
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
- });
+ });
client.startContainerCmd(containerId).exec();
return pipeIn;
@@ -146,5 +155,4 @@ public void destroy() throws Exception {
cleanupLeftovers(UUID.randomUUID());
client.close();
}
-
}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/JShellService.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/JShellService.java
index 1903d4d..e712f55 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/JShellService.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/JShellService.java
@@ -30,7 +30,20 @@ public class JShellService implements Closeable {
private final DockerService dockerService;
private final int startupScriptSize;
- public JShellService(DockerService dockerService, JShellSessionService sessionService, String id, long timeout, boolean renewable, long evalTimeout, long evalTimeoutValidationLeeway, int sysOutCharLimit, int maxMemory, double cpus, @Nullable String cpuSetCpus, String startupScript) throws DockerException {
+ public JShellService(
+ DockerService dockerService,
+ JShellSessionService sessionService,
+ String id,
+ long timeout,
+ boolean renewable,
+ long evalTimeout,
+ long evalTimeoutValidationLeeway,
+ int sysOutCharLimit,
+ int maxMemory,
+ double cpus,
+ @Nullable String cpuSetCpus,
+ String startupScript)
+ throws DockerException {
this.dockerService = dockerService;
this.sessionService = sessionService;
this.id = id;
@@ -39,25 +52,25 @@ public JShellService(DockerService dockerService, JShellSessionService sessionSe
this.evalTimeout = evalTimeout;
this.evalTimeoutValidationLeeway = evalTimeoutValidationLeeway;
this.lastTimeoutUpdate = Instant.now();
- if(!dockerService.isDead(containerName())) {
+ if (!dockerService.isDead(containerName())) {
LOGGER.warn("Tried to create an existing container {}.", containerName());
throw new DockerException("The session isn't completely destroyed, try again later.");
}
try {
- String containerId = dockerService.spawnContainer(
- maxMemory,
- (long) Math.ceil(cpus),
- cpuSetCpus,
- containerName(),
- Duration.ofSeconds(evalTimeout),
- sysOutCharLimit
- );
+ String containerId =
+ dockerService.spawnContainer(
+ maxMemory,
+ (long) Math.ceil(cpus),
+ cpuSetCpus,
+ containerName(),
+ Duration.ofSeconds(evalTimeout),
+ sysOutCharLimit);
PipedInputStream containerInput = new PipedInputStream();
- this.writer = new BufferedWriter(new OutputStreamWriter(new PipedOutputStream(containerInput)));
- InputStream containerOutput = dockerService.startAndAttachToContainer(
- containerId,
- containerInput
- );
+ this.writer =
+ new BufferedWriter(
+ new OutputStreamWriter(new PipedOutputStream(containerInput)));
+ InputStream containerOutput =
+ dockerService.startAndAttachToContainer(containerId, containerInput);
reader = new BufferedReader(new InputStreamReader(containerOutput));
writer.write(sanitize(startupScript));
writer.newLine();
@@ -70,9 +83,10 @@ public JShellService(DockerService dockerService, JShellSessionService sessionSe
}
this.doingOperation = false;
}
+
public Optional eval(String code) throws DockerException {
synchronized (this) {
- if(!tryStartOperation()) {
+ if (!tryStartOperation()) {
return Optional.empty();
}
}
@@ -82,7 +96,7 @@ public Optional eval(String code) throws DockerException {
}
updateLastTimeout();
sessionService.scheduleEvalTimeoutValidation(id, evalTimeout + evalTimeoutValidationLeeway);
- if(!code.endsWith("\n")) code += '\n';
+ if (!code.endsWith("\n")) code += '\n';
try {
writer.write("eval");
writer.newLine();
@@ -102,12 +116,21 @@ public Optional eval(String code) throws DockerException {
stopOperation();
}
}
+
private JShellResult readResult() throws IOException, NumberFormatException, DockerException {
final int snippetsCount = Integer.parseInt(reader.readLine());
List snippetResults = new ArrayList<>();
- for(int i = 0; i < snippetsCount; i++) {
- SnippetStatus status = Utils.nameOrElseThrow(SnippetStatus.class, reader.readLine(), name -> new DockerException(name + " isn't an enum constant"));
- SnippetType type = Utils.nameOrElseThrow(SnippetType.class, reader.readLine(), name -> new DockerException(name + " isn't an enum constant"));
+ for (int i = 0; i < snippetsCount; i++) {
+ SnippetStatus status =
+ Utils.nameOrElseThrow(
+ SnippetStatus.class,
+ reader.readLine(),
+ name -> new DockerException(name + " isn't an enum constant"));
+ SnippetType type =
+ Utils.nameOrElseThrow(
+ SnippetType.class,
+ reader.readLine(),
+ name -> new DockerException(name + " isn't an enum constant"));
int snippetId = Integer.parseInt(reader.readLine());
String source = cleanCode(reader.readLine());
String result = reader.readLine().transform(r -> r.equals("NONE") ? null : r);
@@ -115,24 +138,29 @@ private JShellResult readResult() throws IOException, NumberFormatException, Doc
}
JShellEvalAbortion abortion = null;
String rawAbortionCause = reader.readLine();
- if(!rawAbortionCause.isEmpty()) {
- JShellEvalAbortionCause abortionCause = switch (rawAbortionCause) {
- case "TIMEOUT" -> new JShellEvalAbortionCause.TimeoutAbortionCause();
- case "UNCAUGHT_EXCEPTION" -> {
- String[] split = reader.readLine().split(":");
- yield new JShellEvalAbortionCause.UnhandledExceptionAbortionCause(split[0], split[1]);
- }
- case "COMPILE_TIME_ERROR" -> {
- int errorCount = Integer.parseInt(reader.readLine());
- List errors = new ArrayList<>();
- for(int i = 0; i < errorCount; i++) {
- errors.add(desanitize(reader.readLine()));
- }
- yield new JShellEvalAbortionCause.CompileTimeErrorAbortionCause(errors);
- }
- case "SYNTAX_ERROR" -> new JShellEvalAbortionCause.SyntaxErrorAbortionCause();
- default -> throw new DockerException("Abortion cause " + rawAbortionCause + " doesn't exist");
- };
+ if (!rawAbortionCause.isEmpty()) {
+ JShellEvalAbortionCause abortionCause =
+ switch (rawAbortionCause) {
+ case "TIMEOUT" -> new JShellEvalAbortionCause.TimeoutAbortionCause();
+ case "UNCAUGHT_EXCEPTION" -> {
+ String[] split = reader.readLine().split(":");
+ yield new JShellEvalAbortionCause.UnhandledExceptionAbortionCause(
+ split[0], split[1]);
+ }
+ case "COMPILE_TIME_ERROR" -> {
+ int errorCount = Integer.parseInt(reader.readLine());
+ List errors = new ArrayList<>();
+ for (int i = 0; i < errorCount; i++) {
+ errors.add(desanitize(reader.readLine()));
+ }
+ yield new JShellEvalAbortionCause.CompileTimeErrorAbortionCause(errors);
+ }
+ case "SYNTAX_ERROR" ->
+ new JShellEvalAbortionCause.SyntaxErrorAbortionCause();
+ default ->
+ throw new DockerException(
+ "Abortion cause " + rawAbortionCause + " doesn't exist");
+ };
String causeSource = cleanCode(reader.readLine());
String remainingSource = cleanCode(reader.readLine());
abortion = new JShellEvalAbortion(causeSource, remainingSource, abortionCause);
@@ -144,7 +172,7 @@ private JShellResult readResult() throws IOException, NumberFormatException, Doc
public Optional> snippets(boolean includeStartupScript) throws DockerException {
synchronized (this) {
- if(!tryStartOperation()) {
+ if (!tryStartOperation()) {
return Optional.empty();
}
}
@@ -158,12 +186,13 @@ public Optional> snippets(boolean includeStartupScript) throws Dock
List snippets = new ArrayList<>();
String snippet;
- while(!(snippet = reader.readLine()).isEmpty()) {
+ while (!(snippet = reader.readLine()).isEmpty()) {
snippets.add(cleanCode(snippet));
}
return Optional.of(
- includeStartupScript ? snippets : snippets.subList(startupScriptSize, snippets.size())
- );
+ includeStartupScript
+ ? snippets
+ : snippets.subList(startupScriptSize, snippets.size()));
} catch (IOException ex) {
LOGGER.warn("Unexpected error.", ex);
close();
@@ -178,7 +207,10 @@ public String containerName() {
}
public boolean isInvalidEvalTimeout() {
- return doingOperation && lastTimeoutUpdate.plusSeconds(evalTimeout + evalTimeoutValidationLeeway).isBefore(Instant.now());
+ return doingOperation
+ && lastTimeoutUpdate
+ .plusSeconds(evalTimeout + evalTimeoutValidationLeeway)
+ .isBefore(Instant.now());
}
public boolean shouldDie() {
@@ -208,7 +240,7 @@ public void close() {
} finally {
reader.close();
}
- } catch(IOException ex) {
+ } catch (IOException ex) {
LOGGER.error("Unexpected error while closing.", ex);
} finally {
sessionService.notifyDeath(id);
@@ -221,14 +253,14 @@ public boolean isClosed() {
}
private void updateLastTimeout() {
- if(renewable) {
+ if (renewable) {
lastTimeoutUpdate = Instant.now();
}
}
private void checkContainerOK() throws DockerException {
try {
- if(dockerService.isDead(containerName())) {
+ if (dockerService.isDead(containerName())) {
try {
close();
} finally {
@@ -236,18 +268,19 @@ private void checkContainerOK() throws DockerException {
}
}
String OK = reader.readLine();
- if(OK == null) {
+ if (OK == null) {
try {
close();
} finally {
throw new DockerException("Container of session " + id + " is dead");
}
}
- if(!OK.equals("OK")) {
+ if (!OK.equals("OK")) {
try {
close();
} finally {
- throw new DockerException("Container of session " + id + " returned invalid info : " + OK);
+ throw new DockerException(
+ "Container of session " + id + " returned invalid info : " + OK);
}
}
} catch (IOException ex) {
@@ -257,10 +290,11 @@ private void checkContainerOK() throws DockerException {
}
private synchronized boolean tryStartOperation() {
- if(doingOperation) return false;
+ if (doingOperation) return false;
doingOperation = true;
return true;
}
+
private void stopOperation() {
doingOperation = false;
}
@@ -272,8 +306,8 @@ private static String sanitize(String s) {
private static String desanitize(String text) {
return text.replace("\\n", "\n").replace("\\\\", "\\");
}
+
private static String cleanCode(String code) {
return code.translateEscapes();
}
-
}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/JShellSessionService.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/JShellSessionService.java
index 432c0e3..5aacbb8 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/JShellSessionService.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/JShellSessionService.java
@@ -26,28 +26,32 @@ public class JShellSessionService {
private void initScheduler() {
scheduler = Executors.newSingleThreadScheduledExecutor();
- scheduler.scheduleAtFixedRate(() -> {
- jshellSessions.keySet()
- .stream()
- .filter(id -> jshellSessions.get(id).isClosed())
- .forEach(this::notifyDeath);
- List toDie = jshellSessions.keySet()
- .stream()
- .filter(id -> jshellSessions.get(id).shouldDie())
- .toList();
- for(String id : toDie) {
- try {
- deleteSession(id);
- } catch (DockerException ex) {
- LOGGER.error("Unexpected error when deleting session.", ex);
- }
- }
- }, config.schedulerSessionKillScanRateSeconds(), config.schedulerSessionKillScanRateSeconds(), TimeUnit.SECONDS);
+ scheduler.scheduleAtFixedRate(
+ () -> {
+ jshellSessions.keySet().stream()
+ .filter(id -> jshellSessions.get(id).isClosed())
+ .forEach(this::notifyDeath);
+ List toDie =
+ jshellSessions.keySet().stream()
+ .filter(id -> jshellSessions.get(id).shouldDie())
+ .toList();
+ for (String id : toDie) {
+ try {
+ deleteSession(id);
+ } catch (DockerException ex) {
+ LOGGER.error("Unexpected error when deleting session.", ex);
+ }
+ }
+ },
+ config.schedulerSessionKillScanRateSeconds(),
+ config.schedulerSessionKillScanRateSeconds(),
+ TimeUnit.SECONDS);
}
+
void notifyDeath(String id) {
JShellService shellService = jshellSessions.remove(id);
- if(shellService == null) return;
- if(!shellService.isClosed()) {
+ if (shellService == null) return;
+ if (!shellService.isClosed()) {
LOGGER.error("JShell Service isn't dead when it should for id {}.", id);
}
LOGGER.info("Session {} died.", id);
@@ -57,17 +61,25 @@ public boolean hasSession(String id) {
return jshellSessions.containsKey(id);
}
- public JShellService session(String id, @Nullable StartupScriptId startupScriptId) throws DockerException {
- if(!hasSession(id)) {
+ public JShellService session(String id, @Nullable StartupScriptId startupScriptId)
+ throws DockerException {
+ if (!hasSession(id)) {
return createSession(new SessionInfo(id, true, startupScriptId, false, config));
}
return jshellSessions.get(id);
}
+
public JShellService session(@Nullable StartupScriptId startupScriptId) throws DockerException {
- return createSession(new SessionInfo(UUID.randomUUID().toString(), false, startupScriptId, false, config));
+ return createSession(
+ new SessionInfo(
+ UUID.randomUUID().toString(), false, startupScriptId, false, config));
}
- public JShellService oneTimeSession(@Nullable StartupScriptId startupScriptId) throws DockerException {
- return createSession(new SessionInfo(UUID.randomUUID().toString(), false, startupScriptId, true, config));
+
+ public JShellService oneTimeSession(@Nullable StartupScriptId startupScriptId)
+ throws DockerException {
+ return createSession(
+ new SessionInfo(
+ UUID.randomUUID().toString(), false, startupScriptId, true, config));
}
public void deleteSession(String id) throws DockerException {
@@ -76,46 +88,55 @@ public void deleteSession(String id) throws DockerException {
scheduler.schedule(service::close, 500, TimeUnit.MILLISECONDS);
}
- private synchronized JShellService createSession(SessionInfo sessionInfo) throws DockerException {
- if(hasSession(sessionInfo.id())) { //Just in case race condition happens just before createSession
+ private synchronized JShellService createSession(SessionInfo sessionInfo)
+ throws DockerException {
+ if (hasSession(
+ sessionInfo
+ .id())) { // Just in case race condition happens just before createSession
return jshellSessions.get(sessionInfo.id());
}
- if(jshellSessions.size() >= config.maxAliveSessions()) {
- throw new ResponseStatusException(HttpStatus.TOO_MANY_REQUESTS, "Too many sessions, try again later :(.");
+ if (jshellSessions.size() >= config.maxAliveSessions()) {
+ throw new ResponseStatusException(
+ HttpStatus.TOO_MANY_REQUESTS, "Too many sessions, try again later :(.");
}
LOGGER.info("Creating session : {}.", sessionInfo);
- JShellService service = new JShellService(
- dockerService,
- this,
- sessionInfo.id(),
- sessionInfo.sessionTimeout(),
- sessionInfo.renewable(),
- sessionInfo.evalTimeout(),
- sessionInfo.evalTimeoutValidationLeeway(),
- sessionInfo.sysOutCharLimit(),
- config.dockerMaxRamMegaBytes(),
- config.dockerCPUsUsage(),
- config.dockerCPUSetCPUs(),
- startupScriptsService.get(sessionInfo.startupScriptId()));
+ JShellService service =
+ new JShellService(
+ dockerService,
+ this,
+ sessionInfo.id(),
+ sessionInfo.sessionTimeout(),
+ sessionInfo.renewable(),
+ sessionInfo.evalTimeout(),
+ sessionInfo.evalTimeoutValidationLeeway(),
+ sessionInfo.sysOutCharLimit(),
+ config.dockerMaxRamMegaBytes(),
+ config.dockerCPUsUsage(),
+ config.dockerCPUSetCPUs(),
+ startupScriptsService.get(sessionInfo.startupScriptId()));
jshellSessions.put(sessionInfo.id(), service);
return service;
}
/**
- * Schedule the validation of the session timeout.
- * In case the code runs for too long, checks if the wrapper correctly followed the eval timeout and canceled it,
- * if it didn't, forcefully close the session.
+ * Schedule the validation of the session timeout. In case the code runs for too long, checks if
+ * the wrapper correctly followed the eval timeout and canceled it, if it didn't, forcefully
+ * close the session.
+ *
* @param id the id of the session
* @param timeSeconds the time to schedule
*/
public void scheduleEvalTimeoutValidation(String id, long timeSeconds) {
- scheduler.schedule(() -> {
- JShellService service = jshellSessions.get(id);
- if(service == null) return;
- if(service.isInvalidEvalTimeout()) {
- service.close();
- }
- }, timeSeconds, TimeUnit.SECONDS);
+ scheduler.schedule(
+ () -> {
+ JShellService service = jshellSessions.get(id);
+ if (service == null) return;
+ if (service.isInvalidEvalTimeout()) {
+ service.close();
+ }
+ },
+ timeSeconds,
+ TimeUnit.SECONDS);
}
@Autowired
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/SessionInfo.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/SessionInfo.java
index a1572e3..631cd87 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/SessionInfo.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/SessionInfo.java
@@ -3,8 +3,29 @@
import org.springframework.lang.Nullable;
import org.togetherjava.jshellapi.Config;
-public record SessionInfo(String id, long sessionTimeout, boolean renewable, long evalTimeout, long evalTimeoutValidationLeeway, int sysOutCharLimit, @Nullable StartupScriptId startupScriptId) {
- public SessionInfo(String id, boolean renewable, StartupScriptId startupScriptId, boolean isOneTime, Config config) {
- this(id, isOneTime ? config.oneTimeSessionTimeoutSeconds() : config.regularSessionTimeoutSeconds(), renewable, config.evalTimeoutSeconds(), config.evalTimeoutValidationLeeway(), config.sysOutCharLimit(), startupScriptId);
+public record SessionInfo(
+ String id,
+ long sessionTimeout,
+ boolean renewable,
+ long evalTimeout,
+ long evalTimeoutValidationLeeway,
+ int sysOutCharLimit,
+ @Nullable StartupScriptId startupScriptId) {
+ public SessionInfo(
+ String id,
+ boolean renewable,
+ StartupScriptId startupScriptId,
+ boolean isOneTime,
+ Config config) {
+ this(
+ id,
+ isOneTime
+ ? config.oneTimeSessionTimeoutSeconds()
+ : config.regularSessionTimeoutSeconds(),
+ renewable,
+ config.evalTimeoutSeconds(),
+ config.evalTimeoutValidationLeeway(),
+ config.sysOutCharLimit(),
+ startupScriptId);
}
}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/StartupScriptId.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/StartupScriptId.java
index 50d3d50..3eb5f1d 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/StartupScriptId.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/StartupScriptId.java
@@ -1,5 +1,6 @@
package org.togetherjava.jshellapi.service;
public enum StartupScriptId {
- EMPTY, CUSTOM_DEFAULT;
+ EMPTY,
+ CUSTOM_DEFAULT;
}
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/StartupScriptsService.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/StartupScriptsService.java
index d2ba300..fd05211 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/StartupScriptsService.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/StartupScriptsService.java
@@ -19,8 +19,11 @@ public class StartupScriptsService {
private StartupScriptsService() {
scripts = new EnumMap<>(StartupScriptId.class);
for (StartupScriptId id : StartupScriptId.values()) {
- try (
- InputStream scriptStream = Objects.requireNonNull(StartupScriptsService.class.getResourceAsStream("/jshell_startup/" + id + ".jsh"), "Couldn't load script " + id)) {
+ try (InputStream scriptStream =
+ Objects.requireNonNull(
+ StartupScriptsService.class.getResourceAsStream(
+ "/jshell_startup/" + id + ".jsh"),
+ "Couldn't load script " + id)) {
String script = new String(scriptStream.readAllBytes(), StandardCharsets.UTF_8);
script = cleanEndLines(script);
scripts.put(id, script);
@@ -36,6 +39,7 @@ private String cleanEndLines(String s) {
/**
* Returns corresponding script, or default script if id is null
+ *
* @param id the id or the script, can be null
* @return corresponding script, or default script if id is null
*/
diff --git a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/Utils.java b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/Utils.java
index b5bef46..d19457e 100644
--- a/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/Utils.java
+++ b/JShellAPI/src/main/java/org/togetherjava/jshellapi/service/Utils.java
@@ -7,13 +7,17 @@
import java.util.stream.Stream;
public class Utils {
- public static , X extends Exception> E nameOrElseThrow(Class c, String name, Function exceptionFunction) throws X {
+ public static , X extends Exception> E nameOrElseThrow(
+ Class c, String name, Function exceptionFunction) throws X {
return name(c, name).orElseThrow(() -> exceptionFunction.apply(name));
}
+
public static > Optional name(Class c, String name) {
return predicate(c, e -> e.name().equals(name)).findAny();
}
- public static , K> Optional key(Class c, Function keyMapper, K name) {
+
+ public static , K> Optional key(
+ Class c, Function keyMapper, K name) {
return predicate(c, e -> keyMapper.apply(e).equals(name)).findAny();
}
@@ -22,7 +26,6 @@ public static > Stream predicate(Class c, Predicate p
if (enumConstants == null) {
throw new RuntimeException(); // Impossible
}
- return Arrays.stream(enumConstants)
- .filter(predicate);
+ return Arrays.stream(enumConstants).filter(predicate);
}
}
diff --git a/JShellAPI/src/main/resources/application.properties b/JShellAPI/src/main/resources/application.properties
deleted file mode 100644
index 44ee866..0000000
--- a/JShellAPI/src/main/resources/application.properties
+++ /dev/null
@@ -1,27 +0,0 @@
-# Public API Config
-# 30 * 60 = 1800
-jshellapi.regularSessionTimeoutSeconds=1800
-jshellapi.oneTimeSessionTimeoutSeconds=30
-jshellapi.evalTimeoutSeconds=15
-jshellapi.evalTimeoutValidationLeeway=10
-jshellapi.sysOutCharLimit=1024
-jshellapi.maxAliveSessions=10
-
-# Docker limits config
-jshellapi.dockerMaxRamMegaBytes=128
-jshellapi.dockerCPUsUsage=0.5
-jshellapi.dockerCPUSetCPUs=0
-
-# Internal config
-jshellapi.schedulerSessionKillScanRateSeconds=60
-
-# Docker service config
-jshellapi.dockerResponseTimeout=60
-jshellapi.dockerConnectionTimeout=60
-
-
-# So Spring includes the reason of ResponseStatusException
-server.error.include-message=always
-
-# Spring
-logging.level.org.springframework.web=DEBUG
\ No newline at end of file
diff --git a/JShellAPI/src/main/resources/application.yaml b/JShellAPI/src/main/resources/application.yaml
new file mode 100644
index 0000000..831580c
--- /dev/null
+++ b/JShellAPI/src/main/resources/application.yaml
@@ -0,0 +1,31 @@
+jshellapi:
+
+ # Public API Config
+ regularSessionTimeoutSeconds: 1800
+ oneTimeSessionTimeoutSeconds: 30
+ evalTimeoutSeconds: 15
+ evalTimeoutValidationLeeway: 10
+ sysOutCharLimit: 1024
+ maxAliveSessions: 10
+
+ # Docker limits config
+ dockerMaxRamMegaBytes: 128
+ dockerCPUsUsage: 0.5
+ dockerCPUSetCPUs: 0
+
+ # Internal config
+ schedulerSessionKillScanRateSeconds: 60
+
+ # Docker service config
+ dockerResponseTimeout: 60
+ dockerConnectionTimeout: 60
+
+server:
+ error:
+ include-message: always
+
+logging:
+ level:
+ org:
+ springframework:
+ web: DEBUG
diff --git a/JShellWrapper/.gitignore b/JShellWrapper/.gitignore
deleted file mode 100644
index 9872655..0000000
--- a/JShellWrapper/.gitignore
+++ /dev/null
@@ -1,228 +0,0 @@
-# Created by https://www.toptal.com/developers/gitignore/api/java,intellij,visualstudiocode,eclipse
-# Edit at https://www.toptal.com/developers/gitignore?templates=java,intellij,visualstudiocode,eclipse
-
-### Eclipse ###
-.metadata
-bin/
-tmp/
-*.tmp
-*.bak
-*.swp
-*~.nib
-local.properties
-.settings/
-.loadpath
-.recommenders
-
-# External tool builders
-.externalToolBuilders/
-
-# Locally stored "Eclipse launch configurations"
-*.launch
-
-# PyDev specific (Python IDE for Eclipse)
-*.pydevproject
-
-# CDT-specific (C/C++ Development Tooling)
-.cproject
-
-# CDT- autotools
-.autotools
-
-# Java annotation processor (APT)
-.factorypath
-
-# PDT-specific (PHP Development Tools)
-.buildpath
-
-# sbteclipse plugin
-.target
-
-# Tern plugin
-.tern-project
-
-# TeXlipse plugin
-.texlipse
-
-# STS (Spring Tool Suite)
-.springBeans
-
-# Code Recommenders
-.recommenders/
-
-# Annotation Processing
-.apt_generated/
-.apt_generated_test/
-
-# Scala IDE specific (Scala & Java development for Eclipse)
-.cache-main
-.scala_dependencies
-.worksheet
-
-# Uncomment this line if you wish to ignore the project description file.
-# Typically, this file would be tracked if it contains build/dependency configurations:
-#.project
-
-### Eclipse Patch ###
-# Spring Boot Tooling
-.sts4-cache/
-
-### Intellij ###
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
-
-# AWS User-specific
-.idea/**/aws.xml
-
-# Generated files
-.idea/**/contentModel.xml
-
-# Sensitive or high-churn files
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-.idea/**/dbnavigator.xml
-
-# Gradle
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# Gradle and Maven with auto-import
-# When using Gradle or Maven with auto-import, you should exclude module files,
-# since they will be recreated, and may cause churn. Uncomment if using
-# auto-import.
-.idea/artifacts
-.idea/compiler.xml
-.idea/jarRepositories.xml
-.idea/modules.xml
-.idea/*.iml
-.idea/modules
-*.iml
-*.ipr
-
-# CMake
-cmake-build-*/
-
-# Mongo Explorer plugin
-.idea/**/mongoSettings.xml
-
-# File-based project format
-*.iws
-
-# IntelliJ
-out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# SonarLint plugin
-.idea/sonarlint/
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-# Editor-based Rest Client
-.idea/httpRequests
-
-# Android studio 3.1+ serialized cache file
-.idea/caches/build_file_checksums.ser
-
-### Intellij Patch ###
-# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
-
-*.iml
-modules.xml
-.idea/misc.xml
-*.ipr
-
-# Sonarlint plugin
-# https://plugins.jetbrains.com/plugin/7973-sonarlint
-.idea/**/sonarlint/
-
-# SonarQube Plugin
-# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
-.idea/**/sonarIssues.xml
-
-# Markdown Navigator plugin
-# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
-.idea/**/markdown-navigator.xml
-.idea/**/markdown-navigator-enh.xml
-.idea/**/markdown-navigator/
-
-# Cache file creation bug
-# See https://youtrack.jetbrains.com/issue/JBR-2257
-.idea/$CACHE_FILE$
-
-# CodeStream plugin
-# https://plugins.jetbrains.com/plugin/12206-codestream
-.idea/codestream.xml
-
-# Azure Toolkit for IntelliJ plugin
-# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
-.idea/**/azureSettings.xml
-
-### Java ###
-# Compiled class file
-*.class
-
-# Log file
-*.log
-
-# BlueJ files
-*.ctxt
-
-# Mobile Tools for Java (J2ME)
-.mtj.tmp/
-
-# Package Files #
-*.jar
-*.war
-*.nar
-*.ear
-*.zip
-*.tar.gz
-*.rar
-
-# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
-hs_err_pid*
-replay_pid*
-
-### VisualStudioCode ###
-.vscode/*
-!.vscode/settings.json
-!.vscode/tasks.json
-!.vscode/launch.json
-!.vscode/extensions.json
-!.vscode/*.code-snippets
-
-# Local History for Visual Studio Code
-.history/
-
-# Built Visual Studio Code Extensions
-*.vsix
-
-### VisualStudioCode Patch ###
-# Ignore all local history of files
-.history
-.ionide
-
-# End of https://www.toptal.com/developers/gitignore/api/java,intellij,visualstudiocode,eclipse
\ No newline at end of file
diff --git a/JShellWrapper/.idea/.gitignore b/JShellWrapper/.idea/.gitignore
deleted file mode 100644
index 13566b8..0000000
--- a/JShellWrapper/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/JShellWrapper/.idea/artifacts/JShellWrapper_jar.xml b/JShellWrapper/.idea/artifacts/JShellWrapper_jar.xml
deleted file mode 100644
index c9f2dd2..0000000
--- a/JShellWrapper/.idea/artifacts/JShellWrapper_jar.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- $PROJECT_DIR$/out/
-
-
-
-
-
\ No newline at end of file
diff --git a/JShellWrapper/.idea/misc.xml b/JShellWrapper/.idea/misc.xml
deleted file mode 100644
index 99321d4..0000000
--- a/JShellWrapper/.idea/misc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/JShellWrapper/.idea/vcs.xml b/JShellWrapper/.idea/vcs.xml
deleted file mode 100644
index 6c0b863..0000000
--- a/JShellWrapper/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/JShellWrapper/build.gradle b/JShellWrapper/build.gradle
index 7bbc0b6..82ee63e 100644
--- a/JShellWrapper/build.gradle
+++ b/JShellWrapper/build.gradle
@@ -1,26 +1,30 @@
+import java.time.Instant
+
plugins {
- id 'application' // Debugging only
- id 'com.google.cloud.tools.jib' version '3.3.2'
+ id 'application'
+ id 'com.google.cloud.tools.jib' version '3.4.0'
id 'com.github.johnrengelman.shadow' version '8.1.1'
}
-group 'org.togetherjava'
-version '1.0-SNAPSHOT'
-
-mainClassName = 'Main' // Debugging only
+/* Application plugin is used for debugging */
+application {
+ mainClassName = 'org.togetherjava.jshell.Main'
+ run {
+ standardInput = System.in
+ systemProperty 'line.separator', '\n'
+ systemProperty 'file.encoding', 'UTF-8'
+ }
+}
-run { // Debugging only
- standardInput = System.in
+test {
systemProperty 'line.separator', '\n'
systemProperty 'file.encoding', 'UTF-8'
}
-var outputImage = 'togetherjava.org:5001/togetherjava/jshellwrapper:master' ?: 'latest'
-
jib {
from.image = 'eclipse-temurin:22-alpine'
to {
- image = outputImage
+ image = 'togetherjava.org:5001/togetherjava/jshellwrapper:master' ?: 'latest'
auth {
username = System.getenv('ORG_REGISTRY_USER') ?: ''
password = System.getenv('ORG_REGISTRY_PASSWORD') ?: ''
@@ -28,8 +32,8 @@ jib {
}
container {
ports = [ "8081-8081" ]
- mainClass = 'Main'
- setCreationTime(java.time.Instant.now().toString())
+ mainClass = 'org.togetherjava.jshell.Main'
+ setCreationTime(Instant.now().toString())
}
}
@@ -37,21 +41,4 @@ shadowJar {
archiveBaseName.set('JShellWrapper')
archiveClassifier.set('')
archiveVersion.set('')
-}
-
-repositories {
- mavenCentral()
-}
-
-dependencies {
- testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'
- testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'
-}
-
-tasks.named('test') {
- useJUnitPlatform()
-}
-test {
- systemProperty 'line.separator', '\n'
- systemProperty 'file.encoding', 'UTF-8'
}
\ No newline at end of file
diff --git a/JShellWrapper/settings.gradle b/JShellWrapper/settings.gradle
deleted file mode 100644
index 2db0fde..0000000
--- a/JShellWrapper/settings.gradle
+++ /dev/null
@@ -1,2 +0,0 @@
-rootProject.name = 'JShellWrapper'
-
diff --git a/JShellWrapper/src/main/java/Config.java b/JShellWrapper/src/main/java/Config.java
deleted file mode 100644
index 8df316f..0000000
--- a/JShellWrapper/src/main/java/Config.java
+++ /dev/null
@@ -1,17 +0,0 @@
-
-public record Config(int evalTimeoutSeconds, int sysOutCharLimit) {
- static int loadIntEnv(String envName) {
- return Integer.parseInt(System.getenv(envName));
- }
- public static Config load() {
- return new Config(
- loadIntEnv("evalTimeoutSeconds"),
- loadIntEnv("sysOutCharLimit")
- );
- }
-
- public Config {
- if(evalTimeoutSeconds <= 0) throw new IllegalArgumentException("Invalid evalTimeoutSeconds : " + evalTimeoutSeconds);
- if(sysOutCharLimit <= 0) throw new IllegalArgumentException("Invalid sysOutCharLimit : " + sysOutCharLimit);
- }
-}
diff --git a/JShellWrapper/src/main/java/JShellEvalAbortion.java b/JShellWrapper/src/main/java/JShellEvalAbortion.java
deleted file mode 100644
index b028e2b..0000000
--- a/JShellWrapper/src/main/java/JShellEvalAbortion.java
+++ /dev/null
@@ -1,2 +0,0 @@
-public record JShellEvalAbortion(String sourceCause, String remainingSource, JShellEvalAbortionCause cause) {
-}
diff --git a/JShellWrapper/src/main/java/Main.java b/JShellWrapper/src/main/java/org/togetherjava/jshell/Main.java
similarity index 57%
rename from JShellWrapper/src/main/java/Main.java
rename to JShellWrapper/src/main/java/org/togetherjava/jshell/Main.java
index 7d4f508..da9b5e6 100644
--- a/JShellWrapper/src/main/java/Main.java
+++ b/JShellWrapper/src/main/java/org/togetherjava/jshell/Main.java
@@ -1,3 +1,8 @@
+package org.togetherjava.jshell;
+
+import org.togetherjava.jshell.wrapper.Config;
+import org.togetherjava.jshell.wrapper.JShellWrapper;
+
public class Main {
public static void main(String[] args) {
JShellWrapper wrapper = new JShellWrapper();
diff --git a/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/Config.java b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/Config.java
new file mode 100644
index 0000000..194ffe3
--- /dev/null
+++ b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/Config.java
@@ -0,0 +1,19 @@
+package org.togetherjava.jshell.wrapper;
+
+public record Config(int evalTimeoutSeconds, int sysOutCharLimit) {
+ static int loadIntEnv(String envName) {
+ return Integer.parseInt(System.getenv(envName));
+ }
+
+ public static Config load() {
+ return new Config(loadIntEnv("evalTimeoutSeconds"), loadIntEnv("sysOutCharLimit"));
+ }
+
+ public Config {
+ if (evalTimeoutSeconds <= 0)
+ throw new IllegalArgumentException(
+ "Invalid evalTimeoutSeconds : " + evalTimeoutSeconds);
+ if (sysOutCharLimit <= 0)
+ throw new IllegalArgumentException("Invalid sysOutCharLimit : " + sysOutCharLimit);
+ }
+}
diff --git a/JShellWrapper/src/main/java/EvalResult.java b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/EvalResult.java
similarity index 66%
rename from JShellWrapper/src/main/java/EvalResult.java
rename to JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/EvalResult.java
index ba3bb6a..e45e58a 100644
--- a/JShellWrapper/src/main/java/EvalResult.java
+++ b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/EvalResult.java
@@ -1,6 +1,7 @@
+package org.togetherjava.jshell.wrapper;
+
import jdk.jshell.SnippetEvent;
import java.util.List;
-public record EvalResult(List events, JShellEvalAbortion abortion) {
-}
+public record EvalResult(List events, JShellEvalAbortion abortion) {}
diff --git a/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/JShellEvalAbortion.java b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/JShellEvalAbortion.java
new file mode 100644
index 0000000..5daddd6
--- /dev/null
+++ b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/JShellEvalAbortion.java
@@ -0,0 +1,4 @@
+package org.togetherjava.jshell.wrapper;
+
+public record JShellEvalAbortion(
+ String sourceCause, String remainingSource, JShellEvalAbortionCause cause) {}
diff --git a/JShellWrapper/src/main/java/JShellEvalAbortionCause.java b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/JShellEvalAbortionCause.java
similarity index 65%
rename from JShellWrapper/src/main/java/JShellEvalAbortionCause.java
rename to JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/JShellEvalAbortionCause.java
index 988234e..0d3b8d3 100644
--- a/JShellWrapper/src/main/java/JShellEvalAbortionCause.java
+++ b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/JShellEvalAbortionCause.java
@@ -1,16 +1,15 @@
+package org.togetherjava.jshell.wrapper;
+
import java.util.List;
public sealed interface JShellEvalAbortionCause {
- record TimeoutAbortionCause() implements JShellEvalAbortionCause {
- }
+ record TimeoutAbortionCause() implements JShellEvalAbortionCause {}
- record UnhandledExceptionAbortionCause(String exceptionClass, String exceptionMessage) implements JShellEvalAbortionCause {
- }
+ record UnhandledExceptionAbortionCause(String exceptionClass, String exceptionMessage)
+ implements JShellEvalAbortionCause {}
- record CompileTimeErrorAbortionCause(List errors) implements JShellEvalAbortionCause {
- }
+ record CompileTimeErrorAbortionCause(List errors) implements JShellEvalAbortionCause {}
- record SyntaxErrorAbortionCause() implements JShellEvalAbortionCause {
- }
+ record SyntaxErrorAbortionCause() implements JShellEvalAbortionCause {}
}
diff --git a/JShellWrapper/src/main/java/JShellEvalStop.java b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/JShellEvalStop.java
similarity index 86%
rename from JShellWrapper/src/main/java/JShellEvalStop.java
rename to JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/JShellEvalStop.java
index 862fb95..9c5e7e3 100644
--- a/JShellWrapper/src/main/java/JShellEvalStop.java
+++ b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/JShellEvalStop.java
@@ -1,3 +1,5 @@
+package org.togetherjava.jshell.wrapper;
+
import jdk.jshell.JShell;
import java.util.concurrent.atomic.AtomicBoolean;
diff --git a/JShellWrapper/src/main/java/JShellWrapper.java b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/JShellWrapper.java
similarity index 58%
rename from JShellWrapper/src/main/java/JShellWrapper.java
rename to JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/JShellWrapper.java
index b8cd1e3..90dda35 100644
--- a/JShellWrapper/src/main/java/JShellWrapper.java
+++ b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/JShellWrapper.java
@@ -1,3 +1,5 @@
+package org.togetherjava.jshell.wrapper;
+
import jdk.jshell.*;
import java.io.InputStream;
@@ -8,8 +10,10 @@
import java.util.stream.IntStream;
/**
- * Enter eval to evaluate code, snippets to see snippets, exit to stop.
- * How to use : at startup, one line need to be sent, startup script, first enter the command, for example eval or snippets, then any needed argument. Then "OK" should immediately be sent back, then after some time, the rest of the data.
+ * Enter eval to evaluate code, snippets to see snippets, exit to stop. How to use : at startup, one
+ * line need to be sent, startup script, first enter the command, for example eval or snippets, then
+ * any needed argument. Then "OK" should immediately be sent back, then after some time, the rest of
+ * the data.
*/
public class JShellWrapper {
@@ -23,7 +27,7 @@ public void run(Config config, InputStream in, PrintStream processOut) {
ok(processOut);
processOut.println(startupEval.events().size());
processOut.flush();
- while(true) {
+ while (true) {
String command = processIn.nextLine();
switch (command) {
case "eval" -> eval(processIn, processOut, config, shell, jshellOut);
@@ -44,14 +48,22 @@ private void verifyStartupEval(EvalResult result) {
if (abortion == null) return;
SnippetEvent event = result.events().get(result.events().size() - 1);
// TODO Replace with switch
- if(abortion.cause() instanceof JShellEvalAbortionCause.TimeoutAbortionCause) {
+ if (abortion.cause() instanceof JShellEvalAbortionCause.TimeoutAbortionCause) {
throw new RuntimeException("Timeout exceeded.");
- } else if(abortion.cause() instanceof JShellEvalAbortionCause.UnhandledExceptionAbortionCause) {
- throw new RuntimeException("Following startup script resulted in an exception : " + sanitize(abortion.sourceCause()), event.exception());
- } else if(abortion.cause() instanceof JShellEvalAbortionCause.CompileTimeErrorAbortionCause) {
- throw new RuntimeException("Following startup script was REJECTED : " + sanitize(abortion.sourceCause()));
- } else if(abortion.cause() instanceof JShellEvalAbortionCause.SyntaxErrorAbortionCause) {
- throw new RuntimeException("Following startup script has a syntax error : " + sanitize(abortion.sourceCause()));
+ } else if (abortion.cause()
+ instanceof JShellEvalAbortionCause.UnhandledExceptionAbortionCause) {
+ throw new RuntimeException(
+ "Following startup script resulted in an exception : "
+ + sanitize(abortion.sourceCause()),
+ event.exception());
+ } else if (abortion.cause()
+ instanceof JShellEvalAbortionCause.CompileTimeErrorAbortionCause) {
+ throw new RuntimeException(
+ "Following startup script was REJECTED : " + sanitize(abortion.sourceCause()));
+ } else if (abortion.cause() instanceof JShellEvalAbortionCause.SyntaxErrorAbortionCause) {
+ throw new RuntimeException(
+ "Following startup script has a syntax error : "
+ + sanitize(abortion.sourceCause()));
} else throw new AssertionError();
}
@@ -63,47 +75,67 @@ private void ok(PrintStream processOut) {
private EvalResult eval(JShell shell, String code, AtomicBoolean hasStopped) {
List resultEvents = new ArrayList<>();
JShellEvalAbortion abortion = null;
- while(!code.isEmpty()) {
+ while (!code.isEmpty()) {
var completion = shell.sourceCodeAnalysis().analyzeCompletion(clean(code));
- if(!completion.completeness().isComplete()) {
- abortion = new JShellEvalAbortion(code, "", new JShellEvalAbortionCause.SyntaxErrorAbortionCause());
+ if (!completion.completeness().isComplete()) {
+ abortion =
+ new JShellEvalAbortion(
+ code, "", new JShellEvalAbortionCause.SyntaxErrorAbortionCause());
break;
}
List evalEvents = shell.eval(completion.source());
JShellEvalAbortionCause abortionCause = handleEvents(shell, evalEvents, resultEvents);
- if(abortionCause != null) {
- abortion = new JShellEvalAbortion(completion.source(), completion.remaining(), abortionCause);
+ if (abortionCause != null) {
+ abortion =
+ new JShellEvalAbortion(
+ completion.source(), completion.remaining(), abortionCause);
break;
}
- if(hasStopped.get()) {
- abortion = new JShellEvalAbortion(completion.source(), completion.remaining(), new JShellEvalAbortionCause.TimeoutAbortionCause());
+ if (hasStopped.get()) {
+ abortion =
+ new JShellEvalAbortion(
+ completion.source(),
+ completion.remaining(),
+ new JShellEvalAbortionCause.TimeoutAbortionCause());
break;
}
code = completion.remaining();
}
return new EvalResult(resultEvents, abortion);
}
- private JShellEvalAbortionCause handleEvents(JShell shell, List evalEvents, List resultEvents) {
- for(SnippetEvent event : evalEvents) {
- if (event.causeSnippet() == null) { // Only keep snippet creation events
+
+ private JShellEvalAbortionCause handleEvents(
+ JShell shell, List evalEvents, List resultEvents) {
+ for (SnippetEvent event : evalEvents) {
+ if (event.causeSnippet() == null) { // Only keep snippet creation events
resultEvents.add(event);
- if(event.status() == Snippet.Status.REJECTED) return createCompileErrorCause(shell, event);
- if(event.exception() != null) return createExceptionCause(event);
+ if (event.status() == Snippet.Status.REJECTED)
+ return createCompileErrorCause(shell, event);
+ if (event.exception() != null) return createExceptionCause(event);
}
}
return null;
}
- private JShellEvalAbortionCause.UnhandledExceptionAbortionCause createExceptionCause(SnippetEvent event) {
- if(event.exception() == null) {
+
+ private JShellEvalAbortionCause.UnhandledExceptionAbortionCause createExceptionCause(
+ SnippetEvent event) {
+ if (event.exception() == null) {
return null;
- } else if(event.exception() instanceof EvalException evalException) {
- return new JShellEvalAbortionCause.UnhandledExceptionAbortionCause(evalException.getExceptionClassName(), evalException.getMessage());
+ } else if (event.exception() instanceof EvalException evalException) {
+ return new JShellEvalAbortionCause.UnhandledExceptionAbortionCause(
+ evalException.getExceptionClassName(), evalException.getMessage());
} else {
- return new JShellEvalAbortionCause.UnhandledExceptionAbortionCause(event.exception().getClass().getName(), event.exception().getMessage());
+ return new JShellEvalAbortionCause.UnhandledExceptionAbortionCause(
+ event.exception().getClass().getName(), event.exception().getMessage());
}
}
- private JShellEvalAbortionCause.CompileTimeErrorAbortionCause createCompileErrorCause(JShell shell, SnippetEvent event) {
- return new JShellEvalAbortionCause.CompileTimeErrorAbortionCause(shell.diagnostics(event.snippet()).map(d -> sanitize(d.getMessage(Locale.ENGLISH))).toList());
+
+ private JShellEvalAbortionCause.CompileTimeErrorAbortionCause createCompileErrorCause(
+ JShell shell, SnippetEvent event) {
+ return new JShellEvalAbortionCause.CompileTimeErrorAbortionCause(
+ shell.diagnostics(event.snippet())
+ .map(d -> sanitize(d.getMessage(Locale.ENGLISH)))
+ .toList());
}
/**
@@ -112,8 +144,7 @@ private JShellEvalAbortionCause.CompileTimeErrorAbortionCause createCompileError
* eval
* line count
* code for each line count lines
- *
- * Output format :
+ * Output format :
*
* number of snippet results
* next number of snippets, see writeEvalSnippetEvent
@@ -125,11 +156,21 @@ private JShellEvalAbortionCause.CompileTimeErrorAbortionCause createCompileError
* stdout
*
*/
- private void eval(Scanner processIn, PrintStream processOut, Config config, JShell shell, StringOutputStream jshellOut) {
+ private void eval(
+ Scanner processIn,
+ PrintStream processOut,
+ Config config,
+ JShell shell,
+ StringOutputStream jshellOut) {
AtomicBoolean hasStopped = new AtomicBoolean();
- TimeoutWatcher watcher = new TimeoutWatcher(config.evalTimeoutSeconds(), new JShellEvalStop(shell, hasStopped));
+ TimeoutWatcher watcher =
+ new TimeoutWatcher(
+ config.evalTimeoutSeconds(), new JShellEvalStop(shell, hasStopped));
int lineCount = Integer.parseInt(processIn.nextLine());
- String code = IntStream.range(0, lineCount).mapToObj(i -> processIn.nextLine()).collect(Collectors.joining("\n"));
+ String code =
+ IntStream.range(0, lineCount)
+ .mapToObj(i -> processIn.nextLine())
+ .collect(Collectors.joining("\n"));
ok(processOut);
watcher.start();
@@ -137,7 +178,7 @@ private void eval(Scanner processIn, PrintStream processOut, Config config, JShe
watcher.stop();
List outBuffer = writeEvalResult(result, jshellOut);
- for(String line : outBuffer) {
+ for (String line : outBuffer) {
processOut.println(line);
}
}
@@ -150,18 +191,21 @@ private List writeEvalResult(EvalResult result, StringOutputStream jshel
writeEvalSnippetEvent(outBuffer, event);
}
JShellEvalAbortion abortion = result.abortion();
- if(abortion != null) {
+ if (abortion != null) {
// TODO replace with switch
- if(abortion.cause() instanceof JShellEvalAbortionCause.TimeoutAbortionCause) {
+ if (abortion.cause() instanceof JShellEvalAbortionCause.TimeoutAbortionCause) {
outBuffer.add("TIMEOUT");
- } else if(abortion.cause() instanceof JShellEvalAbortionCause.UnhandledExceptionAbortionCause c) {
+ } else if (abortion.cause()
+ instanceof JShellEvalAbortionCause.UnhandledExceptionAbortionCause c) {
outBuffer.add("UNCAUGHT_EXCEPTION");
outBuffer.add(getExceptionFromCause(c));
- } else if(abortion.cause() instanceof JShellEvalAbortionCause.CompileTimeErrorAbortionCause c) {
+ } else if (abortion.cause()
+ instanceof JShellEvalAbortionCause.CompileTimeErrorAbortionCause c) {
outBuffer.add("COMPILE_TIME_ERROR");
outBuffer.add(String.valueOf(c.errors().size()));
outBuffer.addAll(c.errors());
- } else if(abortion.cause() instanceof JShellEvalAbortionCause.SyntaxErrorAbortionCause c) {
+ } else if (abortion.cause()
+ instanceof JShellEvalAbortionCause.SyntaxErrorAbortionCause c) {
outBuffer.add("SYNTAX_ERROR");
} else throw new AssertionError();
outBuffer.add(sanitize(abortion.sourceCause()));
@@ -187,10 +231,12 @@ private List writeEvalResult(EvalResult result, StringOutputStream jshel
*
*/
private void writeEvalSnippetEvent(List outBuffer, SnippetEvent event) {
- String status = switch (event.status()) {
- case VALID, RECOVERABLE_DEFINED, RECOVERABLE_NOT_DEFINED, REJECTED -> event.status().name();
- default -> throw new RuntimeException("Invalid status");
- };
+ String status =
+ switch (event.status()) {
+ case VALID, RECOVERABLE_DEFINED, RECOVERABLE_NOT_DEFINED, REJECTED ->
+ event.status().name();
+ default -> throw new RuntimeException("Invalid status");
+ };
outBuffer.add(status);
if (event.previousStatus() == Snippet.Status.NONEXISTENT) {
outBuffer.add("ADDITION");
@@ -202,7 +248,8 @@ private void writeEvalSnippetEvent(List outBuffer, SnippetEvent event) {
outBuffer.add(event.value() != null ? event.value() : "NONE");
}
- private String getExceptionFromCause(JShellEvalAbortionCause.UnhandledExceptionAbortionCause cause) {
+ private String getExceptionFromCause(
+ JShellEvalAbortionCause.UnhandledExceptionAbortionCause cause) {
return sanitize(cause.exceptionClass() + ":" + cause.exceptionMessage());
}
@@ -220,18 +267,22 @@ private String getExceptionFromCause(JShellEvalAbortionCause.UnhandledExceptionA
*/
private void snippets(PrintStream processOut, JShell shell) {
ok(processOut);
- shell.snippets().map(Snippet::source).map(JShellWrapper::sanitize).forEach(processOut::println);
+ shell.snippets()
+ .map(Snippet::source)
+ .map(JShellWrapper::sanitize)
+ .forEach(processOut::println);
processOut.println();
}
private static String clean(String s) {
return s.replace("\r", "");
}
+
private static String sanitize(String s) {
return clean(s).replace("\\", "\\\\").replace("\n", "\\n");
}
+
private static String desanitize(String text) {
return text.replace("\\n", "\n").replace("\\\\", "\\");
}
-
}
diff --git a/JShellWrapper/src/main/java/StringOutputStream.java b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/StringOutputStream.java
similarity index 62%
rename from JShellWrapper/src/main/java/StringOutputStream.java
rename to JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/StringOutputStream.java
index 0a546cc..5ca0fb4 100644
--- a/JShellWrapper/src/main/java/StringOutputStream.java
+++ b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/StringOutputStream.java
@@ -1,3 +1,5 @@
+package org.togetherjava.jshell.wrapper;
+
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -12,10 +14,11 @@ public class StringOutputStream extends OutputStream {
/**
* Constructs a new StringOutputStream.
+ *
* @param maxSize the limit in terms of java char, so two bytes per unit
*/
public StringOutputStream(int maxSize) {
- this.bytes = new byte[maxSize*2];
+ this.bytes = new byte[maxSize * 2];
this.maxSize = maxSize;
this.index = 0;
this.byteOverflow = false;
@@ -23,11 +26,11 @@ public StringOutputStream(int maxSize) {
@Override
public void write(int b) {
- if(index == bytes.length) {
+ if (index == bytes.length) {
byteOverflow = true;
return;
}
- bytes[index++] = (byte)b;
+ bytes[index++] = (byte) b;
}
@Override
@@ -38,27 +41,32 @@ public void write(byte[] b) {
@Override
public void write(byte[] b, int off, int len) {
Objects.checkFromIndexSize(off, len, b.length);
- if(index == bytes.length) {
+ if (index == bytes.length) {
byteOverflow = true;
return;
}
int actualLen = Math.min(bytes.length - index, len);
System.arraycopy(b, off, bytes, index, actualLen);
index += actualLen;
- if(len != actualLen) {
+ if (len != actualLen) {
byteOverflow = true;
}
}
public Result readAll() {
- if(index > bytes.length) throw new IllegalStateException(); // Should never happen
- String s = new String(index == bytes.length ? bytes : Arrays.copyOf(bytes, index), StandardCharsets.UTF_8);
+ if (index > bytes.length) throw new IllegalStateException(); // Should never happen
+ String s =
+ new String(
+ index == bytes.length ? bytes : Arrays.copyOf(bytes, index),
+ StandardCharsets.UTF_8);
index = 0;
- if(byteOverflow) {
+ if (byteOverflow) {
byteOverflow = false;
- return new Result(s.charAt(s.length()-1) == UNKNOWN_CHAR ? s.substring(0, s.length()-1) : s, true);
+ return new Result(
+ s.charAt(s.length() - 1) == UNKNOWN_CHAR ? s.substring(0, s.length() - 1) : s,
+ true);
}
- if(s.length() > maxSize) return new Result(s.substring(0, maxSize), true);
+ if (s.length() > maxSize) return new Result(s.substring(0, maxSize), true);
return new Result(s, false);
}
@@ -67,6 +75,5 @@ public void close() {
bytes = null;
}
- public record Result(String content, boolean isOverflow) {
- }
+ public record Result(String content, boolean isOverflow) {}
}
diff --git a/JShellWrapper/src/main/java/TimeoutWatcher.java b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/TimeoutWatcher.java
similarity index 54%
rename from JShellWrapper/src/main/java/TimeoutWatcher.java
rename to JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/TimeoutWatcher.java
index caf06cd..3f08d8e 100644
--- a/JShellWrapper/src/main/java/TimeoutWatcher.java
+++ b/JShellWrapper/src/main/java/org/togetherjava/jshell/wrapper/TimeoutWatcher.java
@@ -1,27 +1,33 @@
+package org.togetherjava.jshell.wrapper;
+
public class TimeoutWatcher {
private final Thread thread;
private boolean timeout;
public TimeoutWatcher(int timeoutSeconds, Runnable timeoutAction) {
- Runnable runnable = () -> {
- try {
- Thread.sleep(timeoutSeconds * 1000L);
- } catch (InterruptedException e) { //Stopped
- return;
- }
- timeout = true;
- timeoutAction.run();
- };
+ Runnable runnable =
+ () -> {
+ try {
+ Thread.sleep(timeoutSeconds * 1000L);
+ } catch (InterruptedException e) { // Stopped
+ return;
+ }
+ timeout = true;
+ timeoutAction.run();
+ };
thread = new Thread(runnable);
thread.setName("Timeout Watcher");
}
+
public synchronized void start() {
timeout = false;
thread.start();
}
+
public synchronized void stop() {
thread.interrupt();
}
+
public boolean isTimeout() {
return timeout;
}
diff --git a/JShellWrapper/src/main/resources/META-INF/MANIFEST.MF b/JShellWrapper/src/main/resources/META-INF/MANIFEST.MF
index 5ee19cb..3be704a 100644
--- a/JShellWrapper/src/main/resources/META-INF/MANIFEST.MF
+++ b/JShellWrapper/src/main/resources/META-INF/MANIFEST.MF
@@ -1,3 +1,2 @@
Manifest-Version: 1.0
-Main-Class: Main
-
+Main-Class: org.togetherjava.jshell.Main
diff --git a/JShellWrapper/src/test/java/JShellWrapperStartupScriptTest.java b/JShellWrapper/src/test/java/JShellWrapperStartupScriptTest.java
index 5387235..4825e8c 100644
--- a/JShellWrapper/src/test/java/JShellWrapperStartupScriptTest.java
+++ b/JShellWrapper/src/test/java/JShellWrapperStartupScriptTest.java
@@ -1,7 +1,8 @@
import static org.junit.jupiter.api.Assertions.*;
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import org.togetherjava.jshell.wrapper.Config;
+import org.togetherjava.jshell.wrapper.JShellWrapper;
import java.io.PrintStream;
@@ -9,7 +10,9 @@ class JShellWrapperStartupScriptTest {
@Test
void testDoubleSnippets() {
Config config = new Config(5, 1024);
- StringInputStream inputStream = new StringInputStream("""
+ StringInputStream inputStream =
+ new StringInputStream(
+ """
import java.util.*; void println(Object o) { System.out.println(o); }
eval
1
@@ -18,7 +21,8 @@ void testDoubleSnippets() {
UnboundStringOutputStream outputStream = new UnboundStringOutputStream();
JShellWrapper jshell = new JShellWrapper();
jshell.run(config, inputStream, new PrintStream(outputStream));
- assertEquals("""
+ assertEquals(
+ """
OK
2
OK
@@ -27,11 +31,12 @@ void testDoubleSnippets() {
ADDITION
3
println(List.of("a", "b", "c"))
-
-
+
+
false
[a, b, c]\\n
OK
- """, outputStream.readAll());
+ """,
+ outputStream.readAll());
}
}
diff --git a/JShellWrapper/src/test/java/JShellWrapperTest.java b/JShellWrapper/src/test/java/JShellWrapperTest.java
index dd0c716..86fe8e8 100644
--- a/JShellWrapper/src/test/java/JShellWrapperTest.java
+++ b/JShellWrapper/src/test/java/JShellWrapperTest.java
@@ -1,29 +1,35 @@
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import org.togetherjava.jshell.wrapper.Config;
+import org.togetherjava.jshell.wrapper.JShellWrapper;
import java.io.InputStream;
import java.io.PrintStream;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
class JShellWrapperTest {
static Config config;
static JShellWrapper jshell;
InputStream in;
PrintStream out;
+
@BeforeAll
static void setUp() {
config = new Config(5, 1024);
jshell = new JShellWrapper();
}
+
void evalTest(String input, String expectedOutput) {
UnboundStringOutputStream out = new UnboundStringOutputStream(128);
jshell.run(config, new StringInputStream("\n" + input + "\nexit\n"), new PrintStream(out));
assertEquals(expectedOutput + "\nOK\n", out.readAll());
}
+
@Test
void testHelloWorld() {
- evalTest("""
+ evalTest(
+ """
eval
1
System.out.println("Hello world!")""",
@@ -36,14 +42,16 @@ void testHelloWorld() {
ADDITION
1
System.out.println("Hello world!")
-
-
+
+
false
Hello world!\\n""");
}
+
@Test
void testMultilinesInput() {
- evalTest("""
+ evalTest(
+ """
eval
4
for(int i = 0; i < 10; i++) {
@@ -59,19 +67,21 @@ void testMultilinesInput() {
ADDITION
1
for(int i = 0; i < 10; i++) {\\n System.out.print(i);\\n}
-
+
VALID
ADDITION
2
\\nSystem.out.println();
-
-
+
+
false
0123456789\\n""");
}
+
@Test
void testStdoutOverflow() {
- evalTest("""
+ evalTest(
+ """
eval
1
for(int i = 0; i < 1024; i++) System.out.print(0)""",
@@ -84,11 +94,13 @@ void testStdoutOverflow() {
ADDITION
1
for(int i = 0; i < 1024; i++) System.out.print(0);
-
-
+
+
false
- %s""".formatted("0".repeat(1024)));
- evalTest("""
+ %s"""
+ .formatted("0".repeat(1024)));
+ evalTest(
+ """
eval
1
for(int i = 0; i <= 1024; i++) System.out.print(0)""",
@@ -101,14 +113,17 @@ void testStdoutOverflow() {
ADDITION
1
for(int i = 0; i <= 1024; i++) System.out.print(0);
-
-
+
+
true
- %s""".formatted("0".repeat(1024)));
+ %s"""
+ .formatted("0".repeat(1024)));
}
+
@Test
void testModificationAndMultiplesSnippets() {
- evalTest("""
+ evalTest(
+ """
eval
2
int i = 0;
@@ -128,13 +143,15 @@ void testModificationAndMultiplesSnippets() {
1
\\nint i = 2;
2
-
+
false
""");
}
+
@Test
void testUseId() {
- evalTest("""
+ evalTest(
+ """
eval
1
System.out.println("Hello world!")""",
@@ -147,14 +164,16 @@ void testUseId() {
ADDITION
1
System.out.println("Hello world!")
-
-
+
+
false
Hello world!\\n""");
}
+
@Test
void testTimeout() {
- evalTest("""
+ evalTest(
+ """
eval
1
while(true);""",
@@ -170,13 +189,15 @@ void testTimeout() {
NONE
TIMEOUT
while(true);
-
+
false
""");
}
+
@Test
- void testUncaughtException() {// TODO other kind of exception, not in EvalException
- evalTest("""
+ void testUncaughtException() { // TODO other kind of exception, not in EvalException
+ evalTest(
+ """
eval
1
throw new RuntimeException("Some message : fail")""",
@@ -193,17 +214,19 @@ void testUncaughtException() {// TODO other kind of exception, not in EvalExcept
UNCAUGHT_EXCEPTION
java.lang.RuntimeException:Some message : fail
throw new RuntimeException("Some message : fail");
-
+
false
""");
}
+
@Test
void testRejected() {
- evalTest("""
+ evalTest(
+ """
eval
1
print""",
- """
+ """
OK
0
OK
@@ -217,14 +240,16 @@ void testRejected() {
1
cannot find symbol\\n symbol: variable print\\n location: class\s
print
-
+
false
""");
}
+
@Test
void testSyntaxError() {
// DEFINITELY_INCOMPLETE
- evalTest("""
+ evalTest(
+ """
eval
1
print(""",
@@ -235,11 +260,12 @@ void testSyntaxError() {
0
SYNTAX_ERROR
print(
-
+
false
""");
// CONSIDERED_INCOMPLETE
- evalTest("""
+ evalTest(
+ """
eval
1
while(true)""",
@@ -250,10 +276,11 @@ void testSyntaxError() {
0
SYNTAX_ERROR
while(true)
-
+
false
""");
- evalTest("""
+ evalTest(
+ """
eval
1
for(int i = 0; i < 10; i++)""",
@@ -264,19 +291,21 @@ void testSyntaxError() {
0
SYNTAX_ERROR
for(int i = 0; i < 10; i++)
-
+
false
""");
}
+
@Test
void testRejectedAndMultiples() {
- evalTest("""
+ evalTest(
+ """
eval
3
int i = 0;
print;
System.out.println(i);""",
- """
+ """
OK
0
OK
@@ -290,7 +319,7 @@ void testRejectedAndMultiples() {
ADDITION
2
\\nprint;
- NONE
+ NONE
COMPILE_TIME_ERROR
1
cannot find symbol\\n symbol: variable print\\n location: class\s
@@ -299,9 +328,11 @@ void testRejectedAndMultiples() {
false
""");
}
+
@Test
void testMultilinesAndHardcodedNewLineInString() {
- evalTest("""
+ evalTest(
+ """
eval
3
{
@@ -316,9 +347,9 @@ void testMultilinesAndHardcodedNewLineInString() {
ADDITION
1
{\\n System.out.println("\\\\n");\\n}
-
-
+
+
false
\\n\\n""");
}
-}
\ No newline at end of file
+}
diff --git a/JShellWrapper/src/test/java/StringInputStream.java b/JShellWrapper/src/test/java/StringInputStream.java
index 7885cd5..8bd679b 100644
--- a/JShellWrapper/src/test/java/StringInputStream.java
+++ b/JShellWrapper/src/test/java/StringInputStream.java
@@ -11,7 +11,7 @@ public StringInputStream(String content) {
@Override
public int read() {
- if(i == content.length) return -1;
+ if (i == content.length) return -1;
return content[i++] & 0xFF;
}
}
diff --git a/JShellWrapper/src/test/java/StringOutputStreamTest.java b/JShellWrapper/src/test/java/StringOutputStreamTest.java
index 63441aa..f106986 100644
--- a/JShellWrapper/src/test/java/StringOutputStreamTest.java
+++ b/JShellWrapper/src/test/java/StringOutputStreamTest.java
@@ -1,12 +1,12 @@
+import static org.junit.jupiter.api.Assertions.*;
+
import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.togetherjava.jshell.wrapper.StringOutputStream;
import java.nio.charset.StandardCharsets;
-import static org.junit.jupiter.api.Assertions.*;
-
class StringOutputStreamTest {
static final String E_ACUTE = "\u00E9";
static final String SMILEY = "\uD83D\uDE0A";
@@ -26,41 +26,43 @@ void tearDown() {
@Test
void testNoOverflow() {
final String hello = "HelloWorld"; // length = 10
- for(byte b : hello.getBytes(StandardCharsets.UTF_8)) {
+ for (byte b : hello.getBytes(StandardCharsets.UTF_8)) {
stream.write(b);
}
assertResult(false, hello, stream.readAll());
final String eAcuteX10 = E_ACUTE.repeat(10);
- for(byte b : eAcuteX10.getBytes(StandardCharsets.UTF_8)) {
+ for (byte b : eAcuteX10.getBytes(StandardCharsets.UTF_8)) {
stream.write(b);
}
assertResult(false, eAcuteX10, stream.readAll());
final String smileyX5 = SMILEY.repeat(5);
- for(byte b : smileyX5.getBytes(StandardCharsets.UTF_8)) {
+ for (byte b : smileyX5.getBytes(StandardCharsets.UTF_8)) {
stream.write(b);
}
assertResult(false, smileyX5, stream.readAll());
}
+
@Test
void testOverflow() {
- final String hello = "Hello World"; // length = 11
- for(byte b : hello.getBytes(StandardCharsets.UTF_8)) {
+ final String hello = "Hello World"; // length = 11
+ for (byte b : hello.getBytes(StandardCharsets.UTF_8)) {
stream.write(b);
}
assertResult(true, "Hello Worl", stream.readAll());
final String eAcuteX11 = E_ACUTE.repeat(11);
- for(byte b : eAcuteX11.getBytes(StandardCharsets.UTF_8)) {
+ for (byte b : eAcuteX11.getBytes(StandardCharsets.UTF_8)) {
stream.write(b);
}
assertResult(true, E_ACUTE.repeat(10), stream.readAll());
}
+
@Test
void testOverflowWithHalfCharacter() {
final String aAndSmileyX5 = 'a' + SMILEY.repeat(5);
- for(byte b : aAndSmileyX5.getBytes(StandardCharsets.UTF_8)) {
+ for (byte b : aAndSmileyX5.getBytes(StandardCharsets.UTF_8)) {
stream.write(b);
}
assertResult(true, 'a' + SMILEY.repeat(4), stream.readAll());
@@ -73,19 +75,23 @@ void testWriteOverload() {
assertResult(false, hello, stream.readAll());
final String eAcuteX15 = E_ACUTE.repeat(15);
- stream.write(eAcuteX15.getBytes(StandardCharsets.UTF_8), 0, 2*10);
+ stream.write(eAcuteX15.getBytes(StandardCharsets.UTF_8), 0, 2 * 10);
assertResult(false, E_ACUTE.repeat(10), stream.readAll());
final String eAcuteX11 = E_ACUTE.repeat(11);
- stream.write(eAcuteX11.getBytes(StandardCharsets.UTF_8), 0, 2*11);
+ stream.write(eAcuteX11.getBytes(StandardCharsets.UTF_8), 0, 2 * 11);
assertResult(true, E_ACUTE.repeat(10), stream.readAll());
final String eAcuteX5AndSmileyX5 = E_ACUTE.repeat(5) + SMILEY.repeat(5);
- stream.write(eAcuteX5AndSmileyX5.getBytes(StandardCharsets.UTF_8), 2*3, (2*2)+(4*4));
+ stream.write(
+ eAcuteX5AndSmileyX5.getBytes(StandardCharsets.UTF_8), 2 * 3, (2 * 2) + (4 * 4));
assertResult(false, E_ACUTE.repeat(2) + SMILEY.repeat(4), stream.readAll());
final String eAcuteX5AndSmileyX5AndA = E_ACUTE.repeat(5) + SMILEY.repeat(5) + 'a';
- stream.write(eAcuteX5AndSmileyX5AndA.getBytes(StandardCharsets.UTF_8), 2*3, (2*2)+(4*4)+1);
+ stream.write(
+ eAcuteX5AndSmileyX5AndA.getBytes(StandardCharsets.UTF_8),
+ 2 * 3,
+ (2 * 2) + (4 * 4) + 1);
assertResult(true, E_ACUTE.repeat(2) + SMILEY.repeat(4), stream.readAll());
}
@@ -93,4 +99,4 @@ void assertResult(boolean isOverflow, String expected, StringOutputStream.Result
assertEquals(isOverflow, result.isOverflow());
assertEquals(expected, result.content());
}
-}
\ No newline at end of file
+}
diff --git a/JShellWrapper/src/test/java/UnboundStringOutputStream.java b/JShellWrapper/src/test/java/UnboundStringOutputStream.java
index b75a4ab..291353c 100644
--- a/JShellWrapper/src/test/java/UnboundStringOutputStream.java
+++ b/JShellWrapper/src/test/java/UnboundStringOutputStream.java
@@ -17,10 +17,10 @@ public UnboundStringOutputStream() {
@Override
public void write(int b) throws IOException {
- if(index == bytes.length) {
+ if (index == bytes.length) {
bytes = Arrays.copyOf(bytes, bytes.length * 2);
}
- bytes[index++] = (byte)b;
+ bytes[index++] = (byte) b;
}
public String readAll() {
diff --git a/README.md b/README.md
index 10181d9..b2029e4 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# JShellPlaygroundBackend
## Prerequisites
-- Java 19
+- Java 21
- Docker
# Structure of the project
@@ -29,35 +29,10 @@ JShellAPI is a REST API, and whenever some code is received, it will create a se
# How to build JShellAPI in and run it in Docker
- Launch Docker
- Run `jibDockerBuild` to create the image
-- Create a folder
-- Create `docker-compose.yml`:
- ```yml
-services:
- jshell-backend-master:
- image: "togetherjava.org:5001/togetherjava/jshellbackend:master"
- command: ["--spring.config.location=file:///home/backend/config/application.properties"]
- restart: always
- ports:
- - 54321:8080
- - 5005:5005
- environment:
- JAVA_TOOL_OPTIONS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
- volumes:
- - /var/run/docker.sock:/var/run/docker.sock
- - ./docker-config.json:/root/.docker/config.json
- - ./config:/home/backend/config
- networks:
- - jshell-backend
-
-networks:
- jshell-backend:
- external: true
- name: develop-bot
- ```
-- Optionaly, create `config/application.properties` where you can put custom config (see the actual [application.properties](JShellAPI/src/main/resources/application.properties))
-- If you don't create it, delete the `command: ["--spring.config.location=file:///home/backend/config/application.properties"]` line in the `docker-compose.yml`
-- Run `docker compose build` or `docker-compose build` in the folder, depending of your version of Docker.
-- Run `docker compose start` or `docker-compose start` in the folder, depending of your version of Docker.
+- Optionally, create `config/application.yaml` where you can put custom config (see the actual [application.yaml](JShellAPI/src/main/resources/application.yaml))
+- If you don't create it, delete the `command: ["--spring.config.location=file:///home/backend/config/application.yaml"]` line in the `docker-compose.yaml`
+- Run `docker compose build` or `docker-compose build` in the folder, depending on your version of Docker.
+- Run `docker compose start` or `docker-compose start` in the folder, depending on your version of Docker.
## How to use JShellApi ?
-See [GUIDE.MD](JShellAPI/GUIDE.MD)
+See [JShellAPI README](JShellAPI/README.MD)
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..5a9399c
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,71 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.25.0'
+ }
+}
+
+group = 'org.togetherjava'
+version = project.version
+
+subprojects {
+ apply plugin: 'java'
+ apply plugin: 'com.diffplug.spotless'
+
+ spotless {
+ java {
+ importOrder()
+ googleJavaFormat()
+ .aosp()
+ .reflowLongStrings()
+ .formatJavadoc(true)
+ .reorderImports(true)
+ .groupArtifact('com.google.googlejavaformat:google-java-format')
+ trimTrailingWhitespace()
+ indentWithSpaces()
+ endWithNewline()
+ }
+ }
+
+ group = 'org.togetherjava'
+ version = project.version
+
+ tasks.withType(JavaCompile).configureEach {
+ options.release = 21
+ }
+
+ java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(21))
+ }
+ }
+
+ repositories {
+ mavenCentral()
+ }
+
+ test {
+ useJUnitPlatform()
+ testLogging {
+ events "passed", "skipped", "failed"
+ }
+ maxParallelForks = Runtime.runtime.availableProcessors()
+ systemProperty 'line.separator', '\n'
+ systemProperty 'file.encoding', 'utf-8'
+ }
+
+ sourceSets {
+ test {
+ java {
+ srcDirs 'src/test/integration', 'src/test/unit'
+ }
+ }
+ }
+
+ dependencies {
+ testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'
+ testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'
+ }
+}
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000..3555b21
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,20 @@
+version: "3.9"
+
+services:
+ jshell-backend-master:
+ image: "togetherjava.org:5001/togetherjava/jshellbackend:master"
+ command: ["--spring.config.location=file:///home/backend/config/application.yaml"]
+ restart: always
+ ports:
+ - 8080:8080
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - ./docker-config.json:/root/.docker/config.json
+ - ./config:/home/backend/config
+ networks:
+ - jshell-backend
+
+networks:
+ jshell-backend:
+ external: true
+ name: develop-bot
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..beb72cc
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+version=1.0.0
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index dec3bcf..c3e8564 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,7 +1,3 @@
-plugins {
- id "org.gradle.toolchains.foojay-resolver" version "0.7.0"
-}
-
rootProject.name = "JShellPlaygroundBackend"
include "JShellWrapper"
include "JShellAPI"