Add support of special symbols in DecimalFormat prefixes/suffixes

This commit is contained in:
Alexey Andreev 2015-06-10 21:24:48 +03:00
parent fc8200f135
commit 3df3447a38
4 changed files with 186 additions and 50 deletions

View File

@ -38,10 +38,10 @@ public class TDecimalFormat extends TNumberFormat {
private static final double[] POWM10_FRAC_ARRAY = { 1E-1, 1E-2, 1E-4, 1E-8, 1E-16, 1E-32, 1E-64, 1E-128, 1E-256 }; private static final double[] POWM10_FRAC_ARRAY = { 1E-1, 1E-2, 1E-4, 1E-8, 1E-16, 1E-32, 1E-64, 1E-128, 1E-256 };
private static final int DOUBLE_MAX_EXPONENT = 308; private static final int DOUBLE_MAX_EXPONENT = 308;
TDecimalFormatSymbols symbols; TDecimalFormatSymbols symbols;
private String positivePrefix = ""; FormatField[] positivePrefix = {};
private String negativePrefix = "-"; FormatField[] negativePrefix = { new TextField("-") };
private String positiveSuffix = ""; FormatField[] positiveSuffix = {};
private String negativeSuffix = ""; FormatField[] negativeSuffix = {};
private int multiplier = 1; private int multiplier = 1;
private int groupingSize; private int groupingSize;
private boolean decimalSeparatorAlwaysShown; private boolean decimalSeparatorAlwaysShown;
@ -71,36 +71,51 @@ public class TDecimalFormat extends TNumberFormat {
return (DecimalFormatSymbols)symbols.clone(); return (DecimalFormatSymbols)symbols.clone();
} }
private StringBuffer fieldsToText(FormatField[] fields, StringBuffer buffer) {
for (FormatField field : fields) {
field.render(this, buffer);
}
return buffer;
}
private String fieldsToText(FormatField[] fields) {
return fieldsToText(fields, new StringBuffer()).toString();
}
private FormatField[] textToFields(String text) {
return new FormatField[] { new TextField(text) };
}
public String getPositivePrefix() { public String getPositivePrefix() {
return positivePrefix; return fieldsToText(positivePrefix);
} }
public void setPositivePrefix(String newValue) { public void setPositivePrefix(String newValue) {
positivePrefix = newValue; positivePrefix = textToFields(newValue);
} }
public String getNegativePrefix() { public String getNegativePrefix() {
return negativePrefix; return fieldsToText(negativePrefix);
} }
public void setNegativePrefix(String newValue) { public void setNegativePrefix(String newValue) {
negativePrefix = newValue; negativePrefix = textToFields(newValue);
} }
public String getPositiveSuffix() { public String getPositiveSuffix() {
return positiveSuffix; return fieldsToText(positiveSuffix);
} }
public void setPositiveSuffix(String newValue) { public void setPositiveSuffix(String newValue) {
positiveSuffix = newValue; positiveSuffix = textToFields(newValue);
} }
public String getNegativeSuffix() { public String getNegativeSuffix() {
return negativeSuffix; return fieldsToText(negativeSuffix);
} }
public void setNegativeSuffix(String newValue) { public void setNegativeSuffix(String newValue) {
negativeSuffix = newValue; negativeSuffix = textToFields(newValue);
} }
public int getMultiplier() { public int getMultiplier() {
@ -221,10 +236,11 @@ public class TDecimalFormat extends TNumberFormat {
@Override @Override
public StringBuffer format(double value, StringBuffer buffer, TFieldPosition field) { public StringBuffer format(double value, StringBuffer buffer, TFieldPosition field) {
if (Double.isNaN(value)) { if (Double.isNaN(value)) {
buffer.append(positivePrefix).append(symbols.getNaN()).append(positiveSuffix); fieldsToText(positivePrefix, buffer).append(symbols.getNaN());
appendSuffix(true, buffer);
} else if (Double.isInfinite(value)) { } else if (Double.isInfinite(value)) {
buffer.append(value > 0 ? positivePrefix : negativePrefix).append(symbols.getInfinity()) fieldsToText(value > 0 ? positivePrefix : negativePrefix, buffer).append(symbols.getInfinity());
.append(value > 0 ? positiveSuffix : negativeSuffix); appendSuffix(value > 0, buffer);
} else { } else {
MantissaAndExponent me = getMantissaAndExponent(value); MantissaAndExponent me = getMantissaAndExponent(value);
if (exponentDigits > 0) { if (exponentDigits > 0) {
@ -257,7 +273,7 @@ public class TDecimalFormat extends TNumberFormat {
if (tenMultiplier == multiplier) { if (tenMultiplier == multiplier) {
exponent += multiplierDigits; exponent += multiplierDigits;
} else if (mantissa >= Long.MAX_VALUE / multiplier || mantissa <= Long.MIN_VALUE / multiplier) { } else if (mantissa >= Long.MAX_VALUE / multiplier || mantissa <= Long.MIN_VALUE / multiplier) {
formatExponent(new BigDecimal(BigInteger.valueOf(mantissa), mantissaLength - exponent), buffer); formatExponent(new BigDecimal(BigInteger.valueOf(mantissa), visibleExponent - exponent), buffer);
return; return;
} else { } else {
mantissa *= multiplier; mantissa *= multiplier;
@ -285,7 +301,7 @@ public class TDecimalFormat extends TNumberFormat {
} }
// Append pattern prefix // Append pattern prefix
buffer.append(positive ? positivePrefix : negativePrefix); fieldsToText(positive ? positivePrefix : negativePrefix, buffer);
int exponentPos = Math.max(visibleExponent, 0); int exponentPos = Math.max(visibleExponent, 0);
for (int i = mantissaLength - 1; i >= exponentPos; --i) { for (int i = mantissaLength - 1; i >= exponentPos; --i) {
@ -331,11 +347,7 @@ public class TDecimalFormat extends TNumberFormat {
} }
// Add suffix // Add suffix
if (positive) { appendSuffix(positive, buffer);
buffer.append(positiveSuffix != null ? positiveSuffix : "");
} else {
buffer.append(negativeSuffix != null ? negativeSuffix : positiveSuffix != null ? positiveSuffix : "");
}
} }
private void formatRegular(long mantissa, int exponent, StringBuffer buffer) { private void formatRegular(long mantissa, int exponent, StringBuffer buffer) {
@ -366,7 +378,7 @@ public class TDecimalFormat extends TNumberFormat {
} }
// Append pattern prefix // Append pattern prefix
buffer.append(positive ? positivePrefix : negativePrefix); fieldsToText(positive ? positivePrefix : negativePrefix, buffer);
// Add insignificant integer zeros // Add insignificant integer zeros
int intLength = Math.max(0, exponent); int intLength = Math.max(0, exponent);
@ -445,11 +457,7 @@ public class TDecimalFormat extends TNumberFormat {
} }
// Add suffix // Add suffix
if (positive) { appendSuffix(positive, buffer);
buffer.append(positiveSuffix != null ? positiveSuffix : "");
} else {
buffer.append(negativeSuffix != null ? negativeSuffix : positiveSuffix != null ? positiveSuffix : "");
}
} }
private void formatExponent(BigDecimal value, StringBuffer buffer) { private void formatExponent(BigDecimal value, StringBuffer buffer) {
@ -480,7 +488,7 @@ public class TDecimalFormat extends TNumberFormat {
} }
// Append pattern prefix // Append pattern prefix
buffer.append(positive ? positivePrefix : negativePrefix); fieldsToText(positive ? positivePrefix : negativePrefix, buffer);
int exponentPos = Math.max(visibleExponent, 0); int exponentPos = Math.max(visibleExponent, 0);
BigInteger mantissaDigitMask = pow10(BigInteger.ONE, mantissaLength - 1); BigInteger mantissaDigitMask = pow10(BigInteger.ONE, mantissaLength - 1);
@ -529,10 +537,17 @@ public class TDecimalFormat extends TNumberFormat {
} }
// Add suffix // Add suffix
appendSuffix(positive, buffer);
}
private void appendSuffix(boolean positive, StringBuffer buffer) {
if (positive) { if (positive) {
buffer.append(positiveSuffix != null ? positiveSuffix : ""); if (positiveSuffix != null) {
fieldsToText(positiveSuffix, buffer);
}
} else { } else {
buffer.append(negativeSuffix != null ? negativeSuffix : positiveSuffix != null ? positiveSuffix : ""); fieldsToText(negativeSuffix != null ? negativeSuffix :
positiveSuffix != null ? positiveSuffix : new FormatField[0], buffer);
} }
} }
@ -554,7 +569,7 @@ public class TDecimalFormat extends TNumberFormat {
} }
// Append pattern prefix // Append pattern prefix
buffer.append(positive ? positivePrefix : negativePrefix); fieldsToText(positive ? positivePrefix : negativePrefix, buffer);
// Add insignificant integer zeros // Add insignificant integer zeros
int intLength = Math.max(0, exponent); int intLength = Math.max(0, exponent);
@ -636,11 +651,7 @@ public class TDecimalFormat extends TNumberFormat {
} }
// Add suffix // Add suffix
if (positive) { appendSuffix(positive, buffer);
buffer.append(positiveSuffix != null ? positiveSuffix : "");
} else {
buffer.append(negativeSuffix != null ? negativeSuffix : positiveSuffix != null ? positiveSuffix : "");
}
} }
private long applyRounding(long mantissa, int mantissaLength, int exponent) { private long applyRounding(long mantissa, int mantissaLength, int exponent) {
@ -893,4 +904,49 @@ public class TDecimalFormat extends TNumberFormat {
this.exponent = exponent; this.exponent = exponent;
} }
} }
interface FormatField {
void render(TDecimalFormat format, StringBuffer buffer);
}
static class TextField implements FormatField {
private String text;
public TextField(String text) {
this.text = text;
}
@Override
public void render(TDecimalFormat format, StringBuffer buffer) {
buffer.append(text);
}
}
static class CurrencyField implements FormatField {
@Override
public void render(TDecimalFormat format, StringBuffer buffer) {
buffer.append(format.getCurrency().getSymbol(format.symbols.getLocale()));
}
}
static class PercentField implements FormatField {
@Override
public void render(TDecimalFormat format, StringBuffer buffer) {
buffer.append(format.symbols.getPercent());
}
}
static class PerMillField implements FormatField {
@Override
public void render(TDecimalFormat format, StringBuffer buffer) {
buffer.append(format.symbols.getPerMill());
}
}
static class MinusField implements FormatField {
@Override
public void render(TDecimalFormat format, StringBuffer buffer) {
buffer.append(format.symbols.getMinusSign());
}
}
} }

View File

@ -15,15 +15,19 @@
*/ */
package org.teavm.classlib.java.text; package org.teavm.classlib.java.text;
import java.util.ArrayList;
import java.util.List;
import org.teavm.classlib.java.text.TDecimalFormat.FormatField;
/** /**
* *
* @author Alexey Andreev * @author Alexey Andreev
*/ */
class TDecimalFormatParser { class TDecimalFormatParser {
private String positivePrefix = ""; private FormatField[] positivePrefix;
private String positiveSuffix = ""; private FormatField[] positiveSuffix;
private String negativePrefix; private FormatField[] negativePrefix;
private String negativeSuffix; private FormatField[] negativeSuffix;
private int groupSize; private int groupSize;
private int minimumIntLength; private int minimumIntLength;
private int intLength; private int intLength;
@ -33,6 +37,7 @@ class TDecimalFormatParser {
private boolean decimalSeparatorRequired; private boolean decimalSeparatorRequired;
private String string; private String string;
private int index; private int index;
private int multiplier;
public void parse(String string) { public void parse(String string) {
groupSize = 0; groupSize = 0;
@ -40,6 +45,7 @@ class TDecimalFormatParser {
fracLength = 0; fracLength = 0;
exponentLength = 0; exponentLength = 0;
decimalSeparatorRequired = false; decimalSeparatorRequired = false;
multiplier = 1;
this.string = string; this.string = string;
index = 0; index = 0;
positivePrefix = parseText(false, false); positivePrefix = parseText(false, false);
@ -47,6 +53,8 @@ class TDecimalFormatParser {
throw new IllegalArgumentException("Positive number pattern not found in " + string); throw new IllegalArgumentException("Positive number pattern not found in " + string);
} }
parseNumber(true); parseNumber(true);
negativePrefix = null;
negativeSuffix = null;
if (index < string.length() && string.charAt(index) != ';') { if (index < string.length() && string.charAt(index) != ';') {
positiveSuffix = parseText(true, false); positiveSuffix = parseText(true, false);
} }
@ -61,11 +69,16 @@ class TDecimalFormatParser {
} }
public void apply(TDecimalFormat format) { public void apply(TDecimalFormat format) {
format.setPositivePrefix(positivePrefix); format.positivePrefix = positivePrefix;
format.setPositiveSuffix(positiveSuffix); format.positiveSuffix = positiveSuffix;
format.setNegativePrefix(negativePrefix != null ? negativePrefix : if (negativePrefix != null) {
format.symbols.getMinusSign() + positivePrefix); format.negativePrefix = negativePrefix;
format.setNegativeSuffix(negativeSuffix != null ? negativeSuffix : positiveSuffix); } else {
format.negativePrefix = new FormatField[positivePrefix.length + 1];
System.arraycopy(positivePrefix, 0, format.negativePrefix, 1, positivePrefix.length);
format.negativePrefix[0] = new TDecimalFormat.MinusField();
}
format.negativeSuffix = negativeSuffix != null ? negativeSuffix : positiveSuffix;
format.setGroupingSize(groupSize); format.setGroupingSize(groupSize);
format.setGroupingUsed(groupSize > 0); format.setGroupingUsed(groupSize > 0);
format.setMinimumIntegerDigits(!decimalSeparatorRequired ? minimumIntLength : format.setMinimumIntegerDigits(!decimalSeparatorRequired ? minimumIntLength :
@ -75,9 +88,11 @@ class TDecimalFormatParser {
format.setMaximumFractionDigits(fracLength); format.setMaximumFractionDigits(fracLength);
format.setDecimalSeparatorAlwaysShown(decimalSeparatorRequired); format.setDecimalSeparatorAlwaysShown(decimalSeparatorRequired);
format.exponentDigits = exponentLength; format.exponentDigits = exponentLength;
format.setMultiplier(multiplier);
} }
private String parseText(boolean suffix, boolean end) { FormatField[] parseText(boolean suffix, boolean end) {
List<FormatField> fields = new ArrayList<>();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
loop: while (index < string.length()) { loop: while (index < string.length()) {
char c = string.charAt(index); char c = string.charAt(index);
@ -114,13 +129,52 @@ class TDecimalFormatParser {
index = next + 1; index = next + 1;
break; break;
} }
// Currency symbol ¤
case '\u00A4':
if (sb.length() > 0) {
fields.add(new TDecimalFormat.TextField(sb.toString()));
sb.setLength(0);
}
fields.add(new TDecimalFormat.CurrencyField());
++index;
break;
case '%':
if (sb.length() > 0) {
fields.add(new TDecimalFormat.TextField(sb.toString()));
sb.setLength(0);
}
fields.add(new TDecimalFormat.PercentField());
++index;
multiplier = 100;
break;
// Per mill symbol
case '\u2030':
if (sb.length() > 0) {
fields.add(new TDecimalFormat.TextField(sb.toString()));
sb.setLength(0);
}
fields.add(new TDecimalFormat.PerMillField());
++index;
multiplier = 1000;
break;
case '-':
if (sb.length() > 0) {
fields.add(new TDecimalFormat.TextField(sb.toString()));
sb.setLength(0);
}
fields.add(new TDecimalFormat.MinusField());
++index;
break;
default: default:
sb.append(c); sb.append(c);
++index; ++index;
break; break;
} }
} }
return sb.toString(); if (sb.length() > 0) {
fields.add(new TDecimalFormat.TextField(sb.toString()));
}
return fields.toArray(new FormatField[fields.size()]);
} }
private void parseNumber(boolean apply) { private void parseNumber(boolean apply) {

View File

@ -15,8 +15,10 @@
*/ */
package org.teavm.classlib.java.text; package org.teavm.classlib.java.text;
import java.util.Objects;
import org.teavm.classlib.impl.unicode.CLDRHelper; import org.teavm.classlib.impl.unicode.CLDRHelper;
import org.teavm.classlib.java.math.TRoundingMode; import org.teavm.classlib.java.math.TRoundingMode;
import org.teavm.classlib.java.util.TCurrency;
import org.teavm.classlib.java.util.TLocale; import org.teavm.classlib.java.util.TLocale;
/** /**
@ -30,6 +32,7 @@ public abstract class TNumberFormat extends TFormat {
private int maximumIntegerDigits = 40, minimumIntegerDigits = 1, private int maximumIntegerDigits = 40, minimumIntegerDigits = 1,
maximumFractionDigits = 3, minimumFractionDigits = 0; maximumFractionDigits = 3, minimumFractionDigits = 0;
private TRoundingMode roundingMode = TRoundingMode.HALF_EVEN; private TRoundingMode roundingMode = TRoundingMode.HALF_EVEN;
TCurrency currency = TCurrency.getInstance(TLocale.getDefault());
public TNumberFormat() { public TNumberFormat() {
} }
@ -39,6 +42,14 @@ public abstract class TNumberFormat extends TFormat {
return super.clone(); return super.clone();
} }
public TCurrency getCurrency() {
return currency;
}
public void setCurrency(TCurrency currency) {
this.currency = currency;
}
@Override @Override
public boolean equals(Object object) { public boolean equals(Object object) {
if (object == this) { if (object == this) {
@ -54,7 +65,8 @@ public abstract class TNumberFormat extends TFormat {
&& maximumIntegerDigits == obj.maximumIntegerDigits && maximumIntegerDigits == obj.maximumIntegerDigits
&& minimumFractionDigits == obj.minimumFractionDigits && minimumFractionDigits == obj.minimumFractionDigits
&& minimumIntegerDigits == obj.minimumIntegerDigits && minimumIntegerDigits == obj.minimumIntegerDigits
&& roundingMode == obj.roundingMode; && roundingMode == obj.roundingMode
&& currency == obj.currency;
} }
public final String format(double value) { public final String format(double value) {
@ -144,7 +156,7 @@ public abstract class TNumberFormat extends TFormat {
return (groupingUsed ? 1231 : 1237) + (parseIntegerOnly ? 1231 : 1237) return (groupingUsed ? 1231 : 1237) + (parseIntegerOnly ? 1231 : 1237)
+ maximumFractionDigits + maximumIntegerDigits + maximumFractionDigits + maximumIntegerDigits
+ minimumFractionDigits + minimumIntegerDigits + + minimumFractionDigits + minimumIntegerDigits +
roundingMode.hashCode(); roundingMode.hashCode() + Objects.hashCode(currency);
} }
public boolean isGroupingUsed() { public boolean isGroupingUsed() {

View File

@ -6,6 +6,7 @@ import java.math.BigInteger;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols; import java.text.DecimalFormatSymbols;
import java.util.Currency;
import java.util.Locale; import java.util.Locale;
import org.junit.Test; import org.junit.Test;
@ -440,6 +441,19 @@ public class DecimalFormatTest {
assertEquals("23E2", format.format(BigInteger.valueOf(23))); assertEquals("23E2", format.format(BigInteger.valueOf(23)));
} }
@Test
public void formatsSpecial() {
DecimalFormat format = createFormat("0%");
assertEquals("23%", format.format(0.23));
format = createFormat("0‰");
assertEquals("230‰", format.format(0.23));
format = createFormat("0.00 ¤");
format.setCurrency(Currency.getInstance("RUB"));
assertEquals("23.00 RUB", format.format(23));
}
private DecimalFormat createFormat(String format) { private DecimalFormat createFormat(String format) {
return new DecimalFormat(format, symbols); return new DecimalFormat(format, symbols);
} }