// FILIUS' „Report“ in the documentation mode measured (ReportGenerator): // each project opened as the menu opens it, run for a while when asked (its // frames in FILIUS' packet log, the Lauscher, as after a simulation), then // the PDF Java writes, read back with iText's own parser: its pages and // their size, every piece of text with its place, font, size and colour, // every image with its place and size, the document's title and creator; // and what the report was made of (the Lauscher's cards, the nodes' cards). // An empty project too (no file). The reference for the editor's report // (packages/editor/test/report.test.ts). // // Usage: java -cp filius.jar:lib/*:classes ReportRun [:]... // java -cp filius.jar:lib/*:classes ReportRun --read ... (a PDF read back alone, with its SHA-256) import com.itextpdf.text.BaseColor; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.parser.GraphicsState; import com.itextpdf.text.pdf.parser.ImageRenderInfo; import com.itextpdf.text.pdf.parser.Matrix; import com.itextpdf.text.pdf.parser.PdfReaderContentParser; import com.itextpdf.text.pdf.parser.RenderListener; import com.itextpdf.text.pdf.parser.TextRenderInfo; import com.itextpdf.text.pdf.parser.Vector; import filius.gui.GUIContainer; import filius.gui.documentation.ReportGenerator; import filius.gui.netzwerksicht.GUIKnotenItem; import filius.hardware.NetzwerkInterface; import filius.hardware.knoten.InternetKnoten; import filius.rahmenprogramm.Information; import filius.rahmenprogramm.SzenarioVerwaltung; import filius.rahmenprogramm.nachrichten.Lauscher; import java.io.File; import java.io.FileOutputStream; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.swing.SwingUtilities; public class ReportRun { static float round(float value) { return Math.round(value * 100) / 100f; } static String colour(BaseColor color) { return color == null ? null : color.getRed() + "," + color.getGreen() + "," + color.getBlue(); } /** Every piece of text and every image of a page, in the order the page draws them. */ static class Listener implements RenderListener { final List texts = new ArrayList<>(); final List images = new ArrayList<>(); public void beginTextBlock() {} public void endTextBlock() {} public void renderText(TextRenderInfo info) { // Spaces alone (iText writes the ends of lines and Chunk.NEWLINE so) carry nothing. if (info.getText().trim().isEmpty()) return; try { Field field = TextRenderInfo.class.getDeclaredField("gs"); field.setAccessible(true); GraphicsState state = (GraphicsState) field.get(info); Vector start = info.getBaseline().getStartPoint(); // x|y|font|size|colour|text, one string a piece, so that the file stays small. texts.add(round(start.get(Vector.I1)) + "|" + round(start.get(Vector.I2)) + "|" + info.getFont().getPostscriptFontName() + "|" + round(state.getFontSize()) + "|" + colour(info.getFillColor()) + "|" + info.getText()); } catch (ReflectiveOperationException error) { throw new IllegalStateException(error); } } public void renderImage(ImageRenderInfo info) { Matrix matrix = info.getImageCTM(); Map image = new LinkedHashMap<>(); image.put("x", round(matrix.get(Matrix.I31))); image.put("y", round(matrix.get(Matrix.I32))); image.put("width", round(matrix.get(Matrix.I11))); image.put("height", round(matrix.get(Matrix.I22))); try { image.put("pixels", List.of(info.getImage().getBufferedImage().getWidth(), info.getImage().getBufferedImage().getHeight())); } catch (Exception error) { image.put("pixels", null); } images.add(image); } } static Map read(String pdf) throws Exception { Map result = new LinkedHashMap<>(); PdfReader reader = new PdfReader(pdf); Map info = reader.getInfo(); result.put("title", info.get("Title")); result.put("creator", info.get("Creator")); List pages = new ArrayList<>(); PdfReaderContentParser parser = new PdfReaderContentParser(reader); for (int number = 1; number <= reader.getNumberOfPages(); number++) { Listener listener = parser.processContent(number, new Listener()); Map page = new LinkedHashMap<>(); page.put("size", List.of(round(reader.getPageSize(number).getWidth()), round(reader.getPageSize(number).getHeight()))); page.put("texts", listener.texts); page.put("images", listener.images); pages.add(page); } result.put("pages", pages); reader.close(); return result; } static Map measure(String name, File pdfFolder) throws Exception { Map result = new LinkedHashMap<>(); // What the report is made of: the Lauscher's cards and every card of the nodes. List packetLogCards = new ArrayList<>(); for (String id : Lauscher.getLauscher().getInterfaceIDs()) { Map card = new LinkedHashMap<>(); card.put("mac", id); card.put("rows", Lauscher.getLauscher().getDaten(id, true, 0).length); packetLogCards.add(card); } result.put("packetLogCards", packetLogCards); List nodes = new ArrayList<>(); for (GUIKnotenItem item : GUIContainer.getGUIContainer().getKnotenItems()) { Map node = new LinkedHashMap<>(); node.put("name", item.getKnoten().holeAnzeigeName()); node.put("type", item.getKnoten().getClass().getSimpleName()); List macs = new ArrayList<>(); if (item.getKnoten() instanceof InternetKnoten) { for (NetzwerkInterface nic : ((InternetKnoten) item.getKnoten()).getNetzwerkInterfaces()) macs.add(nic.getMac()); } node.put("macs", macs); nodes.add(node); } result.put("nodes", nodes); String pdf = new File(pdfFolder, name.replace(".fls", "") + ".pdf").getPath(); ReportGenerator.getInstance().generateReport(pdf); result.put("pdf", read(pdf)); return result; } public static void main(String[] args) throws Exception { // FILIUS' window keeps the JVM alive: end it on any failure too. try { run(args); } catch (Throwable error) { error.printStackTrace(); System.exit(1); } System.exit(0); } interface Step { void run() throws Exception; } static void onEdt(Step step) throws Exception { Exception[] failed = new Exception[1]; SwingUtilities.invokeAndWait(() -> { try { step.run(); } catch (Exception error) { failed[0] = error; } }); if (failed[0] != null) throw failed[0]; } static void run(String[] args) throws Exception { if (args[0].equals("--read")) { Map read = new LinkedHashMap<>(); for (int index = 2; index < args.length; index++) { Map pdf = read(args[index]); byte[] digest = java.security.MessageDigest.getInstance("SHA-256").digest(java.nio.file.Files.readAllBytes(Path.of(args[index]))); StringBuilder hex = new StringBuilder(); for (byte each : digest) hex.append(String.format("%02x", each)); pdf.put("sha256", hex.toString()); read.put(Path.of(args[index]).getFileName().toString(), pdf); } try (FileOutputStream out = new FileOutputStream(args[1])) { out.write((FlsOracle.Json.write(read, "") + "\n").getBytes(StandardCharsets.UTF_8)); } return; } GUIContainer.getGUIContainer(); File pdfFolder = new File(args[1]); pdfFolder.mkdirs(); Map output = new LinkedHashMap<>(); Map projects = new LinkedHashMap<>(); for (int index = 2; index < args.length; index++) { String[] parts = args[index].split(":"); String file = parts[0]; long runFor = parts.length > 1 ? Long.parseLong(parts[1]) : 0; String name = Path.of(file).getFileName().toString(); onEdt(() -> { // As GUIMainMenu opens a file; the packet log starts empty, as after Lauscher.reset. Information.getInformation().reset(); Lauscher.getLauscher().reset(); SzenarioVerwaltung.getInstance().laden(file, GUIContainer.getGUIContainer().getKnotenItems(), GUIContainer.getGUIContainer().getCableItems(), GUIContainer.getGUIContainer().getDocuItems()); GUIContainer.getGUIContainer().setProperty(null); GUIContainer.getGUIContainer().updateViewport(); GUIContainer.getGUIContainer().updateCables(); }); if (runFor > 0) { // As GUIMainMenu.selectMode starts and ends a simulation: every node in the window's order. for (GUIKnotenItem item : GUIContainer.getGUIContainer().getKnotenItems()) item.getKnoten().getSystemSoftware().starten(); Thread.sleep(runFor); for (GUIKnotenItem item : GUIContainer.getGUIContainer().getKnotenItems()) item.getKnoten().getSystemSoftware().beenden(); Thread.sleep(500); } Map[] measured = new Map[1]; onEdt(() -> measured[0] = measure(name, pdfFolder)); projects.put(name, measured[0]); } output.put("projects", projects); Map[] empty = new Map[1]; onEdt(() -> { GUIContainer container = GUIContainer.getGUIContainer(); container.getKnotenItems().clear(); container.getCableItems().clear(); container.getDocuItems().clear(); SzenarioVerwaltung.getInstance().reset(); Lauscher.getLauscher().reset(); container.updateViewport(); empty[0] = measure("empty.fls", pdfFolder); }); output.put("empty", empty[0]); try (FileOutputStream out = new FileOutputStream(args[0])) { out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8)); } } }