blob: 265378ebadb360838d8a2858f326dd891e7b2b67 [file] [log] [blame]
/*
* Copyright (C) 2007-2010 JĂșlio Vilmar Gesser.
* Copyright (C) 2011, 2013-2016 The JavaParser Team.
*
* This file is part of JavaParser.
*
* JavaParser can be used either under the terms of
* a) the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* b) the terms of the Apache License
*
* You should have received a copy of both licenses in LICENCE.LGPL and
* LICENCE.APACHE. Please refer to those files for details.
*
* JavaParser is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*/
package com.github.javaparser.printer;
import com.github.javaparser.Position;
import com.github.javaparser.printer.PrettyPrinterConfiguration.IndentType;
import com.github.javaparser.utils.Utils;
import java.util.Deque;
import java.util.LinkedList;
import static com.github.javaparser.Position.*;
/**
* A support class for code that outputs formatted source code.
*/
public class SourcePrinter {
private final String endOfLineCharacter;
private final String indentation;
private final int tabWidth;
private final IndentType indentType;
private final Deque<String> indents = new LinkedList<>();
private final Deque<String> reindentedIndents = new LinkedList<>();
private String lastPrintedIndent = "";
private final StringBuilder buf = new StringBuilder();
private Position cursor = new Position(1, 0);
private boolean indented = false;
SourcePrinter() {
this(new PrettyPrinterConfiguration());
}
SourcePrinter(final PrettyPrinterConfiguration configuration) {
indentation = configuration.getIndent();
endOfLineCharacter = configuration.getEndOfLineCharacter();
tabWidth = configuration.getTabWidth();
indentType = configuration.getIndentType();
indents.push("");
}
/**
* Add the default indentation to the current indentation and push it on the indentation stack.
* Does not actually output anything.
*/
public SourcePrinter indent() {
String currentIndent = indents.peek();
switch (indentType) {
case SPACES:
case TABS_WITH_SPACE_ALIGN:
indents.push(currentIndent + indentation);
break;
case TABS:
indents.push(indentation + currentIndent);
break;
default:
throw new AssertionError("Unhandled indent type");
}
return this;
}
/**
* Add to the current indentation until it is reaches "column" and push it on the indentation stack.
* Does not actually output anything.
*/
public SourcePrinter indentWithAlignTo(int column) {
indents.push(calculateIndentWithAlignTo(column));
return this;
}
private String calculateIndentWithAlignTo(int column) {
if (column < lastPrintedIndent.length()){
throw new IllegalStateException("Attempt to indent less than the previous indent.");
}
StringBuilder newIndent = new StringBuilder(lastPrintedIndent);
switch (indentType) {
case SPACES:
case TABS_WITH_SPACE_ALIGN:
while (newIndent.length() < column) {
newIndent.append(' ');
}
break;
case TABS:
int logicalIndentLength = newIndent.length();
while ((logicalIndentLength + tabWidth) <= column) {
newIndent.insert(0, '\t');
logicalIndentLength += tabWidth;
}
while (logicalIndentLength < column) {
newIndent.append(' ');
logicalIndentLength++;
}
StringBuilder fullTab = new StringBuilder();
for(int i=0; i<tabWidth; i++){
fullTab.append(' ');
}
String fullTabString = fullTab.toString();
if ((newIndent.length() >= tabWidth)
&& newIndent.substring(newIndent.length() - tabWidth).equals(fullTabString)) {
int i = newIndent.indexOf(fullTabString);
newIndent.replace(i, i + tabWidth, "\t");
}
break;
default:
throw new AssertionError("Unhandled indent type");
}
return newIndent.toString();
}
/**
* Pop the last indentation of the indentation stack.
* Does not actually output anything.
*/
public SourcePrinter unindent() {
if (indents.isEmpty()) {
// Since we start out with an empty indent on the stack, this will only occur
// the second time we over-unindent.
throw new IllegalStateException("Indent/unindent calls are not well-balanced.");
}
indents.pop();
return this;
}
private void append(String arg) {
buf.append(arg);
cursor = cursor.withColumn(cursor.column + arg.length());
}
/**
* Append the source string passed as argument to the buffer.
* If this is being appended at the beginning of a line, performs indentation first.
* <p>
* The source line to be printed should not contain newline/carriage-return characters;
* use {@link #println(String)} to automatically append a newline at the end of the source string.
* If the source line passed as argument contains newline/carriage-return characters would
* impredictably affect a correct computation of the current {@link #getCursor()} position.
*
* @param arg source line to be printed (should not contain newline/carriage-return characters)
* @return this instance, for nesting calls to method as fluent interface
* @see SourcePrinter#println(String)
*/
public SourcePrinter print(final String arg) {
if (!indented) {
lastPrintedIndent = indents.peek();
append(lastPrintedIndent);
indented = true;
}
append(arg);
return this;
}
/**
* Append the source string passed as argument to the buffer, then append a newline.
* If this is being appended at the beginning of a line, performs indentation first.
* <p>
* The source line to be printed should not contain newline/carriage-return characters.
* If the source line passed as argument contains newline/carriage-return characters would
* impredictably affect a correct computation of the current {@link #getCursor()} position.
*
* @param arg source line to be printed (should not contain newline/carriage-return characters)
* @return this instance, for nesting calls to method as fluent interface
*/
public SourcePrinter println(final String arg) {
print(arg);
println();
return this;
}
/**
* Append a newline to the buffer.
*
* @return this instance, for nesting calls to method as fluent interface
*/
public SourcePrinter println() {
buf.append(endOfLineCharacter);
cursor = pos(cursor.line + 1, 0);
indented = false;
return this;
}
/**
* Return the current cursor position (line, column) in the source printer buffer.
* <p>
* Please notice in order to guarantee a correct computation of the cursor position,
* this printer expect the contracts of the methods {@link #print(String)} and {@link #println(String)}
* has been respected through all method calls, meaning the source string passed as argument to those method
* calls did not contain newline/carriage-return characters.
*
* @return the current cursor position (line, column).
*/
public Position getCursor() {
return cursor;
}
/**
* @return the currently printed source code.
* @deprecated use toString()
*/
@Deprecated
public String getSource() {
return toString();
}
/**
* @return the currently printed source code.
*/
@Override
public String toString() {
return buf.toString();
}
/**
* Changes all EOL characters in "content" to the EOL character this SourcePrinter is using.
*/
public String normalizeEolInTextBlock(String content) {
return Utils.normalizeEolInTextBlock(content, endOfLineCharacter);
}
/**
* Set the top-most indent to the column the cursor is currently in, can be undone with
* {@link #reindentToPreviousLevel()}. Does not actually output anything.
*/
public void reindentWithAlignToCursor() {
String newIndent = calculateIndentWithAlignTo(cursor.column);
reindentedIndents.push(indents.pop());
indents.push(newIndent);
}
/**
* Set the top-most indent to the column the cursor was before the last {@link #reindentWithAlignToCursor()} call.
* Does not actually output anything.
*/
public void reindentToPreviousLevel() {
if (reindentedIndents.isEmpty()) {
throw new IllegalStateException("Reindent calls are not well-balanced.");
}
indents.pop();
indents.push(reindentedIndents.pop());
}
/**
* Adds an indent to the top of the stack that is a copy of the current top indent.
* With this you announce "I'm going to indent the next line(s)" but not how far yet.
* Once you do know, you can pop this indent ("unindent") and indent to the right column.
* (Does not actually output anything.)
*/
public void duplicateIndent() {
indents.push(indents.peek());
}
}