feat(fls): read and write a project's own applications as their beans

A project's own applications (classes under projekt/anwendungen, installed
on a host) are no longer left out. The decoder now keeps the Java type of
every primitive — at setters, constructor and call arguments, also through
shared ids like Integer0 — and which properties an object's own getter
made. beanCapture.ts turns a decoded bean back into what the writer writes,
with the node's system software as a mark the writer replaces by the
system software it writes; what the format cannot say is refused, not
guessed (an unresolved value, a Field, a factory-made object, an object a
getter reaches elsewhere in the file). The reader keeps own applications as
captured beans; the corpus no longer warns.

The capture first took a setter with another object's getter result for an
own getter; the round-trip test found it. The Java oracle did not show
whether an application runs on its node's system software, so a mutation
writing the mark instead survived; it now reports onItsSystem (true for all
192 applications of the corpus).

Checked by Java: from the files written again, FILIUS loads Accumulator and
Broadcast installed, switched on and on their node's system software, as
in the original. The round-trip test covers every primitive, null, a
class, an array, shared objects, getter chains, constructed elements, map
reads and static constants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 09:43:16 +00:00
co-authored by Claude Opus 5.5
parent 95c5b7d97b
commit 3548652687
18 changed files with 1444 additions and 404 deletions
+30 -6
View File
@@ -28,7 +28,9 @@
// not know Java's constructors and sees a placeholder per path; they are
// merged here.
import { captureBean } from './beanCapture.js';
import { isDomainName } from './dnsHosts.js';
import type { BeanObject } from './javaBeansWriter.js';
import {
EMAIL_CLIENT_FILE,
EMAIL_SERVER_FILE,
@@ -130,6 +132,13 @@ const SERVER_APPLICATIONS = new Set<string>([
'filius.software.www.WebServer',
]);
/** An application of the project's own: its key, class and bean, which refers to its node's system software by a mark. */
export interface FlsOwnApplication {
key: string | null;
javaClass: string;
bean: BeanObject;
}
/** An installed application with the settings the model reads. */
export interface FlsApplication {
/** The key in installierteAnwendungen — the class name in every corpus file. */
@@ -199,6 +208,12 @@ export interface FlsSystemSoftware {
modem?: FlsModem;
/** Rechner, Notebook: the installed applications, in the file's order. */
applications?: FlsApplication[];
/**
* Rechner, Notebook: the installed applications FILIUS does not ship — a
* project's own, compiled into projekt/anwendungen — kept as their beans,
* to be written again as the file had them.
*/
ownApplications?: FlsOwnApplication[];
/**
* Every node with an IP stack: the root directory (named "root" by Java's
* constructor) as the file has it. A router's or home router's holds only
@@ -232,7 +247,7 @@ export function readSystemSoftware(device: JavaObject, javaClass: FlsNodeClass,
case 'Notebook':
result.ssid = optionalString(value('ssid'), 'ssid', warn) ?? null;
result.fileSystem = fileSystem(value('dateisystem'), warn);
result.applications = applications(value('installierteAnwendungen'), result.fileSystem, warn);
[result.applications, result.ownApplications] = applications(value('installierteAnwendungen'), result.fileSystem, software, warn);
break;
case 'Vermittlungsrechner':
result.ripEnabled = boolean(value('ripEnabled'), false, 'ripEnabled', warn);
@@ -279,11 +294,18 @@ function modemAddress(value: string | null | undefined): string | null {
/**
* installierteAnwendungen: key → application. A class FILIUS does not ship —
* a project's own application, compiled into projekt/anwendungen — is left
* out: this reader cannot run Java classes.
* a project's own application, compiled into projekt/anwendungen — cannot
* run here; it is kept as its bean (beanCapture.ts), or left out with a
* warning where the bean cannot be written again.
*/
function applications(map: JavaValue | undefined, root: FlsFileTree, warn: Warn): FlsApplication[] {
function applications(
map: JavaValue | undefined,
root: FlsFileTree,
software: JavaObject | undefined,
warn: Warn,
): [FlsApplication[], FlsOwnApplication[]] {
const result: FlsApplication[] = [];
const own: FlsOwnApplication[] = [];
for (const [key, application] of mapEntries(map)) {
if (!isJavaObject(application)) {
warn(`application ${describe(key)} is ${describe(application)}, left out`);
@@ -291,7 +313,9 @@ function applications(map: JavaValue | undefined, root: FlsFileTree, warn: Warn)
}
const javaClass = application.className;
if (!(FILIUS_APPLICATION_CLASSES as readonly string[]).includes(javaClass)) {
warn(`application ${javaClass} is not part of FILIUS (a project's own application), left out`);
const captured = captureBean(application, software);
if ('bean' in captured) own.push({ key: typeof key === 'string' ? key : null, javaClass, bean: captured.bean });
else warn(`application ${javaClass}, a project's own, cannot be written again: ${captured.failure}; left out`);
continue;
}
const simple = simpleClassName(javaClass);
@@ -348,7 +372,7 @@ function applications(map: JavaValue | undefined, root: FlsFileTree, warn: Warn)
}
result.push(entry);
}
return result;
return [result, own];
}
/**