/******************************************************************************* * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. 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 * *******************************************************************************/ package com.cisco.yangide.core.parser; import static org.opendaylight.yangtools.antlrv4.code.gen.YangLexer.LEFT_BRACE; import static org.opendaylight.yangtools.antlrv4.code.gen.YangLexer.RIGHT_BRACE; import static org.opendaylight.yangtools.antlrv4.code.gen.YangLexer.S; import static org.opendaylight.yangtools.antlrv4.code.gen.YangLexer.SEMICOLON; import static org.opendaylight.yangtools.antlrv4.code.gen.YangLexer.STRING; import static org.opendaylight.yangtools.antlrv4.code.gen.YangLexer.WS; import static org.opendaylight.yangtools.antlrv4.code.gen.YangLexer.LINE_COMMENT; import static org.opendaylight.yangtools.antlrv4.code.gen.YangLexer.END_BLOCK_COMMENT; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.StringTokenizer; import org.antlr.v4.runtime.Token; import org.opendaylight.yangtools.antlrv4.code.gen.YangLexer; /** * @author Konstantin Zaitsev * @date Jul 22, 2014 */ public class YangTokenFormatter implements ITokenFormatter { private StringBuilder sb; private List<Token> tokens; private Token prevToken; // states private int currIndent = 0; private boolean wasNL; private boolean ovrNL; private boolean wasWS; // was new line separator '{' '}' ';' private int nlCount = 0; private boolean importScope = false; private StringBuilder importStatement = null; // preferences private int indent; private String lineSeparator; private Boolean formatString; private Boolean formatComment; private int maxLineLength; private Boolean compactImport; private boolean spaceForTabs; public YangTokenFormatter(YangFormattingPreferences preferences, int indentationLevel, String lineSeparator) { this.sb = new StringBuilder(); this.spaceForTabs = preferences.isSpaceForTabs(); this.indent = preferences.getIndentSize(); this.formatComment = preferences.isFormatComment(); this.formatString = preferences.isFormatStrings(); this.compactImport = preferences.isCompactImport(); this.maxLineLength = preferences.getMaxLineLength(); this.lineSeparator = lineSeparator; this.currIndent = indentationLevel * this.indent; this.tokens = new ArrayList<>(); } @Override public void process(Token token) { // ignore '\r' token if (isCarriageReturn(token)) { return; } if (isWS(token) && (isWS(prevToken) || isNewLine(prevToken))) { return; } if (isNewLine(token) && (isNewLine(prevToken) && nlCount > 1)) { return; } nlCount = isNewLine(token) ? (nlCount + 1) : 0; prevToken = token; tokens.add(token); // System.out.println(ignore + " '" + token.getText() + "'" + " - " + // YangLexer.tokenNames[token.getType()]); } /** * @param token token to inspect * @return <code>true</code> if token should be ignored from processing */ private boolean checkForIgnore(Token token) { // ignore any repeat whitespace if (isWS(token) && (wasWS || wasNL)) { return true; } // ignore multiple new lines if (isNewLine(token) && (ovrNL || nlCount > 1)) { ovrNL = false; return true; } return false; } /** * @param token * @return <code>true</code> if token processed by import case */ private boolean processImportCase(Token token) { if (!compactImport) { return false; } if (token.getType() == YangLexer.IMPORT_KEYWORD) { importScope = true; importStatement = new StringBuilder(); } if (importScope) { if (token.getType() == RIGHT_BRACE) { importScope = false; printIndent(); sb.append(importStatement.toString()).append(' '); wasWS = true; nlCount = 0; // add indent for right brace processing currIndent += indent; return false; } else if (!isNewLine(token) && !isWS(token)) { if (importStatement.length() > 0 && token.getType() != SEMICOLON) { importStatement.append(' '); } importStatement.append(token.getText()); } return true; } return importScope; } /** * Prints formatted text of token into string buffer. * * @param token current token */ private void processToken(Token token) { int type = token.getType(); if (type == STRING) { String text = token.getText(); if (!formatString || text.length() < maxLineLength) { if (!(wasWS || token.getType() == LEFT_BRACE || token.getType() == SEMICOLON)) { if (wasNL) { printIndent(); } printIndent(); } sb.append(text); } else { printFormattedString(text); } } else if (type == YangLexer.END_BLOCK_COMMENT) { if (formatComment) { sb.append(lineSeparator); printIndent(); String text = token.getText().substring(0, token.getText().length() - 2); text = text.replaceAll("(?m)^\\s+\\*", ""); printFormattedString(text); sb.append(lineSeparator); printIndent(); sb.append("*/"); } else { sb.append(token.getText()); } } else { if (!(wasWS || isNewLine(token) || isWS(token) || token.getType() == SEMICOLON || token.getType() == LEFT_BRACE)) { printIndent(); } if (!isNewLine(token)) { sb.append(token.getText()); } } if (isNewLine(token)) { sb.append(lineSeparator); } } /** * Prints formatted string token. * * @param token string token */ private void printFormattedString(String str) { String text = str; text = text.replaceAll("\\s+", " "); boolean firstLine = true; StringBuilder textSB = new StringBuilder(); StringTokenizer st = new StringTokenizer(text); while (st.hasMoreTokens()) { if (textSB.length() > 0) { textSB.append(" "); } textSB.append(st.nextToken()); if (textSB.length() > maxLineLength) { if (!firstLine || wasNL) { printIndent(); printIndent(); } sb.append(textSB.toString()); if (st.hasMoreTokens()) { sb.append(lineSeparator); } textSB = new StringBuilder(); firstLine = false; } } if (textSB.length() > 0) { if (!firstLine || wasNL) { printIndent(); printIndent(); } sb.append(textSB.toString()); } } /** * Updates the state before token processed. * * @param token current token */ private void updatePreState(Token token) { int type = token.getType(); // decrease indent if '}' token if (type == RIGHT_BRACE) { currIndent -= indent; if (currIndent < 0) { currIndent = 0; } } // add space in expression like 'st {' if (!wasWS && type == LEFT_BRACE) { sb.append(" "); wasWS = true; } } /** * Updates the state after token processed. * * @param token current token */ private void updateState(Token token) { int type = token.getType(); // increase indent if '{' token if (type == LEFT_BRACE) { currIndent += indent; } ovrNL = false; // added new line after '{', ';', '}' tokens if (type == LEFT_BRACE || type == SEMICOLON || type == RIGHT_BRACE) { sb.append(lineSeparator); ovrNL = true; // nlCount++; } wasNL = isNewLine(token) || ovrNL; wasWS = isWS(token); if (isNewLine(token)) { nlCount++; } if (!wasNL) { nlCount = 0; } } /** * Prints appropriate indent to string buffer. */ private void printIndent() { char[] str = new char[currIndent]; Arrays.fill(str, spaceForTabs ? ' ' : '\t'); sb.append(str); } /** * @param token token to inspect * @return <code>true</code> if token is space or space separator */ private boolean isWS(Token token) { if (token == null) { return false; } int type = token.getType(); return (type == WS || type == S) && !token.getText().equals("\n") && !token.getText().equals("\r"); } /** * @param token token to inspect * @return <code>true</code> if token is new line token. */ private boolean isNewLine(Token token) { if (token == null) { return false; } int type = token.getType(); return (type == WS || type == S) && token.getText().equals("\n"); } /** * @param token token to inspect * @return <code>true</code> if token is '\r' token. */ private boolean isCarriageReturn(Token token) { if (token == null) { return false; } int type = token.getType(); return (type == WS || type == S) && token.getText().equals("\r"); } /** * @param token token to inspect * @return <code>true</code> if token is '{' '}' ';' token. */ private boolean isBlockSeparator(Token token) { if (token == null) { return false; } int type = token.getType(); return type == LEFT_BRACE || type == SEMICOLON || type == RIGHT_BRACE; } @Override public String getFormattedContent() { cleanTokens(); for (Token token : tokens) { boolean ignore = checkForIgnore(token); if (!ignore) { if (!processImportCase(token)) { updatePreState(token); processToken(token); updateState(token); } } } return sb.toString(); } /** * Cleans 'new line' tokens before block separators. We assume that we already clean all 'new * line' that occurred more 2 times. * <br> * New line token is not erased if it followed a comment. */ private void cleanTokens() { int idx = 0; while (idx < tokens.size() - 2) { if (isWS(tokens.get(idx)) && isNewLine(tokens.get(idx + 1))) { tokens.remove(tokens.get(idx)); idx--; } else if (isNewLine(tokens.get(idx)) && isBlockSeparator(tokens.get(idx + 1)) && !isComment(idx-1)) { tokens.remove(tokens.get(idx)); idx--; } else { idx++; } } } /** * Returns true if token at idx is a line comment or is a block comment end. */ private boolean isComment(int idx) { return idx > 0 && (tokens.get(idx).getType() == LINE_COMMENT || tokens.get(idx).getType() == END_BLOCK_COMMENT); } }