Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the sample on java.util.Formattable incorrect?

Using the example given for java.util.Formattable (modified to actually set values in the constructor), things seem to work mostly correctly:

import java.nio.CharBuffer;
import java.util.Formatter;
import java.util.Formattable;
import java.util.Locale;
import static java.util.FormattableFlags.*;

public class StockName implements Formattable {
    private String symbol, companyName, frenchCompanyName;
    public StockName(String symbol, String companyName,
                     String frenchCompanyName) {
        this.symbol = symbol;
        this.companyName = companyName;
        this.frenchCompanyName = frenchCompanyName;
    }

    public void formatTo(Formatter fmt, int f, int width, int precision) {
        StringBuilder sb = new StringBuilder();

        // decide form of name
        String name = companyName;
        if (fmt.locale().equals(Locale.FRANCE))
            name = frenchCompanyName;
        boolean alternate = (f & ALTERNATE) == ALTERNATE;
        boolean usesymbol = alternate || (precision != -1 && precision < 10);
        String out = (usesymbol ? symbol : name);

        // apply precision
        if (precision == -1 || out.length() < precision) {
            // write it all
            sb.append(out);
        } else {
            sb.append(out.substring(0, precision - 1)).append('*');
        }

        // apply width and justification
        int len = sb.length();
        if (len < width)
            for (int i = 0; i < width - len; i++)
                if ((f & LEFT_JUSTIFY) == LEFT_JUSTIFY)
                    sb.append(' ');
                else
                    sb.insert(0, ' ');

        fmt.format(sb.toString());
    }

    public String toString() {
        return String.format("%s - %s", symbol, companyName);
    }
}

Running

System.out.printf("%s", new StockName("HUGE", "Huge Fruit, Inc.", "Fruit Titanesque, Inc."));

prints Huge Fruit, Inc. as expected.

However, the following does not work:

System.out.printf("%s", new StockName("PERC", "%Company, Inc.", "Fruit Titanesque, Inc."));

It throws a java.util.MissingFormatArgumentException:

Exception in thread "main" java.util.MissingFormatArgumentException: Format specifier '%C'
        at java.util.Formatter.format(Formatter.java:2519)
        at java.util.Formatter.format(Formatter.java:2455)
        at StockName.formatTo(FormattableTest.java:44)
        at java.util.Formatter$FormatSpecifier.printString(Formatter.java:2879)
        at java.util.Formatter$FormatSpecifier.print(Formatter.java:2763)
        at java.util.Formatter.format(Formatter.java:2520)
        at java.io.PrintStream.format(PrintStream.java:970)
        at java.io.PrintStream.printf(PrintStream.java:871)
        at FormattableTest.main(FormattableTest.java:55)

The sample uses Formatter.format to add the text, while format is supposed to format a format string. This causes things to break when the text that's supposed to be appended contains a percent.

How should I deal with this in formatTo? Should I manually write to the formatter's Appendable (formatter.out().append(text), which can throw an IOException somehow)? Should I attempt to escape the format string (something like formatter.format(text.replace("%","%%")), though that may not be enough)? Should I pass a simple format string to the formatter (formatter.format("%s", text), but that seems redundant)? All of these should work, but what is the correct way semantically?

To clarify, in this hypothetical situation, the parameters given to StockName are user-controlled and can be arbitrary; I don't have exact control over them (and I can't disallow input of %). However, I am able to edit StockName.formatTo.

like image 222
Pokechu22 Avatar asked Mar 13 '17 00:03

Pokechu22


Video Answer


2 Answers

It is actually simple. You escape the percent characters only during formatting, not in the original property:

  // apply precision
  if (precision == -1 || out.length() < precision) {
    // write it all
    sb.append(out.replaceAll("%", "%%"));
  }
  else {
    sb.append(out.substring(0, precision - 1).replaceAll("%", "%%")).append('*');
  }

Then if you do this:

StockName stockName = new StockName("HUGE", "%Huge Fruit, Inc.", "Fruit Titanesque, Inc.");
System.out.printf("%s%n", stockName);
System.out.printf("%s%n", stockName.toString());
System.out.printf("%#s%n", stockName);
System.out.printf("%-10.8s%n", stockName);
System.out.printf("%.12s%n", stockName);
System.out.printf(Locale.FRANCE, "%25s%n", stockName);

The output looks like this:

%Huge Fruit, Inc.
HUGE - %Huge Fruit, Inc.
HUGE
HUGE      
%Huge Fruit*
   Fruit Titanesque, Inc.
like image 60
kriegaex Avatar answered Oct 18 '22 12:10

kriegaex


If you want print percentage symbol %, you must escape it by double writing, e.g.

System.out.printf("%s", new StockName("PERC", "%%Company, Inc.", "Fruit Titanesque, Inc."));

this will print %Company, Inc.

like image 21
Yu Jiaao Avatar answered Oct 18 '22 12:10

Yu Jiaao