private static void appendHexDumpLine(final HexDumpBuilder hexDumpBuilder,
                                          final int lineOffset,
                                          final byte[] lineBytes,
                                          final int len,
                                          final int bytesPerLine,
                                          final CharsetDecoder charsetDecoder) {
        final long firstByteNo = ((long) lineOffset) * bytesPerLine;


        // Builder for the hex values for each byte
        final StringBuilder hexStringBuilder = new StringBuilder();
        // Builder for the decoded forms of each individual byte
        final StringBuilder decodedStringBuilder = new StringBuilder();

        final byte[] singleByteArr = new byte[1];
        final ByteBuffer singleByteBuffer = ByteBuffer.wrap(singleByteArr);

        for (int i = 0; i < lineBytes.length; i++) {
            if (i < len) {
                singleByteBuffer.clear();
                singleByteArr[0] = lineBytes[i];

                final String hex = Hex.encodeHexString(singleByteArr, false);
                hexStringBuilder
                        .append(hex)
                        .append(" ");

                // Try to decode the byte as ascii for the right-hand column
                char chr;
                try {
                    final CharBuffer charBuffer = charsetDecoder.decode(singleByteBuffer);
                    chr = charBuffer.charAt(0);
                } catch (CharacterCodingException e) {
                    chr = DEFAULT_REPLACEMENT_CHAR;
                }
                final char printableChar = asPrintableChar(chr);
                LOGGER.trace("hex: {}, printableChar {}", hex, printableChar);
                decodedStringBuilder.append(printableChar);
            } else {
                // We have gone past the last byte but still need to pad it out with spaces
                // so the decoded text is in the right place. Three spaces because two where
                // the hex pair would be then one for the gap between hex pairs
                hexStringBuilder
                        .append("   ");
                decodedStringBuilder.append(" ");
            }

            // Add an extra space after each 4 byte block
            if (i != 0
                    && (i + 1) % BYTES_PER_BLOCK == 0) {
                hexStringBuilder.append(" ");
            }
        }
        // Add the number of the first byte on the line in hex form, zero padded
        final String paddedLineNo = Strings.padStart(
                Long.toHexString(firstByteNo).toUpperCase(),
                PADDED_LINE_NO_WIDTH,
                '0');
        final String lineStr = paddedLineNo
                + SPACER_AFTER_PADDED_LINE_NO
                + hexStringBuilder
                + decodedStringBuilder;

        final HexDumpLine hexDumpLine = new HexDumpLine(
                lineStr,
                lineOffset + 1,
                Range.of(firstByteNo, firstByteNo + len));
        hexDumpBuilder.addLine(hexDumpLine);
    }
Produces a hex dump similar to the linux xxd binary.

@param inputStream     The stream to dump as hex
@param charset         The charset to decode single bytes with. Even if a multibyte charset
                       is supplied bytes will only be decoded individually.
@param maxHexDumpLines The max number of line of hex dump output. A full line contains 32 bytes.
@return
@throws IOException
stroom-util/src/main/java/stroom/util/string/HexDumpUtil.java