1 /* This file is part of the project "Hilbert II" - http://www.qedeq.org" target="alexandria_uri">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 }