Skip to content

Commit 0c9105a

Browse files
authored
Merge pull request #46 from VariantSync/benjamin/tikz
TikZ export of DiffTrees
2 parents 6c59ddd + 3a17a78 commit 0c9105a

21 files changed

Lines changed: 805 additions & 140 deletions

.github/workflows/maven.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@ name: Java CI with Maven
55

66
on:
77
push:
8-
branches: [ "main" ]
8+
branches: [ "main", "develop" ]
99
pull_request:
10-
branches: [ "main" ]
10+
branches: [ "main", "develop" ]
1111

1212
jobs:
1313
build:
1414

1515
runs-on: ubuntu-latest
1616

1717
steps:
18+
- name: Install Graphviz
19+
run: sudo apt-get install -y graphviz
1820
- uses: actions/checkout@v3
1921
- name: Set up JDK 17
2022
uses: actions/setup-java@v3

src/main/java/org/variantsync/diffdetective/diff/difftree/DiffNode.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,13 @@ public boolean isAdd() {
919919
return this.diffType.equals(DiffType.ADD);
920920
}
921921

922+
/**
923+
* Returns the diff type of this node.
924+
*/
925+
public DiffType getDiffType() {
926+
return this.diffType;
927+
}
928+
922929
/**
923930
* Returns true if this node represents an ELIF macro.
924931
* @see CodeType#ELIF

src/main/java/org/variantsync/diffdetective/diff/difftree/serialize/DiffTreeLineGraphExporter.java

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,60 @@
11
package org.variantsync.diffdetective.diff.difftree.serialize;
22

3-
import org.variantsync.diffdetective.diff.difftree.DiffNode;
3+
import java.io.OutputStream;
4+
import java.io.PrintStream;
5+
46
import org.variantsync.diffdetective.diff.difftree.DiffTree;
5-
import org.variantsync.diffdetective.util.StringUtils;
7+
import org.variantsync.diffdetective.diff.difftree.LineGraphConstants;
8+
import org.variantsync.functjonal.Functjonal;
69

710
/**
811
* Exporter that converts a single DiffTree's nodes and edges to linegraph.
912
*/
10-
public class DiffTreeLineGraphExporter {
11-
private final StringBuilder nodesString = new StringBuilder();
12-
private final StringBuilder edgesString = new StringBuilder();
13-
14-
private final DiffTree diffTree;
15-
13+
public class DiffTreeLineGraphExporter implements Exporter {
14+
private final Format format;
1615
private final DiffTreeSerializeDebugData debugData;
1716

18-
/**
19-
* Creates a new exporter that will export the given tree.
20-
*/
21-
public DiffTreeLineGraphExporter(DiffTree treeToExport) {
22-
this.diffTree = treeToExport;
17+
public DiffTreeLineGraphExporter(Format format) {
18+
this.format = format;
2319
this.debugData = new DiffTreeSerializeDebugData();
2420
}
2521

22+
public DiffTreeLineGraphExporter(DiffTreeLineGraphExportOptions options) {
23+
this(new Format(options.nodeFormat(), options.edgeFormat()));
24+
}
25+
2626
/**
27-
* Converts the given node and its edges to linegraph using the formats specified in the given options.
28-
* The produced linegraph statements will be added to the internal StringBuilders.
29-
* @param node The node to convert to linegraph format together with its edges.
30-
* @param options Options that specify the node and edge format to use.
27+
* Export a line graph of {@code diffTree} into {@code destination}.
28+
*
29+
* @param diffTree to be exported
30+
* @param destination where the result should be written
3131
*/
32-
private void visit(DiffNode node, DiffTreeLineGraphExportOptions options) {
33-
switch (node.diffType) {
34-
case ADD -> ++debugData.numExportedAddNodes;
35-
case REM -> ++debugData.numExportedRemNodes;
36-
case NON -> ++debugData.numExportedNonNodes;
37-
}
32+
@Override
33+
public void exportDiffTree(DiffTree diffTree, OutputStream destination) {
34+
var output = new PrintStream(destination);
35+
format.forEachNode(diffTree, (node) -> {
36+
switch (node.diffType) {
37+
case ADD -> ++debugData.numExportedAddNodes;
38+
case REM -> ++debugData.numExportedRemNodes;
39+
case NON -> ++debugData.numExportedNonNodes;
40+
}
3841

39-
nodesString
40-
.append(options.nodeFormat().toLineGraphLine(node))
41-
.append(StringUtils.LINEBREAK);
42+
output.println(LineGraphConstants.LG_NODE + " " + node.getID() + " " + format.getNodeFormat().toLabel(node));
43+
});
4244

43-
edgesString
44-
.append(options.edgeFormat().getParentEdgeLines(node));
45-
}
45+
format.forEachUniqueEdge(diffTree, (edges) -> {
46+
output.print(Functjonal.unwords(LineGraphConstants.LG_EDGE, edges.get(0).from().getID(), edges.get(0).to().getID(), ""));
4647

47-
/**
48-
* Export this exporter's tree using the given options.
49-
* This method will return the final linegraph as string.
50-
* The string will contain all linegraph statements for the tree's nodes and edges,
51-
* but not the tree header.
52-
* @param options Options that specify the node and edge format to use.
53-
* @return The linegraph as String.
54-
* @see LineGraphExport#composeTreeInLineGraph
55-
*/
56-
public String export(DiffTreeLineGraphExportOptions options) {
57-
diffTree.forAll(n -> visit(n, options));
58-
final String result = nodesString.toString() + edgesString;
59-
StringUtils.clear(nodesString);
60-
StringUtils.clear(edgesString);
61-
return result;
48+
for (var edge : edges) {
49+
output.print(edge.style().lineGraphType());
50+
}
51+
52+
for (var edge : edges) {
53+
output.print(format.getEdgeFormat().labelOf(edge));
54+
}
55+
56+
output.println();
57+
});
6258
}
6359

6460
/**
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.variantsync.diffdetective.diff.difftree.serialize;
2+
3+
import java.io.IOException;
4+
import java.io.OutputStream;
5+
import org.variantsync.diffdetective.diff.difftree.DiffTree;
6+
7+
/**
8+
* Common interface for serialisation of a single {@code DiffTree}.
9+
* Not all formats have to provide a way to deserialize a {@link DiffTree} from this format.
10+
*
11+
* @author Benjamin Moosherr
12+
*/
13+
public interface Exporter {
14+
/**
15+
* Export a {@code diffTree} into {@code destination}.
16+
*
17+
* This method should have no side effects besides writing to {@code destination}. Above all,
18+
* {@code diffTree} shouldn't be modified. Furthermore, {@code destination} shouldn't be
19+
* closed to allow the embedding of the exported format into a surrounding file.
20+
*
21+
* It can be assumed, that {@code destination} is sufficiently buffered.
22+
*
23+
* @param diffTree to be exported
24+
* @param destination where the result should be written
25+
*/
26+
void exportDiffTree(DiffTree diffTree, OutputStream destination) throws IOException;
27+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package org.variantsync.diffdetective.diff.difftree.serialize;
2+
3+
import java.util.List;
4+
import java.util.function.Consumer;
5+
import org.variantsync.diffdetective.diff.difftree.DiffNode;
6+
import org.variantsync.diffdetective.diff.difftree.DiffTree;
7+
import org.variantsync.diffdetective.diff.difftree.serialize.edgeformat.EdgeLabelFormat;
8+
import org.variantsync.diffdetective.diff.difftree.serialize.nodeformat.DiffNodeLabelFormat;
9+
10+
/**
11+
* Format used for exporting a {@link DiffTree}.
12+
* For easy reusability this class is composed of separate node and edge formats.
13+
*
14+
* The exported {@link DiffTree} can be influenced in the following ways:
15+
* - Providing both a node and an edge label format.
16+
* - Changing the order, filtering or adding the nodes and edges by creating a subclass of {@code
17+
* Format}.
18+
*/
19+
public class Format {
20+
private final DiffNodeLabelFormat nodeFormat;
21+
private final EdgeLabelFormat edgeFormat;
22+
23+
public Format(DiffNodeLabelFormat nodeFormat, EdgeLabelFormat edgeFormat) {
24+
this.nodeFormat = nodeFormat;
25+
this.edgeFormat = edgeFormat;
26+
}
27+
28+
public DiffNodeLabelFormat getNodeFormat() {
29+
return nodeFormat;
30+
}
31+
32+
public EdgeLabelFormat getEdgeFormat() {
33+
return edgeFormat;
34+
}
35+
36+
/**
37+
* Iterates over all {@link DiffNode}s in {@code diffTree} and calls {@code callback}.
38+
*
39+
* Exporters should use this method to enable subclasses of {@code Format} to filter nodes, add
40+
* new nodes and change the order of the exported nodes.
41+
*
42+
* This implementation is equivalent to {@link DiffTree#forAll}.
43+
*
44+
* @param diffTree to be exported
45+
* @param callback is called for each node
46+
*/
47+
public void forEachNode(DiffTree diffTree, Consumer<DiffNode> callback) {
48+
diffTree.forAll(callback);
49+
}
50+
51+
/**
52+
* Iterates over all edges in {@code diffTree} and calls {@code callback}.
53+
*
54+
* Exporters should use this method to enable subclasses of {@code Format} to filter edges, add
55+
* new edges and change the order of the exported edges.
56+
*
57+
* This implementation uses {@link forEachUniqueEdge} by calling {@code callback} for each edge
58+
* in the order given by the lists of {@link forEachUniqueEdge}.
59+
*
60+
* @param diffTree to be exported
61+
* @param callback is called for each edge
62+
*/
63+
public void forEachEdge(DiffTree diffTree, Consumer<StyledEdge> callback) {
64+
forEachUniqueEdge(diffTree, (edges) -> {
65+
for (var edge : edges) {
66+
callback.accept(edge);
67+
}
68+
});
69+
}
70+
71+
/**
72+
* Iterates over all edges in {@code diffTree} and calls {@code callback}, visiting parallel edges only once.
73+
*
74+
* Two edges are parallel if they start at the same node and end at the same node. Note that
75+
* the direction of directed edges matters.
76+
*
77+
* All parallel edges are collected into a list and are passed once to {@code callback}.
78+
*
79+
* Exporters should use this method to enable subclasses of {@code Format} to filter edges, add
80+
* new edges and change the order of the exported edges.
81+
*
82+
* @param diffTree to be exported
83+
* @param callback is called for each unique edge
84+
*/
85+
public void forEachUniqueEdge(DiffTree diffTree, Consumer<List<StyledEdge>> callback) {
86+
diffTree.forAll((node) -> {
87+
var beforeParent = node.getBeforeParent();
88+
var afterParent = node.getAfterParent();
89+
90+
// Are both parent edges the same?
91+
if (beforeParent != null && afterParent != null && beforeParent == afterParent) {
92+
callback.accept(List.of(beforeEdge(node), afterEdge(node)));
93+
} else {
94+
if (beforeParent != null) {
95+
callback.accept(List.of(beforeEdge(node)));
96+
}
97+
if (afterParent != null) {
98+
callback.accept(List.of(afterEdge(node)));
99+
}
100+
}
101+
});
102+
}
103+
104+
/**
105+
* Constructs a {@link StyledEdge} from {@code node} and its before parent.
106+
*
107+
* The order of these nodes is permuted according to {@link EdgeLabelFormat#getEdgeDirection}
108+
* of {@link getEdgeFormat()}.
109+
*/
110+
protected StyledEdge beforeEdge(DiffNode node) {
111+
return sortedEdgeWithLabel(node, node.getBeforeParent(), StyledEdge.BEFORE);
112+
}
113+
114+
/**
115+
* Constructs a {@link StyledEdge} from {@code node} and its after parent.
116+
*
117+
* The order of these nodes is permuted according to {@link EdgeLabelFormat#getEdgeDirection}
118+
* of {@link getEdgeFormat()}.
119+
*/
120+
protected StyledEdge afterEdge(DiffNode node) {
121+
return sortedEdgeWithLabel(node, node.getAfterParent(), StyledEdge.AFTER);
122+
}
123+
124+
/**
125+
* Constructs a {@link StyledEdge} from {@code originalFrom} to {@code originalTo}.
126+
*
127+
* The order of these nodes is permuted according to {@link EdgeLabelFormat#getEdgeDirection}
128+
* of {@link getEdgeFormat()}.
129+
*
130+
* @param originalFrom the origin of the constructed edge
131+
* @param originalTo the destination of the constructed edge
132+
* @param style the export style of the constructed edge
133+
* @return a new {@link StyledEdge}
134+
*/
135+
protected StyledEdge sortedEdgeWithLabel(DiffNode originalFrom, DiffNode originalTo, StyledEdge.Style style) {
136+
var edge = edgeFormat.getEdgeDirection().sort(originalFrom, originalTo);
137+
return new StyledEdge(edge.first(), edge.second(), style);
138+
}
139+
}

0 commit comments

Comments
 (0)