/* |
|
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. |
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
* |
|
* This code is free software; you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License version 2 only, as |
|
* published by the Free Software Foundation. Oracle designates this |
|
* particular file as subject to the "Classpath" exception as provided |
|
* by Oracle in the LICENSE file that accompanied this code. |
|
* |
|
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
* version 2 for more details (a copy is included in the LICENSE file that |
|
* accompanied this code). |
|
* |
|
* You should have received a copy of the GNU General Public License version |
|
* 2 along with this work; if not, write to the Free Software Foundation, |
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
* |
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
* or visit www.oracle.com if you need additional information or have any |
|
* questions. |
|
*/ |
|
package java.util; |
|
import jdk.internal.access.JavaLangAccess; |
|
import jdk.internal.access.SharedSecrets; |
|
import java.io.IOException; |
|
import java.io.UncheckedIOException; |
|
import java.nio.CharBuffer; |
|
import java.nio.charset.CharacterCodingException; |
|
import java.nio.charset.StandardCharsets; |
|
/** |
|
* {@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 |
|
* the {@code withXXX} methods return copies of {@code HexFormat} modified |
|
* {@link #withPrefix(String)}, {@link #withSuffix(String)}, {@link #withDelimiter(String)} |
|
* or choice of {@link #withUpperCase()} or {@link #withLowerCase()} parameters. |
|
* <p> |
|
* For primitive to hexadecimal string conversions the {@code toHexDigits} |
|
* methods include {@link #toHexDigits(byte)}, {@link #toHexDigits(int)}, and |
|
* {@link #toHexDigits(long)}, etc. The default is to use lowercase characters {@code "0-9","a-f"}. |
|
* For conversions producing uppercase hexadecimal the characters are {@code "0-9","A-F"}. |
|
* Only the {@link HexFormat#isUpperCase() HexFormat.isUpperCase()} parameter is |
|
* considered; the delimiter, prefix and suffix are not used. |
|
* |
|
* <p> |
|
* For hexadecimal string to primitive conversions the {@code fromHexDigits} |
|
* methods include {@link #fromHexDigits(CharSequence) fromHexDigits(string)}, |
|
* {@link #fromHexDigitsToLong(CharSequence) fromHexDigitsToLong(string)}, and |
|
* {@link #fromHexDigit(int) fromHexDigit(int)} converts a single character or codepoint. |
|
* For conversions from hexadecimal characters the digits and uppercase and lowercase |
|
* characters in {@code "0-9", "a-f", and "A-F"} are converted to corresponding values |
|
* {@code 0-15}. The delimiter, prefix, suffix, and uppercase parameters are not used. |
|
* |
|
* <p> |
|
* For byte array to formatted hexadecimal string conversions |
|
* the {@code formatHex} methods include {@link #formatHex(byte[]) formatHex(byte[])} |
|
* and {@link #formatHex(Appendable, byte[]) formatHex(Appendable, byte[])}. |
|
* The formatted output is a string or is appended to an {@link Appendable} such as |
|
* {@link StringBuilder} or {@link java.io.PrintStream}. |
|
* Each byte value is formatted as the prefix, two hexadecimal characters from the |
|
* uppercase or lowercase digits, and the suffix. |
|
* A delimiter follows each formatted value, except the last. |
|
* For conversions producing uppercase hexadecimal strings use {@link #withUpperCase()}. |
|
* |
|
* <p> |
|
* 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 from the prefix, two case insensitive hexadecimal characters, |
|
* and the suffix. A delimiter follows each formatted value, except the last. |
|
* |
|
* @apiNote |
|
* For example, an individual byte is converted to a string of hexadecimal digits using |
|
* {@link HexFormat#toHexDigits(int) toHexDigits(int)} and converted from a string to a |
|
* primitive value using {@link HexFormat#fromHexDigits(CharSequence) fromHexDigits(string)}. |
|
* <pre>{@code |
|
* HexFormat hex = HexFormat.of(); |
|
* byte b = 127; |
|
* String byteStr = hex.toHexDigits(b); |
|
* |
|
* byte byteVal = (byte)hex.fromHexDigits(byteStr); |
|
* assert(byteStr.equals("7f")); |
|
* assert(b == byteVal); |
|
* |
|
* // The hexadecimal digits are: "7f" |
|
* }</pre> |
|
* <p> |
|
* For a comma ({@code ", "}) separated format with a prefix ({@code "#"}) |
|
* using lowercase hex digits the {@code HexFormat} is: |
|
* <pre>{@code |
|
* HexFormat commaFormat = HexFormat.ofDelimiter(", ").withPrefix("#"); |
|
* byte[] bytes = {0, 1, 2, 3, 124, 125, 126, 127}; |
|
* String str = commaFormat.formatHex(bytes); |
|
* |
|
* byte[] parsed = commaFormat.parseHex(str); |
|
* assert(Arrays.equals(bytes, parsed)); |
|
* |
|
* // The formatted string is: "#00, #01, #02, #03, #7c, #7d, #7e, #7f" |
|
* }</pre> |
|
* <p> |
|
* For a fingerprint of byte values that uses the delimiter colon ({@code ":"}) |
|
* and uppercase characters the {@code HexFormat} is: |
|
* <pre>{@code |
|
* HexFormat formatFingerprint = HexFormat.ofDelimiter(":").withUpperCase(); |
|
* byte[] bytes = {0, 1, 2, 3, 124, 125, 126, 127}; |
|
* String str = formatFingerprint.formatHex(bytes); |
|
* byte[] parsed = formatFingerprint.parseHex(str); |
|
* assert(Arrays.equals(bytes, parsed)); |
|
* |
|
* // The formatted string is: "00:01:02:03:7C:7D:7E:7F" |
|
* }</pre> |
|
* |
|
* <p> |
|
* This is a <a href="{@docRoot}/java.base/java/lang/doc-files/ValueBased.html">value-based</a> |
|
* class; use of identity-sensitive operations (including reference equality |
|
* ({@code ==}), identity hash code, or synchronization) on instances of |
|
* {@code HexFormat} may have unpredictable results and should be avoided. |
|
* The {@code equals} method should be used for comparisons. |
|
* <p> |
|
* This class is immutable and thread-safe. |
|
* <p> |
|
* Unless otherwise noted, passing a null argument to any method will cause a |
|
* {@link java.lang.NullPointerException NullPointerException} to be thrown. |
|
* |
|
* @since 17 |
|
*/ |
|
public final class HexFormat { |
|
// Access to create strings from a byte array. |
|
private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); |
|
private static final byte[] UPPERCASE_DIGITS = { |
|
'0', '1', '2', '3', '4', '5', '6', '7', |
|
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F', |
|
}; |
|
private static final byte[] LOWERCASE_DIGITS = { |
|
'0', '1', '2', '3', '4', '5', '6', '7', |
|
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f', |
|
}; |
|
// Analysis has shown that generating the whole array allows the JIT to generate |
|
// better code compared to a slimmed down array, such as one cutting off after 'f' |
|
private static final byte[] DIGITS = { |
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, |
|
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
}; |
|
/** |
|
* Format each byte of an array as a pair of hexadecimal digits. |
|
* The hexadecimal characters are from lowercase alpha digits. |
|
*/ |
|
private static final HexFormat HEX_FORMAT = |
|
new HexFormat("", "", "", LOWERCASE_DIGITS); |
|
private static final byte[] EMPTY_BYTES = {}; |
|
private final String delimiter; |
|
private final String prefix; |
|
private final String suffix; |
|
private final byte[] digits; |
|
/** |
|
* Returns a HexFormat with a delimiter, prefix, suffix, and array of digits. |
|
* |
|
* @param delimiter a delimiter, non-null |
|
* @param prefix a prefix, non-null |
|
* @param suffix a suffix, non-null |
|
* @param digits byte array of digits indexed by low nibble, non-null |
|
* @throws NullPointerException if any argument is null |
|
*/ |
|
private HexFormat(String delimiter, String prefix, String suffix, byte[] digits) { |
|
this.delimiter = Objects.requireNonNull(delimiter, "delimiter"); |
|
this.prefix = Objects.requireNonNull(prefix, "prefix"); |
|
this.suffix = Objects.requireNonNull(suffix, "suffix"); |
|
this.digits = digits; |
|
} |
|
/** |
|
* Returns a hexadecimal formatter with no delimiter and lowercase characters. |
|
* The delimiter, prefix, and suffix are empty. |
|
* The methods {@link #withDelimiter(String) withDelimiter}, |
|
* {@link #withUpperCase() withUpperCase}, {@link #withLowerCase() withLowerCase}, |
|
* {@link #withPrefix(String) withPrefix}, and {@link #withSuffix(String) withSuffix} |
|
* return copies of formatters with new parameters. |
|
* |
|
* @return a hexadecimal formatter with no delimiter and lowercase characters |
|
*/ |
|
public static HexFormat of() { |
|
return HEX_FORMAT; |
|
} |
|
/** |
|
* Returns a hexadecimal formatter with the delimiter and lowercase characters. |
|
* The prefix and suffix are empty. |
|
* The methods {@link #withDelimiter(String) withDelimiter}, |
|
* {@link #withUpperCase() withUpperCase}, {@link #withLowerCase() withLowerCase}, |
|
* {@link #withPrefix(String) withPrefix}, and {@link #withSuffix(String) withSuffix} |
|
* return copies of formatters with new parameters. |
|
* |
|
* @param delimiter a delimiter, non-null, may be empty |
|
* @return a {@link HexFormat} with the delimiter and lowercase characters |
|
*/ |
|
public static HexFormat ofDelimiter(String delimiter) { |
|
return new HexFormat(delimiter, "", "", LOWERCASE_DIGITS); |
|
} |
|
/** |
|
* Returns a copy of this {@code HexFormat} with the delimiter. |
|
* @param delimiter the delimiter, non-null, may be empty |
|
* @return a copy of this {@code HexFormat} with the delimiter |
|
*/ |
|
public HexFormat withDelimiter(String delimiter) { |
|
return new HexFormat(delimiter, this.prefix, this.suffix, this.digits); |
|
} |
|
/** |
|
* Returns a copy of this {@code HexFormat} with the prefix. |
|
* |
|
* @param prefix a prefix, non-null, may be empty |
|
* @return a copy of this {@code HexFormat} with the prefix |
|
*/ |
|
public HexFormat withPrefix(String prefix) { |
|
return new HexFormat(this.delimiter, prefix, this.suffix, this.digits); |
|
} |
|
/** |
|
* Returns a copy of this {@code HexFormat} with the suffix. |
|
* |
|
* @param suffix a suffix, non-null, may be empty |
|
* @return a copy of this {@code HexFormat} with the suffix |
|
*/ |
|
public HexFormat withSuffix(String suffix) { |
|
return new HexFormat(this.delimiter, this.prefix, suffix, this.digits); |
|
} |
|
/** |
|
* Returns a copy of this {@code HexFormat} to use uppercase hexadecimal characters. |
|
* The uppercase hexadecimal characters are {@code "0-9", "A-F"}. |
|
* |
|
* @return a copy of this {@code HexFormat} with uppercase hexadecimal characters |
|
*/ |
|
public HexFormat withUpperCase() { |
|
return new HexFormat(this.delimiter, this.prefix, this.suffix, UPPERCASE_DIGITS); |
|
} |
|
/** |
|
* Returns a copy of this {@code HexFormat} to use lowercase hexadecimal characters. |
|
* The lowercase hexadecimal characters are {@code "0-9", "a-f"}. |
|
* |
|
* @return a copy of this {@code HexFormat} with lowercase hexadecimal characters |
|
*/ |
|
public HexFormat withLowerCase() { |
|
return new HexFormat(this.delimiter, this.prefix, this.suffix, LOWERCASE_DIGITS); |
|
} |
|
/** |
|
* Returns the delimiter between hexadecimal values in formatted hexadecimal strings. |
|
* |
|
* @return the delimiter, non-null, may be empty {@code ""} |
|
*/ |
|
public String delimiter() { |
|
return delimiter; |
|
} |
|
/** |
|
* Returns the prefix used for each hexadecimal value in formatted hexadecimal strings. |
|
* |
|
* @return the prefix, non-null, may be empty {@code ""} |
|
*/ |
|
public String prefix() { |
|
return prefix; |
|
} |
|
/** |
|
* Returns the suffix used for each hexadecimal value in formatted hexadecimal strings. |
|
* |
|
* @return the suffix, non-null, may be empty {@code ""} |
|
*/ |
|
public String suffix() { |
|
return suffix; |
|
} |
|
/** |
|
* Returns {@code true} if the hexadecimal digits are uppercase, |
|
* otherwise {@code false}. |
|
* |
|
* @return {@code true} if the hexadecimal digits are uppercase, |
|
* otherwise {@code false} |
|
*/ |
|
public boolean isUpperCase() { |
|
return Arrays.equals(digits, UPPERCASE_DIGITS); |
|
} |
|
/** |
|
* Returns a hexadecimal string formatted from a byte array. |
|
* Each byte value is formatted as the prefix, two hexadecimal characters |
|
* {@linkplain #isUpperCase selected from} uppercase or lowercase digits, and the suffix. |
|
* A delimiter follows each formatted value, except the last. |
|
* |
|
* The behavior is equivalent to |
|
* {@link #formatHex(byte[], int, int) formatHex(bytes, 0, bytes.length))}. |
|
* |
|
* @param bytes a non-null array of bytes |
|
* @return a string hexadecimal formatting of the byte array |
|
*/ |
|
public String formatHex(byte[] bytes) { |
|
return formatHex(bytes, 0, bytes.length); |
|
} |
|
/** |
|
* Returns a hexadecimal string formatted from a byte array range. |
|
* Each byte value is formatted as the prefix, two hexadecimal characters |
|
* {@linkplain #isUpperCase selected from} uppercase or lowercase digits, and the suffix. |
|
* A delimiter follows each formatted value, except the last. |
|
* |
|
* @param bytes a non-null array of bytes |
|
* @param fromIndex the initial index of the range, inclusive |
|
* @param toIndex the final index of the range, exclusive |
|
* @return a string hexadecimal formatting each byte of the array range |
|
* @throws IndexOutOfBoundsException if the array range is out of bounds |
|
*/ |
|
public String formatHex(byte[] bytes, int fromIndex, int toIndex) { |
|
Objects.requireNonNull(bytes,"bytes"); |
|
Objects.checkFromToIndex(fromIndex, toIndex, bytes.length); |
|
if (toIndex - fromIndex == 0) { |
|
return ""; |
|
} |
|
// Format efficiently if possible |
|
String s = formatOptDelimiter(bytes, fromIndex, toIndex); |
|
if (s == null) { |
|
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(); |
|
} |
|
return s; |
|
} |
|
/** |
|
* Appends formatted hexadecimal strings from a byte array to the {@link Appendable}. |
|
* Each byte value is formatted as the prefix, two hexadecimal characters |
|
* {@linkplain #isUpperCase selected from} uppercase or lowercase digits, and the suffix. |
|
* 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 {@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 |
|
*/ |
|
public <A extends Appendable> A formatHex(A out, byte[] bytes) { |
|
return formatHex(out, bytes, 0, bytes.length); |
|
} |
|
/** |
|
* Appends formatted hexadecimal strings from a byte array range to the {@link Appendable}. |
|
* Each byte value is formatted as the prefix, two hexadecimal characters |
|
* {@linkplain #isUpperCase selected from} uppercase or lowercase digits, and the suffix. |
|
* 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 {@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. |
|
* @return the {@code Appendable} |
|
* @throws IndexOutOfBoundsException if the array range is out of bounds |
|
* @throws UncheckedIOException if an I/O exception occurs appending to the output |
|
*/ |
|
public <A extends Appendable> A formatHex(A out, byte[] bytes, int fromIndex, int toIndex) { |
|
Objects.requireNonNull(out, "out"); |
|
Objects.requireNonNull(bytes, "bytes"); |
|
Objects.checkFromToIndex(fromIndex, toIndex, bytes.length); |
|
int length = toIndex - fromIndex; |
|
if (length > 0) { |
|
try { |
|
String between = suffix + delimiter + prefix; |
|
out.append(prefix); |
|
toHexDigits(out, bytes[fromIndex]); |
|
if (between.isEmpty()) { |
|
for (int i = 1; i < length; i++) { |
|
toHexDigits(out, bytes[fromIndex + i]); |
|
} |
|
} else { |
|
for (int i = 1; i < length; i++) { |
|
out.append(between); |
|
toHexDigits(out, bytes[fromIndex + i]); |
|
} |
|
} |
|
out.append(suffix); |
|
} catch (IOException ioe) { |
|
throw new UncheckedIOException(ioe.getMessage(), ioe); |
|
} |
|
} |
|
return out; |
|
} |
|
/** |
|
* Returns a string formatting of the range of bytes optimized |
|
* for a single allocation. |
|
* Prefix and suffix must be empty and the delimiter |
|
* must be empty or a single byte character, otherwise null is returned. |
|
* |
|
* @param bytes the bytes, non-null |
|
* @param fromIndex the initial index of the range, inclusive |
|
* @param toIndex the final index of the range, exclusive. |
|
* @return a String formatted or null for non-single byte delimiter |
|
* or non-empty prefix or suffix |
|
*/ |
|
private String formatOptDelimiter(byte[] bytes, int fromIndex, int toIndex) { |
|
byte[] rep; |
|
if (!prefix.isEmpty() || !suffix.isEmpty()) { |
|
return null; |
|
} |
|
int length = toIndex - fromIndex; |
|
if (delimiter.isEmpty()) { |
|
// Allocate the byte array and fill in the hex pairs for each byte |
|
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]); |
|
} |
|
} else if (delimiter.length() == 1 && delimiter.charAt(0) < 256) { |
|
// 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[checkMaxArraySize(length * 3L - 1L)]; |
|
rep[0] = (byte) toHighHexDigit(bytes[fromIndex]); |
|
rep[1] = (byte) toLowHexDigit(bytes[fromIndex]); |
|
for (int i = 1; i < length; i++) { |
|
rep[i * 3 - 1] = (byte) sep; |
|
rep[i * 3 ] = (byte) toHighHexDigit(bytes[fromIndex + i]); |
|
rep[i * 3 + 1] = (byte) toLowHexDigit(bytes[fromIndex + i]); |
|
} |
|
} else { |
|
// Delimiter formatting not to a single byte |
|
return null; |
|
} |
|
try { |
|
// Return a new string using the bytes without making a copy |
|
return jla.newStringNoRepl(rep, StandardCharsets.ISO_8859_1); |
|
} catch (CharacterCodingException cce) { |
|
throw new AssertionError(cce); |
|
} |
|
} |
|
/** |
|
* Checked that the requested size for the result string is |
|
* less than or equal to 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. |
|
* |
|
* 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. |
|
* |
|
* @param string a string containing the byte values with prefix, hexadecimal digits, suffix, |
|
* and delimiters |
|
* @return a byte array with the values parsed from the string |
|
* @throws IllegalArgumentException if the prefix or suffix is not present for each byte value, |
|
* the byte values are not hexadecimal characters, or if the delimiter is not present |
|
* after all but the last byte value |
|
*/ |
|
public byte[] parseHex(CharSequence string) { |
|
return parseHex(string, 0, string.length()); |
|
} |
|
/** |
|
* Returns a byte array containing hexadecimal values parsed from a range of the string. |
|
* |
|
* 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. |
|
* |
|
* @param string a string range containing hexadecimal digits, |
|
* delimiters, prefix, and suffix. |
|
* @param fromIndex the initial index of the range, inclusive |
|
* @param toIndex the final index of the range, exclusive. |
|
* @return a byte array with the values parsed from the string range |
|
* @throws IllegalArgumentException if the prefix or suffix is not present for each byte value, |
|
* the byte values are not hexadecimal characters, or if the delimiter is not present |
|
* after all but the last byte value |
|
* @throws IndexOutOfBoundsException if the string range is out of bounds |
|
*/ |
|
public byte[] parseHex(CharSequence string, int fromIndex, int toIndex) { |
|
Objects.requireNonNull(string, "string"); |
|
Objects.checkFromToIndex(fromIndex, toIndex, string.length()); |
|
if (fromIndex != 0 || toIndex != string.length()) { |
|
string = string.subSequence(fromIndex, toIndex); |
|
} |
|
if (string.isEmpty()) |
|
return EMPTY_BYTES; |
|
if (delimiter.isEmpty() && prefix.isEmpty() && suffix.isEmpty()) |
|
return parseNoDelimiter(string); |
|
// avoid overflow for max length prefix or suffix |
|
long valueChars = prefix.length() + 2L + suffix.length(); |
|
long stride = valueChars + delimiter.length(); |
|
if ((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 = (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()) { |
|
bytes[i] = (byte) fromHexDigits(string, offset); |
|
checkLiteral(string, offset + 2, between); |
|
} |
|
bytes[i] = (byte) fromHexDigits(string, offset); |
|
return bytes; |
|
} |
|
/** |
|
* Returns a byte array containing hexadecimal values parsed from |
|
* a range of the character array. |
|
* |
|
* 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. |
|
* |
|
* @param chars a character array range containing an even number of hexadecimal digits, |
|
* delimiters, prefix, and suffix. |
|
* @param fromIndex the initial index of the range, inclusive |
|
* @param toIndex the final index of the range, exclusive. |
|
* @return a byte array with the values parsed from the character array range |
|
* @throws IllegalArgumentException if the prefix or suffix is not present for each byte value, |
|
* the byte values are not hexadecimal characters, or if the delimiter is not present |
|
* after all but the last byte value |
|
* @throws IndexOutOfBoundsException if the character array range is out of bounds |
|
*/ |
|
public byte[] parseHex(char[] chars, int fromIndex, int toIndex) { |
|
Objects.requireNonNull(chars, "chars"); |
|
Objects.checkFromToIndex(fromIndex, toIndex, chars.length); |
|
CharBuffer cb = CharBuffer.wrap(chars, fromIndex, toIndex - fromIndex); |
|
return parseHex(cb); |
|
} |
|
/** |
|
* Compare the literal and throw an exception if it does not match. |
|
* Pre-condition: {@code index + literal.length() <= string.length()}. |
|
* |
|
* @param string a CharSequence |
|
* @param index the index of the literal in the CharSequence |
|
* @param literal the expected literal |
|
* @throws IllegalArgumentException if the literal is not present |
|
*/ |
|
private static void checkLiteral(CharSequence string, int index, String literal) { |
|
assert index <= string.length() - literal.length() : "pre-checked invariant error"; |
|
if (literal.isEmpty() || |
|
(literal.length() == 1 && literal.charAt(0) == string.charAt(index))) { |
|
return; |
|
} |
|
for (int i = 0; i < literal.length(); i++) { |
|
if (string.charAt(index + i) != literal.charAt(i)) { |
|
throw new IllegalArgumentException(escapeNL("found: \"" + |
|
string.subSequence(index, index + literal.length()) + |
|
"\", expected: \"" + literal + "\", index: " + index + |
|
" ch: " + (int)string.charAt(index + i))); |
|
} |
|
} |
|
} |
|
/** |
|
* Expands new line characters to escaped newlines for display. |
|
* |
|
* @param string a string |
|
* @return a string with newline characters escaped |
|
*/ |
|
private static String escapeNL(String string) { |
|
return string.replace("\n", "\\n") |
|
.replace("\r", "\\r"); |
|
} |
|
/** |
|
* Returns the hexadecimal character for the low 4 bits of the value considering it to be a byte. |
|
* If the parameter {@link #isUpperCase()} is {@code true} the |
|
* character returned for values {@code 10-15} is uppercase {@code "A-F"}, |
|
* otherwise the character returned is lowercase {@code "a-f"}. |
|
* The values in the range {@code 0-9} are returned as {@code "0-9"}. |
|
* |
|
* @param value a value, only the low 4 bits {@code 0-3} of the value are used |
|
* @return the hexadecimal character for the low 4 bits {@code 0-3} of the value |
|
*/ |
|
public char toLowHexDigit(int value) { |
|
return (char)digits[value & 0xf]; |
|
} |
|
/** |
|
* Returns the hexadecimal character for the high 4 bits of the value considering it to be a byte. |
|
* If the parameter {@link #isUpperCase()} is {@code true} the |
|
* character returned for values {@code 10-15} is uppercase {@code "A-F"}, |
|
* otherwise the character returned is lowercase {@code "a-f"}. |
|
* The values in the range {@code 0-9} are returned as {@code "0-9"}. |
|
* |
|
* @param value a value, only bits {@code 4-7} of the value are used |
|
* @return the hexadecimal character for the bits {@code 4-7} of the value |
|
*/ |
|
public char toHighHexDigit(int value) { |
|
return (char)digits[(value >> 4) & 0xf]; |
|
} |
|
/** |
|
* Appends two hexadecimal characters for the byte value to the {@link Appendable}. |
|
* Each nibble (4 bits) from most significant to least significant of the value |
|
* is formatted as if by {@link #toLowHexDigit(int) toLowHexDigit(nibble)}. |
|
* The hexadecimal characters are appended in one or more calls to the |
|
* {@link Appendable} methods. The delimiter, prefix and suffix are not used. |
|
* |
|
* @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 |
|
*/ |
|
public <A extends Appendable> A toHexDigits(A out, byte value) { |
|
Objects.requireNonNull(out, "out"); |
|
try { |
|
out.append(toHighHexDigit(value)); |
|
out.append(toLowHexDigit(value)); |
|
return out; |
|
} catch (IOException ioe) { |
|
throw new UncheckedIOException(ioe.getMessage(), ioe); |
|
} |
|
} |
|
/** |
|
* Returns the two hexadecimal characters for the {@code byte} 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. |
|
* |
|
* @param value a byte value |
|
* @return the two hexadecimal characters for the byte value |
|
*/ |
|
public String toHexDigits(byte value) { |
|
byte[] rep = new byte[2]; |
|
rep[0] = (byte)toHighHexDigit(value); |
|
rep[1] = (byte)toLowHexDigit(value); |
|
try { |
|
return jla.newStringNoRepl(rep, StandardCharsets.ISO_8859_1); |
|
} catch (CharacterCodingException cce) { |
|
throw new AssertionError(cce); |
|
} |
|
} |
|
/** |
|
* Returns the four hexadecimal characters for the {@code char} 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. |
|
* |
|
* @param value a {@code char} value |
|
* @return the four hexadecimal characters for the {@code char} value |
|
*/ |
|
public String toHexDigits(char value) { |
|
return toHexDigits((short)value); |
|
} |
|
/** |
|
* Returns the four hexadecimal characters for the {@code short} 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. |
|
* |
|
* @param value a {@code short} value |
|
* @return the four hexadecimal characters for the {@code short} value |
|
*/ |
|
public String toHexDigits(short value) { |
|
byte[] rep = new byte[4]; |
|
rep[0] = (byte)toHighHexDigit((byte)(value >> 8)); |
|
rep[1] = (byte)toLowHexDigit((byte)(value >> 8)); |
|
rep[2] = (byte)toHighHexDigit((byte)value); |
|
rep[3] = (byte)toLowHexDigit((byte)value); |
|
try { |
|
return jla.newStringNoRepl(rep, StandardCharsets.ISO_8859_1); |
|
} catch (CharacterCodingException cce) { |
|
throw new AssertionError(cce); |
|
} |
|
} |
|
/** |
|
* Returns the eight hexadecimal characters for the {@code int} 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. |
|
* |
|
* @param value an {@code int} value |
|
* @return the eight hexadecimal characters for the {@code int} value |
|
* @see Integer#toHexString |
|
*/ |
|
public String toHexDigits(int value) { |
|
byte[] rep = new byte[8]; |
|
rep[0] = (byte)toHighHexDigit((byte)(value >> 24)); |
|
rep[1] = (byte)toLowHexDigit((byte)(value >> 24)); |
|
rep[2] = (byte)toHighHexDigit((byte)(value >> 16)); |
|
rep[3] = (byte)toLowHexDigit((byte)(value >> 16)); |
|
rep[4] = (byte)toHighHexDigit((byte)(value >> 8)); |
|
rep[5] = (byte)toLowHexDigit((byte)(value >> 8)); |
|
rep[6] = (byte)toHighHexDigit((byte)value); |
|
rep[7] = (byte)toLowHexDigit((byte)value); |
|
try { |
|
return jla.newStringNoRepl(rep, StandardCharsets.ISO_8859_1); |
|
} catch (CharacterCodingException cce) { |
|
throw new AssertionError(cce); |
|
} |
|
} |
|
/** |
|
* 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. |
|
* |
|
* @param value a {@code long} value |
|
* @return the sixteen hexadecimal characters for the {@code long} value |
|
* @see Long#toHexString |
|
*/ |
|
public String toHexDigits(long value) { |
|
byte[] rep = new byte[16]; |
|
rep[0] = (byte)toHighHexDigit((byte)(value >>> 56)); |
|
rep[1] = (byte)toLowHexDigit((byte)(value >>> 56)); |
|
rep[2] = (byte)toHighHexDigit((byte)(value >>> 48)); |
|
rep[3] = (byte)toLowHexDigit((byte)(value >>> 48)); |
|
rep[4] = (byte)toHighHexDigit((byte)(value >>> 40)); |
|
rep[5] = (byte)toLowHexDigit((byte)(value >>> 40)); |
|
rep[6] = (byte)toHighHexDigit((byte)(value >>> 32)); |
|
rep[7] = (byte)toLowHexDigit((byte)(value >>> 32)); |
|
rep[8] = (byte)toHighHexDigit((byte)(value >>> 24)); |
|
rep[9] = (byte)toLowHexDigit((byte)(value >>> 24)); |
|
rep[10] = (byte)toHighHexDigit((byte)(value >>> 16)); |
|
rep[11] = (byte)toLowHexDigit((byte)(value >>> 16)); |
|
rep[12] = (byte)toHighHexDigit((byte)(value >>> 8)); |
|
rep[13] = (byte)toLowHexDigit((byte)(value >>> 8)); |
|
rep[14] = (byte)toHighHexDigit((byte)value); |
|
rep[15] = (byte)toLowHexDigit((byte)value); |
|
try { |
|
return jla.newStringNoRepl(rep, StandardCharsets.ISO_8859_1); |
|
} catch (CharacterCodingException cce) { |
|
throw new AssertionError(cce); |
|
} |
|
} |
|
/** |
|
* Returns up to 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. |
|
* |
|
* @param value a {@code long} value |
|
* @param digits the number of hexadecimal digits to return, 0 to 16 |
|
* @return the hexadecimal characters for the {@code long} value |
|
* @throws IllegalArgumentException if {@code digits} is negative or greater than 16 |
|
*/ |
|
public String toHexDigits(long value, int digits) { |
|
if (digits < 0 || digits > 16) |
|
throw new IllegalArgumentException("number of digits: " + digits); |
|
if (digits == 0) |
|
return ""; |
|
byte[] rep = new byte[digits]; |
|
for (int i = rep.length - 1; i >= 0; i--) { |
|
rep[i] = (byte)toLowHexDigit((byte)(value)); |
|
value = value >>> 4; |
|
} |
|
try { |
|
return jla.newStringNoRepl(rep, StandardCharsets.ISO_8859_1); |
|
} catch (CharacterCodingException cce) { |
|
throw new AssertionError(cce); |
|
} |
|
} |
|
/** |
|
* Returns a byte array containing the parsed hex digits. |
|
* A valid string consists only of an even number of hex digits. |
|
* |
|
* @param string a string containing an even number of only hex digits |
|
* @return a byte array |
|
* @throws IllegalArgumentException if the string length is not valid or |
|
* the string contains non-hexadecimal characters |
|
*/ |
|
private static byte[] parseNoDelimiter(CharSequence string) { |
|
if ((string.length() & 1) != 0) |
|
throw new IllegalArgumentException("string length not even: " + |
|
string.length()); |
|
byte[] bytes = new byte[string.length() / 2]; |
|
for (int i = 0; i < bytes.length; i++) { |
|
bytes[i] = (byte) fromHexDigits(string, i * 2); |
|
} |
|
return bytes; |
|
} |
|
/** |
|
* Check the number of requested digits against a limit. |
|
* |
|
* @param fromIndex the initial index of the range, inclusive |
|
* @param toIndex the final index of the range, exclusive. |
|
* @param limit the maximum allowed |
|
* @return the length of the range |
|
*/ |
|
private static int checkDigitCount(int fromIndex, int toIndex, int limit) { |
|
int length = toIndex - fromIndex; |
|
if (length > limit) |
|
throw new IllegalArgumentException("string length greater than " + |
|
limit + ": " + length); |
|
return length; |
|
} |
|
/** |
|
* Returns {@code true} if the character is a valid hexadecimal character or codepoint. |
|
* The valid hexadecimal characters are: |
|
* <ul> |
|
* <li>{@code '0' ('\u005Cu0030')} through {@code '9' ('\u005Cu0039')} inclusive, |
|
* <li>{@code 'A' ('\u005Cu0041')} through {@code 'F' ('\u005Cu0046')} inclusive, and |
|
* <li>{@code 'a' ('\u005Cu0061')} through {@code 'f' ('\u005Cu0066')} inclusive. |
|
* </ul> |
|
* @param ch a codepoint |
|
* @return {@code true} if the character is valid a hexadecimal character, |
|
* otherwise {@code false} |
|
*/ |
|
public static boolean isHexDigit(int ch) { |
|
return ((ch >>> 8) == 0 && DIGITS[ch] >= 0); |
|
} |
|
/** |
|
* Returns the value for the hexadecimal character or codepoint. |
|
* The value is: |
|
* <ul> |
|
* <li>{@code (ch - '0')} for {@code '0'} through {@code '9'} inclusive, |
|
* <li>{@code (ch - 'A' + 10)} for {@code 'A'} through {@code 'F'} inclusive, and |
|
* <li>{@code (ch - 'a' + 10)} for {@code 'a'} through {@code 'f'} inclusive. |
|
* </ul> |
|
* |
|
* @param ch a character or codepoint |
|
* @return the value {@code 0-15} |
|
* @throws NumberFormatException if the codepoint is not a hexadecimal character |
|
*/ |
|
public static int fromHexDigit(int ch) { |
|
int value; |
|
if ((ch >>> 8) == 0 && (value = DIGITS[ch]) >= 0) { |
|
return value; |
|
} |
|
throw new NumberFormatException("not a hexadecimal digit: \"" + (char) ch + "\" = " + ch); |
|
} |
|
/** |
|
* Returns a value parsed from two hexadecimal characters in a string. |
|
* The characters in the range from {@code index} to {@code index + 1}, |
|
* inclusive, must be valid hex digits according to {@link #fromHexDigit(int)}. |
|
* |
|
* @param string a CharSequence containing the characters |
|
* @param index the index of the first character of the range |
|
* @return the value parsed from the string range |
|
* @throws NumberFormatException if any of the characters in the range |
|
* is not a hexadecimal character |
|
* @throws IndexOutOfBoundsException if the range is out of bounds |
|
* for the {@code CharSequence} |
|
*/ |
|
private static int fromHexDigits(CharSequence string, int index) { |
|
int high = fromHexDigit(string.charAt(index)); |
|
int low = fromHexDigit(string.charAt(index + 1)); |
|
return (high << 4) | low; |
|
} |
|
/** |
|
* Returns the {@code int} value parsed from a string of up to eight hexadecimal characters. |
|
* The hexadecimal characters are parsed from most significant to least significant |
|
* using {@link #fromHexDigit(int)} to form an unsigned value. |
|
* The value is zero extended to 32 bits and is returned as an {@code int}. |
|
* |
|
* @apiNote |
|
* {@link Integer#parseInt(String, int) Integer.parseInt(s, 16)} and |
|
* {@link Integer#parseUnsignedInt(String, int) Integer.parseUnsignedInt(s, 16)} |
|
* are similar but allow all Unicode hexadecimal digits defined by |
|
* {@link Character#digit(char, int) Character.digit(ch, 16)}. |
|
* {@code HexFormat} uses only hexadecimal characters |
|
* {@code "0-9"}, {@code "A-F"} and {@code "a-f"}. |
|
* Signed hexadecimal strings can be parsed with {@link Integer#parseInt(String, int)}. |
|
* |
|
* @param string a CharSequence containing up to eight hexadecimal characters |
|
* @return the value parsed from the string |
|
* @throws IllegalArgumentException if the string length is greater than eight (8) or |
|
* if any of the characters is not a hexadecimal character |
|
*/ |
|
public static int fromHexDigits(CharSequence string) { |
|
return fromHexDigits(string, 0, string.length()); |
|
} |
|
/** |
|
* Returns the {@code int} value parsed from a string range of up to eight hexadecimal |
|
* characters. |
|
* The characters in the range {@code fromIndex} to {@code toIndex}, exclusive, |
|
* are parsed from most significant to least significant |
|
* using {@link #fromHexDigit(int)} to form an unsigned value. |
|
* The value is zero extended to 32 bits and is returned as an {@code int}. |
|
* |
|
* @apiNote |
|
* {@link Integer#parseInt(String, int) Integer.parseInt(s, 16)} and |
|
* {@link Integer#parseUnsignedInt(String, int) Integer.parseUnsignedInt(s, 16)} |
|
* are similar but allow all Unicode hexadecimal digits defined by |
|
* {@link Character#digit(char, int) Character.digit(ch, 16)}. |
|
* {@code HexFormat} uses only hexadecimal characters |
|
* {@code "0-9"}, {@code "A-F"} and {@code "a-f"}. |
|
* Signed hexadecimal strings can be parsed with {@link Integer#parseInt(String, int)}. |
|
* |
|
* @param string a CharSequence containing the characters |
|
* @param fromIndex the initial index of the range, inclusive |
|
* @param toIndex the final index of the range, exclusive. |
|
* @return the value parsed from the string range |
|
* @throws IndexOutOfBoundsException if the range is out of bounds |
|
* for the {@code CharSequence} |
|
* @throws IllegalArgumentException if length of the range is greater than eight (8) or |
|
* if any of the characters is not a hexadecimal character |
|
*/ |
|
public static int fromHexDigits(CharSequence string, int fromIndex, int toIndex) { |
|
Objects.requireNonNull(string, "string"); |
|
Objects.checkFromToIndex(fromIndex, toIndex, string.length()); |
|
int length = checkDigitCount(fromIndex, toIndex, 8); |
|
int value = 0; |
|
for (int i = 0; i < length; i++) { |
|
value = (value << 4) + fromHexDigit(string.charAt(fromIndex + i)); |
|
} |
|
return value; |
|
} |
|
/** |
|
* Returns the long value parsed from a string of up to sixteen hexadecimal characters. |
|
* The hexadecimal characters are parsed from most significant to least significant |
|
* using {@link #fromHexDigit(int)} to form an unsigned value. |
|
* The value is zero extended to 64 bits and is returned as a {@code long}. |
|
* |
|
* @apiNote |
|
* {@link Long#parseLong(String, int) Long.parseLong(s, 16)} and |
|
* {@link Long#parseUnsignedLong(String, int) Long.parseUnsignedLong(s, 16)} |
|
* are similar but allow all Unicode hexadecimal digits defined by |
|
* {@link Character#digit(char, int) Character.digit(ch, 16)}. |
|
* {@code HexFormat} uses only hexadecimal characters |
|
* {@code "0-9"}, {@code "A-F"} and {@code "a-f"}. |
|
* Signed hexadecimal strings can be parsed with {@link Long#parseLong(String, int)}. |
|
* |
|
* @param string a CharSequence containing up to sixteen hexadecimal characters |
|
* @return the value parsed from the string |
|
* @throws IllegalArgumentException if the string length is greater than sixteen (16) or |
|
* if any of the characters is not a hexadecimal character |
|
*/ |
|
public static long fromHexDigitsToLong(CharSequence string) { |
|
return fromHexDigitsToLong(string, 0, string.length()); |
|
} |
|
/** |
|
* Returns the long value parsed from a string range of up to sixteen hexadecimal |
|
* characters. |
|
* The characters in the range {@code fromIndex} to {@code toIndex}, exclusive, |
|
* are parsed from most significant to least significant |
|
* using {@link #fromHexDigit(int)} to form an unsigned value. |
|
* The value is zero extended to 64 bits and is returned as a {@code long}. |
|
* |
|
* @apiNote |
|
* {@link Long#parseLong(String, int) Long.parseLong(s, 16)} and |
|
* {@link Long#parseUnsignedLong(String, int) Long.parseUnsignedLong(s, 16)} |
|
* are similar but allow all Unicode hexadecimal digits defined by |
|
* {@link Character#digit(char, int) Character.digit(ch, 16)}. |
|
* {@code HexFormat} uses only hexadecimal characters |
|
* {@code "0-9"}, {@code "A-F"} and {@code "a-f"}. |
|
* Signed hexadecimal strings can be parsed with {@link Long#parseLong(String, int)}. |
|
* |
|
* @param string a CharSequence containing the characters |
|
* @param fromIndex the initial index of the range, inclusive |
|
* @param toIndex the final index of the range, exclusive. |
|
* @return the value parsed from the string range |
|
* @throws IndexOutOfBoundsException if the range is out of bounds |
|
* for the {@code CharSequence} |
|
* @throws IllegalArgumentException if the length of the range is greater than sixteen (16) or |
|
* if any of the characters is not a hexadecimal character |
|
*/ |
|
public static long fromHexDigitsToLong(CharSequence string, int fromIndex, int toIndex) { |
|
Objects.requireNonNull(string, "string"); |
|
Objects.checkFromToIndex(fromIndex, toIndex, string.length()); |
|
int length = checkDigitCount(fromIndex, toIndex, 16); |
|
long value = 0L; |
|
for (int i = 0; i < length; i++) { |
|
value = (value << 4) + fromHexDigit(string.charAt(fromIndex + i)); |
|
} |
|
return value; |
|
} |
|
/** |
|
* Returns {@code true} if the other object is a {@code HexFormat} |
|
* with the same parameters. |
|
* |
|
* @param o an object, may be null |
|
* @return {@code true} if the other object is a {@code HexFormat} and the parameters |
|
* uppercase, delimiter, prefix, and suffix are equal; |
|
* otherwise {@code false} |
|
*/ |
|
@Override |
|
public boolean equals(Object o) { |
|
if (this == o) |
|
return true; |
|
if (o == null || getClass() != o.getClass()) |
|
return false; |
|
HexFormat otherHex = (HexFormat) o; |
|
return Arrays.equals(digits, otherHex.digits) && |
|
delimiter.equals(otherHex.delimiter) && |
|
prefix.equals(otherHex.prefix) && |
|
suffix.equals(otherHex.suffix); |
|
} |
|
/** |
|
* Returns a hashcode for this {@code HexFormat}. |
|
* |
|
* @return a hashcode for this {@code HexFormat} |
|
*/ |
|
@Override |
|
public int hashCode() { |
|
int result = Objects.hash(delimiter, prefix, suffix); |
|
result = 31 * result + Boolean.hashCode(Arrays.equals(digits, UPPERCASE_DIGITS)); |
|
return result; |
|
} |
|
/** |
|
* Returns a description of the formatter parameters for uppercase, |
|
* delimiter, prefix, and suffix. |
|
* |
|
* @return a description of this {@code HexFormat} |
|
*/ |
|
@Override |
|
public String toString() { |
|
return escapeNL("uppercase: " + Arrays.equals(digits, UPPERCASE_DIGITS) + |
|
", delimiter: \"" + delimiter + |
|
"\", prefix: \"" + prefix + |
|
"\", suffix: \"" + suffix + "\""); |
|
} |
|
} |