/*
* Javolution - Java(TM) Solution for Real-Time and Embedded Systems
* Copyright (C) 2012 - Javolution (http://javolution.org/)
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software is
* freely granted, provided that this notice is preserved.
*/
package javolution.text;
import java.io.IOException;
import javolution.lang.MathLib;
import javolution.lang.Realtime;
/**
* <p> Utility class to parse {@link CharSequence} into primitive types and
* to format primitive types into any {@link Appendable}.</p>
*
* <p> Methods from this class <b>do not create temporary objects</b> and
* are typically faster than standard library methods.</p>
*
* <p> The number of digits when formatting floating point numbers can be
* specified. The default setting for <code>double</code> is 17 digits
* or even 16 digits when the conversion is lossless back and forth
* (mimic the standard library formatting).</p>
* <p>[code]
* TypeFormat.format(0.2, a) = "0.2" // 17 or 16 digits (as long as lossless conversion), remove trailing zeros.
* TypeFormat.format(0.2, 17, false, false, a) = "0.20000000000000001" // Closest 17 digits number.
* TypeFormat.format(0.2, 19, false, false, a) = "0.2000000000000000111" // Closest 19 digits.
* TypeFormat.format(0.2, 4, false, false, a) = "0.2" // Fixed-point notation, remove trailing zeros.
* TypeFormat.format(0.2, 4, false, true, a) = "0.2000" // Fixed-point notation, fixed number of digits.
* TypeFormat.format(0.2, 4, true, false, a) = "2.0E-1" // Scientific notation, remove trailing zeros.
* TypeFormat.format(0.2, 4, true, true, a) = "2.000E-1" // Scientific notation, fixed number of digits.
* [/code]</p>
*
* <p> For non-primitive objects, formatting is typically performed using
* specialized {@link TextFormat} instances.</p>
*
* @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
* @version 5.3, February 15, 2009
*/
@Realtime
public final class TypeFormat {
/**
* Default constructor (forbids derivation).
*/
private TypeFormat() {}
/////////////
// PARSING //
/////////////
/**
* Parses the specified character sequence from the specified position
* as a <code>boolean</code> ignoring cases.
*
* @param csq the character sequence to parse.
* @param cursor the cursor position (being maintained).
* @return the next boolean value.
* @throws IllegalArgumentException if the character sequence from the
* specified position is different from "true" or "false" ignoring
* cases.
*/
public static boolean parseBoolean(CharSequence csq, Cursor cursor) {
int start = cursor.getIndex();
int end = csq.length();
if ((end >= start + 5)
&& (csq.charAt(start) == 'f' || csq.charAt(start) == 'F')) { // False.
if ((csq.charAt(++start) == 'a' || csq.charAt(start) == 'A')
&& (csq.charAt(++start) == 'l' || csq.charAt(start) == 'L')
&& (csq.charAt(++start) == 's' || csq.charAt(start) == 'S')
&& (csq.charAt(++start) == 'e' || csq.charAt(start) == 'E')) {
cursor.increment(5);
return false;
}
} else if ((end >= start + 4)
&& (csq.charAt(start) == 't' || csq.charAt(start) == 'T')) // True.
if ((csq.charAt(++start) == 'r' || csq.charAt(start) == 'R')
&& (csq.charAt(++start) == 'u' || csq.charAt(start) == 'U')
&& (csq.charAt(++start) == 'e' || csq.charAt(start) == 'E')) {
cursor.increment(4);
return true;
}
throw new IllegalArgumentException("Invalid boolean representation");
}
/**
* Parses the whole specified character sequence as a <code>boolean</code>.
*
* @param csq the character sequence to parse.
* @return <code>parseBoolean(csq, new Cursor())</code>
* @throws IllegalArgumentException if the specified character sequence
* is different from "true" or "false" ignoring cases.
*/
public static boolean parseBoolean(CharSequence csq) {
Cursor cursor = new Cursor();
boolean result = parseBoolean(csq, cursor);
if (!cursor.atEnd(csq))
throw new IllegalArgumentException("Extraneous characters \""
+ cursor.tail(csq) + "\"");
return result;
}
/**
* Parses the specified character sequence from the specified position
* as a signed <code>byte</code> in the specified radix.
*
* @param csq the character sequence to parse.
* @param radix the radix to be used while parsing.
* @param cursor the cursor position being updated.
* @return the corresponding <code>byte</code>.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>byte</code>.
*/
public static byte parseByte(CharSequence csq, int radix, Cursor cursor) {
int i = parseInt(csq, radix, cursor);
if ((i < Byte.MIN_VALUE) || (i > Byte.MAX_VALUE))
throw new NumberFormatException("Overflow");
return (byte) i;
}
/**
* Parses the whole specified character sequence
* as a signed <code>byte</code> in the specified radix.
*
* @param csq the character sequence to parse.
* @param radix the radix to be used while parsing.
* @return the corresponding <code>byte</code>.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>byte</code> or contains
* extraneous characters.
*/
public static byte parseByte(CharSequence csq, int radix) {
Cursor cursor = new Cursor();
byte result = parseByte(csq, radix, cursor);
if (!cursor.atEnd(csq))
throw new IllegalArgumentException("Extraneous characters \""
+ cursor.tail(csq) + "\"");
return result;
}
/**
* Parses the specified character sequence from the specified position
* as a signed decimal <code>byte</code>.
*
* @param csq the character sequence to parse.
* @param cursor the cursor position being updated.
* @return the corresponding <code>byte</code>.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>byte</code>.
*/
public static byte parseByte(CharSequence csq, Cursor cursor) {
return parseByte(csq, 10, cursor);
}
/**
* Parses the whole specified character sequence as a signed decimal
* <code>byte</code>.
*
* @param csq the character sequence to parse.
* @return <code>parseByte(csq, 10)</code>
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>byte</code> or contains
* extraneous characters.
*/
public static byte parseByte(CharSequence csq) {
return parseByte(csq, 10);
}
/**
* Parses the specified character sequence from the specified position
* as a signed <code>short</code> in the specified radix.
*
* @param csq the character sequence to parse.
* @param radix the radix to be used while parsing.
* @param cursor the cursor position being updated.
* @return the corresponding <code>short</code>.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>short</code>.
*/
public static short parseShort(CharSequence csq, int radix, Cursor cursor) {
int i = parseInt(csq, radix, cursor);
if ((i < Short.MIN_VALUE) || (i > Short.MAX_VALUE))
throw new NumberFormatException("Overflow");
return (short) i;
}
/**
* Parses the whole specified character sequence
* as a signed <code>short</code> in the specified radix.
*
* @param csq the character sequence to parse.
* @param radix the radix to be used while parsing.
* @return the corresponding <code>short</code>.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>short</code> or contains
* extraneous characters.
*/
public static short parseShort(CharSequence csq, int radix) {
Cursor cursor = new Cursor();
short result = parseShort(csq, radix, cursor);
if (!cursor.atEnd(csq))
throw new IllegalArgumentException("Extraneous characters \""
+ cursor.tail(csq) + "\"");
return result;
}
/**
* Parses the specified character sequence from the specified position
* as a signed decimal <code>short</code>.
*
* @param csq the character sequence to parse.
* @param cursor the cursor position being updated.
* @return the corresponding <code>short</code>.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>short</code>.
*/
public static short parseShort(CharSequence csq, Cursor cursor) {
return parseShort(csq, 10, cursor);
}
/**
* Parses the whole specified character sequence as a signed decimal
* <code>short</code>.
*
* @param csq the character sequence to parse.
* @return <code>parseShort(csq, 10)</code>
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>short</code> or contains
* extraneous characters.
*/
public static short parseShort(CharSequence csq) {
return parseShort(csq, 10);
}
/**
* Parses the specified character sequence from the specified position
* as a signed <code>int</code> in the specified radix.
*
* @param csq the character sequence to parse.
* @param radix the radix to be used while parsing.
* @param cursor the cursor position being updated.
* @return the corresponding <code>int</code>.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>int</code>.
*/
public static int parseInt(CharSequence csq, int radix, Cursor cursor) {
int start = cursor.getIndex();
int end = csq.length();
boolean isNegative = false;
int result = 0; // Accumulates negatively (avoid MIN_VALUE overflow).
int i = start;
for (; i < end; i++) {
char c = csq.charAt(i);
int digit = (c <= '9') ? c - '0'
: ((c <= 'Z') && (c >= 'A')) ? c - 'A' + 10
: ((c <= 'z') && (c >= 'a')) ? c - 'a' + 10 : -1;
if ((digit >= 0) && (digit < radix)) {
int newResult = result * radix - digit;
if (newResult > result)
throw new NumberFormatException("Overflow parsing "
+ csq.subSequence(start, end));
result = newResult;
} else if ((c == '-') && (i == start))
isNegative = true;
else if ((c == '+') && (i == start)) {
// Ok.
} else
break;
}
// Requires one valid digit character and checks for opposite overflow.
if ((result == 0) && ((end == 0) || (csq.charAt(i - 1) != '0')))
throw new NumberFormatException(
"Invalid integer representation for "
+ csq.subSequence(start, end));
if ((result == Integer.MIN_VALUE) && !isNegative)
throw new NumberFormatException("Overflow parsing "
+ csq.subSequence(start, end));
cursor.increment(i - start);
return isNegative ? result : -result;
}
/**
* Parses the whole specified character sequence
* as a signed <code>int</code> in the specified radix.
*
* @param csq the character sequence to parse.
* @param radix the radix to be used while parsing.
* @return the corresponding <code>int</code>.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>int</code> or contains
* extraneous characters.
*/
public static int parseInt(CharSequence csq, int radix) {
Cursor cursor = new Cursor();
int result = parseInt(csq, radix, cursor);
if (!cursor.atEnd(csq))
throw new IllegalArgumentException("Extraneous characters \""
+ cursor.tail(csq) + "\"");
return result;
}
/**
* Parses the specified character sequence from the specified position
* as a signed decimal <code>int</code>.
*
* @param csq the character sequence to parse.
* @param cursor the cursor position being updated.
* @return the corresponding <code>int</code>.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>int</code>.
*/
public static int parseInt(CharSequence csq, Cursor cursor) {
return parseInt(csq, 10, cursor);
}
/**
* Parses the whole specified character sequence as a signed decimal
* <code>int</code>.
*
* @param csq the character sequence to parse.
* @return <code>parseInt(csq, 10)</code>
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>int</code> or contains
* extraneous characters.
*/
public static int parseInt(CharSequence csq) {
return parseInt(csq, 10);
}
/**
* Parses the specified character sequence from the specified position
* as a signed <code>long</code> in the specified radix.
*
* @param csq the character sequence to parse.
* @param radix the radix to be used while parsing.
* @param cursor the cursor position being updated.
* @return the corresponding <code>long</code>.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>long</code>.
*/
public static long parseLong(CharSequence csq, int radix, Cursor cursor) {
final int start = cursor.getIndex();
final int end = csq.length();
boolean isNegative = false;
long result = 0; // Accumulates negatively (avoid MIN_VALUE overflow).
int i = start;
for (; i < end; i++) {
char c = csq.charAt(i);
int digit = (c <= '9') ? c - '0'
: ((c <= 'Z') && (c >= 'A')) ? c - 'A' + 10
: ((c <= 'z') && (c >= 'a')) ? c - 'a' + 10 : -1;
if ((digit >= 0) && (digit < radix)) {
long newResult = result * radix - digit;
if (newResult > result)
throw new NumberFormatException("Overflow parsing "
+ csq.subSequence(start, end));
result = newResult;
} else if ((c == '-') && (i == start))
isNegative = true;
else if ((c == '+') && (i == start)) {
// Ok.
} else
break;
}
// Requires one valid digit character and checks for opposite overflow.
if ((result == 0) && ((end == 0) || (csq.charAt(i - 1) != '0')))
throw new NumberFormatException(
"Invalid integer representation for "
+ csq.subSequence(start, end));
if ((result == Long.MIN_VALUE) && !isNegative)
throw new NumberFormatException("Overflow parsing "
+ csq.subSequence(start, end));
cursor.increment(i - start);
return isNegative ? result : -result;
}
/**
* Parses the whole specified character sequence
* as a signed <code>long</code> in the specified radix.
*
* @param csq the character sequence to parse.
* @param radix the radix to be used while parsing.
* @return the corresponding <code>long</code>.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>long</code> or contains
* extraneous characters.
*/
public static long parseLong(CharSequence csq, int radix) {
Cursor cursor = new Cursor();
long result = parseLong(csq, radix, cursor);
if (!cursor.atEnd(csq))
throw new IllegalArgumentException("Extraneous characters \""
+ cursor.tail(csq) + "\"");
return result;
}
/**
* Parses the specified character sequence from the specified position
* as a signed decimal <code>long</code>.
*
* @param csq the character sequence to parse.
* @param cursor the cursor position being updated.
* @return the corresponding <code>long</code>.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>long</code>.
*/
public static long parseLong(CharSequence csq, Cursor cursor) {
return parseLong(csq, 10, cursor);
}
/**
* Parses the whole specified character sequence as a signed decimal
* <code>long</code>.
*
* @param csq the character sequence to parse.
* @return <code>parseLong(csq, 10)</code>
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>long</code> or contains
* extraneous characters.
*/
public static long parseLong(CharSequence csq) {
return parseLong(csq, 10);
}
/**
* Parses the specified character sequence from the specified position
* as a <code>float</code>.
*
* @param csq the character sequence to parse.
* @param cursor the cursor position (being maintained) or
* <code>null></code> to parse the whole character sequence.
* @return the float number represented by the specified character sequence.
*/
public static float parseFloat(CharSequence csq, Cursor cursor) {
return (float) parseDouble(csq, cursor);
}
/**
* Parses the whole specified character sequence as a <code>float</code>.
*
* @param csq the character sequence to parse.
* @return the float number represented by the specified character sequence.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>long</code> or contains
* extraneous characters.
*/
public static float parseFloat(CharSequence csq) {
return (float) parseDouble(csq);
}
/**
* Parses the specified character sequence from the specified position
* as a <code>double</code>.
*
* @param csq the character sequence to parse.
* @param cursor the cursor position (being maintained) or
* <code>null></code> to parse the whole character sequence.
* @return the double number represented by this character sequence.
* @throws NumberFormatException if the character sequence does not contain
* a parsable <code>double</code>.
*/
public static double parseDouble(CharSequence csq, Cursor cursor)
throws NumberFormatException {
final int start = cursor.getIndex();
final int end = csq.length();
int i = start;
char c = csq.charAt(i);
// Checks for NaN.
if ((c == 'N') && match("NaN", csq, i, end)) {
cursor.increment(3);
return Double.NaN;
}
// Reads sign.
boolean isNegative = (c == '-');
if ((isNegative || (c == '+')) && (++i < end))
c = csq.charAt(i);
// Checks for Infinity.
if ((c == 'I') && match("Infinity", csq, i, end)) {
cursor.increment(i + 8 - start);
return isNegative ? Double.NEGATIVE_INFINITY
: Double.POSITIVE_INFINITY;
}
// At least one digit or a '.' required.
if (((c < '0') || (c > '9')) && (c != '.'))
throw new NumberFormatException("Digit or '.' required");
// Reads decimal and fraction (both merged to a long).
long decimal = 0;
int decimalPoint = -1;
while (true) {
int digit = c - '0';
if ((digit >= 0) && (digit < 10)) {
long tmp = decimal * 10 + digit;
if ((decimal > LONG_MAX_DIV10) || (tmp < decimal))
throw new NumberFormatException(
"Too many digits - Overflow");
decimal = tmp;
} else if ((c == '.') && (decimalPoint < 0))
decimalPoint = i;
else
break;
if (++i >= end)
break;
c = csq.charAt(i);
}
if (isNegative)
decimal = -decimal;
int fractionLength = (decimalPoint >= 0) ? i - decimalPoint - 1 : 0;
// Reads exponent.
int exp = 0;
if ((i < end) && ((c == 'E') || (c == 'e'))) {
c = csq.charAt(++i);
boolean isNegativeExp = (c == '-');
if ((isNegativeExp || (c == '+')) && (++i < end))
c = csq.charAt(i);
if ((c < '0') || (c > '9')) // At least one digit required.
throw new NumberFormatException("Invalid exponent");
while (true) {
int digit = c - '0';
if ((digit >= 0) && (digit < 10)) {
int tmp = exp * 10 + digit;
if ((exp > INT_MAX_DIV10) || (tmp < exp))
throw new NumberFormatException("Exponent Overflow");
exp = tmp;
} else
break;
if (++i >= end)
break;
c = csq.charAt(i);
}
if (isNegativeExp)
exp = -exp;
}
cursor.increment(i - start);
return javolution.lang.MathLib.toDoublePow10(decimal, exp
- fractionLength);
}
private static final int INT_MAX_DIV10 = Integer.MAX_VALUE / 10;
private static final long LONG_MAX_DIV10 = Long.MAX_VALUE / 10;
/**
* Parses the whole specified character sequence as a <code>double</code>.
* The format must be of the form:<code>
* <decimal>{'.'<fraction>}{'E|e'<exponent>}</code>.
*
* @param csq the character sequence to parse.
* @return the double number represented by this character sequence.
* @throws NumberFormatException if the specified character sequence
* does not contain a parsable <code>long</code> or contains
* extraneous characters.
*/
public static double parseDouble(CharSequence csq)
throws NumberFormatException {
Cursor cursor = new Cursor();
double result = parseDouble(csq, cursor);
if (!cursor.atEnd(csq))
throw new IllegalArgumentException("Extraneous characters \""
+ cursor.tail(csq) + "\"");
return result;
}
static boolean match(String str, CharSequence csq, int start, int length) {
for (int i = 0; i < str.length(); i++) {
if ((start + i >= length) || csq.charAt(start + i) != str.charAt(i))
return false;
}
return true;
}
static boolean match(String str, String csq, int start, int length) {
for (int i = 0; i < str.length(); i++) {
if ((start + i >= length) || csq.charAt(start + i) != str.charAt(i))
return false;
}
return true;
}
////////////////
// FORMATTING //
////////////////
/**
* Formats the specified <code>boolean</code> and appends the resulting
* text to the <code>Appendable</code> argument.
*
* @param b a <code>boolean</code>.
* @param a the <code>Appendable</code> to append.
* @return the specified <code>StringBuffer</code> object.
* @throws IOException if an I/O exception occurs.
*/
public static Appendable format(boolean b, Appendable a) throws IOException {
return b ? a.append("true") : a.append("false");
}
/**
* Formats the specified <code>int</code> and appends the resulting
* text (decimal representation) to the <code>Appendable</code> argument.
*
*
* @param i the <code>int</code> number.
* @param a the <code>Appendable</code> to append.
* @return the specified <code>Appendable</code> object.
* @throws IOException if an I/O exception occurs.
*/
public static Appendable format(int i, Appendable a) throws IOException {
if (a instanceof TextBuilder)
return ((TextBuilder) a).append(i);
TextBuilder tb = new TextBuilder();
tb.append(i);
return a.append(tb);
}
/**
* Formats the specified <code>int</code> in the specified radix and appends
* the resulting text to the <code>Appendable</code> argument.
*
* @param i the <code>int</code> number.
* @param radix the radix.
* @param a the <code>Appendable</code> to append.
* @return the specified <code>Appendable</code> object.
* @throws IllegalArgumentException if radix is not in [2 .. 36] range.
* @throws IOException if an I/O exception occurs.
*/
public static Appendable format(int i, int radix, Appendable a)
throws IOException {
if (a instanceof TextBuilder)
return ((TextBuilder) a).append(i, radix);
TextBuilder tb = new TextBuilder();
tb.append(i, radix);
return a.append(tb);
}
/**
* Formats the specified <code>long</code> and appends the resulting
* text (decimal representation) to the <code>Appendable</code> argument.
*
* @param l the <code>long</code> number.
* @param a the <code>Appendable</code> to append.
* @return the specified <code>Appendable</code> object.
* @throws IOException if an I/O exception occurs.
* @see #parseLong
*/
public static Appendable format(long l, Appendable a) throws IOException {
if (a instanceof TextBuilder)
return ((TextBuilder) a).append(l);
TextBuilder tb = new TextBuilder();
tb.append(l);
return a.append(tb);
}
/**
* Formats the specified <code>long</code> in the specified radix and
* appends the resulting text to the <code>Appendable</code> argument.
*
* @param l the <code>long</code> number.
* @param radix the radix.
* @param a the <code>Appendable</code> to append.
* @return the specified <code>Appendable</code> object.
* @throws IllegalArgumentException if radix is not in [2 .. 36] range.
* @throws IOException if an I/O exception occurs.
* @see #parseLong(CharSequence, int)
*/
public static Appendable format(long l, int radix, Appendable a)
throws IOException {
if (a instanceof TextBuilder)
return ((TextBuilder) a).append(l, radix);
TextBuilder tb = new TextBuilder();
tb.append(l, radix);
return a.append(tb);
}
/**
* Formats the specified <code>float</code> value.
*
* @param f the <code>float</code> value.
* @param a the <code>Appendable</code> to append.
* @return <code>TypeFormat.format(f, 10, (MathLib.abs(f) >= 1E7) || (MathLib.abs(f) < 0.001), false, a)</code>
* @throws IOException if an I/O exception occurs.
*/
public static Appendable format(float f, Appendable a) throws IOException {
return TypeFormat.format(f, 10,
(MathLib.abs(f) >= 1E7) || (MathLib.abs(f) < 0.001), false, a);
}
/**
* Formats the specified <code>double</code> value (16 or 17 digits output).
*
* @param d the <code>double</code> value.
* @param a the <code>Appendable</code> to append.
* @return <code>TypeFormat.format(d, -1, (MathLib.abs(d) >= 1E7) || (MathLib.abs(d) < 0.001), false, a)</code>
* @throws IOException if an I/O exception occurs.
* @see TextBuilder#append(double)
*/
public static Appendable format(double d, Appendable a) throws IOException {
return TypeFormat.format(d, -1,
(MathLib.abs(d) >= 1E7) || (MathLib.abs(d) < 0.001), false, a);
}
/**
* Formats the specified <code>double</code> value according to the
* specified formatting arguments.
*
* @param d the <code>double</code> value.
* @param digits the number of significative digits (excludes exponent) or
* <code>-1</code> to mimic the standard library (16 or 17 digits).
* @param scientific <code>true</code> to forces the use of the scientific
* notation (e.g. <code>1.23E3</code>); <code>false</code>
* otherwise.
* @param showZero <code>true</code> if trailing fractional zeros are
* represented; <code>false</code> otherwise.
* @param a the <code>Appendable</code> to append.
* @return the specified <code>Appendable</code> object.
* @throws IllegalArgumentException if <code>(digits > 19)</code>)
* @throws IOException if an I/O exception occurs.
* @see TextBuilder#append(double, int, boolean, boolean)
*/
public static Appendable format(double d, int digits, boolean scientific,
boolean showZero, Appendable a) throws IOException {
if (a instanceof TextBuilder)
return ((TextBuilder) a).append(d, digits, scientific, showZero);
TextBuilder tb = new TextBuilder();
tb.append(d, digits, scientific, showZero);
return a.append(tb);
}
}