Skip to content

Commit 1e49b4c

Browse files
committed
feat: add JavaScript console and reinitialize action, enhance script processing to isolate per-project
1 parent 68f589b commit 1e49b4c

File tree

6 files changed

+501
-22
lines changed

6 files changed

+501
-22
lines changed

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ information.
2727
* Parsing and CSD import handle both single-line and multi-line modern declarations
2828
* Newly created UDOs and Effects default to application setting default (new setting in Program Settings for "Default UDO/Effects Style"); existing projects load as classic for backward compatibility
2929

30+
* Orchestra: added "Replace Instrument" action to context menu
31+
3032
### Updated
3133

3234
* Introduced new Time Unit system encompassing time signatures, measures, new tempo ruler, and new time units:

blue-core/src/main/java/blue/scripting/JavaScriptProxy.java

Lines changed: 209 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,29 @@
1313
* @author unascribed
1414
* @version 1.0
1515
*/
16+
import blue.BlueData;
1617
import blue.BlueSystem;
18+
import java.io.File;
19+
import java.io.IOException;
20+
import java.io.Reader;
21+
import java.io.Writer;
22+
import java.util.ArrayList;
23+
import java.util.Map;
24+
import java.util.WeakHashMap;
1725
import javax.script.ScriptContext;
1826
import javax.script.ScriptEngine;
1927
import javax.script.ScriptException;
2028
import org.netbeans.api.scripting.Scripting;
2129

2230
public class JavaScriptProxy {
2331

24-
private static ScriptEngine engine;
32+
private static final Map<BlueData, ProjectEngineState> projectEngineStates
33+
= new WeakHashMap<>();
2534

26-
static {
35+
private static ProjectEngineState defaultEngineState;
2736

28-
}
37+
private static final ArrayList<JavaScriptProxyListener> listeners
38+
= new ArrayList<>();
2939

3040
public static synchronized final void reinitialize() {
3141
// if (cx != null) {
@@ -34,20 +44,37 @@ public static synchronized final void reinitialize() {
3444
// cx = Context.enter();
3545
// scope = cx.initStandardObjects(null);
3646
// engine = new ScriptEngineManager().getEngineByName("graal.js");
37-
engine = Scripting.createManager().getEngineByMimeType("text/javascript");
38-
engine.getContext().setAttribute(ScriptEngine.FILENAME, "script.mjs", ScriptContext.ENGINE_SCOPE);
47+
ProjectEngineState state = createEngineState();
48+
BlueData currentData = BlueSystem.getCurrentBlueData();
49+
50+
if (currentData == null) {
51+
defaultEngineState = state;
52+
} else {
53+
projectEngineStates.put(currentData, state);
54+
}
55+
56+
ScriptContext context = state.engine.getContext();
57+
context.setAttribute(ScriptEngine.FILENAME, "script.mjs", ScriptContext.ENGINE_SCOPE);
3958
// Context.newBuilder("js").allowIO(true).currentWorkingDirectory(workingDirectory)
4059
System.out.println(BlueSystem.getString("scripting.js.reinitialized"));
60+
61+
for (JavaScriptProxyListener listener : listeners) {
62+
listener.javascriptProxyReinitializePerformed();
63+
}
64+
}
65+
66+
public static synchronized void addJavaScriptProxyListener(JavaScriptProxyListener listener) {
67+
listeners.add(listener);
68+
}
69+
70+
public static synchronized void removeJavaScriptProxyListener(JavaScriptProxyListener listener) {
71+
listeners.remove(listener);
4172
}
4273

4374
public static synchronized final String processJavascriptScore(String script,
4475
double subjectiveDuration, String soundObjectId) throws ScriptException {
45-
if (engine == null) {
46-
reinitialize();
47-
}
48-
49-
50-
String returnScore = "";
76+
ScriptEngine scriptEngine = getCurrentEngineState().engine;
77+
setProjectBindings(scriptEngine);
5178

5279
String init = "blueDuration = " + subjectiveDuration
5380
+ ";\n";
@@ -57,12 +84,12 @@ public static synchronized final String processJavascriptScore(String script,
5784
// engine.setBindings(bindings, ScriptContext.GLOBAL_SCOPE);
5885
//engine.setContext(context);
5986

60-
engine.eval(init);
61-
engine.eval(script);
87+
scriptEngine.eval(init);
88+
scriptEngine.eval(script);
6289

6390
// cx.evaluateString(scope, init, "init", 1, null);
6491
// cx.evaluateString(scope, script, soundObjectId, 1, null);
65-
var res = engine.get("score");
92+
var res = scriptEngine.get("score");
6693

6794
//
6895
//
@@ -75,11 +102,8 @@ public static synchronized final String processJavascriptScore(String script,
75102

76103
public static synchronized final String processJavascriptInstrument(String script,
77104
String instrumentId) throws ScriptException {
78-
if (engine == null) {
79-
reinitialize();
80-
}
81-
String returnInstrument = "";
82-
105+
ScriptEngine scriptEngine = getCurrentEngineState().engine;
106+
setProjectBindings(scriptEngine);
83107
String init = "instrument = '';\n";
84108

85109
// try {
@@ -96,11 +120,174 @@ public static synchronized final String processJavascriptInstrument(String scrip
96120
// } catch (JavaScriptException e) {
97121
// System.out.println(e.getLocalizedMessage());
98122
// }
99-
engine.eval(init);
100-
engine.eval(script);
101-
var res = engine.get("instrument");
123+
scriptEngine.eval(init);
124+
scriptEngine.eval(script);
125+
var res = scriptEngine.get("instrument");
102126
return java.util.Objects.toString(res, null);
103127

104128
}
105129

130+
public static synchronized Object processScript(String script, Reader stdin,
131+
Writer stdout, Writer stderr) throws ScriptException {
132+
ProjectEngineState state = getCurrentEngineState();
133+
ScriptEngine scriptEngine = state.engine;
134+
setProjectBindings(scriptEngine);
135+
state.consoleReader.setDelegate(stdin);
136+
state.consoleWriter.setDelegate(stdout);
137+
state.consoleErrorWriter.setDelegate(stderr);
138+
139+
try {
140+
return scriptEngine.eval(script);
141+
} finally {
142+
state.consoleReader.clearDelegate();
143+
state.consoleWriter.clearDelegate();
144+
state.consoleErrorWriter.clearDelegate();
145+
}
146+
}
147+
148+
private static ProjectEngineState getCurrentEngineState() {
149+
BlueData currentData = BlueSystem.getCurrentBlueData();
150+
151+
if (currentData == null) {
152+
if (defaultEngineState == null) {
153+
defaultEngineState = createEngineState();
154+
}
155+
156+
return defaultEngineState;
157+
}
158+
159+
ProjectEngineState state = projectEngineStates.get(currentData);
160+
161+
if (state == null) {
162+
state = createEngineState();
163+
projectEngineStates.put(currentData, state);
164+
}
165+
166+
return state;
167+
}
168+
169+
private static ProjectEngineState createEngineState() {
170+
ScriptEngine engine = Scripting.createManager().getEngineByMimeType("text/javascript");
171+
ProjectEngineState state = new ProjectEngineState(engine);
172+
installConsoleIo(state, engine.getContext());
173+
return state;
174+
}
175+
176+
private static void installConsoleIo(ProjectEngineState state, ScriptContext context) {
177+
state.consoleReader.clearDelegate();
178+
state.consoleWriter.clearDelegate();
179+
state.consoleErrorWriter.clearDelegate();
180+
181+
state.consoleReader.setFallback(context.getReader());
182+
state.consoleWriter.setFallback(context.getWriter());
183+
state.consoleErrorWriter.setFallback(context.getErrorWriter());
184+
185+
context.setReader(state.consoleReader);
186+
context.setWriter(state.consoleWriter);
187+
context.setErrorWriter(state.consoleErrorWriter);
188+
}
189+
190+
private static void setProjectBindings(ScriptEngine scriptEngine) {
191+
File currentDirFile = BlueSystem.getCurrentProjectDirectory();
192+
String currentDir = currentDirFile == null ? ""
193+
: currentDirFile.getAbsolutePath() + File.separator;
194+
195+
scriptEngine.put("blueData", BlueSystem.getCurrentBlueData());
196+
scriptEngine.put("blueProjectDir", currentDir);
197+
}
198+
199+
private static final class ProjectEngineState {
200+
201+
private final ScriptEngine engine;
202+
203+
private final DelegatingReader consoleReader = new DelegatingReader();
204+
205+
private final DelegatingWriter consoleWriter = new DelegatingWriter();
206+
207+
private final DelegatingWriter consoleErrorWriter = new DelegatingWriter();
208+
209+
private ProjectEngineState(ScriptEngine engine) {
210+
this.engine = engine;
211+
}
212+
}
213+
214+
private static final class DelegatingReader extends Reader {
215+
216+
private Reader fallback;
217+
218+
private Reader delegate;
219+
220+
synchronized void setFallback(Reader fallback) {
221+
this.fallback = fallback;
222+
}
223+
224+
synchronized void setDelegate(Reader delegate) {
225+
this.delegate = delegate;
226+
}
227+
228+
synchronized void clearDelegate() {
229+
this.delegate = null;
230+
}
231+
232+
private synchronized Reader current() {
233+
return delegate != null ? delegate : fallback;
234+
}
235+
236+
@Override
237+
public int read(char[] cbuf, int off, int len) throws IOException {
238+
Reader reader = current();
239+
return reader == null ? -1 : reader.read(cbuf, off, len);
240+
}
241+
242+
@Override
243+
public void close() {
244+
}
245+
}
246+
247+
private static final class DelegatingWriter extends Writer {
248+
249+
private Writer fallback;
250+
251+
private Writer delegate;
252+
253+
synchronized void setFallback(Writer fallback) {
254+
this.fallback = fallback;
255+
}
256+
257+
synchronized void setDelegate(Writer delegate) {
258+
this.delegate = delegate;
259+
}
260+
261+
synchronized void clearDelegate() {
262+
this.delegate = null;
263+
}
264+
265+
private synchronized Writer current() {
266+
return delegate != null ? delegate : fallback;
267+
}
268+
269+
@Override
270+
public void write(char[] cbuf, int off, int len) throws IOException {
271+
Writer writer = current();
272+
273+
if (writer != null) {
274+
writer.write(cbuf, off, len);
275+
}
276+
}
277+
278+
@Override
279+
public void flush() throws IOException {
280+
Writer writer = current();
281+
282+
if (writer != null) {
283+
writer.flush();
284+
}
285+
}
286+
287+
@Override
288+
public void close() throws IOException {
289+
flush();
290+
}
291+
}
292+
106293
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package blue.scripting;
2+
3+
public interface JavaScriptProxyListener {
4+
void javascriptProxyReinitializePerformed();
5+
}

blue-core/src/test/java/blue/scripting/JavaScriptProxyTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
*/
55
package blue.scripting;
66

7+
import blue.BlueData;
8+
import blue.BlueSystem;
9+
import java.io.StringReader;
10+
import java.io.StringWriter;
711
import javax.script.ScriptException;
812
import org.junit.jupiter.api.AfterAll;
913
import org.junit.jupiter.api.AfterEach;
@@ -34,10 +38,14 @@ public static void tearDownClass() throws Exception {
3438

3539
@BeforeEach
3640
void setUp() {
41+
BlueSystem.setCurrentBlueData(new BlueData());
42+
BlueSystem.setCurrentProjectDirectory(null);
3743
}
3844

3945
@AfterEach
4046
void tearDown() {
47+
BlueSystem.setCurrentBlueData(null);
48+
BlueSystem.setCurrentProjectDirectory(null);
4149
}
4250

4351
/**
@@ -76,6 +84,53 @@ void testProcessJavascriptScore() throws ScriptException {
7684
"score += '\\nhi\\n'", 0.0f, "unit test 3"));
7785
}
7886

87+
@Test
88+
void testProcessScriptPreservesSharedEngineState() throws Exception {
89+
JavaScriptProxy.reinitialize();
90+
91+
StringWriter stdout = new StringWriter();
92+
StringWriter stderr = new StringWriter();
93+
94+
Object result = JavaScriptProxy.processScript("var counter = 2; counter;",
95+
new StringReader(""), stdout, stderr);
96+
97+
assertEquals("2", String.valueOf(result));
98+
assertEquals("", stderr.toString());
99+
100+
stdout.getBuffer().setLength(0);
101+
result = JavaScriptProxy.processScript("counter += 5; counter;",
102+
new StringReader(""), stdout, stderr);
103+
104+
assertEquals("7", String.valueOf(result));
105+
assertEquals("", stderr.toString());
106+
}
107+
108+
@Test
109+
void testProcessScriptUsesPerProjectEngineState() throws Exception {
110+
BlueData projectOne = new BlueData();
111+
BlueData projectTwo = new BlueData();
112+
113+
BlueSystem.setCurrentBlueData(projectOne);
114+
JavaScriptProxy.reinitialize();
115+
assertEquals("p1", String.valueOf(JavaScriptProxy.processScript(
116+
"var projectValue = 'p1'; projectValue;",
117+
new StringReader(""), new StringWriter(), new StringWriter())));
118+
119+
BlueSystem.setCurrentBlueData(projectTwo);
120+
JavaScriptProxy.reinitialize();
121+
assertEquals("undefined", String.valueOf(JavaScriptProxy.processScript(
122+
"typeof projectValue;",
123+
new StringReader(""), new StringWriter(), new StringWriter())));
124+
assertEquals("p2", String.valueOf(JavaScriptProxy.processScript(
125+
"var projectValue = 'p2'; projectValue;",
126+
new StringReader(""), new StringWriter(), new StringWriter())));
127+
128+
BlueSystem.setCurrentBlueData(projectOne);
129+
assertEquals("p1", String.valueOf(JavaScriptProxy.processScript(
130+
"projectValue;",
131+
new StringReader(""), new StringWriter(), new StringWriter())));
132+
}
133+
79134
/**
80135
* Test of processJavascriptInstrument method, of class RhinoProxy.
81136
*/

0 commit comments

Comments
 (0)