Skip to content

Follow-up to #1157: unify exception handling #1159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 61 additions & 32 deletions src/main/java/com/fasterxml/jackson/core/io/BigDecimalParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@
import ch.randelshofer.fastdoubleparser.JavaBigDecimalParser;

import java.math.BigDecimal;
import java.util.Arrays;

// Based on a great idea of Eric Obermühlner to use a tree of smaller BigDecimals for parsing
// really big numbers with O(n^1.5) complexity instead of O(n^2) when using the constructor
// for a decimal representation from JDK 8/11:
//
// https://github.com/eobermuhlner/big-math/commit/7a5419aac8b2adba2aa700ccf00197f97b2ad89f

/**
* Internal Jackson Helper class used to implement more optimized parsing of {@link BigDecimal} for REALLY
Expand Down Expand Up @@ -37,6 +30,10 @@ private BigDecimalParser() {}

/**
* Internal Jackson method. Please do not use.
*<p>
* Note: Caller MUST pre-validate that given String represents a valid representation
* of {@link BigDecimal} value: parsers in {@code jackson-core} do that; other
* code must do the same.
*
* @param valueStr
* @return BigDecimal value
Expand All @@ -48,6 +45,10 @@ public static BigDecimal parse(String valueStr) {

/**
* Internal Jackson method. Please do not use.
*<p>
* Note: Caller MUST pre-validate that given String represents a valid representation
* of {@link BigDecimal} value: parsers in {@code jackson-core} do that; other
* code must do the same.
*
* @return BigDecimal value
* @throws NumberFormatException
Expand All @@ -62,25 +63,16 @@ public static BigDecimal parse(final char[] chars, final int off, final int len)
// 20-Aug-2022, tatu: Although "new BigDecimal(...)" only throws NumberFormatException
// operations by "parseBigDecimal()" can throw "ArithmeticException", so handle both:
} catch (ArithmeticException | NumberFormatException e) {
String desc = e.getMessage();
// 05-Feb-2021, tatu: Alas, JDK mostly has null message so:
if (desc == null) {
desc = "Not a valid number representation";
}
String stringToReport;
if (len <= MAX_CHARS_TO_REPORT) {
stringToReport = new String(chars, off, len);
} else {
stringToReport = new String(Arrays.copyOfRange(chars, off, MAX_CHARS_TO_REPORT))
+ "(truncated, full length is " + chars.length + " chars)";
}
throw new NumberFormatException("Value \"" + stringToReport
+ "\" can not be represented as `java.math.BigDecimal`, reason: " + desc);
throw _parseFailure(e, new String(chars, off, len));
}
}

/**
* Internal Jackson method. Please do not use.
*<p>
* Note: Caller MUST pre-validate that given String represents a valid representation
* of {@link BigDecimal} value: parsers in {@code jackson-core} do that; other
* code must do the same.
*
* @param chars
* @return BigDecimal value
Expand All @@ -90,25 +82,62 @@ public static BigDecimal parse(char[] chars) {
return parse(chars, 0, chars.length);
}

/**
* Internal Jackson method. Please do not use.
*<p>
* Note: Caller MUST pre-validate that given String represents a valid representation
* of {@link BigDecimal} value: parsers in {@code jackson-core} do that; other
* code must do the same.
*
* @param valueStr
* @return BigDecimal value
* @throws NumberFormatException
*/
public static BigDecimal parseWithFastParser(final String valueStr) {
try {
return JavaBigDecimalParser.parseBigDecimal(valueStr);
} catch (NumberFormatException nfe) {
final String reportNum = valueStr.length() <= MAX_CHARS_TO_REPORT ?
valueStr : valueStr.substring(0, MAX_CHARS_TO_REPORT) + " [truncated]";
throw new NumberFormatException("Value \"" + reportNum
+ "\" can not be represented as `java.math.BigDecimal`, reason: " + nfe.getMessage());
} catch (ArithmeticException | NumberFormatException e) {
throw _parseFailure(e, valueStr);
}
}

/**
* Internal Jackson method. Please do not use.
*<p>
* Note: Caller MUST pre-validate that given String represents a valid representation
* of {@link BigDecimal} value: parsers in {@code jackson-core} do that; other
* code must do the same.
*
* @return BigDecimal value
* @throws NumberFormatException
*/
public static BigDecimal parseWithFastParser(final char[] ch, final int off, final int len) {
try {
return JavaBigDecimalParser.parseBigDecimal(ch, off, len);
} catch (NumberFormatException nfe) {
final String reportNum = len <= MAX_CHARS_TO_REPORT ?
new String(ch, off, len) : new String(ch, off, MAX_CHARS_TO_REPORT) + " [truncated]";
throw new NumberFormatException("Value \"" + reportNum
+ "\" can not be represented as `java.math.BigDecimal`, reason: " + nfe.getMessage());
} catch (ArithmeticException | NumberFormatException e) {
throw _parseFailure(e, new String(ch, off, len));
}
}

private static NumberFormatException _parseFailure(Exception e, String fullValue) {
String desc = e.getMessage();
// 05-Feb-2021, tatu: Alas, JDK mostly has null message so:
if (desc == null) {
desc = "Not a valid number representation";
}
String valueToReport = _getValueDesc(fullValue);
return new NumberFormatException("Value " + valueToReport
+ " can not be deserialized as `java.math.BigDecimal`, reason: " + desc);
}

private static String _getValueDesc(String fullValue) {
final int len = fullValue.length();
if (len <= MAX_CHARS_TO_REPORT) {
return String.format("\"%s\"", fullValue);
}
return String.format("\"%s\" (truncated to %d chars (from %d))",
fullValue.substring(0, MAX_CHARS_TO_REPORT),
MAX_CHARS_TO_REPORT, len);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ private void _testAllowNaN(JsonFactory f, String doc, int readBytes) throws Exce
/*BigDecimal dec =*/ p.getDecimalValue();
fail("Should fail when trying to access NaN as BigDecimal");
} catch (NumberFormatException e) {
verifyException(e, "can not be represented as `java.math.BigDecimal`");
verifyException(e, "can not be deserialized as `java.math.BigDecimal`");
}

assertToken(JsonToken.END_ARRAY, p.nextToken());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ private void _testAllowNaN(int mode) throws Exception
/*BigDecimal dec =*/ p.getDecimalValue();
fail("Should fail when trying to access NaN as BigDecimal");
} catch (NumberFormatException e) {
verifyException(e, "can not be represented as `java.math.BigDecimal`");
verifyException(e, "can not be deserialized as `java.math.BigDecimal`");
}

assertToken(JsonToken.END_ARRAY, p.nextToken());
Expand Down