docs: public static boolean deepEquals(Object a, Object b) {
          if (a == null || b == null) {
              return a == b;
          } else if (a instanceof Object[] && b instanceof Object[]) {
              return Arrays.deepEquals((Object[]) a, (Object[]) b);
          } else if (a instanceof boolean[] && b instanceof boolean[]) {
              return Arrays.equals((boolean[]) a, (boolean[]) b);
          } else if (a instanceof byte[] && b instanceof byte[]) {
              return Arrays.equals((byte[]) a, (byte[]) b);
          } else if (a instanceof char[] && b instanceof char[]) {
              return Arrays.equals((char[]) a, (char[]) b);
          } else if (a instanceof double[] && b instanceof double[]) {
              return Arrays.equals((double[]) a, (double[]) b);
          } else if (a instanceof float[] && b instanceof float[]) {
              return Arrays.equals((float[]) a, (float[]) b);
          } else if (a instanceof int[] && b instanceof int[]) {
              return Arrays.equals((int[]) a, (int[]) b);
          } else if (a instanceof long[] && b instanceof long[]) {
              return Arrays.equals((long[]) a, (long[]) b);
          } else if (a instanceof short[] && b instanceof short[]) {
              return Arrays.equals((short[]) a, (short[]) b);
          }
          return a.equals(b);
      }
  
      /**
       * NULL safe reference unwrap
       */
      public static <T> T unwrap(Reference<T> reference) {
          return reference == null ? null : reference.get();
      }
  
      /**
       * NULL safe reference unwrap
       */
      public static <T> T unwrap(AtomicReference<T> reference) {
          return reference == null ? null : reference.get();
      }
  
      public static boolean isNull(Object object) {
          return object == null;
      }
  
      public static boolean nonNull(Object object) {
          return object != null;
      }
  
      /**
       * Null-safe equivalent of {@code a.equals(b)}.
       */
      public static boolean equal(Object a, Object b) {
          return (a == null) ? (b == null) : a.equals(b);
      }
  
      /**
       * Null-safe equivalent of {@code !a.equals(b)}.
       */
      public static boolean notEqual(Object a, Object b) {
          return !equal(a, b);
      }
  
      /**
       * Convenience wrapper for {@link Arrays#hashCode}, adding varargs.
       * This can be used to compute a hash code for an object's fields as follows:
       * {@code Objects.hash(a, b, c)}.
       */
      public static int hash(Object... values) {
          return Arrays.hashCode(values);
      }
  
      /**
       * Convenience wrapper for {@link Arrays#hashCode}, adding varargs.
       * This can be used to compute a hash code for an object's fields as follows:
       * {@code Objects.hash(a, b, c)}.
       */
      public static int hash(List<?> values) {
          if (values == null)
              return 0;
  
          int result = 1;
  
          for (Object element : values)
              result = 31 * result + (element == null ? 0 : element.hashCode());
  
          return result;
      }
  
      /**
       * Convenience wrapper for {@link Arrays#hashCode}, adding varargs.
       * This can be used to compute a hash code for an object's fields as follows:
       * {@code Objects.hash(a, b, c)}.
       */
      public static int absHash(Object... values) {
          return abs(Arrays.hashCode(values));
      }
  
      /**
       * Returns 0 for null or {@code o.hashCode()}.
       */
      public static int hashCode(Object o) {
          return (o == null) ? 0 : o.hashCode();
      }
  
      /**
       * @return secondary hash from object's hash
       */
      public static int secondaryHash(Object key) {
          return secondaryHash(key.hashCode());
      }
  
      /**
       * @return secondary hash from identity hash
       */
      public static int secondaryIdentityHash(Object key) {
          return secondaryHash(System.identityHashCode(key));
      }
  
      /**
       * @return secondary hash from hash
       */
      private static int secondaryHash(int h) {
          // Spread bits to regularize both segment and index locations,
          // using variant of single-word Wang/Jenkins hash.
          h += (h << 15) ^ 0xffffcd7d;
          h ^= (h >>> 10);
          h += (h << 3);
          h ^= (h >>> 6);
          h += (h << 2) + (h << 14);
          return h ^ (h >>> 16);
      }
  
      /**
       * Returns {@code o} if non-null, or throws {@code NullPointerException}.
       */
      public static <T> T requireNonNull(T o) {
          if (o == null) {
              throw new NullPointerException();
          }
          return o;
      }
  
      /**
       * Returns {@code o} if non-null, or throws {@code NullPointerException}
       * with the given detail message.
       */
      public static <T> T requireNonNull(T o, String message) {
          if (o == null) {
              throw new NullPointerException(message);
          }
          return o;
      }
  
      /**
       * Returns true if the string is null or 0-length.
       *
       * @param str the string to be examined
       * @return true if str is null or zero length
       */
      public static boolean isEmpty(CharSequence str) {
          if (str == null || str.length() == 0)
              return true;
          else
              return false;
      }
  
      /**
       * Returns false if the string is null or 0-length.
       *
       * @param str the string to be examined
       * @return false if str is null or zero length
       */
      public static boolean isNotEmpty(CharSequence str) {
          if (str == null || str.length() == 0)
              return false;
          else
              return true;
      }
  
      /**
       * Returns NULL for null or {@code o.toString()}.
       */
      public static String toString(Object o) {
          return (o == null) ? null : o.toString();
      }
  
      /**
       * Returns NULL for null or {@code o.toString()}.
       */
      public static List<String> toString(Object... o) {
          List<String> strings = new ArrayList<>();
          for (Object item : o) {
              strings.add(toString(item));
          }
          return strings;
      }
  
      /**
       * Returns NULL for null or {@code o.toString()}.
       */
      public static List<String> toString(Collection o) {
          List<String> strings = new ArrayList<>();
          for (Object item : o) {
              strings.add(toString(item));
          }
          return strings;
      }
  
      /**
       * Returns "null" for null or {@code o.toString()}.
       */
      public static String string(Object o) {
          return (o == null) ? "null" : o.toString();
      }
  
      /**
       * Returns "null" for null or {@code o.toString()}.
       */
      public static List<String> string(Object... o) {
          List<String> strings = new ArrayList<>();
          for (Object item : o) {
              strings.add(string(item));
          }
          return strings;
      }
  
      /**
       * Returns "null" for null or {@code o.toString()}.
       */
      public static List<String> string(Collection o) {
          List<String> strings = new ArrayList<>();
          for (Object item : o) {
              strings.add(toString(item));
          }
          return strings;
      }
  
      /**
       * Returns {@code nullString} for null or {@code o.toString()}.
       */
      public static String string(Object o, String nullString) {
          return (o == null) ? nullString : o.toString();
      }
  
      /**
       * NULL safe startsWith()
       */
      public static boolean startsWith(String string, String prefix) {
          return isEmpty(string) && isEmpty(prefix) || string != null && string.startsWith(prefix);
      }
  
      /**
       * NULL safe contains()
       */
      public static boolean contains(String string, String value) {
          return safe(string).contains(value);
      }
  
      /**
       * NULL safe replace()
       */
      public static String replace(String string, CharSequence target, CharSequence replacement) {
          return string == null ? null : string.replace(target, replacement);
      }
  
      /**
       * NULL safe replaceFirst()
       */
      public static String replaceFirst(String string, String regex, String replacement) {
          return string == null ? null : string.replaceFirst(regex, replacement);
      }
  
      /**
       * NULL safe replaceAll()
       */
      public static String replaceAll(String string, String regex, String replacement) {
          return string == null ? null : string.replaceAll(regex, replacement);
      }
  
      /**
       * NULL safe trim()
       */
      public static String trim(String string) {
          return string == null ? null : string.trim();
      }
  
      /**
       * NULL safe trim()
       */
      public static boolean isTrimmedEmpty(String string) {
          return string == null || string.trim().isEmpty();
      }
  
      /**
       * Returns a string containing the tokens joined.
       *
       * @param delimiter object to insert between {@code tokens}
       * @param tokens    an array objects to be joined
       */
      public static <T> List<T> insertBetween(T delimiter, List<T> tokens) {
          List<T> sb = new ArrayList<>();
          boolean firstTime = true;
          for (T token : tokens) {
              if (firstTime) {
                  firstTime = false;
              } else {
                  sb.add(delimiter);
              }
              sb.add(token);
          }
          return sb;
      }
  
      /**
       * Returns a string containing the tokens joined.
       *
       * @param list an array objects to be joined. Strings will be formed from
       *             the objects by calling object.toString().
       */
      public static CharSequence join(Object... list) {
          return join("", list);
      }
  
      /**
       * Returns a string containing the tokens joined by delimiters.
       *
       * @param tokens an array objects to be joined. Strings will be formed from
       *               the objects by calling object.toString().
       */
      public static String join(CharSequence delimiter, Object... tokens) {
          StringBuilder sb = new StringBuilder();
          boolean firstTime = true;
          for (Object token : tokens) {
              if (firstTime) {
                  firstTime = false;
              } else {
                  sb.append(delimiter);
              }
              sb.append(token);
          }
          return sb.toString();
      }
  
      /**
       * Returns a string containing the tokens joined by delimiters.
       *
       * @param tokens an array objects to be joined. Strings will be formed from
       *               the objects by calling object.toString() NULL items won't be added.
       */
      public static String joinNotNull(CharSequence delimiter, Object... tokens) {
          StringBuilder sb = new StringBuilder();
          boolean firstTime = true;
          for (Object token : tokens) {
              if (token != null) {
                  if (firstTime) {
                      firstTime = false;
                  } else {
                      sb.append(delimiter);
                  }
                  sb.append(token);
              }
          }
          return sb.toString();
      }
  
      /**
       * Returns a string containing the tokens joined by delimiters.
       *
       * @param tokens an array objects to be joined. Strings will be formed from
       *               the objects by calling object.toString() EMPTY items won't be added.
       */
      public static String joinNotEmpty(CharSequence delimiter, Object... tokens) {
          StringBuilder sb = new StringBuilder();
          boolean firstTime = true;
          for (Object token : tokens) {
              if (!isEmpty(toString(token))) {
                  if (firstTime) {
                      firstTime = false;
                  } else {
                      sb.append(delimiter);
                  }
                  sb.append(token);
              }
          }
          return sb.toString();
      }
  
  
      /**
       * Returns a string containing the tokens joined.
       *
       * @param tokens an array objects to be joined. Strings will be formed from
       *               the objects by calling object.toString().
       */
      public static CharSequence join(Iterable tokens) {
          return join("", tokens);
      }
  
      /**
       * Returns a string containing the tokens joined by delimiters.
       *
       * @param tokens an array objects to be joined. Strings will be formed from
       *               the objects by calling object.toString().
       */
      public static String join(CharSequence delimiter, Iterable tokens) {
          StringBuilder sb = new StringBuilder();
          boolean firstTime = true;
          for (Object token : tokens) {
              if (firstTime) {
                  firstTime = false;
              } else {
                  sb.append(delimiter);
              }
              sb.append(token);
          }
          return sb.toString();
      }
  
      /**
       * Returns a string containing the tokens joined by delimiters.
       *
       * @param tokens an array objects to be joined. Strings will be formed from
       *               the objects by calling object.toString() NULL items won't be added.
       */
      public static String joinNotNull(CharSequence delimiter, Iterable tokens) {
          StringBuilder sb = new StringBuilder();
          boolean firstTime = true;
          for (Object token : tokens) {
              if (token != null) {
                  if (firstTime) {
                      firstTime = false;
                  } else {
                      sb.append(delimiter);
                  }
                  sb.append(token);
              }
          }
          return sb.toString();
      }
  
      /**
       * Returns a string containing the tokens joined by delimiters.
       *
       * @param tokens an array objects to be joined. Strings will be formed from
       *               the objects by calling object.toString() EMPTY items won't be added.
       */
      public static String joinNotEmpty(CharSequence delimiter, Iterable tokens) {
          StringBuilder sb = new StringBuilder();
          boolean firstTime = true;
          for (Object token : tokens) {
              if (!isEmpty(toString(token))) {
                  if (firstTime) {
                      firstTime = false;
                  } else {
                      sb.append(delimiter);
                  }
                  sb.append(token);
              }
          }
          return sb.toString();
      }
  
      public static String firstLine(String string) {
          if (string == null) return null;
          final int nextLineIndex = string.indexOf('\n');
          if (nextLineIndex > 0) return string.substring(0, nextLineIndex);
          return string;
      }
  
      /**
       * Splits this strings using the supplied {@code regularExpression}.
       */
      public static List<String> splitAndClearEmpty(ArrayList<String> strings, String regularExpression) {
          ArrayList<String> result = new ArrayList<>();
          for (String string : strings) {
              result.addAll(splitAndClearEmpty(string, regularExpression));
          }
          return result;
      }
  
      /**
       * Splits this string using the supplied {@code regularExpression}.
       */
      public static List<String> splitAndClearEmpty(String string, String regularExpression) {
          ArrayList<String> strings = new ArrayList<>();
          final String[] split = string.split(regularExpression);
          if (split.length == 0) {
              strings.add(string);
          } else {
              strings.addAll(Arrays.asList(split));
          }
  
          Iterator<String> iterator = strings.iterator();
          while (iterator.hasNext()) {
              String item = iterator.next();
              if (item == null || item.trim().length() == 0) {
                  iterator.remove();
              }
          }
  
          return strings;
      }
  
      /**
       * Splits this strings using the supplied {@code regularExpression}.
       */
      public static String[] splitAndClear(String[] strings, String regularExpression) {
          ArrayList<String> result = new ArrayList<>();
          for (String string : strings) {
              result.addAll(splitAndClearEmpty(string, regularExpression));
          }
          return result.toArray(new String[result.size()]);
      }
  
      /**
       * Splits this string using the supplied {@code regularExpression}.
       */
      public static String[] splitAndClear(String string, String regularExpression) {
          ArrayList<String> strings = new ArrayList<>();
          final String[] split = string.split(regularExpression);
          if (split == null || split.length == 0) {
              strings.add(string);
          } else {
              strings.addAll(Arrays.asList(split));
          }
  
          Iterator<String> iterator = strings.iterator();
          while (iterator.hasNext()) {
              String item = iterator.next();
              if (item == null || item.trim().length() == 0) {
                  iterator.remove();
              }
          }
  
          return strings.toArray(new String[strings.size()]);
      }
  
      /**
       * Parse int value from string
       */
      public static int getIntValue(String intNumber) {
          try {
              return Integer.parseInt(intNumber);
          } catch (NumberFormatException e) {
              return 0;
          }
      }
  }