Skip to content

Commit

Permalink
[feat] Use original source for methods and fields (#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
slarse committed Apr 9, 2020
1 parent feb8ccd commit 4d5ffb6
Show file tree
Hide file tree
Showing 9 changed files with 367 additions and 297 deletions.
5 changes: 3 additions & 2 deletions src/main/java/se/kth/spork/cli/PrinterPreprocessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ public void scan(CtElement element) {
*/
private void handleIncorrectExplicitPackages(CtElement element) {
if (element instanceof CtPackageReference) {
String pkgName = ((CtPackageReference) element).getQualifiedName();
CtPackageReference pkgRef = (CtPackageReference) element;
String pkgName = pkgRef.getQualifiedName();
CtElement parent = element.getParent();
if (pkgName.equals(activePackage)) {
if (pkgName.equals(activePackage) || pkgRef.getSimpleName().isEmpty()) {
element.setImplicit(true);
} else if (parent instanceof CtTypeReference) {
String parentQualName = ((CtTypeReference<?>) parent).getQualifiedName();
Expand Down
146 changes: 146 additions & 0 deletions src/main/java/se/kth/spork/cli/SourceExtractor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package se.kth.spork.cli;

import spoon.reflect.cu.SourcePosition;
import spoon.reflect.declaration.CtElement;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;

/**
* A class for extracting source code and related information from Spoon nodes.
*
* @author Simon Larsén
*/
public class SourceExtractor {

/**
* Get the original source fragment corresponding to the nodes provided, or an empty string if the list is
* empty. Note that the nodes must be adjacent in the source file, and in the same order as in the source.
*
* @param nodes A possibly empty list of adjacent nodes.
* @return The original source code fragment, including any leading indentation on the first line.
*/
static String getOriginalSource(List<CtElement> nodes) {
if (nodes.isEmpty())
return "";

SourcePosition firstElemPos = getSourcePos(nodes.get(0));
SourcePosition lastElemPos = getSourcePos(nodes.get(nodes.size() - 1));
return getOriginalSource(firstElemPos, lastElemPos);
}

/**
* Get the original source code fragment associated with this element, including any leading indentation.
*
* @param elem A Spoon element.
* @return The original source code associated with the elemen.
*/
static String getOriginalSource(CtElement elem) {
SourcePosition pos = getSourcePos(elem);
return getOriginalSource(pos, pos);
}

/**
* Return the indentation count for this element. This is a bit hit-and-miss, but it usually works. It finds
* the line that the element starts on, and counts the amount of indentation characters until the first character
* on the line.
*
* @param elem A Spoon element.
* @return The amount of indentation characters preceding the first non-indentation character on the line this
* element is defined on.
*/
static int getIndentation(CtElement elem) {
SourcePosition pos = getSourcePos(elem);
byte[] fileBytes = pos.getCompilationUnit().getOriginalSourceCode().getBytes(Charset.defaultCharset());
int count = 0;

int[] lineSepPositions = pos.getCompilationUnit().getLineSeparatorPositions();
int current = 0;
while (current < lineSepPositions.length && lineSepPositions[current] > pos.getSourceStart())
current++;


while (current + count < pos.getSourceStart()) {
byte b = fileBytes[current + count];
if (!isIndentation(b)) {
break;
}
++count;
}
return count;
}

/**
* Get the source file position from a CtElement, taking care that Spork sometimes stores position information as
* metadata to circumvent the pretty-printers reliance on positional information (e.g. when printing comments).
*/
private static SourcePosition getSourcePos(CtElement elem) {
SourcePosition pos = (SourcePosition) elem.getMetadata(PrinterPreprocessor.POSITION_KEY);
if (pos == null) {
pos = elem.getPosition();
}
assert pos != null && pos != SourcePosition.NOPOSITION;

return pos;
}

private static String getOriginalSource(SourcePosition start, SourcePosition end) {
byte[] source = start.getCompilationUnit().getOriginalSourceCode().getBytes(Charset.defaultCharset());
return getOriginalSource(start, end, source);
}

/**
* Get the original source code fragment starting at start and ending at end, including indentation if start is
* the first element on its source code line.
*
* @param start The source position of the first element.
* @param end The source position of the last element.
* @return The source code fragment starting at start and ending at end, including leading indentation.
* @throws IOException
*/
private static String getOriginalSource(SourcePosition start, SourcePosition end, byte[] source) {
int startByte = precededByIndentation(source, start) ?
getLineStartByte(start) : start.getSourceStart();
int endByte = end.getSourceEnd();

if (isIndentation(source[startByte])) {
startByte++;
endByte++;
}

byte[] content = Arrays.copyOfRange(source, startByte, endByte + 1);

return new String(content, Charset.defaultCharset());
}

private static boolean precededByIndentation(byte[] source, SourcePosition pos) {
int lineStartByte = getLineStartByte(pos);

for (int i = lineStartByte; i < pos.getSourceStart(); i++) {
if (!isIndentation(source[i])) {
return false;
}
}
return true;
}

private static int getLineStartByte(SourcePosition pos) {
if (pos.getLine() == 1)
return 0;

int sourceStart = pos.getSourceStart() - 1;

int[] lineSepPositions = pos.getCompilationUnit().getLineSeparatorPositions();
int current = 0;
while (lineSepPositions[current] > sourceStart)
current++;

return lineSepPositions[current];
}

private static boolean isIndentation(byte b) {
return b == ' ' || b == '\t';
}
}
163 changes: 58 additions & 105 deletions src/main/java/se/kth/spork/cli/SporkPrettyPrinter.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
package se.kth.spork.cli;

import se.kth.spork.base3dm.Revision;
import se.kth.spork.spoon.PcsInterpreter;
import se.kth.spork.spoon.StructuralConflict;
import se.kth.spork.util.Pair;
import spoon.compiler.Environment;
import spoon.reflect.code.CtComment;
import spoon.reflect.cu.SourcePosition;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.reflect.visitor.DefaultTokenWriter;
import spoon.reflect.visitor.PrinterHelper;
import spoon.reflect.visitor.PrintingContext;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.util.*;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;
import java.util.Optional;

public final class SporkPrettyPrinter extends DefaultJavaPrettyPrinter {
public static final String START_CONFLICT = "<<<<<<< LEFT";
Expand Down Expand Up @@ -58,8 +60,15 @@ protected void exit(CtElement e) {

@Override
public SporkPrettyPrinter scan(CtElement e) {
if (e == null)
if (e == null) {
return this;
} else if (e.getMetadata(PcsInterpreter.SINGLE_REVISION_KEY) != null &&
(e instanceof CtMethod || e instanceof CtField)) {
CtElement originalElement = (CtElement) e.getMetadata(PcsInterpreter.ORIGINAL_NODE_KEY);
String originalSource = SourceExtractor.getOriginalSource(originalElement);
printerHelper.writeRawSourceCode(originalSource, SourceExtractor.getIndentation(originalElement));
return this;
}

StructuralConflict structuralConflict = (StructuralConflict) e.getMetadata(StructuralConflict.METADATA_KEY);

Expand Down Expand Up @@ -96,108 +105,11 @@ public void visitCtComment(CtComment comment) {
* Write both pats of a structural conflict.
*/
private void writeStructuralConflict(StructuralConflict structuralConflict) {
String leftSource = getOriginalSource(structuralConflict.left);
String rightSource = getOriginalSource(structuralConflict.right);
String leftSource = SourceExtractor.getOriginalSource(structuralConflict.left);
String rightSource = SourceExtractor.getOriginalSource(structuralConflict.right);
printerHelper.writeConflict(leftSource, rightSource);
}

/**
* Write the provided string plus a line ending only if the string is non-empty.
*/
private void writelnNonEmpty(String s) {
if (s.isEmpty())
return;

PrinterHelper helper = getPrinterTokenWriter().getPrinterHelper();

helper.write(s);
helper.writeln();
}

/**
* Get the original source fragment corresponding to the nodes provided, or an empty string if the list is
* empty. Note that the nodes must be adjacent in the source file, and in the same order as in the source.
*
* @param nodes A possibly empty list of adjacent nodes.
* @return The original source code fragment, including any leading indentation on the first line.
*/
private static String getOriginalSource(List<CtElement> nodes) {
if (nodes.isEmpty())
return "";

SourcePosition firstElemPos = getSourcePos(nodes.get(0));
SourcePosition lastElemPos = getSourcePos(nodes.get(nodes.size() - 1));
return getOriginalSource(firstElemPos, lastElemPos);
}

/**
* Get the source file position from a CtElement, taking care that Spork sometimes stores position information as
* metadata to circumvent the pretty-printers reliance on positional information (e.g. when printing comments).
*/
private static SourcePosition getSourcePos(CtElement elem) {
SourcePosition pos = (SourcePosition) elem.getMetadata(PrinterPreprocessor.POSITION_KEY);
if (pos == null) {
pos = elem.getPosition();
}
assert pos != null && pos != SourcePosition.NOPOSITION;

return pos;
}

private static String getOriginalSource(SourcePosition start, SourcePosition end) {
try (RandomAccessFile file = new RandomAccessFile(start.getFile(), "r")) {
return getOriginalSource(start, end, file);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("failed to read original source fragments");
}
}

/**
* Get the original source code fragment starting at start and ending at end, including indentation if start is
* the first element on its source code line.
*
* @param start The source position of the first element.
* @param end The source position of the last element.
* @param randomAccessFile A random access file pointing to the common source of the start and end elements.
* @return The source code fragment starting at start and ending at end, including leading indentation.
* @throws IOException
*/
private static String getOriginalSource(SourcePosition start, SourcePosition end, RandomAccessFile randomAccessFile) throws IOException {
int startByte = precededByIndentation(randomAccessFile, start) ?
getLineStartByte(start) : start.getSourceStart();
int endByte = end.getSourceEnd();

byte[] content = new byte[endByte - startByte + 1];

randomAccessFile.seek(startByte);
randomAccessFile.read(content);

return new String(content, Charset.defaultCharset());
}

private static boolean precededByIndentation(RandomAccessFile file, SourcePosition pos) throws IOException {
int lineStartByte = getLineStartByte(pos);
byte[] before = new byte[pos.getSourceStart() - lineStartByte];

file.seek(lineStartByte);
file.read(before);

String s = new String(before, Charset.defaultCharset());
return s.matches("\\s+");
}

private static int getLineStartByte(SourcePosition pos) {
if (pos.getLine() == 1)
return 0;

// the index is offset by 2 because:
// 1. zero-indexing means -1
// 2. line separator indexing means that the 0th line separator corresponds to the _end_ of the first line
int prevLineEnd = pos.getLine() - 2;
return pos.getCompilationUnit().getLineSeparatorPositions()[prevLineEnd] + 1; // +1 to get the next line start
}

private class SporkPrinterHelper extends PrinterHelper {
private String lastWritten;

Expand Down Expand Up @@ -267,6 +179,47 @@ private SporkPrinterHelper writeAtLeftMargin(String s) {
return this;
}

/**
* Write raw source code, attempting to honor indentation.
*/
public SporkPrinterHelper writeRawSourceCode(String s, int indentationCount) {
String[] lines = s.split("\n");
if (lines.length == 1) {
super.write(s);
return this;
}

write(lines[0]);

int initialTabCount = getTabCount();
setTabCount(indentationCount / env.getTabulationSize());


for (int i = 1; i < lines.length; i++) {
writeln();
String line = lines[i];
write(trimIndentation(line, indentationCount));
}
setTabCount(initialTabCount);
return this;
}


private String trimIndentation(String s, int trimAmount) {
if (s.length() >= trimAmount && isOnlyWhitespace(s.substring(0, trimAmount))) {
return s.substring(trimAmount);
}
return s;
}

private boolean isOnlyWhitespace(String s) {
for (char c : s.toCharArray()) {
if (!Character.isWhitespace(c))
return false;
}
return true;
}

/**
* Write a raw conflict. Typically, this is used for writing out conflicts in comments.
*/
Expand Down
Loading

0 comments on commit 4d5ffb6

Please sign in to comment.