1 | /* This file is part of the project "Hilbert II" - http://www.qedeq.org |
2 | * |
3 | * Copyright 2000-2014, Michael Meyling <mime@qedeq.org>. |
4 | * |
5 | * "Hilbert II" is free software; you can redistribute |
6 | * it and/or modify it under the terms of the GNU General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2 of the License, or (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | */ |
15 | |
16 | package org.qedeq.base.io; |
17 | |
18 | import org.qedeq.base.utility.Splitter; |
19 | import org.qedeq.base.utility.StringUtility; |
20 | |
21 | |
22 | /** |
23 | * Wraps a text output stream. |
24 | * |
25 | * @author Michael Meyling |
26 | */ |
27 | public abstract class AbstractOutput { |
28 | |
29 | /** Tab level. */ |
30 | private StringBuffer spaces = new StringBuffer(); |
31 | |
32 | /** Break at this column if greater zero. */ |
33 | private int breakAt; |
34 | |
35 | /** Tab level of current line. This is equal to spaces before any character is |
36 | * written. After writing to the current line this value is fixed and doesn't change even |
37 | * if the tab level is changed. |
38 | */ |
39 | private String spacesForCurrentLine = ""; |
40 | |
41 | /** Current column. */ |
42 | private int col; |
43 | |
44 | /** Token buffer. */ |
45 | private StringBuffer tokenBuffer = new StringBuffer(); |
46 | |
47 | /** Whitespace buffer. */ |
48 | private StringBuffer wsBuffer = new StringBuffer(); |
49 | |
50 | /** |
51 | * Constructor. |
52 | */ |
53 | public AbstractOutput() { |
54 | } |
55 | |
56 | /** |
57 | * Add whitespace to output. |
58 | * |
59 | * @param ws Add this whitespace. |
60 | */ |
61 | public void addWs(final String ws) { |
62 | final String[] lines = StringUtility.split(ws, "\n"); |
63 | for (int i = 0; i < lines.length; i++) { |
64 | if (i > 0) { |
65 | println(); |
66 | } |
67 | addWsWithoutCR(lines[i]); |
68 | } |
69 | } |
70 | |
71 | /** |
72 | * Add whitespace to output. Must not contain CRs. |
73 | * |
74 | * @param ws Add this whitespace. |
75 | */ |
76 | private void addWsWithoutCR(final String ws) { |
77 | if (tokenBuffer.length() > 0) { |
78 | if (fits(wsBuffer.length() + tokenBuffer.length())) { |
79 | if (col == 0) { |
80 | appendSpaces(); |
81 | } |
82 | append(wsBuffer.toString()); |
83 | col += wsBuffer.length(); |
84 | append(tokenBuffer.toString()); |
85 | col += tokenBuffer.length(); |
86 | } else { |
87 | // forget non fitting part of white space |
88 | if (col != 0) { |
89 | appendFittingPart(wsBuffer.toString()); |
90 | append("\n"); |
91 | } |
92 | col = 0; |
93 | appendSpaces(); |
94 | append(tokenBuffer.toString()); |
95 | col += tokenBuffer.length(); |
96 | } |
97 | wsBuffer.setLength(0); |
98 | tokenBuffer.setLength(0); |
99 | } |
100 | wsBuffer.append(ws); |
101 | } |
102 | |
103 | /** |
104 | * Append token to output. |
105 | * |
106 | * @param part Add this part. |
107 | */ |
108 | public void addToken(final String part) { |
109 | // remember tabular spaces when we start writing |
110 | if (col == 0 && part.length() > 0) { |
111 | setTabLevel(); |
112 | } |
113 | tokenBuffer.append(part); |
114 | } |
115 | |
116 | /** |
117 | * Flush output. |
118 | */ |
119 | public void flush() { |
120 | addWsWithoutCR(""); |
121 | appendFittingPart(wsBuffer.toString()); |
122 | wsBuffer.setLength(0); |
123 | } |
124 | |
125 | /** |
126 | * Append a part of given text so that the current maximum column is not exceeded. |
127 | * |
128 | * @param txt Write this text. |
129 | * @return Length of written characters. |
130 | */ |
131 | private int appendFittingPart(final String txt) { |
132 | final int columnsLeft = columnsLeft(); |
133 | if (columnsLeft > 0) { |
134 | final String part = StringUtility.substring(txt, 0, columnsLeft); |
135 | append(part); |
136 | col += part.length(); |
137 | return part.length(); |
138 | } else if (columnsLeft < 0) { |
139 | append(txt); |
140 | col += txt.length(); |
141 | return txt.length(); |
142 | } |
143 | return 0; |
144 | } |
145 | |
146 | /** |
147 | * Print character to output. |
148 | * |
149 | * @param c Append this. |
150 | */ |
151 | public void print(final char c) { |
152 | print("" + c); |
153 | } |
154 | |
155 | /** |
156 | * Print text and split at white space if text doesn't fit within maximum column size. |
157 | * Also flushes output. |
158 | * |
159 | * @param text Append this. |
160 | */ |
161 | public void print(final String text) { |
162 | flush(); |
163 | if (text == null) { |
164 | addToken("null"); |
165 | } else { |
166 | final String[] lines = StringUtility.split(text, "\n"); |
167 | for (int i = 0; i < lines.length; i++) { |
168 | final Splitter split = new Splitter(lines[i]); |
169 | while (split.hasNext()) { |
170 | final String token = split.nextToken(); |
171 | final boolean isWhitespace = token.trim().length() == 0; |
172 | if (isWhitespace) { |
173 | addWsWithoutCR(token); |
174 | } else { |
175 | addToken(token); |
176 | } |
177 | } |
178 | if (i + 1 < lines.length) { |
179 | println(); |
180 | } |
181 | } |
182 | } |
183 | flush(); |
184 | } |
185 | |
186 | /** |
187 | * Append text directly to output device. |
188 | * |
189 | * @param text Append this text. |
190 | */ |
191 | public abstract void append(final String text); |
192 | |
193 | /** |
194 | * Get writing position. |
195 | * |
196 | * @return Writing position. |
197 | */ |
198 | public abstract long getPosition(); |
199 | |
200 | /** |
201 | * Print spaces and text to output. |
202 | * |
203 | * @param text Append this. |
204 | */ |
205 | public void printWithoutSplit(final String text) { |
206 | flush(); |
207 | if (text == null) { |
208 | return; |
209 | } |
210 | if (col == 0) { |
211 | if (text.length() > 0) { |
212 | // remember tabular spaces when we start writing |
213 | setTabLevel(); |
214 | appendSpaces(); |
215 | } |
216 | } else if (!fits(text)) { |
217 | println(); |
218 | appendSpaces(); |
219 | } |
220 | append(text); |
221 | col += text.length(); |
222 | } |
223 | |
224 | /** |
225 | * Does the text fit to current line? |
226 | * |
227 | * @param text Check if this text could be appended without line break. |
228 | * @return Does it fit? |
229 | */ |
230 | private boolean fits(final String text) { |
231 | if (text == null) { |
232 | return true; |
233 | } |
234 | return fits(text.length()); |
235 | } |
236 | |
237 | /** |
238 | * Does a text with given length fit to current line? |
239 | * TODO 20110104 m31: should't we use spacesForCurrentLine also? |
240 | * |
241 | * @param length Check if a text of this length could be appended without line break. |
242 | * @return Does it fit? |
243 | */ |
244 | private boolean fits(final int length) { |
245 | return breakAt <= 0 || col + length <= breakAt; |
246 | } |
247 | |
248 | /** |
249 | * How many columns have we left? |
250 | * |
251 | * @return Columns left. Returns -1 if there is no limit. |
252 | */ |
253 | public int columnsLeft() { |
254 | if (breakAt <= 0) { |
255 | return -1; |
256 | } else { |
257 | return Math.max(0, breakAt - col); |
258 | } |
259 | } |
260 | |
261 | /** |
262 | * Print object to output. |
263 | * |
264 | * @param object Append text representation of this. |
265 | */ |
266 | public void print(final Object object) { |
267 | print(String.valueOf(object)); |
268 | } |
269 | |
270 | /** |
271 | * Print spaces text and new line to output. |
272 | * |
273 | * @param token Append this. |
274 | */ |
275 | public final void println(final String token) { |
276 | print(token); |
277 | println(); |
278 | } |
279 | |
280 | /** |
281 | * Print object and new line to output. |
282 | * |
283 | * @param object Append text representation of this. |
284 | */ |
285 | public final void println(final Object object) { |
286 | println(String.valueOf(object)); |
287 | } |
288 | |
289 | /** |
290 | * Print new line to output. |
291 | */ |
292 | public void println() { |
293 | flush(); |
294 | if (col == 0 && spaces.toString().trim().length() > 0) { |
295 | setTabLevel(); |
296 | appendSpaces(); |
297 | } |
298 | append("\n"); |
299 | col = 0; |
300 | } |
301 | |
302 | /** |
303 | * Skip until given column. To do this we append spaces. |
304 | * |
305 | * @param column Skip to this column. |
306 | */ |
307 | public void skipToColumn(final int column) { |
308 | for (int i = col; i < column; i++) { |
309 | printWithoutSplit(" "); |
310 | } |
311 | } |
312 | |
313 | /** |
314 | * Reset tab level to zero. |
315 | */ |
316 | public final void clearLevel() { |
317 | // flush(); |
318 | spaces.setLength(0); |
319 | } |
320 | |
321 | /** |
322 | * Decrement tab level. |
323 | */ |
324 | public final void popLevel() { |
325 | if (spaces.length() > 0) { |
326 | spaces.setLength(spaces.length() - 2); |
327 | } |
328 | } |
329 | |
330 | /** |
331 | * Decrement tab level. |
332 | * |
333 | * @param characters Number of characters to reduce from tab level. |
334 | */ |
335 | public final void popLevel(final int characters) { |
336 | if (spaces.length() > 0) { |
337 | spaces.setLength(Math.max(spaces.length() - characters, 0)); |
338 | } |
339 | } |
340 | |
341 | /** |
342 | * Return current tab string. |
343 | * |
344 | * @return Current tab string. |
345 | */ |
346 | public final String getLevel() { |
347 | return spaces.toString(); |
348 | } |
349 | |
350 | /** |
351 | * Set current tab string. |
352 | * |
353 | * @param level Tab string. |
354 | */ |
355 | public final void setLevel(final String level) { |
356 | spaces.setLength(0); |
357 | spaces.append(level); |
358 | } |
359 | |
360 | /** |
361 | * Increment tab level (this are two spaces). |
362 | */ |
363 | public final void pushLevel() { |
364 | spaces.append(" "); |
365 | } |
366 | |
367 | /** |
368 | * Increment tab level with following symbols. |
369 | * |
370 | * @param symbols Symbols to tab width. Length should be exactly 2 characters! |
371 | */ |
372 | public final void pushLevel(final String symbols) { |
373 | spaces.append(symbols); |
374 | } |
375 | |
376 | /** |
377 | * Set current tab level to current level. Might change unwritten lines. |
378 | */ |
379 | public final void setTabLevel() { |
380 | spacesForCurrentLine = spaces.toString(); |
381 | } |
382 | |
383 | /** |
384 | * Set number of maximum columns. If possible we break before we reach this column number. |
385 | * If less or equal to zero no line breaking is done automatically. |
386 | * |
387 | * @param columns Maximum column size. |
388 | */ |
389 | public void setColumns(final int columns) { |
390 | if (columns < 0) { |
391 | breakAt = 0; |
392 | } else { |
393 | breakAt = columns; |
394 | } |
395 | } |
396 | |
397 | /** |
398 | * Return number of maximum columns. If equal to zero no line breaking is done automatically. |
399 | * |
400 | * @return Maximum column size. |
401 | */ |
402 | public final int getColumns() { |
403 | return breakAt; |
404 | } |
405 | |
406 | /** |
407 | * Append tabulation and increase current column. |
408 | */ |
409 | private void appendSpaces() { |
410 | append(spacesForCurrentLine.toString()); |
411 | col += spacesForCurrentLine.length(); |
412 | } |
413 | |
414 | } |