/** * Copyright (C) 2006-2017 INRIA and contributors * Spoon - http://spoon.gforge.inria.fr/ * * This software is governed by the CeCILL-C License under French law and * abiding by the rules of distribution of free software. You can use, modify * and/or redistribute the software under the terms of the CeCILL-C license as * circulated by CEA, CNRS and INRIA at http://www.cecill.info. * * This program 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 CeCILL-C License for more details. * * The fact that you are presently reading this means that you have had * knowledge of the CeCILL-C license and that you accept its terms. */ package spoon.reflect.visitor.printer; import org.apache.log4j.Level; import spoon.compiler.Environment; import spoon.reflect.code.BinaryOperatorKind; import spoon.reflect.code.UnaryOperatorKind; import spoon.reflect.cu.CompilationUnit; import spoon.reflect.declaration.CtElement; import java.util.ArrayDeque; import java.util.HashMap; import java.util.Map; public class PrinterHelper { /** * Line separator which is used by the system */ private static final String LINE_SEPARATOR = System.getProperty("line.separator"); /** * Environment which Spoon is executed. */ private Environment env; /** * The string buffer in which the code is generated. */ private final StringBuffer sbf = new StringBuffer(); /** * Number of tabs when we print the source code. */ private int nbTabs = 0; /** * Current line number. */ private int line = 1; /** * Mapping for line numbers. */ private Map<Integer, Integer> lineNumberMapping = new HashMap<>(); public PrinterHelper(Environment env) { this.env = env; } /** * Outputs a string. */ public PrinterHelper write(String s) { if (s != null) { sbf.append(s); } return this; } /** * Outputs a char. */ public PrinterHelper write(char c) { sbf.append(c); return this; } /** * Generates a new line. */ public PrinterHelper writeln() { sbf.append(LINE_SEPARATOR); line++; return this; } public PrinterHelper writeTabs() { for (int i = 0; i < nbTabs; i++) { if (env.isUsingTabulations()) { sbf.append("\t"); } else { for (int j = 0; j < env.getTabulationSize(); j++) { sbf.append(" "); } } } return this; } /** * Increments the current number of tabs. */ public PrinterHelper incTab() { nbTabs++; return this; } /** * Decrements the current number of tabs. */ public PrinterHelper decTab() { nbTabs--; return this; } /** * Sets the current number of tabs. */ public PrinterHelper setTabCount(int tabCount) { nbTabs = tabCount; return this; } public void insertLine() { int i = sbf.length() - 1; while (i >= 0 && (sbf.charAt(i) == ' ' || sbf.charAt(i) == '\t')) { i--; } sbf.insert(i + 1, LINE_SEPARATOR); line++; } public boolean removeLine() { String ls = LINE_SEPARATOR; int i = sbf.length() - ls.length(); boolean hasWhite = false; while (i > 0 && !ls.equals(sbf.substring(i, i + ls.length()))) { if (!isWhite(sbf.charAt(i))) { return false; } hasWhite = true; i--; } if (i <= 0) { return false; } hasWhite = hasWhite || isWhite(sbf.charAt(i - 1)); sbf.replace(i, i + ls.length(), hasWhite ? "" : " "); line--; return true; } private boolean isWhite(char c) { return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r'); } public void adjustPosition(CtElement e, CompilationUnit unitExpected) { if (e.getPosition() != null && !e.isImplicit() && e.getPosition().getCompilationUnit() != null && e.getPosition().getCompilationUnit() == unitExpected) { while (line < e.getPosition().getLine()) { insertLine(); } while (line > e.getPosition().getLine()) { if (!removeLine()) { if (line > e.getPosition().getEndLine()) { final String message = "cannot adjust position of " + e.getClass().getSimpleName() + " '" // + e.getShortRepresentation() + "' " + " to match lines: " + line + " > [" // + e.getPosition().getLine() + ", " + e.getPosition().getEndLine() + "]"; // env.report(null, Level.WARN, e, message); } break; } } } } public void undefineLine() { if (lineNumberMapping.get(line) == null) { putLineNumberMapping(0); } } public void mapLine(CtElement e, CompilationUnit unitExpected) { if ((e.getPosition() != null) && (e.getPosition().getCompilationUnit() == unitExpected)) { // only map elements coming from the source CU putLineNumberMapping(e.getPosition().getLine()); } else { undefineLine(); } } public void putLineNumberMapping(int valueLine) { lineNumberMapping.put(this.line, valueLine); } /** * Removes the last non-white character. */ public PrinterHelper removeLastChar() { while (isWhite(sbf.charAt(sbf.length() - 1))) { if (sbf.charAt(sbf.length() - 1) == '\n') { line--; } sbf.deleteCharAt(sbf.length() - 1); } sbf.deleteCharAt(sbf.length() - 1); while (isWhite(sbf.charAt(sbf.length() - 1))) { if (sbf.charAt(sbf.length() - 1) == '\n') { line--; } sbf.deleteCharAt(sbf.length() - 1); } return this; } /** * Write a pre unary operator. */ public void preWriteUnaryOperator(UnaryOperatorKind o) { switch (o) { case POS: write("+"); break; case NEG: write("-"); break; case NOT: write("!"); break; case COMPL: write("~"); break; case PREINC: write("++"); break; case PREDEC: write("--"); break; default: // do nothing (this does not feel right to ignore invalid ops) } } /** * Write a post unary operator. */ public void postWriteUnaryOperator(UnaryOperatorKind o) { switch (o) { case POSTINC: write("++"); break; case POSTDEC: write("--"); break; default: // do nothing (this does not feel right to ignore invalid ops) } } /** * Writes a binary operator. */ public PrinterHelper writeOperator(BinaryOperatorKind o) { switch (o) { case OR: write("||"); break; case AND: write("&&"); break; case BITOR: write("|"); break; case BITXOR: write("^"); break; case BITAND: write("&"); break; case EQ: write("=="); break; case NE: write("!="); break; case LT: write("<"); break; case GT: write(">"); break; case LE: write("<="); break; case GE: write(">="); break; case SL: write("<<"); break; case SR: write(">>"); break; case USR: write(">>>"); break; case PLUS: write("+"); break; case MINUS: write("-"); break; case MUL: write("*"); break; case DIV: write("/"); break; case MOD: write("%"); break; case INSTANCEOF: write("instanceof"); break; } return this; } public void writeStringLiteral(String value, boolean mayContainsSpecialCharacter) { if (!mayContainsSpecialCharacter) { write(value); return; } // handle some special char..... for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); if (Character.UnicodeBlock.of(c) != Character.UnicodeBlock.BASIC_LATIN) { if (c < 0x10) { write("\\u000" + Integer.toHexString(c)); } else if (c < 0x100) { write("\\u00" + Integer.toHexString(c)); } else if (c < 0x1000) { write("\\u0" + Integer.toHexString(c)); } else { write("\\u" + Integer.toHexString(c)); } continue; } switch (c) { case '\b': write("\\b"); //$NON-NLS-1$ break; case '\t': write("\\t"); //$NON-NLS-1$ break; case '\n': write("\\n"); //$NON-NLS-1$ break; case '\f': write("\\f"); //$NON-NLS-1$ break; case '\r': write("\\r"); //$NON-NLS-1$ break; case '\"': write("\\\""); //$NON-NLS-1$ break; case '\'': write("\\'"); //$NON-NLS-1$ break; case '\\': // take care not to display the escape as a potential // real char write("\\\\"); //$NON-NLS-1$ break; default: write(value.charAt(i)); } } } public Map<Integer, Integer> getLineNumberMapping() { return lineNumberMapping; } @Override public String toString() { return sbf.toString(); } private ArrayDeque<Integer> lengths = new ArrayDeque<>(); /** stores the length of the printer */ public void snapshotLength() { lengths.addLast(toString().length()); } /** returns true if something has been written since the last call to snapshotLength() */ public boolean hasNewContent() { return lengths.pollLast() < toString().length(); } }