diff --git a/CHANGES.md b/CHANGES.md index f326ddc..2eea86c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -44,7 +44,7 @@ Swift-Pfade relativ zu `filius-ipad/ios/FiliusPad/TopologyEditor/` (Stand | `src/dnsHosts.ts` | Java `DNSServer.leseRecordListe`, `ResourceRecord`, `Dateisystem.holeDatei`; Swift `State/TopologyDNSResolver.swift` (`TopologyDNSHostsFileCodec`) als Vergleich | neu nach Java: Felder nur an einzelnen Leerzeichen, keine Kommentare, ungültige TTL → 3600, Doppelte bleiben; ins Modell kommt nur, was Java beantwortet (Typ A/MX/NS, Name nicht leer); Javas Fehler nicht übernommen: Label mit Ziffer vorn erlaubt, kein Backslash, letztes Label nicht rein numerisch (RFC 1123/3696), Typ in jeder Schreibung (RFC 4343), negative TTL ungültig (RFC 2181); an 76 Fällen mit Java und Swift gemessen | | `src/topology.ts` | Java `SzenarioVerwaltung.netzwerkLaden`, Konstruktoren und Setter der Geräte; Swift `Persistence/TopologyProjectStore.swift` (Parser) als Vergleich | neu nach Java statt Übertragung: Geräteart aus der Klasse statt aus der Beschriftung, Geräte ohne Koordinaten bei 0,0 statt verworfen, Werte durch Javas Setter, die Zeichen-Enden eines Kabels, die Funk-Eigenschaft einer Netzwerkkarte (`wireless`, bis 30.09.2026 übersehen) | | `test/javabeans.test.ts` | — | neu: je XMLDecoder-Regel ein Test; alle 23 Korpus-Dateien, Klasse und Position jedes Geräts wie Java | -| `test/topology.test.ts` | — | neu: alle 23 Korpus-Dateien Feld für Feld wie Java (Geräte, Schnittstellen, Kabel, Doku-Elemente), dazu die Regeln, die der Korpus nicht berührt | +| `test/topology.test.ts` | — | neu: alle 23 Korpus-Dateien Feld für Feld wie Java (Geräte, Schnittstellen, Kabel, Doku-Elemente), dazu die Regeln, die der Korpus nicht berührt; Java lädt seit M8 über den vollständigen Ladeweg, eigene Anwendungen (`test/ownApplications.ts`) sind bis B2 ausgenommen | | `test/javaIp.test.ts`, `test/ip-check.*` | — | neu: `IP.ipCheck`-Fälle, Ergebnis aus Java | | `src/javaEmail.ts` | Java `EmailServer` (Konten-Bean, `kontenLaden`/`stringZuListeBenutzerkonten`), `EmailAnwendung` (Ordner, Adressbuch, `laden`), `EmailKonto`, `Email`, `AddressEntry`, `EmailUtils` | neu nach Java: E-Mail-Beans über ihre Setter, `/mailserver/konten.txt` und `/konten.txt` im Format von Javas Lesern, ohne ihre Fehler (leere Felder zählen, eine kurze Zeile fällt allein weg, Nachrichten mit leerem Text oder Betreff bleiben, `;` im Betreff bleibt, CRLF, beim Client die letzte vollständige Zeile); Empfänger älterer Dateien als Zeichenketten; gegen Java auf dem Korpus und an 43 Fällen geprüft (13 benannte Java-Fehler) | | `src/javaBeansWriter.ts` | — | neu: Schreiber für das `XMLDecoder`-Format, Gegenstück zu `javabeans.ts`; ausdrücklich typisierte Eingabe, geteilte Objekte über `id`/`idref`, was XML nicht fassen kann (und CR) als ``; von Java selbst geprüft (`BeansDump`) | diff --git a/corpus/corpus.java.json b/corpus/corpus.java.json index 3f6884e..b22525f 100644 --- a/corpus/corpus.java.json +++ b/corpus/corpus.java.json @@ -8021,7 +8021,14 @@ "directory": "root", "children": [] }, - "applications": [], + "applications": [ + { + "key": "filius.software.clientserver.Accumulator", + "class": "filius.software.clientserver.Accumulator", + "aktiv": true, + "port": 55555 + } + ], "dhcpClient": false, "dhcpServer": { "aktiv": false, @@ -13239,6 +13246,12 @@ "children": [] }, "applications": [ + { + "key": "filius.software.clientserver.Broadcast", + "class": "filius.software.clientserver.Broadcast", + "aktiv": true, + "port": 55555 + }, { "key": "filius.software.clientserver.ServerBaustein", "class": "filius.software.clientserver.ServerBaustein", diff --git a/packages/fls/test/exportProject.test.ts b/packages/fls/test/exportProject.test.ts index 00729bb..6a7aa75 100644 --- a/packages/fls/test/exportProject.test.ts +++ b/packages/fls/test/exportProject.test.ts @@ -39,6 +39,7 @@ import { type ProjectToExport, } from '../src/index.js'; import { writeTopologyXml } from '../src/topologyWriter.js'; +import { withoutOwnApplications } from './ownApplications.js'; const here = dirname(fileURLToPath(import.meta.url)); const corpusRoot = join(here, '../../../corpus'); @@ -349,7 +350,8 @@ describe('the editor model exported, as Java loads it', () => { return nic.mac === derived ? derived : undefined; }), ); - const source = original[name]!; + // The model does not install own applications yet (B2). + const source = withoutOwnApplications(original[name]!); const nodeIds = project.graph.nodes.map((node) => node.id); const pairs = nodeIds.map((id) => project.remoteLinkConfigurations.get(id)?.pairIdentifier); // The account files of each host the export writes anew, by path, as the file has them. diff --git a/packages/fls/test/ownApplications.ts b/packages/fls/test/ownApplications.ts new file mode 100644 index 0000000..e80fa2e --- /dev/null +++ b/packages/fls/test/ownApplications.ts @@ -0,0 +1,38 @@ +// A project's own applications (classes under projekt/anwendungen): Java +// installs them when it loads the whole archive (tools/java-oracle, laden); +// this package reads, writes and exports them in a later step (B2). Until +// then the tests compare Java's views without them. + +import { FILIUS_APPLICATION_CLASSES } from '../src/index.js'; + +/** The corpus files with an own application installed on a host. */ +export const OWN_APPLICATION_FILES = ['java-examples/anwendung_komplett.fls', 'java-examples/multicast_server.fls']; + +type View = { nodes?: { system: Record }[] }; + +/** Java's view without the applications FILIUS does not ship. */ +export function withoutOwnApplications(view: T): T { + const { nodes } = view as View; + if (nodes === undefined) return view; + return { + ...view, + nodes: nodes.map((node) => + node.system.applications === undefined + ? node + : { + ...node, + system: { + ...node.system, + applications: (node.system.applications as { class: string }[]).filter((application) => + (FILIUS_APPLICATION_CLASSES as readonly string[]).includes(application.class), + ), + }, + }, + ), + }; +} + +/** Whether Java's view has an own application installed. */ +export function hasOwnApplications(view: unknown): boolean { + return JSON.stringify(withoutOwnApplications(view)) !== JSON.stringify(view); +} diff --git a/packages/fls/test/topology.test.ts b/packages/fls/test/topology.test.ts index bff5fd9..d37c12a 100644 --- a/packages/fls/test/topology.test.ts +++ b/packages/fls/test/topology.test.ts @@ -21,6 +21,7 @@ import { type JavaEmailAccount, } from '../src/index.js'; import { EXPECTED_WARNINGS } from './expectedWarnings.js'; +import { hasOwnApplications, OWN_APPLICATION_FILES, withoutOwnApplications } from './ownApplications.js'; import { javaEmail, javaEmailAccount } from './javaEmailNames.js'; const corpusRoot = join(dirname(fileURLToPath(import.meta.url)), '../../../corpus'); @@ -250,11 +251,18 @@ describe('corpus: the topology as Java loads it', () => { const topology = readTopology(values); const expected = javaView[name] as { nodes: { bounds: number[] | null }[] }; - expect(asJavaView(topology, expected.nodes.map((node) => node.bounds))).toEqual(withoutTemplateFiles(expected, topology)); + expect(asJavaView(topology, expected.nodes.map((node) => node.bounds))).toEqual( + withoutTemplateFiles(withoutOwnApplications(expected), topology), + ); expect(topology.warnings).toEqual(EXPECTED_WARNINGS[name] ?? []); }); } + it('leaves out own applications only where Java installs some', () => { + const files = flsFiles(corpusRoot).map((file) => relative(corpusRoot, file)); + expect(files.filter((name) => hasOwnApplications(javaView[name]))).toEqual(OWN_APPLICATION_FILES); + }); + it("a router's constructor makes the same files in every corpus file", () => { for (const javaClass of ['Vermittlungsrechner', 'Gateway']) { const templates = [...(templateChildren.get(javaClass) ?? [])].map((text) => JSON.parse(text) as { directory?: string }[]); diff --git a/packages/fls/test/topologyWriter.test.ts b/packages/fls/test/topologyWriter.test.ts index 7777625..9c119a5 100644 --- a/packages/fls/test/topologyWriter.test.ts +++ b/packages/fls/test/topologyWriter.test.ts @@ -18,6 +18,7 @@ import { describe, expect, it } from 'vitest'; import { decodeJavaBeans, readFlsArchive, readTopology, writeFlsArchive } from '../src/index.js'; import type { JavaEmail } from '../src/javaEmail.js'; import { writeTopologyXml, type TopologyToWrite } from '../src/topologyWriter.js'; +import { withoutOwnApplications } from './ownApplications.js'; const here = dirname(fileURLToPath(import.meta.url)); const corpusRoot = join(here, '../../../corpus'); @@ -103,7 +104,8 @@ describe('the topology written again, as Java loads it', () => { expect(java?.sha256, 'written.java.json is not about this output: UPDATE_WRITTEN=1, then npm run oracle:java').toBe( createHash('sha256').update(archive).digest('hex'), ); - expect(topologyOf(java!)).toEqual(topologyOf(withRecipientsRead(original[name]!) as JavaFile)); + // The written file has no archive entries: Java installs no own application (B2). + expect(topologyOf(java!)).toEqual(topologyOf(withRecipientsRead(withoutOwnApplications(original[name]!)) as JavaFile)); }); } diff --git a/tools/java-oracle/README.md b/tools/java-oracle/README.md index 56cda43..f09f3da 100644 --- a/tools/java-oracle/README.md +++ b/tools/java-oracle/README.md @@ -20,8 +20,17 @@ Braucht JDK 17+, Maven und `xvfb-run`. Filius öffnet beim Laden sein Hauptfenster (`Information.reset` → `JMainFrame`), deshalb läuft es an einem virtuellen Bildschirm statt „headless". -* Gelesen wird über Filius' eigene, private Lademethode - `SzenarioVerwaltung.netzwerkLaden` (per Reflection) — Javas Weg, kein Nachbau. +* Gelesen wird so, wie Filius eine Datei öffnet: `SzenarioVerwaltung.laden` + entpackt das Archiv, kopiert die eigenen Anwendungen + (`projekt/anwendungen`) dorthin, wo Filius' Klassenlader sucht, und lädt + dann die Konfiguration — Javas Weg, kein Nachbau (seit 30.09.2026, M8). + `--network` lädt nur die Konfiguration über die private Methode + `netzwerkLaden` (per Reflection), wie vorher. Der Unterschied am Korpus: + In `anwendung_komplett.fls` und `multicast_server.fls` sind dann die + eigenen Anwendungen `Accumulator` und `Broadcast` installiert. Filius' + Klassenlader ist ein Singleton: Eine einmal geladene eigene Klasse bleibt + für die übrigen Dateien desselben Laufs geladen. Am Korpus stört das + nicht, denn keine Datei nutzt eine Klasse, die sie nicht mitbringt. * Stand: `gitlab.com/filius1/filius` @ `6640354` (2.14.0), wird beim ersten Lauf nach `.upstream/` geholt und gebaut. * **Gewürfeltes:** Was nicht in der Datei steht, würfelt Filius zum Teil diff --git a/tools/java-oracle/src/FlsOracle.java b/tools/java-oracle/src/FlsOracle.java index fbfd8af..673923a 100644 --- a/tools/java-oracle/src/FlsOracle.java +++ b/tools/java-oracle/src/FlsOracle.java @@ -2,7 +2,12 @@ // Uses FILIUS's own private loader (SzenarioVerwaltung.netzwerkLaden) by // reflection, so the result is Java's view, not a re-implementation of it. // -// Usage: java -Djava.awt.headless=true -cp filius.jar:lib/*:classes FlsOracle +// By default it loads as FILIUS opens a file (SzenarioVerwaltung.laden): the +// archive unpacked, the project's own applications (projekt/anwendungen) +// copied where FILIUS's class loader finds them, then the configuration. +// --network loads the configuration alone (netzwerkLaden), as before M8. +// +// Usage: java -cp filius.jar:lib/*:classes FlsOracle [--sha256] [--network] import filius.gui.netzwerksicht.GUIDocuItem; import filius.gui.netzwerksicht.GUIKabelItem; @@ -63,7 +68,13 @@ import java.util.zip.ZipInputStream; public class FlsOracle { + /** Load the configuration alone (netzwerkLaden) instead of the whole archive (laden). */ + static boolean networkOnly; + static List options; + public static void main(String[] args) throws Exception { + options = Arrays.asList(args).subList(2, args.length); + networkOnly = options.contains("--network"); Path root = Path.of(args[0]); List files; try (Stream walk = Files.walk(root)) { @@ -74,7 +85,7 @@ public class FlsOracle { String name = root.relativize(file).toString(); try { Map view = read(file); - if (args.length > 2 && args[2].equals("--sha256")) view.put("sha256", sha256(Files.readAllBytes(file))); + if (options.contains("--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 @@ -123,6 +134,13 @@ public class FlsOracle { } static Map load(Path file) throws Exception { + if (!networkOnly) { + List nodes = new ArrayList<>(); + List cables = new ArrayList<>(); + List docs = new ArrayList<>(); + boolean success = SzenarioVerwaltung.getInstance().laden(file.toString(), nodes, cables, docs); + return describe(success, nodes, cables, docs); + } Path xml = Files.createTempFile("konfiguration", ".xml"); try { extractConfiguration(file, xml);