Wednesday, June 21, 2006

Answer: Using JFormattedTextField for integers

I wrote earlier about using JFormattedTextField for integers and the difference between using an initial value of Integer (rounds input) v. BigInteger (rejects invalid input). I also wrote that I did not understand why there was a difference.

I now know what happened.

The culprit for handling Integer turns out to be NumberFormat and friends. The rounding behavior is not numerical rounding at all; it is truncating the input text.

The parser for the input stops after finding valid input and returns a parsed value in stringToValue(String):Object. This discards any trailing text, which in the case of parsing an integer is everything from the decimal point forward. The same behavior can be seen with a date parser: Jun 21, 2006xxx parses to Jun 21, 2006 and the xxx is discarded.

The solution to enforce correct input even with trailing garbage is cumbersome: initialize JFormattedTextField with a custom formatter and check that parsed input deparses back cleanly:

new JFormattedTextField(new Integer(0))

becomes:

new JFormattedTextField(new NumberFormatter(
        NumberFormat.getIntegerInstance()) {
    @Override
    public Object stringToValue(final String text)
            throws ParseException {
        // Throws if the input is corrupt from the start
        final Object parsed = super.stringToValue(text);
        final String deparsed = valueToString(parsed);

        // Throws if there is no clean roundtrip,
        // such as trailing garbage characters
        // For date parsing, etc., consider equalsIgnoreCase
        if (!deparsed.equals(text))
            throw new ParseException(text, deparsed.length());

        return parsed;
    }
}) {
    {
        // Initialize to the original starting value
        setValue(new Integer(0));
    }
}

(Yes, the anonymous instance syntax is awkward here.)

That's a lot of work to achieve what seems like a simple goal: actually valid input for a JFormattedTextField.

UPDATE: Given the hoops to jump through for this more "correct" solution, I think I'll stick to new JFormattedTextField(new BigInteger(0)) for now, although that does nothing to help with other input types such as dates.

Post a Comment