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    }