Skip to content

Commit 2884b5b

Browse files
committed
first version of new feature: display errors in error tab
1 parent 0b4f538 commit 2884b5b

File tree

5 files changed

+362
-15
lines changed

5 files changed

+362
-15
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.openstreetmap.josm.plugins.scripting.ui;
2+
3+
import javax.validation.constraints.NotNull;
4+
5+
public interface IScriptErrorHandler {
6+
7+
/**
8+
* Callback for {@link ScriptExecutor}. An implementer to log or
9+
* display the exception according to his context.
10+
*
11+
* @param exception the exception
12+
*/
13+
void handleScriptExecutionError(@NotNull final Throwable exception);
14+
}

src/main/java/org/openstreetmap/josm/plugins/scripting/ui/ScriptExecutor.java

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import javax.script.ScriptException;
2121
import javax.swing.*;
2222
import javax.validation.constraints.NotNull;
23+
import javax.validation.constraints.Null;
2324
import java.awt.*;
2425
import java.io.*;
2526
import java.lang.reflect.InvocationTargetException;
@@ -252,15 +253,30 @@ void runScriptWithPluggedEngine(
252253
}
253254

254255
/**
255-
* <p>Runs the script <tt>script</tt> using the script engine described
256-
* in <tt>desc</tt> on the Swing EDT.</p>
256+
* Runs the script <tt>script</tt> using the script engine described
257+
* in <tt>desc</tt> on the Swing EDT.
257258
*
258259
* @param desc the script engine descriptor. Must not be null.
259260
* @param script the script. Ignored if null.
260261
*/
261262
public void runScriptWithPluggedEngine(
262263
@NotNull final ScriptEngineDescriptor desc,
263264
final String script) {
265+
runScriptWithPluggedEngine(desc, script, null /* no error handler */);
266+
}
267+
268+
/**
269+
* Runs the script <tt>script</tt> using the script engine described
270+
* in <tt>desc</tt> on the Swing EDT.
271+
*
272+
* @param desc the script engine descriptor. Must not be null.
273+
* @param script the script. Ignored if null.
274+
* @param handler the error handler
275+
*/
276+
public void runScriptWithPluggedEngine(
277+
@NotNull final ScriptEngineDescriptor desc,
278+
final String script,
279+
final IScriptErrorHandler handler) {
264280
Objects.requireNonNull(desc);
265281
if (script == null) return;
266282
final ScriptEngine engine = JSR223ScriptEngineProvider.getInstance()
@@ -273,12 +289,17 @@ public void runScriptWithPluggedEngine(
273289
try {
274290
engine.eval(script);
275291
} catch(ScriptException e){
276-
warnExecutingScriptFailed(e);
292+
if (handler != null) {
293+
handler.handleScriptExecutionError(e);
294+
} else {
295+
warnExecutingScriptFailed(e);
296+
}
277297
}
278298
};
279299
runOnSwingEDT(task);
280300
}
281301

302+
282303
/**
283304
* Runs a script with a GraalVM engine.
284305
*
@@ -291,6 +312,23 @@ public void runScriptWithPluggedEngine(
291312
public void runScriptWithGraalEngine(
292313
@NotNull final ScriptEngineDescriptor desc,
293314
final String script) {
315+
runScriptWithGraalEngine(desc, script, null /* no error handler */);
316+
}
317+
318+
/**
319+
* Runs a script with a GraalVM engine.
320+
*
321+
* Runs the script on the Swing EDT. Handling errors is delegated to
322+
* <code>errorHandler</code>.
323+
*
324+
* @param desc the descriptor
325+
* @param script the script
326+
* @param errorHandler callback to handle errors
327+
*/
328+
public void runScriptWithGraalEngine(
329+
@NotNull final ScriptEngineDescriptor desc,
330+
final String script,
331+
final @Null IScriptErrorHandler errorHandler) {
294332
Objects.requireNonNull(desc);
295333
if (!desc.getEngineType().equals(
296334
ScriptEngineDescriptor.ScriptEngineType.GRAALVM)) {
@@ -311,8 +349,18 @@ public void runScriptWithGraalEngine(
311349
try {
312350
facade.resetContext();
313351
facade.eval(desc, script);
314-
} catch(GraalVMEvalException e){
315-
warnExecutingScriptFailed(e);
352+
} catch(GraalVMEvalException e) {
353+
if (errorHandler == null) {
354+
warnExecutingScriptFailed(e);
355+
} else {
356+
errorHandler.handleScriptExecutionError(e);
357+
}
358+
} catch(Throwable t) {
359+
if (errorHandler != null){
360+
errorHandler.handleScriptExecutionError(t);
361+
} else {
362+
throw t;
363+
}
316364
} finally {
317365
facade.resetContext();
318366
}
@@ -367,20 +415,43 @@ public void runScriptWithEmbeddedEngine(@NotNull final File scriptFile)
367415
* @param script the script. Ignored if null.
368416
*/
369417
public void runScriptWithEmbeddedEngine(final String script) {
418+
runScriptWithEmbeddedEngine(script, null /* no error handler */);
419+
}
420+
421+
/**
422+
* Runs the script <tt>script</tt> using the embedded scripting engine
423+
* on the Swing EDT.
424+
*
425+
* @param script the script. Ignored if null.
426+
* @param handler the handler
427+
*/
428+
public void runScriptWithEmbeddedEngine(final String script, final IScriptErrorHandler handler) {
370429
if (script == null) return;
371430
try {
372431
RhinoEngine engine = RhinoEngine.getInstance();
373432
engine.enterSwingThreadContext();
374433
engine.evaluateOnSwingThread(script);
375434
} catch(JavaScriptException e){
376435
logger.log(Level.SEVERE, "failed to execute script", e);
377-
warnJavaScriptExceptionCaught(e);
378-
} catch(RhinoException e){
436+
if (handler != null) {
437+
handler.handleScriptExecutionError(e);
438+
} else {
439+
warnJavaScriptExceptionCaught(e);
440+
}
441+
} catch(RhinoException e) {
379442
logger.log(Level.SEVERE, "failed to execute script", e);
380-
notifyRhinoException(e);
443+
if (handler != null) {
444+
handler.handleScriptExecutionError(e);
445+
} else {
446+
notifyRhinoException(e);
447+
}
381448
} catch(RuntimeException e){
382449
logger.log(Level.SEVERE, "failed to execute script", e);
383-
notifyRuntimeException(e);
450+
if (handler != null) {
451+
handler.handleScriptExecutionError(e);
452+
} else {
453+
notifyRuntimeException(e);
454+
}
384455
}
385456
}
386457
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package org.openstreetmap.josm.plugins.scripting.ui.console;
2+
3+
import org.openstreetmap.josm.plugins.scripting.ui.IScriptErrorHandler;
4+
5+
import javax.validation.constraints.NotNull;
6+
import javax.validation.constraints.Null;
7+
import java.beans.PropertyChangeEvent;
8+
import java.beans.PropertyChangeListener;
9+
import java.beans.PropertyChangeSupport;
10+
11+
public class ErrorModel implements IScriptErrorHandler {
12+
/**
13+
* The name of the error property
14+
*/
15+
public static final String PROP_ERROR = ErrorModel.class.getName() + ".errorChanged";
16+
17+
private static ErrorModel instance;
18+
19+
private Throwable error = null;
20+
21+
/**
22+
* Replies the unique instance of the error model
23+
*
24+
* @return the error model
25+
*/
26+
public static @NotNull ErrorModel getInstance() {
27+
if (instance == null) {
28+
instance = new ErrorModel();
29+
}
30+
return instance;
31+
}
32+
33+
final private PropertyChangeSupport support = new PropertyChangeSupport(this);
34+
35+
/**
36+
* Add a property change listener
37+
*
38+
* @param listener the listener
39+
*/
40+
public void addPropertyChangeListener(final PropertyChangeListener listener) {
41+
if (listener == null) {
42+
return;
43+
}
44+
support.addPropertyChangeListener(listener);
45+
}
46+
47+
/**
48+
* Remove a property change listener
49+
*
50+
* @param listener the listener
51+
*/
52+
public void removePropertyChangeListener(final PropertyChangeListener listener) {
53+
if (listener == null) {
54+
return;
55+
}
56+
support.removePropertyChangeListener(listener);
57+
}
58+
59+
protected void fireErrorChanged(Throwable oldError, Throwable newError) {
60+
if (oldError == null && newError == null) {
61+
// nothing changed, don't fire an event
62+
return;
63+
} else if (oldError != null && newError != null) {
64+
if (oldError.equals(newError)) {
65+
// nothing changed, don't fire an event
66+
return;
67+
}
68+
}
69+
final var event = new PropertyChangeEvent(
70+
this,
71+
PROP_ERROR,
72+
oldError,
73+
newError
74+
);
75+
support.firePropertyChange(event);
76+
}
77+
78+
private ErrorModel() {
79+
}
80+
81+
/**
82+
* Sets the current error
83+
*
84+
* @param error the error
85+
*/
86+
public void setError(@Null Throwable error) {
87+
final var oldError = this.error;
88+
this.error = error;
89+
fireErrorChanged(oldError, this.error);
90+
}
91+
92+
/**
93+
* Clears the current error
94+
*/
95+
public void clearError() {
96+
setError(null);
97+
}
98+
99+
/* -------------------------------------------------------------------- */
100+
/* IScriptErrorHandler */
101+
/* -------------------------------------------------------------------- */
102+
@Override
103+
public void handleScriptExecutionError(Throwable exception) {
104+
setError(exception);
105+
}
106+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package org.openstreetmap.josm.plugins.scripting.ui.console;
2+
3+
4+
import org.graalvm.polyglot.PolyglotException;
5+
import org.openstreetmap.josm.plugins.scripting.graalvm.GraalVMFacadeFactory;
6+
7+
import javax.swing.*;
8+
import javax.validation.constraints.NotNull;
9+
import javax.validation.constraints.Null;
10+
import java.awt.*;
11+
import java.io.PrintWriter;
12+
import java.io.StringWriter;
13+
import java.text.MessageFormat;
14+
import java.util.Arrays;
15+
import java.util.stream.Collectors;
16+
17+
/**
18+
* Displays errors when the execution of a script fails
19+
*/
20+
public class ErrorOutputPanel extends JPanel {
21+
22+
private JTextPane paneOutput;
23+
private JScrollPane editorScrollPane;
24+
25+
public ErrorOutputPanel() {
26+
build();
27+
}
28+
29+
protected void build() {
30+
setLayout(new BorderLayout());
31+
paneOutput = new JTextPane();
32+
paneOutput.setEditable(false);
33+
editorScrollPane = new JScrollPane(paneOutput);
34+
editorScrollPane.setVerticalScrollBarPolicy(
35+
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
36+
editorScrollPane.setHorizontalScrollBarPolicy(
37+
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
38+
add(editorScrollPane, BorderLayout.CENTER);
39+
}
40+
41+
protected void displayPolyglotException(PolyglotException exception) {
42+
paneOutput.setText(formatPolyglotException(exception));
43+
}
44+
45+
protected void displayGeneralException(Throwable exception) {
46+
final var builder = new StringBuilder();
47+
builder.append(exception.getMessage());
48+
builder.append("\n");
49+
final var writer = new StringWriter();
50+
exception.printStackTrace(new PrintWriter(writer));
51+
builder.append(writer.getBuffer());
52+
paneOutput.setText(builder.toString()); }
53+
54+
55+
/**
56+
* Displays an exception
57+
*
58+
* @param exception the exception
59+
*/
60+
public void displayException(@NotNull Throwable exception) {
61+
if (GraalVMFacadeFactory.isGraalVMPresent()) {
62+
final var polyglotException = lookupPolyglotException(exception);
63+
if (polyglotException != null) {
64+
displayPolyglotException(polyglotException);
65+
return;
66+
}
67+
}
68+
displayGeneralException(exception);
69+
// scroll to top
70+
paneOutput.setCaretPosition(0);
71+
}
72+
73+
protected @Null PolyglotException lookupPolyglotException(Throwable t) {
74+
while(t != null) {
75+
if (t instanceof PolyglotException) {
76+
break;
77+
}
78+
t = t.getCause();
79+
}
80+
if (t == null) {
81+
return null;
82+
} else {
83+
return (PolyglotException) t;
84+
}
85+
}
86+
87+
protected String formatPolyglotException(@NotNull final PolyglotException exception) {
88+
return exception.getMessage() +
89+
"\n" +
90+
Arrays.stream(exception.getStackTrace())
91+
.filter(element -> element.getClassName().contains("<js>"))
92+
.map(element -> MessageFormat.format(
93+
"{0}: line={1}",
94+
element.getFileName(),
95+
element.getLineNumber()
96+
))
97+
.collect(Collectors.joining("\n"));
98+
}
99+
}

0 commit comments

Comments
 (0)