// 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; }