mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 12:07:51 +02:00
The dark lord Gosling designed the Java package naming system so that ownership flows from the DNS system. Since we own the domain name registry.google, it seems only appropriate that we should use google.registry as our package name.
230 lines
8.6 KiB
Java
230 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();
|
|
}
|
|
}
|
|
}
|