• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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