Files
mdg-filius/packages/fls/src/javaFileSystem.ts
T
julian.kiedaischandClaude Opus 5.5 ec5523d7c7 feat(fls): Java's view of the e-mail programs
The Java view now reads what FILIUS 2.14 keeps for its e-mail programs:
the server's domain, switch and bean accounts with their mailboxes, and the
accounts of /mailserver/konten.txt, which Java reads at every simulation
start and which so win over the bean list; the client's three folders and
address book, and its account from /konten.txt (no bean holds it).

javaEmail.ts ports the bean setters and both file readers
(stringZuListeBenutzerkonten, EmailKonto(String)); javaString.ts Java's
trim, split and StringTokenizer; javaFileSystem.ts the file lookup, now
shared with the DNS server, with the four non-ASCII units Java's
equalsIgnoreCase also takes for an ASCII letter (measured over the BMP).

Checked against Java on the corpus (the oracle dumps the e-mail state) and
on 43 account files read by EmailServer.kontenLaden and
EmailAnwendung.laden (EmailFileCases). Found: older files keep recipients
as strings in the typed lists; a getter copy of the sender is filled and
set back; one short server line makes Java throw for the whole file; a
message with an empty text is lost on reading; a broken last client line
empties the account.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 07:06:55 +00:00

52 lines
2.0 KiB
TypeScript

// How Java FILIUS finds a file by path (Dateisystem.holeDatei): the first
// node in preorder whose path equals the path ignoring case, and only if that
// node is a file.
import type { FlsFileTree } from './systemSoftware.js';
/**
* The content of the file at an ASCII path as `Dateisystem.holeDatei` finds
* it. Undefined when there is none; a file without content reads as empty
* (Java would fail on it).
*/
export function javaFileContent(root: FlsFileTree | undefined, asciiPath: string): string | undefined {
if (root === undefined) return undefined;
const found = firstInPreorder(root, '', (path) => javaEqualsIgnoreCase(path, asciiPath));
return found?.kind === 'file' ? (found.content ?? '') : undefined;
}
/** Preorder below the root; a node's path is "/" plus each name after the root's. */
function firstInPreorder(node: FlsFileTree, path: string, matches: (path: string) => boolean): FlsFileTree | undefined {
for (const child of node.children) {
const childPath = `${path}/${child.kind === 'other' ? child.className : String(child.name)}`;
if (matches(childPath)) return child;
const found = firstInPreorder(child, childPath, matches);
if (found !== undefined) return found;
}
return undefined;
}
// Besides ASCII case, String.equalsIgnoreCase takes these BMP units for an
// ASCII letter (measured on the JDK over every unit): dotted and dotless I,
// the Kelvin sign and the long s.
const JAVA_ASCII_CASE_FOLDS = new Map([
['\u0130', 'i'],
['\u0131', 'i'],
['\u212A', 'k'],
['\u017F', 's'],
]);
/** `String.equalsIgnoreCase` against an ASCII path, unit by unit. */
function javaEqualsIgnoreCase(text: string, asciiPath: string): boolean {
if (text.length !== asciiPath.length) return false;
for (let index = 0; index < text.length; index++) {
if (asciiFold(text[index]!) !== asciiFold(asciiPath[index]!)) return false;
}
return true;
}
function asciiFold(unit: string): string {
if (unit >= 'A' && unit <= 'Z') return unit.toLowerCase();
return JAVA_ASCII_CASE_FOLDS.get(unit) ?? unit;
}