001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002 *
003 * Copyright 2000-2013, Michael Meyling <mime@qedeq.org>.
004 *
005 * "Hilbert II" is free software; you can redistribute
006 * it and/or modify it under the terms of the GNU General Public
007 * License as published by the Free Software Foundation; either
008 * version 2 of the License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU General Public License for more details.
014 */
015
016 package org.qedeq.base.io;
017
018 import org.qedeq.base.utility.Splitter;
019 import org.qedeq.base.utility.StringUtility;
020
021
022 /**
023 * Wraps a text output stream.
024 *
025 * @author Michael Meyling
026 */
027 public abstract class AbstractOutput {
028
029 /** Tab level. */
030 private StringBuffer spaces = new StringBuffer();
031
032 /** Break at this column if greater zero. */
033 private int breakAt;
034
035 /** Tab level of current line. This is equal to spaces before any character is
036 * written. After writing to the current line this value is fixed and doesn't change even
037 * if the tab level is changed.
038 */
039 private String spacesForCurrentLine = "";
040
041 /** Current column. */
042 private int col;
043
044 /** Token buffer. */
045 private StringBuffer tokenBuffer = new StringBuffer();
046
047 /** Whitespace buffer. */
048 private StringBuffer wsBuffer = new StringBuffer();
049
050 /**
051 * Constructor.
052 */
053 public AbstractOutput() {
054 }
055
056 /**
057 * Add whitespace to output.
058 *
059 * @param ws Add this whitespace.
060 */
061 public void addWs(final String ws) {
062 final String[] lines = StringUtility.split(ws, "\n");
063 for (int i = 0; i < lines.length; i++) {
064 if (i > 0) {
065 println();
066 }
067 addWsWithoutCR(lines[i]);
068 }
069 }
070
071 /**
072 * Add whitespace to output. Must not contain CRs.
073 *
074 * @param ws Add this whitespace.
075 */
076 private void addWsWithoutCR(final String ws) {
077 if (tokenBuffer.length() > 0) {
078 if (fits(wsBuffer.length() + tokenBuffer.length())) {
079 if (col == 0) {
080 appendSpaces();
081 }
082 append(wsBuffer.toString());
083 col += wsBuffer.length();
084 append(tokenBuffer.toString());
085 col += tokenBuffer.length();
086 } else {
087 // forget non fitting part of white space
088 if (col != 0) {
089 appendFittingPart(wsBuffer.toString());
090 append("\n");
091 }
092 col = 0;
093 appendSpaces();
094 append(tokenBuffer.toString());
095 col += tokenBuffer.length();
096 }
097 wsBuffer.setLength(0);
098 tokenBuffer.setLength(0);
099 }
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 }
|