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 }