org.jboss.windup.decompiler.procyon.LineNumberFormatter Maven / Gradle / Ivy
package org.jboss.windup.decompiler.procyon;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import com.strobel.decompiler.languages.LineNumberPosition;
/**
* FIXME - This source file requires its original license and credits for original authors.
*
* A LineNumberFormatter
is used to rewrite an existing .java file, introducing line number information. It
* can handle either, or both, of the following jobs:
*
*
* - Introduce line numbers as leading comments.
*
- Stretch the file so that the line number comments match the physical lines.
*
*/
class LineNumberFormatter {
private final List _positions;
private final File _file;
private final EnumSet _options;
public enum LineNumberOption {
LEADING_COMMENTS,
STRETCHED,
}
/**
* Constructs an instance.
*
* @param file the file whose line numbers should be fixed
* @param lineNumberPositions a recipe for how to fix the line numbers in 'file'.
* @param options controls how 'this' represents line numbers in the resulting file
*/
public LineNumberFormatter(File file, List lineNumberPositions,
EnumSet options) {
_file = file;
_positions = lineNumberPositions;
_options = (options == null ? EnumSet.noneOf(LineNumberOption.class) : options);
}
/**
* Rewrites the file passed to 'this' constructor so that the actual line numbers match the recipe passed to 'this'
* constructor.
*/
public void reformatFile() throws IOException {
List lineBrokenPositions = new ArrayList<>();
List brokenLines = breakLines(lineBrokenPositions);
emitFormatted(brokenLines, lineBrokenPositions);
}
/**
* Processes {@link #_file}, breaking apart any lines on which multiple line-number markers appear in different
* columns.
*
* @return the list of broken lines
*/
private List breakLines(List o_LineBrokenPositions) throws IOException {
int numLinesRead = 0;
int lineOffset = 0;
List brokenLines = new ArrayList<>();
try (BufferedReader r = new BufferedReader(new FileReader(_file))) {
for (int posIndex = 0; posIndex < _positions.size(); posIndex++) {
LineNumberPosition pos = _positions.get(posIndex);
o_LineBrokenPositions.add(new LineNumberPosition(
pos.getOriginalLine(), pos.getEmittedLine() + lineOffset, pos.getEmittedColumn()));
// Copy the input file up to but not including the emitted line # in "pos".
while (numLinesRead < pos.getEmittedLine() - 1) {
brokenLines.add(r.readLine());
numLinesRead++;
}
// Read the line that contains the next line number annotations, but don't write it yet.
String line = r.readLine();
numLinesRead++;
// See if there are two original line annotations on the same emitted line.
LineNumberPosition nextPos;
int prevPartLen = 0;
char[] indent = {};
do {
nextPos = (posIndex < _positions.size() - 1) ? _positions.get(posIndex + 1) : null;
if (nextPos != null
&& nextPos.getEmittedLine() == pos.getEmittedLine()
&& nextPos.getOriginalLine() > pos.getOriginalLine()) {
// Two different source line numbers on the same emitted line!
posIndex++;
lineOffset++;
String firstPart = line.substring(0, nextPos.getEmittedColumn() - prevPartLen - 1);
brokenLines.add(new String(indent) + firstPart);
prevPartLen += firstPart.length();
indent = new char[prevPartLen];
Arrays.fill(indent, ' ');
line = line.substring(firstPart.length(), line.length());
// Alter the position while adding it.
o_LineBrokenPositions.add(new LineNumberPosition(
nextPos.getOriginalLine(), nextPos.getEmittedLine() + lineOffset, nextPos
.getEmittedColumn()));
} else {
nextPos = null;
}
}
while (nextPos != null);
// Nothing special here-- just emit the line.
brokenLines.add(new String(indent) + line);
}
// Copy out the remainder of the file.
String line;
while ((line = r.readLine()) != null) {
brokenLines.add(line);
}
}
return brokenLines;
}
private void emitFormatted(List brokenLines, List lineBrokenPositions)
throws IOException {
File tempFile = new File(_file.getAbsolutePath() + ".fixed");
int globalOffset = 0;
int numLinesRead = 0;
Iterator lines = brokenLines.iterator();
int maxLineNo = LineNumberPosition.computeMaxLineNumber(lineBrokenPositions);
try (LineNumberPrintWriter w = new LineNumberPrintWriter(
maxLineNo, new BufferedWriter(new FileWriter(tempFile)))) {
// Suppress all line numbers if we weren't asked to show them.
if (!_options.contains(LineNumberOption.LEADING_COMMENTS)) {
w.suppressLineNumbers();
}
// Suppress stretching if we weren't asked to do it.
boolean doStretching = (_options.contains(LineNumberOption.STRETCHED));
for (LineNumberPosition pos : lineBrokenPositions) {
int nextTarget = pos.getOriginalLine();
int nextActual = pos.getEmittedLine();
int requiredAdjustment = (nextTarget - nextActual - globalOffset);
if (doStretching && requiredAdjustment < 0) {
// We currently need to remove newlines to squeeze things together.
// prefer to remove empty lines,
// 1. read all lines before nextActual and remove empty lines as needed
List stripped = new ArrayList<>();
while (numLinesRead < nextActual - 1) {
String line = lines.next();
numLinesRead++;
if ((requiredAdjustment < 0) && line.trim().isEmpty()) {
requiredAdjustment++;
globalOffset--;
} else {
stripped.add(line);
}
}
// 2. print non empty lines while stripping further as needed
int lineNoToPrint = (stripped.size() + requiredAdjustment <= 0)
? nextTarget : LineNumberPrintWriter.NO_LINE_NUMBER;
for (String line : stripped) {
if (requiredAdjustment < 0) {
w.print(lineNoToPrint, line);
w.print(" ");
requiredAdjustment++;
globalOffset--;
} else {
w.println(lineNoToPrint, line);
}
}
// 3. read and print next actual
String line = lines.next();
numLinesRead++;
if (requiredAdjustment < 0) {
w.print(nextTarget, line);
w.print(" ");
globalOffset--;
} else {
w.println(nextTarget, line);
}
} else {
while (numLinesRead < nextActual) {
String line = lines.next();
numLinesRead++;
boolean isLast = (numLinesRead >= nextActual);
int lineNoToPrint = isLast ? nextTarget : LineNumberPrintWriter.NO_LINE_NUMBER;
if (requiredAdjustment > 0 && doStretching) {
// We currently need to inject newlines to space things out.
do {
w.println("");
requiredAdjustment--;
globalOffset++;
}
while (isLast && requiredAdjustment > 0);
w.println(lineNoToPrint, line);
} else {
// No tweaks needed-- we are on the ball.
w.println(lineNoToPrint, line);
}
}
}
}
// Finish out the file.
String line;
while (lines.hasNext()) {
line = lines.next();
w.println(line);
}
}
// Delete the original file and rename the formatted temp file over the original.
_file.delete();
tempFile.renameTo(_file);
}
}