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
+17 -1
View File
@@ -60,6 +60,11 @@ export interface FlsPortReference {
export interface FlsLink {
ends: [FlsPortReference, FlsPortReference];
wireless: boolean;
/**
* The nodes the editor draws the cable between (JCablePanel ziel1, ziel2),
* by index; null where the file names none.
*/
panel: [number | null, number | null];
}
export type FlsColor = [red: number, green: number, blue: number, alpha: number];
@@ -146,7 +151,10 @@ export function readTopology(values: readonly JavaValue[]): FlsTopology {
const nodes: FlsNode[] = [];
for (const [index, item] of nodeItems.entries()) {
const node = context.node(item, index, nodes.length);
if (node !== undefined) nodes.push(node);
if (node !== undefined) {
if (isJavaObject(item)) context.nodeItems.set(item, nodes.length);
nodes.push(node);
}
}
const links: FlsLink[] = [];
for (const [index, item] of cableItems.entries()) {
@@ -168,6 +176,8 @@ function listOf(value: JavaValue | undefined, className: string): JavaValue[] {
class Reader {
/** Every port object a node owns, to resolve cable ends by identity as Java does. */
private readonly portOwners = new Map<JavaObject, FlsPortReference>();
/** Every node item read, to resolve a cable panel's ends. */
readonly nodeItems = new Map<JavaObject, number>();
constructor(private readonly warnings: string[]) {}
@@ -310,9 +320,15 @@ class Reader {
this.warnings.push(`cable ${itemIndex + 1}: an end is not a port of a device, left out`);
return undefined;
}
const panel = (item as JavaObject).properties.get('kabelpanel');
const panelEnd = (property: string) => {
const target = isJavaObject(panel) ? panel.properties.get(property) : undefined;
return isJavaObject(target) ? (this.nodeItems.get(target) ?? null) : null;
};
return {
ends: [first, second],
wireless: this.booleanProperty(cable, 'wireless', `cable ${itemIndex + 1}`),
panel: [panelEnd('ziel1'), panelEnd('ziel2')],
};
}