Fork of the Fernflower decompiler
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fernflower/src/org/jetbrains/java/decompiler/main/TextBuffer.java

295 lines
8.5 KiB

// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.java.decompiler.main;
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
import java.util.*;
/**
* Allows to connect text with resulting lines
*
* @author egor
*/
public class TextBuffer {
private final String myLineSeparator = DecompilerContext.getNewLineSeparator();
private final String myIndent = (String)DecompilerContext.getProperty(IFernflowerPreferences.INDENT_STRING);
private final StringBuilder myStringBuilder;
private Map<Integer, Integer> myLineToOffsetMapping = null;
public TextBuffer() {
myStringBuilder = new StringBuilder();
}
public TextBuffer(int size) {
myStringBuilder = new StringBuilder(size);
}
public TextBuffer(String text) {
myStringBuilder = new StringBuilder(text);
}
public TextBuffer append(String str) {
myStringBuilder.append(str);
return this;
}
public TextBuffer append(char ch) {
myStringBuilder.append(ch);
return this;
}
public TextBuffer append(int i) {
myStringBuilder.append(i);
return this;
}
public TextBuffer appendLineSeparator() {
myStringBuilder.append(myLineSeparator);
return this;
}
public TextBuffer appendIndent(int length) {
while (length-- > 0) {
append(myIndent);
}
return this;
}
public TextBuffer prepend(String s) {
insert(0, s);
return this;
}
public TextBuffer enclose(String left, String right) {
prepend(left);
append(right);
return this;
}
public boolean containsOnlyWhitespaces() {
for (int i = 0; i < myStringBuilder.length(); i++) {
if (myStringBuilder.charAt(i) != ' ') {
return false;
}
}
return true;
}
@Override
public String toString() {
String original = myStringBuilder.toString();
if (myLineToOffsetMapping == null || myLineToOffsetMapping.isEmpty()) {
if (myLineMapping != null) {
return addOriginalLineNumbers();
}
return original;
}
else {
StringBuilder res = new StringBuilder();
String[] srcLines = original.split(myLineSeparator);
int currentLineStartOffset = 0;
int currentLine = 0;
int previousMarkLine = 0;
int dumpedLines = 0;
ArrayList<Integer> linesWithMarks = new ArrayList<>(myLineToOffsetMapping.keySet());
Collections.sort(linesWithMarks);
for (Integer markLine : linesWithMarks) {
Integer markOffset = myLineToOffsetMapping.get(markLine);
while (currentLine < srcLines.length) {
String line = srcLines[currentLine];
int lineEnd = currentLineStartOffset + line.length() + myLineSeparator.length();
if (markOffset <= lineEnd) {
int requiredLine = markLine - 1;
int linesToAdd = requiredLine - dumpedLines;
dumpedLines = requiredLine;
appendLines(res, srcLines, previousMarkLine, currentLine, linesToAdd);
previousMarkLine = currentLine;
break;
}
currentLineStartOffset = lineEnd;
currentLine++;
}
}
if (previousMarkLine < srcLines.length) {
appendLines(res, srcLines, previousMarkLine, srcLines.length, srcLines.length - previousMarkLine);
}
return res.toString();
}
}
private String addOriginalLineNumbers() {
StringBuilder sb = new StringBuilder();
int lineStart = 0, lineEnd;
int count = 0, length = myLineSeparator.length();
while ((lineEnd = myStringBuilder.indexOf(myLineSeparator, lineStart)) > 0) {
++count;
sb.append(myStringBuilder.substring(lineStart, lineEnd));
Set<Integer> integers = myLineMapping.get(count);
if (integers != null) {
sb.append("//");
for (Integer integer : integers) {
sb.append(' ').append(integer);
}
}
sb.append(myLineSeparator);
lineStart = lineEnd + length;
}
if (lineStart < myStringBuilder.length()) {
sb.append(myStringBuilder.substring(lineStart));
}
return sb.toString();
}
private void appendLines(StringBuilder res, String[] srcLines, int from, int to, int requiredLineNumber) {
if (to - from > requiredLineNumber) {
List<String> strings = compactLines(Arrays.asList(srcLines).subList(from, to) ,requiredLineNumber);
int separatorsRequired = requiredLineNumber - 1;
for (String s : strings) {
res.append(s);
if (separatorsRequired-- > 0) {
res.append(myLineSeparator);
}
}
res.append(myLineSeparator);
}
else if (to - from <= requiredLineNumber) {
for (int i = from; i < to; i++) {
res.append(srcLines[i]).append(myLineSeparator);
}
for (int i = 0; i < requiredLineNumber - to + from; i++) {
res.append(myLineSeparator);
}
}
}
public int length() {
return myStringBuilder.length();
}
public String substring(int start) {
return myStringBuilder.substring(start);
}
public TextBuffer setStart(int position) {
myStringBuilder.delete(0, position);
shiftMapping(0, -position);
return this;
}
public void setLength(int position) {
myStringBuilder.setLength(position);
if (myLineToOffsetMapping != null) {
HashMap<Integer, Integer> newMap = new HashMap<>();
for (Map.Entry<Integer, Integer> entry : myLineToOffsetMapping.entrySet()) {
if (entry.getValue() <= position) {
newMap.put(entry.getKey(), entry.getValue());
}
}
myLineToOffsetMapping = newMap;
}
}
public TextBuffer append(TextBuffer buffer) {
if (buffer.myLineToOffsetMapping != null && !buffer.myLineToOffsetMapping.isEmpty()) {
checkMapCreated();
for (Map.Entry<Integer, Integer> entry : buffer.myLineToOffsetMapping.entrySet()) {
myLineToOffsetMapping.put(entry.getKey(), entry.getValue() + myStringBuilder.length());
}
}
myStringBuilder.append(buffer.myStringBuilder);
return this;
}
private void shiftMapping(int startOffset, int shiftOffset) {
if (myLineToOffsetMapping != null) {
HashMap<Integer, Integer> newMap = new HashMap<>();
for (Map.Entry<Integer, Integer> entry : myLineToOffsetMapping.entrySet()) {
int newValue = entry.getValue();
if (newValue >= startOffset) {
newValue += shiftOffset;
}
if (newValue >= 0) {
newMap.put(entry.getKey(), newValue);
}
}
myLineToOffsetMapping = newMap;
}
}
private void checkMapCreated() {
if (myLineToOffsetMapping == null) {
myLineToOffsetMapping = new HashMap<>();
}
}
public TextBuffer insert(int offset, String s) {
myStringBuilder.insert(offset, s);
shiftMapping(offset, s.length());
return this;
}
public int countLines() {
return countLines(0);
}
public int countLines(int from) {
return count(myLineSeparator, from);
}
public int count(String substring, int from) {
int count = 0, length = substring.length(), p = from;
while ((p = myStringBuilder.indexOf(substring, p)) > 0) {
++count;
p += length;
}
return count;
}
private static List<String> compactLines(List<String> srcLines, int requiredLineNumber) {
if (srcLines.size() < 2 || srcLines.size() <= requiredLineNumber) {
return srcLines;
}
List<String> res = new LinkedList<>(srcLines);
// first join lines with a single { or }
for (int i = res.size()-1; i > 0 ; i--) {
String s = res.get(i);
if (s.trim().equals("{") || s.trim().equals("}")) {
res.set(i-1, res.get(i-1).concat(s));
res.remove(i);
}
if (res.size() <= requiredLineNumber) {
return res;
}
}
// now join empty lines
for (int i = res.size()-1; i > 0 ; i--) {
String s = res.get(i);
if (s.trim().isEmpty()) {
res.set(i-1, res.get(i-1).concat(s));
res.remove(i);
}
if (res.size() <= requiredLineNumber) {
return res;
}
}
return res;
}
private Map<Integer, Set<Integer>> myLineMapping = null; // new to original
public void dumpOriginalLineNumbers(int[] lineMapping) {
if (lineMapping.length > 0) {
myLineMapping = new HashMap<>();
for (int i = 0; i < lineMapping.length; i += 2) {
int key = lineMapping[i + 1];
Set<Integer> existing = myLineMapping.get(key);
if (existing == null) {
existing = new TreeSet<>();
myLineMapping.put(key, existing);
}
existing.add(lineMapping[i]);
}
}
}
}