001package com.studentgui.apphelpers; 002 003import java.io.BufferedReader; 004import java.io.InputStreamReader; 005import java.nio.file.Path; 006import java.util.function.Consumer; 007 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * Helper to invoke the repository's Python plot runner asynchronously. 013 * <p> 014 * This wrapper launches the repository's Python runner script in a 015 * background thread and collects its combined output. It is used by some 016 * legacy pages; newer pages prefer the Java-based charting helpers. 017 * </p> 018 */ 019public class PythonPlotter { 020 private static final Logger LOG = LoggerFactory.getLogger(PythonPlotter.class); 021 022 /** 023 * Run the python runner for the given module and student name in a background thread. 024 * The {@code onComplete} consumer receives combined stdout/stderr text when the 025 * process finishes. 026 * 027 * @param moduleName module identifier passed to the python runner (non-null) 028 * @param studentName student display name used by the plotter (non-null) 029 * @param onComplete optional consumer receiving process output when complete; may be null 030 */ 031 public static void runPlotAsync(final String moduleName, final String studentName, final Consumer<String> onComplete) { 032 if (studentName == null || studentName.trim().isEmpty()) { 033 String msg = "No student selected for plot generation"; 034 LOG.warn(msg); 035 if (onComplete != null) { 036 onComplete.accept(msg); 037 } 038 return; 039 } 040 041 Path script = Helpers.PROJECT_ROOT.resolve("appPages").resolve("run_plot.py"); 042 043 Thread t = new Thread(() -> { 044 StringBuilder out = new StringBuilder(); 045 try { 046 ProcessBuilder pb = new ProcessBuilder("python", script.toString(), moduleName, studentName); 047 pb.directory(Helpers.PROJECT_ROOT.toFile()); 048 pb.redirectErrorStream(true); 049 Process p = pb.start(); 050 try (BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()))) { 051 String line; 052 while ((line = r.readLine()) != null) { 053 out.append(line).append(System.lineSeparator()); 054 } 055 } 056 int rc = p.waitFor(); 057 out.append("Exit code: ").append(rc).append(System.lineSeparator()); 058 } catch (java.io.IOException | InterruptedException e) { 059 LOG.error("Error running python plot runner", e); 060 out.append("Error: ").append(e.toString()).append(System.lineSeparator()); 061 if (e instanceof InterruptedException) { 062 Thread.currentThread().interrupt(); 063 } 064 } 065 if (onComplete != null) { 066 try { 067 onComplete.accept(out.toString()); 068 } catch (Exception ex) { 069 // Log and continue: we don't want a faulty consumer to terminate 070 // the worker thread unexpectedly. Avoid catching Error/Throwable. 071 LOG.warn("onComplete consumer threw an exception; continuing. Output length={}", out.length(), ex); 072 } 073 } 074 }, "PythonPlotter-" + moduleName); 075 t.setDaemon(true); 076 t.start(); 077 } 078 079 /** 080 * Private constructor to prevent instantiation of this helper class. 081 */ 082 private PythonPlotter() { 083 // utility only 084 } 085}