@abreen/tada 1.13.9 → 1.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/build/generate-content-assets.ts +2 -10
- package/build/types.d.ts +18 -6
- package/build/utils/html-comments.ts +3 -0
- package/build/utils/jdi-runner/TraceRunner.java +227 -38
- package/build/utils/markdown-partials.ts +235 -0
- package/build/utils/markdown.ts +12 -1
- package/build/utils/python-runner/trace_runner.py +58 -13
- package/build/utils/render.ts +14 -12
- package/build/utils/trace-core.ts +136 -14
- package/build/utils/trace-heap.ts +34 -0
- package/build/utils/trace-java.ts +60 -7
- package/build/utils/trace-layout.ts +58 -13
- package/build/utils/trace-python.ts +6 -2
- package/build/utils/trace-svg.ts +54 -60
- package/build/utils/trace.ts +193 -78
- package/build/watch/compiler-types.d.ts +2 -2
- package/build/watch/compiler.ts +5 -1
- package/init/content/lectures/02/_pr1.md +1 -1
- package/init/content/lectures/02/index.md +2 -2
- package/init/content/lectures/02/subdir/_foobar.md +3 -0
- package/init/content/lectures/02/subdir/_pr2.md +3 -3
- package/package.json +1 -1
- package/src/slides/index.ts +6 -2
- package/src/trace/index.ts +49 -12
- package/src/trace/style.scss +10 -0
- package/src/trace/types.d.ts +16 -4
- package/build/utils/include.ts +0 -57
- package/init/content/lectures/02/subdir/_foobar.html +0 -3
package/README.md
CHANGED
|
@@ -40,6 +40,7 @@ A static site generator. The successor to Presto.
|
|
|
40
40
|
* `??? question ... ???` renders a Q & A section; answers hidden until click
|
|
41
41
|
* `+++ ... +++ ... +++` renders a two-column layout
|
|
42
42
|
* Special heading subtitles with `## Heading # A subtitle here`
|
|
43
|
+
* `{{{ _partial.md }}}` syntax for including partials
|
|
43
44
|
- Automatically generated favicon
|
|
44
45
|
* Text, color, font and font weight taken from config file
|
|
45
46
|
|
|
@@ -333,6 +334,51 @@ templates][lodash].
|
|
|
333
334
|
- Custom variables from the `"vars"` property of the config are available
|
|
334
335
|
under `vars` (e.g., `<%= vars.staffEmail %>`)
|
|
335
336
|
|
|
337
|
+
### Markdown partials
|
|
338
|
+
|
|
339
|
+
Use the `{{{ _partial.md }}}` syntax to include a partial (a fragment of
|
|
340
|
+
Markdown in a separate file whose name must start with `_`) at that particular
|
|
341
|
+
location in a Markdown file.
|
|
342
|
+
|
|
343
|
+
This is useful for reducing duplication across multiple documents which use the
|
|
344
|
+
exact same content, or for breaking up a large document into building blocks
|
|
345
|
+
which are easier to edit.
|
|
346
|
+
|
|
347
|
+
Partials are never built into their own pages, and they don't have front matter.
|
|
348
|
+
|
|
349
|
+
The `{{{ ... }}}` syntax is not a simple string replacement; instead, the
|
|
350
|
+
Markdown syntax tree of the partial is *added* to the syntax tree of the
|
|
351
|
+
Markdown file that includes it. This is a powerful feature because it allows you
|
|
352
|
+
to include content as a subtree.
|
|
353
|
+
|
|
354
|
+
For example, this partial `_groceries.md`
|
|
355
|
+
|
|
356
|
+
```
|
|
357
|
+
* apples
|
|
358
|
+
* oranges
|
|
359
|
+
* pears
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
can be inserted as a sub-list of a larger list in a Markdown page
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
title: To do list
|
|
366
|
+
|
|
367
|
+
* Grocery shopping
|
|
368
|
+
{{{ _groceries.md }}}
|
|
369
|
+
* Car wash
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
which results in:
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
* Grocery shopping
|
|
376
|
+
* apples
|
|
377
|
+
* oranges
|
|
378
|
+
* pears
|
|
379
|
+
* Car wash
|
|
380
|
+
```
|
|
381
|
+
|
|
336
382
|
|
|
337
383
|
[inter]: https://fonts.google.com/specimen/Inter
|
|
338
384
|
[lodash]: https://lodash.info/doc/template
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
HtmlOutputAnalysis,
|
|
15
15
|
TraceToolAvailability,
|
|
16
16
|
} from './types';
|
|
17
|
+
import type { TraceCache } from './watch/compiler-types';
|
|
17
18
|
|
|
18
19
|
const log = makeLogger(import.meta.url);
|
|
19
20
|
|
|
@@ -37,16 +38,7 @@ function writeRecordOutputs(
|
|
|
37
38
|
export class ContentRenderer {
|
|
38
39
|
private siteVariables: SiteVariables;
|
|
39
40
|
private traceToolAvailability: TraceToolAvailability | undefined;
|
|
40
|
-
private traceCache: Map
|
|
41
|
-
string,
|
|
42
|
-
{
|
|
43
|
-
manifestUrl: string;
|
|
44
|
-
artifactId: string;
|
|
45
|
-
highlightedSource: string;
|
|
46
|
-
totalSteps: number;
|
|
47
|
-
mtime: number;
|
|
48
|
-
}
|
|
49
|
-
> = new Map();
|
|
41
|
+
private traceCache: TraceCache = new Map();
|
|
50
42
|
|
|
51
43
|
constructor(siteVariables: SiteVariables) {
|
|
52
44
|
this.siteVariables = siteVariables;
|
package/build/types.d.ts
CHANGED
|
@@ -86,9 +86,9 @@ export interface RenderPlainTextOptions {
|
|
|
86
86
|
{
|
|
87
87
|
manifestUrl: string;
|
|
88
88
|
artifactId: string;
|
|
89
|
-
highlightedSource: string;
|
|
89
|
+
highlightedSources: { file: string; highlightedSource: string }[];
|
|
90
90
|
totalSteps: number;
|
|
91
|
-
|
|
91
|
+
sourceMtims: Record<string, number>;
|
|
92
92
|
}
|
|
93
93
|
>;
|
|
94
94
|
traceToolAvailability?: TraceToolAvailability;
|
|
@@ -206,7 +206,13 @@ export interface TraceStep {
|
|
|
206
206
|
file: string;
|
|
207
207
|
stack: TraceStackFrame[];
|
|
208
208
|
heap: Record<string, TraceHeapObject>;
|
|
209
|
-
|
|
209
|
+
output?: TraceOutputEvent[];
|
|
210
|
+
transientHeapRoots?: string[];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface TraceOutputEvent {
|
|
214
|
+
stream: 'stdout' | 'stderr';
|
|
215
|
+
text: string;
|
|
210
216
|
}
|
|
211
217
|
|
|
212
218
|
export interface TraceStackFrame {
|
|
@@ -232,13 +238,18 @@ export type TraceValue =
|
|
|
232
238
|
export type TraceHeapObject =
|
|
233
239
|
| { type: string; elements: TraceValue[] }
|
|
234
240
|
| { type: string; fields: Record<string, TraceValue> }
|
|
235
|
-
| { type: string; value: string };
|
|
241
|
+
| { type: string; value: string | number | boolean };
|
|
236
242
|
|
|
237
243
|
/** Manifest written alongside trace chunks */
|
|
238
244
|
export interface TraceManifest {
|
|
239
245
|
totalSteps: number;
|
|
240
246
|
chunkSize: number;
|
|
241
|
-
|
|
247
|
+
primaryFile: string;
|
|
248
|
+
sources: TraceSource[];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface TraceSource {
|
|
252
|
+
file: string;
|
|
242
253
|
source: string;
|
|
243
254
|
lineToSteps: Record<number, number[]>;
|
|
244
255
|
}
|
|
@@ -267,8 +278,9 @@ export interface TraceLayout {
|
|
|
267
278
|
|
|
268
279
|
/** A single entry in a chunk file (new format with precomputed SVG). */
|
|
269
280
|
export interface TraceChunkEntry {
|
|
281
|
+
file: string;
|
|
270
282
|
line: number;
|
|
271
|
-
|
|
283
|
+
output?: TraceOutputEvent[];
|
|
272
284
|
svg: string;
|
|
273
285
|
}
|
|
274
286
|
|
|
@@ -3,6 +3,7 @@ import com.sun.jdi.connect.*;
|
|
|
3
3
|
import com.sun.jdi.event.*;
|
|
4
4
|
import com.sun.jdi.request.*;
|
|
5
5
|
import java.io.*;
|
|
6
|
+
import java.nio.charset.StandardCharsets;
|
|
6
7
|
import java.util.*;
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -17,7 +18,10 @@ public class TraceRunner {
|
|
|
17
18
|
|
|
18
19
|
private static final Map<Long, String> objectIdMap = new HashMap<>();
|
|
19
20
|
private static long nextObjId = 1;
|
|
20
|
-
private static
|
|
21
|
+
private static final List<OutputEvent> outputEvents = new ArrayList<>();
|
|
22
|
+
private static final Set<String> unnamedClassNames = new HashSet<>();
|
|
23
|
+
|
|
24
|
+
private record OutputEvent(String stream, String text) {}
|
|
21
25
|
|
|
22
26
|
public static void main(String[] args) throws Exception {
|
|
23
27
|
if (args.length < 2) {
|
|
@@ -27,6 +31,9 @@ public class TraceRunner {
|
|
|
27
31
|
|
|
28
32
|
String className = args[0];
|
|
29
33
|
String classPath = args[1];
|
|
34
|
+
for (int i = 2; i < args.length; i++) {
|
|
35
|
+
unnamedClassNames.add(args[i]);
|
|
36
|
+
}
|
|
30
37
|
|
|
31
38
|
LaunchingConnector connector =
|
|
32
39
|
Bootstrap.virtualMachineManager().defaultConnector();
|
|
@@ -39,27 +46,31 @@ public class TraceRunner {
|
|
|
39
46
|
InputStream targetStdout = process.getInputStream();
|
|
40
47
|
InputStream targetStderr = process.getErrorStream();
|
|
41
48
|
|
|
42
|
-
Thread
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
} catch (IOException ignored) {
|
|
46
|
-
}
|
|
47
|
-
});
|
|
49
|
+
Thread stdoutThread = new Thread(() -> readStream("stdout", targetStdout));
|
|
50
|
+
Thread stderrThread = new Thread(() -> readStream("stderr", targetStderr));
|
|
51
|
+
stdoutThread.setDaemon(true);
|
|
48
52
|
stderrThread.setDaemon(true);
|
|
53
|
+
stdoutThread.start();
|
|
49
54
|
stderrThread.start();
|
|
50
55
|
|
|
51
56
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
EventRequestManager requests = vm.eventRequestManager();
|
|
58
|
+
ClassPrepareRequest cpr = requests.createClassPrepareRequest();
|
|
54
59
|
cpr.addClassFilter(className);
|
|
55
60
|
cpr.setSuspendPolicy(EventRequest.SUSPEND_ALL);
|
|
56
61
|
cpr.enable();
|
|
57
62
|
|
|
63
|
+
ExceptionRequest exceptionRequest =
|
|
64
|
+
requests.createExceptionRequest(null, false, true);
|
|
65
|
+
exceptionRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
|
|
66
|
+
exceptionRequest.enable();
|
|
67
|
+
|
|
58
68
|
vm.eventQueue().remove().resume(); // consume VMStartEvent
|
|
59
69
|
|
|
60
70
|
boolean running = true;
|
|
61
71
|
while (running) {
|
|
62
72
|
EventSet eventSet = vm.eventQueue().remove();
|
|
73
|
+
boolean resumed = false;
|
|
63
74
|
for (Event event : eventSet) {
|
|
64
75
|
if (event instanceof ClassPrepareEvent cpe) {
|
|
65
76
|
Method mainMethod = findMainMethod(cpe.referenceType());
|
|
@@ -75,7 +86,7 @@ public class TraceRunner {
|
|
|
75
86
|
cpr.disable();
|
|
76
87
|
|
|
77
88
|
} else if (event instanceof BreakpointEvent bpe) {
|
|
78
|
-
emitState(bpe.thread(),
|
|
89
|
+
emitState(bpe.thread(), drainOutputEventsJson());
|
|
79
90
|
|
|
80
91
|
var step = vm.eventRequestManager().createStepRequest(
|
|
81
92
|
bpe.thread(),
|
|
@@ -91,14 +102,31 @@ public class TraceRunner {
|
|
|
91
102
|
bpe.request().disable();
|
|
92
103
|
|
|
93
104
|
} else if (event instanceof StepEvent se) {
|
|
94
|
-
emitState(se.thread(),
|
|
105
|
+
emitState(se.thread(), drainOutputEventsJson());
|
|
106
|
+
|
|
107
|
+
} else if (event instanceof ExceptionEvent ee
|
|
108
|
+
&& isUserClass(ee.location().declaringType())) {
|
|
109
|
+
String initialOutput = drainOutputEventsJson();
|
|
110
|
+
String statePrefix = buildStatePrefix(ee.thread());
|
|
111
|
+
eventSet.resume();
|
|
112
|
+
resumed = true;
|
|
113
|
+
waitForVmTermination(vm.eventQueue());
|
|
114
|
+
process.waitFor();
|
|
115
|
+
stdoutThread.join(1000);
|
|
116
|
+
stderrThread.join(1000);
|
|
117
|
+
String finalOutput = mergeOutputJson(
|
|
118
|
+
initialOutput,
|
|
119
|
+
drainOutputEventsJson());
|
|
120
|
+
emitState(statePrefix, finalOutput);
|
|
121
|
+
running = false;
|
|
122
|
+
break;
|
|
95
123
|
|
|
96
124
|
} else if (event instanceof VMDeathEvent
|
|
97
125
|
|| event instanceof VMDisconnectEvent) {
|
|
98
126
|
running = false;
|
|
99
127
|
}
|
|
100
128
|
}
|
|
101
|
-
if (running) {
|
|
129
|
+
if (running && !resumed) {
|
|
102
130
|
eventSet.resume();
|
|
103
131
|
}
|
|
104
132
|
}
|
|
@@ -108,6 +136,7 @@ public class TraceRunner {
|
|
|
108
136
|
vm.dispose();
|
|
109
137
|
} catch (VMDisconnectedException ignored) {
|
|
110
138
|
}
|
|
139
|
+
stdoutThread.join(1000);
|
|
111
140
|
stderrThread.join(1000);
|
|
112
141
|
}
|
|
113
142
|
}
|
|
@@ -127,10 +156,18 @@ public class TraceRunner {
|
|
|
127
156
|
}
|
|
128
157
|
|
|
129
158
|
private static void emitState(ThreadReference thread,
|
|
130
|
-
|
|
159
|
+
String outputJson) throws Exception {
|
|
131
160
|
Thread.sleep(1);
|
|
132
|
-
|
|
161
|
+
emitState(buildStatePrefix(thread), outputJson);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private static void emitState(String statePrefix, String outputJson) {
|
|
165
|
+
System.out.println(statePrefix + outputJson + "}");
|
|
166
|
+
System.out.flush();
|
|
167
|
+
}
|
|
133
168
|
|
|
169
|
+
private static String buildStatePrefix(ThreadReference thread)
|
|
170
|
+
throws Exception {
|
|
134
171
|
Location loc = thread.frame(0).location();
|
|
135
172
|
int line = loc.lineNumber();
|
|
136
173
|
String file = loc.sourceName();
|
|
@@ -138,30 +175,13 @@ public class TraceRunner {
|
|
|
138
175
|
var reachable = new LinkedHashMap<String, ObjectReference>();
|
|
139
176
|
String stackJson = serializeStack(thread, reachable);
|
|
140
177
|
|
|
141
|
-
// Carry forward objects from the previous step that are still alive.
|
|
142
|
-
// This keeps in-flight objects (e.g., on the operand stack during
|
|
143
|
-
// chained constructor calls) visible in the diagram.
|
|
144
|
-
for (var entry : previousReachable.entrySet()) {
|
|
145
|
-
if (!reachable.containsKey(entry.getKey())) {
|
|
146
|
-
try {
|
|
147
|
-
if (!entry.getValue().isCollected()) {
|
|
148
|
-
reachable.put(entry.getKey(), entry.getValue());
|
|
149
|
-
}
|
|
150
|
-
} catch (Exception ignored) {
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
178
|
String heapJson = serializeHeap(reachable);
|
|
156
|
-
previousReachable = new LinkedHashMap<>(reachable);
|
|
157
179
|
|
|
158
|
-
|
|
159
|
-
"{\"line\":" + line
|
|
180
|
+
return "{\"line\":" + line
|
|
160
181
|
+ ",\"file\":\"" + jsonEscape(file)
|
|
161
182
|
+ "\",\"stack\":" + stackJson
|
|
162
183
|
+ ",\"heap\":" + heapJson
|
|
163
|
-
+ ",\"
|
|
164
|
-
System.out.flush();
|
|
184
|
+
+ ",\"output\":";
|
|
165
185
|
}
|
|
166
186
|
|
|
167
187
|
private static String serializeStack(ThreadReference thread,
|
|
@@ -191,7 +211,7 @@ public class TraceRunner {
|
|
|
191
211
|
if (!frame.location().method().isStatic()) {
|
|
192
212
|
try {
|
|
193
213
|
ObjectReference thisObj = frame.thisObject();
|
|
194
|
-
if (thisObj != null) {
|
|
214
|
+
if (thisObj != null && shouldEmitThis(declType)) {
|
|
195
215
|
sb.append("\"this\":")
|
|
196
216
|
.append(serializeValue(thisObj, reachable));
|
|
197
217
|
firstVar = false;
|
|
@@ -238,6 +258,25 @@ public class TraceRunner {
|
|
|
238
258
|
return sb.toString();
|
|
239
259
|
}
|
|
240
260
|
|
|
261
|
+
private static boolean shouldEmitThis(ReferenceType type) {
|
|
262
|
+
if (!unnamedClassNames.contains(type.name())) {
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
return hasInstanceFields(type);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private static boolean hasInstanceFields(ReferenceType type) {
|
|
269
|
+
try {
|
|
270
|
+
for (Field field : type.visibleFields()) {
|
|
271
|
+
if (!field.isStatic()) {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
} catch (Exception ignored) {
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
|
|
241
280
|
private static String serializeValue(Value val,
|
|
242
281
|
Map<String, ObjectReference> reachable) {
|
|
243
282
|
if (val == null) {
|
|
@@ -336,6 +375,11 @@ public class TraceRunner {
|
|
|
336
375
|
}
|
|
337
376
|
|
|
338
377
|
ReferenceType type = obj.referenceType();
|
|
378
|
+
String boxedPrimitiveJson = serializeBoxedPrimitive(obj, type);
|
|
379
|
+
if (boxedPrimitiveJson != null) {
|
|
380
|
+
return boxedPrimitiveJson;
|
|
381
|
+
}
|
|
382
|
+
|
|
339
383
|
if (!isUserClass(type)) {
|
|
340
384
|
// JDK object: show type name and toString value
|
|
341
385
|
try {
|
|
@@ -369,6 +413,74 @@ public class TraceRunner {
|
|
|
369
413
|
return sb.toString();
|
|
370
414
|
}
|
|
371
415
|
|
|
416
|
+
private static String serializeBoxedPrimitive(ObjectReference obj,
|
|
417
|
+
ReferenceType type) {
|
|
418
|
+
if (!isBoxedPrimitive(type)) {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
Field valueField = type.fieldByName("value");
|
|
423
|
+
if (valueField == null) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
Value value = obj.getValue(valueField);
|
|
429
|
+
return "{\"type\":\"" + jsonEscape(type.name())
|
|
430
|
+
+ "\",\"value\":" + primitiveJsonLiteral(value) + "}";
|
|
431
|
+
} catch (Exception ignored) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private static boolean isBoxedPrimitive(ReferenceType type) {
|
|
437
|
+
return switch (type.name()) {
|
|
438
|
+
case "java.lang.Boolean",
|
|
439
|
+
"java.lang.Byte",
|
|
440
|
+
"java.lang.Character",
|
|
441
|
+
"java.lang.Double",
|
|
442
|
+
"java.lang.Float",
|
|
443
|
+
"java.lang.Integer",
|
|
444
|
+
"java.lang.Long",
|
|
445
|
+
"java.lang.Short" -> true;
|
|
446
|
+
default -> false;
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private static String primitiveJsonLiteral(Value value) {
|
|
451
|
+
if (value instanceof BooleanValue v) {
|
|
452
|
+
return String.valueOf(v.value());
|
|
453
|
+
}
|
|
454
|
+
if (value instanceof ByteValue v) {
|
|
455
|
+
return String.valueOf(v.value());
|
|
456
|
+
}
|
|
457
|
+
if (value instanceof CharValue v) {
|
|
458
|
+
return "\"" + jsonEscape(String.valueOf(v.value())) + "\"";
|
|
459
|
+
}
|
|
460
|
+
if (value instanceof DoubleValue v) {
|
|
461
|
+
double n = v.value();
|
|
462
|
+
return Double.isFinite(n)
|
|
463
|
+
? String.valueOf(n)
|
|
464
|
+
: "\"" + jsonEscape(String.valueOf(n)) + "\"";
|
|
465
|
+
}
|
|
466
|
+
if (value instanceof FloatValue v) {
|
|
467
|
+
float n = v.value();
|
|
468
|
+
return Float.isFinite(n)
|
|
469
|
+
? String.valueOf(n)
|
|
470
|
+
: "\"" + jsonEscape(String.valueOf(n)) + "\"";
|
|
471
|
+
}
|
|
472
|
+
if (value instanceof IntegerValue v) {
|
|
473
|
+
return String.valueOf(v.value());
|
|
474
|
+
}
|
|
475
|
+
if (value instanceof LongValue v) {
|
|
476
|
+
return String.valueOf(v.value());
|
|
477
|
+
}
|
|
478
|
+
if (value instanceof ShortValue v) {
|
|
479
|
+
return String.valueOf(v.value());
|
|
480
|
+
}
|
|
481
|
+
return "\"" + jsonEscape(String.valueOf(value)) + "\"";
|
|
482
|
+
}
|
|
483
|
+
|
|
372
484
|
private static String getObjectId(ObjectReference obj) {
|
|
373
485
|
return objectIdMap.computeIfAbsent(
|
|
374
486
|
obj.uniqueID(), k -> "obj_" + nextObjId++);
|
|
@@ -383,14 +495,91 @@ public class TraceRunner {
|
|
|
383
495
|
&& !name.startsWith("com.sun.");
|
|
384
496
|
}
|
|
385
497
|
|
|
386
|
-
private static
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
498
|
+
private static void waitForVmTermination(EventQueue eventQueue)
|
|
499
|
+
throws InterruptedException {
|
|
500
|
+
boolean running = true;
|
|
501
|
+
while (running) {
|
|
502
|
+
try {
|
|
503
|
+
EventSet eventSet = eventQueue.remove();
|
|
504
|
+
for (Event event : eventSet) {
|
|
505
|
+
if (event instanceof VMDeathEvent
|
|
506
|
+
|| event instanceof VMDisconnectEvent) {
|
|
507
|
+
running = false;
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (running) {
|
|
512
|
+
eventSet.resume();
|
|
513
|
+
}
|
|
514
|
+
} catch (VMDisconnectedException ignored) {
|
|
515
|
+
running = false;
|
|
516
|
+
}
|
|
390
517
|
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private static void readStream(String streamName, InputStream stream) {
|
|
521
|
+
byte[] buffer = new byte[4096];
|
|
522
|
+
try {
|
|
523
|
+
int n;
|
|
524
|
+
while ((n = stream.read(buffer)) != -1) {
|
|
525
|
+
String text = new String(buffer, 0, n, StandardCharsets.UTF_8);
|
|
526
|
+
synchronized (outputEvents) {
|
|
527
|
+
if (!outputEvents.isEmpty()
|
|
528
|
+
&& outputEvents.get(outputEvents.size() - 1)
|
|
529
|
+
.stream().equals(streamName)) {
|
|
530
|
+
OutputEvent previous =
|
|
531
|
+
outputEvents.remove(outputEvents.size() - 1);
|
|
532
|
+
outputEvents.add(new OutputEvent(
|
|
533
|
+
streamName,
|
|
534
|
+
previous.text() + text));
|
|
535
|
+
} else {
|
|
536
|
+
outputEvents.add(new OutputEvent(streamName, text));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
} catch (IOException ignored) {
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
private static String drainOutputEventsJson() {
|
|
545
|
+
List<OutputEvent> drained;
|
|
546
|
+
synchronized (outputEvents) {
|
|
547
|
+
drained = new ArrayList<>(outputEvents);
|
|
548
|
+
outputEvents.clear();
|
|
549
|
+
}
|
|
550
|
+
return outputEventsJson(drained);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
private static String outputEventsJson(List<OutputEvent> events) {
|
|
554
|
+
var sb = new StringBuilder("[");
|
|
555
|
+
boolean first = true;
|
|
556
|
+
for (OutputEvent event : events) {
|
|
557
|
+
if (!first) {
|
|
558
|
+
sb.append(',');
|
|
559
|
+
}
|
|
560
|
+
first = false;
|
|
561
|
+
sb.append("{\"stream\":\"")
|
|
562
|
+
.append(event.stream())
|
|
563
|
+
.append("\",\"text\":\"")
|
|
564
|
+
.append(jsonEscape(event.text()))
|
|
565
|
+
.append("\"}");
|
|
566
|
+
}
|
|
567
|
+
sb.append(']');
|
|
391
568
|
return sb.toString();
|
|
392
569
|
}
|
|
393
570
|
|
|
571
|
+
private static String mergeOutputJson(String first, String second) {
|
|
572
|
+
if (first.equals("[]")) {
|
|
573
|
+
return second;
|
|
574
|
+
}
|
|
575
|
+
if (second.equals("[]")) {
|
|
576
|
+
return first;
|
|
577
|
+
}
|
|
578
|
+
return first.substring(0, first.length() - 1)
|
|
579
|
+
+ ","
|
|
580
|
+
+ second.substring(1);
|
|
581
|
+
}
|
|
582
|
+
|
|
394
583
|
private static String jsonEscape(String s) {
|
|
395
584
|
var sb = new StringBuilder();
|
|
396
585
|
for (int i = 0; i < s.length(); i++) {
|