All Downloads are FREE. Search and download functionalities are using the official Maven repository.

java.util.HexFormat Maven / Gradle / Ivy

There is a newer version: 17.alpha.0.57
Show newest version
/*
 * 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.
 * 

* 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. *

* 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. * *

* 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. * *

* 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()}. * *

* 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)}. *

{@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"
 * }
*

* For a comma ({@code ", "}) separated format with a prefix ({@code "#"}) * using lowercase hex digits the {@code HexFormat} is: *

{@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"
 * }
*

* For a fingerprint of byte values that uses the delimiter colon ({@code ":"}) * and uppercase characters the {@code HexFormat} is: *

{@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"
 * }
* *

* This is a value-based * 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. *

* This class is immutable and thread-safe. *

* 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 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 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 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 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 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 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: *

* @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: *
    *
  • {@code (ch - '0')} for {@code '0'} through {@code '9'} inclusive, *
  • {@code (ch - 'A' + 10)} for {@code 'A'} through {@code 'F'} inclusive, and *
  • {@code (ch - 'a' + 10)} for {@code 'a'} through {@code 'f'} inclusive. *
* * @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 + "\""); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy