001// Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 002// The Molly framework is freely distributable under the terms of an 003// MIT-style license. For details, see the molly pages web site at: 004// http://www.mollypages.org/. Use, modify, have fun ! 005 006package fc.io; 007 008import java.io.*; 009 010import fc.util.*; 011 012/** 013Writes supplied bytes in hex/ascii form. Useful for hex dumps and 014debugging. Each <tt>write(..)</tt> method call is independent of previous 015invokations and prints data seperately from previous lines. This stream 016can also optionally print in other bases instead of hex. 017<p> 018In addition to it's streamy goodness, this class also provided some misc. 019static utility functions such as {@link #toHex} 020 021@author hursh jain 022**/ 023public final class HexOutputStream extends FilterOutputStream 024{ 025static final boolean dbg = false; 026 027final PrintStream out; 028final String sp1 = " "; 029final String sp2 = " "; 030final String sp3 = " "; 031final String nl = IOUtil.LINE_SEP; 032 033boolean showHex = true; 034int baseValue; 035 036//max digits needed to print 0..255 for any given base 037int baseMaxWidth = 2; //default for hex 038 039boolean autoflush = true; 040boolean linenumbers = true; 041int width; 042byte[] tempBuf; //to collect write(int) calls 043int tempBufPtr; 044int tempBufLimit; 045 046/** 047Constructs a new HexOutputStream with a default width of 04816 hex numbers (and corresponding ascii values) per line. 049 050@param out the underlying outputstream to send the data to 051**/ 052public HexOutputStream(OutputStream out) 053 { 054 this (out, 16); 055 } 056 057/** 058Constructs a new HexOutputStream with the specified column width. 059 060@param out the underlying outputstream to send the data to 061@param width the number of hex numbers to print per line 062**/ 063public HexOutputStream(OutputStream out, int width) 064 { 065 super(null); 066 067 Argcheck.notnull(out); 068 Argcheck.istrue(width > 0, "the specified width must be greater than 0"); 069 070 if (out instanceof PrintStream) 071 this.out = (PrintStream) out; 072 else 073 this.out = new PrintStream(out); 074 075 this.width = width; 076 tempBuf = new byte[width]; 077 tempBufLimit = width - 1; 078 if (dbg) System.out.println("Constructed HexOutputStream, out="+out); 079 } 080 081/** 082Be careful with this call, if the underlying stream 083is say, <tt>System.out</tt>, then calling this method will 084close <tt>System.out</tt> 085**/ 086public void close() { 087 out.close(); 088 } 089 090public void flush() { 091 out.flush(); 092 } 093 094public void write(int b) 095 { 096 tempBuf[tempBufPtr++] = (byte) b; 097 098 if (tempBufPtr == tempBufLimit) { 099 write(tempBuf, 0, tempBufLimit); 100 tempBufPtr = 0; 101 } 102 } 103 104public void write(byte[] buf) 105 { 106 if (buf == null) { 107 out.println("null"); 108 return; 109 } 110 write(buf, 0, buf.length); 111 } 112 113public void write(byte[] buf, int off, int len) 114 { 115 if (buf == null) { 116 out.println("null"); 117 return; 118 } 119 120 //faster if we use a buffer as opposed to multiple println() 121 //calls 122 StringBuffer strbuf = new StringBuffer(buf.length * 3); 123 124 int totalLen = len - off ; 125 126 //the absolute index into the buffer of the last character 127 int end = off + len; 128 129 int linesToPrint = totalLen / width; 130 int lineNumPrintWidth = Integer.toString(totalLen).length(); 131 int byteCount = 0; 132 133 if (dbg) System.out.println("width="+width+"; totallen="+totalLen+"; end="+end+"; linesToPrint="+linesToPrint+"; lineNumPrintWidth="+lineNumPrintWidth); 134 135 for (int n = off; n < totalLen; n += width) 136 { 137 if (linenumbers && linesToPrint > 0) { 138 strbuf.append(StringUtil.fixedWidth( 139 String.valueOf(byteCount), 140 lineNumPrintWidth, HAlign.RIGHT, '0')); 141 byteCount += width; 142 } 143 144 strbuf.append("| "); 145 // k cannot exceed lesser of [width (like 16) or the 146 // number of actual characters left in this buffer] 147 int k, c = 0; 148 149 int kmax = n + width; 150 //k is an absolute index into buf 151 for (k = n; k < kmax && k < end; k++) 152 { 153 c = buf[k] & 0xFF; //byte value is now unsigned 154 155 if (showHex) 156 { 157 /** 158 //This works but let's see if we can use a 159 //faster custom impl. 160 if (c < 16) { 161 strbuf.append('0'); //we want 0A not A 162 } 163 strbuf.append(Integer.toHexString(c)); 164 **/ 165 //->the custom impl. 166 strbuf.append(toHex(c)); 167 } 168 else { 169 strbuf.append(StringUtil.fixedWidth( 170 Integer.toString(c, baseValue), 171 baseMaxWidth, HAlign.RIGHT, '0')); 172 } 173 174 strbuf.append(sp1); 175 } 176 177 int pad = 0; 178 if (k == end) { //loop finished before width, pad to align 179 pad = n + width - k; 180 181 // add 1 to baseMaxWidth to account for space between 182 // each digit group 183 184 strbuf.append(StringUtil.repeat(' ', 185 (baseMaxWidth+1) * pad)); 186 } 187 188 strbuf.append("| "); 189 190 //we can repeat the loop, but timewise it seems to be 191 //about the same as creating 2 string buf's and 192 //populating them within 1 loop. 2 loop is slower 193 //but 2 bufs is also slow, when appending buf2 to buf1 194 //an arraycopy is done internally inside of stringbuffer 195 196 for (k = n; k < kmax && k < end; k++) 197 { 198 c = buf[k] & 0xFF; //byte value is now unsigned 199 if (c < 32 || c >= 127) 200 strbuf.append('.'); 201 else 202 strbuf.append((char)c); 203 } 204 205 if (k == end) { //loop finished before width, pad to align 206 strbuf.append(StringUtil.repeat(" ", pad)); 207 } 208 209 strbuf.append(" |"); 210 strbuf.append(nl); 211 } 212 213 out.print(strbuf.toString()); 214 if (autoflush) { 215 out.flush(); 216 } 217 } 218 219 220/** 221Sets this stream to flush it's contents after every 222write statement. By default, this is <tt>true</tt>. 223**/ 224public void setAutoFlush(boolean val) { 225 autoflush = val; 226 } 227 228/** 229<tt>true/false</tt> enables or disables showing line numbers at 230the beginning of each line, <i>if</i> there is more than 1 line in 231the output. The line number prefix is a running counter of the 232number of bytes displayed so far. This counter is reset after at 233the beginning of each write method call. 234**/ 235public void showLineNumbers(boolean val) { 236 linenumbers = val; 237 } 238 239/** 240Shows output in the specified base (instead of the default hex). 241To redundantly set to hex, specify <tt>16</tt>. To show decimal, 242specify <tt>10</tt>. 243 244@param base the base to show each byte in. Must be between 245 2 and 36 (inclusive) 246@throws IllegalArgumentException 247 if the specified base is not between [2, 36] 248**/ 249public void setBase(int base) 250 { 251 if (base < 2 || base > 36) 252 throw new IllegalArgumentException("The base must be between 2 and 36 (both inclusive)"); 253 baseValue = base; 254 baseMaxWidth = calcBaseMaxWidth(base); 255 if (baseValue != 16) { 256 showHex = false; 257 } 258 } 259 260 261// essentially we need to ask the question: 262// is the number greater than range [0 - base] ? if yes: 263// then is the number greater than range [0 - base * base] ? 264// and so on 265int calcBaseMaxWidth(int base) { 266 int n = 0; 267 int start = 255; 268 do { 269 start = start / base; 270 n++; 271 } 272 while ( start > 0); 273 return n; 274 } 275 276 277//Fast hex conversion 278 279static final char[] hex = "0123456789abcdef".toCharArray(); 280 281static final char[] toHex(final int c) //the lower 8 bits of c are hexed 282 { 283 final char[] buf = new char[2]; 284 if (c < 16) { 285 buf[0] = '0'; //we want 0A not A 286 } 287 else { 288 buf[0] = hex[ (c >> 4) & 0x0F ]; 289 } 290 buf[1] = hex[ c & 0x0f ]; 291 return buf; 292 } 293 294/** 295Utility method to convert a byte buffer to a hexadecimal string. 296useful for md5 and other hashes. 297*/ 298public static final String toHex(final byte[] buf) 299 { 300 Argcheck.notnull(buf, "buf was null"); 301 final StringBuilder tmp = new StringBuilder(); 302 for (int n = 0; n < buf.length; n++) 303 { 304 int c = buf[n] & 0xFF; //byte value is now unsigned 305 tmp.append(toHex(c)); 306 } 307 return tmp.toString(); 308 } 309 310/** 311Unit Test: 312<pre> 313Usage: java HexOutputStream <options> 314options: 315 -file filename 316 or 317 -data string 318</pre> 319**/ 320public static void main(String[] args) throws Exception 321 { 322 int cols = -1; 323 HexOutputStream hexout = null; 324 Args myargs = new Args(args); 325 myargs.setUsage("Usage: java HexOutputStream <options>\noptions:\n\t-file filename or -data string\r\n\t-cols num (optional number of display columns)\n\t-base num (optional base 2..36 to show bytes in)"); 326 327 if (myargs.getFlagCount() == 0) 328 myargs.showError(); 329 330 byte[] buf = null; 331 332 if (myargs.flagExists("file")) { 333 String filename = myargs.get("file"); 334 buf = IOUtil.fileToByteArray(filename); 335 } 336 else { 337 String temp = myargs.getRequired("data"); 338 if (temp != null) 339 buf = temp.getBytes(); 340 } 341 342 if (myargs.flagExists("cols")) 343 cols = Integer.parseInt(myargs.get("cols")); 344 345 if (cols >= 0) 346 hexout= new HexOutputStream(System.out, cols); 347 else 348 hexout = new HexOutputStream(System.out); 349 350 if (myargs.flagExists("base")) 351 hexout.setBase(Byte.parseByte(myargs.get("base"))); 352 353 long start = -1; 354 if (dbg) start = System.currentTimeMillis(); 355 hexout.write(buf); 356 if (dbg) System.out.println("Time: " + (System.currentTimeMillis() - start) + " ms"); 357 hexout.close(); 358 } 359 360} //~class HexOutputStream