feat(fls): write the topology of a .fls, loaded by Java as the original

topologyWriter.ts writes konfiguration.xml from Java's view of a project:
the version line, the node items (label position, device with name,
host label flags, interfaces or ports), the cable items and the
documentation items. What a device's constructor makes is changed through
getters, as XMLEncoder does, and more is added; fewer than it makes set the
list anew. Cable ends name the port objects the getters return (the writer
now names a getter's result, <void id="Port0" property="port"/>), and each
cable carries the nodes the editor draws it between.

Checked by Java against Java: every corpus file, read and written again,
loads in Java FILIUS (the oracle's loading method, --sha256 ties its result
to the written bytes) to the same devices, names, positions, interfaces,
cables and documentation as the original; so does a synthetic topology with
what the corpus never sets. The oracle and the reader now also report a
cable's drawing ends (JCablePanel ziel1/ziel2): in the corpus the nodes it
joins, for all 183 cables. Mutation probes rewrite the files and let Java
load each mutant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 08:04:21 +00:00
co-authored by Claude Opus 5.5
parent 3795316fae
commit 314f696515
17 changed files with 16251 additions and 204 deletions
+11
View File
@@ -85,6 +85,17 @@ wird leer, der Eintrag bleibt trotzdem; Typen außer A/MX/NS (auch `a`,
`CNAME`, eine Kommentarzeile) gelten als gültig; eine TTL, die kein `int`
ist, wird still 3600.
## Was Java aus unseren Dateien lädt
`run.sh` lädt auch die Dateien unter `packages/fls/test/written/`, die
`topologyWriter.test.ts` mit `UPDATE_WRITTEN=1` schreibt (nicht
eingecheckt), mit derselben Lademethode wie den Korpus, und schreibt
`packages/fls/test/written.java.json` samt SHA-256 jeder Datei
(`--sha256`), damit der Test merkt, wenn der Befund nicht mehr zur Ausgabe
passt. Je Kabel steht auch `panel`: die Geräte, zwischen denen die
Oberfläche es zeichnet (`JCablePanel.ziel1/ziel2`) — im Korpus bei allen 183
Kabeln die Geräte, die es verbindet.
## Was Java aus unserem XML macht
`BeansDump` liest jede `*.xml` unter `packages/fls/test/beans/` mit
+10
View File
@@ -88,3 +88,13 @@ beans="$root/packages/fls/test/beans"
rm -f "$beans/beans.java.json"
HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" BeansDump "$beans" "$beans/beans.java.json" 2>&1 | grep -v 'logback\|^$' || true
test -s "$beans/beans.java.json"
# What Java loads from the files packages/fls writes (topologyWriter.test.ts
# writes them with UPDATE_WRITTEN=1; the files are not checked in, their
# SHA-256 is).
written="$root/packages/fls/test/written"
if [ -d "$written" ]; then
rm -f "$written.java.json"
HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" FlsOracle "$written" "$written.java.json" --sha256 2>&1 | grep -v 'logback\|^$' || true
test -s "$written.java.json"
fi
+21 -1
View File
@@ -6,6 +6,7 @@
import filius.gui.netzwerksicht.GUIDocuItem;
import filius.gui.netzwerksicht.GUIKabelItem;
import filius.gui.netzwerksicht.JCablePanel;
import filius.gui.netzwerksicht.GUIKnotenItem;
import filius.hardware.Kabel;
import filius.hardware.NetzwerkInterface;
@@ -72,7 +73,9 @@ public class FlsOracle {
for (Path file : files) {
String name = root.relativize(file).toString();
try {
result.put(name, read(file));
Map<String, Object> view = read(file);
if (args.length > 2 && args[2].equals("--sha256")) view.put("sha256", sha256(Files.readAllBytes(file)));
result.put(name, view);
} catch (Throwable error) {
Throwable cause = error instanceof java.lang.reflect.InvocationTargetException && error.getCause() != null
? error.getCause()
@@ -96,6 +99,19 @@ public class FlsOracle {
System.exit(0);
}
/** The position of an item in a list by identity, null when absent. */
static Integer indexOf(List<?> list, Object item) {
if (item == null) return null;
for (int index = 0; index < list.size(); index++) if (list.get(index) == item) return index;
return null;
}
static String sha256(byte[] bytes) throws Exception {
StringBuilder hex = new StringBuilder();
for (byte value : java.security.MessageDigest.getInstance("SHA-256").digest(bytes)) hex.append(String.format("%02x", value));
return hex.toString();
}
/**
* Loads the file twice. What FILIUS does not find in a file it rolls at
* random (MAC addresses, a switch's SSID) — such values differ between
@@ -208,6 +224,10 @@ public class FlsOracle {
}
json.put("ends", ends);
json.put("wireless", cable != null && cable.getWireless());
// The nodes the editor draws the cable between (JCablePanel).
JCablePanel panel = item.getKabelpanel();
json.put("panel", panel == null ? null
: Arrays.asList(indexOf(nodes, panel.getZiel1()), indexOf(nodes, panel.getZiel2())));
cableList.add(json);
}