code: static MessageTuple convertMessageString(String messagePattern, Object... args) {
      Throwable throwable = null;
  
      if (args.length > 0 && args[args.length - 1] instanceof Throwable) {
        throwable = (Throwable) args[args.length - 1];
      }
  
      if (!messagePattern.contains(PLACEHOLDER)) {
        return new MessageTuple(messagePattern, throwable);
      }
  
      StringBuilder sb = new StringBuilder();
  
      int placeholderCount = 0;
      int k = 0;
      while (messagePattern.indexOf(PLACEHOLDER, k) != -1) {
        int l = messagePattern.indexOf(PLACEHOLDER, k);
        if (l == 0 || messagePattern.charAt(l - 1) != ESCAPE_SIGN) {
          sb.append(messagePattern.substring(k, l));
          sb.append("%s");
          k = l + 2;
          placeholderCount++;
        } else {
          sb.append(messagePattern.substring(k, l + 3));
          k = l + 3;
        }
      }
  
      if (k < messagePattern.length()) {
        sb.append(messagePattern.substring(k));
      }
  
      int argsLength = (throwable != null) ? args.length - 1 : args.length;
  
      if (argsLength != placeholderCount) {
        throw new IllegalArgumentException("Placeholder count don't matches argument count (placeholders: "
            + placeholderCount + ", arguments: " + argsLength + ")");
      }
  
      Object[] trimmed = new Object[argsLength];
      System.arraycopy(args, 0, trimmed, 0, argsLength);
  
      return new MessageTuple(String.format(sb.toString(), trimmed), throwable);
    }
docs: convert the message and the arguments and convert everything in a {@code MessageTuple}. The {@code MessageTuple}
  contains the message and the optional throwable.
  
  @param messagePattern
  the message with optional placeholders
  @param args
  the arguments for the placeholders and the optional Throwable
  @return MessageTuple that contains the Throwable and the formatted message