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.
1 comment:
I have created a subclass of the DecimalFormat, where I override:
/* (non-Javadoc)
* @see java.text.Format#parseObject(java.lang.String)
*/
@Override
public Object parseObject(String source) throws ParseException {
ParsePosition pos = new ParsePosition(0);
Object result = parseObject(source, pos);
int index = pos.getIndex();
if (index == 0) { // nothing has been parsed
throw new ParseException("Unparseable number: \"" + source + "\"",
pos.getErrorIndex());
}
int length = source.length();
for (; index < length; index++) {
if (!Character.isWhitespace(source.charAt(index))) {
// non-white space before end - error
throw new ParseException("Unparseable number: \"" + source + "\"",
index);
}
}
return result;
}
/* (non-Javadoc)
* @see java.text.NumberFormat#parse(java.lang.String)
*/
@Override
public Number parse(String source) throws ParseException {
ParsePosition pos = new ParsePosition(0);
Number result = parse(source, pos);
int index = pos.getIndex();
if (index == 0) { // nothing has been parsed
throw new ParseException("Unparseable number: \"" + source + "\"",
pos.getErrorIndex());
}
int length = source.length();
for (; index < length; index++) {
if (!Character.isWhitespace(source.charAt(index))) {
// non-white space before end - error
throw new ParseException("Unparseable number: \"" + source + "\"",
index);
}
}
return result;
}
and then use it as
new JFormattedTextField(new MyDecimalFormat());
Post a Comment