Speed up %d for locales with non-ASCII digits.
The small "localizeDigits" part of this change is the big speedup. It takes
%d in the "ar" Locale from 5x more expensive than "en_US", to just a small
percentage more expensive.
The big part of this change: removing the useless Transformer and FloatUtils
classes touches many lines (mostly just changing the level of indentation),
and only has a small percentage effect on performance. The motivation for
this is actually primarily that it makes the code easier to read, and makes
it easier to share code.
Change-Id: I07e86a3ee448510d9ae36579a9af283349f777f1
diff --git a/libcore/luni/src/main/java/java/util/Formatter.java b/libcore/luni/src/main/java/java/util/Formatter.java
index db35ef1..a27e7a1 100644
--- a/libcore/luni/src/main/java/java/util/Formatter.java
+++ b/libcore/luni/src/main/java/java/util/Formatter.java
@@ -529,6 +529,9 @@
public final class Formatter implements Closeable, Flushable {
private static final char[] ZEROS = new char[] { '0', '0', '0', '0', '0', '0', '0', '0', '0' };
+ // The cached line separator.
+ private static String lineSeparator;
+
/**
* The enumeration giving the available styles for formatting very large
* decimal numbers.
@@ -544,13 +547,16 @@
DECIMAL_FLOAT
}
+ // User-settable parameters.
private Appendable out;
-
private Locale locale;
+ // Implementation details.
+ private Object arg;
private boolean closed = false;
-
+ private FormatToken formatToken;
private IOException lastIOException;
+ private LocaleData localeData;
/**
* Constructs a {@code Formatter}.
@@ -824,9 +830,7 @@
* @throws UnsupportedEncodingException
* if the charset with the specified name is not supported.
*/
- public Formatter(OutputStream os, String csn)
- throws UnsupportedEncodingException {
-
+ public Formatter(OutputStream os, String csn) throws UnsupportedEncodingException {
this(os, csn, Locale.getDefault());
}
@@ -870,7 +874,7 @@
locale = Locale.getDefault();
}
- private void checkClosed() {
+ private void checkNotClosed() {
if (closed) {
throw new FormatterClosedException();
}
@@ -884,7 +888,7 @@
* if the {@code Formatter} has been closed.
*/
public Locale locale() {
- checkClosed();
+ checkNotClosed();
return locale;
}
@@ -896,7 +900,7 @@
* if the {@code Formatter} has been closed.
*/
public Appendable out() {
- checkClosed();
+ checkNotClosed();
return out;
}
@@ -911,7 +915,7 @@
*/
@Override
public String toString() {
- checkClosed();
+ checkNotClosed();
return out.toString();
}
@@ -923,7 +927,7 @@
* if the {@code Formatter} has been closed.
*/
public void flush() {
- checkClosed();
+ checkNotClosed();
if (out instanceof Flushable) {
try {
((Flushable) out).flush();
@@ -986,17 +990,10 @@
* if the {@code Formatter} has been closed.
*/
public Formatter format(String format, Object... args) {
- doFormat(format, args);
- return this;
+ return format(this.locale, format, args);
}
/**
- * Cached transformer. Improves performance when format() is called multiple
- * times.
- */
- private Transformer transformer;
-
- /**
* Writes a formatted string to the output destination of the {@code Formatter}.
*
* @param l
@@ -1022,7 +1019,8 @@
public Formatter format(Locale l, String format, Object... args) {
Locale originalLocale = locale;
try {
- this.locale = l;
+ this.locale = (l == null ? Locale.US : l);
+ this.localeData = LocaleData.get(locale);
doFormat(format, args);
} finally {
this.locale = originalLocale;
@@ -1031,15 +1029,9 @@
}
private void doFormat(String format, Object... args) {
- checkClosed();
-
- // Reuse the previous transformer if the locale matches.
- if (transformer == null || !transformer.locale.equals(locale)) {
- transformer = new Transformer(this, locale);
- }
+ checkNotClosed();
FormatSpecifierParser fsp = new FormatSpecifierParser(format);
-
int currentObjectIndex = 0;
Object lastArgument = null;
boolean hasLastArgumentSet = false;
@@ -1068,7 +1060,7 @@
hasLastArgumentSet = true;
}
- CharSequence substitution = transformer.transform(token, argument);
+ CharSequence substitution = transform(token, argument);
// The substitution is null if we called Formattable.formatTo.
if (substitution != null) {
outputCharSequence(substitution, 0, substitution.length());
@@ -1398,923 +1390,820 @@
}
}
+ private NumberFormat getNumberFormat() {
+ return LocaleCache.getNumberFormat(locale);
+ }
+
/*
- * Transforms the argument to the formatted string according to the format
- * information contained in the format token.
+ * Gets the formatted string according to the format token and the
+ * argument.
*/
- private static class Transformer {
- private Formatter formatter;
- private FormatToken formatToken;
- private Object arg;
- private Locale locale;
- private LocaleData localeData;
- private static String lineSeparator;
+ private CharSequence transform(FormatToken token, Object argument) {
+ this.formatToken = token;
+ this.arg = argument;
- Transformer(Formatter formatter, Locale locale) {
- this.formatter = formatter;
- this.locale = (locale == null ? Locale.US : locale);
- this.localeData = LocaleData.get(locale);
- }
-
- private NumberFormat getNumberFormat() {
- return LocaleCache.getNumberFormat(locale);
- }
-
- /*
- * Gets the formatted string according to the format token and the
- * argument.
- */
- CharSequence transform(FormatToken token, Object argument) {
- this.formatToken = token;
- this.arg = argument;
-
- // There are only two format specifiers that matter: "%d" and "%s".
- // Nothing else is common in the wild. We fast-path these two to
- // avoid the heavyweight machinery needed to cope with flags, width,
- // and precision.
- if (token.isDefault()) {
- switch (token.getConversionType()) {
- case 's':
- if (arg == null) {
- return "null";
- } else if (!(arg instanceof Formattable)) {
- return arg.toString();
- }
- break;
- case 'd':
- if (arg instanceof Integer || arg instanceof Long || arg instanceof Short || arg instanceof Byte) {
- if (localeData.zeroDigit == '0') {
- return arg.toString();
- }
- }
- }
- }
-
- formatToken.checkFlags(arg);
- CharSequence result;
+ // There are only two format specifiers that matter: "%d" and "%s".
+ // Nothing else is common in the wild. We fast-path these two to
+ // avoid the heavyweight machinery needed to cope with flags, width,
+ // and precision.
+ if (token.isDefault()) {
switch (token.getConversionType()) {
- case 'B':
- case 'b': {
- result = transformFromBoolean();
- break;
+ case 's':
+ if (arg == null) {
+ return "null";
+ } else if (!(arg instanceof Formattable)) {
+ return arg.toString();
}
- case 'H':
- case 'h': {
- result = transformFromHashCode();
- break;
- }
- case 'S':
- case 's': {
- result = transformFromString();
- break;
- }
- case 'C':
- case 'c': {
- result = transformFromCharacter();
- break;
- }
- case 'd':
- case 'o':
- case 'x':
- case 'X': {
- if (arg == null || arg instanceof BigInteger) {
- result = transformFromBigInteger();
- } else {
- result = transformFromInteger();
- }
- break;
- }
- case 'e':
- case 'E':
- case 'g':
- case 'G':
- case 'f':
- case 'a':
- case 'A': {
- result = transformFromFloat();
- break;
- }
- case '%': {
- result = transformFromPercent();
- break;
- }
- case 'n': {
- result = transformFromLineSeparator();
- break;
- }
- case 't':
- case 'T': {
- result = transformFromDateTime();
- break;
- }
- default:
- throw token.unknownFormatConversionException();
- }
-
- if (Character.isUpperCase(token.getConversionType())) {
- if (result != null) {
- result = result.toString().toUpperCase(locale);
+ break;
+ case 'd':
+ if (arg instanceof Integer || arg instanceof Long || arg instanceof Short || arg instanceof Byte) {
+ String result = arg.toString();
+ return (localeData.zeroDigit == '0') ? result : localizeDigits(result);
}
}
- return result;
}
- private IllegalFormatConversionException badArgumentType() {
- throw new IllegalFormatConversionException(formatToken.getConversionType(),
- arg.getClass());
- }
-
- /*
- * Transforms the Boolean argument to a formatted string.
- */
- private CharSequence transformFromBoolean() {
- CharSequence result;
- if (arg instanceof Boolean) {
- result = arg.toString();
- } else if (arg == null) {
- result = "false";
+ formatToken.checkFlags(arg);
+ CharSequence result;
+ switch (token.getConversionType()) {
+ case 'B': case 'b':
+ result = transformFromBoolean();
+ break;
+ case 'H': case 'h':
+ result = transformFromHashCode();
+ break;
+ case 'S': case 's':
+ result = transformFromString();
+ break;
+ case 'C': case 'c':
+ result = transformFromCharacter();
+ break;
+ case 'd': case 'o': case 'x': case 'X':
+ if (arg == null || arg instanceof BigInteger) {
+ result = transformFromBigInteger();
} else {
- result = "true";
+ result = transformFromInteger();
}
- return padding(result, 0);
+ break;
+ case 'A': case 'a': case 'E': case 'e': case 'f': case 'G': case 'g':
+ result = transformFromFloat();
+ break;
+ case '%':
+ result = transformFromPercent();
+ break;
+ case 'n':
+ result = transformFromLineSeparator();
+ break;
+ case 't': case 'T':
+ result = transformFromDateTime();
+ break;
+ default:
+ throw token.unknownFormatConversionException();
}
- /*
- * Transforms the hash code of the argument to a formatted string.
- */
- private CharSequence transformFromHashCode() {
- CharSequence result;
- if (arg == null) {
- result = "null";
- } else {
- result = Integer.toHexString(arg.hashCode());
- }
- return padding(result, 0);
- }
-
- /*
- * Transforms the String to a formatted string.
- */
- private CharSequence transformFromString() {
- if (arg instanceof Formattable) {
- int flags = 0;
- if (formatToken.flagMinus) {
- flags |= FormattableFlags.LEFT_JUSTIFY;
- }
- if (formatToken.flagSharp) {
- flags |= FormattableFlags.ALTERNATE;
- }
- if (Character.isUpperCase(formatToken.getConversionType())) {
- flags |= FormattableFlags.UPPERCASE;
- }
- ((Formattable) arg).formatTo(formatter, flags, formatToken.getWidth(),
- formatToken.getPrecision());
- // all actions have been taken out in the
- // Formattable.formatTo, thus there is nothing to do, just
- // returns null, which tells the Parser to add nothing to the
- // output.
- return null;
- }
- CharSequence result = arg != null ? arg.toString() : "null";
- return padding(result, 0);
- }
-
- /*
- * Transforms the Character to a formatted string.
- */
- private CharSequence transformFromCharacter() {
- if (arg == null) {
- return padding("null", 0);
- }
- if (arg instanceof Character) {
- return padding(String.valueOf(arg), 0);
- } else if (arg instanceof Byte || arg instanceof Short || arg instanceof Integer) {
- int codePoint = ((Number) arg).intValue();
- if (!Character.isValidCodePoint(codePoint)) {
- throw new IllegalFormatCodePointException(codePoint);
- }
- CharSequence result = (codePoint < Character.MIN_SUPPLEMENTARY_CODE_POINT)
- ? String.valueOf((char) codePoint)
- : String.valueOf(Character.toChars(codePoint));
- return padding(result, 0);
- } else {
- throw badArgumentType();
+ if (Character.isUpperCase(token.getConversionType())) {
+ if (result != null) {
+ result = result.toString().toUpperCase(locale);
}
}
+ return result;
+ }
- /*
- * Transforms percent to a formatted string. Only '-' is legal flag.
- * Precision and arguments are illegal.
- */
- private CharSequence transformFromPercent() {
- return padding("%", 0);
+ private IllegalFormatConversionException badArgumentType() {
+ throw new IllegalFormatConversionException(formatToken.getConversionType(), arg.getClass());
+ }
+
+ /**
+ * Returns a CharSequence corresponding to {@code s} with all the ASCII digits replaced
+ * by digits appropriate to this formatter's locale. Other characters remain unchanged.
+ */
+ private CharSequence localizeDigits(String s) {
+ int length = s.length();
+ int offsetToLocalizedDigits = localeData.zeroDigit - '0';
+ StringBuilder result = new StringBuilder(length);
+ for (int i = 0; i < length; ++i) {
+ char ch = s.charAt(i);
+ if (ch >= '0' && ch <= '9') {
+ ch += offsetToLocalizedDigits;
+ }
+ result.append(ch);
}
+ return result;
+ }
- /*
- * Transforms line separator to a formatted string. Any flag, width,
- * precision or argument is illegal.
- */
- private CharSequence transformFromLineSeparator() {
- if (lineSeparator == null) {
- lineSeparator = AccessController.doPrivileged(new PrivilegedAction<String>() {
- public String run() {
- return System.getProperty("line.separator");
- }
- });
- }
- return lineSeparator;
+ private CharSequence transformFromBoolean() {
+ CharSequence result;
+ if (arg instanceof Boolean) {
+ result = arg.toString();
+ } else if (arg == null) {
+ result = "false";
+ } else {
+ result = "true";
}
+ return padding(result, 0);
+ }
- /*
- * Pads characters to the formatted string.
- */
- private CharSequence padding(CharSequence source, int startIndex) {
- boolean sourceIsStringBuilder = (source instanceof StringBuilder);
-
- int start = startIndex;
- int width = formatToken.getWidth();
- int precision = formatToken.getPrecision();
-
- int length = source.length();
- if (precision >= 0) {
- length = Math.min(length, precision);
- if (sourceIsStringBuilder) {
- ((StringBuilder) source).setLength(length);
- } else {
- source = source.subSequence(0, length);
- }
- }
- if (width > 0) {
- width = Math.max(source.length(), width);
- }
- if (length >= width) {
- return source;
- }
-
- char paddingChar = '\u0020'; // space as padding char.
- if (formatToken.flagZero) {
- if (formatToken.getConversionType() == 'd') {
- paddingChar = localeData.zeroDigit;
- } else {
- paddingChar = '0'; // No localized digits for bases other than decimal.
- }
- } else {
- // if padding char is space, always pad from the start.
- start = 0;
- }
- char[] paddingChars = new char[width - length];
- Arrays.fill(paddingChars, paddingChar);
-
- boolean paddingRight = formatToken.flagMinus;
- StringBuilder result = toStringBuilder(source);
- if (paddingRight) {
- result.append(paddingChars);
- } else {
- result.insert(start, paddingChars);
- }
- return result;
+ private CharSequence transformFromHashCode() {
+ CharSequence result;
+ if (arg == null) {
+ result = "null";
+ } else {
+ result = Integer.toHexString(arg.hashCode());
}
+ return padding(result, 0);
+ }
- private StringBuilder toStringBuilder(CharSequence cs) {
- return cs instanceof StringBuilder ? (StringBuilder) cs : new StringBuilder(cs);
- }
-
- private StringBuilder wrapParentheses(StringBuilder result) {
- result.setCharAt(0, '('); // Replace the '-'.
- if (formatToken.flagZero) {
- formatToken.setWidth(formatToken.getWidth() - 1);
- result = (StringBuilder) padding(result, 1);
- result.append(')');
- } else {
- result.append(')');
- result = (StringBuilder) padding(result, 0);
+ private CharSequence transformFromString() {
+ if (arg instanceof Formattable) {
+ int flags = 0;
+ if (formatToken.flagMinus) {
+ flags |= FormattableFlags.LEFT_JUSTIFY;
}
- return result;
- }
-
- /*
- * Transforms the Integer to a formatted string.
- */
- private CharSequence transformFromInteger() {
- int startIndex = 0;
- StringBuilder result = new StringBuilder();
- char currentConversionType = formatToken.getConversionType();
-
- long value;
- if (arg instanceof Long) {
- value = ((Long) arg).longValue();
- } else if (arg instanceof Integer) {
- value = ((Integer) arg).longValue();
- } else if (arg instanceof Short) {
- value = ((Short) arg).longValue();
- } else if (arg instanceof Byte) {
- value = ((Byte) arg).longValue();
- } else {
- throw badArgumentType();
- }
-
if (formatToken.flagSharp) {
- if (currentConversionType == 'o') {
- result.append("0");
- startIndex += 1;
- } else {
- result.append("0x");
- startIndex += 2;
- }
+ flags |= FormattableFlags.ALTERNATE;
}
-
- if ('d' == currentConversionType) {
- if (formatToken.flagComma || localeData.zeroDigit != '0') {
- NumberFormat numberFormat = getNumberFormat();
- numberFormat.setGroupingUsed(formatToken.flagComma);
- result.append(numberFormat.format(arg));
- } else {
- result.append(value);
- }
-
- if (value < 0) {
- if (formatToken.flagParenthesis) {
- return wrapParentheses(result);
- } else if (formatToken.flagZero) {
- startIndex++;
- }
- } else {
- if (formatToken.flagAdd) {
- result.insert(0, '+');
- startIndex += 1;
- } else if (formatToken.flagSpace) {
- result.insert(0, ' ');
- startIndex += 1;
- }
- }
- } else {
- // Undo sign-extension, since we'll be using Long.to(Octal|Hex)String.
- if (arg instanceof Byte) {
- value &= 0xffL;
- } else if (arg instanceof Short) {
- value &= 0xffffL;
- } else if (arg instanceof Integer) {
- value &= 0xffffffffL;
- }
- if ('o' == currentConversionType) {
- result.append(Long.toOctalString(value));
- } else {
- result.append(Long.toHexString(value));
- }
+ if (Character.isUpperCase(formatToken.getConversionType())) {
+ flags |= FormattableFlags.UPPERCASE;
}
-
- return padding(result, startIndex);
+ ((Formattable) arg).formatTo(this, flags, formatToken.getWidth(),
+ formatToken.getPrecision());
+ // all actions have been taken out in the
+ // Formattable.formatTo, thus there is nothing to do, just
+ // returns null, which tells the Parser to add nothing to the
+ // output.
+ return null;
}
+ CharSequence result = arg != null ? arg.toString() : "null";
+ return padding(result, 0);
+ }
- private CharSequence transformFromSpecialNumber() {
- if (!(arg instanceof Number) || arg instanceof BigDecimal) {
- return null;
- }
-
- Number number = (Number) arg;
- double d = number.doubleValue();
- String source = null;
- if (Double.isNaN(d)) {
- source = "NaN";
- } else if (d == Double.POSITIVE_INFINITY) {
- if (formatToken.flagAdd) {
- source = "+Infinity";
- } else if (formatToken.flagSpace) {
- source = " Infinity";
- } else {
- source = "Infinity";
- }
- } else if (d == Double.NEGATIVE_INFINITY) {
- if (formatToken.flagParenthesis) {
- source = "(Infinity)";
- } else {
- source = "-Infinity";
- }
- } else {
- return null;
- }
-
- formatToken.setPrecision(FormatToken.UNSET);
- formatToken.flagZero = false;
- return padding(source, 0);
- }
-
- private CharSequence transformFromNull() {
- formatToken.flagZero = false;
+ private CharSequence transformFromCharacter() {
+ if (arg == null) {
return padding("null", 0);
}
-
- /*
- * Transforms a BigInteger to a formatted string.
- */
- private CharSequence transformFromBigInteger() {
- int startIndex = 0;
- boolean isNegative = false;
- StringBuilder result = new StringBuilder();
- BigInteger bigInt = (BigInteger) arg;
- char currentConversionType = formatToken.getConversionType();
-
- if (bigInt == null) {
- return transformFromNull();
+ if (arg instanceof Character) {
+ return padding(String.valueOf(arg), 0);
+ } else if (arg instanceof Byte || arg instanceof Short || arg instanceof Integer) {
+ int codePoint = ((Number) arg).intValue();
+ if (!Character.isValidCodePoint(codePoint)) {
+ throw new IllegalFormatCodePointException(codePoint);
}
+ CharSequence result = (codePoint < Character.MIN_SUPPLEMENTARY_CODE_POINT)
+ ? String.valueOf((char) codePoint)
+ : String.valueOf(Character.toChars(codePoint));
+ return padding(result, 0);
+ } else {
+ throw badArgumentType();
+ }
+ }
- isNegative = (bigInt.compareTo(BigInteger.ZERO) < 0);
+ private CharSequence transformFromPercent() {
+ return padding("%", 0);
+ }
- if ('d' == currentConversionType) {
- NumberFormat numberFormat = getNumberFormat();
- numberFormat.setGroupingUsed(formatToken.flagComma);
- result.append(numberFormat.format(bigInt));
- } else if ('o' == currentConversionType) {
- // convert BigInteger to a string presentation using radix 8
- result.append(bigInt.toString(8));
- } else {
- // convert BigInteger to a string presentation using radix 16
- result.append(bigInt.toString(16));
- }
- if (formatToken.flagSharp) {
- startIndex = isNegative ? 1 : 0;
- if (currentConversionType == 'o') {
- result.insert(startIndex, "0");
- startIndex += 1;
- } else if (currentConversionType == 'x' || currentConversionType == 'X') {
- result.insert(startIndex, "0x");
- startIndex += 2;
+ private CharSequence transformFromLineSeparator() {
+ if (lineSeparator == null) {
+ lineSeparator = AccessController.doPrivileged(new PrivilegedAction<String>() {
+ public String run() {
+ return System.getProperty("line.separator");
}
+ });
+ }
+ return lineSeparator;
+ }
+
+ private CharSequence padding(CharSequence source, int startIndex) {
+ int start = startIndex;
+ int width = formatToken.getWidth();
+ int precision = formatToken.getPrecision();
+
+ int length = source.length();
+ if (precision >= 0) {
+ length = Math.min(length, precision);
+ if (source instanceof StringBuilder) {
+ ((StringBuilder) source).setLength(length);
+ } else {
+ source = source.subSequence(0, length);
+ }
+ }
+ if (width > 0) {
+ width = Math.max(source.length(), width);
+ }
+ if (length >= width) {
+ return source;
+ }
+
+ char paddingChar = '\u0020'; // space as padding char.
+ if (formatToken.flagZero) {
+ if (formatToken.getConversionType() == 'd') {
+ paddingChar = localeData.zeroDigit;
+ } else {
+ paddingChar = '0'; // No localized digits for bases other than decimal.
+ }
+ } else {
+ // if padding char is space, always pad from the start.
+ start = 0;
+ }
+ char[] paddingChars = new char[width - length];
+ Arrays.fill(paddingChars, paddingChar);
+
+ boolean paddingRight = formatToken.flagMinus;
+ StringBuilder result = toStringBuilder(source);
+ if (paddingRight) {
+ result.append(paddingChars);
+ } else {
+ result.insert(start, paddingChars);
+ }
+ return result;
+ }
+
+ private StringBuilder toStringBuilder(CharSequence cs) {
+ return cs instanceof StringBuilder ? (StringBuilder) cs : new StringBuilder(cs);
+ }
+
+ private StringBuilder wrapParentheses(StringBuilder result) {
+ result.setCharAt(0, '('); // Replace the '-'.
+ if (formatToken.flagZero) {
+ formatToken.setWidth(formatToken.getWidth() - 1);
+ result = (StringBuilder) padding(result, 1);
+ result.append(')');
+ } else {
+ result.append(')');
+ result = (StringBuilder) padding(result, 0);
+ }
+ return result;
+ }
+
+ private CharSequence transformFromInteger() {
+ int startIndex = 0;
+ StringBuilder result = new StringBuilder();
+ char currentConversionType = formatToken.getConversionType();
+
+ long value;
+ if (arg instanceof Long) {
+ value = ((Long) arg).longValue();
+ } else if (arg instanceof Integer) {
+ value = ((Integer) arg).longValue();
+ } else if (arg instanceof Short) {
+ value = ((Short) arg).longValue();
+ } else if (arg instanceof Byte) {
+ value = ((Byte) arg).longValue();
+ } else {
+ throw badArgumentType();
+ }
+
+ if (formatToken.flagSharp) {
+ if (currentConversionType == 'o') {
+ result.append("0");
+ startIndex += 1;
+ } else {
+ result.append("0x");
+ startIndex += 2;
+ }
+ }
+
+ if ('d' == currentConversionType) {
+ if (formatToken.flagComma) {
+ NumberFormat numberFormat = getNumberFormat();
+ numberFormat.setGroupingUsed(true);
+ result.append(numberFormat.format(arg));
+ } else if (localeData.zeroDigit != '0') {
+ result.append(localizeDigits(Long.toString(value)));
+ } else {
+ result.append(value);
}
- if (!isNegative) {
+ if (value < 0) {
+ if (formatToken.flagParenthesis) {
+ return wrapParentheses(result);
+ } else if (formatToken.flagZero) {
+ startIndex++;
+ }
+ } else {
if (formatToken.flagAdd) {
result.insert(0, '+');
startIndex += 1;
- }
- if (formatToken.flagSpace) {
+ } else if (formatToken.flagSpace) {
result.insert(0, ' ');
startIndex += 1;
}
}
-
- /* pad paddingChar to the output */
- if (isNegative && formatToken.flagParenthesis) {
- return wrapParentheses(result);
+ } else {
+ // Undo sign-extension, since we'll be using Long.to(Octal|Hex)String.
+ if (arg instanceof Byte) {
+ value &= 0xffL;
+ } else if (arg instanceof Short) {
+ value &= 0xffffL;
+ } else if (arg instanceof Integer) {
+ value &= 0xffffffffL;
}
- if (isNegative && formatToken.flagZero) {
- startIndex++;
+ if ('o' == currentConversionType) {
+ result.append(Long.toOctalString(value));
+ } else {
+ result.append(Long.toHexString(value));
}
- return padding(result, startIndex);
}
- /*
- * Transforms a Float,Double or BigDecimal to a formatted string.
- */
- private CharSequence transformFromFloat() {
- StringBuilder result = new StringBuilder();
- int startIndex = 0;
- char currentConversionType = formatToken.getConversionType();
+ return padding(result, startIndex);
+ }
- if (arg == null) {
- return transformFromNull();
- }
+ private CharSequence transformFromSpecialNumber() {
+ if (!(arg instanceof Number) || arg instanceof BigDecimal) {
+ return null;
+ }
- if (!(arg instanceof Float || arg instanceof Double || arg instanceof BigDecimal)) {
- throw badArgumentType();
- }
-
- CharSequence specialNumberResult = transformFromSpecialNumber();
- if (null != specialNumberResult) {
- return specialNumberResult;
- }
-
- if (currentConversionType != 'a' && currentConversionType != 'A' &&
- !formatToken.isPrecisionSet()) {
- formatToken.setPrecision(FormatToken.DEFAULT_PRECISION);
- }
-
- // output result
- FloatUtil floatUtil = new FloatUtil(result, formatToken,
- (DecimalFormat) getNumberFormat(), localeData, arg);
- floatUtil.transform(currentConversionType);
-
- formatToken.setPrecision(FormatToken.UNSET);
-
- if (localeData.minusSign == result.charAt(0)) {
- if (formatToken.flagParenthesis) {
- return wrapParentheses(result);
- }
+ Number number = (Number) arg;
+ double d = number.doubleValue();
+ String source = null;
+ if (Double.isNaN(d)) {
+ source = "NaN";
+ } else if (d == Double.POSITIVE_INFINITY) {
+ if (formatToken.flagAdd) {
+ source = "+Infinity";
+ } else if (formatToken.flagSpace) {
+ source = " Infinity";
} else {
- if (formatToken.flagSpace) {
- result.insert(0, ' ');
- startIndex++;
- }
- if (formatToken.flagAdd) {
- result.insert(0, floatUtil.getAddSign());
- startIndex++;
- }
+ source = "Infinity";
}
-
- char firstChar = result.charAt(0);
- if (formatToken.flagZero && (firstChar == floatUtil.getAddSign() || firstChar == localeData.minusSign)) {
- startIndex = 1;
+ } else if (d == Double.NEGATIVE_INFINITY) {
+ if (formatToken.flagParenthesis) {
+ source = "(Infinity)";
+ } else {
+ source = "-Infinity";
}
+ } else {
+ return null;
+ }
- if (currentConversionType == 'a' || currentConversionType == 'A') {
+ formatToken.setPrecision(FormatToken.UNSET);
+ formatToken.flagZero = false;
+ return padding(source, 0);
+ }
+
+ private CharSequence transformFromNull() {
+ formatToken.flagZero = false;
+ return padding("null", 0);
+ }
+
+ private CharSequence transformFromBigInteger() {
+ int startIndex = 0;
+ StringBuilder result = new StringBuilder();
+ BigInteger bigInt = (BigInteger) arg;
+ char currentConversionType = formatToken.getConversionType();
+
+ if (bigInt == null) {
+ return transformFromNull();
+ }
+
+ boolean isNegative = (bigInt.compareTo(BigInteger.ZERO) < 0);
+
+ if ('d' == currentConversionType) {
+ NumberFormat numberFormat = getNumberFormat();
+ numberFormat.setGroupingUsed(formatToken.flagComma);
+ result.append(numberFormat.format(bigInt));
+ } else if ('o' == currentConversionType) {
+ // convert BigInteger to a string presentation using radix 8
+ result.append(bigInt.toString(8));
+ } else {
+ // convert BigInteger to a string presentation using radix 16
+ result.append(bigInt.toString(16));
+ }
+ if (formatToken.flagSharp) {
+ startIndex = isNegative ? 1 : 0;
+ if (currentConversionType == 'o') {
+ result.insert(startIndex, "0");
+ startIndex += 1;
+ } else if (currentConversionType == 'x' || currentConversionType == 'X') {
+ result.insert(startIndex, "0x");
startIndex += 2;
}
- return padding(result, startIndex);
}
- /*
- * Transforms a Date to a formatted string.
- */
- private CharSequence transformFromDateTime() {
- if (arg == null) {
- return transformFromNull();
+ if (!isNegative) {
+ if (formatToken.flagAdd) {
+ result.insert(0, '+');
+ startIndex += 1;
}
+ if (formatToken.flagSpace) {
+ result.insert(0, ' ');
+ startIndex += 1;
+ }
+ }
- Calendar calendar;
- if (arg instanceof Calendar) {
- calendar = (Calendar) arg;
- } else {
- Date date = null;
- if (arg instanceof Long) {
- date = new Date(((Long) arg).longValue());
- } else if (arg instanceof Date) {
- date = (Date) arg;
- } else {
- throw badArgumentType();
- }
- calendar = Calendar.getInstance(locale);
- calendar.setTime(date);
- }
+ /* pad paddingChar to the output */
+ if (isNegative && formatToken.flagParenthesis) {
+ return wrapParentheses(result);
+ }
+ if (isNegative && formatToken.flagZero) {
+ startIndex++;
+ }
+ return padding(result, startIndex);
+ }
- StringBuilder result = new StringBuilder();
- if (!appendT(result, formatToken.getDateSuffix(), calendar)) {
- throw formatToken.unknownFormatConversionException();
- }
- return padding(result, 0);
+ private CharSequence transformFromDateTime() {
+ if (arg == null) {
+ return transformFromNull();
}
-
- private boolean appendT(StringBuilder result, char conversion, Calendar calendar) {
- switch (conversion) {
- case 'A':
- result.append(localeData.longWeekdayNames[calendar.get(Calendar.DAY_OF_WEEK)]);
- return true;
- case 'a':
- result.append(localeData.shortWeekdayNames[calendar.get(Calendar.DAY_OF_WEEK)]);
- return true;
- case 'B':
- result.append(localeData.longMonthNames[calendar.get(Calendar.MONTH)]);
- return true;
- case 'b': case 'h':
- result.append(localeData.shortMonthNames[calendar.get(Calendar.MONTH)]);
- return true;
- case 'C':
- appendLocalized(result, calendar.get(Calendar.YEAR) / 100, 2);
- return true;
- case 'D':
- appendT(result, 'm', calendar);
- result.append('/');
- appendT(result, 'd', calendar);
- result.append('/');
- appendT(result, 'y', calendar);
- return true;
- case 'F':
- appendT(result, 'Y', calendar);
- result.append('-');
- appendT(result, 'm', calendar);
- result.append('-');
- appendT(result, 'd', calendar);
- return true;
- case 'H':
- appendLocalized(result, calendar.get(Calendar.HOUR_OF_DAY), 2);
- return true;
- case 'I':
- appendLocalized(result, to12Hour(calendar.get(Calendar.HOUR)), 2);
- return true;
- case 'L':
- appendLocalized(result, calendar.get(Calendar.MILLISECOND), 3);
- return true;
- case 'M':
- appendLocalized(result, calendar.get(Calendar.MINUTE), 2);
- return true;
- case 'N':
- appendLocalized(result, calendar.get(Calendar.MILLISECOND) * 1000000L, 9);
- return true;
- case 'Q':
- appendLocalized(result, calendar.getTimeInMillis(), 0);
- return true;
- case 'R':
- appendT(result, 'H', calendar);
- result.append(':');
- appendT(result, 'M', calendar);
- return true;
- case 'S':
- appendLocalized(result, calendar.get(Calendar.SECOND), 2);
- return true;
- case 'T':
- appendT(result, 'H', calendar);
- result.append(':');
- appendT(result, 'M', calendar);
- result.append(':');
- appendT(result, 'S', calendar);
- return true;
- case 'Y':
- appendLocalized(result, calendar.get(Calendar.YEAR), 4);
- return true;
- case 'Z':
- TimeZone timeZone = calendar.getTimeZone();
- result.append(timeZone.getDisplayName(timeZone.inDaylightTime(calendar.getTime()),
- TimeZone.SHORT, locale));
- return true;
- case 'c':
- appendT(result, 'a', calendar);
- result.append(' ');
- appendT(result, 'b', calendar);
- result.append(' ');
- appendT(result, 'd', calendar);
- result.append(' ');
- appendT(result, 'T', calendar);
- result.append(' ');
- appendT(result, 'Z', calendar);
- result.append(' ');
- appendT(result, 'Y', calendar);
- return true;
- case 'd':
- appendLocalized(result, calendar.get(Calendar.DAY_OF_MONTH), 2);
- return true;
- case 'e':
- appendLocalized(result, calendar.get(Calendar.DAY_OF_MONTH), 0);
- return true;
- case 'j':
- appendLocalized(result, calendar.get(Calendar.DAY_OF_YEAR), 3);
- return true;
- case 'k':
- appendLocalized(result, calendar.get(Calendar.HOUR_OF_DAY), 0);
- return true;
- case 'l':
- appendLocalized(result, to12Hour(calendar.get(Calendar.HOUR)), 0);
- return true;
- case 'm':
- // Calendar.JANUARY is 0; humans want January represented as 1.
- appendLocalized(result, calendar.get(Calendar.MONTH) + 1, 2);
- return true;
- case 'p':
- result.append(localeData.amPm[calendar.get(Calendar.AM_PM)].toLowerCase(locale));
- return true;
- case 'r':
- appendT(result, 'I', calendar);
- result.append(':');
- appendT(result, 'M', calendar);
- result.append(':');
- appendT(result, 'S', calendar);
- result.append(' ');
- result.append(localeData.amPm[calendar.get(Calendar.AM_PM)]);
- return true;
- case 's':
- appendLocalized(result, calendar.getTimeInMillis() / 1000, 0);
- return true;
- case 'y':
- appendLocalized(result, calendar.get(Calendar.YEAR) % 100, 2);
- return true;
- case 'z':
- long offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
- char sign = '+';
- if (offset < 0) {
- sign = '-';
- offset = -offset;
- }
- result.append(sign);
- appendLocalized(result, offset / 3600000, 2);
- appendLocalized(result, (offset % 3600000) / 60000, 2);
- return true;
- }
- return false;
- }
-
- private int to12Hour(int hour) {
- return hour == 0 ? 12 : hour;
- }
-
- private void appendLocalized(StringBuilder result, long value, int width) {
- int paddingIndex = result.length();
- char zeroDigit = localeData.zeroDigit;
- if (zeroDigit == '0') {
- result.append(value);
+
+ Calendar calendar;
+ if (arg instanceof Calendar) {
+ calendar = (Calendar) arg;
+ } else {
+ Date date = null;
+ if (arg instanceof Long) {
+ date = new Date(((Long) arg).longValue());
+ } else if (arg instanceof Date) {
+ date = (Date) arg;
} else {
- NumberFormat numberFormat = getNumberFormat();
- numberFormat.setGroupingUsed(false);
- result.append(numberFormat.format(value));
+ throw badArgumentType();
}
- int zeroCount = width - (result.length() - paddingIndex);
- if (zeroCount <= 0) {
- return;
+ calendar = Calendar.getInstance(locale);
+ calendar.setTime(date);
+ }
+
+ StringBuilder result = new StringBuilder();
+ if (!appendT(result, formatToken.getDateSuffix(), calendar)) {
+ throw formatToken.unknownFormatConversionException();
+ }
+ return padding(result, 0);
+ }
+
+ private boolean appendT(StringBuilder result, char conversion, Calendar calendar) {
+ switch (conversion) {
+ case 'A':
+ result.append(localeData.longWeekdayNames[calendar.get(Calendar.DAY_OF_WEEK)]);
+ return true;
+ case 'a':
+ result.append(localeData.shortWeekdayNames[calendar.get(Calendar.DAY_OF_WEEK)]);
+ return true;
+ case 'B':
+ result.append(localeData.longMonthNames[calendar.get(Calendar.MONTH)]);
+ return true;
+ case 'b': case 'h':
+ result.append(localeData.shortMonthNames[calendar.get(Calendar.MONTH)]);
+ return true;
+ case 'C':
+ appendLocalized(result, calendar.get(Calendar.YEAR) / 100, 2);
+ return true;
+ case 'D':
+ appendT(result, 'm', calendar);
+ result.append('/');
+ appendT(result, 'd', calendar);
+ result.append('/');
+ appendT(result, 'y', calendar);
+ return true;
+ case 'F':
+ appendT(result, 'Y', calendar);
+ result.append('-');
+ appendT(result, 'm', calendar);
+ result.append('-');
+ appendT(result, 'd', calendar);
+ return true;
+ case 'H':
+ appendLocalized(result, calendar.get(Calendar.HOUR_OF_DAY), 2);
+ return true;
+ case 'I':
+ appendLocalized(result, to12Hour(calendar.get(Calendar.HOUR)), 2);
+ return true;
+ case 'L':
+ appendLocalized(result, calendar.get(Calendar.MILLISECOND), 3);
+ return true;
+ case 'M':
+ appendLocalized(result, calendar.get(Calendar.MINUTE), 2);
+ return true;
+ case 'N':
+ appendLocalized(result, calendar.get(Calendar.MILLISECOND) * 1000000L, 9);
+ return true;
+ case 'Q':
+ appendLocalized(result, calendar.getTimeInMillis(), 0);
+ return true;
+ case 'R':
+ appendT(result, 'H', calendar);
+ result.append(':');
+ appendT(result, 'M', calendar);
+ return true;
+ case 'S':
+ appendLocalized(result, calendar.get(Calendar.SECOND), 2);
+ return true;
+ case 'T':
+ appendT(result, 'H', calendar);
+ result.append(':');
+ appendT(result, 'M', calendar);
+ result.append(':');
+ appendT(result, 'S', calendar);
+ return true;
+ case 'Y':
+ appendLocalized(result, calendar.get(Calendar.YEAR), 4);
+ return true;
+ case 'Z':
+ TimeZone timeZone = calendar.getTimeZone();
+ result.append(timeZone.getDisplayName(timeZone.inDaylightTime(calendar.getTime()),
+ TimeZone.SHORT, locale));
+ return true;
+ case 'c':
+ appendT(result, 'a', calendar);
+ result.append(' ');
+ appendT(result, 'b', calendar);
+ result.append(' ');
+ appendT(result, 'd', calendar);
+ result.append(' ');
+ appendT(result, 'T', calendar);
+ result.append(' ');
+ appendT(result, 'Z', calendar);
+ result.append(' ');
+ appendT(result, 'Y', calendar);
+ return true;
+ case 'd':
+ appendLocalized(result, calendar.get(Calendar.DAY_OF_MONTH), 2);
+ return true;
+ case 'e':
+ appendLocalized(result, calendar.get(Calendar.DAY_OF_MONTH), 0);
+ return true;
+ case 'j':
+ appendLocalized(result, calendar.get(Calendar.DAY_OF_YEAR), 3);
+ return true;
+ case 'k':
+ appendLocalized(result, calendar.get(Calendar.HOUR_OF_DAY), 0);
+ return true;
+ case 'l':
+ appendLocalized(result, to12Hour(calendar.get(Calendar.HOUR)), 0);
+ return true;
+ case 'm':
+ // Calendar.JANUARY is 0; humans want January represented as 1.
+ appendLocalized(result, calendar.get(Calendar.MONTH) + 1, 2);
+ return true;
+ case 'p':
+ result.append(localeData.amPm[calendar.get(Calendar.AM_PM)].toLowerCase(locale));
+ return true;
+ case 'r':
+ appendT(result, 'I', calendar);
+ result.append(':');
+ appendT(result, 'M', calendar);
+ result.append(':');
+ appendT(result, 'S', calendar);
+ result.append(' ');
+ result.append(localeData.amPm[calendar.get(Calendar.AM_PM)]);
+ return true;
+ case 's':
+ appendLocalized(result, calendar.getTimeInMillis() / 1000, 0);
+ return true;
+ case 'y':
+ appendLocalized(result, calendar.get(Calendar.YEAR) % 100, 2);
+ return true;
+ case 'z':
+ long offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
+ char sign = '+';
+ if (offset < 0) {
+ sign = '-';
+ offset = -offset;
}
- if (zeroDigit == '0') {
- result.insert(paddingIndex, ZEROS, 0, zeroCount);
- } else {
- for (int i = 0; i < zeroCount; ++i) {
- result.insert(paddingIndex, zeroDigit);
- }
+ result.append(sign);
+ appendLocalized(result, offset / 3600000, 2);
+ appendLocalized(result, (offset % 3600000) / 60000, 2);
+ return true;
+ }
+ return false;
+ }
+
+ private int to12Hour(int hour) {
+ return hour == 0 ? 12 : hour;
+ }
+
+ private void appendLocalized(StringBuilder result, long value, int width) {
+ int paddingIndex = result.length();
+ char zeroDigit = localeData.zeroDigit;
+ if (zeroDigit == '0') {
+ result.append(value);
+ } else {
+ result.append(localizeDigits(Long.toString(value)));
+ }
+ int zeroCount = width - (result.length() - paddingIndex);
+ if (zeroCount <= 0) {
+ return;
+ }
+ if (zeroDigit == '0') {
+ result.insert(paddingIndex, ZEROS, 0, zeroCount);
+ } else {
+ for (int i = 0; i < zeroCount; ++i) {
+ result.insert(paddingIndex, zeroDigit);
}
}
}
- // TODO: merge this into Transformer; the distinction is not obviously useful.
- private static class FloatUtil {
- private final StringBuilder result;
- private final DecimalFormat decimalFormat;
- private final LocaleData localeData;
- private final FormatToken formatToken;
- private final Object argument;
-
- FloatUtil(StringBuilder result, FormatToken formatToken, DecimalFormat decimalFormat,
- LocaleData localeData, Object argument) {
- this.result = result;
- this.formatToken = formatToken;
- this.decimalFormat = decimalFormat;
- this.localeData = localeData;
- this.argument = argument;
+ private CharSequence transformFromFloat() {
+ if (arg == null) {
+ return transformFromNull();
}
- void transform(char conversionType) {
- switch (conversionType) {
- case 'a': case 'A':
- transform_a();
- break;
- case 'e': case 'E':
- transform_e();
- break;
- case 'f':
- transform_f();
- break;
- case 'g':
- case 'G':
- transform_g();
- break;
- default:
- throw formatToken.unknownFormatConversionException();
+ if (!(arg instanceof Float || arg instanceof Double || arg instanceof BigDecimal)) {
+ throw badArgumentType();
+ }
+
+ CharSequence specialNumberResult = transformFromSpecialNumber();
+ if (specialNumberResult != null) {
+ return specialNumberResult;
+ }
+
+ char conversionType = formatToken.getConversionType();
+ if (conversionType != 'a' && conversionType != 'A' && !formatToken.isPrecisionSet()) {
+ formatToken.setPrecision(FormatToken.DEFAULT_PRECISION);
+ }
+
+ StringBuilder result = new StringBuilder();
+ switch (conversionType) {
+ case 'a': case 'A':
+ transform_a(result);
+ break;
+ case 'e': case 'E':
+ transform_e(result);
+ break;
+ case 'f':
+ transform_f(result);
+ break;
+ case 'g':
+ case 'G':
+ transform_g(result);
+ break;
+ default:
+ throw formatToken.unknownFormatConversionException();
+ }
+
+ formatToken.setPrecision(FormatToken.UNSET);
+
+ int startIndex = 0;
+ if (localeData.minusSign == result.charAt(0)) {
+ if (formatToken.flagParenthesis) {
+ return wrapParentheses(result);
+ }
+ } else {
+ if (formatToken.flagSpace) {
+ result.insert(0, ' ');
+ startIndex++;
+ }
+ if (formatToken.flagAdd) {
+ result.insert(0, '+');
+ startIndex++;
}
}
- char getAddSign() {
- return '+';
+ char firstChar = result.charAt(0);
+ if (formatToken.flagZero && (firstChar == '+' || firstChar == localeData.minusSign)) {
+ startIndex = 1;
}
- void transform_e() {
- StringBuilder pattern = new StringBuilder();
- pattern.append('0');
- if (formatToken.getPrecision() > 0) {
- pattern.append('.');
- char[] zeros = new char[formatToken.getPrecision()];
- Arrays.fill(zeros, '0'); // This is a *pattern* character, so no localization.
- pattern.append(zeros);
- }
- pattern.append("E+00");
- decimalFormat.applyPattern(pattern.toString());
- String formattedString = decimalFormat.format(argument);
- result.append(formattedString.replace('E', 'e'));
-
- // if the flag is sharp and decimal separator is always given out.
- if (formatToken.flagSharp && formatToken.getPrecision() == 0) {
- int indexOfE = result.indexOf("e");
- result.insert(indexOfE, localeData.decimalSeparator);
- }
+ if (conversionType == 'a' || conversionType == 'A') {
+ startIndex += 2;
}
+ return padding(result, startIndex);
+ }
- void transform_g() {
- int precision = formatToken.getPrecision();
- precision = (0 == precision ? 1 : precision);
+ private void transform_e(StringBuilder result) {
+ StringBuilder pattern = new StringBuilder();
+ pattern.append('0');
+ if (formatToken.getPrecision() > 0) {
+ pattern.append('.');
+ char[] zeros = new char[formatToken.getPrecision()];
+ Arrays.fill(zeros, '0'); // This is a *pattern* character, so no localization.
+ pattern.append(zeros);
+ }
+ pattern.append("E+00");
+ DecimalFormat decimalFormat = (DecimalFormat) getNumberFormat();
+ decimalFormat.applyPattern(pattern.toString());
+ String formattedString = decimalFormat.format(arg);
+ result.append(formattedString.replace('E', 'e'));
+
+ // if the flag is sharp and decimal separator is always given out.
+ if (formatToken.flagSharp && formatToken.getPrecision() == 0) {
+ int indexOfE = result.indexOf("e");
+ result.insert(indexOfE, localeData.decimalSeparator);
+ }
+ }
+
+ private void transform_g(StringBuilder result) {
+ int precision = formatToken.getPrecision();
+ precision = (0 == precision ? 1 : precision);
+ formatToken.setPrecision(precision);
+
+ double d = ((Number) arg).doubleValue();
+ if (d == 0.0) {
+ precision--;
formatToken.setPrecision(precision);
+ transform_f(result);
+ return;
+ }
- if (0.0 == ((Number) argument).doubleValue()) {
- precision--;
+ boolean requireScientificRepresentation = true;
+ d = Math.abs(d);
+ if (Double.isInfinite(d)) {
+ precision = formatToken.getPrecision();
+ precision--;
+ formatToken.setPrecision(precision);
+ transform_e(result);
+ return;
+ }
+ BigDecimal b = new BigDecimal(d, new MathContext(precision));
+ d = b.doubleValue();
+ long l = b.longValue();
+
+ if (d >= 1 && d < Math.pow(10, precision)) {
+ if (l < Math.pow(10, precision)) {
+ requireScientificRepresentation = false;
+ precision -= String.valueOf(l).length();
+ precision = precision < 0 ? 0 : precision;
+ l = Math.round(d * Math.pow(10, precision + 1));
+ if (String.valueOf(l).length() <= formatToken.getPrecision()) {
+ precision++;
+ }
formatToken.setPrecision(precision);
- transform_f();
- return;
}
-
- boolean requireScientificRepresentation = true;
- double d = ((Number) argument).doubleValue();
- d = Math.abs(d);
- if (Double.isInfinite(d)) {
- precision = formatToken.getPrecision();
- precision--;
- formatToken.setPrecision(precision);
- transform_e();
- return;
- }
- BigDecimal b = new BigDecimal(d, new MathContext(precision));
- d = b.doubleValue();
- long l = b.longValue();
-
- if (d >= 1 && d < Math.pow(10, precision)) {
- if (l < Math.pow(10, precision)) {
- requireScientificRepresentation = false;
- precision -= String.valueOf(l).length();
- precision = precision < 0 ? 0 : precision;
- l = Math.round(d * Math.pow(10, precision + 1));
- if (String.valueOf(l).length() <= formatToken
- .getPrecision()) {
- precision++;
- }
+ } else {
+ l = b.movePointRight(4).longValue();
+ if (d >= Math.pow(10, -4) && d < 1) {
+ requireScientificRepresentation = false;
+ precision += 4 - String.valueOf(l).length();
+ l = b.movePointRight(precision + 1).longValue();
+ if (String.valueOf(l).length() <= formatToken.getPrecision()) {
+ precision++;
+ }
+ l = b.movePointRight(precision).longValue();
+ if (l >= Math.pow(10, precision - 4)) {
formatToken.setPrecision(precision);
}
+ }
+ }
+ if (requireScientificRepresentation) {
+ precision = formatToken.getPrecision();
+ precision--;
+ formatToken.setPrecision(precision);
+ transform_e(result);
+ } else {
+ transform_f(result);
+ }
+ }
- } else {
- l = b.movePointRight(4).longValue();
- if (d >= Math.pow(10, -4) && d < 1) {
- requireScientificRepresentation = false;
- precision += 4 - String.valueOf(l).length();
- l = b.movePointRight(precision + 1).longValue();
- if (String.valueOf(l).length() <= formatToken
- .getPrecision()) {
- precision++;
- }
- l = b.movePointRight(precision).longValue();
- if (l >= Math.pow(10, precision - 4)) {
- formatToken.setPrecision(precision);
- }
+ private void transform_f(StringBuilder result) {
+ // TODO: store a default DecimalFormat we can clone?
+ String pattern = "0.000000";
+ DecimalFormat decimalFormat = (DecimalFormat) getNumberFormat();
+ if (formatToken.flagComma || formatToken.getPrecision() != 6) {
+ StringBuilder patternBuilder = new StringBuilder();
+ if (formatToken.flagComma) {
+ patternBuilder.append(',');
+ int groupingSize = decimalFormat.getGroupingSize();
+ if (groupingSize > 1) {
+ char[] sharps = new char[groupingSize - 1];
+ Arrays.fill(sharps, '#');
+ patternBuilder.append(sharps);
}
}
- if (requireScientificRepresentation) {
- precision = formatToken.getPrecision();
- precision--;
- formatToken.setPrecision(precision);
- transform_e();
- } else {
- transform_f();
+ patternBuilder.append('0');
+ if (formatToken.getPrecision() > 0) {
+ patternBuilder.append('.');
+ char[] zeros = new char[formatToken.getPrecision()];
+ Arrays.fill(zeros, '0'); // This is a *pattern* character, so no localization.
+ patternBuilder.append(zeros);
}
+ pattern = patternBuilder.toString();
+ }
+ // TODO: if DecimalFormat.toPattern was cheap, we could make this cheap (preferably *in* DecimalFormat).
+ decimalFormat.applyPattern(pattern);
+ result.append(decimalFormat.format(arg));
+ // if the flag is sharp and decimal separator is always given out.
+ if (formatToken.flagSharp && formatToken.getPrecision() == 0) {
+ result.append(localeData.decimalSeparator);
+ }
+ }
+
+ private void transform_a(StringBuilder result) {
+ if (arg instanceof Float) {
+ result.append(Float.toHexString(((Float) arg).floatValue()));
+ } else if (arg instanceof Double) {
+ result.append(Double.toHexString(((Double) arg).doubleValue()));
+ } else {
+ throw badArgumentType();
}
- void transform_f() {
- // TODO: store a default DecimalFormat we can clone?
- String pattern = "0.000000";
- if (formatToken.flagComma || formatToken.getPrecision() != 6) {
- StringBuilder patternBuilder = new StringBuilder();
- if (formatToken.flagComma) {
- patternBuilder.append(',');
- int groupingSize = decimalFormat.getGroupingSize();
- if (groupingSize > 1) {
- char[] sharps = new char[groupingSize - 1];
- Arrays.fill(sharps, '#');
- patternBuilder.append(sharps);
- }
- }
- patternBuilder.append('0');
- if (formatToken.getPrecision() > 0) {
- patternBuilder.append('.');
- char[] zeros = new char[formatToken.getPrecision()];
- Arrays.fill(zeros, '0'); // This is a *pattern* character, so no localization.
- patternBuilder.append(zeros);
- }
- pattern = patternBuilder.toString();
- }
- // TODO: if DecimalFormat.toPattern was cheap, we could make this cheap (preferably *in* DecimalFormat).
- decimalFormat.applyPattern(pattern);
- result.append(decimalFormat.format(argument));
- // if the flag is sharp and decimal separator is always given out.
- if (formatToken.flagSharp && formatToken.getPrecision() == 0) {
- result.append(localeData.decimalSeparator);
- }
+ if (!formatToken.isPrecisionSet()) {
+ return;
}
- void transform_a() {
- if (argument instanceof Float) {
- Float F = (Float) argument;
- result.append(Float.toHexString(F.floatValue()));
- } else if (argument instanceof Double) {
- Double D = (Double) argument;
- result.append(Double.toHexString(D.doubleValue()));
- } else {
- throw badArgumentType();
- }
+ int precision = formatToken.getPrecision();
+ precision = (0 == precision ? 1 : precision);
+ int indexOfFirstFractionalDigit = result.indexOf(".") + 1;
+ int indexOfP = result.indexOf("p");
+ int fractionalLength = indexOfP - indexOfFirstFractionalDigit;
- if (!formatToken.isPrecisionSet()) {
- return;
- }
-
- int precision = formatToken.getPrecision();
- precision = (0 == precision ? 1 : precision);
- int indexOfFirstFractionalDigit = result.indexOf(".") + 1;
- int indexOfP = result.indexOf("p");
- int fractionalLength = indexOfP - indexOfFirstFractionalDigit;
-
- if (fractionalLength == precision) {
- return;
- }
-
- if (fractionalLength < precision) {
- char zeros[] = new char[precision - fractionalLength];
- Arrays.fill(zeros, '0'); // %a shouldn't be localized.
- result.insert(indexOfP, zeros);
- return;
- }
- result.delete(indexOfFirstFractionalDigit + precision, indexOfP);
+ if (fractionalLength == precision) {
+ return;
}
- private IllegalFormatConversionException badArgumentType() {
- throw new IllegalFormatConversionException(formatToken.getConversionType(),
- argument.getClass());
+ if (fractionalLength < precision) {
+ char zeros[] = new char[precision - fractionalLength];
+ Arrays.fill(zeros, '0'); // %a shouldn't be localized.
+ result.insert(indexOfP, zeros);
+ return;
}
+ result.delete(indexOfFirstFractionalDigit + precision, indexOfP);
}
private static class FormatSpecifierParser {