Skip to content

Commit 29990ca

Browse files
committed
[fix] Redesign structural conflict handling #55
Now uses a dummy node with both parts of the conflict as metadata, which allows handling of structural conflicts in places where you cannot put in an arbitrary amount of nodes
1 parent 434eea4 commit 29990ca

13 files changed

Lines changed: 245 additions & 147 deletions

File tree

Lines changed: 110 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
package se.kth.spork.cli;
22

3-
import se.kth.spork.spoon.ConflictInfo;
3+
import se.kth.spork.spoon.StructuralConflict;
44
import spoon.compiler.Environment;
5+
import spoon.reflect.cu.SourcePosition;
56
import spoon.reflect.declaration.CtElement;
67
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
78
import spoon.reflect.visitor.PrinterHelper;
89
import spoon.reflect.visitor.TokenWriter;
910

11+
import java.io.IOException;
12+
import java.io.RandomAccessFile;
13+
import java.nio.charset.Charset;
14+
import java.util.List;
15+
1016
public class SporkPrettyPrinter extends DefaultJavaPrettyPrinter {
1117
public static final String START_CONFLICT = "<<<<<<< LEFT";
1218
public static final String MID_CONFLICT = "=======";
@@ -24,58 +30,121 @@ public SporkPrettyPrinter(Environment env) {
2430

2531
@Override
2632
public SporkPrettyPrinter scan(CtElement e) {
27-
if (e == null || !e.getMetadataKeys().contains(ConflictInfo.CONFLICT_METADATA)) {
33+
if (e == null || !e.getMetadataKeys().contains(StructuralConflict.STRUCTURAL_CONFLICT_METADATA_KEY)) {
2834
super.scan(e);
2935
return this;
3036
}
3137

32-
// relies on the fact that structural conflicts can only occur in ordered nodes
33-
// in unordered nodes, there's no guarantee that a start marker comes before an end marker,
34-
// for example
35-
ConflictInfo conflictInfo = (ConflictInfo) e.getMetadata("SPORK_CONFLICT");
36-
switch (conflictInfo.marker) {
37-
case LEFT_START:
38-
writeConflictMarker(START_CONFLICT);
39-
super.scan(e);
40-
if (conflictInfo.rightSize == 0) {
41-
getPrinterTokenWriter().writeln();
42-
writeConflictMarker(MID_CONFLICT);
43-
writeConflictMarker(END_CONFLICT);
44-
}
45-
break;
46-
case RIGHT_START:
47-
if (conflictInfo.leftSize == 0) {
48-
writeConflictMarker(START_CONFLICT);
49-
}
50-
writeConflictMarker(MID_CONFLICT);
51-
super.scan(e);
52-
if (conflictInfo.rightSize == 1) {
53-
getPrinterTokenWriter().writeln();
54-
writeConflictMarker(END_CONFLICT);
55-
}
56-
break;
57-
case RIGHT_END:
58-
assert conflictInfo.rightSize > 1;
59-
super.scan(e);
60-
getPrinterTokenWriter().writeln();
61-
writeConflictMarker(END_CONFLICT);
62-
break;
63-
default:
64-
throw new RuntimeException("Internal error, couldn't finish writing conflict");
65-
}
38+
StructuralConflict structuralConflict =
39+
(StructuralConflict) e.getMetadata( StructuralConflict.STRUCTURAL_CONFLICT_METADATA_KEY);
40+
writeStructuralConflict(structuralConflict);
6641

6742
return this;
6843
}
6944

70-
private void writeConflictMarker(String conflictMarker) {
71-
TokenWriter printer = getPrinterTokenWriter();
72-
PrinterHelper helper = printer.getPrinterHelper();
73-
int tabCount = helper.getTabCount();
45+
/**
46+
* Write both pats of a structural conflict.
47+
*/
48+
private void writeStructuralConflict(StructuralConflict structuralConflict) {
49+
String leftSource = getOriginalSource(structuralConflict.left);
50+
String rightSource = getOriginalSource(structuralConflict.right);
51+
52+
PrinterHelper helper = getPrinterTokenWriter().getPrinterHelper();
53+
int tabBefore = helper.getTabCount();
7454
helper.setTabCount(0);
7555

56+
helper.writeln();
57+
writeConflictMarker(START_CONFLICT);
58+
writelnNonEmpty(leftSource);
59+
writeConflictMarker(MID_CONFLICT);
60+
writelnNonEmpty(rightSource);
61+
writeConflictMarker(END_CONFLICT);
62+
63+
helper.setTabCount(tabBefore);
64+
}
65+
66+
private void writeConflictMarker(String conflictMarker) {
67+
TokenWriter printer = getPrinterTokenWriter();
7668
printer.writeLiteral(conflictMarker);
7769
printer.writeln();
70+
}
71+
72+
/**
73+
* Write the provided string plus a line ending only if the string is non-empty.
74+
*/
75+
private void writelnNonEmpty(String s) {
76+
if (s.isEmpty())
77+
return;
78+
79+
PrinterHelper helper = getPrinterTokenWriter().getPrinterHelper();
80+
81+
helper.write(s);
82+
helper.writeln();
83+
}
84+
85+
/**
86+
* Get the original source fragment corresponding to the nodes provided, or an empty string if the list is
87+
* empty. Note that the nodes must be adjacent in the source file, and in the same order as in the source.
88+
*
89+
* @param nodes A possibly empty list of adjacent nodes.
90+
* @return The original source code fragment, including any leading indentation on the first line.
91+
*/
92+
private static String getOriginalSource(List<CtElement> nodes) {
93+
if (nodes.isEmpty())
94+
return "";
95+
96+
SourcePosition firstElemPos = nodes.get(0).getPosition();
97+
SourcePosition lastElemPos = nodes.get(nodes.size() - 1).getPosition();
98+
try (RandomAccessFile file = new RandomAccessFile(firstElemPos.getFile(), "r")) {
99+
return getOriginalSource(firstElemPos, lastElemPos, file);
100+
} catch (IOException e) {
101+
e.printStackTrace();
102+
throw new RuntimeException("failed to read original source fragments");
103+
}
104+
}
105+
106+
/**
107+
* Get the original source code fragment starting at start and ending at end, including indentation if start is
108+
* the first element on its source code line.
109+
*
110+
* @param start The source position of the first element.
111+
* @param end The source position of the last element.
112+
* @param randomAccessFile A random access file pointing to the common source of the start and end elements.
113+
* @return The source code fragment starting at start and ending at end, including leading indentation.
114+
* @throws IOException
115+
*/
116+
private static String getOriginalSource(SourcePosition start, SourcePosition end, RandomAccessFile randomAccessFile) throws IOException {
117+
int startByte = precededByIndentation(randomAccessFile, start) ?
118+
getLineStartByte(start) : start.getSourceStart();
119+
int endByte = end.getSourceEnd();
120+
121+
byte[] content = new byte[endByte - startByte + 1];
122+
123+
randomAccessFile.seek(startByte);
124+
randomAccessFile.read(content);
125+
126+
return new String(content, Charset.defaultCharset());
127+
}
128+
129+
private static boolean precededByIndentation(RandomAccessFile file, SourcePosition pos) throws IOException {
130+
int lineStartByte = getLineStartByte(pos);
131+
byte[] before = new byte[pos.getSourceStart() - lineStartByte];
132+
133+
file.seek(lineStartByte);
134+
file.read(before);
135+
136+
String s = new String(before, Charset.defaultCharset());
137+
return s.matches("\\s+");
138+
}
139+
140+
private static int getLineStartByte(SourcePosition pos) {
141+
if (pos.getLine() == 1)
142+
return 0;
78143

79-
helper.setTabCount(tabCount);
144+
// the index is offset by 2 because:
145+
// 1. zero-indexing means -1
146+
// 2. line separator indexing means that the 0th line separator corresponds to the _end_ of the first line
147+
int prevLineEnd = pos.getLine() - 2;
148+
return pos.getCompilationUnit().getLineSeparatorPositions()[prevLineEnd] + 1; // +1 to get the next line start
80149
}
81150
}

src/main/java/se/kth/spork/spoon/ConflictInfo.java

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)