001 // Copyright 2004, 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry.util.io;
016
017 import java.io.IOException;
018 import java.io.OutputStream;
019 import java.io.PrintWriter;
020 import java.io.Writer;
021
022 /**
023 * A kind of super-formatter. It is sent a stream of binary data and formats it in a human-readable
024 * dump format which is forwarded to its output stream.
025 * <p>
026 * Currently, output is in hex though options to change that may be introduced.
027 *
028 * @author Howard Lewis Ship
029 */
030
031 public class BinaryDumpOutputStream extends OutputStream
032 {
033 private static final char[] HEX =
034 { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
035
036 private PrintWriter _out;
037
038 private boolean locked = false;
039
040 private boolean _showOffset = true;
041
042 private int bytesPerLine = 16;
043
044 private int _spacingInterval = 4;
045
046 private char substituteChar = '.';
047
048 private String offsetSeperator = ": ";
049
050 private int offset = 0;
051
052 private int lineCount = 0;
053
054 private int bytesSinceSpace = 0;
055
056 private char[] ascii = null;
057
058 private boolean showAscii = true;
059
060 private String asciiBegin = " |";
061
062 private String asciiEnd = "|";
063
064 /**
065 * Creates a <code>PrintWriter</code> for <code>System.out</code>.
066 */
067
068 public BinaryDumpOutputStream()
069 {
070 this(new PrintWriter(System.out, true));
071 }
072
073 public BinaryDumpOutputStream(PrintWriter out)
074 {
075 this._out = out;
076 }
077
078 public BinaryDumpOutputStream(Writer out)
079 {
080 this._out = new PrintWriter(out);
081 }
082
083 public void close() throws IOException
084 {
085 if (_out != null)
086 {
087 if (lineCount > 0)
088 finishFinalLine();
089
090 _out.close();
091 }
092
093 _out = null;
094 }
095
096 private void finishFinalLine()
097 {
098 // Since we only finish the final line after at least one byte has
099 // been written to it, we don't need to worry about
100 // the offset.
101
102 while (lineCount < bytesPerLine)
103 {
104 // After every <n> bytes, emit a space.
105
106 if (_spacingInterval > 0 && bytesSinceSpace == _spacingInterval)
107 {
108 _out.print(' ');
109 bytesSinceSpace = 0;
110 }
111
112 // Two spaces to substitute for the two hex digits.
113
114 _out.print(" ");
115
116 if (showAscii)
117 ascii[lineCount] = ' ';
118
119 lineCount++;
120 bytesSinceSpace++;
121 }
122
123 if (showAscii)
124 {
125 _out.print(asciiBegin);
126 _out.print(ascii);
127 _out.print(asciiEnd);
128 }
129
130 _out.println();
131 }
132
133 /**
134 * Forwards the <code>flush()</code> to the <code>PrintWriter</code>.
135 */
136
137 public void flush() throws IOException
138 {
139 _out.flush();
140 }
141
142 public String getAsciiBegin()
143 {
144 return asciiBegin;
145 }
146
147 public String getAsciiEnd()
148 {
149 return asciiEnd;
150 }
151
152 public int getBytesPerLine()
153 {
154 return bytesPerLine;
155 }
156
157 public String getOffsetSeperator()
158 {
159 return offsetSeperator;
160 }
161
162 public boolean getShowAscii()
163 {
164 return showAscii;
165 }
166
167 public char getSubstituteChar()
168 {
169 return substituteChar;
170 }
171
172 public void setAsciiBegin(String value)
173 {
174 if (locked)
175 throw new IllegalStateException();
176
177 asciiBegin = value;
178 }
179
180 public void setAsciiEnd(String value)
181 {
182 if (locked)
183 throw new IllegalStateException();
184
185 asciiEnd = value;
186 }
187
188 public void setBytesPerLine(int value)
189 {
190 if (locked)
191 throw new IllegalStateException();
192
193 bytesPerLine = value;
194
195 ascii = null;
196 }
197
198 public void setOffsetSeperator(String value)
199 {
200 if (locked)
201 throw new IllegalStateException();
202
203 offsetSeperator = value;
204 }
205
206 public void setShowAscii(boolean value)
207 {
208 if (locked)
209 throw new IllegalStateException();
210
211 showAscii = value;
212 }
213
214 /**
215 * Sets the character used in the ASCII dump that substitutes for characters outside the range
216 * of 32..126.
217 */
218
219 public void setSubstituteChar(char value)
220 {
221 if (locked)
222 throw new IllegalStateException();
223
224 substituteChar = value;
225 }
226
227 public void write(int b) throws IOException
228 {
229 char letter;
230
231 if (showAscii && ascii == null)
232 ascii = new char[bytesPerLine];
233
234 // Prevent further customization after output starts being written.
235
236 locked = true;
237
238 if (lineCount == bytesPerLine)
239 {
240 if (showAscii)
241 {
242 _out.print(asciiBegin);
243 _out.print(ascii);
244 _out.print(asciiEnd);
245 }
246
247 _out.println();
248
249 bytesSinceSpace = 0;
250 lineCount = 0;
251 offset += bytesPerLine;
252 }
253
254 if (lineCount == 0 && _showOffset)
255 {
256 writeHex(offset, 4);
257 _out.print(offsetSeperator);
258 }
259
260 // After every <n> bytes, emit a space.
261
262 if (_spacingInterval > 0 && bytesSinceSpace == _spacingInterval)
263 {
264 _out.print(' ');
265 bytesSinceSpace = 0;
266 }
267
268 writeHex(b, 2);
269
270 if (showAscii)
271 {
272 if (b < 32 | b > 127)
273 letter = substituteChar;
274 else
275 letter = (char) b;
276
277 ascii[lineCount] = letter;
278 }
279
280 lineCount++;
281 bytesSinceSpace++;
282 }
283
284 private void writeHex(int value, int digits)
285 {
286 int i;
287 int nybble;
288
289 for (i = 0; i < digits; i++)
290 {
291 nybble = (value >> 4 * (digits - i - 1)) & 0x0f;
292
293 _out.print(HEX[nybble]);
294 }
295 }
296
297 public void setSpacingInterval(int spacingInterval)
298 {
299 this._spacingInterval = spacingInterval;
300 }
301
302 public boolean isShowOffset()
303 {
304 return _showOffset;
305 }
306
307 public void setShowOffset(boolean showOffset)
308 {
309 this._showOffset = showOffset;
310 }
311
312 public int getSpacingInterval()
313 {
314 return _spacingInterval;
315 }
316 }