/*******************************************************************************
* Copyright (c) 2011 Subgraph.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Subgraph - initial API and implementation
******************************************************************************/
package com.subgraph.vega.ui.httpviewer.syntax.js.formatter;
import java.util.ArrayList;
import java.util.List;
import com.subgraph.vega.ui.httpviewer.syntax.js.formatter.FormatterState.Mode;
import com.subgraph.vega.ui.httpviewer.syntax.js.formatter.JavascriptFormatterConfig.BraceStyle;
/*
* Adapted from http://jsbeautifier.org/
*/
public class JavascriptFormatter {
private final static String[] PUNCTUATION = { "++", "--", "+=", "-=", "*=", "/=", "%=", "===", "==", "!==", "!=", ">>>=", ">>>", ">>=", "<<=", ">=", "<=", ">>", "<<",
">", "<", "&&", "&=", "||", "!=", "|", "!!", "!", ",", "::", "^=", "^", ":", "=", "+", "-", "*", "/", "%", "&"};
private final static String[] LINE_STARTERS = { "continue", "try", "throw", "return", "var", "if", "switch", "case", "default", "for", "while", "break", "function" };
private final static int PREFIX_NONE = 0;
private final static int PREFIX_NEWLINE = 1;
private final static int PREFIX_SPACE = 2;
private final static int CHAR_EOF = -1;
private enum TokenType { TK_START_EXPR, TK_END_EXPR, TK_START_BLOCK, TK_END_BLOCK, TK_WORD,
TK_SEMICOLON, TK_STRING, TK_EQUALS, TK_OPERATOR, TK_BLOCK_COMMENT, TK_INLINE_COMMENT,
TK_COMMENT, TK_EOF, TK_UNKNOWN
}
static class Token {
final TokenType type;
final String text;
Token(TokenType type, int c) {
this.type = type;
this.text = String.valueOf((char)c);
}
Token(TokenType type, String text) {
this.type = type;
this.text = text;
}
}
private final StringBuilder output = new StringBuilder();
private String lastWord = "";
private String secondLastText = "";
private String lastText = "";
private TokenType lastTokenType = TokenType.TK_START_EXPR;
private String input;
private int pos;
private int newlineCount = 0;
private boolean wantedNewline;
private boolean justAddedNewline;
private boolean doBlockJustClosed;
private String indentString;
private FormatterState state = new FormatterState(Mode.BLOCK);
private List<FormatterState> stateStack = new ArrayList<FormatterState>();
private JavascriptFormatterConfig config = new JavascriptFormatterConfig();
public String format(String input) {
StringBuilder sb = new StringBuilder();
for(int i = 0; i < config.indentCount; i++) {
sb.append(' ');
}
indentString = sb.toString();
reset(input);
Token token = nextToken();
while(token.type != TokenType.TK_EOF) {
switch(token.type) {
case TK_START_EXPR:
handleStartExpr(token.text);
break;
case TK_END_EXPR:
handleEndExpr(token.text);
break;
case TK_START_BLOCK:
handleStartBlock(token.text);
break;
case TK_END_BLOCK:
handleEndBlock(token.text);
break;
case TK_WORD:
handleWord(token.text);
break;
case TK_SEMICOLON:
handleSemicolon(token.text);
break;
case TK_STRING:
handleString(token.text);
break;
case TK_EQUALS:
handleEquals(token.text);
break;
case TK_OPERATOR:
handleOperator(token.text);
break;
case TK_BLOCK_COMMENT:
handleBlockComment(token.text);
break;
case TK_INLINE_COMMENT:
handleInlineComment(token.text);
break;
case TK_COMMENT:
handleComment(token.text);
break;
case TK_UNKNOWN:
handleUnknown(token.text);
break;
}
pushToken(token);
token = nextToken();
}
return output.toString();
}
private void pushToken(Token token) {
secondLastText = lastText;
lastText = token.text;
lastTokenType = token.type;
}
private void reset(String input) {
pos = 0;
this.input = input;
}
private Token nextToken() {
newlineCount = 0;
if(pos >= input.length())
return new Token(TokenType.TK_EOF, "");
wantedNewline = false;
int c = input.charAt(pos);
pos += 1;
boolean keepWhitespace = (config.keepArrayIndentation && state.mode.isArray);
if(keepWhitespace) {
int whitespaceCount = 0;
while(Character.isWhitespace(c)) {
if(c == '\n') {
trimOutput();
output.append("\n");
justAddedNewline = true;
whitespaceCount = 0;
} else if(c == '\t') {
whitespaceCount += 4;
} else if(c == '\r') {
// do nothing
} else {
whitespaceCount += 1;
}
if(pos >= input.length()) {
return new Token(TokenType.TK_EOF, "");
}
c = input.charAt(pos);
pos += 1;
}
if(state.indentationBaseline == -1) {
state.indentationBaseline = whitespaceCount;
}
if(justAddedNewline) {
for(int i = 0; i < state.indentationLevel + 1; i++) {
output.append(indentString);
}
if(state.indentationBaseline != -1) {
for(int i = 0; i < whitespaceCount - state.indentationBaseline; i++) {
output.append(' ');
}
}
}
} else {
while(Character.isWhitespace(c)) {
if(c == '\n') {
if(config.maxPreserveNewline == 0 || config.maxPreserveNewline > newlineCount)
newlineCount += 1;
}
if(pos >= input.length())
return new Token(TokenType.TK_EOF, "");
c = input.charAt(pos);
pos += 1;
}
if(config.preserveNewlines && newlineCount > 1) {
for(int i = 0; i < newlineCount; i++) {
printNewline(i == 0);
justAddedNewline = true;
}
}
wantedNewline = newlineCount > 0;
}
if(Character.isJavaIdentifierStart(c)) {
StringBuilder text = new StringBuilder();
text.append((char)c);
if(pos < input.length()) {
while(Character.isJavaIdentifierPart(input.charAt(pos))) {
text.append(input.charAt(pos));
pos += 1;
if(pos == input.length())
break;
}
}
if(!atEnd() && text.toString().matches("^[0-9]+[Ee]$") && (peek() == '-' || peek() == '+')) {
int sign = next();
Token t = nextToken();
text.append((char) sign);
text.append(t.text);
return new Token(TokenType.TK_WORD, text.toString());
}
if(text.toString().equals("in")) {
return new Token(TokenType.TK_WORD, text.toString());
}
if(wantedNewline && notLastToken(TokenType.TK_OPERATOR, TokenType.TK_EQUALS)
&& !state.ifLine && (config.preserveNewlines || !"var".equals(lastText))) {
printNewline();
}
return new Token(TokenType.TK_WORD, text.toString());
}
switch(c) {
case '(':
case '[':
return new Token(TokenType.TK_START_EXPR, c);
case ')':
case ']':
return new Token(TokenType.TK_END_EXPR, c);
case '{':
return new Token(TokenType.TK_START_BLOCK, c);
case '}':
return new Token(TokenType.TK_END_BLOCK, c);
case ';':
return new Token(TokenType.TK_SEMICOLON, c);
case '/':
Token t = maybeCommentToken();
if(t != null)
return t;
}
boolean isRegex =
c == '/'
&& ((lastTokenType == TokenType.TK_WORD && ("return".equals(lastText) || "do".equals(lastText))
|| (lastTokenType == TokenType.TK_COMMENT || lastTokenType == TokenType.TK_START_EXPR
|| lastTokenType == TokenType.TK_START_BLOCK || lastTokenType == TokenType.TK_END_BLOCK ||
lastTokenType == TokenType.TK_OPERATOR || lastTokenType == TokenType.TK_EQUALS || lastTokenType == TokenType.TK_EOF ||
lastTokenType == TokenType.TK_SEMICOLON)));
if(c == '"' || c == '\'' || isRegex) {
int sep = c;
boolean esc = false;
StringBuilder sb = new StringBuilder();
sb.append((char) c);
if(pos < input.length()) {
if(sep == '/') {
boolean inCharClass = false;
while(esc || inCharClass || input.charAt(pos) != sep) {
sb.append(input.charAt(pos));
if(!esc) {
esc = input.charAt(pos) == '\\';
if(input.charAt(pos) == '[') {
inCharClass = true;
} else if(input.charAt(pos) == ']') {
inCharClass = false;
}
} else {
esc = false;
}
pos += 1;
if(pos >= input.length()) {
return new Token(TokenType.TK_STRING, sb.toString());
}
}
} else {
while(esc || input.charAt(pos) != sep) {
sb.append(input.charAt(pos));
if(!esc) {
esc = input.charAt(pos) == '\\';
} else {
esc = false;
}
pos += 1;
if(pos >= input.length()) {
return new Token(TokenType.TK_STRING, sb.toString());
}
}
}
}
pos += 1;
sb.append((char) sep);
if(sep == '/') {
while(pos < input.length() && Character.isJavaIdentifierPart(input.charAt(pos))) {
sb.append(input.charAt(pos));
pos += 1;
}
}
return new Token(TokenType.TK_STRING, sb.toString());
}
if(c == '#') {
if(output.length() == 0 && peek() == '!') {
StringBuilder sb = new StringBuilder();
sb.append((char) c);
while(!atEnd() && c != '\n') {
c = next();
sb.append((char) c);
}
output.append(sb.toString().trim());
output.append('\n');
printNewline();
return nextToken();
}
if(!atEnd() && Character.isDigit(peek())) {
StringBuilder sb = new StringBuilder();
sb.append('#');
do {
c = next();
sb.append((char) c);
} while(pos < input.length() && c != '#' && c != '=');
if(c == '#') {
//
} else if(peek() == '[' && input.charAt(pos + 1) == ']') {
sb.append("[]");
pos += 2;
} else if(peek() == '{' && input.charAt(pos + 1) == '}') {
sb.append("{}");
pos += 2;
}
return new Token(TokenType.TK_WORD, sb.toString());
}
}
if(c == '<' && peekStr(3).equals("!--")) {
pos += 3;
state.inHtmlComment = true;
return new Token(TokenType.TK_COMMENT, "<!--");
}
if(c == '-' && state.inHtmlComment && peekStr(2).equals("->")) {
state.inHtmlComment = false;
pos += 2;
if(wantedNewline)
printNewline();
return new Token(TokenType.TK_COMMENT, "-->");
}
for(String p: PUNCTUATION) {
int plen = p.length();
if(c == p.charAt(0)) {
if(plen == 1 || p.substring(1).equals(peekStr(plen - 1))) {
pos += plen - 1;
if(c == '=')
return new Token(TokenType.TK_EQUALS, p);
else
return new Token(TokenType.TK_OPERATOR, p);
}
}
}
return new Token(TokenType.TK_UNKNOWN, c);
}
private boolean atEnd() {
return pos >= input.length();
}
private int next() {
if(atEnd())
return CHAR_EOF;
pos += 1;
return input.charAt(pos - 1);
}
private String peekStr(int length) {
if(pos + length > input.length()) {
return "";
}
return input.substring(pos, pos + length);
}
private int peek() {
if(atEnd())
return CHAR_EOF;
return input.charAt(pos);
}
private Token maybeCommentToken() {
if(peek() != '*' && peek() != '/')
return null;
TokenType type = TokenType.TK_INLINE_COMMENT;
StringBuilder sb = new StringBuilder();
int cc = next();
sb.append('/');
if(cc == '*') {
while(!(cc == '*' && peek() == '/') && (cc != CHAR_EOF)) {
sb.append((char) cc);
if(cc == '\n' || cc == '\r')
type = TokenType.TK_BLOCK_COMMENT;
cc = next();
}
if(cc == '*') {
next();
sb.append("*/");
}
return new Token(type, sb.toString());
} else {
while(cc != '\n' && cc != '\r' && cc != CHAR_EOF) {
sb.append((char) cc);
cc = next();
}
return new Token(TokenType.TK_COMMENT, sb.toString());
}
}
private void handleStartExpr(String text) {
if(text.equals("[")) {
if(lastTokenType == TokenType.TK_WORD || ")".equals(lastText)) {
for(String ls: LINE_STARTERS) {
if(ls.equals(text))
printSingleSpace();
setMode(Mode.PAREN_EXPR);
printToken(text);
return;
}
}
if(state.mode == Mode.ARRAY_EXPR || state.mode == Mode.ARRAY_INDENTED_EXPR) {
if("]".equals(secondLastText) && ",".equals(lastText)) {
if(state.mode == Mode.ARRAY_EXPR) {
state.mode = Mode.ARRAY_INDENTED_EXPR;
if(!config.keepArrayIndentation) {
indent();
}
}
setMode(Mode.ARRAY_EXPR);
if(!config.keepArrayIndentation) {
printNewline();
}
} else if ("[".equals(lastText)) {
if(state.mode == Mode.ARRAY_EXPR) {
state.mode = Mode.ARRAY_INDENTED_EXPR;
if(!config.keepArrayIndentation) {
indent();
}
}
setMode(Mode.ARRAY_EXPR);
if(!config.keepArrayIndentation) {
printNewline();
}
} else {
setMode(Mode.ARRAY_EXPR);
}
} else {
setMode(Mode.ARRAY_EXPR);
}
} else {
setMode(Mode.ARRAY_EXPR);
}
if(";".equals(lastText) || lastTokenType == TokenType.TK_START_BLOCK) {
printNewline();
} else if(lastTokenType == TokenType.TK_END_EXPR || lastTokenType == TokenType.TK_START_EXPR || lastTokenType == TokenType.TK_END_BLOCK || ".".equals(lastText)) {
// do nothing
} else if(lastTokenType != TokenType.TK_WORD && lastTokenType != TokenType.TK_OPERATOR) {
printSingleSpace();
} else if("function".equals(lastWord) || "typeof".equals(lastWord)) {
printSingleSpace();
} else if("catch".equals(lastText)){
printSingleSpace();
} else {
for(String ls: LINE_STARTERS) {
if(ls.equals(lastText))
printSingleSpace();
}
}
printToken(text);
}
private void handleEndExpr(String text) {
if("]".equals(text)) {
if(config.keepArrayIndentation) {
if("}".equals(lastText)) {
removeIndent();
printToken(text);
restoreMode();
}
} else {
if(state.mode == Mode.ARRAY_INDENTED_EXPR) {
if("]".equals(lastText)) {
restoreMode();
printNewline();
printToken(text);
}
}
}
}
restoreMode();
printToken(text);
}
private void handleStartBlock(String text) {
if(isLastWord("do")) {
setMode(Mode.DO_BLOCK);
} else {
setMode(Mode.BLOCK);
}
if(config.braceStyle == BraceStyle.EXPAND) {
if(notLastToken(TokenType.TK_OPERATOR)) {
if(isLastText("return", "=")) {
printSingleSpace();
} else {
printNewline(true);
}
}
printToken(text);
indent();
} else {
if(notLastToken(TokenType.TK_OPERATOR, TokenType.TK_START_EXPR)) {
if(isLastToken(TokenType.TK_START_BLOCK)) {
printNewline();
} else {
printSingleSpace();
}
} else {
if(state.previousMode.isArray && isLastText(",")) {
if(isSecondLastText("}")) {
printSingleSpace();
} else {
printNewline();
}
}
}
indent();
printToken(text);
}
}
private void handleEndBlock(String text) {
restoreMode();
if(config.braceStyle == BraceStyle.EXPAND) {
if(notLastText("{")) {
printNewline();
}
printToken(text);
} else {
if(isLastToken(TokenType.TK_START_BLOCK)) {
if(justAddedNewline) {
removeIndent();
} else {
trimOutput();
}
} else {
if(state.mode.isArray && config.keepArrayIndentation) {
config.keepArrayIndentation = false;
printNewline();
config.keepArrayIndentation = true;
} else {
printNewline();
}
}
printToken(text);
}
}
private void handleWord(String text) {
if(doBlockJustClosed) {
printSingleSpace();
printToken(text);
printSingleSpace();
doBlockJustClosed = false;
return;
}
if("function".equals(text)) {
if(state.varLine) {
state.varLineReindented = true;
}
if((justAddedNewline || isLastText(";")) && notLastText("{")) {
newlineCount = (justAddedNewline) ? (newlineCount) : 0;
if(!config.preserveNewlines) {
newlineCount = 1;
}
for(int i = 0; i < (2 - newlineCount); i++) {
printNewline(false);
}
}
}
if("case".equals(text) || "default".equals(text)) {
if(isLastText(":")) {
removeIndent();
} else {
state.indentationLevel -= 1;
printNewline();
state.indentationLevel += 1;
}
printToken(text);
state.inCase = true;
return;
}
int prefix = PREFIX_NONE;
String ltext = text.toLowerCase();
if(isLastToken(TokenType.TK_END_BLOCK)) {
if(!("else".equals(ltext) || "catch".equals(ltext) || "finally".equals(ltext))) {
prefix = PREFIX_NEWLINE;
} else {
if(config.braceStyle == BraceStyle.EXPAND || config.braceStyle == BraceStyle.END_EXPAND) {
prefix = PREFIX_NEWLINE;
} else {
prefix = PREFIX_SPACE;
printSingleSpace();
}
}
} else if (isLastToken(TokenType.TK_SEMICOLON) && (state.mode == Mode.BLOCK || state.mode == Mode.DO_BLOCK)) {
prefix = PREFIX_NEWLINE;
} else if(isLastToken(TokenType.TK_SEMICOLON) && state.mode.isExpression) {
prefix = PREFIX_SPACE;
} else if(isLastToken(TokenType.TK_STRING)) {
prefix = PREFIX_NEWLINE;
} else if(isLastToken(TokenType.TK_WORD)) {
if(isLastText("else")) {
trimOutput(true);
}
prefix = PREFIX_SPACE;
} else if(isLastToken(TokenType.TK_START_BLOCK)) {
prefix = PREFIX_NEWLINE;
} else if(isLastToken(TokenType.TK_END_EXPR)) {
printSingleSpace();
prefix = PREFIX_NEWLINE;
}
if(containsString(LINE_STARTERS, text) && notLastText(")")) {
if(isLastText("else")) {
prefix = PREFIX_SPACE;
} else {
prefix = PREFIX_NEWLINE;
}
}
if(state.ifLine && isLastToken(TokenType.TK_END_EXPR)) {
state.ifLine = false;
}
if(containsString(new String[] { "else", "catch", "finally"}, ltext)) {
if(notLastToken(TokenType.TK_END_BLOCK) || config.braceStyle == BraceStyle.EXPAND || config.braceStyle == BraceStyle.END_EXPAND) {
printNewline();
} else {
trimOutput(true);
printSingleSpace();
}
} else if(prefix == PREFIX_NEWLINE) {
if((isLastToken(TokenType.TK_START_EXPR) || isLastText("=", ",")) && "function".equals(text)) {
// do nothing
} else if ("function".equals(text) && isLastText("new")) {
printSingleSpace();
} else if(isLastText("return", "throw")) {
printSingleSpace();
} else if(notLastToken(TokenType.TK_END_EXPR)) {
if((notLastToken(TokenType.TK_START_EXPR) || !"var".equals(text)) && notLastText(":")) {
if("if".equals(text) && isLastWord("else") && notLastText("{")) {
printSingleSpace();
} else {
state.varLine = false;
state.varLineReindented = false;
printNewline();
}
}
} else if(containsString(LINE_STARTERS, text) && notLastText(")")) {
state.varLine = false;
state.varLineReindented = false;
printNewline();
}
} else if(state.mode.isArray && isLastText(",") && isSecondLastText(")")) {
printNewline();
} else if(prefix == PREFIX_SPACE) {
printSingleSpace();
}
printToken(text);
lastWord = text;
if("var".equals(text)) {
state.varLine = true;
state.varLineReindented = false;
state.varLineTainted = false;
} else if("if".equals(text)) {
state.ifLine = true;
} else if("else".equals(text)) {
state.ifLine = false;
}
}
private void handleSemicolon(String text) {
printToken(text);
state.varLine = false;
state.varLineReindented = false;
if(state.mode == Mode.OBJECT) {
state.mode = Mode.BLOCK;
}
}
private void handleString(String text) {
if(isLastToken(TokenType.TK_START_BLOCK, TokenType.TK_END_BLOCK, TokenType.TK_SEMICOLON)) {
printNewline();
} else if(isLastToken(TokenType.TK_WORD)) {
printSingleSpace();
}
printToken(text);
}
private void handleEquals(String text) {
if(state.varLine) {
state.varLineTainted = true;
}
printSingleSpace();
printToken(text);
printSingleSpace();
}
private void handleOperator(String text) {
boolean spaceBefore = true;
boolean spaceAfter = true;
if(state.varLine && ",".equals(text) && state.mode.isExpression) {
state.varLineTainted = false;
}
if(state.varLine) {
if(",".equals(text)) {
if(state.varLineTainted) {
printToken(text);
state.varLineReindented = true;
state.varLineTainted = false;
printNewline();
return;
} else {
state.varLineTainted = false;
}
}
}
if(isLastText("return", "throw")) {
printSingleSpace();
printToken(text);
return;
}
if(":".equals(text) && state.inCase) {
printToken(text);
printNewline();
state.inCase = false;
return;
}
if("::".equals(text)) {
printToken(text);
return;
}
if(",".equals(text)) {
if(state.varLine) {
if(state.varLineTainted) {
printToken(text);
printNewline();
state.varLineTainted = false;
} else {
printToken(text);
printSingleSpace();
}
} else if(isLastToken(TokenType.TK_END_BLOCK) && state.mode != Mode.PAREN_EXPR) {
printToken(text);
if(state.mode == Mode.OBJECT && isLastText("}")) {
printNewline();
} else {
printSingleSpace();
}
} else {
if(state.mode == Mode.OBJECT) {
printToken(text);
printNewline();
} else {
printToken(text);
printSingleSpace();
}
}
return;
} else if (containsString(new String[] {"--", "++", "!"}, text)
|| ( containsString(new String[] { "-", "+" }, text)
&& isLastToken(TokenType.TK_START_BLOCK, TokenType.TK_START_EXPR, TokenType.TK_EQUALS, TokenType.TK_OPERATOR)
|| isLastText(LINE_STARTERS))) {
spaceBefore = false;
spaceAfter = false;
if(isLastText(";") && state.mode.isExpression) {
spaceBefore = true;
}
if(isLastToken(TokenType.TK_WORD) && isLastText(LINE_STARTERS)) {
spaceBefore = true;
}
if(state.mode == Mode.BLOCK && isLastText("{", ";")) {
printNewline();
}
} else if(".".equals(text)) {
spaceBefore = false;
} else if(":".equals(text)) {
if(state.ternaryDepth == 0) {
state.mode = Mode.OBJECT;
spaceBefore = false;
} else {
state.ternaryDepth -= 1;
}
} else if("?".equals(text)) {
state.ternaryDepth += 1;
}
if(spaceBefore) {
printSingleSpace();
}
printToken(text);
if(spaceAfter) {
printSingleSpace();
}
}
private void handleBlockComment(String text) {
final String[] lines = text.split("\\r?\\n");
if(text.matches("^/\\*\\*")) {
for(int i = 0; i < lines.length; i++) {
printNewline();
if(i > 0)
output.append(" ");
output.append(lines[i].trim());
}
} else {
if(lines.length > 1) {
printNewline();
trimOutput();
} else {
printSingleSpace();
}
for(int i = 0; i < lines.length; i++) {
output.append(lines[i]);
output.append("\n");
}
}
printNewline();
}
private void handleInlineComment(String text) {
printSingleSpace();
printToken(text);
if(state.mode.isExpression) {
printSingleSpace();
} else {
printNewline();
}
}
private void handleComment(String text) {
if(wantedNewline) {
printNewline();
} else {
printSingleSpace();
}
printToken(text);
printNewline();
}
private void handleUnknown(String text) {
if(isLastText("return", "throw")) {
printSingleSpace();
}
printToken(text);
}
private void trimOutput() {
trimOutput(false);
}
private void trimOutput(boolean eatNewlines) {
while(endsWith(' ') || endsWithIndent() || (eatNewlines && (endsWith('\n') || endsWith('\r')))) {
dropLast();
}
}
private void printNewline() {
printNewline(true);
}
private void printNewline(boolean ignoreRepeated) {
state.eatNextSpace = false;
if(config.keepArrayIndentation && state.mode.isArray) {
return;
}
trimOutput();
if(outputEmpty())
return;
if(lastOutputChar() != '\n' || !ignoreRepeated) {
justAddedNewline = true;
output.append("\n");
}
for(int i = 0; i < state.indentationLevel; i++) {
output.append(indentString);
}
if(state.varLine && state.varLineReindented) {
if(config.indentChar == ' ') {
output.append(" ");
} else {
output.append(indentString);
}
}
}
private void printSingleSpace() {
if(state.eatNextSpace) {
state.eatNextSpace = false;
return;
}
char last = ' ';
if(output.length() > 0) {
last = output.charAt(output.length() - 1);
}
if(last != ' ' && last != '\n' && !(indentString.length() == 1 && indentString.charAt(0) == last)) {
output.append(' ');
}
}
private void printToken(String text) {
justAddedNewline = false;
state.eatNextSpace = false;
output.append(text);
}
private void indent() {
state.indentationLevel += 1;
}
private void removeIndent() {
if(output.length() > 0 && matchIndentString(lastOutputChar())) {
dropLast();
}
}
private void setMode(FormatterState.Mode mode) {
if(state != null) {
stateStack.add(state);
}
state = new FormatterState(config, mode, state);
}
private void restoreMode() {
doBlockJustClosed = (state.mode == Mode.DO_BLOCK);
if(stateStack.size() > 0) {
state = stateStack.remove(stateStack.size() - 1);
}
}
private boolean endsWith(char c) {
if(outputEmpty())
return false;
return lastOutputChar() == c;
}
private boolean endsWithIndent() {
if(outputEmpty())
return false;
return matchIndentString(lastOutputChar());
}
private boolean matchIndentString(char c) {
return indentString.length() == 1 && indentString.charAt(0) == c;
}
private char lastOutputChar() {
return output.charAt(output.length() - 1);
}
private boolean outputEmpty() {
return output.length() == 0;
}
private void dropLast() {
if(!outputEmpty()) {
output.deleteCharAt(output.length() - 1);
}
}
private boolean containsString(String[] haystack, String needle) {
for(String s: haystack)
if(s.equals(needle))
return true;
return false;
}
private boolean notLastToken(TokenType ... types) {
for(TokenType tt: types) {
if(lastTokenType == tt)
return false;
}
return true;
}
private boolean isLastToken(TokenType ...types) {
for(TokenType tt: types) {
if(lastTokenType == tt)
return true;
}
return false;
}
private boolean isLastText(String ... strings) {
for(String s: strings) {
if(s.equals(lastText))
return true;
}
return false;
}
private boolean notLastText(String ...strings) {
for(String s: strings) {
if(s.equals(lastText))
return false;
}
return true;
}
private boolean isSecondLastText(String text) {
return text.equals(secondLastText);
}
private boolean isLastWord(String text) {
return text.equals(lastWord);
}
}