Skip to content

Commit a24d513

Browse files
committed
Addressed minor gaps in notebook cell execution
- Correctly printing the runtime errors stacktrace - Handling null output stream callback - Performing lineEnding normalization only where required in cell setContent. - Minor performance improvements
1 parent 30ad28c commit a24d513

File tree

8 files changed

+139
-51
lines changed

8 files changed

+139
-51
lines changed

build.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@
6666
patches/dev-dependency-licenses.diff
6767
patches/nb-telemetry.diff
6868
patches/change-method-parameters-refactoring-qualified-names.diff
69-
patches/upgrade-lsp4j.diff
70-
patches/updated-show-input-params.diff
71-
patches/java-notebooks.diff
69+
patches/upgrade-lsp4j.diff
70+
patches/updated-show-input-params.diff
71+
patches/java-notebooks.diff
7272
</string>
7373
<filterchain>
7474
<tokenfilter delimoutput=" ">

nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CellState.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ public String getNotebookUri() {
8383
}
8484

8585
public void setContent(String newContent, int newVersion) throws InterruptedException, ExecutionException {
86-
String normalizedContent = NotebookUtils.normalizeLineEndings(newContent);
8786
VersionAwareContent currentContent = content.get();
8887

8988
if (currentContent.getVersion() != newVersion - 1) {
@@ -100,13 +99,14 @@ public void setContent(String newContent, int newVersion) throws InterruptedExce
10099
int receivedVersion = newCellState.getVersion();
101100

102101
if (receivedVersion > currentContent.getVersion()) {
103-
VersionAwareContent newVersionContent = new VersionAwareContent(newCellState.getText(), receivedVersion);
102+
VersionAwareContent newVersionContent = new VersionAwareContent(NotebookUtils.normalizeLineEndings(newCellState.getText()), receivedVersion);
104103
content.updateAndGet(current -> current != currentContent && receivedVersion <= current.getVersion() ? current : newVersionContent);
105104
} else {
106105
LOG.log(Level.WARNING, "Version mismatch: Received version to be greater than current version, received version: {0}, current version: {1}", new Object[]{receivedVersion, currentContent.getVersion()});
107106
}
108107
} else {
109-
VersionAwareContent newVersionContent = new VersionAwareContent(normalizedContent, newVersion);
108+
// newContent is already normalized during applyChanges
109+
VersionAwareContent newVersionContent = new VersionAwareContent(newContent, newVersion);
110110

111111
if (!content.compareAndSet(currentContent, newVersionContent)) {
112112
LOG.log(Level.WARNING, "Concurrent modification detected. Version expected: {0}, current: {1}", new Object[]{newVersion - 1, content.get().getVersion()});

nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
*/
1616
package org.netbeans.modules.nbcode.java.notebook;
1717

18-
import jdk.jshell.JShell;
19-
import jdk.jshell.SnippetEvent;
18+
import java.io.PrintWriter;
19+
import java.io.Writer;
2020
import java.util.ArrayList;
2121
import java.util.Collections;
2222
import java.util.List;
@@ -29,8 +29,11 @@
2929
import java.util.regex.Matcher;
3030
import java.util.regex.Pattern;
3131
import jdk.jshell.Diag;
32+
import jdk.jshell.EvalException;
33+
import jdk.jshell.JShell;
3234
import jdk.jshell.JShellException;
3335
import jdk.jshell.SourceCodeAnalysis;
36+
import jdk.jshell.SnippetEvent;
3437
import org.netbeans.modules.java.lsp.server.notebook.CellExecutionResult;
3538
import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams;
3639
import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams.Builder;
@@ -228,17 +231,98 @@ private List<String> getRuntimeErrors(SnippetEvent event) {
228231
JShellException jshellException = event.exception();
229232
if (jshellException != null) {
230233
String msg = jshellException.getMessage();
234+
boolean msgAdded = false;
231235
if (msg != null && !msg.isBlank()) {
232236
runtimeErrors.add(msg);
237+
msgAdded = true;
233238
}
234-
if (!jshellException.fillInStackTrace().toString().isBlank()) {
235-
runtimeErrors.add(jshellException.fillInStackTrace().toString());
239+
// Getting the exception stacktrace/details:
240+
// stacktrace for EvalException provides the exception that the snippet code generated
241+
// stacktrace for non-EvalException is not helpful as it is only internal details
242+
String stacktrace = jshellException instanceof EvalException
243+
? getStackTrace((EvalException) jshellException)
244+
: msgAdded ? "" : jshellException.toString();
245+
if (!stacktrace.isBlank()) {
246+
runtimeErrors.add(stacktrace);
236247
}
237248
}
238249

239250
return runtimeErrors;
240251
}
241252

253+
private String getStackTrace(EvalException exception) {
254+
return printStackTrace(null, exception).toString();
255+
}
256+
257+
private StringBuilder printStackTrace(StringBuilder output, EvalException exception) {
258+
StringBuilder sb = printStackTrace(output, (Throwable) exception);
259+
260+
return correctExceptionName(sb, 0, exception);
261+
}
262+
263+
private StringBuilder correctExceptionName(StringBuilder output, int startIndex, EvalException exception) {
264+
// EvalException has the peculiarity that it replaces the actual cause,
265+
// while retaining the name, stacktrace and subsequent causes.
266+
// This is unhelpful since it hides the actual exception in the output.
267+
// Note: jdk.internal.jshell.tool.JShellTool.displayEvalException() uses
268+
// elaborate code to perform the user-friendly printing on console.
269+
String actualName = exception.getExceptionClassName();
270+
String wrapperName = exception.getClass().getName();
271+
if (actualName != null && !wrapperName.equals(actualName)) {
272+
int foundAt = output.indexOf(wrapperName, startIndex);
273+
if (foundAt >= 0) {
274+
output.replace(foundAt, foundAt + wrapperName.length(), actualName);
275+
foundAt += actualName.length();
276+
if (foundAt < output.length()) {
277+
Throwable cause = exception;
278+
Throwable cycleDetector = cause;
279+
do {
280+
cause = cause.getCause();
281+
282+
if (cycleDetector != null) {
283+
// Check for loops in cause using a tortoise-hare detector.
284+
cycleDetector = cycleDetector.getCause();
285+
if (cycleDetector != null) {
286+
cycleDetector = cycleDetector.getCause();
287+
if (cycleDetector == cause) {
288+
cause = null; // Cycle has been detected; break
289+
}
290+
}
291+
}
292+
} while (cause != null && !(cause instanceof EvalException));
293+
if (cause != null) {
294+
correctExceptionName(output, foundAt, (EvalException) cause);
295+
}
296+
}
297+
}
298+
}
299+
return output;
300+
}
301+
302+
private StringBuilder printStackTrace(StringBuilder output, Throwable exception) {
303+
if (exception == null) {
304+
return output != null ? output : new StringBuilder(0);
305+
}
306+
StringBuilder sb = output != null ? output : new StringBuilder();
307+
PrintWriter stackWriter = new PrintWriter(new Writer() {
308+
@Override
309+
public void write(char[] cbuf, int off, int len) {
310+
sb.append(cbuf, off, len);
311+
}
312+
313+
@Override
314+
public void flush() {
315+
}
316+
317+
@Override
318+
public void close() {
319+
}
320+
});
321+
exception.printStackTrace(stackWriter);
322+
323+
return sb;
324+
}
325+
242326
private List<String> getSnippetValue(SnippetEvent event) {
243327
return event.value() != null ? List.of(event.value()) : Collections.emptyList();
244328
}
@@ -250,11 +334,11 @@ private void flushStreams(String notebookId) {
250334
}
251335
}
252336

253-
// This method is directly taken from JShell tool implementation in jdk with some minor modifications
337+
// Note: This method is taken from jdk.internal.jshell.tool.JShellTool with some simplifications
254338
private List<String> displayableDiagnostic(String source, Diag diag) {
255339
List<String> toDisplay = new ArrayList<>();
256340

257-
for (String line : diag.getMessage(null).split("\\r?\\n")) {
341+
for (String line : diag.getMessage(null).split("\\R")) {
258342
if (!line.trim().startsWith("location:")) {
259343
toDisplay.add(line);
260344
}

nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CustomInputStream.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,12 @@ public CustomInputStream(NbCodeLanguageClient client) {
4747
@Override
4848
public synchronized int read(byte[] b, int off, int len) throws IOException {
4949
try {
50-
NbCodeLanguageClient client = this.client.get();
51-
if (client == null) {
52-
LOG.log(Level.WARNING, "client is null");
53-
return -1;
54-
}
55-
5650
if (currentStream == null || currentStream.available() == 0) {
51+
NbCodeLanguageClient client = this.client.get();
52+
if (client == null) {
53+
LOG.log(Level.WARNING, "client is null");
54+
return -1;
55+
}
5756
CompletableFuture<String> future = client.showInputBox(new ShowInputBoxParams(USER_PROMPT_REQUEST, "", true));
5857
String userInput = future.get();
5958

nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/JshellStreamsHandler.java

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,6 @@ private Consumer<byte[]> createCallback(BiConsumer<String, byte[]> callback) {
6161
return callback != null ? output -> callback.accept(notebookId, output) : null;
6262
}
6363

64-
public void setOutStreamCallback(BiConsumer<String, byte[]> callback) {
65-
outStream.setCallback(createCallback(callback));
66-
}
67-
68-
public void setErrStreamCallback(BiConsumer<String, byte[]> callback) {
69-
errStream.setCallback(createCallback(callback));
70-
}
71-
7264
public PrintStream getPrintOutStream() {
7365
return printOutStream;
7466
}
@@ -89,37 +81,41 @@ public void flushOutputStreams() {
8981
try {
9082
outStream.flush();
9183
} catch (IOException exception) {
92-
LOG.log(Level.WARNING, "IOException occurred while flushing out stream: {0}", exception.getMessage());
84+
LOG.log(Level.WARNING, "IOException occurred while flushing out stream: {0}", exception.toString());
9385
}
9486
try {
9587
errStream.flush();
9688
} catch (IOException exception) {
97-
LOG.log(Level.WARNING, "IOException occurred while flushing error stream: {0}", exception.getMessage());
89+
LOG.log(Level.WARNING, "IOException occurred while flushing error stream: {0}", exception.toString());
9890
}
9991
}
10092

10193
@Override
10294
public void close() {
10395
try {
10496
printOutStream.close();
97+
} catch (Exception exception) {
98+
LOG.log(Level.WARNING, "Exception occurred while closing print out stream: {0}", exception.toString());
99+
}
100+
try {
105101
printErrStream.close();
106102
} catch (Exception exception) {
107-
LOG.log(Level.WARNING, "Exception occurred while closing print streams: {0}", exception.getMessage());
103+
LOG.log(Level.WARNING, "Exception occurred while closing print err stream: {0}", exception.toString());
108104
}
109105
try {
110106
outStream.close();
111107
} catch (IOException exception) {
112-
LOG.log(Level.WARNING, "IOException occurred while closing out stream: {0}", exception.getMessage());
108+
LOG.log(Level.WARNING, "IOException occurred while closing out stream: {0}", exception.toString());
113109
}
114110
try {
115111
errStream.close();
116112
} catch (IOException exception) {
117-
LOG.log(Level.WARNING, "IOException occurred while closing error stream: {0}", exception.getMessage());
113+
LOG.log(Level.WARNING, "IOException occurred while closing error stream: {0}", exception.toString());
118114
}
119115
try {
120116
inputStream.close();
121117
} catch (IOException exception) {
122-
LOG.log(Level.WARNING, "IOException occurred while closing input stream: {0}", exception.getMessage());
118+
LOG.log(Level.WARNING, "IOException occurred while closing input stream: {0}", exception.toString());
123119
}
124120
}
125121
}

nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentServiceHandlerImpl.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@
3535
import org.eclipse.lsp4j.CompletionList;
3636
import org.eclipse.lsp4j.CompletionParams;
3737
import org.eclipse.lsp4j.DefinitionParams;
38-
import org.eclipse.lsp4j.services.LanguageClient;
39-
import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
40-
import org.netbeans.modules.java.lsp.server.notebook.NotebookDocumentServiceHandler;
4138
import org.eclipse.lsp4j.DidChangeNotebookDocumentParams;
4239
import org.eclipse.lsp4j.DidCloseNotebookDocumentParams;
4340
import org.eclipse.lsp4j.DidOpenNotebookDocumentParams;
@@ -71,6 +68,9 @@
7168
import org.eclipse.lsp4j.WillSaveTextDocumentParams;
7269
import org.eclipse.lsp4j.jsonrpc.messages.Either;
7370
import org.eclipse.lsp4j.jsonrpc.messages.Either3;
71+
import org.eclipse.lsp4j.services.LanguageClient;
72+
import org.netbeans.modules.java.lsp.server.notebook.NotebookDocumentServiceHandler;
73+
import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
7474
import org.netbeans.modules.java.lsp.server.protocol.ShowStatusMessageParams;
7575
import org.openide.util.NbBundle;
7676
import org.openide.util.lookup.ServiceProvider;
@@ -133,8 +133,12 @@ public void didSave(DidSaveNotebookDocumentParams params) {
133133
public void didClose(DidCloseNotebookDocumentParams params) {
134134
String notebookUri = params.getNotebookDocument().getUri();
135135
NotebookSessionManager.getInstance().closeSession(notebookUri);
136-
notebookStateMap.remove(notebookUri);
137-
notebookCellMap.entrySet().removeIf(entry -> notebookUri.equals(entry.getValue()));
136+
NotebookDocumentStateManager state = notebookStateMap.remove(notebookUri);
137+
if (state != null) {
138+
state.getCellsMap().keySet().forEach(notebookCellMap::remove);
139+
} else {
140+
notebookCellMap.values().removeIf(notebookUri::equals);
141+
}
138142
}
139143

140144
@Override

nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/NotebookDocumentStateManager.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,14 @@ private void updateNotebookCellStructure(NotebookDocumentChangeEventCellStructur
121121
List<NotebookCell> cellsDetail = updatedStructure.getArray().getCells();
122122

123123
if (cellsItem != null && cellsDetail != null) {
124-
for (int i = 0; i < cellsDetail.size(); i++) {
125-
TextDocumentItem cellItem = cellsItem.get(i);
124+
Iterator<NotebookCell> details = cellsDetail.iterator();
125+
for (TextDocumentItem cellItem: cellsItem) {
126126
String uri = cellItem.getUri();
127127
openedCellUris.add(uri);
128-
129-
addNewCellState(cellsDetail.get(i), cellItem);
130128
cellsNotebookMap.put(uri, notebookDoc.getUri());
129+
if (details.hasNext()) {
130+
addNewCellState(details.next(), cellItem);
131+
}
131132
}
132133
}
133134

nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/StreamingOutputStream.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.io.OutputStream;
2121
import java.util.concurrent.atomic.AtomicBoolean;
2222
import java.util.function.Consumer;
23-
import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
2423
import org.openide.util.RequestProcessor;
2524
import org.openide.util.RequestProcessor.Task;
2625

@@ -31,9 +30,10 @@
3130
public class StreamingOutputStream extends OutputStream {
3231

3332
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
34-
private volatile Consumer<byte[]> callback;
33+
private final Consumer<byte[]> callback;
3534
private static final int MAX_BUFFER_SIZE = 1024;
3635
private final AtomicBoolean isPeriodicFlushOutputStream;
36+
private final boolean noop;
3737

3838
static RequestProcessor getRequestProcessor() {
3939
return RPSingleton.instance;
@@ -50,19 +50,22 @@ private static final class RPSingleton {
5050
}
5151

5252
public StreamingOutputStream(Consumer<byte[]> callback) {
53+
this.noop = callback == null;
5354
this.callback = callback;
54-
this.isPeriodicFlushOutputStream = new AtomicBoolean(true);
55+
this.isPeriodicFlushOutputStream = new AtomicBoolean(!noop);
5556
createAndScheduleTask();
5657
}
5758

5859
@Override
5960
public synchronized void write(int b) throws IOException {
61+
if (noop) return;
6062
buffer.write(b);
6163
ifBufferOverflowFlush();
6264
}
6365

6466
@Override
6567
public synchronized void write(byte[] b, int off, int len) throws IOException {
68+
if (noop) return;
6669
if (len >= MAX_BUFFER_SIZE) {
6770
flushToCallback();
6871
byte[] chunk = new byte[len];
@@ -76,33 +79,34 @@ public synchronized void write(byte[] b, int off, int len) throws IOException {
7679

7780
@Override
7881
public synchronized void flush() throws IOException {
82+
if (noop) return;
7983
flushToCallback();
8084
}
8185

8286
@Override
8387
public synchronized void write(byte[] b) throws IOException {
88+
if (noop) return;
8489
write(b, 0, b.length);
8590
}
8691

8792
@Override
8893
public synchronized void close() throws IOException {
89-
flushToCallback();
90-
isPeriodicFlushOutputStream.set(false);
94+
if (!noop) {
95+
flushToCallback();
96+
isPeriodicFlushOutputStream.set(false);
97+
}
9198
super.close();
9299
}
93100

94-
@NonNull
95-
public void setCallback(Consumer<byte[]> cb) {
96-
this.callback = cb;
97-
}
98-
99101
private void ifBufferOverflowFlush() {
102+
if (noop) return;
100103
if (buffer.size() > MAX_BUFFER_SIZE) {
101104
flushToCallback();
102105
}
103106
}
104107

105108
private synchronized void flushToCallback() {
109+
if (noop) return;
106110
if (buffer.size() > 0) {
107111
byte[] output = buffer.toByteArray();
108112
buffer.reset();

0 commit comments

Comments
 (0)