|  | 
|  | 1 | +package io.kafbat.ui.util; | 
|  | 2 | + | 
|  | 3 | +import java.nio.ByteBuffer; | 
|  | 4 | +import java.nio.CharBuffer; | 
|  | 5 | +import java.nio.charset.CharsetDecoder; | 
|  | 6 | +import java.nio.charset.StandardCharsets; | 
|  | 7 | +import java.util.List; | 
|  | 8 | +import java.util.regex.Pattern; | 
|  | 9 | + | 
|  | 10 | +/** | 
|  | 11 | + * Inspired by: https://github.com/tchiotludo/akhq/blob/dev/src/main/java/org/akhq/utils/ContentUtils.java | 
|  | 12 | + */ | 
|  | 13 | +public class ContentUtils { | 
|  | 14 | +  private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); | 
|  | 15 | + | 
|  | 16 | +  private static final CharsetDecoder UTF8_DECODER = StandardCharsets.UTF_8.newDecoder(); | 
|  | 17 | + | 
|  | 18 | +  private ContentUtils() { | 
|  | 19 | +  } | 
|  | 20 | + | 
|  | 21 | +  /** | 
|  | 22 | +   * Detects if bytes contain a UTF-8 string or something else. | 
|  | 23 | +   * @param value the bytes to test for a UTF-8 encoded {@code java.lang.String} value | 
|  | 24 | +   * @return  true, if the byte[] contains a UTF-8 encode  {@code java.lang.String} | 
|  | 25 | +   */ | 
|  | 26 | +  public static boolean isValidUtf8(byte[] value) { | 
|  | 27 | +    // Any data exceeding 10KB will be treated as a string. | 
|  | 28 | +    if (value.length > 10_000) { | 
|  | 29 | +      return true; | 
|  | 30 | +    } | 
|  | 31 | +    try { | 
|  | 32 | +      CharBuffer decode = UTF8_DECODER.decode(ByteBuffer.wrap(value)); | 
|  | 33 | +      return decode.chars().allMatch(ContentUtils::isValidUtf8); | 
|  | 34 | +    } catch (Exception e) { | 
|  | 35 | +      return false; | 
|  | 36 | +    } | 
|  | 37 | +  } | 
|  | 38 | + | 
|  | 39 | +  public static boolean isValidUtf8(int c) { | 
|  | 40 | +    // SKIP NULL Symbols | 
|  | 41 | +    if (c == 0) { | 
|  | 42 | +      return false; | 
|  | 43 | +    } | 
|  | 44 | +    // Well known symbols | 
|  | 45 | +    if (Character.isAlphabetic(c) | 
|  | 46 | +        || Character.isDigit(c) | 
|  | 47 | +        || Character.isWhitespace(c) | 
|  | 48 | +        || Character.isEmoji(c) | 
|  | 49 | +    ) { | 
|  | 50 | +      return true; | 
|  | 51 | +    } | 
|  | 52 | +    // We could read only whitespace controls like | 
|  | 53 | +    return !Character.isISOControl(c); | 
|  | 54 | +  } | 
|  | 55 | + | 
|  | 56 | +  /** | 
|  | 57 | +   * Converts bytes to long. | 
|  | 58 | +   * | 
|  | 59 | +   * @param value the bytes to convert in to a long | 
|  | 60 | +   * @return the long build from the given bytes | 
|  | 61 | +   */ | 
|  | 62 | +  public static Long asLong(byte[] value) { | 
|  | 63 | +    return value != null ? ByteBuffer.wrap(value).getLong() : null; | 
|  | 64 | +  } | 
|  | 65 | + | 
|  | 66 | +  /** | 
|  | 67 | +   * Converts the given bytes to {@code int}. | 
|  | 68 | +   * | 
|  | 69 | +   * @param value the bytes to convert into a {@code int} | 
|  | 70 | +   * @return the {@code int} build from the given bytes | 
|  | 71 | +   */ | 
|  | 72 | +  public static Integer asInt(byte[] value) { | 
|  | 73 | +    return value != null ? ByteBuffer.wrap(value).getInt() : null; | 
|  | 74 | +  } | 
|  | 75 | + | 
|  | 76 | +  /** | 
|  | 77 | +   * Converts the given bytes to {@code short}. | 
|  | 78 | +   * | 
|  | 79 | +   * @param value the bytes to convert into a {@code short} | 
|  | 80 | +   * @return the {@code short} build from the given bytes | 
|  | 81 | +   */ | 
|  | 82 | +  public static Short asShort(byte[] value) { | 
|  | 83 | +    return value != null ? ByteBuffer.wrap(value).getShort() : null; | 
|  | 84 | +  } | 
|  | 85 | + | 
|  | 86 | +  /** | 
|  | 87 | +   * Converts the given bytes either into a {@code java.lang.string}, {@code int}, | 
|  | 88 | +   * {@code long} or {@code short} depending on the content it contains. | 
|  | 89 | +   * @param value     the bytes to convert | 
|  | 90 | +   * @return  the value as an  {@code java.lang.string}, {@code int}, {@code long} or {@code short} | 
|  | 91 | +   */ | 
|  | 92 | +  public static String convertToString(byte[] value) { | 
|  | 93 | +    String valueAsString = null; | 
|  | 94 | + | 
|  | 95 | +    if (value != null) { | 
|  | 96 | +      try { | 
|  | 97 | +        if (ContentUtils.isValidUtf8(value)) { | 
|  | 98 | +          valueAsString = new String(value); | 
|  | 99 | +        } else { | 
|  | 100 | +          if (value.length == Long.BYTES) { | 
|  | 101 | +            valueAsString = String.valueOf(ContentUtils.asLong(value)); | 
|  | 102 | +          } else if (value.length == Integer.BYTES) { | 
|  | 103 | +            valueAsString = String.valueOf(ContentUtils.asInt(value)); | 
|  | 104 | +          } else if (value.length == Short.BYTES) { | 
|  | 105 | +            valueAsString = String.valueOf(ContentUtils.asShort(value)); | 
|  | 106 | +          } else { | 
|  | 107 | +            valueAsString = bytesToHex(value); | 
|  | 108 | +          } | 
|  | 109 | +        } | 
|  | 110 | +      } catch (Exception ex) { | 
|  | 111 | +        // Show the header as hexadecimal string | 
|  | 112 | +        valueAsString = bytesToHex(value); | 
|  | 113 | +      } | 
|  | 114 | +    } | 
|  | 115 | +    return valueAsString; | 
|  | 116 | +  } | 
|  | 117 | + | 
|  | 118 | +  // https://stackoverflow.com/questions/9655181/java-convert-a-byte-array-to-a-hex-string | 
|  | 119 | +  public static String bytesToHex(byte[] bytes) { | 
|  | 120 | +    byte[] hexChars = new byte[bytes.length * 2]; | 
|  | 121 | +    for (int j = 0; j < bytes.length; j++) { | 
|  | 122 | +      int v = bytes[j] & 0xFF; | 
|  | 123 | +      hexChars[j * 2] = HEX_ARRAY[v >>> 4]; | 
|  | 124 | +      hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; | 
|  | 125 | +    } | 
|  | 126 | +    return new String(hexChars, StandardCharsets.UTF_8); | 
|  | 127 | +  } | 
|  | 128 | + | 
|  | 129 | +} | 
0 commit comments