001: package org.obe.util;
002:
003: import java.io.OutputStream;
004: import java.io.PrintWriter;
005: import java.io.Writer;
006: import java.util.Stack;
007:
008: /**
009: * A PrintWriter class that supports tab stops and programmable indentation.
010: * The writer interprets tab characters in the usual way, by emitting spaces to
011: * align the following characters at the next tab stop to the right of the
012: * current print position. The default tab stops are at every 8 characters,
013: * that is, in columns 9, 17, 25, ... 81. The {@link #setTabs} method allows
014: * custom tab stops to be specified. Indentation can be controlled via the
015: * {@link #pushIndent()}, {@link #pushIndent(int)} and {@link #popIndent()}
016: * methods.
017: * <p/>
018: * The writer scans all arguments for newline characters, and achieves the
019: * desired indentation level by inserting a corresponding number of spaces into
020: * the output before continuing with the characters following the newline.
021: * Indentation settings are held internally on a stack so that cancelling the
022: * current setting automatically restores the previous one.
023: *
024: * @author Adrian Price
025: */
026: public class FormattingPrintWriter extends PrintWriter {
027: private static final String NEWLINE = System.getProperty(
028: "line.separator", "\r\n");
029: // Standard tab settings
030: private static final int[] STD_TABS = { 9, 17, 25, 33, 41, 49, 57,
031: 65, 73, 81 };
032:
033: private boolean _autoFlush;
034: private int[] _tabs = STD_TABS;
035: private Stack _stack = new Stack();
036: private int _indent;
037: private int _pos;
038:
039: /**
040: * Returns a string consisting of the specified number of spaces.
041: *
042: * @return The requested whitespace string.
043: */
044: private static String spaces(int n) {
045: char[] ca = new char[n];
046: for (int i = 0; i < n; i++)
047: ca[i] = ' ';
048: return new String(ca, 0, ca.length);
049: }
050:
051: /**
052: * Constructs a new FormattingPrintWriter, without automatic line flushing.
053: *
054: * @param out A character-output stream.
055: */
056: public FormattingPrintWriter(Writer out) {
057: super (out);
058: }
059:
060: /**
061: * Constructs a new FormattingPrintWriter.
062: *
063: * @param out A character-output stream.
064: * @param autoFlush If <code>true</code>, the println() methods will flush
065: * the output buffer.
066: */
067: public FormattingPrintWriter(Writer out, boolean autoFlush) {
068: super (out, autoFlush);
069: _autoFlush = autoFlush;
070: }
071:
072: /**
073: * Constructs a new PrintWriter, without automatic line flushing, from an
074: * existing OutputStream. This convenience constructor creates the necessary
075: * intermediate OutputStreamWriter, which will convert characters into bytes
076: * using the default character encoding.
077: *
078: * @param out An output stream.
079: */
080: public FormattingPrintWriter(OutputStream out) {
081: super (out);
082: }
083:
084: /**
085: * Constructs a new PrintWriter from an existing OutputStream. This
086: * convenience constructor creates the necessary intermediate
087: * OutputStreamWriter, which will convert characters into bytes using the
088: * default character encoding.
089: *
090: * @param out An output stream.
091: * @param autoFlush if <code>true</code>, the println() methods will flush
092: * the output buffer.
093: */
094: public FormattingPrintWriter(OutputStream out, boolean autoFlush) {
095: super (out, autoFlush);
096: _autoFlush = autoFlush;
097: }
098:
099: /**
100: * Restores the default tab stops.
101: */
102: public void clearTabs() {
103: setTabs(null);
104: }
105:
106: /**
107: * Sets custom tab stops. At output positions past the rightmost tab stop,
108: * tab characters are converted into single spaces.
109: *
110: * @param tabs Unity-based tab stop positions, as an ascending sequence of
111: * positive integers.
112: */
113: public void setTabs(int[] tabs) {
114: synchronized (lock) {
115: if (tabs == null) {
116: _tabs = STD_TABS;
117: } else {
118: for (int i = 0, n = tabs.length - 1; i < n; i++) {
119: if (tabs[i] <= 0 || tabs[i] >= tabs[i + 1])
120: throw new IllegalArgumentException(
121: "Tab stops must be an ascending sequence of positive integers.");
122: }
123: _tabs = new int[tabs.length];
124: System.arraycopy(tabs, 0, _tabs, 0, tabs.length);
125: }
126: if (_pos != 0)
127: println();
128: }
129: }
130:
131: /**
132: * Returns unity-based tab stop positions, as an ascending sequence of
133: * positive integers.
134: *
135: * @return Current tab stops.
136: */
137: public int[] getTabs() {
138: return (int[]) _tabs.clone();
139: }
140:
141: /**
142: * Increases the indentation level by the specified amount.
143: *
144: * @param i Number of columns by which to increase indentation.
145: */
146: public void pushIndent(int i) {
147: if (i <= 0) {
148: throw new IllegalArgumentException(
149: "Indentation must be a positive integer");
150: }
151: synchronized (lock) {
152: _stack.push(new Integer(i));
153: _indent += i;
154: }
155: }
156:
157: /**
158: * Increases the indentation level to the next tab stop past the current
159: * output position.
160: */
161: public void pushIndent() {
162: // Indent to the nearest tab stop to the right of the current
163: // indentation level, if such a tab stop exists.
164: for (int i = 0, n = _tabs.length; i < n; i++)
165: if (_tabs[i] > _indent) {
166: pushIndent(_tabs[i] - _indent);
167: return;
168: }
169: // Past the rightmost tab stop, indentation is one space.
170: pushIndent(1);
171: }
172:
173: /**
174: * Restores the previous indentation level.
175: *
176: * @throws IllegalStateException if the current indentation level is 0.
177: */
178: public void popIndent() {
179: if (_stack.empty())
180: throw new IllegalStateException();
181: _indent -= ((Integer) _stack.pop()).intValue();
182: }
183:
184: /**
185: * Returns the current indentation level.
186: *
187: * @return Indentation level as a character count.
188: */
189: public int getIndent() {
190: return _indent;
191: }
192:
193: /**
194: * Returns the current output position (zero-based).
195: *
196: * @return The output position.
197: */
198: public int getPosition() {
199: return _pos;
200: }
201:
202: /**
203: * Expands a tab character by setting the output position to the next tab
204: * stop past the current output position.
205: *
206: * @return Space-filled string.
207: */
208: private String expandTab() {
209: // If pos is after the last tab stop, translate tab characters to spaces.
210: String s = " ";
211: int curpos = _indent + _pos;
212: for (int i = 0; i < _tabs.length; i++) {
213: // Tab stops use 1-based column numbers,
214: if (_tabs[i] - 1 > curpos) {
215: // curpos is a 0-based column index.
216: s = spaces(_tabs[i] - curpos - 1);
217: break;
218: }
219: }
220: _pos += s.length();
221: return s;
222: }
223:
224: /**
225: * Expands embedded tab and newline escape sequences, adjusting the output
226: * position accordingly. The method recognizes 'C'/Java-style '\t', '\r' and
227: * '\n' escape sequences.
228: *
229: * @param ch Character to expand.
230: * @return String containing (expanded) input character.
231: */
232: private String expandEscapes(char ch) {
233: StringBuffer result = new StringBuffer();
234: switch (ch) {
235: case '\t':
236: if (_pos == 0 && _indent > 0)
237: result.append(spaces(_indent));
238: result.append(expandTab());
239: break;
240: case '\n':
241: _pos = 0;
242: case '\r':
243: result.append(ch);
244: break;
245: default:
246: if (_pos == 0 && _indent > 0)
247: result.append(spaces(_indent));
248: result.append(ch);
249: _pos++;
250: }
251: return result.toString();
252: }
253:
254: /**
255: * Expands embedded tab and newline escape sequences, adjusting the output
256: * position accordingly. The method recognizes 'C'/Java-style '\t', '\r' and
257: * '\n' escape sequences.
258: *
259: * @param s Source string.
260: * @param off Offset at which to start copying.
261: * @param len Number of source characters to process.
262: * @return Copy of the source string where all escape sequences have been
263: * replaced by their equivalent characters.
264: */
265: private String expandEscapes(String s, int off, int len) {
266: StringBuffer result = new StringBuffer(len);
267: for (int i = off, end = off + len; i < end; i++) {
268: char ch = s.charAt(i);
269: switch (ch) {
270: case '\t':
271: if (_pos == 0 && _indent > 0)
272: result.append(spaces(_indent));
273: result.append(expandTab());
274: break;
275: case '\n':
276: _pos = 0;
277: case '\r':
278: result.append(ch);
279: break;
280: default:
281: if (_pos == 0 && _indent > 0)
282: result.append(spaces(_indent));
283: result.append(ch);
284: _pos++;
285: }
286: }
287: return result.toString();
288: }
289:
290: /**
291: * Writes a character, which may be a tab or newline.
292: *
293: * @param c Character to write.
294: */
295: private void _writeEx(int c) {
296: String s = expandEscapes((char) c);
297: super .write(s, 0, s.length());
298: }
299:
300: /**
301: * Writes a string which may contain tab or newline characters.
302: *
303: * @param s Source string.
304: * @param off Offset at which to start writing.
305: * @param len Number of source characters to process.
306: */
307: private void _writeEx(String s, int off, int len) {
308: s = expandEscapes(s, off, len);
309: super .write(s, 0, s.length());
310: }
311:
312: /**
313: * Writes a string that does not contain embedded tabs or newlines.
314: *
315: * @param s Source string.
316: * @param off Offset at which to start writing.
317: * @param len Number of source characters to process.
318: */
319: private void _write(String s, int off, int len) {
320: _pos += len;
321: super .write(s, off, len);
322: }
323:
324: public void print(boolean b) {
325: String s = String.valueOf(b);
326: _write(s, 0, s.length());
327: }
328:
329: public void print(char c) {
330: _writeEx(c);
331: }
332:
333: public void print(int i) {
334: String s = String.valueOf(i);
335: _write(s, 0, s.length());
336: }
337:
338: public void print(long l) {
339: String s = String.valueOf(l);
340: _write(s, 0, s.length());
341: }
342:
343: public void print(float f) {
344: String s = String.valueOf(f);
345: _write(s, 0, s.length());
346: }
347:
348: public void print(double d) {
349: String s = String.valueOf(d);
350: _write(s, 0, s.length());
351: }
352:
353: public void print(char[] ca) {
354: _writeEx(new String(ca), 0, ca.length);
355: }
356:
357: public void print(String s) {
358: _writeEx(s, 0, s.length());
359: }
360:
361: public void print(Object obj) {
362: String s = String.valueOf(obj);
363: _writeEx(s, 0, s.length());
364: }
365:
366: private void newLine() {
367: _write(NEWLINE, 0, NEWLINE.length());
368: _pos = 0;
369: if (_autoFlush)
370: flush();
371: }
372:
373: public void println() {
374: synchronized (lock) {
375: newLine();
376: }
377: }
378:
379: public void println(boolean b) {
380: synchronized (lock) {
381: print(b);
382: newLine();
383: }
384: }
385:
386: public void println(char c) {
387: synchronized (lock) {
388: print(c);
389: newLine();
390: }
391: }
392:
393: public void println(int i) {
394: synchronized (lock) {
395: print(i);
396: newLine();
397: }
398: }
399:
400: public void println(long l) {
401: synchronized (lock) {
402: print(l);
403: newLine();
404: }
405: }
406:
407: public void println(float f) {
408: synchronized (lock) {
409: print(f);
410: newLine();
411: }
412: }
413:
414: public void println(double d) {
415: synchronized (lock) {
416: print(d);
417: newLine();
418: }
419: }
420:
421: public void println(char[] c) {
422: synchronized (lock) {
423: print(c);
424: newLine();
425: }
426: }
427:
428: public void println(String s) {
429: synchronized (lock) {
430: print(s);
431: newLine();
432: }
433: }
434:
435: public void println(Object obj) {
436: synchronized (lock) {
437: print(obj);
438: newLine();
439: }
440: }
441:
442: public void write(int c) {
443: _writeEx(c);
444: }
445:
446: public void write(char[] buf, int off, int len) {
447: _writeEx(new String(buf, off, len), 0, len);
448: }
449:
450: public void write(char[] buf) {
451: _writeEx(new String(buf), 0, buf.length);
452: }
453:
454: public void write(String s, int off, int len) {
455: _writeEx(s, off, len);
456: }
457:
458: public void write(String s) {
459: _writeEx(s, 0, s.length());
460: }
461: }
|