io.vertx.db2client.impl.drda.CrossConverters Maven / Gradle / Ivy
/*
* Copyright (C) 2019,2020 IBM Corporation
*
* 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 io.vertx.db2client.impl.drda;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.Ref;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Calendar;
import java.util.Locale;
// All currently supported types are mapped to one of the following jdbc types:
// Types.SMALLINT;
// Types.INTEGER;
// Types.BIGINT;
// Types.REAL;
// Types.DOUBLE;
// Types.DECIMAL;
// Types.DATE;
// Types.TIME;
// Types.TIMESTAMP;
// Types.CHAR;
// Types.VARCHAR;
// Types.LONGVARCHAR;
// Types.CLOB;
// Types.BLOB;
//
final class CrossConverters {
/**
* Value used to signal unknown length of data.
*/
public static final int UNKNOWN_LENGTH = Integer.MIN_VALUE;
private final static BigDecimal bdMaxByteValue__ =
BigDecimal.valueOf(Byte.MAX_VALUE);
private final static BigDecimal bdMinByteValue__ =
BigDecimal.valueOf(Byte.MIN_VALUE);
private final static BigDecimal bdMaxShortValue__ =
BigDecimal.valueOf(Short.MAX_VALUE);
private final static BigDecimal bdMinShortValue__ =
BigDecimal.valueOf(Short.MIN_VALUE);
private final static BigDecimal bdMaxIntValue__ =
BigDecimal.valueOf(Integer.MAX_VALUE);
private final static BigDecimal bdMinIntValue__ =
BigDecimal.valueOf(Integer.MIN_VALUE);
private final static BigDecimal bdMaxLongValue__ =
BigDecimal.valueOf(Long.MAX_VALUE);
private final static BigDecimal bdMinLongValue__ =
BigDecimal.valueOf(Long.MIN_VALUE);
private final static BigDecimal bdMaxFloatValue__ =
new BigDecimal(Float.MAX_VALUE);
private final static BigDecimal bdMinFloatValue__ =
new BigDecimal(-Float.MAX_VALUE);
private final static BigDecimal bdMaxDoubleValue__ =
new BigDecimal(Double.MAX_VALUE);
private final static BigDecimal bdMinDoubleValue__ =
new BigDecimal(-Double.MAX_VALUE);
// Since BigDecimals are immutable, we can return pointers to these canned 0's and 1's.
private final static BigDecimal bdZero__ = BigDecimal.valueOf(0);
private final static BigDecimal bdOne__ = BigDecimal.valueOf(1);
private CrossConverters() {
}
// ---------------------------------------------------------------------------
// The following methods are used for input cross conversion.
// ---------------------------------------------------------------------------
//---------------------------- setObject() methods ---------------------------
// Convert from boolean source to target type.
// In support of PS.setBoolean().
static final Object setObject(int targetType, boolean source) {
short numVal = source ? (short) 1 : 0;
switch (targetType) {
case ClientTypes.BIT:
case ClientTypes.BOOLEAN:
return Boolean.valueOf(source);
case ClientTypes.SMALLINT:
return Short.valueOf(numVal);
case ClientTypes.INTEGER:
return Integer.valueOf(numVal);
case ClientTypes.BIGINT:
return Long.valueOf(numVal);
case ClientTypes.REAL:
return Float.valueOf(numVal);
case ClientTypes.DOUBLE:
return Double.valueOf(numVal);
case ClientTypes.DECIMAL:
return BigDecimal.valueOf(numVal);
case ClientTypes.CHAR:
case ClientTypes.VARCHAR:
case ClientTypes.LONGVARCHAR:
return String.valueOf(source);
default:
throw new IllegalArgumentException("SQLState.LANG_DATA_TYPE_SET_MISMATCH " +
"boolean" + ClientTypes.getTypeString(targetType));
}
}
// Convert from byte source to target type
// In support of PS.setByte()
static final Object setObject(int targetType, byte source) {
return setObject(targetType, (short) source);
}
// Convert from short source to target type
// In support of PS.setShort()
static final Object setObject(int targetType, short source) {
switch (targetType) {
case ClientTypes.BIT:
case ClientTypes.BOOLEAN:
return Boolean.valueOf(source != 0);
case ClientTypes.SMALLINT:
return Short.valueOf(source);
case ClientTypes.INTEGER:
return Integer.valueOf(source);
case ClientTypes.BIGINT:
return Long.valueOf(source);
case ClientTypes.REAL:
return Float.valueOf(source);
case ClientTypes.DOUBLE:
return Double.valueOf(source);
case ClientTypes.DECIMAL:
return BigDecimal.valueOf(source);
case ClientTypes.CHAR:
case ClientTypes.VARCHAR:
case ClientTypes.LONGVARCHAR:
return String.valueOf(source);
default:
throw new IllegalArgumentException("SQLState.LANG_DATA_TYPE_SET_MISMATCH " +
"byte" + ClientTypes.getTypeString(targetType));
}
}
// Convert from integer source to target type
// In support of PS.setInt()
static final Object setObject(int targetType, int source) {
switch (targetType) {
case ClientTypes.BIT:
case ClientTypes.BOOLEAN:
return Boolean.valueOf(source != 0);
case ClientTypes.SMALLINT:
if (source > Short.MAX_VALUE || source < Short.MIN_VALUE) {
throw new IllegalArgumentException("Outside range for SMALLINT (Short) " + source);
}
return Short.valueOf((short) source);
case ClientTypes.INTEGER:
return Integer.valueOf(source);
case ClientTypes.BIGINT:
return Long.valueOf(source);
case ClientTypes.REAL:
return Float.valueOf(source);
case ClientTypes.DOUBLE:
return Double.valueOf(source);
case ClientTypes.DECIMAL:
return BigDecimal.valueOf(source);
case ClientTypes.CHAR:
case ClientTypes.VARCHAR:
case ClientTypes.LONGVARCHAR:
return String.valueOf(source);
default:
throw new IllegalArgumentException("SQLState.LANG_DATA_TYPE_SET_MISMATCH int " + ClientTypes.getTypeString(targetType));
}
}
static final boolean setBooleanFromObject(Object source, int sourceType) {
switch (sourceType) {
case ClientTypes.SMALLINT:
return getBooleanFromShort(((Short) source).shortValue());
case ClientTypes.INTEGER:
return getBooleanFromInt(((Integer) source).intValue());
case ClientTypes.BIGINT:
return getBooleanFromLong(((BigInteger) source).longValue());
case ClientTypes.REAL:
return getBooleanFromFloat(((Float) source).floatValue());
case ClientTypes.DOUBLE:
return getBooleanFromDouble(((Double) source).doubleValue());
case ClientTypes.DECIMAL:
return getBooleanFromLong(((BigDecimal) source).longValue());
case ClientTypes.CHAR:
case ClientTypes.VARCHAR:
case ClientTypes.LONGVARCHAR:
return getBooleanFromString((String) source);
default:
throw new IllegalArgumentException(ClientTypes.getTypeString(sourceType) + " boolean");
}
}
static final byte setByteFromObject(Object source, int sourceType) {
switch (sourceType) {
case ClientTypes.SMALLINT:
return getByteFromShort(((Short) source).shortValue());
case ClientTypes.INTEGER:
return getByteFromInt(((Integer) source).intValue());
case ClientTypes.BIGINT:
return getByteFromLong(((BigInteger) source).longValue());
case ClientTypes.REAL:
return getByteFromFloat(((Float) source).floatValue());
case ClientTypes.DOUBLE:
return getByteFromDouble(((Double) source).doubleValue());
case ClientTypes.DECIMAL:
return getByteFromLong(((BigDecimal) source).longValue());
case ClientTypes.CHAR:
case ClientTypes.VARCHAR:
case ClientTypes.LONGVARCHAR:
return getByteFromString((String) source);
default:
throw new IllegalArgumentException(ClientTypes.getTypeString(sourceType) + " byte");
}
}
// Convert from long source to target type
// In support of PS.setLong()
static final Object setObject(int targetType, long source) {
switch (targetType) {
case ClientTypes.BIT:
case ClientTypes.BOOLEAN:
return Boolean.valueOf(source != 0);
case ClientTypes.SMALLINT:
if (source > Short.MAX_VALUE || source < Short.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for SMALLINT: " + source);
}
return Short.valueOf((short) source);
case ClientTypes.INTEGER:
if (source > Integer.MAX_VALUE || source < Integer.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for INTEGER: " + source);
}
return Integer.valueOf((int) source);
case ClientTypes.BIGINT:
return Long.valueOf(source);
case ClientTypes.REAL:
return Float.valueOf(source);
case ClientTypes.DOUBLE:
return Double.valueOf(source);
case ClientTypes.DECIMAL:
return BigDecimal.valueOf(source);
case ClientTypes.CHAR:
case ClientTypes.VARCHAR:
case ClientTypes.LONGVARCHAR:
return String.valueOf(source);
default:
throw new IllegalArgumentException("SQLState.LANG_DATA_TYPE_SET_MISMATCH long" + ClientTypes.getTypeString(targetType));
}
}
// Convert from floating point source to target type
// In support of PS.setFloat()
static final Object setObject(int targetType, float source) {
switch (targetType) {
case ClientTypes.BIT:
case ClientTypes.BOOLEAN:
return Boolean.valueOf(source != 0);
case ClientTypes.SMALLINT:
if (source > Short.MAX_VALUE || source < Short.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for SMALLINT: " + source);
}
return Short.valueOf((short) source);
case ClientTypes.INTEGER:
if (source > Integer.MAX_VALUE || source < Integer.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for INTEGER: " + source);
}
return Integer.valueOf((int) source);
case ClientTypes.BIGINT:
if (source > Long.MAX_VALUE || source < Long.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for BIGINT: " + source);
}
return Long.valueOf((long) source);
case ClientTypes.REAL:
// change the check from (source > Float.MAX_VALUE || source < -Float.MIN_VALUE))
// to the following:
//-----------------------------------------------------------------------------------
// -infinity 0 +infinity
// |__________________________|======|________________________|
// <-3.4E+38| | | |>+3.4E+38
// | | |_________________ |
// | |-1.4E-45 +1.79E+308
// | | |_________________ |
// | |-4.9E-324 Short.MAX_VALUE || source < Short.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for SMALLINT: " + source);
}
return Short.valueOf((short) source);
case ClientTypes.INTEGER:
if (source > Integer.MAX_VALUE || source < Integer.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for INTEGER: " + source);
}
return Integer.valueOf((int) source);
case ClientTypes.BIGINT:
if (source > Long.MAX_VALUE || source < Long.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for BIGINT: " + source);
}
return Long.valueOf((long) source);
case ClientTypes.REAL:
if (source > Float.MAX_VALUE || source < -Float.MAX_VALUE) {
throw new IllegalArgumentException("Value outside range for REAL: " + source);
}
return Float.valueOf((float) source);
case ClientTypes.DOUBLE:
// change the check from (source > Double.MAX_VALUE || source < -Double.MIN_VALUE))
// to the following:
//-------------------------------------------------------------------------------------
// -infinity 0 +infinity
// |__________________________|======|________________________|
// <-1.79E+308| | | |>+1.79E+308
// | | |_________________ |
// | |-4.9E-324
* Get a boolean value from a CHAR column. In order to match the embedded
* driver and JCC we return false iff the CHAR value is "0" or "false".
*
*
*
* Leading and trailing whitespace is removed from the input string before
* it's compared to "0" and "false". No other normalization is performed.
* Specifically, no case conversion is performed, so the comparison is
* case sensitive, and everything that doesn't exactly match "0" or "false"
* will be considered true.
*
*
* @param source the value of a CHAR column
* @return false if source is "0" or "false", true otherwise
*/
static final boolean getBooleanFromString(String source) {
String trimmed = source.trim();
return !(trimmed.equals("0") || trimmed.equals("false"));
}
//---------------------------- getByte*() methods ----------------------------
static final byte getByteFromShort(short source) {
if (source > Byte.MAX_VALUE || source < Byte.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for TINYINT: " + source);
}
return (byte) source;
}
static final byte getByteFromInt(int source) {
if (source > Byte.MAX_VALUE || source < Byte.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for TINYINT: " + source);
}
return (byte) source;
}
static final byte getByteFromLong(long source) {
if (source > Byte.MAX_VALUE || source < Byte.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for TINYINT: " + source);
}
return (byte) source;
}
static final byte getByteFromFloat(float source) {
if (source > Byte.MAX_VALUE || source < Byte.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for TINYINT: " + source);
}
return (byte) source;
}
static final byte getByteFromDouble(double source) {
if (source > Byte.MAX_VALUE || source < Byte.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for TINYINT: " + source);
}
return (byte) source;
}
static final byte getByteFromBoolean(boolean source) {
return source ? (byte) 1 : (byte) 0;
}
static final byte getByteFromString(String source) {
return parseByte(source);
}
//---------------------------- getShort*() methods ---------------------------
static final short getShortFromInt(int source) {
if (source > Short.MAX_VALUE || source < Short.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for SMALLINT: " + source);
}
return (short) source;
}
static final short getShortFromLong(long source) {
if (source > Short.MAX_VALUE || source < Short.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for SMALLINT: " + source);
}
return (short) source;
}
static final short getShortFromFloat(float source) {
if (source > Short.MAX_VALUE || source < Short.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for SMALLINT: " + source);
}
return (short) source;
}
static final short getShortFromDouble(double source) {
if (source > Short.MAX_VALUE || source < Short.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for SMALLINT: " + source);
}
return (short) source;
}
static final short getShortFromBoolean(boolean source) {
return source ? (short) 1 : (short) 0;
}
static final short getShortFromString(String source) {
try {
return parseShort(source);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("SQLState.LANG_FORMAT_EXCEPTION short", e);
}
}
//---------------------------- getInt*() methods -----------------------------
static final int getIntFromLong(long source) {
if (source > Integer.MAX_VALUE || source < Integer.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for INTEGER: " + source);
}
return (int) source;
}
static final int getIntFromFloat(float source) {
if (source > Integer.MAX_VALUE || source < Integer.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for INTEGER: " + source);
}
return (int) source;
}
static final int getIntFromDouble(double source) {
if (source > Integer.MAX_VALUE || source < Integer.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for INTEGER: " + source);
}
return (int) source;
}
static final int getIntFromBoolean(boolean source) {
return source ? (int) 1 : (int) 0;
}
static final int getIntFromString(String source) {
return parseInt(source);
}
//---------------------------- getLong*() methods ----------------------------
static final long getLongFromFloat(float source) {
if (source > Long.MAX_VALUE || source < Long.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for BIGINT: " + source);
}
return (long) source;
}
static final long getLongFromDouble(double source) {
if (source > Long.MAX_VALUE || source < Long.MIN_VALUE) {
throw new IllegalArgumentException("Value outside range for BIGINT: " + source);
}
return (long) source;
}
static final long getLongFromBoolean(boolean source) {
return source ? (long) 1 : (long) 0;
}
static final long getLongFromString(String source) {
return parseLong(source);
}
//---------------------------- getFloat*() methods ---------------------------
static final float getFloatFromDouble(double source) {
if (Float.isInfinite((float)source)) {
throw new IllegalArgumentException("Value outside range for DOUBLE: " + source);
}
return (float) source;
}
static final float getFloatFromBoolean(boolean source) {
return source ? (float) 1 : (float) 0;
}
static final float getFloatFromString(String source) {
return Float.parseFloat(source.trim());
}
//---------------------------- getDouble*() methods --------------------------
static final double getDoubleFromBoolean(boolean source) {
return source ? (double) 1 : (double) 0;
}
static final double getDoubleFromString(String source) {
return Double.parseDouble(source.trim());
}
//---------------------------- getBigDecimal*() methods ----------------------
static final BigDecimal getBigDecimalFromString(String source) {
// Unfortunately, the big decimal constructor calls java.lang.Long.parseLong(),
// which doesn't like spaces, so we have to call trim() to get rid of the spaces from CHAR columns.
return new BigDecimal(source.trim());
}
//---------------------------- getString*() methods --------------------------
static final String getStringFromBytes(byte[] bytes) {
StringBuffer stringBuffer = new StringBuffer(bytes.length * 2);
for (int i = 0; i < bytes.length; i++) {
String hexForByte = Integer.toHexString(bytes[i] & 0xff);
// If the byte is x0-F, prepend a "0" in front to ensure 2 char representation
if (hexForByte.length() == 1) {
stringBuffer.append('0');
}
stringBuffer.append(hexForByte);
}
return stringBuffer.toString();
}
// All Numeric, and Date/Time types use String.valueOf (source)
//---------------------------- getDate*() methods ----------------------------
static final LocalDate getDateFromString(String source) {
return LocalDate.parse(source);
// return date_valueOf(source);
}
//---------------------------- getTime*() methods ----------------------------
static final LocalTime getTimeFromString(String source) {
return LocalTime.parse(source);
// return time_valueOf(source, cal);
}
//---------------------------- getTimestamp*() methods -----------------------
static final Timestamp getTimestampFromString(String source, Calendar cal) {
return timestamp_valueOf(source, cal);
}
/**
* Initialize the date components of a {@code java.util.Calendar} from
* a string on the format YYYY-MM-DD. All other components are left
* untouched.
*
* @param cal the calendar whose date components to initialize
* @param date a string representing a date
* @throws IllegalArgumentException if the date string is not on the
* format YYYY-MM-DD
*/
private static void initDatePortion(Calendar cal, String date) {
// Expect string on format YYYY-MM-DD
if (date.length() != 10 ||
date.charAt(4) != '-' || date.charAt(7) != '-') {
throw new IllegalArgumentException();
}
int year =
digit(date.charAt(0)) * 1000 +
digit(date.charAt(1)) * 100 +
digit(date.charAt(2)) * 10 +
digit(date.charAt(3));
int month =
digit(date.charAt(5)) * 10 +
digit(date.charAt(6)) - 1; // subtract one since
// Calendar.JANUARY == 0
int day =
digit(date.charAt(8)) * 10 +
digit(date.charAt(9));
cal.set(year, month, day);
}
/**
* Convert a character to a digit.
*
* @param ch the character
* @return the corresponding digit (0-9)
* @throws IllegalArgumentException if {@code ch} doesn't represent a digit
*/
private static int digit(char ch) {
int result = Character.digit(ch, 10);
if (result == -1) {
throw new IllegalArgumentException();
}
return result;
}
/**
* Initialize the time components of a {@code java.util.Calendar} from a
* string on the format HH:MM:SS. All other components are left untouched.
*
* @param cal the calendar whose time components to initialize
* @param time a string representing a time
* @throws IllegalArgumentException if the time string is not on the
* format HH:MM:SS
*/
private static void initTimePortion(Calendar cal, String time) {
// Expect string on format HH:MM:SS
if (time.length() != 8 ||
time.charAt(2) != ':' || time.charAt(5) != ':') {
throw new IllegalArgumentException();
}
int hour = digit(time.charAt(0)) * 10 + digit(time.charAt(1));
int minute = digit(time.charAt(3)) * 10 + digit(time.charAt(4));
int second = digit(time.charAt(6)) * 10 + digit(time.charAt(7));
cal.set(Calendar.HOUR_OF_DAY, hour);
cal.set(Calendar.MINUTE, minute);
cal.set(Calendar.SECOND, second);
}
/**
* Convert a string to a timestamp in the specified calendar. Accept the
* same format as {@code java.sql.Timestamp.valueOf()}.
*
* @param s the string to parse
* @param cal the calendar (or null to use the default calendar)
* @return a {@code java.sql.Timestamp} value that represents the timestamp
* in the calendar {@code cal}
* @throws IllegalArgumentException if the format of the string is invalid
*/
private static Timestamp timestamp_valueOf(String s, Calendar cal) {
if (s == null) {
throw new IllegalArgumentException();
}
s = s.trim();
if (cal == null) {
return Timestamp.valueOf(s);
}
cal.clear();
// Split into date and time components
String[] dateAndTime = s.split(" ");
if (dateAndTime.length != 2) {
throw new IllegalArgumentException();
}
String dateString = dateAndTime[0];
String timeAndNanoString = dateAndTime[1];
initDatePortion(cal, dateString);
// Split the time and nano components. The nano component is optional,
// and is separated from the time component with a decimal point.
String[] timeAndNanos = timeAndNanoString.split("\\.");
if (timeAndNanos.length < 1 || timeAndNanos.length > 2) {
throw new IllegalArgumentException();
}
String timeString = timeAndNanos[0];
initTimePortion(cal, timeString);
int nanos = 0;
if (timeAndNanos.length > 1) {
String nanoString = timeAndNanos[1];
int extraZeros = 9 - nanoString.length();
if (extraZeros < 0) {
throw new IllegalArgumentException();
}
// parseInt() may throw NumberFormatException. NFE is a subclass
// of IllegalArgumentException, so no need to document separately
// in the javadoc.
nanos = Integer.parseInt(nanoString);
for (int i = 0; i < extraZeros; i++) {
nanos *= 10;
}
}
Timestamp ts = new Timestamp(cal.getTimeInMillis());
ts.setNanos(nanos);
return ts;
}
private static byte parseByte(String s) throws NumberFormatException {
int i = parseInt(s);
if (i < Byte.MIN_VALUE || i > Byte.MAX_VALUE) {
throw new NumberFormatException();
}
return (byte) i;
}
private static short parseShort(String s) throws NumberFormatException {
int i = parseInt(s);
if (i < Short.MIN_VALUE || i > Short.MAX_VALUE) {
throw new NumberFormatException();
}
return (short) i;
}
// Custom version of java.lang.parseInt() that allows for space padding of char fields.
private static int parseInt(String s) throws NumberFormatException {
if (s == null) {
throw new NumberFormatException("null");
}
int result = 0;
boolean negative = false;
int i = 0;
int max = s.length();
int limit;
int multmin;
int digit;
if (max == 0) {
throw new NumberFormatException(s);
}
if (s.charAt(0) == '-') {
negative = true;
limit = Integer.MIN_VALUE;
i++;
} else {
limit = -Integer.MAX_VALUE;
}
multmin = limit / 10;
// Special handle the first digit to get things started.
if (i < max) {
digit = Character.digit(s.charAt(i++), 10);
if (digit < 0) {
throw new NumberFormatException(s);
} else {
result = -digit;
}
}
// Now handle all the subsequent digits or space padding.
while (i < max) {
char c = s.charAt(i++);
if (c == ' ') {
skipPadding(s, i, max);
break;
}
// Accumulating negatively avoids surprises near MAX_VALUE
digit = Character.digit(c, 10);
if (digit < 0) {
throw new NumberFormatException(s);
}
if (result < multmin) {
throw new NumberFormatException(s);
}
result *= 10;
if (result < limit + digit) {
throw new NumberFormatException(s);
}
result -= digit;
}
if (negative) {
if (i > 1) {
return result;
} else { // Only got "-"
throw new NumberFormatException(s);
}
} else {
return -result;
}
}
private static long parseLong(String s) throws NumberFormatException {
if (s == null) {
throw new NumberFormatException("null");
}
long result = 0;
boolean negative = false;
int i = 0, max = s.length();
long limit;
long multmin;
int digit;
if (max == 0) {
throw new NumberFormatException(s);
}
if (s.charAt(0) == '-') {
negative = true;
limit = Long.MIN_VALUE;
i++;
} else {
limit = -Long.MAX_VALUE;
}
multmin = limit / 10;
if (i < max) {
digit = Character.digit(s.charAt(i++), 10);
if (digit < 0) {
throw new NumberFormatException(s);
} else {
result = -digit;
}
}
while (i < max) {
char c = s.charAt(i++);
if (c == ' ') {
skipPadding(s, i, max);
break;
}
// Accumulating negatively avoids surprises near MAX_VALUE
digit = Character.digit(c, 10);
if (digit < 0) {
throw new NumberFormatException(s);
}
if (result < multmin) {
throw new NumberFormatException(s);
}
result *= 10;
if (result < limit + digit) {
throw new NumberFormatException(s);
}
result -= digit;
}
if (negative) {
if (i > 1) {
return result;
} else { // Only got "-"
throw new NumberFormatException(s);
}
} else {
return -result;
}
}
private static void skipPadding(String s, int i, int length)
throws NumberFormatException {
while (i < length) {
if (s.charAt(i++) != ' ') {
throw new NumberFormatException(s);
}
}
}
}