package ch.bfh.lpdg; import ch.bfh.lpdg.datastructure.Dependency; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; public class GraphHelper { public final static GraphHelper INSTANCE = new GraphHelper(); private final InteractionHandler interactionHandler = InteractionHandler.getInstance(); private GraphHelper() { } public static GraphHelper getInstance() { return INSTANCE; } /** * This method creates a new .tex file containing a list of the found dependencies and the dependeccy graph created with Graphviz * * @param dependency for which the .tex file should be created * @param outputPath in which the created file should be located. * @return the path of the newly created .tex file. */ public Path createFileForDependency(final Dependency dependency, final String outputPath) { final String dependencyFileName = "dependencies_" + dependency.getName() + ".tex"; final String dependencyFilePath = outputPath + dependencyFileName; var dependencyFile = new File(outputPath + dependencyFileName); var latexHelper = new LatexHelper(); this.writeDependencyToFile(dependencyFile, dependency); var result = latexHelper.compileTempDocument(outputPath, dependencyFilePath); interactionHandler.printDebugMessage(String.valueOf(result)); if (!generatePdfFromDotFile(outputPath + "dependencyGraph.dot")) { interactionHandler.printDebugMessage("Something went wrong trying to generate a pdf from the dot file."); } //Second compile cycle to include the generated graph in the file. var result2 = latexHelper.compileTempDocument(outputPath, dependencyFilePath); interactionHandler.printDebugMessage(String.valueOf(result2)); return dependencyFile.toPath(); } /** * This method created the .tex file containing a list of the dependencies and its graph. * * @param file in which should be written * @param dependency the dependency for which the graph should be created */ private void writeDependencyToFile(final File file, final Dependency dependency) { try { final String dependencyList = dependency.toLaTeXString(); final String graphString = dependency.toGraphString(); final String truncatedGraphString = this.truncateGraphString(graphString); final String reorganizedGraphString = this.reorganizeGraphString(truncatedGraphString); BufferedWriter writer = Files.newBufferedWriter(file.toPath()); writer.write(""" \\documentclass{article} \\usepackage[pdf]{graphviz} \\usepackage{pdflscape} \\begin{document} \\begin{verbatim} """); writer.write(dependencyList); writer.write("\\end{verbatim}\n"); if (dependency.getDependencyList().size() != 0) { writer.write("\\pagebreak\n"); writer.write("\\begin{landscape}\n"); writer.write("\\digraph{dependencyGraph}{\n"); writer.write("\tnewrank=true;\n\t"); writer.write("\trank=\"same\";\n\t"); writer.write("\trankdir=TB;\n\t"); writer.write("\tratio=\"fill\";\n\t"); writer.write("\tsize=\"8.3,11.7!\";\n\t"); writer.write("\tmargin=0;\n\t"); writer.write(reorganizedGraphString); writer.write("}\n"); writer.write("\\end{landscape}\n"); } writer.write("\\end{document}\n"); writer.close(); } catch (IOException e) { System.err.println("Error in the temporary file: " + e); } } /** * Removes duplicated lines from graphString * * @param graphString for which duplicated lines should be removed * @return a truncated string */ private String truncateGraphString(final String graphString) { StringBuilder builder = new StringBuilder(); for (String line : new LinkedHashSet<>(Arrays.asList(graphString.split("\n")))) { builder.append(line).append("\n"); } return builder.toString(); } private String reorganizeGraphString(String graphString) { StringBuilder result = new StringBuilder(); String[] lines = graphString.split("\n"); Map lastOccurrences = new LinkedHashMap<>(); // Iterate to find the last occurrence index for each node for (int i = 0; i < lines.length; i++) { String line = lines[i].trim(); if (!line.isEmpty()) { String[] parts = line.split(" -> "); String rightNode = parts[1].substring(0, parts[1].indexOf(';')).trim(); lastOccurrences.put(rightNode, i); } } // Append lines with the last occurrences for (int i : lastOccurrences.values()) { result.append(lines[i]).append("\n"); } // Append lines with other occurrences for (int i = 0; i < lines.length; i++) { String line = lines[i].trim(); if (!line.isEmpty()) { String[] parts = line.split(" -> "); String rightNode = parts[1].substring(0, parts[1].indexOf(';')).trim(); // Skip if it's the last occurrence if (i != lastOccurrences.get(rightNode)) { result.append(lines[i]).append("\n"); } } } return result.toString(); } /** * This method generated a pdf document for the .dot document. * This is required to include it to the .tex document given to the user. * * @param dependencyGraphPath of the generated .dot file * @return if the generation was successful or not */ private boolean generatePdfFromDotFile(final String dependencyGraphPath) { try { List cmd = Arrays.asList( "dot", "-Tpdf", "-o", dependencyGraphPath.replace("dot", "pdf"), dependencyGraphPath); ProcessBuilder processBuilder = new ProcessBuilder(cmd); // Redirect the error stream to the output stream processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); var res = process.waitFor(); return res == 0; } catch (Exception e) { System.err.println("Error while compiling the dot file to pdf: " + e); return false; } } }