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
015package org.apache.tapestry.util.io;
016
017import java.io.IOException;
018import java.io.OutputStream;
019import java.io.PrintWriter;
020import 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
031public class BinaryDumpOutputStream extends OutputStream
032{
033    private PrintWriter out;
034
035    private boolean locked = false;
036
037    private boolean showOffset = true;
038
039    private int bytesPerLine = 16;
040
041    private int spacingInterval = 4;
042
043    private char substituteChar = '.';
044
045    private String offsetSeperator = ": ";
046
047    private int offset = 0;
048
049    private int lineCount = 0;
050
051    private int bytesSinceSpace = 0;
052
053    private char[] ascii = null;
054
055    private boolean showAscii = true;
056
057    private String asciiBegin = "  |";
058
059    private String asciiEnd = "|";
060
061    private static final char[] HEX =
062    { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
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}