mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 03:57:51 +02:00
229 lines
8.6 KiB
Java
229 lines
8.6 KiB
Java
// Copyright 2016 The Nomulus 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();
|
|
}
|
|
}
|
|
}
|