11package se .kth .spork .cli ;
22
3- import se .kth .spork .spoon .ConflictInfo ;
3+ import se .kth .spork .spoon .StructuralConflict ;
44import spoon .compiler .Environment ;
5+ import spoon .reflect .cu .SourcePosition ;
56import spoon .reflect .declaration .CtElement ;
67import spoon .reflect .visitor .DefaultJavaPrettyPrinter ;
78import spoon .reflect .visitor .PrinterHelper ;
89import 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+
1016public 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}
0 commit comments