001/* 002 * Copyright 2023 the original author or authors. 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package de.cuioss.tools.codec; 017 018import java.nio.ByteBuffer; 019import java.nio.charset.Charset; 020import java.nio.charset.StandardCharsets; 021 022import lombok.Getter; 023import lombok.ToString; 024 025/** 026 * <h2>Overview</h2> Converts hexadecimal Strings. The Charset used for certain 027 * operation can be set, the default is set in {@link StandardCharsets#UTF_8} 028 * 029 * This class is thread-safe. 030 * <h3>Usage</h3> 031 * 032 * <pre> 033 * String roundtrip = "roundtrip"; 034 * assertEquals(roundtrip, new String(Hex.decodeHex(Hex.encodeHex(roundtrip.getBytes())))); 035 * </pre> 036 * 037 * 038 * @author https://github.com/apache/commons-codec/blob/master/src/main/java/org/apache/commons/codec/binary/Hex.java 039 */ 040@ToString 041public class Hex { 042 043 /** 044 * Default charset is {@link StandardCharsets#UTF_8} 045 */ 046 public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; 047 048 /** 049 * Used to build output as Hex 050 */ 051 private static final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 052 'e', 'f' }; 053 054 /** 055 * Used to build output as Hex 056 */ 057 private static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 058 'E', 'F' }; 059 060 /** 061 * Converts a String representing hexadecimal values into an array of bytes of 062 * those same values. The returned array will be half the length of the passed 063 * String, as it takes two characters to represent any given byte. An exception 064 * is thrown if the passed String has an odd number of elements. 065 * 066 * @param data A String containing hexadecimal digits 067 * @return A byte array containing binary data decoded from the supplied char 068 * array. 069 * @throws DecoderException Thrown if an odd number or illegal of characters is 070 * supplied 071 */ 072 public static byte[] decodeHex(final String data) throws DecoderException { 073 return decodeHex(data.toCharArray()); 074 } 075 076 /** 077 * Converts an array of characters representing hexadecimal values into an array 078 * of bytes of those same values. The returned array will be half the length of 079 * the passed array, as it takes two characters to represent any given byte. An 080 * exception is thrown if the passed char array has an odd number of elements. 081 * 082 * @param data An array of characters containing hexadecimal digits 083 * @return A byte array containing binary data decoded from the supplied char 084 * array. 085 * @throws DecoderException Thrown if an odd number or illegal of characters is 086 * supplied 087 */ 088 @SuppressWarnings("squid:ForLoopCounterChangedCheck") // owolff: original code 089 public static byte[] decodeHex(final char[] data) throws DecoderException { 090 091 final var len = data.length; 092 093 if ((len & 0x01) != 0) { 094 throw new DecoderException("Odd number of characters."); 095 } 096 097 final var out = new byte[len >> 1]; 098 099 // two characters form the hex value. 100 for (int i = 0, j = 0; j < len; i++) { 101 var f = toDigit(data[j], j) << 4; 102 j++; 103 f = f | toDigit(data[j], j); 104 j++; 105 out[i] = (byte) (f & 0xFF); 106 } 107 108 return out; 109 } 110 111 /** 112 * Converts an array of bytes into an array of characters representing the 113 * hexadecimal values of each byte in order. The returned array will be double 114 * the length of the passed array, as it takes two characters to represent any 115 * given byte. 116 * 117 * @param data a byte[] to convert to Hex characters 118 * @return A char[] containing lower-case hexadecimal characters 119 */ 120 public static char[] encodeHex(final byte[] data) { 121 return encodeHex(data, true); 122 } 123 124 /** 125 * Converts a byte buffer into an array of characters representing the 126 * hexadecimal values of each byte in order. The returned array will be double 127 * the length of the passed array, as it takes two characters to represent any 128 * given byte. 129 * 130 * <p> 131 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after 132 * this method the value {@link ByteBuffer#remaining() remaining()} will be 133 * zero. 134 * </p> 135 * 136 * @param data a byte buffer to convert to Hex characters 137 * @return A char[] containing lower-case hexadecimal characters 138 */ 139 public static char[] encodeHex(final ByteBuffer data) { 140 return encodeHex(data, true); 141 } 142 143 /** 144 * Converts an array of bytes into an array of characters representing the 145 * hexadecimal values of each byte in order. The returned array will be double 146 * the length of the passed array, as it takes two characters to represent any 147 * given byte. 148 * 149 * @param data a byte[] to convert to Hex characters 150 * @param toLowerCase {@code true} converts to lowercase, {@code false} to 151 * uppercase 152 * @return A char[] containing hexadecimal characters in the selected case 153 * 154 */ 155 public static char[] encodeHex(final byte[] data, final boolean toLowerCase) { 156 return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); 157 } 158 159 /** 160 * Converts a byte buffer into an array of characters representing the 161 * hexadecimal values of each byte in order. The returned array will be double 162 * the length of the passed array, as it takes two characters to represent any 163 * given byte. 164 * 165 * <p> 166 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after 167 * this method the value {@link ByteBuffer#remaining() remaining()} will be 168 * zero. 169 * </p> 170 * 171 * @param data a byte buffer to convert to Hex characters 172 * @param toLowerCase {@code true} converts to lowercase, {@code false} to 173 * uppercase 174 * @return A char[] containing hexadecimal characters in the selected case 175 */ 176 public static char[] encodeHex(final ByteBuffer data, final boolean toLowerCase) { 177 return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); 178 } 179 180 /** 181 * Converts an array of bytes into an array of characters representing the 182 * hexadecimal values of each byte in order. The returned array will be double 183 * the length of the passed array, as it takes two characters to represent any 184 * given byte. 185 * 186 * @param data a byte[] to convert to Hex characters 187 * @param toDigits the output alphabet (must contain at least 16 chars) 188 * @return A char[] containing the appropriate characters from the alphabet For 189 * best results, this should be either upper- or lower-case hex. 190 * 191 */ 192 @SuppressWarnings("squid:ForLoopCounterChangedCheck") // owolff: original code 193 protected static char[] encodeHex(final byte[] data, final char[] toDigits) { 194 final var l = data.length; 195 final var out = new char[l << 1]; 196 // two characters form the hex value. 197 for (int i = 0, j = 0; i < l; i++) { 198 out[j] = toDigits[(0xF0 & data[i]) >>> 4]; 199 j++; 200 out[j] = toDigits[0x0F & data[i]]; 201 j++; 202 } 203 return out; 204 } 205 206 /** 207 * Converts a byte buffer into an array of characters representing the 208 * hexadecimal values of each byte in order. The returned array will be double 209 * the length of the passed array, as it takes two characters to represent any 210 * given byte. 211 * 212 * <p> 213 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after 214 * this method the value {@link ByteBuffer#remaining() remaining()} will be 215 * zero. 216 * </p> 217 * 218 * @param byteBuffer a byte buffer to convert to Hex characters 219 * @param toDigits the output alphabet (must be at least 16 characters) 220 * @return A char[] containing the appropriate characters from the alphabet For 221 * best results, this should be either upper- or lower-case hex. 222 * 223 */ 224 protected static char[] encodeHex(final ByteBuffer byteBuffer, final char[] toDigits) { 225 return encodeHex(toByteArray(byteBuffer), toDigits); 226 } 227 228 /** 229 * Converts an array of bytes into a String representing the hexadecimal values 230 * of each byte in order. The returned String will be double the length of the 231 * passed array, as it takes two characters to represent any given byte. 232 * 233 * @param data a byte[] to convert to Hex characters 234 * @return A String containing lower-case hexadecimal characters 235 * 236 */ 237 public static String encodeHexString(final byte[] data) { 238 return new String(encodeHex(data)); 239 } 240 241 /** 242 * Converts an array of bytes into a String representing the hexadecimal values 243 * of each byte in order. The returned String will be double the length of the 244 * passed array, as it takes two characters to represent any given byte. 245 * 246 * @param data a byte[] to convert to Hex characters 247 * @param toLowerCase {@code true} converts to lowercase, {@code false} to 248 * uppercase 249 * @return A String containing lower-case hexadecimal characters 250 * 251 */ 252 public static String encodeHexString(final byte[] data, final boolean toLowerCase) { 253 return new String(encodeHex(data, toLowerCase)); 254 } 255 256 /** 257 * Converts a byte buffer into a String representing the hexadecimal values of 258 * each byte in order. The returned String will be double the length of the 259 * passed array, as it takes two characters to represent any given byte. 260 * 261 * <p> 262 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after 263 * this method the value {@link ByteBuffer#remaining() remaining()} will be 264 * zero. 265 * </p> 266 * 267 * @param data a byte buffer to convert to Hex characters 268 * @return A String containing lower-case hexadecimal characters 269 * 270 */ 271 public static String encodeHexString(final ByteBuffer data) { 272 return new String(encodeHex(data)); 273 } 274 275 /** 276 * Converts a byte buffer into a String representing the hexadecimal values of 277 * each byte in order. The returned String will be double the length of the 278 * passed array, as it takes two characters to represent any given byte. 279 * 280 * <p> 281 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after 282 * this method the value {@link ByteBuffer#remaining() remaining()} will be 283 * zero. 284 * </p> 285 * 286 * @param data a byte buffer to convert to Hex characters 287 * @param toLowerCase {@code true} converts to lowercase, {@code false} to 288 * uppercase 289 * @return A String containing lower-case hexadecimal characters 290 * 291 */ 292 public static String encodeHexString(final ByteBuffer data, final boolean toLowerCase) { 293 return new String(encodeHex(data, toLowerCase)); 294 } 295 296 /** 297 * Convert the byte buffer to a byte array. All bytes identified by 298 * {@link ByteBuffer#remaining()} will be used. 299 * 300 * @param byteBuffer the byte buffer 301 * @return the byte[] 302 */ 303 private static byte[] toByteArray(final ByteBuffer byteBuffer) { 304 final var remaining = byteBuffer.remaining(); 305 // Use the underlying buffer if possible 306 if (byteBuffer.hasArray()) { 307 final var byteArray = byteBuffer.array(); 308 if (remaining == byteArray.length) { 309 byteBuffer.position(remaining); 310 return byteArray; 311 } 312 } 313 // Copy the bytes 314 final var byteArray = new byte[remaining]; 315 byteBuffer.get(byteArray); 316 return byteArray; 317 } 318 319 /** 320 * Converts a hexadecimal character to an integer. 321 * 322 * @param ch A character to convert to an integer digit 323 * @param index The index of the character in the source 324 * @return An integer 325 * @throws DecoderException Thrown if ch is an illegal hex character 326 */ 327 protected static int toDigit(final char ch, final int index) throws DecoderException { 328 final var digit = Character.digit(ch, 16); 329 if (digit == -1) { 330 throw new DecoderException("Illegal hexadecimal character " + ch + " at index " + index); 331 } 332 return digit; 333 } 334 335 @Getter 336 private final Charset charset; 337 338 /** 339 * Creates a new codec with the default charset name {@link #DEFAULT_CHARSET} 340 */ 341 public Hex() { 342 // use default encoding 343 charset = DEFAULT_CHARSET; 344 } 345 346 /** 347 * Creates a new codec with the given Charset. 348 * 349 * @param charset the charset. 350 * 351 */ 352 public Hex(final Charset charset) { 353 this.charset = charset; 354 } 355 356 /** 357 * Creates a new codec with the given charset name. 358 * 359 * @param charsetName the charset name. 360 * @throws java.nio.charset.UnsupportedCharsetException If the named charset is 361 * unavailable 362 * 363 * throws 364 * UnsupportedCharsetException 365 * if the named charset is 366 * unavailable 367 */ 368 public Hex(final String charsetName) { 369 this(Charset.forName(charsetName)); 370 } 371 372 /** 373 * Converts an array of character bytes representing hexadecimal values into an 374 * array of bytes of those same values. The returned array will be half the 375 * length of the passed array, as it takes two characters to represent any given 376 * byte. An exception is thrown if the passed char array has an odd number of 377 * elements. 378 * 379 * @param array An array of character bytes containing hexadecimal digits 380 * @return A byte array containing binary data decoded from the supplied byte 381 * array (representing characters). 382 * @throws DecoderException Thrown if an odd number of characters is supplied to 383 * this function 384 * @see #decodeHex(char[]) 385 */ 386 public byte[] decode(final byte[] array) throws DecoderException { 387 return decodeHex(new String(array, getCharset()).toCharArray()); 388 } 389 390 /** 391 * Converts a buffer of character bytes representing hexadecimal values into an 392 * array of bytes of those same values. The returned array will be half the 393 * length of the passed array, as it takes two characters to represent any given 394 * byte. An exception is thrown if the passed char array has an odd number of 395 * elements. 396 * 397 * <p> 398 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after 399 * this method the value {@link ByteBuffer#remaining() remaining()} will be 400 * zero. 401 * </p> 402 * 403 * @param buffer An array of character bytes containing hexadecimal digits 404 * @return A byte array containing binary data decoded from the supplied byte 405 * array (representing characters). 406 * @throws DecoderException Thrown if an odd number of characters is supplied to 407 * this function 408 * @see #decodeHex(char[]) 409 * 410 */ 411 public byte[] decode(final ByteBuffer buffer) throws DecoderException { 412 return decodeHex(new String(toByteArray(buffer), getCharset()).toCharArray()); 413 } 414 415 /** 416 * Converts a String or an array of character bytes representing hexadecimal 417 * values into an array of bytes of those same values. The returned array will 418 * be half the length of the passed String or array, as it takes two characters 419 * to represent any given byte. An exception is thrown if the passed char array 420 * has an odd number of elements. 421 * 422 * @param object A String, ByteBuffer, byte[], or an array of character bytes 423 * containing hexadecimal digits 424 * @return A byte array containing binary data decoded from the supplied byte 425 * array (representing characters). 426 * @throws DecoderException Thrown if an odd number of characters is supplied to 427 * this function or the object is not a String or 428 * char[] 429 * @see #decodeHex(char[]) 430 */ 431 public Object decode(final Object object) throws DecoderException { 432 if (object instanceof String string) { 433 return decode(string.toCharArray()); 434 } 435 if (object instanceof byte[] bytes) { 436 return decode(bytes); 437 } 438 if (object instanceof ByteBuffer buffer) { 439 return decode(buffer); 440 } 441 try { 442 return decodeHex((char[]) object); 443 } catch (final ClassCastException e) { 444 throw new DecoderException(e.getMessage(), e); 445 } 446 } 447 448 /** 449 * Converts an array of bytes into an array of bytes for the characters 450 * representing the hexadecimal values of each byte in order. The returned array 451 * will be double the length of the passed array, as it takes two characters to 452 * represent any given byte. 453 * <p> 454 * The conversion from hexadecimal characters to the returned bytes is performed 455 * with the charset named by {@link #getCharset()}. 456 * </p> 457 * 458 * @param array a byte[] to convert to Hex characters 459 * @return A byte[] containing the bytes of the lower-case hexadecimal 460 * characters No longer throws IllegalStateException if the charsetName 461 * is invalid. 462 * @see #encodeHex(byte[]) 463 */ 464 public byte[] encode(final byte[] array) { 465 return encodeHexString(array).getBytes(getCharset()); 466 } 467 468 /** 469 * Converts byte buffer into an array of bytes for the characters representing 470 * the hexadecimal values of each byte in order. The returned array will be 471 * double the length of the passed array, as it takes two characters to 472 * represent any given byte. 473 * 474 * <p> 475 * The conversion from hexadecimal characters to the returned bytes is performed 476 * with the charset named by {@link #getCharset()}. 477 * </p> 478 * 479 * <p> 480 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after 481 * this method the value {@link ByteBuffer#remaining() remaining()} will be 482 * zero. 483 * </p> 484 * 485 * @param array a byte buffer to convert to Hex characters 486 * @return A byte[] containing the bytes of the lower-case hexadecimal 487 * characters 488 * @see #encodeHex(byte[]) 489 */ 490 public byte[] encode(final ByteBuffer array) { 491 return encodeHexString(array).getBytes(getCharset()); 492 } 493 494 /** 495 * Converts a String or an array of bytes into an array of characters 496 * representing the hexadecimal values of each byte in order. The returned array 497 * will be double the length of the passed String or array, as it takes two 498 * characters to represent any given byte. 499 * <p> 500 * The conversion from hexadecimal characters to bytes to be encoded to 501 * performed with the charset named by {@link #getCharset()}. 502 * </p> 503 * 504 * @param object a String, ByteBuffer, or byte[] to convert to Hex characters 505 * @return A char[] containing lower-case hexadecimal characters 506 * @throws EncoderException Thrown if the given object is not a String or byte[] 507 * @see #encodeHex(byte[]) 508 */ 509 public Object encode(final Object object) throws EncoderException { 510 byte[] byteArray; 511 if (object instanceof String string) { 512 byteArray = string.getBytes(getCharset()); 513 } else if (object instanceof ByteBuffer buffer) { 514 byteArray = toByteArray(buffer); 515 } else { 516 try { 517 byteArray = (byte[]) object; 518 } catch (final ClassCastException e) { 519 throw new EncoderException(e.getMessage(), e); 520 } 521 } 522 return encodeHex(byteArray); 523 } 524 525}