8327640: Allow NumberFormat strict parsing
Reviewed-by: naoto
This commit is contained in:
parent
2ede14335a
commit
941bee197f
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -22,7 +22,7 @@
|
||||
*/
|
||||
/*
|
||||
* @test
|
||||
* @bug 8177552 8222756
|
||||
* @bug 8177552 8222756 8327640
|
||||
* @summary Checks the equals and hashCode method of CompactNumberFormat
|
||||
* @modules jdk.localedata
|
||||
* @run testng/othervm TestEquality
|
||||
@ -131,6 +131,18 @@ public class TestEquality {
|
||||
cnf1.setParseBigDecimal(true);
|
||||
checkEquals(cnf1, cnf2, false, "8th", "different parse big decimal");
|
||||
|
||||
// Changing the parseBigDecimal of second object; objects must be equal
|
||||
cnf2.setParseBigDecimal(true);
|
||||
checkEquals(cnf1, cnf2, true, "9th", "");
|
||||
|
||||
// Changing the strict parsing value of first object; objects must not be equal
|
||||
cnf1.setStrict(true);
|
||||
checkEquals(cnf1, cnf2, false, "10th", "different strict parsing");
|
||||
|
||||
// Changing the strict parsing value of second object; objects must be equal
|
||||
cnf2.setStrict(true);
|
||||
checkEquals(cnf1, cnf2, true, "11th", "");
|
||||
|
||||
}
|
||||
|
||||
private void checkEquals(CompactNumberFormat cnf1, CompactNumberFormat cnf2,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -22,7 +22,7 @@
|
||||
*/
|
||||
/*
|
||||
* @test
|
||||
* @bug 8177552
|
||||
* @bug 8177552 8327640
|
||||
* @modules jdk.localedata
|
||||
* @summary Checks the serialization feature of CompactNumberFormat
|
||||
* @run testng/othervm TestSerialization
|
||||
@ -71,6 +71,7 @@ public class TestSerialization {
|
||||
|
||||
FORMAT_FR_FR.setParseIntegerOnly(true);
|
||||
FORMAT_FR_FR.setGroupingUsed(true);
|
||||
FORMAT_FR_FR.setStrict(true);
|
||||
|
||||
// Setting minimum integer digits beyond the allowed range
|
||||
FORMAT_DE_DE.setMinimumIntegerDigits(320);
|
||||
|
53
test/jdk/java/text/Format/DecimalFormat/EqualityTest.java
Normal file
53
test/jdk/java/text/Format/DecimalFormat/EqualityTest.java
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8327640
|
||||
* @summary Check parseStrict correctness for DecimalFormat.equals()
|
||||
* @run junit EqualityTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
public class EqualityTest {
|
||||
|
||||
private static final DecimalFormat fmt1 = new DecimalFormat();
|
||||
private static final DecimalFormat fmt2 = new DecimalFormat();
|
||||
|
||||
// Ensure that parseStrict is reflected correctly for DecimalFormat.equals()
|
||||
@Test
|
||||
public void checkStrictTest() {
|
||||
// parseStrict is false by default
|
||||
assertEquals(fmt1, fmt2);
|
||||
fmt1.setStrict(true);
|
||||
assertNotEquals(fmt1, fmt2);
|
||||
fmt2.setStrict(true);
|
||||
assertEquals(fmt1, fmt2);
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
/*
|
||||
* @test
|
||||
* @bug 8327640
|
||||
* @summary Check parseStrict correctness for DecimalFormat serialization
|
||||
* @run junit/othervm SerializationTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class SerializationTest {
|
||||
|
||||
private static final NumberFormat FORMAT = NumberFormat.getInstance();
|
||||
|
||||
@BeforeAll
|
||||
public static void mutateFormat() {
|
||||
FORMAT.setStrict(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerialization() throws IOException, ClassNotFoundException {
|
||||
// Serialize
|
||||
serialize("fmt.ser", FORMAT);
|
||||
// Deserialize
|
||||
deserialize("fmt.ser", FORMAT);
|
||||
}
|
||||
|
||||
private void serialize(String fileName, NumberFormat... formats)
|
||||
throws IOException {
|
||||
try (ObjectOutputStream os = new ObjectOutputStream(
|
||||
new FileOutputStream(fileName))) {
|
||||
for (NumberFormat fmt : formats) {
|
||||
os.writeObject(fmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void deserialize(String fileName, NumberFormat... formats)
|
||||
throws IOException, ClassNotFoundException {
|
||||
try (ObjectInputStream os = new ObjectInputStream(
|
||||
new FileInputStream(fileName))) {
|
||||
for (NumberFormat fmt : formats) {
|
||||
NumberFormat obj = (NumberFormat) os.readObject();
|
||||
assertEquals(fmt, obj, "Serialized and deserialized"
|
||||
+ " objects do not match");
|
||||
|
||||
String badNumber = "fooofooo23foo";
|
||||
assertThrows(ParseException.class, () -> fmt.parse(badNumber));
|
||||
assertThrows(ParseException.class, () -> obj.parse(badNumber));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
417
test/jdk/java/text/Format/NumberFormat/LenientParseTest.java
Normal file
417
test/jdk/java/text/Format/NumberFormat/LenientParseTest.java
Normal file
@ -0,0 +1,417 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8327640
|
||||
* @summary Test suite for NumberFormat parsing when lenient.
|
||||
* @run junit/othervm -Duser.language=en -Duser.country=US LenientParseTest
|
||||
* @run junit/othervm -Duser.language=ja -Duser.country=JP LenientParseTest
|
||||
* @run junit/othervm -Duser.language=zh -Duser.country=CN LenientParseTest
|
||||
* @run junit/othervm -Duser.language=tr -Duser.country=TR LenientParseTest
|
||||
* @run junit/othervm -Duser.language=de -Duser.country=DE LenientParseTest
|
||||
* @run junit/othervm -Duser.language=fr -Duser.country=FR LenientParseTest
|
||||
* @run junit/othervm -Duser.language=ar -Duser.country=AR LenientParseTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.text.CompactNumberFormat;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
// Tests lenient parsing, this is done by testing the NumberFormat factory instances
|
||||
// against a number of locales with different formatting conventions. The locales
|
||||
// used all use a grouping size of 3. When lenient, parsing only fails
|
||||
// if the prefix and/or suffix are not found, or the first character after the
|
||||
// prefix is un-parseable. The tested locales all use groupingSize of 3.
|
||||
public class LenientParseTest {
|
||||
|
||||
// Used to retrieve the locale's expected symbols
|
||||
private static final DecimalFormatSymbols dfs =
|
||||
new DecimalFormatSymbols(Locale.getDefault());
|
||||
private static final DecimalFormat dFmt = (DecimalFormat)
|
||||
NumberFormat.getNumberInstance(Locale.getDefault());
|
||||
private static final DecimalFormat cFmt =
|
||||
(DecimalFormat) NumberFormat.getCurrencyInstance(Locale.getDefault());
|
||||
private static final DecimalFormat pFmt =
|
||||
(DecimalFormat) NumberFormat.getPercentInstance(Locale.getDefault());
|
||||
private static final CompactNumberFormat cmpctFmt =
|
||||
(CompactNumberFormat) NumberFormat.getCompactNumberInstance(Locale.getDefault(),
|
||||
NumberFormat.Style.SHORT);
|
||||
|
||||
// All NumberFormats should parse leniently (which is the default)
|
||||
static {
|
||||
// To effectively test compactNumberFormat, these should be set accordingly
|
||||
cmpctFmt.setParseIntegerOnly(false);
|
||||
cmpctFmt.setGroupingUsed(true);
|
||||
}
|
||||
|
||||
// ---- NumberFormat tests ----
|
||||
// Test prefix/suffix behavior with a predefined DecimalFormat
|
||||
// Non-localized, only run once
|
||||
@ParameterizedTest
|
||||
@MethodSource("badParseStrings")
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void numFmtFailParseTest(String toParse, int expectedErrorIndex) {
|
||||
// Format with grouping size = 3, prefix = a, suffix = b
|
||||
DecimalFormat nonLocalizedDFmt = new DecimalFormat("a#,#00.00b");
|
||||
failParse(nonLocalizedDFmt, toParse, expectedErrorIndex);
|
||||
}
|
||||
|
||||
// All input Strings should parse fully and return the expected value.
|
||||
// Expected index should be the length of the parse string, since it parses fully
|
||||
@ParameterizedTest
|
||||
@MethodSource("validFullParseStrings")
|
||||
public void numFmtSuccessFullParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(dFmt, toParse, toParse.length()));
|
||||
}
|
||||
|
||||
// All input Strings should parse partially and return expected value
|
||||
// with the expected final index
|
||||
@ParameterizedTest
|
||||
@MethodSource("validPartialParseStrings")
|
||||
public void numFmtSuccessPartialParseTest(String toParse, double expectedValue,
|
||||
int expectedIndex) {
|
||||
assertEquals(expectedValue, successParse(dFmt, toParse, expectedIndex));
|
||||
}
|
||||
|
||||
// Parse partially due to no grouping
|
||||
@ParameterizedTest
|
||||
@MethodSource("noGroupingParseStrings")
|
||||
public void numFmtStrictGroupingNotUsed(String toParse, double expectedValue, int expectedIndex) {
|
||||
dFmt.setGroupingUsed(false);
|
||||
assertEquals(expectedValue, successParse(dFmt, toParse, expectedIndex));
|
||||
dFmt.setGroupingUsed(true);
|
||||
}
|
||||
|
||||
// Parse partially due to integer only
|
||||
@ParameterizedTest
|
||||
@MethodSource("integerOnlyParseStrings")
|
||||
public void numFmtStrictIntegerOnlyUsed(String toParse, int expectedValue, int expectedIndex) {
|
||||
dFmt.setParseIntegerOnly(true);
|
||||
assertEquals(expectedValue, successParse(dFmt, toParse, expectedIndex));
|
||||
dFmt.setParseIntegerOnly(false);
|
||||
}
|
||||
|
||||
// ---- CurrencyFormat tests ----
|
||||
// All input Strings should pass and return expected value.
|
||||
@ParameterizedTest
|
||||
@MethodSource("currencyValidFullParseStrings")
|
||||
public void currFmtSuccessParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(cFmt, toParse, toParse.length()));
|
||||
}
|
||||
|
||||
// Strings may parse partially or fail. This is because the mapped
|
||||
// data may cause the error to occur before the suffix can be found, (if the locale
|
||||
// uses a suffix).
|
||||
@ParameterizedTest
|
||||
@MethodSource("currencyValidPartialParseStrings")
|
||||
public void currFmtParseTest(String toParse, double expectedValue,
|
||||
int expectedIndex) {
|
||||
if (cFmt.getPositiveSuffix().length() > 0) {
|
||||
// Since the error will occur before suffix is found, exception is thrown.
|
||||
failParse(cFmt, toParse, expectedIndex);
|
||||
} else {
|
||||
// Empty suffix, thus even if the error occurs, we have already found the
|
||||
// prefix, and simply parse partially
|
||||
assertEquals(expectedValue, successParse(cFmt, toParse, expectedIndex));
|
||||
}
|
||||
}
|
||||
|
||||
// ---- PercentFormat tests ----
|
||||
// All input Strings should pass and return expected value.
|
||||
@ParameterizedTest
|
||||
@MethodSource("percentValidFullParseStrings")
|
||||
public void percentFmtSuccessParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(pFmt, toParse, toParse.length()));
|
||||
}
|
||||
|
||||
// Strings may parse partially or fail. This is because the mapped
|
||||
// data may cause the error to occur before the suffix can be found, (if the locale
|
||||
// uses a suffix).
|
||||
@ParameterizedTest
|
||||
@MethodSource("percentValidPartialParseStrings")
|
||||
public void percentFmtParseTest(String toParse, double expectedValue,
|
||||
int expectedIndex) {
|
||||
if (pFmt.getPositiveSuffix().length() > 0) {
|
||||
// Since the error will occur before suffix is found, exception is thrown.
|
||||
failParse(pFmt, toParse, expectedIndex);
|
||||
} else {
|
||||
// Empty suffix, thus even if the error occurs, we have already found the
|
||||
// prefix, and simply parse partially
|
||||
assertEquals(expectedValue, successParse(pFmt, toParse, expectedIndex));
|
||||
}
|
||||
}
|
||||
|
||||
// ---- CompactNumberFormat tests ----
|
||||
// Can match to both the decimalFormat patterns and the compact patterns
|
||||
// Unlike the other tests, this test is only ran against the US Locale and
|
||||
// tests against data built with the thousands format (K).
|
||||
@ParameterizedTest
|
||||
@MethodSource("compactValidPartialParseStrings")
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void compactFmtFailParseTest(String toParse, double expectedValue, int expectedErrorIndex) {
|
||||
assertEquals(expectedValue, successParse(cmpctFmt, toParse, expectedErrorIndex));
|
||||
}
|
||||
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("compactValidFullParseStrings")
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void compactFmtSuccessParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(cmpctFmt, toParse, toParse.length()));
|
||||
}
|
||||
|
||||
// ---- Helper test methods ----
|
||||
|
||||
// Method is used when a String should parse successfully. This does not indicate
|
||||
// that the entire String was used, however. The index and errorIndex values
|
||||
// should be as expected.
|
||||
private double successParse(NumberFormat fmt, String toParse, int expectedIndex) {
|
||||
Number parsedValue = assertDoesNotThrow(() -> fmt.parse(toParse));
|
||||
ParsePosition pp = new ParsePosition(0);
|
||||
assertDoesNotThrow(() -> fmt.parse(toParse, pp));
|
||||
assertEquals(-1, pp.getErrorIndex(),
|
||||
"ParsePosition ErrorIndex is not in correct location");
|
||||
assertEquals(expectedIndex, pp.getIndex(),
|
||||
"ParsePosition Index is not in correct location");
|
||||
return parsedValue.doubleValue();
|
||||
}
|
||||
|
||||
// Method is used when a String should fail parsing. Indicated by either a thrown
|
||||
// ParseException, or null is returned depending on which parse method is invoked.
|
||||
// errorIndex should be as expected.
|
||||
private void failParse(NumberFormat fmt, String toParse, int expectedErrorIndex) {
|
||||
ParsePosition pp = new ParsePosition(0);
|
||||
assertThrows(ParseException.class, () -> fmt.parse(toParse));
|
||||
assertNull(fmt.parse(toParse, pp));
|
||||
assertEquals(expectedErrorIndex, pp.getErrorIndex());
|
||||
}
|
||||
|
||||
// ---- Data Providers ----
|
||||
|
||||
// Strings that should fail when parsed leniently.
|
||||
// Given as Arguments<String, expectedErrorIndex>
|
||||
// Non-localized data. For reference, the pattern of nonLocalizedDFmt is
|
||||
// "a#,#00.00b"
|
||||
private static Stream<Arguments> badParseStrings() {
|
||||
return Stream.of(
|
||||
// No prefix
|
||||
Arguments.of("1,1b", 0),
|
||||
// No suffix
|
||||
Arguments.of("a1,11", 5),
|
||||
// Digit does not follow the last grouping separator
|
||||
// Current behavior fails on the grouping separator
|
||||
Arguments.of("a1,11,z", 5),
|
||||
// No suffix after grouping
|
||||
Arguments.of("a1,11,", 5),
|
||||
// No prefix and suffix
|
||||
Arguments.of("1,11", 0),
|
||||
// First character after prefix is un-parseable
|
||||
// Behavior is to expect error index at 0, not 1
|
||||
Arguments.of("ac1,11", 0));
|
||||
}
|
||||
|
||||
// These data providers use US locale grouping and decimal separators
|
||||
// for readability, however, the data is tested against multiple locales
|
||||
// and is converted appropriately at runtime.
|
||||
|
||||
// Strings that should parse successfully, and consume the entire String
|
||||
// Form of Arguments(parseString, expectedParsedNumber)
|
||||
private static Stream<Arguments> validFullParseStrings() {
|
||||
return Stream.of(
|
||||
// Many subsequent grouping symbols
|
||||
Arguments.of("1,,,1", 11d),
|
||||
Arguments.of("11,,,11,,,11", 111111d),
|
||||
// Bad grouping size (with decimal)
|
||||
Arguments.of("1,1.", 11d),
|
||||
Arguments.of("11,111,11.", 1111111d),
|
||||
// Improper grouping size (with decimal and digits after)
|
||||
Arguments.of("1,1.1", 11.1d),
|
||||
Arguments.of("1,11.1", 111.1d),
|
||||
Arguments.of("1,1111.1", 11111.1d),
|
||||
Arguments.of("11,111,11.1", 1111111.1d),
|
||||
// Starts with grouping symbol
|
||||
Arguments.of(",111,,1,1", 11111d),
|
||||
Arguments.of(",1", 1d),
|
||||
Arguments.of(",,1", 1d),
|
||||
// Leading Zeros (not digits)
|
||||
Arguments.of("000,1,1", 11d),
|
||||
Arguments.of("000,111,11,,1", 111111d),
|
||||
Arguments.of("0,000,1,,1,1", 111d),
|
||||
Arguments.of("1,234.00", 1234d),
|
||||
Arguments.of("1,234.0", 1234d),
|
||||
Arguments.of("1,234.", 1234d),
|
||||
Arguments.of("1,234.00123", 1234.00123d),
|
||||
Arguments.of("1,234.012", 1234.012d),
|
||||
Arguments.of("1,234.224", 1234.224d),
|
||||
Arguments.of("1", 1d),
|
||||
Arguments.of("10", 10d),
|
||||
Arguments.of("100", 100d),
|
||||
Arguments.of("1000", 1000d),
|
||||
Arguments.of("1,000", 1000d),
|
||||
Arguments.of("10,000", 10000d),
|
||||
Arguments.of("10000", 10000d),
|
||||
Arguments.of("100,000", 100000d),
|
||||
Arguments.of("1,000,000", 1000000d),
|
||||
Arguments.of("10,000,000", 10000000d))
|
||||
.map(args -> Arguments.of(
|
||||
localizeText(String.valueOf(args.get()[0])), args.get()[1]));
|
||||
}
|
||||
|
||||
// Strings that should parse successfully, but do not use the entire String
|
||||
// Form of Arguments(parseString, expectedParsedNumber, expectedIndex)
|
||||
private static Stream<Arguments> validPartialParseStrings() {
|
||||
return Stream.of(
|
||||
// End with grouping symbol
|
||||
Arguments.of("11,", 11d, 2),
|
||||
Arguments.of("11,,", 11d, 3),
|
||||
Arguments.of("11,,,", 11d, 4),
|
||||
// Random chars that aren't the expected symbols
|
||||
Arguments.of("1,1P111", 11d, 3),
|
||||
Arguments.of("1.1P111", 1.1d, 3),
|
||||
Arguments.of("1P,1111", 1d, 1),
|
||||
Arguments.of("1P.1111", 1d, 1),
|
||||
Arguments.of("1,1111P", 11111d, 6),
|
||||
// Grouping occurs after decimal separator)
|
||||
Arguments.of("1.11,11", 1.11d, 4),
|
||||
Arguments.of("1.,11,11", 1d, 2))
|
||||
.map(args -> Arguments.of(
|
||||
localizeText(String.valueOf(args.get()[0])), args.get()[1], args.get()[2]));
|
||||
}
|
||||
|
||||
// Test data input for when parse integer only is true
|
||||
// Form of Arguments(parseString, expectedParsedNumber, expectedIndex)
|
||||
private static Stream<Arguments> integerOnlyParseStrings() {
|
||||
return Stream.of(
|
||||
Arguments.of("1234.1234", 1234, 4),
|
||||
Arguments.of("1234.12", 1234, 4),
|
||||
Arguments.of("1234.1a", 1234, 4),
|
||||
Arguments.of("1234.", 1234, 4))
|
||||
.map(args -> Arguments.of(
|
||||
localizeText(String.valueOf(args.get()[0])), args.get()[1], args.get()[2]));
|
||||
}
|
||||
|
||||
// Test data input for when no grouping is true
|
||||
// Form of Arguments(parseString, expectedParsedNumber, expectedIndex)
|
||||
private static Stream<Arguments> noGroupingParseStrings() {
|
||||
return Stream.of(
|
||||
Arguments.of("12,34", 12d, 2),
|
||||
Arguments.of("1234,", 1234d, 4),
|
||||
Arguments.of("123,456.789", 123d, 3))
|
||||
.map(args -> Arguments.of(
|
||||
localizeText(String.valueOf(args.get()[0])), args.get()[1], args.get()[2]));
|
||||
}
|
||||
|
||||
// Mappers for respective data providers to adjust values accordingly
|
||||
// Localized percent prefix/suffix is added, with appropriate expected values
|
||||
// adjusted. Expected parsed number should be divided by 100.
|
||||
private static Stream<Arguments> percentValidPartialParseStrings() {
|
||||
return validPartialParseStrings().map(args ->
|
||||
Arguments.of(pFmt.getPositivePrefix() + args.get()[0] + pFmt.getPositiveSuffix(),
|
||||
(double) args.get()[1] / 100, (int) args.get()[2] + pFmt.getPositivePrefix().length())
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> percentValidFullParseStrings() {
|
||||
return validFullParseStrings().map(args -> Arguments.of(
|
||||
pFmt.getPositivePrefix() + args.get()[0] + pFmt.getPositiveSuffix(),
|
||||
(double) args.get()[1] / 100)
|
||||
);
|
||||
}
|
||||
|
||||
// Mappers for respective data providers to adjust values accordingly
|
||||
// Localized percent prefix/suffix is added, with appropriate expected values
|
||||
// adjusted. Separators replaced for monetary versions.
|
||||
private static Stream<Arguments> currencyValidPartialParseStrings() {
|
||||
return validPartialParseStrings().map(args -> Arguments.of(
|
||||
cFmt.getPositivePrefix() + String.valueOf(args.get()[0])
|
||||
.replace(dfs.getGroupingSeparator(), dfs.getMonetaryGroupingSeparator())
|
||||
.replace(dfs.getDecimalSeparator(), dfs.getMonetaryDecimalSeparator())
|
||||
+ cFmt.getPositiveSuffix(),
|
||||
args.get()[1], (int) args.get()[2] + cFmt.getPositivePrefix().length())
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> currencyValidFullParseStrings() {
|
||||
return validFullParseStrings().map(args -> Arguments.of(
|
||||
cFmt.getPositivePrefix() + String.valueOf(args.get()[0])
|
||||
.replace(dfs.getGroupingSeparator(), dfs.getMonetaryGroupingSeparator())
|
||||
.replace(dfs.getDecimalSeparator(), dfs.getMonetaryDecimalSeparator())
|
||||
+ cFmt.getPositiveSuffix(),
|
||||
args.get()[1])
|
||||
);
|
||||
}
|
||||
|
||||
// Compact Pattern Data Provider provides test input for both DecimalFormat patterns
|
||||
// and the compact patterns. As there is no method to retrieve compact patterns,
|
||||
// thus test only against US English locale, and use a hard coded K - 1000
|
||||
private static Stream<Arguments> compactValidPartialParseStrings() {
|
||||
return Stream.concat(validPartialParseStrings().map(args -> Arguments.of(args.get()[0],
|
||||
args.get()[1], args.get()[2])), validPartialParseStrings().map(args -> Arguments.of(args.get()[0] + "K",
|
||||
args.get()[1], args.get()[2]))
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> compactValidFullParseStrings() {
|
||||
return Stream.concat(validFullParseStrings().map(args -> Arguments.of(args.get()[0],
|
||||
args.get()[1])), validFullParseStrings().map(args -> Arguments.of(args.get()[0] + "K",
|
||||
(double)args.get()[1] * 1000.0))
|
||||
);
|
||||
}
|
||||
|
||||
// Replace the grouping and decimal separators with localized variants
|
||||
// Used during localization of data
|
||||
private static String localizeText(String text) {
|
||||
// As this is a single pass conversion, this is safe for multiple replacement,
|
||||
// even if a ',' could be a decimal separator for a locale.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
if (c == ',') {
|
||||
sb.append(dfs.getGroupingSeparator());
|
||||
} else if (c == '.') {
|
||||
sb.append(dfs.getDecimalSeparator());
|
||||
} else if (c == '0') {
|
||||
sb.append(dfs.getZeroDigit());
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8327640
|
||||
* @summary Unit test for the isStrict() and setStrict() parsing related methods
|
||||
* @run junit StrictMethodsTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.text.CompactNumberFormat;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.FieldPosition;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParsePosition;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class StrictMethodsTest {
|
||||
|
||||
// Check that DecimalFormat implements isStrict()/setStrict()
|
||||
// Ensure that the default value is false, and can be set to true via API
|
||||
@Test
|
||||
public void decimalFormatTest() {
|
||||
DecimalFormat dFmt = (DecimalFormat) NumberFormat.getInstance();
|
||||
assertFalse(dFmt.isStrict());
|
||||
dFmt.setStrict(true);
|
||||
assertTrue(dFmt.isStrict());
|
||||
}
|
||||
|
||||
// Check that CompactNumberFormat implements isStrict()/setStrict()
|
||||
// Ensure that the default value is false, and can be set to true via API
|
||||
@Test
|
||||
public void compactFormatTest() {
|
||||
CompactNumberFormat cFmt = (CompactNumberFormat) NumberFormat.getCompactNumberInstance();
|
||||
assertFalse(cFmt.isStrict());
|
||||
cFmt.setStrict(true);
|
||||
assertTrue(cFmt.isStrict());
|
||||
}
|
||||
|
||||
// Check that NumberFormat throws exception for isStrict()/setStrict()
|
||||
// when subclass does not implement said methods
|
||||
@Test
|
||||
public void numberFormatTest() {
|
||||
FooFormat fmt = new FooFormat();
|
||||
assertThrows(UnsupportedOperationException.class, fmt::isStrict);
|
||||
assertThrows(UnsupportedOperationException.class, () -> fmt.setStrict(false));
|
||||
}
|
||||
|
||||
// Dummy NumberFormat class to check that isStrict() and setStrict()
|
||||
// are not implemented by default
|
||||
private static class FooFormat extends NumberFormat {
|
||||
|
||||
// Provide overrides for abstract methods
|
||||
@Override
|
||||
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number parse(String source, ParsePosition parsePosition) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
525
test/jdk/java/text/Format/NumberFormat/StrictParseTest.java
Normal file
525
test/jdk/java/text/Format/NumberFormat/StrictParseTest.java
Normal file
@ -0,0 +1,525 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8327640
|
||||
* @summary Test suite for NumberFormat parsing with strict leniency
|
||||
* @run junit/othervm -Duser.language=en -Duser.country=US StrictParseTest
|
||||
* @run junit/othervm -Duser.language=ja -Duser.country=JP StrictParseTest
|
||||
* @run junit/othervm -Duser.language=zh -Duser.country=CN StrictParseTest
|
||||
* @run junit/othervm -Duser.language=tr -Duser.country=TR StrictParseTest
|
||||
* @run junit/othervm -Duser.language=de -Duser.country=DE StrictParseTest
|
||||
* @run junit/othervm -Duser.language=fr -Duser.country=FR StrictParseTest
|
||||
* @run junit/othervm -Duser.language=ar -Duser.country=AR StrictParseTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.text.CompactNumberFormat;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
// Tests strict parsing, this is done by testing the NumberFormat factory instances
|
||||
// against a number of locales with different formatting conventions. The locales
|
||||
// used all use a grouping size of 3.
|
||||
public class StrictParseTest {
|
||||
|
||||
// Used to retrieve the locale's expected symbols
|
||||
private static final DecimalFormatSymbols dfs =
|
||||
new DecimalFormatSymbols(Locale.getDefault());
|
||||
// We re-use these formats for the respective factory tests
|
||||
private static final DecimalFormat dFmt =
|
||||
(DecimalFormat) NumberFormat.getNumberInstance(Locale.getDefault());
|
||||
private static final DecimalFormat cFmt =
|
||||
(DecimalFormat) NumberFormat.getCurrencyInstance(Locale.getDefault());
|
||||
private static final DecimalFormat pFmt =
|
||||
(DecimalFormat) NumberFormat.getPercentInstance(Locale.getDefault());
|
||||
private static final CompactNumberFormat cmpctFmt =
|
||||
(CompactNumberFormat) NumberFormat.getCompactNumberInstance(Locale.getDefault(),
|
||||
NumberFormat.Style.SHORT);
|
||||
|
||||
|
||||
// All NumberFormats should parse strictly
|
||||
static {
|
||||
dFmt.setStrict(true);
|
||||
pFmt.setStrict(true);
|
||||
cFmt.setStrict(true);
|
||||
cmpctFmt.setStrict(true);
|
||||
// To effectively test strict compactNumberFormat parsing
|
||||
cmpctFmt.setParseIntegerOnly(false);
|
||||
cmpctFmt.setGroupingUsed(true);
|
||||
cmpctFmt.setGroupingSize(3);
|
||||
}
|
||||
|
||||
// ---- NumberFormat tests ----
|
||||
|
||||
// Guarantee some edge case test input
|
||||
@Test // Non-localized, run once
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void uniqueCaseNumberFormatTest() {
|
||||
// Format with grouping size = 3, prefix = a, suffix = b
|
||||
DecimalFormat nonLocalizedDFmt = new DecimalFormat("a#,#00.00b");
|
||||
nonLocalizedDFmt.setStrict(true);
|
||||
// Text after suffix
|
||||
failParse(nonLocalizedDFmt, "a12bfoo", 3);
|
||||
failParse(nonLocalizedDFmt, "a123,456.00bc", 11);
|
||||
// Text after prefix
|
||||
failParse(nonLocalizedDFmt, "ac123", 0);
|
||||
// Missing suffix
|
||||
failParse(nonLocalizedDFmt, "a123", 4);
|
||||
// Prefix contains a decimal separator
|
||||
failParse(nonLocalizedDFmt, ".a123", 0);
|
||||
// Test non grouping size of 3
|
||||
nonLocalizedDFmt.setGroupingSize(1);
|
||||
successParse(nonLocalizedDFmt, "a1,2,3,4b");
|
||||
failParse(nonLocalizedDFmt, "a1,2,3,45,6b", 8);
|
||||
nonLocalizedDFmt.setGroupingSize(5);
|
||||
successParse(nonLocalizedDFmt, "a12345,67890b");
|
||||
successParse(nonLocalizedDFmt, "a1234,67890b");
|
||||
failParse(nonLocalizedDFmt, "a123456,7890b", 6);
|
||||
|
||||
}
|
||||
|
||||
// All input Strings should fail
|
||||
@ParameterizedTest
|
||||
@MethodSource("badParseStrings")
|
||||
public void numFmtFailParseTest(String toParse, int expectedErrorIndex) {
|
||||
failParse(dFmt, toParse, expectedErrorIndex);
|
||||
}
|
||||
|
||||
// All input Strings should pass and return expected value.
|
||||
@ParameterizedTest
|
||||
@MethodSource("validParseStrings")
|
||||
public void numFmtSuccessParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(dFmt, toParse));
|
||||
}
|
||||
|
||||
// All input Strings should fail
|
||||
@ParameterizedTest
|
||||
@MethodSource("negativeBadParseStrings")
|
||||
public void negNumFmtFailParseTest(String toParse, int expectedErrorIndex) {
|
||||
failParse(dFmt, toParse, expectedErrorIndex);
|
||||
}
|
||||
|
||||
// All input Strings should pass and return expected value.
|
||||
@ParameterizedTest
|
||||
@MethodSource("negativeValidParseStrings")
|
||||
public void negNumFmtSuccessParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(dFmt, toParse));
|
||||
}
|
||||
|
||||
// Exception should be thrown if grouping separator occurs anywhere
|
||||
// Don't pass badParseStrings as a data source, since they may fail for other reasons
|
||||
@ParameterizedTest
|
||||
@MethodSource({"validParseStrings", "noGroupingParseStrings"})
|
||||
public void numFmtStrictGroupingNotUsed(String toParse) {
|
||||
// When grouping is not used, if a grouping separator is found,
|
||||
// a failure should occur
|
||||
dFmt.setGroupingUsed(false);
|
||||
int failIndex = toParse.indexOf(
|
||||
dFmt.getDecimalFormatSymbols().getGroupingSeparator());
|
||||
if (failIndex > -1) {
|
||||
failParse(dFmt, toParse, failIndex);
|
||||
} else {
|
||||
successParse(dFmt, toParse);
|
||||
}
|
||||
dFmt.setGroupingUsed(true);
|
||||
}
|
||||
|
||||
// Exception should be thrown if decimal separator occurs anywhere
|
||||
// Don't pass badParseStrings for same reason as previous method.
|
||||
@ParameterizedTest
|
||||
@MethodSource({"validParseStrings", "integerOnlyParseStrings"})
|
||||
public void numFmtStrictIntegerOnlyUsed(String toParse) {
|
||||
// When integer only is true, if a decimal separator is found,
|
||||
// a failure should occur
|
||||
dFmt.setParseIntegerOnly(true);
|
||||
int failIndex = toParse.indexOf(dfs.getDecimalSeparator());
|
||||
if (failIndex > -1) {
|
||||
failParse(dFmt, toParse, failIndex);
|
||||
} else {
|
||||
successParse(dFmt, toParse);
|
||||
}
|
||||
dFmt.setParseIntegerOnly(false);
|
||||
}
|
||||
|
||||
// ---- CurrencyFormat tests ----
|
||||
@ParameterizedTest
|
||||
@MethodSource("currencyBadParseStrings")
|
||||
public void currFmtFailParseTest(String toParse, int expectedErrorIndex) {
|
||||
failParse(cFmt, toParse, expectedErrorIndex);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("currencyValidParseStrings")
|
||||
public void currFmtSuccessParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(cFmt, toParse));
|
||||
}
|
||||
|
||||
// ---- PercentFormat tests ----
|
||||
@ParameterizedTest
|
||||
@MethodSource("percentBadParseStrings")
|
||||
public void percentFmtFailParseTest(String toParse, int expectedErrorIndex) {
|
||||
failParse(pFmt, toParse, expectedErrorIndex);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("percentValidParseStrings")
|
||||
public void percentFmtSuccessParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(pFmt, toParse));
|
||||
}
|
||||
|
||||
// ---- CompactNumberFormat tests ----
|
||||
// Can match to both the decimalFormat patterns and the compact patterns
|
||||
// Thus we test leniency for both. Unlike the other tests, this test
|
||||
// is only ran against the US Locale and tests against data built with the
|
||||
// thousands format (K).
|
||||
@ParameterizedTest
|
||||
@MethodSource("compactBadParseStrings")
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void compactFmtFailParseTest(String toParse, int expectedErrorIndex) {
|
||||
failParse(cmpctFmt, toParse, expectedErrorIndex);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("compactValidParseStrings")
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void compactFmtSuccessParseTest(String toParse, double expectedValue) {
|
||||
assertEquals(expectedValue, successParse(cmpctFmt, toParse));
|
||||
}
|
||||
|
||||
// Checks some odd leniency edge cases between matching of default pattern
|
||||
// and compact pattern.
|
||||
@Test // Non-localized, run once
|
||||
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||
public void compactFmtEdgeParseTest() {
|
||||
// Uses a compact format with unique and non-empty prefix/suffix for both
|
||||
// default and compact patterns
|
||||
CompactNumberFormat cnf = new CompactNumberFormat("a##0.0#b", DecimalFormatSymbols
|
||||
.getInstance(Locale.US), new String[]{"", "c0d"});
|
||||
cnf.setStrict(true);
|
||||
|
||||
// Existing behavior of failed prefix parsing has errorIndex return
|
||||
// the beginning of prefix, even if the error occurred later in the prefix.
|
||||
// Prefix empty
|
||||
failParse(cnf, "12345d", 0);
|
||||
failParse(cnf, "1b", 0);
|
||||
// Prefix bad
|
||||
failParse(cnf, "aa1d", 0);
|
||||
failParse(cnf, "cc1d", 0);
|
||||
failParse(cnf, "aa1b", 0);
|
||||
failParse(cnf, "cc1b", 0);
|
||||
|
||||
// Suffix error index is always the start of the failed suffix
|
||||
// not necessarily where the error occurred in the suffix. This is
|
||||
// consistent with the prefix error index behavior.
|
||||
// Suffix empty
|
||||
failParse(cnf, "a1", 2);
|
||||
failParse(cnf, "c1", 2);
|
||||
// Suffix bad
|
||||
failParse(cnf, "a1dd", 2);
|
||||
failParse(cnf, "c1dd", 2);
|
||||
failParse(cnf, "a1bb", 2);
|
||||
failParse(cnf, "c1bb", 2);
|
||||
}
|
||||
|
||||
// Ensure that on failure, the original index of the PP remains the same
|
||||
@Test
|
||||
public void parsePositionIndexTest() {
|
||||
failParse(dFmt, localizeText("123,456,,789.00"), 8, 4);
|
||||
}
|
||||
|
||||
// ---- Helper test methods ----
|
||||
|
||||
// Should parse entire String successfully, and return correctly parsed value.
|
||||
private double successParse(NumberFormat fmt, String toParse) {
|
||||
// For Strings that don't have grouping separators, we test them with
|
||||
// grouping off so that they do not fail under the expectation that
|
||||
// grouping symbols should occur
|
||||
if (!toParse.contains(String.valueOf(dfs.getGroupingSeparator())) &&
|
||||
!toParse.contains(String.valueOf(dfs.getMonetaryGroupingSeparator()))) {
|
||||
fmt.setGroupingUsed(false);
|
||||
}
|
||||
Number parsedValue = assertDoesNotThrow(() -> fmt.parse(toParse));
|
||||
ParsePosition pp = new ParsePosition(0);
|
||||
assertDoesNotThrow(() -> fmt.parse(toParse, pp));
|
||||
assertEquals(-1, pp.getErrorIndex(),
|
||||
"ParsePosition ErrorIndex is not in correct location");
|
||||
assertEquals(toParse.length(), pp.getIndex(),
|
||||
"ParsePosition Index is not in correct location");
|
||||
fmt.setGroupingUsed(true);
|
||||
return parsedValue.doubleValue();
|
||||
}
|
||||
|
||||
// Method which tests a parsing failure. Either a ParseException is thrown,
|
||||
// or null is returned depending on which parse method is invoked. When failing,
|
||||
// index should remain the initial index set to the ParsePosition while
|
||||
// errorIndex is the index of failure.
|
||||
private void failParse(NumberFormat fmt, String toParse, int expectedErrorIndex) {
|
||||
failParse(fmt, toParse, expectedErrorIndex, 0);
|
||||
}
|
||||
|
||||
// Variant to check non 0 initial parse index
|
||||
private void failParse(NumberFormat fmt, String toParse,
|
||||
int expectedErrorIndex, int initialParseIndex) {
|
||||
ParsePosition pp = new ParsePosition(initialParseIndex);
|
||||
assertThrows(ParseException.class, () -> fmt.parse(toParse));
|
||||
assertNull(fmt.parse(toParse, pp));
|
||||
assertEquals(expectedErrorIndex, pp.getErrorIndex());
|
||||
assertEquals(initialParseIndex, pp.getIndex());
|
||||
}
|
||||
|
||||
// ---- Data Providers ----
|
||||
// These data providers use US locale grouping and decimal separators
|
||||
// for readability, however, the data is tested against multiple locales
|
||||
// and is converted appropriately at runtime.
|
||||
|
||||
// Strings that should fail when parsed with strict leniency.
|
||||
// Given as Arguments<String, expectedErrorIndex>
|
||||
private static Stream<Arguments> badParseStrings() {
|
||||
return Stream.of(
|
||||
// Grouping symbol focus
|
||||
// Grouping symbol right before decimal
|
||||
Arguments.of("1,.", 2),
|
||||
Arguments.of("1,.1", 2),
|
||||
// Does not end with proper grouping size
|
||||
Arguments.of("1,1", 2),
|
||||
Arguments.of("1,11", 3),
|
||||
Arguments.of("1,1111", 5),
|
||||
Arguments.of("11,111,11", 8),
|
||||
// Does not end with proper grouping size (with decimal)
|
||||
Arguments.of("1,1.", 3),
|
||||
Arguments.of("1,11.", 4),
|
||||
Arguments.of("1,1111.", 5),
|
||||
Arguments.of("11,111,11.", 9),
|
||||
// Ends on a grouping symbol
|
||||
// Suffix matches correctly, so failure is on the ","
|
||||
Arguments.of("11,111,", 6),
|
||||
Arguments.of("11,", 2),
|
||||
Arguments.of("11,,", 3),
|
||||
// Ends with grouping symbol. Failure should occur on grouping,
|
||||
// even if non recognized char after
|
||||
Arguments.of("11,a", 2),
|
||||
// Improper grouping size (with decimal and digits after)
|
||||
Arguments.of("1,1.1", 3),
|
||||
Arguments.of("1,11.1", 4),
|
||||
Arguments.of("1,1111.1", 5),
|
||||
Arguments.of("11,111,11.1", 9),
|
||||
// Subsequent grouping symbols
|
||||
Arguments.of("1,,1", 2),
|
||||
Arguments.of("1,1,,1", 3),
|
||||
Arguments.of("1,,1,1", 2),
|
||||
// Invalid grouping sizes
|
||||
Arguments.of("1,11,111", 4),
|
||||
Arguments.of("11,11,111", 5),
|
||||
Arguments.of("111,11,11", 6),
|
||||
// First group is too large
|
||||
Arguments.of("1111,11,111", 3),
|
||||
Arguments.of("00000,11,111", 3),
|
||||
Arguments.of("111,1111111111", 7),
|
||||
Arguments.of("111,11", 5),
|
||||
Arguments.of("111,1111111111.", 7),
|
||||
Arguments.of("111,11.", 6),
|
||||
Arguments.of("111,1111111111.", 7),
|
||||
// Starts with grouping symbol
|
||||
Arguments.of(",111,,1,1", 0),
|
||||
Arguments.of(",1", 0),
|
||||
Arguments.of(",,1", 0),
|
||||
// Leading Zeros (not digits)
|
||||
Arguments.of("000,1,1", 5),
|
||||
Arguments.of("000,111,11,,1", 10),
|
||||
Arguments.of("0,000,1,,1,1", 7),
|
||||
// Bad suffix
|
||||
Arguments.of("1a", 1),
|
||||
// Bad chars in numerical portion
|
||||
Arguments.of("123a4", 3),
|
||||
Arguments.of("123.4a5", 5),
|
||||
// Variety of edge cases
|
||||
Arguments.of("123,456.77a", 10),
|
||||
Arguments.of("1,234a", 5),
|
||||
Arguments.of("1,.a", 2),
|
||||
Arguments.of("1.a", 2),
|
||||
Arguments.of(".22a", 3),
|
||||
Arguments.of(".1a1", 2),
|
||||
Arguments.of("1,234,a", 5))
|
||||
.map(args -> Arguments.of(
|
||||
localizeText(String.valueOf(args.get()[0])), args.get()[1]));
|
||||
}
|
||||
|
||||
// Strings that should parse fully. (Both in lenient and strict)
|
||||
// Given as Arguments<String, expectedParsedNumber>
|
||||
private static Stream<Arguments> validParseStrings() {
|
||||
return Stream.of(
|
||||
Arguments.of("1,234.00", 1234d),
|
||||
Arguments.of("1,234.0", 1234d),
|
||||
Arguments.of("1,234.", 1234d),
|
||||
Arguments.of("1", 1d),
|
||||
Arguments.of("10", 10d),
|
||||
Arguments.of("100", 100d),
|
||||
Arguments.of("1000", 1000d),
|
||||
Arguments.of("1,000", 1000d),
|
||||
Arguments.of("10,000", 10000d),
|
||||
Arguments.of("10000", 10000d),
|
||||
Arguments.of("100,000", 100000d),
|
||||
Arguments.of("1,000,000", 1000000d),
|
||||
Arguments.of("10,000,000", 10000000d))
|
||||
.map(args -> Arguments.of(
|
||||
localizeText(String.valueOf(args.get()[0])), args.get()[1]));
|
||||
}
|
||||
|
||||
// Separate test data set for integer only. Can not use "badParseStrings", as
|
||||
// there is test data where the failure may occur from some other issue,
|
||||
// not related to grouping
|
||||
private static Stream<Arguments> integerOnlyParseStrings() {
|
||||
return Stream.of(
|
||||
Arguments.of("234.a"),
|
||||
Arguments.of("234.a1"),
|
||||
Arguments.of("234.1"),
|
||||
Arguments.of("234.1a"),
|
||||
Arguments.of("234."))
|
||||
.map(args -> Arguments.of(localizeText(String.valueOf(args.get()[0]))));
|
||||
}
|
||||
|
||||
// Separate test data set for no grouping. Can not use "badParseStrings", as
|
||||
// there is test data where the failure may occur from some other issue,
|
||||
// not related to grouping
|
||||
private static Stream<Arguments> noGroupingParseStrings() {
|
||||
return Stream.of(
|
||||
Arguments.of("12,34.a"),
|
||||
Arguments.of("123,.a1"),
|
||||
Arguments.of(",1234"),
|
||||
Arguments.of("123,"))
|
||||
.map(args -> Arguments.of(localizeText(String.valueOf(args.get()[0]))));
|
||||
}
|
||||
|
||||
// Negative variant of a numerical format
|
||||
private static Stream<Arguments> negativeBadParseStrings() {
|
||||
return badParseStrings().map(args -> Arguments.of(
|
||||
dFmt.getNegativePrefix() + args.get()[0] + dFmt.getNegativeSuffix(),
|
||||
(int)args.get()[1] + dFmt.getNegativePrefix().length())
|
||||
);
|
||||
}
|
||||
|
||||
// Negative variant of a numerical format
|
||||
private static Stream<Arguments> negativeValidParseStrings() {
|
||||
return validParseStrings().map(args -> Arguments.of(
|
||||
dFmt.getNegativePrefix() + args.get()[0] + dFmt.getNegativeSuffix(),
|
||||
(double) args.get()[1] * -1)
|
||||
);
|
||||
}
|
||||
|
||||
// Same as original with a percent prefix/suffix.
|
||||
// Additionally, increment expected error index if a prefix is added
|
||||
private static Stream<Arguments> percentBadParseStrings() {
|
||||
return badParseStrings().map(args -> Arguments.of(
|
||||
pFmt.getPositivePrefix() + args.get()[0] + pFmt.getPositiveSuffix(),
|
||||
(int)args.get()[1] + pFmt.getPositivePrefix().length())
|
||||
);
|
||||
}
|
||||
|
||||
// Expected parsed value should be / 100 as it is a percent format.
|
||||
private static Stream<Arguments> percentValidParseStrings() {
|
||||
return validParseStrings().map(args -> Arguments.of(
|
||||
pFmt.getPositivePrefix() + args.get()[0] + pFmt.getPositiveSuffix(),
|
||||
(double)args.get()[1] / 100.0)
|
||||
);
|
||||
}
|
||||
|
||||
// Same as original with a currency prefix/suffix, but replace separators
|
||||
// with monetary variants. Additionally, increment expected error index
|
||||
// if a prefix is added
|
||||
private static Stream<Arguments> currencyBadParseStrings() {
|
||||
return badParseStrings().map(args -> Arguments.of(
|
||||
cFmt.getPositivePrefix() + String.valueOf(args.get()[0])
|
||||
.replace(dfs.getGroupingSeparator(), dfs.getMonetaryGroupingSeparator())
|
||||
.replace(dfs.getDecimalSeparator(), dfs.getMonetaryDecimalSeparator())
|
||||
+ cFmt.getPositiveSuffix(),
|
||||
(int)args.get()[1] + cFmt.getPositivePrefix().length())
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> currencyValidParseStrings() {
|
||||
return validParseStrings().map(args -> Arguments.of(
|
||||
cFmt.getPositivePrefix() + String.valueOf(args.get()[0])
|
||||
.replace(dfs.getGroupingSeparator(), dfs.getMonetaryGroupingSeparator())
|
||||
.replace(dfs.getDecimalSeparator(), dfs.getMonetaryDecimalSeparator())
|
||||
+ cFmt.getPositiveSuffix(),
|
||||
args.get()[1])
|
||||
);
|
||||
}
|
||||
|
||||
// Compact Pattern Data Provider provides test input for both DecimalFormat patterns
|
||||
// and the compact patterns. As there is no method to retrieve compact patterns,
|
||||
// thus test only against US English locale, and use a hard coded K - 1000
|
||||
private static Stream<Arguments> compactBadParseStrings() {
|
||||
return Stream.concat(
|
||||
badParseStrings().map(args -> Arguments.of(args.get()[0], args.get()[1])),
|
||||
badParseStrings().map(args -> Arguments.of(args.get()[0] + "K", args.get()[1]))
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> compactValidParseStrings() {
|
||||
return Stream.concat(
|
||||
validParseStrings().map(args -> Arguments.of(
|
||||
args.get()[0], args.get()[1])),
|
||||
validParseStrings().map(args -> Arguments.of(
|
||||
args.get()[0] + "K", (double) args.get()[1] * 1000))
|
||||
);
|
||||
}
|
||||
|
||||
// Replace the grouping and decimal separators with localized variants
|
||||
// Used during localization of data
|
||||
private static String localizeText(String text) {
|
||||
// As this is a single pass conversion, this is safe for multiple replacement,
|
||||
// even if a ',' could be a decimal separator for a locale.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
if (c == ',') {
|
||||
sb.append(dfs.getGroupingSeparator());
|
||||
} else if (c == '.') {
|
||||
sb.append(dfs.getDecimalSeparator());
|
||||
} else if (c == '0') {
|
||||
sb.append(dfs.getZeroDigit());
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user