Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8251989: Hex formatting and parsing utility #482

Closed
wants to merge 33 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c1bb451
8251989: Hex formatting and parsing utility
RogerRiggs Sep 2, 2020
3170b49
Various code review comments, rename UpperCase and LowerCase methods …
RogerRiggs Oct 13, 2020
e7edf07
temp updates
RogerRiggs Oct 14, 2020
1892eca
Merge branch 'master' into 8251989-hex-formatter
RogerRiggs Oct 14, 2020
ec334db
Added assertions to testVariableLength and samples
RogerRiggs Oct 14, 2020
59bdb07
Cleanup of javadoc markup
RogerRiggs Oct 15, 2020
880cebb
Merge branch 'master' into 8251989-hex-formatter
RogerRiggs Oct 15, 2020
bad80cb
Expanded test coverage and fixed related bugs; Added static imports f…
RogerRiggs Oct 16, 2020
450bfaf
Test enhancements from Chris Hegarty
RogerRiggs Oct 16, 2020
d9f2b6e
Merge branch 'master' into 8251989-hex-formatter
RogerRiggs Oct 16, 2020
1d2c26f
Correct length of StringBuilder in formatHex;
RogerRiggs Oct 19, 2020
0a47544
Review comment updates, in the example code, and to describe the char…
RogerRiggs Oct 21, 2020
2b493d3
Review comment updates to class javadoc
RogerRiggs Oct 22, 2020
94e6061
- Added @see and @link references to Integer.toHexString and Long.toH…
RogerRiggs Oct 26, 2020
2aeab7d
The HexFormat API indexing model for array and string ranges is changed
RogerRiggs Oct 27, 2020
80a7118
Merge branch 'master' into 8251989-hex-formatter
RogerRiggs Oct 27, 2020
552d308
Merge branch 'master' into 8251989-hex-formatter
RogerRiggs Nov 2, 2020
1d36cfb
Merge branch 'master' into 8251989-hex-formatter
RogerRiggs Nov 24, 2020
b19d282
Clarified that suffix() and prefix() methods do not return null, inst…
RogerRiggs Nov 25, 2020
cad6a70
Addressed review comments on use of formatted hexadecimal strings, up…
RogerRiggs Nov 27, 2020
29f9bf7
Merge branch 'master' into 8251989-hex-formatter
RogerRiggs Nov 27, 2020
a1ce9d7
Clarified hexadecimal characters used in converting from characters t…
RogerRiggs Nov 30, 2020
bbcb213
Review comment updates:
RogerRiggs Dec 1, 2020
892d08e
Increased memory to 4G for the test and add diagnostic info for OOME
RogerRiggs Dec 1, 2020
a137740
Clarify that the fromHexDigits does not use the uppercase parameter.
RogerRiggs Dec 2, 2020
848c7a6
Clarify that the fromHexDigit method does not use the prefix, suffix,…
RogerRiggs Dec 2, 2020
0a8088b
Add class level clarification of use of uppercase for primitive conve…
RogerRiggs Dec 2, 2020
9ddda60
Merge branch 'master' into 8251989-hex-formatter
RogerRiggs Dec 4, 2020
af7b1fa
Added @apiNote to fromHexDigits methods to link and compare to simila…
RogerRiggs Dec 5, 2020
044d463
Clarified parsing of hex as unsigned in fromHexDigits methods.
RogerRiggs Dec 9, 2020
8a6d379
Merge branch 'master' into 8251989-hex-formatter
RogerRiggs Dec 9, 2020
b9e288f
Merge branch 'master' into 8251989-hex-formatter
RogerRiggs Dec 15, 2020
4866a0a
Correct typos in fromHexDigits methods javadoc
RogerRiggs Dec 15, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/java.base/share/classes/java/lang/Integer.java
Original file line number Diff line number Diff line change
@@ -259,9 +259,9 @@ public static String toUnsignedString(int i, int radix) {
* </blockquote>
* <p>
Copy link

@Marcono1234 Marcono1234 Oct 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Marcono1234, thanks for making a comment in an OpenJDK project!

All comments and discussions in the OpenJDK Community must be made available under the OpenJDK Terms of Use. If you already are an OpenJDK Author, Committer or Reviewer, please click here to open a new issue so that we can record that fact. Please Use "Add GitHub user Marcono1234 for the summary.

If you are not an OpenJDK Author, Committer or Reviewer, simply check the box below to accept the OpenJDK Terms of Use for your comments.

Your comment will be automatically restored once you have accepted the OpenJDK Terms of Use.

Sorry, something went wrong.

* @apiNote
* {@link java.util.HexFormat} provides formatting and parsing
* The {@link java.util.HexFormat} class provides formatting and parsing
* of byte arrays and primitives to return a string or adding to an {@link Appendable}.
* HexFormat formats and parses uppercase or lowercase hexadecimal characters,
* {@code HexFormat} formats and parses uppercase or lowercase hexadecimal characters,
* with leading zeros and for byte arrays includes for each byte
* a delimiter, prefix, and suffix.
*
4 changes: 2 additions & 2 deletions src/java.base/share/classes/java/lang/Long.java
Original file line number Diff line number Diff line change
@@ -294,9 +294,9 @@ private static BigInteger toUnsignedBigInteger(long i) {
* </blockquote>
* <p>
Copy link

@Marcono1234 Marcono1234 Oct 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Marcono1234, thanks for making a comment in an OpenJDK project!

All comments and discussions in the OpenJDK Community must be made available under the OpenJDK Terms of Use. If you already are an OpenJDK Author, Committer or Reviewer, please click here to open a new issue so that we can record that fact. Please Use "Add GitHub user Marcono1234 for the summary.

If you are not an OpenJDK Author, Committer or Reviewer, simply check the box below to accept the OpenJDK Terms of Use for your comments.

Your comment will be automatically restored once you have accepted the OpenJDK Terms of Use.

Sorry, something went wrong.

* @apiNote
* {@link java.util.HexFormat} provides formatting and parsing
* The {@link java.util.HexFormat} class provides formatting and parsing
* of byte arrays and primitives to return a string or adding to an {@link Appendable}.
* HexFormat formats and parses uppercase or lowercase hexadecimal characters,
* {@code HexFormat} formats and parses uppercase or lowercase hexadecimal characters,
* with leading zeros and for byte arrays includes for each byte
* a delimiter, prefix, and suffix.
*
62 changes: 39 additions & 23 deletions src/java.base/share/classes/java/util/HexFormat.java
Original file line number Diff line number Diff line change
@@ -35,8 +35,8 @@
import java.nio.charset.StandardCharsets;

/**
* Converts between bytes and chars and hex-encoded strings which may include additional
* formatting markup such as prefixes, suffixes, and delimiters.
* {@code HexFormat} converts between bytes and chars and hex-encoded strings which may include
* additional formatting markup such as prefixes, suffixes, and delimiters.
* <p>
* There are two factories of {@code HexFormat} with preset parameters {@link #of()} and
* {@link #ofDelimiter(String) ofDelimiter(delimiter)}. For other parameter combinations
@@ -72,7 +72,7 @@
* For formatted hexadecimal string to byte array conversions the
* {@code parseHex} methods include {@link #parseHex(CharSequence) parseHex(CharSequence)} and
* {@link #parseHex(char[], int, int) parseHex(char[], offset, length)}.
* Each byte value is parsed as the prefix, two case insensitive hexadecimal characters,
* Each byte value is parsed from the prefix, two case insensitive hexadecimal characters,
* and the suffix. A delimiter follows each formatted value, except the last.
*
* @apiNote
@@ -168,7 +168,8 @@ public final class HexFormat {
private static final HexFormat HEX_FORMAT =
new HexFormat("", "", "", LOWERCASE_DIGITS);

private static final byte[] emptyBytes = new byte[0];
private static final byte[] EMPTY_BYTES = new byte[0];

private final String delimiter;
private final String prefix;
private final String suffix;
@@ -343,8 +344,9 @@ public String formatHex(byte[] bytes, int fromIndex, int toIndex) {
// Format efficiently if possible
String s = formatOptDelimiter(bytes, fromIndex, toIndex);
if (s == null) {
StringBuilder sb = new StringBuilder((toIndex - fromIndex) *
(delimiter.length() + prefix.length() + 2 + suffix.length()) - delimiter.length());
long stride = prefix.length() + 2L + suffix.length() + delimiter.length();
int capacity = checkMaxArraySize((toIndex - fromIndex) * stride - delimiter.length());
StringBuilder sb = new StringBuilder(capacity);
formatHex(sb, bytes, fromIndex, toIndex);
s = sb.toString();
}
@@ -358,8 +360,8 @@ public String formatHex(byte[] bytes, int fromIndex, int toIndex) {
* A delimiter follows each formatted value, except the last.
* The formatted hexadecimal strings are appended in zero or more calls to the {@link Appendable} methods.
*
* @param <A> The type of Appendable
* @param out an Appendable, non-null
* @param <A> The type of {@code Appendable}
* @param out an {@code Appendable}, non-null
* @param bytes a byte array
* @return the {@code Appendable}
* @throws UncheckedIOException if an I/O exception occurs appending to the output
@@ -375,8 +377,8 @@ public <A extends Appendable> A formatHex(A out, byte[] bytes) {
* A delimiter follows each formatted value, except the last.
* The formatted hexadecimal strings are appended in zero or more calls to the {@link Appendable} methods.
*
* @param <A> The type of Appendable
* @param out an Appendable, non-null
* @param <A> The type of {@code Appendable}
* @param out an {@code Appendable}, non-null
* @param bytes a byte array, non-null
* @param fromIndex the initial index of the range, inclusive
* @param toIndex the final index of the range, exclusive.
@@ -433,7 +435,7 @@ private String formatOptDelimiter(byte[] bytes, int fromIndex, int toIndex) {
int length = toIndex - fromIndex;
if (delimiter.isEmpty()) {
// Allocate the byte array and fill in the hex pairs for each byte
rep = new byte[length * 2];
rep = new byte[checkMaxArraySize(length * 2L)];
for (int i = 0; i < length; i++) {
rep[i * 2] = (byte)toHighHexDigit(bytes[fromIndex + i]);
rep[i * 2 + 1] = (byte)toLowHexDigit(bytes[fromIndex + i]);
@@ -442,7 +444,7 @@ private String formatOptDelimiter(byte[] bytes, int fromIndex, int toIndex) {
// Allocate the byte array and fill in the characters for the first byte
// Then insert the delimiter and hexadecimal characters for each of the remaining bytes
char sep = delimiter.charAt(0);
rep = new byte[length * 3 - 1];
rep = new byte[checkMaxArraySize(length * 3L - 1L)];
rep[0] = (byte) toHighHexDigit(bytes[fromIndex]);
rep[1] = (byte) toLowHexDigit(bytes[fromIndex]);
for (int i = 1; i < length; i++) {
@@ -462,10 +464,24 @@ private String formatOptDelimiter(byte[] bytes, int fromIndex, int toIndex) {
}
}

/**
* Checked that the requested size for the result string is less than the max array size.
*
* @param length the requested size of a byte array.
* @return the length
* @throws OutOfMemoryError if the size is larger than Integer.MAX_VALUE
*/
private static int checkMaxArraySize(long length) {
if (length > Integer.MAX_VALUE)
throw new OutOfMemoryError("String size " + length +
" exceeds maximum " + (Integer.MAX_VALUE));
return (int)length;
}

/**
* Returns a byte array containing hexadecimal values parsed from the string.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string -> charSequence. And flow through at param tag and param name.

*
* Each byte value is parsed as the prefix, two case insensitive hexadecimal characters,
* Each byte value is parsed from the prefix, two case insensitive hexadecimal characters,
* and the suffix. A delimiter follows each formatted value, except the last.
* The delimiters, prefixes, and suffixes strings must be present; they may be empty strings.
* A valid string consists only of the above format.
@@ -484,7 +500,7 @@ public byte[] parseHex(CharSequence string) {
/**
* Returns a byte array containing hexadecimal values parsed from a range of the string.
*
* Each byte value is parsed as the prefix, two case insensitive hexadecimal characters,
* Each byte value is parsed from the prefix, two case insensitive hexadecimal characters,
* and the suffix. A delimiter follows each formatted value, except the last.
* The delimiters, prefixes, and suffixes strings must be present; they may be empty strings.
* A valid string consists only of the above format.
@@ -508,20 +524,21 @@ public byte[] parseHex(CharSequence string, int fromIndex, int toIndex) {
}

if (string.length() == 0)
return emptyBytes;
return EMPTY_BYTES;
if (delimiter.isEmpty() && prefix.isEmpty() && suffix.isEmpty())
return parseNoDelimiter(string);

int valueChars = prefix.length() + 2 + suffix.length();
int stride = valueChars + delimiter.length();
// avoid overflow for max length prefix or suffix
long valueChars = prefix.length() + 2L + suffix.length();
long stride = valueChars + delimiter.length();
if (string.length() < valueChars || (string.length() - valueChars) % stride != 0)
throw new IllegalArgumentException("extra or missing delimiters " +
"or values consisting of prefix, two hexadecimal digits, and suffix");

checkLiteral(string, 0, prefix);
checkLiteral(string, string.length() - suffix.length(), suffix);
String between = suffix + delimiter + prefix;
final int len = (string.length() - valueChars) / stride + 1;
final int len = (int)((string.length() - valueChars) / stride + 1L);
byte[] bytes = new byte[len];
int i, offset;
for (i = 0, offset = prefix.length(); i < len - 1; i++, offset += 2 + between.length()) {
@@ -543,7 +560,7 @@ public byte[] parseHex(CharSequence string, int fromIndex, int toIndex) {
* Returns a byte array containing hexadecimal values parsed from
* a range of the character array.
*
* Each byte value is parsed as the prefix, two case insensitive hexadecimal characters,
* Each byte value is parsed from the prefix, two case insensitive hexadecimal characters,
* and the suffix. A delimiter follows each formatted value, except the last.
* The delimiters, prefixes, and suffixes strings must be present; they may be empty strings.
* A valid character array range consists only of the above format.
@@ -656,8 +673,8 @@ public String toHexDigits(byte value) {
* The hexadecimal characters are appended in one or more calls to the
* {@link Appendable} methods.
*
* @param <A> The type of Appendable
* @param out an Appendable, non-null
* @param <A> The type of {@code Appendable}
* @param out an {@code Appendable}, non-null
* @param value a byte value
* @return the {@code Appendable}
* @throws UncheckedIOException if an I/O exception occurs appending to the output
@@ -737,8 +754,7 @@ public String toHexDigits(int value) {
}

/**
* Returns the sixteen hexadecimal characters for the {@code long} value
* considering it to be unsigned.
* Returns the sixteen hexadecimal characters for the {@code long} value.
* Each nibble (4 bits) from most significant to least significant of the value
* is formatted as if by {@link #toLowHexDigit(int) toLowHexDigit(nibble)}.
* The delimiter, prefix and suffix are not used.
20 changes: 19 additions & 1 deletion test/jdk/java/util/HexFormat/HexFormatTest.java
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@
/*
* @test
* @summary Check HexFormat formatting and parsing
* @run testng HexFormatTest
* @run testng/othervm HexFormatTest
*/

@Test
@@ -622,6 +622,24 @@ static void testIOException(String delimiter, String prefix, String suffix, bool
() -> hex.toHexDigits(throwingAppendable, (byte)1));
}

@Test(dataProvider="HexFormattersParsers")
static void testOOME(String delimiter, String prefix, String suffix, boolean uppercase,
HexFormat hex) {
// compute the size of byte array that will exceed the buffer
long valueChars = prefix.length() + 2 + suffix.length();
long stride = valueChars + delimiter.length();
long max = Integer.MAX_VALUE & 0xFFFFFFFFL;
long len = max / stride;
long remainder = max - ((len - 1) * stride);
if (remainder > valueChars) {
len++;
}
byte[] bytes = new byte[(int)len];
Throwable ex = expectThrows(OutOfMemoryError.class,
() -> hex.formatHex(bytes));
System.out.println("ex: " + ex);
}

/**
* Example code from the HexFormat javadoc.
* Showing simple usage of the API using "assert" to express the correct results