private static Validator @NotNull [] decode(String formatString, int argumentCount, boolean isPrefix, boolean noVerify) {
    final ArrayList<Validator> parameters = new ArrayList<>();

    final Matcher matcher = fsPattern.matcher(formatString);
    boolean previousAllowed = false;
    int implicit = 0;
    int pos = 0;
    int i = 0;
    while (matcher.find(i)) {
      final int start = matcher.start();
      if (start != i) {
        checkText(formatString.substring(i, start));
      }
      i = matcher.end();
      boolean isAllVerifier = false;
      //theoretically, it could be a correct specifier, divided into several parts, for example: "%1$t" + "Y"
      //it is better to add ALL_VERIFIER
      if (noVerify || isPrefix && i == formatString.length()) {
        isAllVerifier = true;
      }
      final String specifier = matcher.group();
      final String posSpec = matcher.group("posSpec");
      final String flags = matcher.group("flags");
      final String width = matcher.group("width");
      final String precision = matcher.group("precision");
      final String dateSpec = matcher.group("dateSpec");
      @NonNls final String conversion = matcher.group("conversion");

      int flagBits = 0;
      for (int j = 0; j < flags.length(); j++) {
        final char flag = flags.charAt(j);
        final int bit = flag(flag);
        if (bit == -1) {
          throw new IllegalFormatException(InspectionGadgetsBundle.message("format.string.error.unexpected.flag", flag, specifier));
        }
        if ((flagBits | bit) == flagBits) {
          throw new IllegalFormatException(InspectionGadgetsBundle.message("format.string.error.duplicate.flag", flag, specifier));
        }
        flagBits |= bit;
      }

      // check this first because it should not affect "implicit"
      if ("n".equals(conversion)) {
        // no flags allowed
        checkFlags(flagBits, 0, specifier);
        if (!StringUtil.isEmpty(width)) {
          throw new IllegalFormatException(InspectionGadgetsBundle.message("format.string.error.width.not.allowed", width, specifier));
        }
        checkNoPrecision(precision, specifier);
        continue;
      }
      else if ("%".equals(conversion)) { // literal '%'
        checkFlags(flagBits, LEFT_JUSTIFY, specifier);
        checkNoPrecision(precision, specifier);
        continue;
      }

      if (posSpec != null) {
        if (isAllBitsSet(flagBits, PREVIOUS)) {
          throw new IllegalFormatException(
            InspectionGadgetsBundle.message("format.string.error.unnecessary.position.specifier", posSpec, specifier));
        }
        final String num = posSpec.substring(0, posSpec.length() - 1);
        pos = Integer.parseInt(num) - 1;
        if (pos < 0) {
          throw new IllegalFormatException(
            InspectionGadgetsBundle.message("format.string.error.illegal.position.specifier", posSpec, specifier));
        }
        previousAllowed = true;
      }
      else if (isAllBitsSet(flagBits, PREVIOUS)) {
        // reuse last pos
        if (!previousAllowed) {
          throw new IllegalFormatException(InspectionGadgetsBundle.message("format.string.error.previous.element.not.found", specifier));
        }
      }
      else {
        previousAllowed = true;
        pos = implicit++;
      }

      final Validator allowed;
      if (isAllVerifier) {
        allowed = new AllValidatorWithRange(TextRange.create(matcher.start(), matcher.end()),
                                            new Spec(posSpec, flags, width, precision, dateSpec, conversion));
      }
      else if (dateSpec != null) {  // a t or T
        checkFlags(flagBits, LEFT_JUSTIFY | PREVIOUS, specifier);
        DateTimeConversionType dateTimeConversionType = getDateTimeConversionType(conversion.charAt(0));
        if (dateTimeConversionType == DateTimeConversionType.UNKNOWN) {
          throw new IllegalFormatException(
            InspectionGadgetsBundle.message("format.string.error.unknown.conversion", dateSpec + conversion));
        }
        checkNoPrecision(precision, specifier);
        allowed = new DateValidator(specifier, dateTimeConversionType);
      }
      else {
        switch (conversion.charAt(0)) {
          case 'b', 'B', 'h', 'H' -> { // boolean (general); Integer hex string (general)
            checkFlags(flagBits, LEFT_JUSTIFY | PREVIOUS, specifier);
            allowed = ALL_VALIDATOR;
          }
          case 's', 'S' -> { // formatted string (general)
            checkFlags(flagBits, LEFT_JUSTIFY | ALTERNATE | PREVIOUS, specifier);
            allowed = (flagBits & ALTERNATE) != 0 ? new FormattableValidator(specifier) : ALL_VALIDATOR;
          }
          case 'c', 'C' -> { // unicode character
            checkFlags(flagBits, LEFT_JUSTIFY | PREVIOUS, specifier);
            checkNoPrecision(precision, specifier);
            allowed = new CharValidator(specifier);
          }
          case 'd' -> { // decimal integer
            checkFlags(flagBits, ~ALTERNATE, specifier);
            checkNoPrecision(precision, specifier);
            allowed = new IntValidator(specifier);
          }
          case 'o', 'x', 'X' -> { // octal integer, hexadecimal integer
            checkFlags(flagBits, ~(PLUS | LEADING_SPACE | GROUP), specifier);
            checkNoPrecision(precision, specifier);
            allowed = new IntValidator(specifier);
          }
          case 'a', 'A' -> { // hexadecimal floating-point number
            checkFlags(flagBits, ~(PARENTHESES | GROUP), specifier);
            allowed = new FloatValidator(specifier);
          }
          case 'e', 'E' -> { // floating point -> decimal number in computerized scientific notation
            checkFlags(flagBits, ~GROUP, specifier);
            allowed = new FloatValidator(specifier);
          }
          case 'g', 'G' -> { // scientific notation
            checkFlags(flagBits, ~ALTERNATE, specifier);
            allowed = new FloatValidator(specifier);
          }
          case 'f' -> // floating point -> decimal number
            allowed = new FloatValidator(specifier);
          default -> throw new IllegalFormatException(InspectionGadgetsBundle.message("format.string.error.unknown.conversion", specifier));
        }
      }
      if (precision != null && precision.length() < 2) {
        throw new IllegalFormatException(InspectionGadgetsBundle.message("format.string.error.invalid.precision", specifier));
      }
      if (isAllBitsSet(flagBits, LEADING_SPACE | PLUS)) {
        throw new IllegalFormatException(
          InspectionGadgetsBundle.message("format.string.error.illegal.flag.combination", ' ', '+', specifier));
      }
      if (isAllBitsSet(flagBits, LEFT_JUSTIFY | ZERO_PAD)) {
        throw new IllegalFormatException(
          InspectionGadgetsBundle.message("format.string.error.illegal.flag.combination", '-', '0', specifier));
      }
      if (StringUtil.isEmpty(width)) {
        if (isAllBitsSet(flagBits, LEFT_JUSTIFY)) {
          throw new IllegalFormatException(InspectionGadgetsBundle.message("format.string.error.left.justify.no.width", specifier));
        }
        if (isAllBitsSet(flagBits, ZERO_PAD)) {
          throw new IllegalFormatException(InspectionGadgetsBundle.message("format.string.error.zero.padding.no.width", specifier));
        }
      }
      storeValidator(allowed, pos, parameters, argumentCount);
    }
    if (i < formatString.length()) {
      String endString = formatString.substring(i);
      if (!isPrefix) {
        checkText(endString);
      }
      else {
        //only case, when fsPattern doesn't find a specifier, is when there is no any conversion.
        //add "s" as a random reasonable conversion
        String suggestedString = endString + "s";
        Matcher endMatcher = fsPattern.matcher(suggestedString);
        if (!endMatcher.find() || endMatcher.end() != suggestedString.length()) {
          checkText(endString);
        }
      }
    }

    return parameters.toArray(new Validator[0]);
  }
private static Validator @NotNull [] decode(String formatString, int argumentCount, boolean isPrefix, boolean noVerify)