├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── build.gradle ├── ch.obermuhlner.scriptengine.example ├── build.gradle └── src │ └── main │ └── java │ └── ch │ └── obermuhlner │ └── scriptengine │ └── example │ ├── Person.java │ ├── ScriptEngineExample.java │ └── ScriptEnginePerformance.java ├── ch.obermuhlner.scriptengine.jshell ├── build.gradle └── src │ ├── main │ ├── java │ │ └── ch │ │ │ └── obermuhlner │ │ │ └── scriptengine │ │ │ └── jshell │ │ │ ├── JShellCompiledScript.java │ │ │ ├── JShellScriptEngine.java │ │ │ ├── JShellScriptEngineFactory.java │ │ │ └── VariablesTransfer.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── javax.script.ScriptEngineFactory │ └── test │ └── java │ └── ch │ └── obermuhlner │ └── scriptengine │ └── jshell │ ├── JShellScriptEngineFactoryTest.java │ └── JShellScriptEngineTest.java ├── docs ├── performance │ ├── Compile_Multiple_Evaluations.csv │ └── Compile_Multiple_Evaluations.svg └── releases │ ├── next_release_note.md │ ├── template_release_note.md │ ├── v1.0.0.md │ └── v1.1.0.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pom.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle stuff 2 | 3 | .gradle 4 | build 5 | gradle.properties 6 | 7 | # Eclipse IDE stuff 8 | 9 | .project 10 | .classpath 11 | bin 12 | out 13 | .settings 14 | 15 | # IntelliJ IDEA stuff 16 | 17 | *.iml 18 | .idea 19 | 20 | # other stuff 21 | 22 | trigger_*.sh -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk11 4 | 5 | before_install: 6 | - chmod +x gradlew 7 | 8 | addons: 9 | sonarcloud: 10 | organization: "eobermuhlner" 11 | token: 12 | secure: CMkYxNxTDZMUjymPwCJ87ZcZYq230m5LjH5kLLyUvxfUBIVb5zOXB+OMBGa4GVP5KmNLHDciFe1xO3Y+72Cg9FYvnw+HIqYNB0fQ/oVz32rvTePfLG8M8SfhHY04ZRNukN65LMZwgRqrrl6fCHClNGdWdVnYL5FGeKP1GxIBB5RASNqiI5/huMd6VXtwxqH61JjW60UNYPWUGBPZ4401b9KNmGuGpggkKX4uBXqpb0IPznm0CFPon9e4hm8o/8GIlAZthUCDAAn2S4FEl5nXftA9uEZyWZlYuW8zgKgE1qmRXcqqpUmImiVlQppKUJgbErkIW/SOf2DMyxP7kw9gK35/GegN/crS5vsq4+kRJ97b0u4yvUgr6c7J1OUZuifEXIfLSPUsfKAQRXTQmN5PyWVW1ZonFIoF5Brz4BrU+nuN71hiBJykGNo6b0YcQKylni2uCzUJMPyWPXmH59by78+ZTG3bOFVgzMmKOF3KSFeY3euUiovX2EfLjXwO9Z+23Pt9cFD5esL3DHCsVy8PMSOW8QFS7dvuWu2lbwZG5jLroNbv4KtBQMR2/rPDgbg/lzm2QI5lyhz51HyMkvnrwxyN3qSSVGiCd/o8z7ALNeIgZMAHC6VQ1TbuBYLetP3dkyHxRNXi60L/xRlxE0xXLkZDbn7rYD+OO6wy5P3NNZM= 13 | 14 | script: 15 | - ./gradlew sonarqube 16 | 17 | after_success: 18 | - bash <(curl -s https://codecov.io/bash) 19 | 20 | # set sudo to required, because this setup gets more memory 21 | sudo: required 22 | 23 | # keep gradle cache 24 | before_cache: 25 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 26 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 27 | cache: 28 | directories: 29 | - $HOME/.gradle/caches/ 30 | - $HOME/.gradle/wrapper/ 31 | - $HOME/.sonar/cache 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Eric Obermühlner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://api.travis-ci.org/eobermuhlner/jshell-scriptengine.svg?branch=master)](https://travis-ci.org/eobermuhlner/jshell-scriptengine) 2 | [![Code Coverage](https://badgen.net/codecov/c/github/eobermuhlner/jshell-scriptengine)](https://codecov.io/gh/eobermuhlner/jshell-scriptengine) 3 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=jshell-scriptengine&metric=alert_status)](https://sonarcloud.io/dashboard?id=jshell-scriptengine) 4 | [![Open Issues](https://badgen.net/github/open-issues/eobermuhlner/jshell-scriptengine)](https://github.com/eobermuhlner/jshell-scriptengine/issues) 5 | [![Last Commits](https://badgen.net/github/last-commit/eobermuhlner/jshell-scriptengine)](https://github.com/eobermuhlner/jshell-scriptengine/graphs/commit-activity) 6 | [![Maven Central - jshell-scriptengine](https://img.shields.io/maven-central/v/ch.obermuhlner/jshell-scriptengine.svg)](https://search.maven.org/artifact/ch.obermuhlner/jshell-scriptengine) 7 | 8 | # JShell scripting engine 9 | 10 | The [JShell](https://docs.oracle.com/javase/9/jshell/introduction-jshell.htm) 11 | was introduced with Java 9 and was designed to be used 12 | for interactive execution of code snippets in Java. 13 | 14 | The `jshell-scriptengine` library is a Java 11 wrapper around the JShell 15 | API that executes an entire script and handles the binding of variables. 16 | 17 | `jshell-scriptengine` is a good alternative to the usual `javascript` 18 | commonly used, especially if the users that will end up writing the 19 | scripts are already experienced Java programmers or can use the leverage 20 | that a `Java` framework can provide. 21 | 22 | Using a scripting engine is a powerful choice if your project needs to 23 | execute code that can be easily changed outside of the development cycle 24 | and when already deployed. 25 | 26 | Typical use cases are: 27 | - directory in your deployed application containing scripts for customizable business logic 28 | - editor in your application to edit (and persist) scripts 29 | 30 | If you believe that `Java` as a scripting language does not exactly fit 31 | your needs, consider the sibling project 32 | [`spel-scriptengine`](https://github.com/eobermuhlner/spel-scriptengine) 33 | (Spring Expression Language Scripting Engine). 34 | 35 | ## Using JShell scripting engine in your projects 36 | 37 | To use the JShell scripting you can either download the newest version of the .jar file from the 38 | [published releases on Github](https://github.com/eobermuhlner/jshell-scriptengine/releases/) 39 | or use the following dependency to 40 | [Maven Central](https://search.maven.org/#search%7Cga%7C1%7Cjshell-scriptengine) 41 | in your build script (please verify the version number to be the newest release): 42 | 43 | ### Use JShell scripting engine in Maven build 44 | 45 | ```xml 46 | 47 | ch.obermuhlner 48 | jshell-scriptengine 49 | 1.1.0 50 | 51 | ``` 52 | 53 | ### Use JShell scripting engine in Gradle build 54 | 55 | ```gradle 56 | repositories { 57 | mavenCentral() 58 | } 59 | 60 | dependencies { 61 | compile 'ch.obermuhlner:jshell-scriptengine:1.1.0' 62 | } 63 | ``` 64 | 65 | ## Simple usage 66 | 67 | The following code snippet shows a simple usage of the JShell script engine: 68 | ```java 69 | try { 70 | ScriptEngineManager manager = new ScriptEngineManager(); 71 | ScriptEngine engine = manager.getEngineByName("jshell"); 72 | 73 | String script = "" + 74 | "System.out.println(\"Input A: \" + inputA);" + 75 | "System.out.println(\"Input B: \" + inputB);" + 76 | "var output = inputA + inputB;" + 77 | "1000 + output;"; 78 | 79 | engine.put("inputA", 2); 80 | engine.put("inputB", 3); 81 | 82 | Object result = engine.eval(script); 83 | System.out.println("Result: " + result); 84 | 85 | Object output = engine.get("output"); 86 | System.out.println("Output Variable: " + output); 87 | 88 | } catch (ScriptException e) { 89 | e.printStackTrace(); 90 | } 91 | ``` 92 | 93 | The console output of this snippet shows that the bindings for input and output variables are working correctly. 94 | The return value of the JShell script is the value of the last statement `1000 + output`. 95 | ```console 96 | Input A: 2 97 | Input B: 3 98 | Result: 1005 99 | Output Variable: 5 100 | ``` 101 | 102 | ## Access to classes 103 | 104 | The JShell script is executed in the same Thread as the caller 105 | and has therefore access to the same classes. 106 | 107 | Assume your project has the following class: 108 | ```java 109 | package ch.obermuhlner.scriptengine.example; 110 | 111 | public class Person { 112 | public String name; 113 | public int birthYear; 114 | 115 | @Override 116 | public String toString() { 117 | return "Person{name=" + name + ", birthYear=" + birthYear + "}"; 118 | } 119 | } 120 | ``` 121 | 122 | In this case you can run a JShell script that uses this class `Person`: 123 | ```java 124 | try { 125 | ScriptEngineManager manager = new ScriptEngineManager(); 126 | ScriptEngine engine = manager.getEngineByName("jshell"); 127 | 128 | String script = "" + 129 | "import ch.obermuhlner.scriptengine.example.Person;" + 130 | "var person = new Person();" + 131 | "person.name = \"Eric\";" + 132 | "person.birthYear = 1967;"; 133 | 134 | Object result = engine.eval(script); 135 | System.out.println("Result: " + result); 136 | 137 | Object person = engine.get("person"); 138 | System.out.println("Person Variable: " + person); 139 | 140 | } catch (ScriptException e) { 141 | e.printStackTrace(); 142 | } 143 | ``` 144 | 145 | The console output of this snippet shows that the variable `person` created inside the JShell script is now available in the calling Java: 146 | ```console 147 | Result: 1967 148 | Person Variable: Person{name=Eric, birthYear=1967} 149 | ``` 150 | 151 | ## Error handling 152 | 153 | ```java 154 | try { 155 | ScriptEngineManager manager = new ScriptEngineManager(); 156 | ScriptEngine engine = manager.getEngineByName("jshell"); 157 | 158 | String script = "" + 159 | "System.out.println(unknown);" + 160 | "var message = \"Should never reach this point\""; 161 | 162 | Object result = engine.eval(script); 163 | System.out.println("Result: " + result); 164 | } catch (ScriptException e) { 165 | e.printStackTrace(); 166 | } 167 | ``` 168 | 169 | The console output of this snippet shows that the variable `unknown` cannot be found: 170 | ```console 171 | javax.script.ScriptException: cannot find symbol 172 | symbol: variable unknown 173 | location: class 174 | System.out.println(unknown); 175 | at ch.obermuhlner.scriptengine.jshell.JShellScriptEngine.evaluateScript(JShellScriptEngine.java:216) 176 | at ch.obermuhlner.scriptengine.jshell.JShellScriptEngine.eval(JShellScriptEngine.java:98) 177 | at ch.obermuhlner.scriptengine.jshell.JShellScriptEngine.eval(JShellScriptEngine.java:84) 178 | at ch.obermuhlner.scriptengine.jshell.JShellScriptEngine.eval(JShellScriptEngine.java:74) 179 | at ch.obermuhlner.scriptengine.example.ScriptEngineExample.runErrorExample(ScriptEngineExample.java:84) 180 | at ch.obermuhlner.scriptengine.example.ScriptEngineExample.main(ScriptEngineExample.java:14) 181 | ``` 182 | 183 | ## Compiling 184 | 185 | The `JShellScriptEngine` implements the `Compilable` interface. 186 | 187 | You can compile a script into a `CompiledScript` and execute it multiple 188 | times with different bindings. 189 | 190 | ```java 191 | try { 192 | ScriptEngineManager manager = new ScriptEngineManager(); 193 | ScriptEngine engine = manager.getEngineByName("jshell"); 194 | Compilable compiler = (Compilable) engine; 195 | 196 | CompiledScript compiledScript = compiler.compile("var output = alpha + beta"); 197 | 198 | { 199 | Bindings bindings = engine.createBindings(); 200 | bindings.put("alpha", 2); 201 | bindings.put("beta", 3); 202 | 203 | Object result = compiledScript.eval(bindings); 204 | Integer output = (Integer) bindings.get("output"); 205 | System.out.println("Result (Integer): " + result); 206 | System.out.println("Output (Integer): " + output); 207 | } 208 | 209 | { 210 | Bindings bindings = engine.createBindings(); 211 | bindings.put("alpha", "aaa"); 212 | bindings.put("beta", "bbb"); 213 | 214 | Object result = compiledScript.eval(bindings); 215 | String output = (String) bindings.get("output"); 216 | System.out.println("Result (String): " + result); 217 | System.out.println("Output (String): " + output); 218 | } 219 | } catch (ScriptException e) { 220 | e.printStackTrace(); 221 | } 222 | ``` 223 | 224 | The console output shows that the same compiled script was able to run 225 | with different bindings, which where even of different runtime types. 226 | 227 | ```console 228 | Result (Integer): 5 229 | Output (Integer): 5 230 | Result (String): aaabbb 231 | Output (String): aaabbb 232 | ``` 233 | 234 | Separating the compilation from the evaluation is more efficient if you 235 | need to evaluate the same script multiple times. 236 | 237 | Here the execution times in milliseconds for: 238 | * Multi Eval 239 | * many calls to `JShellScriptEngine.eval(String)` 240 | (essentially compile and evaluate every time) 241 | * Compile + Multi Eval 242 | * single call to `JShellScriptEngine.compile(String)` 243 | * many calls to `JShellCompiledScript.eval(Bindings)` 244 | 245 | ![Performance: Compile Multiple Evaluations](docs/performance/Compile_Multiple_Evaluations.svg) 246 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eobermuhlner/jshell-scriptengine/789fcbeb7404137ffbc31ab6dc02936d7c6324a3/build.gradle -------------------------------------------------------------------------------- /ch.obermuhlner.scriptengine.example/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'eclipse' 3 | 4 | repositories { 5 | mavenLocal() 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | compile project(':ch.obermuhlner.scriptengine.jshell') 11 | //compile 'ch.obermuhlner:jshell-scriptengine:1.1.0' 12 | 13 | testCompile 'junit:junit:4.12' 14 | } 15 | 16 | -------------------------------------------------------------------------------- /ch.obermuhlner.scriptengine.example/src/main/java/ch/obermuhlner/scriptengine/example/Person.java: -------------------------------------------------------------------------------- 1 | package ch.obermuhlner.scriptengine.example; 2 | 3 | public class Person { 4 | public String name; 5 | public int birthYear; 6 | 7 | @Override 8 | public String toString() { 9 | return "Person{name=" + name + ", birthYear=" + birthYear + "}"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ch.obermuhlner.scriptengine.example/src/main/java/ch/obermuhlner/scriptengine/example/ScriptEngineExample.java: -------------------------------------------------------------------------------- 1 | package ch.obermuhlner.scriptengine.example; 2 | 3 | import javax.script.*; 4 | 5 | public class ScriptEngineExample { 6 | public static void main(String[] args) { 7 | runNashornExamples(); 8 | runJShellExamples(); 9 | } 10 | 11 | private static void runNashornExamples() { 12 | runExample("nashorn", "2+3"); 13 | } 14 | 15 | private static void runJShellExamples() { 16 | runExample("jshell", "2+3"); 17 | 18 | runJShellBindingExample(); 19 | runJShellVisibleClassesExample(); 20 | runJShellErrorExample(); 21 | runJShellCompileExample(); 22 | } 23 | 24 | private static void runExample(String engineName, String script) { 25 | try { 26 | System.out.println("Engine: " + engineName); 27 | ScriptEngineManager manager = new ScriptEngineManager(); 28 | ScriptEngine engine = manager.getEngineByName(engineName); 29 | Object result = engine.eval(script); 30 | System.out.println("Result: " + result); 31 | } catch (ScriptException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | 36 | private static void runJShellBindingExample() { 37 | try { 38 | ScriptEngineManager manager = new ScriptEngineManager(); 39 | ScriptEngine engine = manager.getEngineByName("jshell"); 40 | 41 | String script = "" + 42 | "System.out.println(\"Input A: \" + inputA);" + 43 | "System.out.println(\"Input B: \" + inputB);" + 44 | "var output = inputA + inputB;" + 45 | "1000 + output;"; 46 | 47 | engine.put("inputA", 2); 48 | engine.put("inputB", 3); 49 | 50 | Object result = engine.eval(script); 51 | System.out.println("Result: " + result); 52 | 53 | Object output = engine.get("output"); 54 | System.out.println("Output Variable: " + output); 55 | 56 | } catch (ScriptException e) { 57 | e.printStackTrace(); 58 | } 59 | } 60 | 61 | private static void runJShellVisibleClassesExample() { 62 | try { 63 | ScriptEngineManager manager = new ScriptEngineManager(); 64 | ScriptEngine engine = manager.getEngineByName("jshell"); 65 | 66 | String script = "" + 67 | "import ch.obermuhlner.scriptengine.example.Person;" + 68 | "var person = new Person();" + 69 | "person.name = \"Eric\";" + 70 | "person.birthYear = 1967;"; 71 | 72 | Object result = engine.eval(script); 73 | System.out.println("Result: " + result); 74 | 75 | Object person = engine.get("person"); 76 | System.out.println("Person Variable: " + person); 77 | } catch (ScriptException e) { 78 | e.printStackTrace(); 79 | } 80 | } 81 | 82 | private static void runJShellErrorExample() { 83 | try { 84 | ScriptEngineManager manager = new ScriptEngineManager(); 85 | ScriptEngine engine = manager.getEngineByName("jshell"); 86 | 87 | String script = "" + 88 | "System.out.println(unknown);" + 89 | "var message = \"Should never reach this point\""; 90 | 91 | Object result = engine.eval(script); 92 | System.out.println("Result: " + result); 93 | } catch (ScriptException e) { 94 | e.printStackTrace(); 95 | } 96 | } 97 | 98 | private static void runJShellCompileExample() { 99 | try { 100 | ScriptEngineManager manager = new ScriptEngineManager(); 101 | ScriptEngine engine = manager.getEngineByName("jshell"); 102 | Compilable compiler = (Compilable) engine; 103 | 104 | CompiledScript compiledScript = compiler.compile("var output = alpha + beta"); 105 | 106 | { 107 | Bindings bindings = engine.createBindings(); 108 | 109 | bindings.put("alpha", 2); 110 | bindings.put("beta", 3); 111 | Object result = compiledScript.eval(bindings); 112 | Integer output = (Integer) bindings.get("output"); 113 | System.out.println("Result (Integer): " + result); 114 | System.out.println("Output (Integer): " + output); 115 | } 116 | 117 | { 118 | Bindings bindings = engine.createBindings(); 119 | 120 | bindings.put("alpha", "aaa"); 121 | bindings.put("beta", "bbb"); 122 | Object result = compiledScript.eval(bindings); 123 | String output = (String) bindings.get("output"); 124 | System.out.println("Result (String): " + result); 125 | System.out.println("Output (String): " + output); 126 | } 127 | } catch (ScriptException e) { 128 | e.printStackTrace(); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /ch.obermuhlner.scriptengine.example/src/main/java/ch/obermuhlner/scriptengine/example/ScriptEnginePerformance.java: -------------------------------------------------------------------------------- 1 | package ch.obermuhlner.scriptengine.example; 2 | 3 | import javax.script.*; 4 | import java.awt.desktop.FilesEvent; 5 | import java.io.*; 6 | 7 | public class ScriptEnginePerformance { 8 | public static void main(String[] args) { 9 | runCompilePerformance(); 10 | } 11 | 12 | private static void runCompilePerformance() { 13 | System.out.print("Warmup "); 14 | for (int i = 0; i < 10; i++) { 15 | System.out.print("."); 16 | for (int j = 0; j < 1; j++) { 17 | runMultiEvalExample(i); 18 | runCompileMultiEvalExample(i); 19 | } 20 | } 21 | System.out.println(); 22 | 23 | System.out.print("Measure "); 24 | try(PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("Compile_Multiple_Evaluations.csv")))) { 25 | out.println("# csv2chart.title=Multiple Eval vs. Single Compile + Multiple Eval"); 26 | out.println("n, Multi Eval, Compile + Multi Eval"); 27 | for (int i = 0; i <= 100; i+=10) { 28 | System.out.print("."); 29 | int n = i; 30 | double millis1 = measure(() -> runMultiEvalExample(n)); 31 | double millis2 = measure(() -> runCompileMultiEvalExample(n)); 32 | out.println(i + ", " + millis1 + ", " + millis2); 33 | } 34 | } catch (IOException e) { 35 | e.printStackTrace(); 36 | } 37 | System.out.println(); 38 | 39 | System.out.print("Finished"); 40 | } 41 | 42 | private static double measure(Runnable runnable) { 43 | long startNanos = System.nanoTime(); 44 | runnable.run(); 45 | long endNanos = System.nanoTime(); 46 | return (endNanos - startNanos) / 1_000_000.0; 47 | } 48 | 49 | private static void runMultiEvalExample(int n) { 50 | try { 51 | ScriptEngineManager manager = new ScriptEngineManager(); 52 | ScriptEngine engine = manager.getEngineByName("jshell"); 53 | 54 | for (int i = 0; i < n; i++) { 55 | engine.put("alpha", 2); 56 | engine.put("beta", 3); 57 | Object result = engine.eval("alpha + beta"); 58 | } 59 | } catch (ScriptException e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | 64 | private static void runCompileMultiEvalExample(int n) { 65 | try { 66 | ScriptEngineManager manager = new ScriptEngineManager(); 67 | ScriptEngine engine = manager.getEngineByName("jshell"); 68 | Compilable compiler = (Compilable) engine; 69 | 70 | CompiledScript compiledScript = compiler.compile("alpha + beta"); 71 | 72 | for (int i = 0; i < n; i++) { 73 | Bindings bindings = engine.createBindings(); 74 | 75 | bindings.put("alpha", 2); 76 | bindings.put("beta", 3); 77 | Object result = compiledScript.eval(bindings); 78 | } 79 | } catch (ScriptException e) { 80 | e.printStackTrace(); 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /ch.obermuhlner.scriptengine.jshell/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'eclipse' 4 | id 'maven' 5 | id 'signing' 6 | id 'jacoco' 7 | id 'org.sonarqube' version '2.7' 8 | } 9 | 10 | group = 'ch.obermuhlner' 11 | version = '1.1.0' 12 | archivesBaseName = 'jshell-scriptengine' 13 | 14 | ext { 15 | moduleName = "ch.obermuhlner.scriptengine.jshell" 16 | 17 | if (!project.hasProperty("ossrhUsername")) { 18 | ossrhUsername = "undefined" 19 | } 20 | if (!project.hasProperty("ossrhPassword")) { 21 | ossrhPassword="undefined" 22 | } 23 | } 24 | 25 | repositories { 26 | mavenLocal() 27 | mavenCentral() 28 | } 29 | 30 | dependencies { 31 | testCompile 'junit:junit:4.12' 32 | 33 | testCompile("org.assertj:assertj-core:3.11.1") 34 | } 35 | 36 | test { 37 | testLogging { 38 | events "failed" 39 | exceptionFormat "full" 40 | } 41 | } 42 | 43 | sonarqube { 44 | properties { 45 | property "sonar.projectKey", "jshell-scriptengine" 46 | } 47 | } 48 | 49 | jacocoTestReport { 50 | reports { 51 | xml.enabled = true 52 | html.enabled = true 53 | } 54 | } 55 | 56 | check.dependsOn jacocoTestReport 57 | 58 | jar { 59 | manifest { 60 | attributes( 61 | "Automatic-Module-Name": moduleName, 62 | "Bundle-ManifestVersion": 2, 63 | "Bundle-Name": archivesBaseName, 64 | "Bundle-SymbolicName": moduleName, 65 | "Bundle-Version": version, 66 | "Export-Package": "ch.obermuhlner.scriptengine.jshell") 67 | } 68 | } 69 | 70 | task sourcesJar(type: Jar, dependsOn: classes) { 71 | classifier = 'sources' 72 | from sourceSets.main.allSource 73 | } 74 | 75 | task javadocJar(type: Jar, dependsOn: javadoc) { 76 | classifier = 'javadoc' 77 | from javadoc.destinationDir 78 | } 79 | 80 | artifacts { 81 | archives jar 82 | archives sourcesJar 83 | archives javadocJar 84 | } 85 | 86 | signing { 87 | required { 88 | gradle.taskGraph.hasTask("uploadArchives") 89 | } 90 | 91 | sign configurations.archives 92 | } 93 | 94 | uploadArchives { 95 | repositories { 96 | mavenDeployer { 97 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 98 | 99 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { 100 | authentication(userName: ossrhUsername, password: ossrhPassword) 101 | } 102 | 103 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { 104 | authentication(userName: ossrhUsername, password: ossrhPassword) 105 | } 106 | 107 | pom.project { 108 | name 'JShell ScriptEngine' 109 | packaging 'jar' 110 | // optionally artifactId can be defined here 111 | description 'Java script engine for JShell.' 112 | url 'https://github.com/eobermuhlner/jshell-scriptengine' 113 | 114 | scm { 115 | connection 'scm:git:git://github.com/jshell-scriptengine.git' 116 | developerConnection 'scm:ssh://github.com:eobermuhlner/jshell-scriptengine.git' 117 | url 'https://github.com/eobermuhlner/jshell-scriptengine/' 118 | } 119 | 120 | licenses { 121 | license { 122 | name 'MIT License' 123 | url 'https://raw.githubusercontent.com/eobermuhlner/jshell-scriptengine/master/LICENSE.txt' 124 | } 125 | } 126 | 127 | developers { 128 | developer { 129 | id 'eobermuhlner' 130 | name 'Eric Obermuhlner' 131 | email 'eobermuhlner@gmail.com' 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | 139 | // See http://central.sonatype.org/pages/gradle.html 140 | -------------------------------------------------------------------------------- /ch.obermuhlner.scriptengine.jshell/src/main/java/ch/obermuhlner/scriptengine/jshell/JShellCompiledScript.java: -------------------------------------------------------------------------------- 1 | package ch.obermuhlner.scriptengine.jshell; 2 | 3 | import jdk.jshell.*; 4 | import jdk.jshell.execution.DirectExecutionControl; 5 | import jdk.jshell.execution.LocalExecutionControlProvider; 6 | import jdk.jshell.spi.ExecutionControl; 7 | import jdk.jshell.spi.ExecutionControlProvider; 8 | import jdk.jshell.spi.ExecutionEnv; 9 | 10 | import javax.script.*; 11 | import java.lang.reflect.Method; 12 | import java.lang.reflect.Modifier; 13 | import java.util.*; 14 | import java.util.stream.Collectors; 15 | 16 | /** 17 | * Compiled script of a {@link JShellScriptEngine}. 18 | */ 19 | public class JShellCompiledScript extends CompiledScript { 20 | private final JShellScriptEngine engine; 21 | private final List snippets; 22 | 23 | JShellCompiledScript(JShellScriptEngine engine, String script) throws ScriptException { 24 | this.engine = engine; 25 | 26 | try (JShell jshell = JShell.builder() 27 | .executionEngine(new LocalExecutionControlProvider(), null) 28 | .build()) { 29 | this.snippets = compileScript(jshell, script); 30 | } 31 | } 32 | 33 | @Override 34 | public Object eval(ScriptContext context) throws ScriptException { 35 | Bindings globalBindings = context.getBindings(ScriptContext.GLOBAL_SCOPE); 36 | Bindings engineBindings = context.getBindings(ScriptContext.ENGINE_SCOPE); 37 | 38 | final AccessDirectExecutionControl accessDirectExecutionControl = new AccessDirectExecutionControl(); 39 | try (JShell jshell = JShell.builder() 40 | .executionEngine(new AccessDirectExecutionControlProvider(accessDirectExecutionControl), null) 41 | .build()) { 42 | pushVariables(jshell, accessDirectExecutionControl, globalBindings, engineBindings); 43 | Object result = evaluateSnippets(jshell, accessDirectExecutionControl); 44 | pullVariables(jshell, accessDirectExecutionControl, globalBindings, engineBindings); 45 | 46 | return result; 47 | } 48 | } 49 | 50 | private void pushVariables(JShell jshell, AccessDirectExecutionControl accessDirectExecutionControl, Bindings globalBindings, Bindings engineBindings) throws ScriptException { 51 | Map variables = mergeBindings(globalBindings, engineBindings); 52 | VariablesTransfer.setVariables(variables); 53 | 54 | for (Map.Entry entry : variables.entrySet()) { 55 | String name = entry.getKey(); 56 | Object value = entry.getValue(); 57 | String type = determineType(value); 58 | String script = type + " " + name + " = (" + type + ") " + VariablesTransfer.class.getName() + ".getVariableValue(\"" + name + "\");"; 59 | evaluateSnippet(jshell, accessDirectExecutionControl, script); 60 | } 61 | } 62 | 63 | private void pullVariables(JShell jshell, AccessDirectExecutionControl accessDirectExecutionControl, Bindings globalBindings, Bindings engineBindings) throws ScriptException { 64 | try { 65 | jshell.variables().forEach(varSnippet -> { 66 | String name = varSnippet.name(); 67 | String script = VariablesTransfer.class.getName() + ".setVariableValue(\"" + name + "\", " + name + ");"; 68 | try { 69 | evaluateSnippet(jshell, accessDirectExecutionControl, script); 70 | Object value = VariablesTransfer.getVariableValue(name); 71 | setBindingsValue(globalBindings, engineBindings, name, value); 72 | } catch (ScriptException e) { 73 | throw new ScriptRuntimeException(e); 74 | } 75 | }); 76 | } catch (ScriptRuntimeException e) { 77 | throw (ScriptException) e.getCause(); 78 | } 79 | 80 | VariablesTransfer.clear(); 81 | } 82 | 83 | private void setBindingsValue(Bindings globalBindings, Bindings engineBindings, String name, Object value) { 84 | if (!engineBindings.containsKey(name) && globalBindings.containsKey(name)) { 85 | globalBindings.put(name, value); 86 | } else { 87 | engineBindings.put(name, value); 88 | } 89 | } 90 | 91 | private String determineType(Object value) { 92 | if (value == null) { 93 | return Object.class.getCanonicalName(); 94 | } 95 | 96 | Class clazz = value.getClass(); 97 | while (clazz != null) { 98 | if(isValidType(clazz)) { 99 | return clazz.getCanonicalName(); 100 | } 101 | for(Class interfaceClazz : clazz.getInterfaces()) { 102 | if(isValidType(interfaceClazz)) { 103 | return interfaceClazz.getCanonicalName(); 104 | } 105 | } 106 | clazz = clazz.getSuperclass(); 107 | } 108 | 109 | return Object.class.getCanonicalName(); 110 | } 111 | 112 | private boolean isValidType(Class clazz) { 113 | if(clazz.getCanonicalName() == null) { 114 | return false; 115 | } 116 | 117 | if((clazz.getModifiers() & (Modifier.PRIVATE | Modifier.PROTECTED)) != 0) { 118 | return false; 119 | } 120 | 121 | return true; 122 | } 123 | 124 | private Map mergeBindings(Bindings... bindingsToMerge) { 125 | Map variables = new HashMap<>(); 126 | 127 | for (Bindings bindings : bindingsToMerge) { 128 | if (bindings != null) { 129 | for (Map.Entry globalEntry : bindings.entrySet()) { 130 | variables.put(globalEntry.getKey(), globalEntry.getValue()); 131 | } 132 | } 133 | } 134 | 135 | return variables; 136 | } 137 | 138 | private Object evaluateSnippets(JShell jshell, AccessDirectExecutionControl accessDirectExecutionControl) throws ScriptException { 139 | Object result = null; 140 | 141 | for (String snippetScript : snippets) { 142 | result = evaluateSnippet(jshell, accessDirectExecutionControl, snippetScript); 143 | } 144 | 145 | return result; 146 | } 147 | 148 | private Object evaluateSnippet(JShell jshell, AccessDirectExecutionControl accessDirectExecutionControl, String snippetScript) throws ScriptException { 149 | Object result = null; 150 | 151 | List events = jshell.eval(snippetScript); 152 | 153 | for (SnippetEvent event : events) { 154 | if (event.status() == Snippet.Status.VALID && event.exception() == null) { 155 | result = accessDirectExecutionControl.getLastValue(); 156 | } else { 157 | throwAsScriptException(jshell, event); 158 | } 159 | } 160 | return result; 161 | } 162 | 163 | private void throwAsScriptException(JShell jshell, SnippetEvent event) throws ScriptException { 164 | if (event.exception() != null) { 165 | JShellException exception = event.exception(); 166 | String message = exception.getMessage() == null ? "" : ": " + exception.getMessage(); 167 | if (exception instanceof EvalException) { 168 | EvalException evalException = (EvalException) exception; 169 | throw new ScriptException(evalException.getExceptionClassName() + message + "\n" + event.snippet().source()); 170 | } 171 | throw new ScriptException(message + "\n" + event.snippet().source()); 172 | } 173 | 174 | Snippet snippet = event.snippet(); 175 | Optional optionalDiag = jshell.diagnostics(snippet).findAny(); 176 | if (optionalDiag.isPresent()) { 177 | Diag diag = optionalDiag.get(); 178 | throw new ScriptException(diag.getMessage(null) + "\n" + snippet); 179 | } 180 | 181 | if (snippet instanceof DeclarationSnippet) { 182 | DeclarationSnippet declarationSnippet = (DeclarationSnippet) snippet; 183 | List unresolvedDependencies = jshell.unresolvedDependencies(declarationSnippet).collect(Collectors.toList()); 184 | if (!unresolvedDependencies.isEmpty()) { 185 | throw new ScriptException("Unresolved dependencies: " + unresolvedDependencies + "\n" + snippet); 186 | } 187 | } 188 | 189 | throw new ScriptException("Unknown error\n" + snippet); 190 | } 191 | 192 | @Override 193 | public ScriptEngine getEngine() { 194 | return engine; 195 | } 196 | 197 | private static List compileScript(JShell jshell, String script) throws ScriptException { 198 | List snippets = new ArrayList<>(); 199 | 200 | while (!script.isEmpty()) { 201 | SourceCodeAnalysis.CompletionInfo completionInfo = jshell.sourceCodeAnalysis().analyzeCompletion(script); 202 | if (!completionInfo.completeness().isComplete()) { 203 | throw new ScriptException("Incomplete script\n" + script); 204 | } 205 | 206 | snippets.add(completionInfo.source()); 207 | 208 | script = completionInfo.remaining(); 209 | } 210 | 211 | return snippets; 212 | } 213 | 214 | private static class ScriptRuntimeException extends RuntimeException { 215 | public ScriptRuntimeException(ScriptException cause) { 216 | super(cause); 217 | } 218 | } 219 | 220 | private static class AccessDirectExecutionControl extends DirectExecutionControl { 221 | private Object lastValue; 222 | 223 | @Override 224 | protected String invoke(Method doitMethod) throws Exception { 225 | lastValue = doitMethod.invoke(null); 226 | return valueString(lastValue); 227 | } 228 | 229 | public Object getLastValue() { 230 | return lastValue; 231 | } 232 | } 233 | 234 | private static class AccessDirectExecutionControlProvider implements ExecutionControlProvider { 235 | private AccessDirectExecutionControl accessDirectExecutionControl; 236 | 237 | AccessDirectExecutionControlProvider(AccessDirectExecutionControl accessDirectExecutionControl) { 238 | this.accessDirectExecutionControl = accessDirectExecutionControl; 239 | } 240 | 241 | @Override 242 | public String name() { 243 | return "accessdirect"; 244 | } 245 | 246 | @Override 247 | public ExecutionControl generate(ExecutionEnv env, Map parameters) throws Throwable { 248 | return accessDirectExecutionControl; 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /ch.obermuhlner.scriptengine.jshell/src/main/java/ch/obermuhlner/scriptengine/jshell/JShellScriptEngine.java: -------------------------------------------------------------------------------- 1 | package ch.obermuhlner.scriptengine.jshell; 2 | 3 | import javax.script.*; 4 | import java.io.BufferedReader; 5 | import java.io.IOException; 6 | import java.io.Reader; 7 | import java.util.*; 8 | 9 | /** 10 | * Script engine for JShell. 11 | */ 12 | public class JShellScriptEngine implements ScriptEngine, Compilable { 13 | 14 | private ScriptContext context = new SimpleScriptContext(); 15 | 16 | @Override 17 | public ScriptContext getContext() { 18 | return context; 19 | } 20 | 21 | @Override 22 | public void setContext(ScriptContext context) { 23 | Objects.requireNonNull(context); 24 | this.context = context; 25 | } 26 | 27 | @Override 28 | public Bindings createBindings() { 29 | return new SimpleBindings(); 30 | } 31 | 32 | @Override 33 | public Bindings getBindings(int scope) { 34 | return context.getBindings(scope); 35 | } 36 | 37 | @Override 38 | public void setBindings(Bindings bindings, int scope) { 39 | context.setBindings(bindings, scope); 40 | } 41 | 42 | @Override 43 | public void put(String key, Object value) { 44 | getBindings(ScriptContext.ENGINE_SCOPE).put(key, value); 45 | } 46 | 47 | @Override 48 | public Object get(String key) { 49 | return getBindings(ScriptContext.ENGINE_SCOPE).get(key); 50 | } 51 | 52 | @Override 53 | public ScriptEngineFactory getFactory() { 54 | return new JShellScriptEngineFactory(); 55 | } 56 | 57 | @Override 58 | public Object eval(Reader reader) throws ScriptException { 59 | return eval(readScript(reader)); 60 | } 61 | 62 | @Override 63 | public Object eval(String script) throws ScriptException { 64 | return eval(script, context); 65 | } 66 | 67 | @Override 68 | public Object eval(Reader reader, ScriptContext context) throws ScriptException { 69 | return eval(readScript(reader), context); 70 | } 71 | 72 | @Override 73 | public Object eval(String script, ScriptContext context) throws ScriptException { 74 | return eval(script, context.getBindings(ScriptContext.ENGINE_SCOPE)); 75 | } 76 | 77 | @Override 78 | public Object eval(Reader reader, Bindings bindings) throws ScriptException { 79 | return eval(readScript(reader), bindings); 80 | } 81 | 82 | @Override 83 | public Object eval(String script, Bindings bindings) throws ScriptException { 84 | CompiledScript compile = compile(script); 85 | 86 | return compile.eval(bindings); 87 | } 88 | 89 | @Override 90 | public CompiledScript compile(Reader reader) throws ScriptException { 91 | return compile(readScript(reader)); 92 | } 93 | 94 | @Override 95 | public CompiledScript compile(String script) throws ScriptException { 96 | return new JShellCompiledScript(this, script); 97 | } 98 | 99 | private String readScript(Reader reader) throws ScriptException { 100 | try { 101 | StringBuilder s = new StringBuilder(); 102 | BufferedReader bufferedReader = new BufferedReader(reader); 103 | String line; 104 | while ((line = bufferedReader.readLine()) != null) { 105 | s.append(line); 106 | s.append("\n"); 107 | } 108 | return s.toString(); 109 | } catch (IOException e) { 110 | throw new ScriptException(e); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /ch.obermuhlner.scriptengine.jshell/src/main/java/ch/obermuhlner/scriptengine/jshell/JShellScriptEngineFactory.java: -------------------------------------------------------------------------------- 1 | package ch.obermuhlner.scriptengine.jshell; 2 | 3 | import javax.script.ScriptEngine; 4 | import javax.script.ScriptEngineFactory; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | /** 9 | * Factory for {@link JShellScriptEngine}. 10 | */ 11 | public class JShellScriptEngineFactory implements ScriptEngineFactory { 12 | @Override 13 | public String getEngineName() { 14 | return "JShell ScriptEngine"; 15 | } 16 | 17 | @Override 18 | public String getEngineVersion() { 19 | return "1.1.0"; 20 | } 21 | 22 | @Override 23 | public List getExtensions() { 24 | return Arrays.asList("jsh", "jshell"); 25 | } 26 | 27 | @Override 28 | public List getMimeTypes() { 29 | return Arrays.asList("text/x-jshell-source"); 30 | } 31 | 32 | @Override 33 | public List getNames() { 34 | return Arrays.asList("JShell", "jshell", "ch.obermuhlner:jshell-scriptengine", "obermuhlner-jshell"); 35 | } 36 | 37 | @Override 38 | public String getLanguageName() { 39 | return "JShell"; 40 | } 41 | 42 | @Override 43 | public String getLanguageVersion() { 44 | return System.getProperty("java.version"); 45 | } 46 | 47 | @Override 48 | public Object getParameter(String key) { 49 | switch (key) { 50 | case ScriptEngine.ENGINE: 51 | return getEngineName(); 52 | case ScriptEngine.ENGINE_VERSION: 53 | return getEngineVersion(); 54 | case ScriptEngine.LANGUAGE: 55 | return getLanguageName(); 56 | case ScriptEngine.LANGUAGE_VERSION: 57 | return getLanguageVersion(); 58 | case ScriptEngine.NAME: 59 | return getNames().get(0); 60 | default: 61 | return null; 62 | } 63 | } 64 | 65 | @Override 66 | public String getMethodCallSyntax(String obj, String method, String... args) { 67 | StringBuilder s = new StringBuilder(); 68 | s.append(obj); 69 | s.append("."); 70 | s.append(method); 71 | s.append("("); 72 | for(int i = 0; i < args.length; i++) { 73 | if (i > 0) { 74 | s.append(","); 75 | } 76 | s.append(args[i]); 77 | } 78 | s.append(")"); 79 | return s.toString(); 80 | } 81 | 82 | @Override 83 | public String getOutputStatement(String toDisplay) { 84 | return "System.out.println(" + toDisplay + ")"; 85 | } 86 | 87 | @Override 88 | public String getProgram(String... statements) { 89 | StringBuilder s = new StringBuilder(); 90 | for(String statement : statements) { 91 | s.append(statement); 92 | s.append(";\n"); 93 | } 94 | return s.toString(); 95 | } 96 | 97 | @Override 98 | public ScriptEngine getScriptEngine() { 99 | return new JShellScriptEngine(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /ch.obermuhlner.scriptengine.jshell/src/main/java/ch/obermuhlner/scriptengine/jshell/VariablesTransfer.java: -------------------------------------------------------------------------------- 1 | package ch.obermuhlner.scriptengine.jshell; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Utility to transfer variables in and out of a JShell evaluation. 7 | */ 8 | public class VariablesTransfer { 9 | private static final ThreadLocal> threadLocalVariables = new ThreadLocal<>(); 10 | 11 | private VariablesTransfer() { 12 | // empty 13 | } 14 | 15 | /** 16 | * Sets all variables for an evaluation. 17 | * 18 | * @param variables the name/value pairs 19 | */ 20 | public static void setVariables(Map variables) { 21 | threadLocalVariables.set(variables); 22 | } 23 | 24 | /** 25 | * Returns the variable value for the specified name. 26 | * 27 | * @param name the name of the variable 28 | * @return the value of the variable, or null if not defined 29 | */ 30 | public static Object getVariableValue(String name) { 31 | return threadLocalVariables.get().get(name); 32 | } 33 | 34 | /** 35 | * Sets the variable value for the specified name. 36 | * 37 | * @param name the name of the variable 38 | * @param value the value of the variable 39 | */ 40 | public static void setVariableValue(String name, Object value) { 41 | threadLocalVariables.get().put(name, value); 42 | } 43 | 44 | /** 45 | * Clears all variables. 46 | */ 47 | public static void clear() { 48 | threadLocalVariables.get().clear(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /ch.obermuhlner.scriptengine.jshell/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory: -------------------------------------------------------------------------------- 1 | ch.obermuhlner.scriptengine.jshell.JShellScriptEngineFactory -------------------------------------------------------------------------------- /ch.obermuhlner.scriptengine.jshell/src/test/java/ch/obermuhlner/scriptengine/jshell/JShellScriptEngineFactoryTest.java: -------------------------------------------------------------------------------- 1 | package ch.obermuhlner.scriptengine.jshell; 2 | 3 | import org.junit.Test; 4 | 5 | import javax.script.ScriptEngine; 6 | import java.util.Arrays; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | import static org.junit.Assert.assertTrue; 10 | 11 | public class JShellScriptEngineFactoryTest { 12 | @Test 13 | public void testGetEngineName() { 14 | JShellScriptEngineFactory factory = new JShellScriptEngineFactory(); 15 | assertThat(factory.getEngineName()).isEqualTo("JShell ScriptEngine"); 16 | } 17 | 18 | @Test 19 | public void testGetEngineVersion() { 20 | JShellScriptEngineFactory factory = new JShellScriptEngineFactory(); 21 | assertThat(factory.getEngineVersion()).isEqualTo("1.1.0"); 22 | } 23 | 24 | @Test 25 | public void testGetLanguageName() { 26 | JShellScriptEngineFactory factory = new JShellScriptEngineFactory(); 27 | assertThat(factory.getLanguageName()).isEqualTo("JShell"); 28 | } 29 | 30 | @Test 31 | public void testGetLanguageVersion() { 32 | JShellScriptEngineFactory factory = new JShellScriptEngineFactory(); 33 | assertThat(factory.getLanguageVersion()).isEqualTo(System.getProperty("java.version")); 34 | } 35 | 36 | @Test 37 | public void testGetExtensions() { 38 | JShellScriptEngineFactory factory = new JShellScriptEngineFactory(); 39 | assertThat(factory.getExtensions()).isEqualTo(Arrays.asList("jsh", "jshell")); 40 | } 41 | 42 | @Test 43 | public void testGetMimeTypes() { 44 | JShellScriptEngineFactory factory = new JShellScriptEngineFactory(); 45 | assertThat(factory.getMimeTypes()).isEqualTo(Arrays.asList("text/x-jshell-source")); 46 | } 47 | 48 | @Test 49 | public void testGetNames() { 50 | JShellScriptEngineFactory factory = new JShellScriptEngineFactory(); 51 | assertThat(factory.getNames()).isEqualTo(Arrays.asList("JShell", "jshell", "ch.obermuhlner:jshell-scriptengine", "obermuhlner-jshell")); 52 | } 53 | 54 | @Test 55 | public void testGetParameters() { 56 | JShellScriptEngineFactory factory = new JShellScriptEngineFactory(); 57 | assertThat(factory.getParameter(ScriptEngine.ENGINE)).isEqualTo(factory.getEngineName()); 58 | assertThat(factory.getParameter(ScriptEngine.ENGINE_VERSION)).isEqualTo(factory.getEngineVersion()); 59 | assertThat(factory.getParameter(ScriptEngine.LANGUAGE)).isEqualTo(factory.getLanguageName()); 60 | assertThat(factory.getParameter(ScriptEngine.LANGUAGE_VERSION)).isEqualTo(factory.getLanguageVersion()); 61 | assertThat(factory.getParameter(ScriptEngine.NAME)).isEqualTo("JShell"); 62 | assertThat(factory.getParameter("unknown")).isEqualTo(null); 63 | } 64 | 65 | @Test 66 | public void testGetMethodCallSyntax() { 67 | JShellScriptEngineFactory factory = new JShellScriptEngineFactory(); 68 | assertThat(factory.getMethodCallSyntax("obj", "method")).isEqualTo("obj.method()"); 69 | assertThat(factory.getMethodCallSyntax("obj", "method", "alpha")).isEqualTo("obj.method(alpha)"); 70 | assertThat(factory.getMethodCallSyntax("obj", "method", "alpha", "beta")).isEqualTo("obj.method(alpha,beta)"); 71 | } 72 | 73 | @Test 74 | public void testGetOutputStatement() { 75 | JShellScriptEngineFactory factory = new JShellScriptEngineFactory(); 76 | assertThat(factory.getOutputStatement("alpha")).isEqualTo("System.out.println(alpha)"); 77 | } 78 | 79 | @Test 80 | public void testGetProgram() { 81 | JShellScriptEngineFactory factory = new JShellScriptEngineFactory(); 82 | assertThat(factory.getProgram()).isEqualTo(""); 83 | assertThat(factory.getProgram("alpha")).isEqualTo("alpha;\n"); 84 | assertThat(factory.getProgram("alpha", "beta")).isEqualTo("alpha;\nbeta;\n"); 85 | } 86 | 87 | @Test 88 | public void testGetScriptEngine() { 89 | JShellScriptEngineFactory factory = new JShellScriptEngineFactory(); 90 | assertThat(factory.getScriptEngine() instanceof JShellScriptEngine).isTrue(); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /ch.obermuhlner.scriptengine.jshell/src/test/java/ch/obermuhlner/scriptengine/jshell/JShellScriptEngineTest.java: -------------------------------------------------------------------------------- 1 | package ch.obermuhlner.scriptengine.jshell; 2 | 3 | import org.junit.Ignore; 4 | import org.junit.Test; 5 | 6 | import javax.script.*; 7 | 8 | import java.io.IOException; 9 | import java.io.Reader; 10 | import java.io.StringReader; 11 | 12 | import static org.assertj.core.api.Assertions.*; 13 | 14 | 15 | public class JShellScriptEngineTest { 16 | @Test 17 | public void testEmpty() throws ScriptException { 18 | assertScript("", null); 19 | } 20 | 21 | @Test 22 | public void testSimple() throws ScriptException { 23 | assertScript("2+3", 5); 24 | } 25 | 26 | @Test 27 | public void testSimpleDeclareVariable() throws ScriptException { 28 | assertScript("var alpha = 123", 123); 29 | } 30 | 31 | @Ignore("lastValue() has no access to default value of declarations") 32 | @Test 33 | public void testSimpleDeclareIntVariable() throws ScriptException { 34 | assertScript("int alpha", 123); 35 | } 36 | 37 | @Test 38 | public void testFailUnknownVariable() { 39 | assertThatThrownBy(() -> { 40 | assertScript("unknown", null); 41 | }).isInstanceOf(ScriptException.class).hasMessageContaining("unknown"); 42 | } 43 | 44 | @Test 45 | public void testFailIncompleteScript() { 46 | assertThatThrownBy(() -> { 47 | assertScript("foo(", null); 48 | }).isInstanceOf(ScriptException.class).hasMessageContaining("Incomplete"); 49 | } 50 | 51 | @Test 52 | public void testFailSameVariable() { 53 | assertThatThrownBy(() -> { 54 | assertScript("" + 55 | "var alpha = 0;" + 56 | "var alpha = 1;", 57 | null); 58 | }).isInstanceOf(ScriptException.class).hasMessageContaining("alpha"); 59 | } 60 | 61 | @Test 62 | public void testFailEvalDivByZero() { 63 | assertThatThrownBy(() -> { 64 | assertScript("1/0", null); 65 | }).isInstanceOf(ScriptException.class).hasMessageContaining("ArithmeticException"); 66 | } 67 | 68 | @Test 69 | public void testFailEvalNullPointerException() { 70 | assertThatThrownBy(() -> { 71 | assertScript("" + 72 | "Object foo = null;" + 73 | "foo.toString()", 74 | null); 75 | }).isInstanceOf(ScriptException.class).hasMessageContaining("NullPointerException"); 76 | } 77 | 78 | @Test 79 | public void testBindingsExistingVariable() throws ScriptException { 80 | ScriptEngineManager manager = new ScriptEngineManager(); 81 | ScriptEngine engine = manager.getEngineByName("jshell"); 82 | engine.put("alpha", 2); 83 | engine.put("beta", 3); 84 | engine.put("gamma", 0); 85 | 86 | Object result = engine.eval("gamma = alpha + beta"); 87 | 88 | assertThat(result).isEqualTo(5); 89 | assertThat(engine.get("gamma")).isEqualTo(5); 90 | } 91 | 92 | @Test 93 | public void testBindingsNewVariable() throws ScriptException { 94 | ScriptEngineManager manager = new ScriptEngineManager(); 95 | ScriptEngine engine = manager.getEngineByName("jshell"); 96 | engine.put("alpha", 2); 97 | engine.put("beta", 3); 98 | 99 | Object result = engine.eval("var gamma = alpha + beta"); 100 | assertThat(result).isEqualTo(5); 101 | assertThat(engine.get("gamma")).isEqualTo(5); 102 | } 103 | 104 | @Test 105 | public void testBindingsMultipleEval() throws ScriptException { 106 | ScriptEngineManager manager = new ScriptEngineManager(); 107 | ScriptEngine engine = manager.getEngineByName("jshell"); 108 | 109 | engine.put("alpha", 2); 110 | engine.put("beta", 3); 111 | engine.put("gamma", 0); 112 | 113 | Object result; 114 | result = engine.eval("gamma = alpha + beta"); 115 | assertThat(result).isEqualTo(5); 116 | assertThat(engine.get("gamma")).isEqualTo(5); 117 | 118 | result = engine.eval("gamma = alpha + beta + gamma"); 119 | assertThat(result).isEqualTo(10); 120 | assertThat(engine.get("gamma")).isEqualTo(10); 121 | 122 | engine.put("alpha", "aaa"); 123 | engine.put("beta", "bbb"); 124 | engine.put("gamma", ""); 125 | 126 | result = engine.eval("gamma = alpha + beta"); 127 | assertThat(result).isEqualTo("aaabbb"); 128 | assertThat(engine.get("gamma")).isEqualTo("aaabbb"); 129 | 130 | result = engine.eval("gamma = alpha + beta + gamma"); 131 | assertThat(result).isEqualTo("aaabbbaaabbb"); 132 | assertThat(engine.get("gamma")).isEqualTo("aaabbbaaabbb"); 133 | 134 | } 135 | 136 | @Test 137 | public void testBindingsGlobalVariable() throws ScriptException { 138 | ScriptEngineManager manager = new ScriptEngineManager(); 139 | ScriptEngine engine = manager.getEngineByName("jshell"); 140 | Bindings globalBindings = engine.getBindings(ScriptContext.GLOBAL_SCOPE); 141 | globalBindings.put("alpha", 2); 142 | globalBindings.put("beta", 3); 143 | globalBindings.put("gamma", 0); 144 | 145 | Object result = engine.eval("gamma = alpha + beta"); 146 | assertThat(result).isEqualTo(5); 147 | assertThat(engine.get("gamma")).isEqualTo(null); 148 | assertThat(globalBindings.get("gamma")).isEqualTo(5); 149 | } 150 | 151 | @Test 152 | public void testBindingsOverrideGlobalVariable() throws ScriptException { 153 | ScriptEngineManager manager = new ScriptEngineManager(); 154 | ScriptEngine engine = manager.getEngineByName("jshell"); 155 | Bindings globalBindings = engine.getBindings(ScriptContext.GLOBAL_SCOPE); 156 | globalBindings.put("alpha", 2); 157 | globalBindings.put("beta", 3); 158 | globalBindings.put("gamma", 0); 159 | 160 | engine.put("gamma", 999); 161 | 162 | assertThat(engine.get("gamma")).isEqualTo(999); 163 | assertThat(globalBindings.get("gamma")).isEqualTo(0); 164 | 165 | Object result = engine.eval("gamma = alpha + beta"); 166 | assertThat(result).isEqualTo(5); 167 | assertThat(engine.get("gamma")).isEqualTo(5); 168 | assertThat(globalBindings.get("gamma")).isEqualTo(0); 169 | } 170 | 171 | @Test 172 | public void testBindingsPublicClass() throws ScriptException { 173 | ScriptEngineManager manager = new ScriptEngineManager(); 174 | ScriptEngine engine = manager.getEngineByName("jshell"); 175 | PublicClass publicClass = new PublicClass(); 176 | publicClass.message = "hello"; 177 | engine.put("alpha", publicClass); 178 | 179 | Object result = engine.eval("" + 180 | "ch.obermuhlner.scriptengine.jshell.JShellScriptEngineTest.PublicClass beta = alpha;" + 181 | "var message = alpha.message"); 182 | assertThat(result).isEqualTo("hello"); 183 | assertThat(engine.get("alpha")).isSameAs(publicClass); 184 | assertThat(engine.get("message")).isEqualTo("hello"); 185 | } 186 | 187 | @Test 188 | public void testBindingsPrivateClassFail() throws ScriptException { 189 | ScriptEngineManager manager = new ScriptEngineManager(); 190 | ScriptEngine engine = manager.getEngineByName("jshell"); 191 | PrivateClass privateClass = new PrivateClass(); 192 | engine.put("alpha", privateClass); 193 | 194 | assertThatThrownBy(() -> { 195 | Object result = engine.eval("ch.obermuhlner.scriptengine.jshell.JShellScriptEngineTest.PrivateClass beta = alpha"); 196 | }).isInstanceOf(ScriptException.class).hasMessageContaining("PrivateClass"); 197 | } 198 | 199 | @Test 200 | public void testBindingsPrivateClassAsObject() throws ScriptException { 201 | ScriptEngineManager manager = new ScriptEngineManager(); 202 | ScriptEngine engine = manager.getEngineByName("jshell"); 203 | PrivateClass privateClass = new PrivateClass(); 204 | engine.put("alpha", privateClass); 205 | 206 | Object result = engine.eval("Object beta = alpha"); 207 | assertThat(engine.get("alpha")).isSameAs(privateClass); 208 | assertThat(engine.get("beta")).isSameAs(privateClass); 209 | } 210 | 211 | @Test 212 | public void testBindingsProtectedClassFail() { 213 | ScriptEngineManager manager = new ScriptEngineManager(); 214 | ScriptEngine engine = manager.getEngineByName("jshell"); 215 | ProtectedClass protectedClass = new ProtectedClass(); 216 | engine.put("alpha", protectedClass); 217 | 218 | assertThatThrownBy(() -> { 219 | Object result = engine.eval("ch.obermuhlner.scriptengine.jshell.JShellScriptEngineTest.ProtectedClass beta = alpha"); 220 | }).isInstanceOf(ScriptException.class).hasMessageContaining("ProtectedClass"); 221 | } 222 | 223 | @Test 224 | public void testBindingsProtectedClassAsObject() throws ScriptException { 225 | ScriptEngineManager manager = new ScriptEngineManager(); 226 | ScriptEngine engine = manager.getEngineByName("jshell"); 227 | ProtectedClass protectedClass = new ProtectedClass(); 228 | engine.put("alpha", protectedClass); 229 | 230 | Object result = engine.eval("Object beta = alpha"); 231 | assertThat(engine.get("alpha")).isSameAs(protectedClass); 232 | assertThat(engine.get("beta")).isSameAs(protectedClass); 233 | } 234 | 235 | @Test 236 | public void testBindingsPrivateClassAsInterface() throws ScriptException { 237 | ScriptEngineManager manager = new ScriptEngineManager(); 238 | ScriptEngine engine = manager.getEngineByName("jshell"); 239 | PrivateClassWithMyInterface privateClassWithMyInterface = new PrivateClassWithMyInterface(); 240 | engine.put("alpha", privateClassWithMyInterface); 241 | 242 | Object result = engine.eval("" + 243 | "ch.obermuhlner.scriptengine.jshell.JShellScriptEngineTest.MyInterface beta = alpha;" + 244 | "alpha.getMessage()"); 245 | assertThat(result).isEqualTo("my message"); 246 | assertThat(engine.get("alpha")).isSameAs(privateClassWithMyInterface); 247 | assertThat(engine.get("beta")).isSameAs(privateClassWithMyInterface); 248 | } 249 | 250 | @Test 251 | public void testBindingsNullAsObject() throws ScriptException { 252 | ScriptEngineManager manager = new ScriptEngineManager(); 253 | ScriptEngine engine = manager.getEngineByName("jshell"); 254 | engine.put("alpha", null); 255 | 256 | Object result = engine.eval("Object beta = alpha"); 257 | assertThat(engine.get("alpha")).isNull(); 258 | assertThat(engine.get("beta")).isNull(); 259 | } 260 | 261 | @Test 262 | public void testBindingsAnonymousClassAsObject() throws ScriptException { 263 | ScriptEngineManager manager = new ScriptEngineManager(); 264 | ScriptEngine engine = manager.getEngineByName("jshell"); 265 | Object anonymous = new Object() { 266 | }; 267 | System.out.println(anonymous.getClass().getCanonicalName()); 268 | engine.put("alpha", anonymous); 269 | 270 | Object result = engine.eval("Object beta = alpha"); 271 | assertThat(engine.get("alpha")).isSameAs(anonymous); 272 | assertThat(engine.get("beta")).isSameAs(anonymous); 273 | } 274 | 275 | @Test 276 | public void testBindingsPutIllegalVariable() throws ScriptException { 277 | ScriptEngineManager manager = new ScriptEngineManager(); 278 | ScriptEngine engine = manager.getEngineByName("jshell"); 279 | 280 | assertThatThrownBy(() -> { 281 | engine.put("illegal with spaces", 2); 282 | Object result = engine.eval("1234"); 283 | }).isInstanceOf(ScriptException.class); 284 | } 285 | 286 | @Test 287 | public void testBindingsGetIllegalVariable() throws ScriptException { 288 | ScriptEngineManager manager = new ScriptEngineManager(); 289 | ScriptEngine engine = manager.getEngineByName("jshell"); 290 | 291 | assertThat(engine.get("illegal with spaces")).isNull(); 292 | } 293 | 294 | @Test 295 | public void testCompilable() throws ScriptException { 296 | ScriptEngineManager manager = new ScriptEngineManager(); 297 | ScriptEngine engine = manager.getEngineByName("jshell"); 298 | assertThat(engine).isInstanceOf(Compilable.class); 299 | 300 | String script = "alpha + beta"; 301 | 302 | Compilable compiler = (Compilable) engine; 303 | CompiledScript compiledScript = compiler.compile(script); 304 | assertThat(compiledScript.getEngine()).isSameAs(engine); 305 | 306 | for (int i = 0; i < 2; i++) { 307 | Bindings bindings = engine.createBindings(); 308 | 309 | bindings.put("alpha", 2); 310 | bindings.put("beta", 3); 311 | Object result = compiledScript.eval(bindings); 312 | assertThat(result).isEqualTo(5); 313 | } 314 | 315 | for (int i = 0; i < 2; i++) { 316 | Bindings bindings = engine.createBindings(); 317 | 318 | bindings.put("alpha", "aaa"); 319 | bindings.put("beta", "bbb"); 320 | Object result = compiledScript.eval(bindings); 321 | assertThat(result).isEqualTo("aaabbb"); 322 | } 323 | } 324 | 325 | @Test 326 | public void testCompilableReader() throws ScriptException { 327 | ScriptEngineManager manager = new ScriptEngineManager(); 328 | ScriptEngine engine = manager.getEngineByName("jshell"); 329 | assertThat(engine).isInstanceOf(Compilable.class); 330 | 331 | String script = "alpha + beta"; 332 | StringReader scriptReader = new StringReader(script); 333 | 334 | Compilable compiler = (Compilable) engine; 335 | CompiledScript compiledScript = compiler.compile(scriptReader); 336 | assertThat(compiledScript.getEngine()).isSameAs(engine); 337 | 338 | for (int i = 0; i < 2; i++) { 339 | Bindings bindings = engine.createBindings(); 340 | 341 | bindings.put("alpha", 2); 342 | bindings.put("beta", 3); 343 | Object result = compiledScript.eval(bindings); 344 | assertThat(result).isEqualTo(5); 345 | } 346 | 347 | for (int i = 0; i < 2; i++) { 348 | Bindings bindings = engine.createBindings(); 349 | 350 | bindings.put("alpha", "aaa"); 351 | bindings.put("beta", "bbb"); 352 | Object result = compiledScript.eval(bindings); 353 | assertThat(result).isEqualTo("aaabbb"); 354 | } 355 | } 356 | 357 | @Test 358 | public void testEvalReader() throws ScriptException { 359 | ScriptEngineManager manager = new ScriptEngineManager(); 360 | ScriptEngine engine = manager.getEngineByName("jshell"); 361 | 362 | Reader reader = new StringReader("1234"); 363 | Object result = engine.eval(reader); 364 | assertThat(result).isEqualTo(1234); 365 | } 366 | 367 | @Test 368 | public void testEvalReaderContext() throws ScriptException { 369 | ScriptEngineManager manager = new ScriptEngineManager(); 370 | ScriptEngine engine = manager.getEngineByName("jshell"); 371 | 372 | ScriptContext context = new SimpleScriptContext(); 373 | context.getBindings(ScriptContext.ENGINE_SCOPE).put("alpha", 1000); 374 | Reader reader = new StringReader("alpha+999"); 375 | Object result = engine.eval(reader, context); 376 | assertThat(result).isEqualTo(1999); 377 | } 378 | 379 | @Test 380 | public void testEvalReaderBindings() throws ScriptException { 381 | ScriptEngineManager manager = new ScriptEngineManager(); 382 | ScriptEngine engine = manager.getEngineByName("jshell"); 383 | 384 | SimpleBindings bindings = new SimpleBindings(); 385 | bindings.put("alpha", 1000); 386 | Reader reader = new StringReader("alpha+321"); 387 | Object result = engine.eval(reader, bindings); 388 | assertThat(result).isEqualTo(1321); 389 | } 390 | 391 | @Test 392 | public void testEvalReaderFail() { 393 | ScriptEngineManager manager = new ScriptEngineManager(); 394 | ScriptEngine engine = manager.getEngineByName("jshell"); 395 | 396 | Reader reader = new FailReader(); 397 | assertThatThrownBy(() -> { 398 | Object result = engine.eval(reader); 399 | }).isInstanceOf(ScriptException.class); 400 | } 401 | 402 | @Test 403 | public void testSetGetContext() throws ScriptException { 404 | ScriptEngineManager manager = new ScriptEngineManager(); 405 | ScriptEngine engine = manager.getEngineByName("jshell"); 406 | 407 | assertThat(engine.getContext()).isNotNull(); 408 | 409 | SimpleScriptContext context = new SimpleScriptContext(); 410 | engine.setContext(context); 411 | assertThat(engine.getContext()).isSameAs(context); 412 | 413 | assertThatThrownBy(() -> { 414 | engine.setContext(null); 415 | }).isInstanceOf(NullPointerException.class); 416 | } 417 | 418 | @Test 419 | public void testCreateBindings() throws ScriptException { 420 | ScriptEngineManager manager = new ScriptEngineManager(); 421 | ScriptEngine engine = manager.getEngineByName("jshell"); 422 | 423 | assertThat(engine.createBindings()).isNotNull(); 424 | } 425 | 426 | @Test 427 | public void testSetBindings() throws ScriptException { 428 | ScriptEngineManager manager = new ScriptEngineManager(); 429 | ScriptEngine engine = manager.getEngineByName("jshell"); 430 | 431 | engine.setBindings(null, ScriptContext.GLOBAL_SCOPE); 432 | 433 | assertThatThrownBy(() -> { 434 | engine.setBindings(null, ScriptContext.ENGINE_SCOPE); 435 | }).isInstanceOf(NullPointerException.class); 436 | 437 | assertThatThrownBy(() -> { 438 | engine.setBindings(new SimpleBindings(), -999); 439 | }).isInstanceOf(IllegalArgumentException.class); 440 | } 441 | 442 | @Test 443 | public void testGetFactory() throws ScriptException { 444 | ScriptEngineManager manager = new ScriptEngineManager(); 445 | ScriptEngine engine = manager.getEngineByName("jshell"); 446 | 447 | ScriptEngineFactory factory = engine.getFactory(); 448 | assertThat(factory.getClass()).isSameAs(JShellScriptEngineFactory.class); 449 | } 450 | 451 | private void assertScript(String script, Object expectedResult) throws ScriptException { 452 | ScriptEngineManager manager = new ScriptEngineManager(); 453 | ScriptEngine engine = manager.getEngineByName("jshell"); 454 | Object result = engine.eval(script); 455 | assertThat(result).isEqualTo(expectedResult); 456 | } 457 | 458 | public static class PublicClass { 459 | public String message; 460 | } 461 | 462 | private static class PrivateClass { 463 | } 464 | 465 | public interface MyInterface { 466 | default String getMessage() { 467 | return "my message"; 468 | } 469 | } 470 | 471 | private static class PrivateClassWithMyInterface implements MyInterface { 472 | } 473 | 474 | protected static class ProtectedClass { 475 | } 476 | 477 | public static class FailReader extends Reader { 478 | public FailReader() { 479 | } 480 | 481 | @Override 482 | public int read(char[] cbuf, int off, int len) throws IOException { 483 | throw new IOException("FailReader: always fails in read()"); 484 | } 485 | 486 | @Override 487 | public void close() throws IOException { 488 | throw new IOException("FailReader: always fails in close()"); 489 | } 490 | } 491 | } 492 | -------------------------------------------------------------------------------- /docs/performance/Compile_Multiple_Evaluations.csv: -------------------------------------------------------------------------------- 1 | # csv2chart.title=Multiple Eval vs. Single Compile + Multiple Eval 2 | n, Multi Eval, Compile + Multi Eval 3 | 0, 0.849778, 3.217388 4 | 10, 1471.577447, 798.607958 5 | 20, 3865.350557, 1388.355621 6 | 30, 6858.387189, 1987.354454 7 | 40, 10875.47284, 2639.965203 8 | 50, 16443.564772, 3126.698816 9 | 60, 23191.713807, 3580.409182 10 | 70, 29726.403625, 4117.896041 11 | 80, 38329.799896, 5183.050093 12 | 90, 49922.128895, 5613.678949 13 | 100, 61896.662791, 5943.751045 14 | -------------------------------------------------------------------------------- /docs/performance/Compile_Multiple_Evaluations.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Multiple Eval vs. Single Compile + Multiple EvalMulti EvalCompile + Multi Eval05101520253035404550556065707580859095100105n05,00010,00015,00020,00025,00030,00035,00040,00045,00050,00055,00060,000 7 | -------------------------------------------------------------------------------- /docs/releases/next_release_note.md: -------------------------------------------------------------------------------- 1 | # API changes 2 | 3 | No API changes. 4 | 5 | 6 | # Bugfixes 7 | 8 | No Bugfix changes. 9 | 10 | 11 | # Enhancements 12 | 13 | ## Added `Example.method(y)` 14 | 15 | Added `Example.method(y)` 16 | 17 | 18 | # Examples 19 | 20 | Note: The example code is available on github, but not part of the 21 | `jshell-scriptengine` library. 22 | 23 | No changes in the examples. 24 | -------------------------------------------------------------------------------- /docs/releases/template_release_note.md: -------------------------------------------------------------------------------- 1 | # API changes 2 | 3 | No API changes. 4 | 5 | 6 | # Bugfixes 7 | 8 | No Bugfix changes. 9 | 10 | 11 | # Enhancements 12 | 13 | ## Added `Example.method(y)` 14 | 15 | Added `Example.method(y)` 16 | 17 | 18 | # Examples 19 | 20 | Note: The example code is available on github, but not part of the 21 | `jshell-scriptengine` library. 22 | 23 | No changes in the examples. 24 | -------------------------------------------------------------------------------- /docs/releases/v1.0.0.md: -------------------------------------------------------------------------------- 1 | # Release 1.0.0 2 | 3 | First release with correct name (jshell-scriptengine). 4 | 5 | Compiled with `jdk-11.0.4+11`. 6 | -------------------------------------------------------------------------------- /docs/releases/v1.1.0.md: -------------------------------------------------------------------------------- 1 | # Release 1.1.0 2 | 3 | # API changes 4 | 5 | ## Support for `Compilable` for separate compilation + evaluation 6 | 7 | The `jshell-scriptengine` supports now the `Compilable` interface. 8 | 9 | If the same script needs to be evaluated multiple times with different 10 | variable bindings then it is much faster to compile the script only once 11 | and evaluate the compiled script multiple times. 12 | 13 | # Bugfixes 14 | 15 | ## Close `JShell` instance after evaluation 16 | 17 | In order to free resources the JShell instance is now closed after 18 | evaluation. 19 | 20 | # Examples 21 | 22 | Note: The example code is available on github, but not part of the 23 | `jshell-scriptengine` library. 24 | 25 | ## Added `ScriptEngineExample.runJShellCompileExample()` example method 26 | 27 | The `ScriptEngineExample` contains now an example separate compilation 28 | and evaluation. 29 | 30 | ## Added `ScriptEnginePerformance` example class 31 | 32 | The `ScriptEnginePerformance` measures the performance of code snippets 33 | using the `JShellScriptEngine`. 34 | 35 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eobermuhlner/jshell-scriptengine/789fcbeb7404137ffbc31ab6dc02936d7c6324a3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Mar 30 17:07:14 CEST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | ch.obermuhlner 6 | scriptengine-jshell 7 | 0.1.0 8 | JShell ScriptEngine 9 | Java script engine for JShell. 10 | https://github.com/eobermuhlner/java-scriptengine 11 | 12 | 13 | MIT License 14 | https://raw.githubusercontent.com/eobermuhlner/java-scriptengine/master/LICENSE.txt 15 | 16 | 17 | 18 | 19 | eobermuhlner 20 | Eric Obermuhlner 21 | eobermuhlner@gmail.com 22 | 23 | 24 | 25 | scm:git:git://github.com/java-scriptengine.git 26 | scm:ssh://github.com:eobermuhlner/java-scriptengine.git 27 | https://github.com/eobermuhlner/java-scriptengine/ 28 | 29 | 30 | 31 | junit 32 | junit 33 | 4.13.1 34 | test 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'ch.obermuhlner.scriptengine.example' 2 | include 'ch.obermuhlner.scriptengine.jshell' 3 | 4 | --------------------------------------------------------------------------------