Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get number of placeholders in Formatter.format() String

In a Java program I am using a String with Formatter.format() function, which I get from server. I cannot be sure that String to be formatted has placeholder or a valid number of them. If it the String is not as expected I would like to throw an exception - or log it somehow.

At this point I don't care of what type of placeholders are (String, Integer,...), I would just like to get number of expected parameters for each String.

What is the easiest way to achieve this? One way could be to use a regex, but I am thinking if there is something more convenient - a built in function for example.

Here are some examples:

Example String | number of placeholders:
%d of %d          | 2
This is my %s     | 1
A simple content. | 0
This is 100%      | 0
Hi! My name is %s and I have %d dogs and a %d cats. | 3

EDIT: Formatter.format() throws an exception if there are not enough provided parameters. There is a possibility that I get a String without placeholders. In this case, even if I provide paramters (will be omitted), no exception will be thrown (eventhough I would like to throw one) only that String value will be returned. I need to report the error to server.

like image 224
urgas9 Avatar asked Feb 07 '23 22:02

urgas9


2 Answers

You could do it with a regular expression that defines the format of a placeholder to count the total amount of matches in your String.

// %[argument_index$][flags][width][.precision][t]conversion
String formatSpecifier
    = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
// The pattern that defines a placeholder
Pattern pattern = Pattern.compile(formatSpecifier);
// The String to test
String[] values = {
    "%d of %d",
    "This is my %s", 
    "A simple content.", 
    "This is 100%", "Hi! My name is %s and I have %d dogs and a %d cats."
};
// Iterate over the Strings to test
for (String value : values) {
    // Build the matcher for a given String
    Matcher matcher = pattern.matcher(value);
    // Count the total amount of matches in the String
    int counter = 0;
    while (matcher.find()) {
        counter++;
    }
    // Print the result
    System.out.printf("%s=%d%n", value, counter);
}

Output:

%d of %d=2
This is my %s=1
A simple content.=0
This is 100%=0
Hi! My name is %s and I have %d dogs and a %d cats.=3
like image 52
Nicolas Filotto Avatar answered Feb 09 '23 11:02

Nicolas Filotto


I have adapted the code of String.format(). Here is the result :

private static final String formatSpecifier
    = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";

private static Pattern fsPattern = Pattern.compile(formatSpecifier);

private static int parse(String s) {
  int count = 0;
  Matcher m = fsPattern.matcher(s);
  for (int i = 0, len = s.length(); i < len; ) {
    if (m.find(i)) {
      // Anything between the start of the string and the beginning
      // of the format specifier is either fixed text or contains
      // an invalid format string.
      if (m.start() != i) {
        // Make sure we didn't miss any invalid format specifiers
        checkText(s, i, m.start());
        // Assume previous characters were fixed text
      }

      count++;
      i = m.end();
    } else {
      // No more valid format specifiers.  Check for possible invalid
      // format specifiers.
      checkText(s, i, len);
      // The rest of the string is fixed text
      break;
    }
  }
  return count;
}

private static void checkText(String s, int start, int end) {
  for (int i = start; i < end; i++) {
    // Any '%' found in the region starts an invalid format specifier.
    if (s.charAt(i) == '%') {
      char c = (i == end - 1) ? '%' : s.charAt(i + 1);
      throw new UnknownFormatConversionException(String.valueOf(c));
    }
  }
}

Test :

public static void main(String[] args) {
  System.out.println(parse("Hello %s, My name is %s. I am %d years old."));
}

Output :

3

like image 27
Arnaud Denoyelle Avatar answered Feb 09 '23 11:02

Arnaud Denoyelle