package ssw.mj; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import ssw.mj.scanner.Token; import java.awt.*; import java.io.IOException; import java.lang.reflect.Field; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; import java.util.List; public class Visualizer { // Config Flags private static final boolean debug = false; private static final String visualizerUrl = "https://ssw.jku.at/General/Staff/Weninger/Teaching/CB/Visualization/v1_0"; // private static final String visualizerUrl = "http://localhost:5173/General/Staff/Weninger/Teaching/CB/Visualization/v1_0"; public static final Gson gson = debug ? new GsonBuilder().setPrettyPrinting().create() : new Gson(); // Remember when the browser was last opened, to ensure that we // don't open a huge number of tabs private static LocalDateTime browserLastOpened; public static class VisualizerException extends RuntimeException { public VisualizerException(String message) { super("[Recorder]: " + message); } } private interface WithDescription { String getDescription(); } public enum VisualizationType implements WithDescription { SCANNER_V1 { @Override public String getDescription() { return "Scanner Visualization"; } }, PARSER_V1 { @Override public String getDescription() { return "Parser Visualization"; } } } private record ScannerPayload(List receivedTokens, List expectedTokens) { public String toJson() { return gson.toJson(this); } } private record ParserPayload(List traces) { public String toJson() { return gson.toJson(this); } } /** * Transforms scanner tokens to our visualizer format and ingests them to the desired method * * @param receivedTokens The tokens received from the student's scanner * @param expectedTokens The tokens expected by the test case */ public static void createScannerVisualization(String sourcecode, List receivedTokens, List expectedTokens, boolean openInBrowserAfterwards) { String payload = new ScannerPayload(receivedTokens, expectedTokens).toJson(); VisualizerIngestData visData = new VisualizerIngestData(VisualizationType.SCANNER_V1, sourcecode, payload); Path path = ingestFile(visData); if (openInBrowserAfterwards) { openBrowser(path.toUri().toString()); } } /** * Get the secret recorder from the parser and ingest the tokens to the visualizer */ public static void createParserVisualization(String sourcecode, boolean openInBrowserAfterwards) { Recorder recorder = getRecorderFromParserUsingReflection(); // If we don't have a recorder, we can't create a visualization if (recorder == null) return; String payload = new ParserPayload(recorder.getTrace()).toJson(); VisualizerIngestData visData = new VisualizerIngestData(VisualizationType.PARSER_V1, sourcecode, payload); Path path = ingestFile(visData); if (openInBrowserAfterwards) { openBrowser(path.toUri().toString()); } } // Data to be sent to the ingest endpoint private record VisualizerIngestData(VisualizationType type, String sourcecode, String payload) { public String toJson() { return gson.toJson(this); } } /** * Create a visualization file * * @param data The visualization data * @return The path at which the visualization file has been created */ private static Path ingestFile(VisualizerIngestData data) { String html = "\n" + "\n" + " \n" + " \n" + " \n" + " Compiler Visualization\n" + " \n" + " \n" + "
\n" + "\n" + " \n" + "\n" + " \n" + " \n" + ""; try { // Get a temporary file to write the visualization to Path path = Files.createTempFile("microjava-visualization", ".html"); Files.writeString(path, html); System.out.println(data.type.getDescription() + " created successfully. View it at: " + path.toUri().toString()); return path; } catch (IOException e) { throw new VisualizerException("Failed to create visualization: " + e.getMessage()); } } /** * Try to open the browser if possible and not rate limited * * @param url The url to open */ private static void openBrowser(String url) { if (browserLastOpened != null && browserLastOpened.plusSeconds(5).isAfter(LocalDateTime.now())) { return; } if (Desktop.isDesktopSupported()) { try { Desktop.getDesktop().browse(new URI(url)); } catch (Exception e) { // Ignore } } } /** * Use reflection to get the secret recorder from the parser * * @return The recorder */ private static Recorder getRecorderFromParserUsingReflection() { try { // Get the parser class Class parserClass = Class.forName("ssw.mj.impl.Parser"); // __recorder__ is a static field that has secretly been added in the Parser class by the TracingClassLoader // during a run of the application / the unit tests with the CLI setting // -Djava.system.class.loader=ssw.mj.TracingClassLoader Field recorderField = parserClass.getField("__recorder__"); // Get the value of the field Recorder recorder = (Recorder) recorderField.get(null); return recorder; } catch (Exception e) { // throw new VisualizerException("Failed to get recorder from parser: " + e.getMessage()); // We don't want to throw an exception here, since we might not have a recorder // when the custom class loader is not used return null; } } }