google-nomulus/java/google/registry/util/HexDumper.java
mcilwain aa2f283f7c Convert entire project to strict lexicographical import sort ordering
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=127234970
2016-07-13 15:59:53 -04:00

229 lines
8.6 KiB
Java

// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import javax.annotation.Nonnegative;
import javax.annotation.WillNotClose;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Hex Dump Utility.
*
* <p>This class takes binary data and prints it out in a way that humans can read. It's just like
* the hex dump programs you remembered as a kid. There a column on the left for the address (or
* offset) in decimal, the middle columns are each digit in hexadecimal, and the column on the
* right is the ASCII representation where non-printable characters are represented by {@code '.'}.
*
* <p>It's easiest to generate a simple {@link String} by calling {@link #dumpHex(byte[])}, or you
* can stream data with {@link #HexDumper(Writer, int, int)}.
*
* <p>Example output:
* <pre> {@code
* [222 bytes total]
* 00000000 90 0d 03 00 08 03 35 58 61 46 fd f3 f3 73 01 88 ......5XaF...s..
* 00000016 cd 04 00 03 08 00 37 05 02 52 09 3f 8d 30 1c 45 ......7..R.?.0.E
* 00000032 72 69 63 20 45 63 68 69 64 6e 61 20 28 74 65 73 ric Echidna (tes
* 00000048 74 20 6b 65 79 29 20 3c 65 72 69 63 40 62 6f 75 t key) <eric@bou
* 00000064 6e 63 79 63 61 73 74 6c 65 2e 6f 72 67 3e 00 0a ncycastle.org>..
* 00000080 09 10 35 58 61 46 fd f3 f3 73 4b 5b 03 fe 2e 53 ..5XaF...sK[...S
* 00000096 04 28 ab cb 35 3b e2 1b 63 91 65 3a 86 b9 fb 47 .(..5;..c.e:...G
* 00000112 d5 4c 6a 21 50 f5 2e 39 76 aa d5 86 d7 96 3b 9a .Lj!P..9v.....;.
* 00000128 1a c3 6d c0 50 7f c6 25 9a 04 de 0f 1f 20 ae 70 ..m.P..%..... .p
* 00000144 f9 77 c4 8b bf ec 3c 2f 59 58 b8 47 81 6a 59 25 .w....</YX.G.jY%
* 00000160 82 b0 ba e2 a9 43 94 aa fc 92 2b b3 76 77 f5 ba .....C....+.vw..
* 00000176 5b 59 9a de 22 1c 79 06 88 d2 ba 97 51 e3 11 2e [Y..".y.....Q...
* 00000192 5b c0 c6 8c 34 4d a7 28 77 bf 11 27 e7 6c 8e 1c [...4M.(w..'.l..
* 00000208 b4 a6 66 18 8e 69 3c 18 b7 97 d5 34 9a bb ..f..i<....4..
* }</pre>
*/
@NotThreadSafe
public final class HexDumper extends OutputStream {
@Nonnegative
public static final int DEFAULT_PER_LINE = 16;
@Nonnegative
public static final int DEFAULT_PER_GROUP = 4;
private Writer upstream;
@Nonnegative
private final int perLine;
@Nonnegative
private final int perGroup;
private long totalBytes;
@Nonnegative
private int lineCount;
private StringBuilder line;
private final char[] asciis;
/**
* Calls {@link #dumpHex(byte[], int, int)} with {@code perLine} set to
* {@value #DEFAULT_PER_LINE} and {@code perGroup} set to {@value #DEFAULT_PER_GROUP}.
*/
public static String dumpHex(byte[] data) {
return dumpHex(data, DEFAULT_PER_LINE, DEFAULT_PER_GROUP);
}
/**
* Convenience static method for generating a hex dump as a {@link String}.
*
* <p>This method adds an additional line to the beginning with the total number of bytes.
*
* @see #HexDumper(Writer, int, int)
*/
public static String dumpHex(byte[] data, @Nonnegative int perLine, @Nonnegative int perGroup) {
checkNotNull(data, "data");
StringWriter writer = new StringWriter();
writer.write(String.format("[%d bytes total]\n", data.length));
try (HexDumper hexDump = new HexDumper(writer, perLine, perGroup)) {
hexDump.write(data);
} catch (IOException e) {
throw new RuntimeException(e);
}
return writer.toString();
}
/**
* Calls {@link #HexDumper(Writer, int, int)} with {@code perLine} set to
* {@value #DEFAULT_PER_LINE} and {@code perGroup} set to {@value #DEFAULT_PER_GROUP}.
*/
public HexDumper(@WillNotClose Writer writer) {
this(writer, DEFAULT_PER_LINE, DEFAULT_PER_GROUP);
}
/**
* Construct a new streaming {@link HexDumper} object.
*
* <p>The output is line-buffered so a single write call is made to {@code out} for each line.
* This is done to avoid system call overhead {@code out} is a resource, and reduces the chance
* of lines being broken by other threads writing to {@code out} (or its underlying resource) at
* the same time.
*
* <p>This object will <i>not</i> close {@code out}. You must close <i>both</i> this object and
* {@code out}, and this object must be closed <i>first</i>.
*
* @param out is the stream to which the hex dump text is written. It is <i>not</i> closed.
* @param perLine determines how many hex characters to show on each line.
* @param perGroup how many columns of hex digits should be grouped together. If this value is
* {@code > 0}, an extra space will be inserted after each Nth column for readability.
* Grouping can be disabled by setting this to {@code 0}.
* @see #dumpHex(byte[])
*/
public HexDumper(@WillNotClose Writer out, @Nonnegative int perLine, @Nonnegative int perGroup) {
checkArgument(0 < perLine, "0 < perLine <= INT32_MAX");
checkArgument(0 <= perGroup && perGroup < perLine, "0 <= perGroup < perLine");
this.upstream = checkNotNull(out, "out");
this.totalBytes = 0L;
this.perLine = perLine;
this.perGroup = perGroup;
this.asciis = new char[perLine];
this.line = newLine();
}
/** Initializes member variables at the beginning of a new line. */
private StringBuilder newLine() {
lineCount = 0;
return new StringBuilder(String.format("%08d ", totalBytes));
}
/**
* Writes a single byte to the current line buffer, flushing if end of line.
*
* @throws IOException upon failure to write to upstream {@link Writer#write(String) writer}.
* @throws IllegalStateException if this object has been {@link #close() closed}.
*/
@Override
public void write(int b) throws IOException {
String flush = null;
line.append(String.format("%02x ", (byte) b));
asciis[lineCount] = b >= 32 && b <= 126 ? (char) b : '.';
++lineCount;
++totalBytes;
if (lineCount == perLine) {
line.append(' ');
line.append(asciis);
line.append('\n');
flush = line.toString();
line = newLine();
} else {
if (perGroup > 0 && lineCount % perGroup == 0) {
line.append(' ');
}
}
// Writing upstream is deferred until the end in order to *somewhat* maintain a correct
// internal state in the event that an exception is thrown. This also avoids the need for
// a try statement which usually makes code run slower.
if (flush != null) {
upstream.write(flush);
}
}
/**
* Writes partial line buffer (if any) to upstream {@link Writer}.
*
* @throws IOException upon failure to write to upstream {@link Writer#write(String) writer}.
* @throws IllegalStateException if this object has been {@link #close() closed}.
*/
@Override
public void flush() throws IOException {
if (line.length() > 0) {
upstream.write(line.toString());
line = new StringBuilder();
}
}
/**
* Writes out the final line (if incomplete) and invalidates this object.
*
* <p>This object must be closed <i>before</i> you close the upstream writer. Please note that
* this method <i>does not</i> close upstream writer for you.
*
* <p>If you attempt to write to this object after calling this method,
* {@link IllegalStateException} will be thrown. However, it's safe to call close multiple times,
* as subsequent calls will be treated as a no-op.
*
* @throws IOException upon failure to write to upstream {@link Writer#write(String) writer}.
*/
@Override
public void close() throws IOException {
if (lineCount > 0) {
while (lineCount < perLine) {
asciis[lineCount] = ' ';
line.append(" ");
++lineCount;
if (perGroup > 0 && lineCount % perGroup == 0 && lineCount != perLine) {
line.append(' ');
}
}
line.append(' ');
line.append(asciis);
line.append('\n');
flush();
}
}
}