mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
8327640: Allow NumberFormat strict parsing
Reviewed-by: naoto
This commit is contained in:
parent
2ede14335a
commit
941bee197f
12 changed files with 1569 additions and 103 deletions
|
@ -586,6 +586,18 @@ public class ChoiceFormat extends NumberFormat {
|
|||
return Double.valueOf(bestNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStrict() {
|
||||
throw new UnsupportedOperationException(
|
||||
"ChoiceFormat does not utilize leniency when parsing");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrict(boolean strict) {
|
||||
throw new UnsupportedOperationException(
|
||||
"ChoiceFormat does not utilize leniency when parsing");
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the least double greater than {@code d}.
|
||||
* If {@code NaN}, returns same value.
|
||||
|
|
|
@ -348,6 +348,15 @@ public final class CompactNumberFormat extends NumberFormat {
|
|||
*/
|
||||
private String pluralRules = "";
|
||||
|
||||
/**
|
||||
* True if this {@code CompactNumberFormat} will parse numbers with strict
|
||||
* leniency.
|
||||
*
|
||||
* @serial
|
||||
* @since 23
|
||||
*/
|
||||
private boolean parseStrict = false;
|
||||
|
||||
/**
|
||||
* The map for plural rules that maps LDML defined tags (e.g. "one") to
|
||||
* its rule.
|
||||
|
@ -1498,22 +1507,40 @@ public final class CompactNumberFormat extends NumberFormat {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses a compact number from a string to produce a {@code Number}.
|
||||
* {@inheritDoc NumberFormat}
|
||||
* <p>
|
||||
* The method attempts to parse text starting at the index given by
|
||||
* {@code pos}.
|
||||
* If parsing succeeds, then the index of {@code pos} is updated
|
||||
* to the index after the last character used (parsing does not necessarily
|
||||
* use all characters up to the end of the string), and the parsed
|
||||
* number is returned. The updated {@code pos} can be used to
|
||||
* indicate the starting point for the next call to this method.
|
||||
* If an error occurs, then the index of {@code pos} is not
|
||||
* changed, the error index of {@code pos} is set to the index of
|
||||
* the character where the error occurred, and {@code null} is returned.
|
||||
* <p>
|
||||
* The value is the numeric part in the given text multiplied
|
||||
* The returned value is the numeric part in the given text multiplied
|
||||
* by the numeric equivalent of the affix attached
|
||||
* (For example, "K" = 1000 in {@link java.util.Locale#US US locale}).
|
||||
* <p>
|
||||
* A {@code CompactNumberFormat} can match
|
||||
* the default prefix/suffix to a compact prefix/suffix interchangeably.
|
||||
* <p>
|
||||
* Parsing can be done in either a strict or lenient manner, by default it is lenient.
|
||||
* <p>
|
||||
* Parsing fails when <b>lenient</b>, if the prefix and/or suffix are non-empty
|
||||
* and cannot be found due to parsing ending early, or the first character
|
||||
* after the prefix cannot be parsed.
|
||||
* <p>
|
||||
* Parsing fails when <b>strict</b>, if in {@code text},
|
||||
* <ul>
|
||||
* <li> The default or a compact prefix is not found. For example, the {@code
|
||||
* Locale.US} currency format prefix: "{@code $}"
|
||||
* <li> The default or a compact suffix is not found. For example, a {@code Locale.US}
|
||||
* {@link NumberFormat.Style#SHORT} compact suffix: "{@code K}"
|
||||
* <li> {@link #isGroupingUsed()} returns {@code false}, and the grouping
|
||||
* symbol is found
|
||||
* <li> {@link #isGroupingUsed()} returns {@code true}, and {@link
|
||||
* #getGroupingSize()} is not adhered to
|
||||
* <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
|
||||
* 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
|
||||
* </ul>
|
||||
* <p>
|
||||
* The subclass returned depends on the value of
|
||||
* {@link #isParseBigDecimal}.
|
||||
* <ul>
|
||||
|
@ -1553,7 +1580,6 @@ public final class CompactNumberFormat extends NumberFormat {
|
|||
* @return the parsed value, or {@code null} if the parse fails
|
||||
* @throws NullPointerException if {@code text} or
|
||||
* {@code pos} is null
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public Number parse(String text, ParsePosition pos) {
|
||||
|
@ -1661,6 +1687,13 @@ public final class CompactNumberFormat extends NumberFormat {
|
|||
return cnfMultiplier;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Neither prefix match, should fail now (strict or lenient), before
|
||||
// position is incremented by subparseNumber(). Otherwise, an empty
|
||||
// prefix could pass through here, position gets incremented by the
|
||||
// numerical portion, and return a faulty errorIndex and index later.
|
||||
pos.errorIndex = position;
|
||||
return null;
|
||||
}
|
||||
|
||||
digitList.setRoundingMode(getRoundingMode());
|
||||
|
@ -1705,6 +1738,11 @@ public final class CompactNumberFormat extends NumberFormat {
|
|||
status, gotPositive, gotNegative, num);
|
||||
|
||||
if (multiplier.longValue() == -1L) {
|
||||
if (parseStrict) {
|
||||
// When strict, if -1L was returned, index should be
|
||||
// reset to the original index to ensure failure
|
||||
pos.index = oldStart;
|
||||
}
|
||||
return null;
|
||||
} else if (multiplier.longValue() != 1L) {
|
||||
cnfMultiplier = multiplier;
|
||||
|
@ -1886,7 +1924,10 @@ public final class CompactNumberFormat extends NumberFormat {
|
|||
|
||||
if (prefix.equals(matchedPrefix)
|
||||
|| matchedPrefix.equals(defaultPrefix)) {
|
||||
return matchAffix(text, position, suffix, defaultSuffix, matchedSuffix);
|
||||
// Suffix must match exactly when strict
|
||||
return parseStrict ? matchAffix(text, position, suffix, defaultSuffix, matchedSuffix)
|
||||
&& text.length() == position + suffix.length()
|
||||
: matchAffix(text, position, suffix, defaultSuffix, matchedSuffix);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -1924,10 +1965,11 @@ public final class CompactNumberFormat extends NumberFormat {
|
|||
String positiveSuffix = getAffix(true, false, false, compactIndex, num);
|
||||
String negativeSuffix = getAffix(true, false, true, compactIndex, num);
|
||||
|
||||
// Do not break if a match occur; there is a possibility that the
|
||||
// When lenient, do not break if a match occurs; there is a possibility that the
|
||||
// subsequent affixes may match the longer subsequence in the given
|
||||
// string.
|
||||
// For example, matching "3Mdx" with "M", "Md" should match with "Md"
|
||||
// string. For example, matching "3Mdx" with "M", "Md" should match
|
||||
// with "Md". However, when strict, break as the match should be exact,
|
||||
// and thus no need to check for a longer suffix.
|
||||
boolean match = matchPrefixAndSuffix(text, position, positivePrefix, matchedPrefix,
|
||||
defaultDecimalFormat.getPositivePrefix(), positiveSuffix,
|
||||
matchedPosSuffix, defaultDecimalFormat.getPositiveSuffix());
|
||||
|
@ -1935,6 +1977,10 @@ public final class CompactNumberFormat extends NumberFormat {
|
|||
matchedPosIndex = compactIndex;
|
||||
matchedPosSuffix = positiveSuffix;
|
||||
gotPos = true;
|
||||
if (parseStrict) {
|
||||
// when strict, exit early with exact match, same for negative
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match = matchPrefixAndSuffix(text, position, negativePrefix, matchedPrefix,
|
||||
|
@ -1944,29 +1990,39 @@ public final class CompactNumberFormat extends NumberFormat {
|
|||
matchedNegIndex = compactIndex;
|
||||
matchedNegSuffix = negativeSuffix;
|
||||
gotNeg = true;
|
||||
if (parseStrict) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Suffix in the given text does not match with the compact
|
||||
// patterns suffixes; match with the default suffix
|
||||
// When strict, text must end with the default suffix
|
||||
if (!gotPos && !gotNeg) {
|
||||
String positiveSuffix = defaultDecimalFormat.getPositiveSuffix();
|
||||
String negativeSuffix = defaultDecimalFormat.getNegativeSuffix();
|
||||
if (text.regionMatches(position, positiveSuffix, 0,
|
||||
positiveSuffix.length())) {
|
||||
boolean containsPosSuffix = text.regionMatches(position,
|
||||
positiveSuffix, 0, positiveSuffix.length());
|
||||
boolean endsWithPosSuffix = containsPosSuffix && text.length() ==
|
||||
position + positiveSuffix.length();
|
||||
if (parseStrict ? endsWithPosSuffix : containsPosSuffix) {
|
||||
// Matches the default positive prefix
|
||||
matchedPosSuffix = positiveSuffix;
|
||||
gotPos = true;
|
||||
}
|
||||
if (text.regionMatches(position, negativeSuffix, 0,
|
||||
negativeSuffix.length())) {
|
||||
boolean containsNegSuffix = text.regionMatches(position,
|
||||
negativeSuffix, 0, negativeSuffix.length());
|
||||
boolean endsWithNegSuffix = containsNegSuffix && text.length() ==
|
||||
position + negativeSuffix.length();
|
||||
if (parseStrict ? endsWithNegSuffix : containsNegSuffix) {
|
||||
// Matches the default negative suffix
|
||||
matchedNegSuffix = negativeSuffix;
|
||||
gotNeg = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If both matches, take the longest one
|
||||
// If both match, take the longest one
|
||||
if (gotPos && gotNeg) {
|
||||
if (matchedPosSuffix.length() > matchedNegSuffix.length()) {
|
||||
gotNeg = false;
|
||||
|
@ -2077,6 +2133,7 @@ public final class CompactNumberFormat extends NumberFormat {
|
|||
decimalFormat.setGroupingSize(getGroupingSize());
|
||||
decimalFormat.setGroupingUsed(isGroupingUsed());
|
||||
decimalFormat.setParseIntegerOnly(isParseIntegerOnly());
|
||||
decimalFormat.setStrict(parseStrict);
|
||||
|
||||
try {
|
||||
defaultDecimalFormat = new DecimalFormat(decimalPattern, symbols);
|
||||
|
@ -2316,6 +2373,31 @@ public final class CompactNumberFormat extends NumberFormat {
|
|||
super.setParseIntegerOnly(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc NumberFormat}
|
||||
*
|
||||
* @see #setStrict(boolean)
|
||||
* @see #parse(String, ParsePosition)
|
||||
* @since 23
|
||||
*/
|
||||
@Override
|
||||
public boolean isStrict() {
|
||||
return parseStrict;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc NumberFormat}
|
||||
*
|
||||
* @see #isStrict()
|
||||
* @see #parse(String, ParsePosition)
|
||||
* @since 23
|
||||
*/
|
||||
@Override
|
||||
public void setStrict(boolean strict) {
|
||||
decimalFormat.setStrict(strict);
|
||||
parseStrict = strict; // don't call super, default is UOE
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link #parse(String, ParsePosition)}
|
||||
* method returns {@code BigDecimal}. The default value is false.
|
||||
|
@ -2373,7 +2455,8 @@ public final class CompactNumberFormat extends NumberFormat {
|
|||
&& roundingMode.equals(other.roundingMode)
|
||||
&& pluralRules.equals(other.pluralRules)
|
||||
&& groupingSize == other.groupingSize
|
||||
&& parseBigDecimal == other.parseBigDecimal;
|
||||
&& parseBigDecimal == other.parseBigDecimal
|
||||
&& parseStrict == other.parseStrict;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -50,6 +50,7 @@ import java.util.Currency;
|
|||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import sun.util.locale.provider.LocaleProviderAdapter;
|
||||
import sun.util.locale.provider.ResourceBundleBasedAdapter;
|
||||
|
||||
|
@ -2140,18 +2141,32 @@ public class DecimalFormat extends NumberFormat {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses text from a string to produce a {@code Number}.
|
||||
* {@inheritDoc NumberFormat}
|
||||
* <p>
|
||||
* The method attempts to parse text starting at the index given by
|
||||
* {@code pos}.
|
||||
* If parsing succeeds, then the index of {@code pos} is updated
|
||||
* to the index after the last character used (parsing does not necessarily
|
||||
* use all characters up to the end of the string), and the parsed
|
||||
* number is returned. The updated {@code pos} can be used to
|
||||
* indicate the starting point for the next call to this method.
|
||||
* If an error occurs, then the index of {@code pos} is not
|
||||
* changed, the error index of {@code pos} is set to the index of
|
||||
* the character where the error occurred, and null is returned.
|
||||
* Parsing can be done in either a strict or lenient manner, by default it is lenient.
|
||||
* <p>
|
||||
* Parsing fails when <b>lenient</b>, if the prefix and/or suffix are non-empty
|
||||
* and cannot be found due to parsing ending early, or the first character
|
||||
* after the prefix cannot be parsed.
|
||||
* <p>
|
||||
* Parsing fails when <b>strict</b>, if in {@code text},
|
||||
* <ul>
|
||||
* <li> The prefix is not found. For example, a {@code Locale.US} currency
|
||||
* format prefix: "{@code $}"
|
||||
* <li> The suffix is not found. For example, a {@code Locale.US} percent
|
||||
* format suffix: "{@code %}"
|
||||
* <li> {@link #isGroupingUsed()} returns {@code true}, and {@link
|
||||
* #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
|
||||
* 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
|
||||
* </ul>
|
||||
* <p>
|
||||
* The subclass returned depends on the value of {@link #isParseBigDecimal}
|
||||
* as well as on the string being parsed.
|
||||
|
@ -2371,22 +2386,34 @@ public class DecimalFormat extends NumberFormat {
|
|||
return false;
|
||||
}
|
||||
|
||||
// position will serve as new index when success, otherwise it will
|
||||
// serve as errorIndex when failure
|
||||
position = subparseNumber(text, position, digits, true, isExponent, status);
|
||||
|
||||
// First character after the prefix was un-parseable, should
|
||||
// fail regardless if lenient or strict.
|
||||
if (position == -1) {
|
||||
parsePosition.index = oldStart;
|
||||
parsePosition.errorIndex = oldStart;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for suffix
|
||||
// When strict, text should end with the suffix.
|
||||
// When lenient, text only needs to contain the suffix.
|
||||
if (!isExponent) {
|
||||
if (gotPositive) {
|
||||
gotPositive = text.regionMatches(position,positiveSuffix,0,
|
||||
positiveSuffix.length());
|
||||
boolean containsPosSuffix =
|
||||
text.regionMatches(position, positiveSuffix, 0, positiveSuffix.length());
|
||||
boolean endsWithPosSuffix =
|
||||
containsPosSuffix && text.length() == position + positiveSuffix.length();
|
||||
gotPositive = parseStrict ? endsWithPosSuffix : containsPosSuffix;
|
||||
}
|
||||
if (gotNegative) {
|
||||
gotNegative = text.regionMatches(position,negativeSuffix,0,
|
||||
negativeSuffix.length());
|
||||
boolean containsNegSuffix =
|
||||
text.regionMatches(position, negativeSuffix, 0, negativeSuffix.length());
|
||||
boolean endsWithNegSuffix =
|
||||
containsNegSuffix && text.length() == position + negativeSuffix.length();
|
||||
gotNegative = parseStrict ? endsWithNegSuffix : containsNegSuffix;
|
||||
}
|
||||
|
||||
// If both match, take longest
|
||||
|
@ -2404,8 +2431,9 @@ public class DecimalFormat extends NumberFormat {
|
|||
return false;
|
||||
}
|
||||
|
||||
// No failures, thus increment the index by the suffix
|
||||
parsePosition.index = position +
|
||||
(gotPositive ? positiveSuffix.length() : negativeSuffix.length()); // mark success!
|
||||
(gotPositive ? positiveSuffix.length() : negativeSuffix.length());
|
||||
} else {
|
||||
parsePosition.index = position;
|
||||
}
|
||||
|
@ -2420,7 +2448,7 @@ public class DecimalFormat extends NumberFormat {
|
|||
|
||||
/**
|
||||
* Parses a number from the given {@code text}. The text is parsed
|
||||
* beginning at position, until an unparseable character is seen.
|
||||
* beginning at {@code position}, until an unparseable character is seen.
|
||||
*
|
||||
* @param text the string to parse
|
||||
* @param position the position at which parsing begins
|
||||
|
@ -2438,7 +2466,7 @@ public class DecimalFormat extends NumberFormat {
|
|||
boolean isExponent, boolean[] status) {
|
||||
// process digits or Inf, find decimal position
|
||||
status[STATUS_INFINITE] = false;
|
||||
if (!isExponent && text.regionMatches(position,symbols.getInfinity(),0,
|
||||
if (!isExponent && text.regionMatches(position, symbols.getInfinity(), 0,
|
||||
symbols.getInfinity().length())) {
|
||||
position += symbols.getInfinity().length();
|
||||
status[STATUS_INFINITE] = true;
|
||||
|
@ -2467,6 +2495,8 @@ public class DecimalFormat extends NumberFormat {
|
|||
// We have to track digitCount ourselves, because digits.count will
|
||||
// pin when the maximum allowable digits is reached.
|
||||
int digitCount = 0;
|
||||
int prevSeparatorIndex = -groupingSize;
|
||||
int startPos = position; // Rely on startPos as index after prefix
|
||||
|
||||
int backup = -1;
|
||||
for (; position < text.length(); ++position) {
|
||||
|
@ -2488,6 +2518,13 @@ public class DecimalFormat extends NumberFormat {
|
|||
digit = Character.digit(ch, 10);
|
||||
}
|
||||
|
||||
// Enforce the grouping size on the first group
|
||||
if (parseStrict && isGroupingUsed() && position == startPos + groupingSize
|
||||
&& prevSeparatorIndex == -groupingSize && !sawDecimal
|
||||
&& digit >= 0 && digit <= 9) {
|
||||
return position;
|
||||
}
|
||||
|
||||
if (digit == 0) {
|
||||
// Cancel out backup setting (see grouping handler below)
|
||||
backup = -1; // Do this BEFORE continue statement below!!!
|
||||
|
@ -2517,6 +2554,10 @@ public class DecimalFormat extends NumberFormat {
|
|||
// 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);
|
||||
}
|
||||
// If we're only parsing integers, or if we ALREADY saw the
|
||||
// decimal, then don't parse this one.
|
||||
if (isParseIntegerOnly() || sawDecimal) {
|
||||
|
@ -2525,8 +2566,23 @@ public class DecimalFormat extends NumberFormat {
|
|||
digits.decimalAt = digitCount; // Not digits.count!
|
||||
sawDecimal = true;
|
||||
} else if (!isExponent && ch == grouping && isGroupingUsed()) {
|
||||
if (sawDecimal) {
|
||||
break;
|
||||
if (parseStrict) {
|
||||
// text should not start with grouping when strict
|
||||
if (position == startPos) {
|
||||
return startPos;
|
||||
}
|
||||
// when strict, fail if grouping occurs after decimal OR
|
||||
// current group violates grouping size
|
||||
if (sawDecimal || (isGroupingViolation(position, prevSeparatorIndex))) {
|
||||
return groupingViolationIndex(position, prevSeparatorIndex);
|
||||
}
|
||||
prevSeparatorIndex = position; // track previous
|
||||
} else {
|
||||
// when lenient, only exit if grouping occurs after decimal
|
||||
// subsequent grouping symbols are allowed when lenient
|
||||
if (sawDecimal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Ignore grouping characters, if we are using them, but
|
||||
// require that they be followed by a digit. Otherwise
|
||||
|
@ -2554,6 +2610,23 @@ public class DecimalFormat extends NumberFormat {
|
|||
}
|
||||
}
|
||||
|
||||
// (When strict), within the loop we enforce grouping when encountering
|
||||
// decimal/grouping symbols. Once outside loop, we need to check
|
||||
// the final grouping, ex: "1,234". Only check the final grouping
|
||||
// if we have not seen a decimal separator, to prevent a non needed check,
|
||||
// for ex: "1,234.", "1,234.12"
|
||||
if (parseStrict) {
|
||||
if (!sawDecimal && isGroupingViolation(position, prevSeparatorIndex)) {
|
||||
// -1, since position is incremented by one too many when loop is finished
|
||||
// "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);
|
||||
}
|
||||
}
|
||||
|
||||
// If a grouping symbol is not followed by a digit, it must be
|
||||
// backed up to either exit early or fail depending on leniency
|
||||
if (backup != -1) {
|
||||
position = backup;
|
||||
}
|
||||
|
@ -2575,7 +2648,30 @@ public class DecimalFormat extends NumberFormat {
|
|||
}
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
// Checks to make sure grouping size is not violated. Used when strict.
|
||||
private boolean isGroupingViolation(int pos, int prevGroupingPos) {
|
||||
assert parseStrict : "Grouping violations should only occur when strict";
|
||||
return isGroupingUsed() && // Only violates if using grouping
|
||||
// Checks if a previous grouping symbol was seen.
|
||||
prevGroupingPos != -groupingSize &&
|
||||
// The check itself, - 1 to account for grouping/decimal symbol
|
||||
pos - 1 != prevGroupingPos + groupingSize;
|
||||
}
|
||||
|
||||
// Calculates the index that violated the grouping size
|
||||
// Violation can be over or under the grouping size
|
||||
// under - Current group has a grouping size of less than the expected
|
||||
// over - Current group has a grouping size of more than the expected
|
||||
private int groupingViolationIndex(int pos, int prevGroupingPos) {
|
||||
// Both examples assume grouping size of 3 and 0 indexed
|
||||
// under ex: "1,23,4". (4) OR "1,,2". (2) When under, violating char is grouping symbol
|
||||
// over ex: "1,2345,6. (5) When over, violating char is the excess digit
|
||||
// This method is only evaluated when a grouping symbol is found, thus
|
||||
// we can take the minimum of either the current pos, or where we expect
|
||||
// the current group to have ended
|
||||
return Math.min(pos, prevGroupingPos + groupingSize + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2888,6 +2984,30 @@ public class DecimalFormat extends NumberFormat {
|
|||
fastPathCheckNeeded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc NumberFormat}
|
||||
*
|
||||
* @see #setStrict(boolean)
|
||||
* @see #parse(String, ParsePosition)
|
||||
* @since 23
|
||||
*/
|
||||
@Override
|
||||
public boolean isStrict() {
|
||||
return parseStrict;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc NumberFormat}
|
||||
*
|
||||
* @see #isStrict()
|
||||
* @see #parse(String, ParsePosition)
|
||||
* @since 23
|
||||
*/
|
||||
@Override
|
||||
public void setStrict(boolean strict) {
|
||||
parseStrict = strict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link #parse(java.lang.String, java.text.ParsePosition)}
|
||||
* method returns {@code BigDecimal}. The default value is false.
|
||||
|
@ -2991,7 +3111,8 @@ public class DecimalFormat extends NumberFormat {
|
|||
&& maximumFractionDigits == other.maximumFractionDigits
|
||||
&& minimumFractionDigits == other.minimumFractionDigits
|
||||
&& roundingMode == other.roundingMode
|
||||
&& symbols.equals(other.symbols);
|
||||
&& symbols.equals(other.symbols)
|
||||
&& parseStrict == other.parseStrict;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4176,6 +4297,15 @@ public class DecimalFormat extends NumberFormat {
|
|||
*/
|
||||
private boolean useExponentialNotation; // Newly persistent in the Java 2 platform v.1.2
|
||||
|
||||
/**
|
||||
* True if this {@code DecimalFormat} will parse numbers with strict
|
||||
* leniency.
|
||||
*
|
||||
* @serial
|
||||
* @since 23
|
||||
*/
|
||||
private boolean parseStrict = false;
|
||||
|
||||
/**
|
||||
* FieldPositions describing the positive prefix String. This is
|
||||
* lazily created. Use {@code getPositivePrefixFieldPositions}
|
||||
|
|
|
@ -106,6 +106,9 @@ import java.io.Serializable;
|
|||
* </pre>
|
||||
* </blockquote>
|
||||
*
|
||||
* <p> Subclasses may also consider implementing leniency when parsing.
|
||||
* The definition of leniency should be delegated to the subclass.
|
||||
*
|
||||
* <p>
|
||||
* And finally subclasses may define a set of constants to identify the various
|
||||
* fields in the formatted output. These constants are used to create a FieldPosition
|
||||
|
@ -210,37 +213,36 @@ public abstract class Format implements Serializable, Cloneable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses text from a string to produce an object.
|
||||
* Parses text from the given string to produce an object.
|
||||
* <p>
|
||||
* The method attempts to parse text starting at the index given by
|
||||
* {@code pos}.
|
||||
* If parsing succeeds, then the index of {@code pos} is updated
|
||||
* This method attempts to parse text starting at the index given by
|
||||
* {@code pos}. If parsing succeeds, then the index of {@code pos} is updated
|
||||
* to the index after the last character used (parsing does not necessarily
|
||||
* use all characters up to the end of the string), and the parsed
|
||||
* object is returned. The updated {@code pos} can be used to
|
||||
* indicate the starting point for the next call to this method.
|
||||
* If an error occurs, then the index of {@code pos} is not
|
||||
* changed, the error index of {@code pos} is set to the index of
|
||||
* the character where the error occurred, and null is returned.
|
||||
* the character where the error occurred, and {@code null} is returned.
|
||||
*
|
||||
* @param source A {@code String}, part of which should be parsed.
|
||||
* @param source the {@code String} to parse
|
||||
* @param pos A {@code ParsePosition} object with index and error
|
||||
* index information as described above.
|
||||
* @return An {@code Object} parsed from the string. In case of
|
||||
* error, returns null.
|
||||
* @throws NullPointerException if {@code source} or {@code pos} is null.
|
||||
* error, returns {@code null}.
|
||||
* @throws NullPointerException if {@code source} or {@code pos} is
|
||||
* {@code null}.
|
||||
*/
|
||||
public abstract Object parseObject (String source, ParsePosition pos);
|
||||
|
||||
/**
|
||||
* Parses text from the beginning of the given string to produce an object.
|
||||
* The method may not use the entire text of the given string.
|
||||
* This method may not use the entire text of the given string.
|
||||
*
|
||||
* @param source A {@code String} whose beginning should be parsed.
|
||||
* @param source A {@code String}, to be parsed from the beginning.
|
||||
* @return An {@code Object} parsed from the string.
|
||||
* @throws ParseException if the beginning of the specified string
|
||||
* cannot be parsed.
|
||||
* @throws NullPointerException if {@code source} is null.
|
||||
* @throws ParseException if parsing fails
|
||||
* @throws NullPointerException if {@code source} is {@code null}.
|
||||
*/
|
||||
public Object parseObject(String source) throws ParseException {
|
||||
ParsePosition pos = new ParsePosition(0);
|
||||
|
|
|
@ -38,8 +38,8 @@
|
|||
|
||||
package java.text;
|
||||
|
||||
import java.io.InvalidObjectException;
|
||||
import java.io.IOException;
|
||||
import java.io.InvalidObjectException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.math.BigInteger;
|
||||
|
@ -52,6 +52,7 @@ import java.util.Map;
|
|||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import sun.util.locale.provider.LocaleProviderAdapter;
|
||||
import sun.util.locale.provider.LocaleServiceProviderPool;
|
||||
|
||||
|
@ -147,7 +148,6 @@ import sun.util.locale.provider.LocaleServiceProviderPool;
|
|||
* if false, 3456.00 → "3456"
|
||||
* This is independent of parsing. If you want parsing to stop at the decimal
|
||||
* point, use setParseIntegerOnly.
|
||||
*
|
||||
* <p>
|
||||
* You can also use forms of the {@code parse} and {@code format}
|
||||
* methods with {@code ParsePosition} and {@code FieldPosition} to
|
||||
|
@ -175,9 +175,23 @@ import sun.util.locale.provider.LocaleServiceProviderPool;
|
|||
* numbers: "(12)" for -12.
|
||||
* </ol>
|
||||
*
|
||||
* <h2><a id="synchronization">Synchronization</a></h2>
|
||||
*
|
||||
* <h2><a id="leniency">Leniency</a></h2>
|
||||
* {@code NumberFormat} by default, parses leniently. Subclasses may consider
|
||||
* implementing strict parsing and as such, overriding and providing
|
||||
* implementations for the optional {@link #isStrict()} and {@link
|
||||
* #setStrict(boolean)} methods.
|
||||
* <p>
|
||||
* Lenient parsing should be used when attempting to parse a number
|
||||
* out of a String that contains non-numerical or non-format related values.
|
||||
* For example, using a {@link Locale#US} currency format to parse the number
|
||||
* {@code 1000} out of the String "$1,000.00 was paid".
|
||||
* <p>
|
||||
* Strict parsing should be used when attempting to ensure a String adheres exactly
|
||||
* to a locale's conventions, and can thus serve to validate input. For example, successfully
|
||||
* parsing the number {@code 1000.55} out of the String "1.000,55" confirms the String
|
||||
* exactly adhered to the {@link Locale#GERMANY} numerical conventions.
|
||||
*
|
||||
* <h2><a id="synchronization">Synchronization</a></h2>
|
||||
* Number formats are generally not synchronized.
|
||||
* It is recommended to create separate format instances for each thread.
|
||||
* If multiple threads access a format concurrently, it must be synchronized
|
||||
|
@ -285,23 +299,11 @@ public abstract class NumberFormat extends Format {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses text from a string to produce a {@code Number}.
|
||||
* <p>
|
||||
* The method attempts to parse text starting at the index given by
|
||||
* {@code pos}.
|
||||
* If parsing succeeds, then the index of {@code pos} is updated
|
||||
* to the index after the last character used (parsing does not necessarily
|
||||
* use all characters up to the end of the string), and the parsed
|
||||
* number is returned. The updated {@code pos} can be used to
|
||||
* indicate the starting point for the next call to this method.
|
||||
* If an error occurs, then the index of {@code pos} is not
|
||||
* changed, the error index of {@code pos} is set to the index of
|
||||
* the character where the error occurred, and null is returned.
|
||||
* <p>
|
||||
* See the {@link #parse(String, ParsePosition)} method for more information
|
||||
* on number parsing.
|
||||
* {@inheritDoc Format}
|
||||
*
|
||||
* @param source A {@code String}, part of which should be parsed.
|
||||
* @implSpec This implementation is equivalent to calling {@code parse(source,
|
||||
* pos)}.
|
||||
* @param source the {@code String} to parse
|
||||
* @param pos A {@code ParsePosition} object with index and error
|
||||
* index information as described above.
|
||||
* @return A {@code Number} parsed from the string. In case of
|
||||
|
@ -399,33 +401,44 @@ public abstract class NumberFormat extends Format {
|
|||
FieldPosition pos);
|
||||
|
||||
/**
|
||||
* Returns a Long if possible (e.g., within the range [Long.MIN_VALUE,
|
||||
* Parses text from the beginning of the given string to produce a {@code Number}.
|
||||
* <p>
|
||||
* This method attempts to parse text starting at the index given by the
|
||||
* {@code ParsePosition}. If parsing succeeds, then the index of the {@code
|
||||
* ParsePosition} is updated to the index after the last character used
|
||||
* (parsing does not necessarily use all characters up to the end of the
|
||||
* string), and the parsed number is returned. The updated {@code
|
||||
* ParsePosition} can be used to indicate the starting
|
||||
* point for the next call to this method. If an error occurs, then the
|
||||
* index of the {@code ParsePosition} is not changed, the error index of the
|
||||
* {@code ParsePosition} is set to the index of the character where the error
|
||||
* occurred, and {@code null} is returned.
|
||||
* <p>
|
||||
* This method will return a Long if possible (e.g., within the range [Long.MIN_VALUE,
|
||||
* Long.MAX_VALUE] and with no decimals), otherwise a Double.
|
||||
* If IntegerOnly is set, will stop at a decimal
|
||||
* point (or equivalent; e.g., for rational numbers "1 2/3", will stop
|
||||
* after the 1).
|
||||
* Does not throw an exception; if no object can be parsed, index is
|
||||
* unchanged!
|
||||
*
|
||||
* @param source the String to parse
|
||||
* @param parsePosition the parse position
|
||||
* @return the parsed value
|
||||
* @see java.text.NumberFormat#isParseIntegerOnly
|
||||
* @see java.text.Format#parseObject
|
||||
* @param source the {@code String} to parse
|
||||
* @param parsePosition A {@code ParsePosition} object with index and error
|
||||
* index information as described above.
|
||||
* @return A {@code Number} parsed from the string. In case of
|
||||
* failure, returns {@code null}.
|
||||
* @throws NullPointerException if {@code source} or {@code ParsePosition}
|
||||
* is {@code null}.
|
||||
* @see #isStrict()
|
||||
*/
|
||||
public abstract Number parse(String source, ParsePosition parsePosition);
|
||||
|
||||
/**
|
||||
* Parses text from the beginning of the given string to produce a number.
|
||||
* The method may not use the entire text of the given string.
|
||||
* Parses text from the beginning of the given string to produce a {@code Number}.
|
||||
* <p>
|
||||
* See the {@link #parse(String, ParsePosition)} method for more information
|
||||
* on number parsing.
|
||||
* This method will return a Long if possible (e.g., within the range [Long.MIN_VALUE,
|
||||
* Long.MAX_VALUE] and with no decimals), otherwise a Double.
|
||||
*
|
||||
* @param source A {@code String} whose beginning should be parsed.
|
||||
* @param source A {@code String}, to be parsed from the beginning.
|
||||
* @return A {@code Number} parsed from the string.
|
||||
* @throws ParseException if the beginning of the specified string
|
||||
* cannot be parsed.
|
||||
* @throws ParseException if parsing fails
|
||||
* @throws NullPointerException if {@code source} is {@code null}.
|
||||
* @see #isStrict()
|
||||
*/
|
||||
public Number parse(String source) throws ParseException {
|
||||
ParsePosition parsePosition = new ParsePosition(0);
|
||||
|
@ -463,6 +476,44 @@ public abstract class NumberFormat extends Format {
|
|||
parseIntegerOnly = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return {@code true} if this format will parse numbers strictly;
|
||||
* {@code false} otherwise}
|
||||
*
|
||||
* @implSpec The default implementation always throws {@code
|
||||
* UnsupportedOperationException}. Subclasses should override this method
|
||||
* when implementing strict parsing.
|
||||
* @throws UnsupportedOperationException if the implementation of this
|
||||
* method does not support this operation
|
||||
* @see ##leniency Leniency Section
|
||||
* @see #setStrict(boolean)
|
||||
* @since 23
|
||||
*/
|
||||
public boolean isStrict() {
|
||||
throw new UnsupportedOperationException("Subclasses should override this " +
|
||||
"method when implementing strict parsing");
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the leniency value for parsing. Parsing can either be strict or lenient,
|
||||
* by default it is lenient.
|
||||
*
|
||||
* @implSpec The default implementation always throws {@code
|
||||
* UnsupportedOperationException}. Subclasses should override this method
|
||||
* when implementing strict parsing.
|
||||
* @param strict {@code true} if parsing should be done strictly;
|
||||
* {@code false} otherwise
|
||||
* @throws UnsupportedOperationException if the implementation of this
|
||||
* method does not support this operation
|
||||
* @see ##leniency Leniency Section
|
||||
* @see #isStrict()
|
||||
* @since 23
|
||||
*/
|
||||
public void setStrict(boolean strict) {
|
||||
throw new UnsupportedOperationException("Subclasses should override this " +
|
||||
"method when implementing strict parsing");
|
||||
}
|
||||
|
||||
//============== Locale Stuff =====================
|
||||
|
||||
/**
|
||||
|
@ -759,12 +810,12 @@ public abstract class NumberFormat extends Format {
|
|||
return false;
|
||||
}
|
||||
NumberFormat other = (NumberFormat) obj;
|
||||
return (maximumIntegerDigits == other.maximumIntegerDigits
|
||||
return maximumIntegerDigits == other.maximumIntegerDigits
|
||||
&& minimumIntegerDigits == other.minimumIntegerDigits
|
||||
&& maximumFractionDigits == other.maximumFractionDigits
|
||||
&& minimumFractionDigits == other.minimumFractionDigits
|
||||
&& groupingUsed == other.groupingUsed
|
||||
&& parseIntegerOnly == other.parseIntegerOnly);
|
||||
&& parseIntegerOnly == other.parseIntegerOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue