8333755: NumberFormat integer only parsing breaks when format has suffix

Reviewed-by: naoto
This commit is contained in:
Justin Lu 2024-06-26 17:10:09 +00:00
parent b5d589623c
commit bffc8484c3
5 changed files with 169 additions and 101 deletions

View file

@ -2150,10 +2150,7 @@ public class DecimalFormat extends NumberFormat {
* #getGroupingSize()} is not adhered to
* <li> {@link #isGroupingUsed()} returns {@code false}, and the grouping
* symbol is found
* <li> {@link #isParseIntegerOnly()} returns {@code true}, and the decimal
* separator is found
* <li> {@link #isGroupingUsed()} returns {@code true} and {@link
* #isParseIntegerOnly()} returns {@code false}, and the grouping
* <li> {@link #isGroupingUsed()} returns {@code true} and the grouping
* symbol occurs after the decimal separator
* <li> Any other characters are found, that are not the expected symbols,
* and are not digits that occur within the numerical portion
@ -2379,7 +2376,8 @@ public class DecimalFormat extends NumberFormat {
// position will serve as new index when success, otherwise it will
// serve as errorIndex when failure
position = subparseNumber(text, position, digits, true, isExponent, status);
NumericPosition pos = subparseNumber(text, position, digits, true, isExponent, status);
position = pos.fullPos;
// First character after the prefix was un-parseable, should
// fail regardless if lenient or strict.
@ -2422,9 +2420,15 @@ public class DecimalFormat extends NumberFormat {
return false;
}
// No failures, thus increment the index by the suffix
parsePosition.index = position +
(gotPositive ? positiveSuffix.length() : negativeSuffix.length());
// When parsing integer only, index should be int pos
// If intPos is 0, the entire value was integer
if (isParseIntegerOnly() && pos.intPos > 0) {
parsePosition.index = pos.intPos;
} else {
// increment the index by the suffix
parsePosition.index = position +
(gotPositive ? positiveSuffix.length() : negativeSuffix.length());
}
} else {
parsePosition.index = position;
}
@ -2437,6 +2441,19 @@ public class DecimalFormat extends NumberFormat {
return true;
}
/**
* NumericPosition is a helper record class that stores two indices of interest.
* {@code fullPos} is either the first unparseable character or -1 in case
* of no valid number parsed. {@code intPos} reflects the position of
* a parsed decimal symbol, if one exists. When parsing with {@code isParseIntegerOnly()},
* {@code fullPos} is used to match the suffix, and reset the {@code ParsePosition}
* index to {@code intPos}.
*
* @param fullPos an index that reflects the full traversal of the numerical String
* @param intPos an index that reflects the position of a parsed decimal symbol.
*/
record NumericPosition(int fullPos, int intPos) {}
/**
* Parses a number from the given {@code text}. The text is parsed
* beginning at {@code position}, until an unparseable character is seen.
@ -2449,14 +2466,15 @@ public class DecimalFormat extends NumberFormat {
* @param status upon return contains boolean status flags indicating
* whether the value is infinite and whether it is
* positive
* @return returns the position of the first unparseable character or
* -1 in case of no valid number parsed
* @return returns a {@code NumericPosition} that stores both a full
* traversal index, and an int only index.
*/
int subparseNumber(String text, int position,
DigitList digits, boolean checkExponent,
boolean isExponent, boolean[] status) {
NumericPosition subparseNumber(String text, int position,
DigitList digits, boolean checkExponent,
boolean isExponent, boolean[] status) {
// process digits or Inf, find decimal position
status[STATUS_INFINITE] = false;
int intIndex = 0;
if (!isExponent && text.regionMatches(position, symbols.getInfinity(), 0,
symbols.getInfinity().length())) {
position += symbols.getInfinity().length();
@ -2516,7 +2534,7 @@ public class DecimalFormat extends NumberFormat {
if (parseStrict && isGroupingUsed() && position == startPos + groupingSize
&& prevSeparatorIndex == -groupingSize && !sawDecimal
&& digit >= 0 && digit <= 9) {
return position;
return new NumericPosition(position, intIndex);
}
if (digit == 0) {
@ -2538,37 +2556,44 @@ public class DecimalFormat extends NumberFormat {
--digits.decimalAt;
} else {
++digitCount;
digits.append((char)(digit + '0'));
if (!sawDecimal || !isParseIntegerOnly()) {
digits.append((char)(digit + '0'));
}
}
} else if (digit > 0 && digit <= 9) { // [sic] digit==0 handled above
sawDigit = true;
++digitCount;
digits.append((char)(digit + '0'));
if (!sawDecimal || !isParseIntegerOnly()) {
digits.append((char) (digit + '0'));
}
// Cancel out backup setting (see grouping handler below)
backup = -1;
} else if (!isExponent && ch == decimal) {
// Check grouping size on decimal separator
if (parseStrict && isGroupingViolation(position, prevSeparatorIndex)) {
return groupingViolationIndex(position, prevSeparatorIndex);
return new NumericPosition(
groupingViolationIndex(position, prevSeparatorIndex), intIndex);
}
// If we're only parsing integers, or if we ALREADY saw the
// decimal, then don't parse this one.
if (isParseIntegerOnly() || sawDecimal) {
if (sawDecimal) {
break;
}
intIndex = position;
digits.decimalAt = digitCount; // Not digits.count!
sawDecimal = true;
} else if (!isExponent && ch == grouping && isGroupingUsed()) {
if (parseStrict) {
// text should not start with grouping when strict
if (position == startPos) {
return startPos;
return new NumericPosition(startPos, intIndex);
}
// when strict, fail if grouping occurs after decimal OR
// current group violates grouping size
if (sawDecimal || (isGroupingViolation(position, prevSeparatorIndex))) {
return groupingViolationIndex(position, prevSeparatorIndex);
return new NumericPosition(
groupingViolationIndex(position, prevSeparatorIndex), intIndex);
}
prevSeparatorIndex = position; // track previous
} else {
@ -2621,7 +2646,8 @@ public class DecimalFormat extends NumberFormat {
// "1,234%" and "1,234" both end with pos = 5, since '%' breaks
// the loop before incrementing position. In both cases, check
// should be done at pos = 4
return groupingViolationIndex(position - 1, prevSeparatorIndex);
return new NumericPosition(
groupingViolationIndex(position - 1, prevSeparatorIndex), intIndex);
}
}
@ -2636,8 +2662,9 @@ public class DecimalFormat extends NumberFormat {
digits.decimalAt = digitCount; // Not digits.count!
}
// Adjust for exponent, if any
if (exponent != 0) {
// If parsing integer only, adjust exponent if it occurs
// in integer portion, otherwise ignore it
if (!sawDecimal || !isParseIntegerOnly()) {
digits.decimalAt = shiftDecimalAt(digits.decimalAt, exponent);
}
@ -2646,10 +2673,10 @@ public class DecimalFormat extends NumberFormat {
// parse "$" with pattern "$#0.00". (return index 0 and error
// index 1).
if (!sawDigit && digitCount == 0) {
return -1;
return new NumericPosition(-1, intIndex);
}
}
return position;
return new NumericPosition(position, intIndex);
}
// Calculate the final decimal position based off the exponent value
@ -2917,7 +2944,8 @@ public class DecimalFormat extends NumberFormat {
* have '{@code U+2030}'.
*
* <P>Example: with multiplier 100, 1.23 is formatted as "123", and
* "123" is parsed into 1.23.
* "123" is parsed into 1.23. If {@code isParseIntegerOnly()} returns {@code true},
* "123" is parsed into 1.
*
* @param newValue the new multiplier
* @see #getMultiplier