1 /* 2 * Copyright (C) 2007-2010 Júlio Vilmar Gesser. 3 * Copyright (C) 2011, 2013-2016 The JavaParser Team. 4 * 5 * This file is part of JavaParser. 6 * 7 * JavaParser can be used either under the terms of 8 * a) the GNU Lesser General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 * b) the terms of the Apache License 12 * 13 * You should have received a copy of both licenses in LICENCE.LGPL and 14 * LICENCE.APACHE. Please refer to those files for details. 15 * 16 * JavaParser is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU Lesser General Public License for more details. 20 */ 21 22 package com.github.javaparser.printer; 23 24 import com.github.javaparser.Position; 25 import com.github.javaparser.printer.PrettyPrinterConfiguration.IndentType; 26 import com.github.javaparser.utils.Utils; 27 28 import java.util.Deque; 29 import java.util.LinkedList; 30 31 import static com.github.javaparser.Position.*; 32 33 /** 34 * A support class for code that outputs formatted source code. 35 */ 36 public class SourcePrinter { 37 private final String endOfLineCharacter; 38 private final String indentation; 39 private final int tabWidth; 40 private final IndentType indentType; 41 42 private final Deque<String> indents = new LinkedList<>(); 43 private final Deque<String> reindentedIndents = new LinkedList<>(); 44 private String lastPrintedIndent = ""; 45 private final StringBuilder buf = new StringBuilder(); 46 private Position cursor = new Position(1, 0); 47 private boolean indented = false; 48 SourcePrinter()49 SourcePrinter() { 50 this(new PrettyPrinterConfiguration()); 51 } 52 SourcePrinter(final PrettyPrinterConfiguration configuration)53 SourcePrinter(final PrettyPrinterConfiguration configuration) { 54 indentation = configuration.getIndent(); 55 endOfLineCharacter = configuration.getEndOfLineCharacter(); 56 tabWidth = configuration.getTabWidth(); 57 indentType = configuration.getIndentType(); 58 indents.push(""); 59 } 60 61 /** 62 * Add the default indentation to the current indentation and push it on the indentation stack. 63 * Does not actually output anything. 64 */ indent()65 public SourcePrinter indent() { 66 String currentIndent = indents.peek(); 67 switch (indentType) { 68 case SPACES: 69 case TABS_WITH_SPACE_ALIGN: 70 indents.push(currentIndent + indentation); 71 break; 72 73 case TABS: 74 indents.push(indentation + currentIndent); 75 break; 76 77 default: 78 throw new AssertionError("Unhandled indent type"); 79 } 80 return this; 81 } 82 83 /** 84 * Add to the current indentation until it is reaches "column" and push it on the indentation stack. 85 * Does not actually output anything. 86 */ indentWithAlignTo(int column)87 public SourcePrinter indentWithAlignTo(int column) { 88 indents.push(calculateIndentWithAlignTo(column)); 89 return this; 90 } 91 calculateIndentWithAlignTo(int column)92 private String calculateIndentWithAlignTo(int column) { 93 if (column < lastPrintedIndent.length()){ 94 throw new IllegalStateException("Attempt to indent less than the previous indent."); 95 } 96 97 StringBuilder newIndent = new StringBuilder(lastPrintedIndent); 98 switch (indentType) { 99 case SPACES: 100 case TABS_WITH_SPACE_ALIGN: 101 while (newIndent.length() < column) { 102 newIndent.append(' '); 103 } 104 break; 105 106 case TABS: 107 int logicalIndentLength = newIndent.length(); 108 while ((logicalIndentLength + tabWidth) <= column) { 109 newIndent.insert(0, '\t'); 110 logicalIndentLength += tabWidth; 111 } 112 while (logicalIndentLength < column) { 113 newIndent.append(' '); 114 logicalIndentLength++; 115 } 116 StringBuilder fullTab = new StringBuilder(); 117 for(int i=0; i<tabWidth; i++){ 118 fullTab.append(' '); 119 } 120 String fullTabString = fullTab.toString(); 121 if ((newIndent.length() >= tabWidth) 122 && newIndent.substring(newIndent.length() - tabWidth).equals(fullTabString)) { 123 int i = newIndent.indexOf(fullTabString); 124 newIndent.replace(i, i + tabWidth, "\t"); 125 } 126 break; 127 128 default: 129 throw new AssertionError("Unhandled indent type"); 130 } 131 132 return newIndent.toString(); 133 } 134 135 /** 136 * Pop the last indentation of the indentation stack. 137 * Does not actually output anything. 138 */ unindent()139 public SourcePrinter unindent() { 140 if (indents.isEmpty()) { 141 // Since we start out with an empty indent on the stack, this will only occur 142 // the second time we over-unindent. 143 throw new IllegalStateException("Indent/unindent calls are not well-balanced."); 144 } 145 indents.pop(); 146 return this; 147 } 148 append(String arg)149 private void append(String arg) { 150 buf.append(arg); 151 cursor = cursor.withColumn(cursor.column + arg.length()); 152 } 153 154 /** 155 * Append the source string passed as argument to the buffer. 156 * If this is being appended at the beginning of a line, performs indentation first. 157 * <p> 158 * The source line to be printed should not contain newline/carriage-return characters; 159 * use {@link #println(String)} to automatically append a newline at the end of the source string. 160 * If the source line passed as argument contains newline/carriage-return characters would 161 * impredictably affect a correct computation of the current {@link #getCursor()} position. 162 * 163 * @param arg source line to be printed (should not contain newline/carriage-return characters) 164 * @return this instance, for nesting calls to method as fluent interface 165 * @see SourcePrinter#println(String) 166 */ print(final String arg)167 public SourcePrinter print(final String arg) { 168 if (!indented) { 169 lastPrintedIndent = indents.peek(); 170 append(lastPrintedIndent); 171 indented = true; 172 } 173 append(arg); 174 return this; 175 } 176 177 /** 178 * Append the source string passed as argument to the buffer, then append a newline. 179 * If this is being appended at the beginning of a line, performs indentation first. 180 * <p> 181 * The source line to be printed should not contain newline/carriage-return characters. 182 * If the source line passed as argument contains newline/carriage-return characters would 183 * impredictably affect a correct computation of the current {@link #getCursor()} position. 184 * 185 * @param arg source line to be printed (should not contain newline/carriage-return characters) 186 * @return this instance, for nesting calls to method as fluent interface 187 */ println(final String arg)188 public SourcePrinter println(final String arg) { 189 print(arg); 190 println(); 191 return this; 192 } 193 194 /** 195 * Append a newline to the buffer. 196 * 197 * @return this instance, for nesting calls to method as fluent interface 198 */ println()199 public SourcePrinter println() { 200 buf.append(endOfLineCharacter); 201 cursor = pos(cursor.line + 1, 0); 202 indented = false; 203 return this; 204 } 205 206 /** 207 * Return the current cursor position (line, column) in the source printer buffer. 208 * <p> 209 * Please notice in order to guarantee a correct computation of the cursor position, 210 * this printer expect the contracts of the methods {@link #print(String)} and {@link #println(String)} 211 * has been respected through all method calls, meaning the source string passed as argument to those method 212 * calls did not contain newline/carriage-return characters. 213 * 214 * @return the current cursor position (line, column). 215 */ getCursor()216 public Position getCursor() { 217 return cursor; 218 } 219 220 /** 221 * @return the currently printed source code. 222 * @deprecated use toString() 223 */ 224 @Deprecated getSource()225 public String getSource() { 226 return toString(); 227 } 228 229 /** 230 * @return the currently printed source code. 231 */ 232 @Override toString()233 public String toString() { 234 return buf.toString(); 235 } 236 237 /** 238 * Changes all EOL characters in "content" to the EOL character this SourcePrinter is using. 239 */ normalizeEolInTextBlock(String content)240 public String normalizeEolInTextBlock(String content) { 241 return Utils.normalizeEolInTextBlock(content, endOfLineCharacter); 242 } 243 244 /** 245 * Set the top-most indent to the column the cursor is currently in, can be undone with 246 * {@link #reindentToPreviousLevel()}. Does not actually output anything. 247 */ reindentWithAlignToCursor()248 public void reindentWithAlignToCursor() { 249 String newIndent = calculateIndentWithAlignTo(cursor.column); 250 reindentedIndents.push(indents.pop()); 251 indents.push(newIndent); 252 } 253 254 /** 255 * Set the top-most indent to the column the cursor was before the last {@link #reindentWithAlignToCursor()} call. 256 * Does not actually output anything. 257 */ reindentToPreviousLevel()258 public void reindentToPreviousLevel() { 259 if (reindentedIndents.isEmpty()) { 260 throw new IllegalStateException("Reindent calls are not well-balanced."); 261 } 262 indents.pop(); 263 indents.push(reindentedIndents.pop()); 264 } 265 266 /** 267 * Adds an indent to the top of the stack that is a copy of the current top indent. 268 * With this you announce "I'm going to indent the next line(s)" but not how far yet. 269 * Once you do know, you can pop this indent ("unindent") and indent to the right column. 270 * (Does not actually output anything.) 271 */ duplicateIndent()272 public void duplicateIndent() { 273 indents.push(indents.peek()); 274 } 275 } 276