[jOOQ/jOOQ#8736] Avoid NumberFormatException in Convert
This commit makes `NumberFormatException`s less likely to happen in `Convert.ConvertAll#from(Object)` by first attempting to parse the string representation of the value using `Ints#tryParse()` or `Longs#tryParse()` (as appropriate). If that fails the conversion will still try to use `BigDecimal(String)`, as that also allows parsing numbers with fractions and / or exponent. Note that this change required moving `tryParseInt()` and `tryParseLong()` from `Tools` to the new public classes `Ints` and `Longs` which have the license header from the corresponding Guava sources, where the methods were copied from.
This commit is contained in:
parent
dce1a6a2b9
commit
ae13be078d
@ -80,6 +80,7 @@ import org.jooq.conf.SettingsTools;
|
||||
import org.jooq.conf.StatementType;
|
||||
import org.jooq.exception.ControlFlowSignal;
|
||||
import org.jooq.exception.DetachedException;
|
||||
import org.jooq.tools.Ints;
|
||||
import org.jooq.tools.JooqLogger;
|
||||
|
||||
/**
|
||||
@ -160,7 +161,7 @@ abstract class AbstractQuery extends AbstractQueryPart implements Query {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public Query bind(String param, Object value) {
|
||||
Integer index = Tools.tryParseInt(param);
|
||||
Integer index = Ints.tryParse(param);
|
||||
if (index != null)
|
||||
return bind(index, value);
|
||||
|
||||
|
||||
@ -172,6 +172,7 @@ import org.jooq.exception.MappingException;
|
||||
import org.jooq.exception.SQLDialectNotSupportedException;
|
||||
import org.jooq.tools.Convert;
|
||||
import org.jooq.tools.JooqLogger;
|
||||
import org.jooq.tools.Longs;
|
||||
import org.jooq.tools.StringUtils;
|
||||
import org.jooq.tools.jdbc.JDBCUtils;
|
||||
import org.jooq.tools.jdbc.MockArray;
|
||||
@ -443,7 +444,7 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
|
||||
private static final long parse(Class<? extends java.util.Date> type, String date) throws SQLException {
|
||||
|
||||
// Try reading a plain number first
|
||||
Long number = Tools.tryParseLong(date);
|
||||
Long number = Longs.tryParse(date);
|
||||
if (number != null)
|
||||
return number;
|
||||
|
||||
|
||||
@ -255,6 +255,7 @@ import org.jooq.exception.NoDataFoundException;
|
||||
import org.jooq.exception.TooManyRowsException;
|
||||
import org.jooq.impl.ResultsImpl.ResultOrRowsImpl;
|
||||
import org.jooq.impl.Tools.Cache.CachedOperation;
|
||||
import org.jooq.tools.Ints;
|
||||
import org.jooq.tools.JooqLogger;
|
||||
import org.jooq.tools.StringUtils;
|
||||
import org.jooq.tools.jdbc.JDBCUtils;
|
||||
@ -1752,91 +1753,6 @@ final class Tools {
|
||||
return result;
|
||||
}
|
||||
|
||||
static Integer tryParseInt(String string) {
|
||||
return tryParseInt(string, 0, string.length());
|
||||
}
|
||||
|
||||
// adapted from com.google.common.primitives.Longs#tryParse()
|
||||
private static Integer tryParseInt(String string, int begin, int end) {
|
||||
if (string == null || end - begin < 1)
|
||||
return null;
|
||||
|
||||
int radix = 10;
|
||||
char firstChar = string.charAt(begin);
|
||||
boolean negative = firstChar == '-';
|
||||
int index = negative || firstChar == '+' ? begin + 1 : begin;
|
||||
if (index == end)
|
||||
return null;
|
||||
|
||||
int digit = Character.digit(string.charAt(index++), 10);
|
||||
if (digit < 0 || digit >= radix)
|
||||
return null;
|
||||
|
||||
int accum = -digit;
|
||||
|
||||
int cap = Integer.MIN_VALUE / radix;
|
||||
|
||||
while (index < end) {
|
||||
digit = Character.digit(string.charAt(index++), 10);
|
||||
if (digit < 0 || digit >= radix || accum < cap)
|
||||
return null;
|
||||
|
||||
accum *= radix;
|
||||
if (accum < Integer.MIN_VALUE + digit)
|
||||
return null;
|
||||
|
||||
accum -= digit;
|
||||
}
|
||||
|
||||
if (negative)
|
||||
return accum;
|
||||
else if (accum == Integer.MIN_VALUE)
|
||||
return null;
|
||||
else
|
||||
return -accum;
|
||||
}
|
||||
|
||||
// adapted from com.google.common.primitives.Longs#tryParse()
|
||||
static Long tryParseLong(String string) {
|
||||
if (string == null || string.isEmpty())
|
||||
return null;
|
||||
|
||||
int radix = 10;
|
||||
char firstChar = string.charAt(0);
|
||||
boolean negative = firstChar == '-';
|
||||
int index = negative || firstChar == '+' ? 1 : 0;
|
||||
int length = string.length();
|
||||
if (index == length)
|
||||
return null;
|
||||
|
||||
int digit = Character.digit(string.charAt(index++), 10);
|
||||
if (digit < 0 || digit >= radix)
|
||||
return null;
|
||||
|
||||
long accum = -digit;
|
||||
|
||||
long cap = Long.MIN_VALUE / radix;
|
||||
|
||||
while (index < length) {
|
||||
digit = Character.digit(string.charAt(index++), 10);
|
||||
if (digit < 0 || digit >= radix || accum < cap)
|
||||
return null;
|
||||
|
||||
accum *= radix;
|
||||
if (accum < Long.MIN_VALUE + digit)
|
||||
return null;
|
||||
|
||||
accum -= digit;
|
||||
}
|
||||
|
||||
if (negative)
|
||||
return accum;
|
||||
else if (accum == Long.MIN_VALUE)
|
||||
return null;
|
||||
else
|
||||
return -accum;
|
||||
}
|
||||
|
||||
static final <T> List<Field<T>> inline(T[] values) {
|
||||
if (values == null)
|
||||
return new ArrayList<Field<T>>();
|
||||
@ -2514,7 +2430,7 @@ final class Tools {
|
||||
int end = i;
|
||||
|
||||
// Try getting the {numbered placeholder}
|
||||
Integer index = tryParseInt(sql, start, end);
|
||||
Integer index = Ints.tryParse(sql, start, end);
|
||||
if (index != null) {
|
||||
QueryPart substitute = substitutes.get(index);
|
||||
render.visit(substitute);
|
||||
|
||||
@ -580,7 +580,9 @@ public final class Convert {
|
||||
return (U) (((Boolean) from) ? Byte.valueOf((byte) 1) : Byte.valueOf((byte) 0));
|
||||
|
||||
try {
|
||||
return (U) Byte.valueOf(new BigDecimal(from.toString().trim()).byteValue());
|
||||
String fromString = from.toString().trim();
|
||||
Integer asInt = Ints.tryParse(fromString);
|
||||
return (U) Byte.valueOf(asInt != null ? asInt.byteValue() : new BigDecimal(fromString).byteValue());
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return Reflect.initValue(toClass);
|
||||
@ -594,7 +596,9 @@ public final class Convert {
|
||||
return (U) (((Boolean) from) ? Short.valueOf((short) 1) : Short.valueOf((short) 0));
|
||||
|
||||
try {
|
||||
return (U) Short.valueOf(new BigDecimal(from.toString().trim()).shortValue());
|
||||
String fromString = from.toString().trim();
|
||||
Integer asInt = Ints.tryParse(fromString);
|
||||
return (U) Short.valueOf(asInt != null ? asInt.shortValue() : new BigDecimal(fromString).shortValue());
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return Reflect.initValue(toClass);
|
||||
@ -608,7 +612,9 @@ public final class Convert {
|
||||
return (U) (((Boolean) from) ? Integer.valueOf(1) : Integer.valueOf(0));
|
||||
|
||||
try {
|
||||
return (U) Integer.valueOf(new BigDecimal(from.toString().trim()).intValue());
|
||||
String fromString = from.toString().trim();
|
||||
Integer asInt = Ints.tryParse(fromString);
|
||||
return (U) Integer.valueOf(asInt != null ? asInt.intValue() : new BigDecimal(fromString).intValue());
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return Reflect.initValue(toClass);
|
||||
@ -632,7 +638,9 @@ public final class Convert {
|
||||
|
||||
|
||||
try {
|
||||
return (U) Long.valueOf(new BigDecimal(from.toString().trim()).longValue());
|
||||
String fromString = from.toString().trim();
|
||||
Long asLong = Longs.tryParse(fromString);
|
||||
return (U) Long.valueOf(asLong != null ? asLong.longValue() : new BigDecimal(fromString).longValue());
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return Reflect.initValue(toClass);
|
||||
@ -648,7 +656,9 @@ public final class Convert {
|
||||
if (wrapperFrom == Boolean.class)
|
||||
return (U) (((Boolean) from) ? ubyte(1) : ubyte(0));
|
||||
|
||||
return (U) ubyte(new BigDecimal(from.toString().trim()).shortValue());
|
||||
String fromString = from.toString().trim();
|
||||
Integer asInt = Ints.tryParse(fromString);
|
||||
return (U) ubyte(asInt != null ? asInt.shortValue() : new BigDecimal(fromString).shortValue());
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return null;
|
||||
@ -662,7 +672,9 @@ public final class Convert {
|
||||
if (wrapperFrom == Boolean.class)
|
||||
return (U) (((Boolean) from) ? ushort(1) : ushort(0));
|
||||
|
||||
return (U) ushort(new BigDecimal(from.toString().trim()).intValue());
|
||||
String fromString = from.toString().trim();
|
||||
Integer asInt = Ints.tryParse(fromString);
|
||||
return (U) ushort(asInt != null ? asInt.intValue() : new BigDecimal(fromString).intValue());
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return null;
|
||||
@ -676,7 +688,9 @@ public final class Convert {
|
||||
if (wrapperFrom == Boolean.class)
|
||||
return (U) (((Boolean) from) ? uint(1) : uint(0));
|
||||
|
||||
return (U) uint(new BigDecimal(from.toString().trim()).longValue());
|
||||
String fromString = from.toString().trim();
|
||||
Long asLong = Longs.tryParse(fromString);
|
||||
return (U) uint(asLong != null ? asLong.longValue() : new BigDecimal(fromString).longValue());
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return null;
|
||||
@ -695,7 +709,10 @@ public final class Convert {
|
||||
|
||||
|
||||
try {
|
||||
return (U) ulong(new BigDecimal(from.toString().trim()).toBigInteger().toString());
|
||||
String fromString = from.toString().trim();
|
||||
// tryParse() will return null in case of overflow
|
||||
Long asLong = Longs.tryParse(fromString);
|
||||
return asLong != null ? (U) ulong(asLong.longValue()) : (U) ulong(new BigDecimal(fromString).toBigInteger());
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
return null;
|
||||
|
||||
78
jOOQ/src/main/java/org/jooq/tools/Ints.java
Normal file
78
jOOQ/src/main/java/org/jooq/tools/Ints.java
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Guava Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package org.jooq.tools;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Static utility methods pertaining to {@code int} primitives, that are not already found in either
|
||||
* {@link Integer} or {@link Arrays}.
|
||||
*
|
||||
* <p>See the Guava User Guide article on <a
|
||||
* href="https://github.com/google/guava/wiki/PrimitivesExplained">primitive utilities</a>.
|
||||
*
|
||||
* @author Kevin Bourrillion
|
||||
* @since 1.0
|
||||
*/
|
||||
public final class Ints {
|
||||
private Ints() {}
|
||||
|
||||
public static Integer tryParse(String string) {
|
||||
return tryParse(string, 0, string.length());
|
||||
}
|
||||
|
||||
// adapted from com.google.common.primitives.Longs#tryParse()
|
||||
// modified for performance and to allow parsing numbers with leading '+'
|
||||
public static Integer tryParse(String string, int begin, int end) {
|
||||
if (begin < 0 || end > string.length() || end - begin < 1)
|
||||
return null;
|
||||
|
||||
int radix = 10;
|
||||
char firstChar = string.charAt(begin);
|
||||
boolean negative = firstChar == '-';
|
||||
int index = negative || firstChar == '+' ? begin + 1 : begin;
|
||||
if (index == end)
|
||||
return null;
|
||||
|
||||
int digit = Character.digit(string.charAt(index++), 10);
|
||||
if (digit < 0 || digit >= radix)
|
||||
return null;
|
||||
|
||||
int accum = -digit;
|
||||
|
||||
int cap = Integer.MIN_VALUE / radix;
|
||||
|
||||
while (index < end) {
|
||||
digit = Character.digit(string.charAt(index++), 10);
|
||||
if (digit < 0 || digit >= radix || accum < cap)
|
||||
return null;
|
||||
|
||||
accum *= radix;
|
||||
if (accum < Integer.MIN_VALUE + digit)
|
||||
return null;
|
||||
|
||||
accum -= digit;
|
||||
}
|
||||
|
||||
if (negative)
|
||||
return accum;
|
||||
else if (accum == Integer.MIN_VALUE)
|
||||
return null;
|
||||
else
|
||||
return -accum;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
74
jOOQ/src/main/java/org/jooq/tools/Longs.java
Normal file
74
jOOQ/src/main/java/org/jooq/tools/Longs.java
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Guava Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package org.jooq.tools;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Static utility methods pertaining to {@code long} primitives, that are not already found in
|
||||
* either {@link Long} or {@link Arrays}.
|
||||
*
|
||||
* <p>See the Guava User Guide article on <a
|
||||
* href="https://github.com/google/guava/wiki/PrimitivesExplained">primitive utilities</a>.
|
||||
*
|
||||
* @author Kevin Bourrillion
|
||||
* @since 1.0
|
||||
*/
|
||||
public final class Longs {
|
||||
private Longs() {}
|
||||
|
||||
// adapted from com.google.common.primitives.Longs#tryParse()
|
||||
// modified for performance and to allow parsing numbers with leading '+'
|
||||
public static Long tryParse(String string) {
|
||||
if (string.isEmpty())
|
||||
return null;
|
||||
|
||||
int radix = 10;
|
||||
char firstChar = string.charAt(0);
|
||||
boolean negative = firstChar == '-';
|
||||
int index = negative || firstChar == '+' ? 1 : 0;
|
||||
int length = string.length();
|
||||
if (index == length)
|
||||
return null;
|
||||
|
||||
int digit = Character.digit(string.charAt(index++), 10);
|
||||
if (digit < 0 || digit >= radix)
|
||||
return null;
|
||||
|
||||
long accum = -digit;
|
||||
|
||||
long cap = Long.MIN_VALUE / radix;
|
||||
|
||||
while (index < length) {
|
||||
digit = Character.digit(string.charAt(index++), 10);
|
||||
if (digit < 0 || digit >= radix || accum < cap)
|
||||
return null;
|
||||
|
||||
accum *= radix;
|
||||
if (accum < Long.MIN_VALUE + digit)
|
||||
return null;
|
||||
|
||||
accum -= digit;
|
||||
}
|
||||
|
||||
if (negative)
|
||||
return accum;
|
||||
else if (accum == Long.MIN_VALUE)
|
||||
return null;
|
||||
else
|
||||
return -accum;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1424,4 +1424,5 @@ public final class StringUtils {
|
||||
String[] result = new String[matchList.size()];
|
||||
return matchList.toArray(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user