summaryrefslogtreecommitdiff
path: root/support/latex-dependency-grapher/source/main/java/ch/bfh/lpdg/GraphHelper.java
blob: fd6a754b7d998808474aaed3333aeb1083354390 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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<String, Integer> 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<String> 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;
        }
    }
}