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:
@@ -0,0 +1,142 @@
|
||||
// A decoded JavaBean (javabeans.ts) turned back into what javaBeansWriter.ts
|
||||
// writes, so that a bean this package does not understand — a project's own
|
||||
// application — is written again as the file had it. Java's primitive types
|
||||
// come from the decoder (propertyKinds, argKinds, a call's kinds); a number
|
||||
// without one was an Integer or a Double. The node's system software, which
|
||||
// the bean refers to, is marked (SYSTEM_SOFTWARE_CLASS) and put back by the
|
||||
// writer as the system software of the node it writes.
|
||||
//
|
||||
// What the writer's format cannot say is refused, never guessed: a value the
|
||||
// decoder left unresolved, a Field, an object a static factory made, and an
|
||||
// object the bean reached by a getter elsewhere in the file (another part of
|
||||
// the node, whose identity a copy would lose). Objects the bean shares with
|
||||
// itself stay shared.
|
||||
|
||||
import { isJavaObject, isUnresolved, type JavaArray, type JavaObject, type JavaValue, type PrimitiveKind } from './javabeans.js';
|
||||
import type { BeanObject, BeanStatement, BeanValue } from './javaBeansWriter.js';
|
||||
|
||||
/** The class name of the mark where a captured bean refers to its node's system software. */
|
||||
export const SYSTEM_SOFTWARE_CLASS = '#systemSoftware';
|
||||
|
||||
export type CaptureResult = { readonly bean: BeanObject } | { readonly failure: string };
|
||||
|
||||
class CaptureFailure extends Error {}
|
||||
|
||||
/**
|
||||
* The bean `object` as the writer writes it; `systemSoftware` is the node's
|
||||
* system software as the decoder has it.
|
||||
*/
|
||||
export function captureBean(object: JavaObject, systemSoftware: JavaValue | undefined): CaptureResult {
|
||||
const captured = new Map<JavaObject, BeanObject>();
|
||||
|
||||
const value = (item: JavaValue, kind: PrimitiveKind | undefined): BeanValue => {
|
||||
if (item === null || typeof item === 'boolean') return item;
|
||||
if (typeof item === 'string') return kind === 'char' ? { kind: 'char', value: item } : item;
|
||||
if (typeof item === 'number' || typeof item === 'bigint') return number(item, kind);
|
||||
if (isJavaObject(item)) {
|
||||
if (item === systemSoftware) return { kind: 'object', className: SYSTEM_SOFTWARE_CLASS };
|
||||
if (item.fromGetter) throw new CaptureFailure(`it refers to ${item.className}, which a getter reaches elsewhere in the file`);
|
||||
return bean(item);
|
||||
}
|
||||
if (isUnresolved(item)) throw new CaptureFailure(`a value is unresolved (${item.reason})`);
|
||||
switch (item.kind) {
|
||||
case 'array':
|
||||
return { kind: 'array', componentClass: item.componentClass, items: (item as JavaArray).items.map((each) => value(each, undefined)) };
|
||||
case 'class':
|
||||
return { kind: 'class', name: item.name };
|
||||
default:
|
||||
throw new CaptureFailure(`it holds a Field (${item.className}.${item.name})`);
|
||||
}
|
||||
};
|
||||
|
||||
const bean = (item: JavaObject): BeanObject => {
|
||||
const known = captured.get(item);
|
||||
if (known !== undefined) return known;
|
||||
if (item.factoryMethod !== undefined) throw new CaptureFailure(`${item.className}.${item.factoryMethod} makes an object this writer cannot`);
|
||||
const statements: BeanStatement[] = [];
|
||||
const result: BeanObject =
|
||||
item.staticField === undefined
|
||||
? { kind: 'object', className: item.className, args: item.args.map((arg, index) => value(arg, item.argKinds[index])), statements }
|
||||
: { kind: 'object', className: item.className, staticField: item.staticField, statements };
|
||||
captured.set(item, result);
|
||||
statements.push(...statementsOf(item));
|
||||
return result;
|
||||
};
|
||||
|
||||
/** Setters and getters with what follows on their results; the constructor's elements; the calls but reads. */
|
||||
const statementsOf = (item: JavaObject): BeanStatement[] => {
|
||||
const statements: BeanStatement[] = [];
|
||||
for (const [property, each] of item.properties) {
|
||||
if (item.propertiesFromGetters.has(property) && isJavaObject(each)) {
|
||||
statements.push({ get: property, statements: statementsOf(each) });
|
||||
} else {
|
||||
statements.push({ set: property, value: value(each, item.propertyKinds.get(property)) });
|
||||
}
|
||||
}
|
||||
for (const [index, element] of item.presetElements) {
|
||||
statements.push({ getIndex: index, statements: isJavaObject(element) ? statementsOf(element) : [] });
|
||||
}
|
||||
for (const [key, entry] of item.presetEntries) {
|
||||
statements.push({ call: 'get', args: [key], statements: isJavaObject(entry) ? statementsOf(entry) : [] });
|
||||
}
|
||||
for (const call of item.calls) {
|
||||
// A read (get(i), get(key)) is the preset element or entry above, or a value set before.
|
||||
if (call.method === 'get' && call.args.length === 1) continue;
|
||||
statements.push({ call: call.method, args: call.args.map((arg, index) => value(arg, call.kinds?.[index])) });
|
||||
}
|
||||
return statements;
|
||||
};
|
||||
|
||||
try {
|
||||
return { bean: bean(object) };
|
||||
} catch (error) {
|
||||
if (error instanceof CaptureFailure) return { failure: error.message };
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function number(item: number | bigint, kind: PrimitiveKind | undefined): BeanValue {
|
||||
switch (kind) {
|
||||
case 'byte':
|
||||
case 'short':
|
||||
case 'int':
|
||||
return { kind, value: Number(item) };
|
||||
case 'long':
|
||||
return { kind: 'long', value: item };
|
||||
case 'float':
|
||||
case 'double':
|
||||
return { kind, value: Number(item) };
|
||||
case 'char':
|
||||
return { kind: 'char', value: String.fromCharCode(Number(item)) };
|
||||
default:
|
||||
// An Integer, or a Double where the number has a fraction.
|
||||
return typeof item === 'number' && !Number.isInteger(item) ? { kind: 'double', value: item } : { kind: 'int', value: Number(item) };
|
||||
}
|
||||
}
|
||||
|
||||
/** The bean with each mark of the system software replaced by `systemSoftware`. */
|
||||
export function withSystemSoftware(value: BeanValue, systemSoftware: BeanValue): BeanValue {
|
||||
const seen = new Map<BeanObject, BeanObject>();
|
||||
const replace = (item: BeanValue): BeanValue => {
|
||||
if (item === null || typeof item !== 'object') return item;
|
||||
if (item.kind === 'array') return { ...item, items: item.items.map(replace) };
|
||||
if (item.kind !== 'object') return item;
|
||||
if (item.className === SYSTEM_SOFTWARE_CLASS) return systemSoftware;
|
||||
return object(item);
|
||||
};
|
||||
const statement = (each: BeanStatement): BeanStatement => {
|
||||
if ('set' in each || 'setIndex' in each) return { ...each, value: replace(each.value) };
|
||||
if ('call' in each) return { ...each, args: each.args.map(replace), ...(each.statements === undefined ? {} : { statements: each.statements.map(statement) }) };
|
||||
return { ...each, statements: each.statements.map(statement) };
|
||||
};
|
||||
const object = (item: BeanObject): BeanObject => {
|
||||
const known = seen.get(item);
|
||||
if (known !== undefined) return known;
|
||||
const statements: BeanStatement[] = [];
|
||||
const result: BeanObject = { ...item, ...(item.args === undefined ? {} : { args: item.args.map(replace) }), statements };
|
||||
seen.set(item, result);
|
||||
statements.push(...(item.statements ?? []).map(statement));
|
||||
return result;
|
||||
};
|
||||
return replace(value);
|
||||
}
|
||||
@@ -45,8 +45,18 @@ export interface JavaObject {
|
||||
readonly factoryMethod: string | undefined;
|
||||
/** Set through setters, `Field.set` or created lazily by a getter. */
|
||||
readonly properties: Map<string, JavaValue>;
|
||||
/** Method calls in order (`add`, `put`, …), with their arguments. */
|
||||
readonly calls: { method: string; args: JavaValue[] }[];
|
||||
/** The Java type of each property set to a primitive (`int`, `long`, …): numbers alone do not say. */
|
||||
readonly propertyKinds: Map<string, PrimitiveKind>;
|
||||
/**
|
||||
* The properties whose value this object's own getter made (a placeholder
|
||||
* the file's statements then changed) — not set to an object from
|
||||
* elsewhere.
|
||||
*/
|
||||
readonly propertiesFromGetters: Set<string>;
|
||||
/** The Java types of the constructor's arguments, where they are primitives. */
|
||||
readonly argKinds: (PrimitiveKind | undefined)[];
|
||||
/** Method calls in order (`add`, `put`, …), with their arguments and those arguments' primitive types. */
|
||||
readonly calls: { method: string; args: JavaValue[]; kinds?: (PrimitiveKind | undefined)[] }[];
|
||||
/**
|
||||
* Elements the object already held before the file's statements — a list
|
||||
* its constructor filled, reached by `<void index="i">` on a getter's
|
||||
@@ -62,6 +72,23 @@ export interface JavaObject {
|
||||
readonly fromGetter: boolean;
|
||||
}
|
||||
|
||||
/** The primitive types XMLEncoder writes as elements of their own. */
|
||||
export type PrimitiveKind = 'boolean' | 'byte' | 'char' | 'short' | 'int' | 'long' | 'float' | 'double';
|
||||
|
||||
const PRIMITIVE_TAGS = new Set<string>(['boolean', 'byte', 'char', 'short', 'int', 'long', 'float', 'double']);
|
||||
|
||||
/** A wrapper's id prefix, as XMLEncoder names a shared value (Integer0), and its primitive. */
|
||||
const KIND_OF_ID_PREFIX: Readonly<Record<string, PrimitiveKind>> = {
|
||||
Boolean: 'boolean',
|
||||
Byte: 'byte',
|
||||
Character: 'char',
|
||||
Short: 'short',
|
||||
Integer: 'int',
|
||||
Long: 'long',
|
||||
Float: 'float',
|
||||
Double: 'double',
|
||||
};
|
||||
|
||||
export interface JavaArray {
|
||||
readonly kind: 'array';
|
||||
readonly componentClass: string;
|
||||
@@ -262,6 +289,9 @@ function newObject(fields: Partial<JavaObject> & { className: string }): JavaObj
|
||||
staticField: fields.staticField,
|
||||
factoryMethod: fields.factoryMethod,
|
||||
properties: new Map(),
|
||||
propertyKinds: new Map(),
|
||||
propertiesFromGetters: new Set(),
|
||||
argKinds: [],
|
||||
calls: [],
|
||||
presetElements: new Map(),
|
||||
presetEntries: new Map(),
|
||||
@@ -271,6 +301,15 @@ function newObject(fields: Partial<JavaObject> & { className: string }): JavaObj
|
||||
|
||||
class Decoder {
|
||||
private readonly ids = new Map<string, JavaValue>();
|
||||
/** The primitive type of each id that names a primitive value. */
|
||||
private readonly idKinds = new Map<string, PrimitiveKind>();
|
||||
|
||||
/** The primitive type an argument element stands for, if any. */
|
||||
private kindOf(element: Element): PrimitiveKind | undefined {
|
||||
if (PRIMITIVE_TAGS.has(element.name)) return element.name as PrimitiveKind;
|
||||
const idref = element.attributes.idref;
|
||||
return idref === undefined ? undefined : this.idKinds.get(idref);
|
||||
}
|
||||
|
||||
topLevel(java: Element): JavaValue[] {
|
||||
return java.children.map((child) => this.expression(child));
|
||||
@@ -332,6 +371,10 @@ class Decoder {
|
||||
|
||||
private primitive(element: Element, value: JavaValue): JavaValue {
|
||||
this.store(element.attributes.id, value);
|
||||
const id = element.attributes.id;
|
||||
// An element of its own (<int>), or a constant named by its wrapper (Integer0).
|
||||
const kind = PRIMITIVE_TAGS.has(element.name) ? (element.name as PrimitiveKind) : KIND_OF_ID_PREFIX[id?.replace(/\d+$/, '') ?? ''];
|
||||
if (id !== undefined && kind !== undefined) this.idKinds.set(id, kind);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -382,7 +425,10 @@ class Decoder {
|
||||
factoryMethod: attributes.method,
|
||||
});
|
||||
this.store(attributes.id, object);
|
||||
for (const arg of args) object.args.push(this.expression(arg));
|
||||
for (const arg of args) {
|
||||
object.args.push(this.expression(arg));
|
||||
object.argKinds.push(this.kindOf(arg));
|
||||
}
|
||||
this.applyStatements(
|
||||
element.children.filter((child) => child.name === 'void'),
|
||||
object,
|
||||
@@ -400,12 +446,14 @@ class Decoder {
|
||||
private statement(element: Element, target: JavaValue | undefined): JavaValue {
|
||||
const attributes = element.attributes;
|
||||
const args: JavaValue[] = [];
|
||||
const kinds: (PrimitiveKind | undefined)[] = [];
|
||||
const nested: Element[] = [];
|
||||
for (const child of element.children) {
|
||||
if (child.name === 'void') {
|
||||
nested.push(child);
|
||||
} else {
|
||||
args.push(this.expression(child));
|
||||
kinds.push(this.kindOf(child));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,17 +464,23 @@ class Decoder {
|
||||
value =
|
||||
args.length === 0
|
||||
? this.getProperty(target, attributes.property, attributes.id)
|
||||
: this.setProperty(target, attributes.property, args[0]!);
|
||||
: this.setProperty(target, attributes.property, args[0]!, kinds[0]);
|
||||
} else if (attributes.index !== undefined) {
|
||||
const index = Number.parseInt(attributes.index, 10);
|
||||
value = args.length === 0 ? this.getIndex(target, index, attributes.id) : this.setIndex(target, index, args[0]!);
|
||||
} else if (attributes.method !== undefined) {
|
||||
value = this.call(target, attributes.method, args, attributes.id);
|
||||
value = this.call(target, attributes.method, args, attributes.id, kinds);
|
||||
} else if (attributes.idref !== undefined) {
|
||||
value = this.ids.get(attributes.idref) ?? null;
|
||||
}
|
||||
|
||||
this.store(attributes.id, value);
|
||||
// A getter's primitive result, shared by id: its wrapper names the type.
|
||||
const prefix = attributes.id?.replace(/\d+$/, '');
|
||||
const kind = prefix === undefined ? undefined : KIND_OF_ID_PREFIX[prefix];
|
||||
if (kind !== undefined && (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean')) {
|
||||
this.idKinds.set(attributes.id!, kind);
|
||||
}
|
||||
this.applyStatements(nested, value);
|
||||
return value;
|
||||
}
|
||||
@@ -439,10 +493,16 @@ class Decoder {
|
||||
return newObject({ className, id, args, factoryMethod: method });
|
||||
}
|
||||
|
||||
private call(target: JavaValue | undefined, method: string, args: JavaValue[], id: string | undefined): JavaValue {
|
||||
private call(
|
||||
target: JavaValue | undefined,
|
||||
method: string,
|
||||
args: JavaValue[],
|
||||
id: string | undefined,
|
||||
kinds: (PrimitiveKind | undefined)[] = [],
|
||||
): JavaValue {
|
||||
if (typeof target === 'object' && target !== null && (target as JavaField).kind === 'field') {
|
||||
const field = target as JavaField;
|
||||
if (method === 'set' && args.length === 2) return this.setProperty(args[0]!, field.name, args[1]!);
|
||||
if (method === 'set' && args.length === 2) return this.setProperty(args[0]!, field.name, args[1]!, kinds[1]);
|
||||
if (method === 'get' && args.length === 1) return this.getProperty(args[0]!, field.name, id);
|
||||
}
|
||||
if (args.length === 0) {
|
||||
@@ -450,15 +510,20 @@ class Decoder {
|
||||
if (property !== undefined) return this.getProperty(target, property, id);
|
||||
}
|
||||
if (isJavaObject(target)) {
|
||||
target.calls.push({ method, args });
|
||||
target.calls.push(kinds.some((kind) => kind !== undefined) ? { method, args, kinds } : { method, args });
|
||||
if (method === 'get' && args.length === 1 && typeof args[0] === 'number') return this.getIndex(target, args[0], id);
|
||||
if (method === 'get' && args.length === 1 && typeof args[0] === 'string') return this.getEntry(target, args[0], id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private setProperty(target: JavaValue | undefined, property: string, value: JavaValue): JavaValue {
|
||||
if (isJavaObject(target)) target.properties.set(property, value);
|
||||
private setProperty(target: JavaValue | undefined, property: string, value: JavaValue, kind?: PrimitiveKind): JavaValue {
|
||||
if (isJavaObject(target)) {
|
||||
target.properties.set(property, value);
|
||||
target.propertiesFromGetters.delete(property);
|
||||
if (kind === undefined) target.propertyKinds.delete(property);
|
||||
else target.propertyKinds.set(property, kind);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -482,6 +547,7 @@ class Decoder {
|
||||
}
|
||||
const placeholder = newObject({ className: prefix ?? 'unknown', id, fromGetter: true });
|
||||
target.properties.set(property, placeholder);
|
||||
target.propertiesFromGetters.add(property);
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
// the web server, the peer-to-peer application and the e-mail server make
|
||||
// their directories only where the files lack them, and the DNS server reads
|
||||
// /dns/hosts when it is installed. Each is put under its key and given the
|
||||
// system software, as installApp does.
|
||||
// system software, as installApp does. A project's own application is
|
||||
// written as the file had it (beanCapture.ts).
|
||||
//
|
||||
// E-mail: the server's domain and bean accounts with their mailboxes, the
|
||||
// client's three folders and address book. The client's account is no bean
|
||||
@@ -33,6 +34,7 @@
|
||||
// FILIUS to the same topology and system software (topologyWriter.test.ts,
|
||||
// tools/java-oracle).
|
||||
|
||||
import { withSystemSoftware } from './beanCapture.js';
|
||||
import {
|
||||
encodeJavaBeans,
|
||||
int,
|
||||
@@ -165,7 +167,8 @@ function device(node: NodeToWrite, ports: BeanValue[]): BeanObject {
|
||||
);
|
||||
}
|
||||
// Named for the applications, which are given it.
|
||||
const system = (node.system.applications?.length ?? 0) > 0 ? reference('SystemSoftware') : undefined;
|
||||
const applicationCount = (node.system.applications?.length ?? 0) + (node.system.ownApplications?.length ?? 0);
|
||||
const system = applicationCount > 0 ? reference('SystemSoftware') : undefined;
|
||||
const systemStatementList = systemStatements(node.system, node.javaClass, system);
|
||||
statements.push(
|
||||
system === undefined
|
||||
@@ -198,13 +201,20 @@ function systemStatements(system: SystemToWrite, javaClass: FlsNodeClass, self:
|
||||
});
|
||||
}
|
||||
if (system.fileSystem !== undefined) statements.push(fileSystemStatement(system.fileSystem, system.templateFiles !== false));
|
||||
if (self !== undefined && system.applications !== undefined) {
|
||||
if (self !== undefined) {
|
||||
statements.push({
|
||||
get: 'installierteAnwendungen',
|
||||
statements: system.applications.map((application) => ({
|
||||
call: 'put',
|
||||
args: [application.key, object(application.javaClass, applicationStatements(application, self))],
|
||||
})),
|
||||
statements: [
|
||||
...(system.applications ?? []).map((application): BeanStatement => ({
|
||||
call: 'put',
|
||||
args: [application.key, object(application.javaClass, applicationStatements(application, self))],
|
||||
})),
|
||||
// A project's own: its bean as the file had it, on this node's system software.
|
||||
...(system.ownApplications ?? []).map((application): BeanStatement => ({
|
||||
call: 'put',
|
||||
args: [application.key, withSystemSoftware(application.bean, self)],
|
||||
})),
|
||||
],
|
||||
});
|
||||
}
|
||||
if (system.dhcpServer !== undefined) {
|
||||
|
||||
Reference in New Issue
Block a user