├── .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 | [](https://travis-ci.org/eobermuhlner/jshell-scriptengine)
2 | [](https://codecov.io/gh/eobermuhlner/jshell-scriptengine)
3 | [](https://sonarcloud.io/dashboard?id=jshell-scriptengine)
4 | [](https://github.com/eobermuhlner/jshell-scriptengine/issues)
5 | [](https://github.com/eobermuhlner/jshell-scriptengine/graphs/commit-activity)
6 | [](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 | 
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