diff options
Diffstat (limited to 'icu/src')
-rw-r--r-- | icu/src/main/java/com/ibm/icu4jni/text/DecimalFormat.java | 565 | ||||
-rw-r--r-- | icu/src/main/java/com/ibm/icu4jni/text/NativeDecimalFormat.java | 640 |
2 files changed, 573 insertions, 632 deletions
diff --git a/icu/src/main/java/com/ibm/icu4jni/text/DecimalFormat.java b/icu/src/main/java/com/ibm/icu4jni/text/DecimalFormat.java deleted file mode 100644 index d995dc6..0000000 --- a/icu/src/main/java/com/ibm/icu4jni/text/DecimalFormat.java +++ /dev/null @@ -1,565 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.ibm.icu4jni.text; - -import com.ibm.icu4jni.text.NativeDecimalFormat.UNumberFormatAttribute; -import com.ibm.icu4jni.text.NativeDecimalFormat.UNumberFormatTextAttribute; -import com.ibm.icu4jni.util.LocaleData; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.text.AttributedCharacterIterator; -import java.text.AttributedString; -import java.text.DecimalFormatSymbols; -import java.text.FieldPosition; -import java.text.Format; -import java.text.NumberFormat; -import java.text.ParsePosition; -import java.util.Currency; -import java.util.Locale; - -public class DecimalFormat { - // Constants corresponding to the native type UNumberFormatSymbol, for use with getSymbol/setSymbol. - private static final int UNUM_DECIMAL_SEPARATOR_SYMBOL = 0; - private static final int UNUM_GROUPING_SEPARATOR_SYMBOL = 1; - private static final int UNUM_PATTERN_SEPARATOR_SYMBOL = 2; - private static final int UNUM_PERCENT_SYMBOL = 3; - private static final int UNUM_ZERO_DIGIT_SYMBOL = 4; - private static final int UNUM_DIGIT_SYMBOL = 5; - private static final int UNUM_MINUS_SIGN_SYMBOL = 6; - private static final int UNUM_PLUS_SIGN_SYMBOL = 7; - private static final int UNUM_CURRENCY_SYMBOL = 8; - private static final int UNUM_INTL_CURRENCY_SYMBOL = 9; - private static final int UNUM_MONETARY_SEPARATOR_SYMBOL = 10; - private static final int UNUM_EXPONENTIAL_SYMBOL = 11; - private static final int UNUM_PERMILL_SYMBOL = 12; - private static final int UNUM_PAD_ESCAPE_SYMBOL = 13; - private static final int UNUM_INFINITY_SYMBOL = 14; - private static final int UNUM_NAN_SYMBOL = 15; - private static final int UNUM_SIGNIFICANT_DIGIT_SYMBOL = 16; - private static final int UNUM_MONETARY_GROUPING_SEPARATOR_SYMBOL = 17; - private static final int UNUM_FORMAT_SYMBOL_COUNT = 18; - - // The address of the ICU DecimalFormat* on the native heap. - private final int addr; - - // TODO: store all these in java.text.DecimalFormat instead! - private boolean negPrefNull; - private boolean negSuffNull; - private boolean posPrefNull; - private boolean posSuffNull; - - /** - * Cache the BigDecimal form of the multiplier. This is null until we've - * formatted a BigDecimal (with a multiplier that is not 1), or the user has - * explicitly called {@link #setMultiplier(int)} with any multiplier. - */ - private transient BigDecimal multiplierBigDecimal = null; - - public DecimalFormat(String pattern, Locale locale, DecimalFormatSymbols symbols) { - this.addr = NativeDecimalFormat.openDecimalFormat(locale.toString(), pattern); - setDecimalFormatSymbols(symbols); - } - - // Used to implement clone. - private DecimalFormat(DecimalFormat other) { - this.addr = NativeDecimalFormat.cloneDecimalFormatImpl(other.addr); - this.negPrefNull = other.negPrefNull; - this.negSuffNull = other.negSuffNull; - this.posPrefNull = other.posPrefNull; - this.posSuffNull = other.posSuffNull; - } - - // TODO: remove this and just have java.text.DecimalFormat.hashCode do the right thing itself. - @Override - public int hashCode() { - return this.getPositivePrefix().hashCode(); - } - - @Override - public Object clone() { - return new DecimalFormat(this); - } - - @Override - protected void finalize() { - NativeDecimalFormat.closeDecimalFormatImpl(this.addr); - } - - /** - * Note: this doesn't check that the underlying native DecimalFormat objects' configured - * native DecimalFormatSymbols objects are equal. It is assumed that the - * caller (java.text.DecimalFormat) will check the java.text.DecimalFormatSymbols objects - * instead, for performance. - * - * This is also unreasonably expensive, calling down to JNI multiple times. - * - * TODO: remove this and just have java.text.DecimalFormat.equals do the right thing itself. - */ - @Override - public boolean equals(Object object) { - if (object == this) { - return true; - } - if (!(object instanceof DecimalFormat)) { - return false; - } - DecimalFormat obj = (DecimalFormat) object; - if (obj.addr == this.addr) { - return true; - } - return obj.toPattern().equals(this.toPattern()) && - obj.isDecimalSeparatorAlwaysShown() == this.isDecimalSeparatorAlwaysShown() && - obj.getGroupingSize() == this.getGroupingSize() && - obj.getMultiplier() == this.getMultiplier() && - obj.getNegativePrefix().equals(this.getNegativePrefix()) && - obj.getNegativeSuffix().equals(this.getNegativeSuffix()) && - obj.getPositivePrefix().equals(this.getPositivePrefix()) && - obj.getPositiveSuffix().equals(this.getPositiveSuffix()) && - obj.getMaximumIntegerDigits() == this.getMaximumIntegerDigits() && - obj.getMaximumFractionDigits() == this.getMaximumFractionDigits() && - obj.getMinimumIntegerDigits() == this.getMinimumIntegerDigits() && - obj.getMinimumFractionDigits() == this.getMinimumFractionDigits() && - obj.isGroupingUsed() == this.isGroupingUsed() && - obj.getCurrency() == this.getCurrency(); - } - - /** - * Copies the java.text.DecimalFormatSymbols' settings into our native peer. - */ - public void setDecimalFormatSymbols(final java.text.DecimalFormatSymbols dfs) { - NativeDecimalFormat.setSymbol(this.addr, UNUM_CURRENCY_SYMBOL, dfs.getCurrencySymbol()); - - NativeDecimalFormat.setSymbol(this.addr, UNUM_DECIMAL_SEPARATOR_SYMBOL, dfs.getDecimalSeparator()); - NativeDecimalFormat.setSymbol(this.addr, UNUM_DIGIT_SYMBOL, dfs.getDigit()); - - char groupingSeparator = dfs.getGroupingSeparator(); - NativeDecimalFormat.setSymbol(this.addr, UNUM_GROUPING_SEPARATOR_SYMBOL, groupingSeparator); - NativeDecimalFormat.setSymbol(this.addr, UNUM_MONETARY_GROUPING_SEPARATOR_SYMBOL, groupingSeparator); - - NativeDecimalFormat.setSymbol(this.addr, UNUM_INFINITY_SYMBOL, dfs.getInfinity()); - NativeDecimalFormat.setSymbol(this.addr, UNUM_INTL_CURRENCY_SYMBOL, dfs.getInternationalCurrencySymbol()); - NativeDecimalFormat.setSymbol(this.addr, UNUM_MINUS_SIGN_SYMBOL, dfs.getMinusSign()); - NativeDecimalFormat.setSymbol(this.addr, UNUM_MONETARY_SEPARATOR_SYMBOL, dfs.getMonetaryDecimalSeparator()); - NativeDecimalFormat.setSymbol(this.addr, UNUM_NAN_SYMBOL, dfs.getNaN()); - NativeDecimalFormat.setSymbol(this.addr, UNUM_PATTERN_SEPARATOR_SYMBOL, dfs.getPatternSeparator()); - NativeDecimalFormat.setSymbol(this.addr, UNUM_PERCENT_SYMBOL, dfs.getPercent()); - NativeDecimalFormat.setSymbol(this.addr, UNUM_PERMILL_SYMBOL, dfs.getPerMill()); - NativeDecimalFormat.setSymbol(this.addr, UNUM_ZERO_DIGIT_SYMBOL, dfs.getZeroDigit()); - } - - private BigDecimal applyMultiplier(BigDecimal valBigDecimal) { - if (multiplierBigDecimal == null) { - multiplierBigDecimal = BigDecimal.valueOf(getMultiplier()); - } - // Get new value by multiplying multiplier. - return valBigDecimal.multiply(multiplierBigDecimal); - } - - public StringBuffer format(Object value, StringBuffer buffer, FieldPosition field) { - if (!(value instanceof Number)) { - throw new IllegalArgumentException(); - } - if (buffer == null || field == null) { - throw new NullPointerException(); - } - String fieldType = getFieldType(field.getFieldAttribute()); - Number number = (Number) value; - if (number instanceof BigInteger) { - BigInteger valBigInteger = (BigInteger) number; - String result = NativeDecimalFormat.format(this.addr, valBigInteger.toString(10), - field, fieldType, null, 0); - return buffer.append(result); - } else if (number instanceof BigDecimal) { - BigDecimal valBigDecimal = (BigDecimal) number; - if (getMultiplier() != 1) { - valBigDecimal = applyMultiplier(valBigDecimal); - } - StringBuilder val = new StringBuilder(); - val.append(valBigDecimal.unscaledValue().toString(10)); - int scale = valBigDecimal.scale(); - scale = makeScalePositive(scale, val); - String result = NativeDecimalFormat.format(this.addr, val.toString(), - field, fieldType, null, scale); - return buffer.append(result); - } else if (number instanceof Double || number instanceof Float) { - double dv = number.doubleValue(); - String result = NativeDecimalFormat.format(this.addr, dv, field, fieldType, null); - return buffer.append(result); - } else { - long lv = number.longValue(); - String result = NativeDecimalFormat.format(this.addr, lv, field, fieldType, null); - return buffer.append(result); - } - } - - public StringBuffer format(long value, StringBuffer buffer, FieldPosition field) { - if (buffer == null || field == null) { - throw new NullPointerException(); - } - String fieldType = getFieldType(field.getFieldAttribute()); - buffer.append(NativeDecimalFormat.format(this.addr, value, field, fieldType, null)); - return buffer; - } - - public StringBuffer format(double value, StringBuffer buffer, FieldPosition field) { - if (buffer == null || field == null) { - throw new NullPointerException(); - } - String fieldType = getFieldType(field.getFieldAttribute()); - buffer.append(NativeDecimalFormat.format(this.addr, value, field, fieldType, null)); - return buffer; - } - - public void applyLocalizedPattern(String pattern) { - NativeDecimalFormat.applyPattern(this.addr, true, pattern); - } - - public void applyPattern(String pattern) { - NativeDecimalFormat.applyPattern(this.addr, false, pattern); - } - - public AttributedCharacterIterator formatToCharacterIterator(Object object) { - if (!(object instanceof Number)) { - throw new IllegalArgumentException(); - } - Number number = (Number) object; - String text = null; - StringBuffer attributes = new StringBuffer(); - - if(number instanceof BigInteger) { - BigInteger valBigInteger = (BigInteger) number; - text = NativeDecimalFormat.format(this.addr, - valBigInteger.toString(10), null, null, attributes, 0); - } else if(number instanceof BigDecimal) { - BigDecimal valBigDecimal = (BigDecimal) number; - if (getMultiplier() != 1) { - valBigDecimal = applyMultiplier(valBigDecimal); - } - StringBuilder val = new StringBuilder(); - val.append(valBigDecimal.unscaledValue().toString(10)); - int scale = valBigDecimal.scale(); - scale = makeScalePositive(scale, val); - text = NativeDecimalFormat.format(this.addr, val.toString(), null, - null, attributes, scale); - } else if (number instanceof Double || number instanceof Float) { - double dv = number.doubleValue(); - text = NativeDecimalFormat.format(this.addr, dv, null, null, attributes); - } else { - long lv = number.longValue(); - text = NativeDecimalFormat.format(this.addr, lv, null, null, attributes); - } - - AttributedString as = new AttributedString(text); - - String[] attrs = attributes.toString().split(";"); - // add NumberFormat field attributes to the AttributedString - int size = attrs.length / 3; - if(size * 3 != attrs.length) { - return as.getIterator(); - } - for (int i = 0; i < size; i++) { - Format.Field attribute = getField(attrs[3*i]); - as.addAttribute(attribute, attribute, Integer.parseInt(attrs[3*i+1]), - Integer.parseInt(attrs[3*i+2])); - } - - // return the CharacterIterator from AttributedString - return as.getIterator(); - } - - private int makeScalePositive(int scale, StringBuilder val) { - if (scale < 0) { - scale = -scale; - for (int i = scale; i > 0; i--) { - val.append('0'); - } - scale = 0; - } - return scale; - } - - public String toLocalizedPattern() { - return NativeDecimalFormat.toPatternImpl(this.addr, true); - } - - public String toPattern() { - return NativeDecimalFormat.toPatternImpl(this.addr, false); - } - - public Number parse(String string, ParsePosition position) { - return NativeDecimalFormat.parse(addr, string, position); - } - - // start getter and setter - - public int getMaximumFractionDigits() { - return NativeDecimalFormat.getAttribute(this.addr, - UNumberFormatAttribute.UNUM_MAX_FRACTION_DIGITS.ordinal()); - } - - public int getMaximumIntegerDigits() { - return NativeDecimalFormat.getAttribute(this.addr, - UNumberFormatAttribute.UNUM_MAX_INTEGER_DIGITS.ordinal()); - } - - public int getMinimumFractionDigits() { - return NativeDecimalFormat.getAttribute(this.addr, - UNumberFormatAttribute.UNUM_MIN_FRACTION_DIGITS.ordinal()); - } - - public int getMinimumIntegerDigits() { - return NativeDecimalFormat.getAttribute(this.addr, - UNumberFormatAttribute.UNUM_MIN_INTEGER_DIGITS.ordinal()); - } - - public Currency getCurrency() { - String curr = NativeDecimalFormat.getSymbol(this.addr, UNUM_INTL_CURRENCY_SYMBOL); - if (curr.equals("") || curr.equals("\u00a4\u00a4")) { - return null; - } - return Currency.getInstance(curr); - } - - public int getGroupingSize() { - return NativeDecimalFormat.getAttribute(this.addr, - UNumberFormatAttribute.UNUM_GROUPING_SIZE.ordinal()); - } - - public int getMultiplier() { - return NativeDecimalFormat.getAttribute(this.addr, - UNumberFormatAttribute.UNUM_MULTIPLIER.ordinal()); - } - - public String getNegativePrefix() { - if (negPrefNull) { - return null; - } - return NativeDecimalFormat.getTextAttribute(this.addr, - UNumberFormatTextAttribute.UNUM_NEGATIVE_PREFIX.ordinal()); - } - - public String getNegativeSuffix() { - if (negSuffNull) { - return null; - } - return NativeDecimalFormat.getTextAttribute(this.addr, - UNumberFormatTextAttribute.UNUM_NEGATIVE_SUFFIX.ordinal()); - } - - public String getPositivePrefix() { - if (posPrefNull) { - return null; - } - return NativeDecimalFormat.getTextAttribute(this.addr, - UNumberFormatTextAttribute.UNUM_POSITIVE_PREFIX.ordinal()); - } - - public String getPositiveSuffix() { - if (posSuffNull) { - return null; - } - return NativeDecimalFormat.getTextAttribute(this.addr, - UNumberFormatTextAttribute.UNUM_POSITIVE_SUFFIX.ordinal()); - } - - public boolean isDecimalSeparatorAlwaysShown() { - return NativeDecimalFormat.getAttribute(this.addr, - UNumberFormatAttribute.UNUM_DECIMAL_ALWAYS_SHOWN.ordinal()) != 0; - } - - public boolean isParseIntegerOnly() { - return NativeDecimalFormat.getAttribute(this.addr, - UNumberFormatAttribute.UNUM_PARSE_INT_ONLY.ordinal()) != 0; - } - - public boolean isGroupingUsed() { - return NativeDecimalFormat.getAttribute(this.addr, - UNumberFormatAttribute.UNUM_GROUPING_USED.ordinal()) != 0; - } - - public void setDecimalSeparatorAlwaysShown(boolean value) { - int i = value ? -1 : 0; - NativeDecimalFormat.setAttribute(this.addr, - UNumberFormatAttribute.UNUM_DECIMAL_ALWAYS_SHOWN.ordinal(), i); - } - - public void setCurrency(Currency currency) { - NativeDecimalFormat.setSymbol(this.addr, UNUM_CURRENCY_SYMBOL, currency.getSymbol()); - NativeDecimalFormat.setSymbol(this.addr, UNUM_INTL_CURRENCY_SYMBOL, currency.getCurrencyCode()); - } - - public void setGroupingSize(int value) { - NativeDecimalFormat.setAttribute(this.addr, - UNumberFormatAttribute.UNUM_GROUPING_SIZE.ordinal(), value); - } - - public void setGroupingUsed(boolean value) { - int i = value ? -1 : 0; - NativeDecimalFormat.setAttribute(this.addr, - UNumberFormatAttribute.UNUM_GROUPING_USED.ordinal(), i); - } - - public void setMaximumFractionDigits(int value) { - NativeDecimalFormat.setAttribute(this.addr, - UNumberFormatAttribute.UNUM_MAX_FRACTION_DIGITS.ordinal(), value); - } - - public void setMaximumIntegerDigits(int value) { - NativeDecimalFormat.setAttribute(this.addr, - UNumberFormatAttribute.UNUM_MAX_INTEGER_DIGITS.ordinal(), value); - } - - public void setMinimumFractionDigits(int value) { - NativeDecimalFormat.setAttribute(this.addr, - UNumberFormatAttribute.UNUM_MIN_FRACTION_DIGITS.ordinal(), value); - } - - public void setMinimumIntegerDigits(int value) { - NativeDecimalFormat.setAttribute(this.addr, - UNumberFormatAttribute.UNUM_MIN_INTEGER_DIGITS.ordinal(), value); - } - - public void setMultiplier(int value) { - NativeDecimalFormat.setAttribute(this.addr, - UNumberFormatAttribute.UNUM_MULTIPLIER.ordinal(), value); - // Update the cached BigDecimal for multiplier. - multiplierBigDecimal = BigDecimal.valueOf(value); - } - - public void setNegativePrefix(String value) { - negPrefNull = value == null; - if (!negPrefNull) { - NativeDecimalFormat.setTextAttribute(this.addr, - UNumberFormatTextAttribute.UNUM_NEGATIVE_PREFIX.ordinal(), - value); - } - } - - public void setNegativeSuffix(String value) { - negSuffNull = value == null; - if (!negSuffNull) { - NativeDecimalFormat.setTextAttribute(this.addr, - UNumberFormatTextAttribute.UNUM_NEGATIVE_SUFFIX.ordinal(), - value); - } - } - - public void setPositivePrefix(String value) { - posPrefNull = value == null; - if (!posPrefNull) { - NativeDecimalFormat.setTextAttribute(this.addr, - UNumberFormatTextAttribute.UNUM_POSITIVE_PREFIX.ordinal(), - value); - } - } - - public void setPositiveSuffix(String value) { - posSuffNull = value == null; - if (!posSuffNull) { - NativeDecimalFormat.setTextAttribute(this.addr, - UNumberFormatTextAttribute.UNUM_POSITIVE_SUFFIX.ordinal(), - value); - } - } - - public void setParseIntegerOnly(boolean value) { - int i = value ? -1 : 0; - NativeDecimalFormat.setAttribute(this.addr, - UNumberFormatAttribute.UNUM_PARSE_INT_ONLY.ordinal(), i); - } - - static protected String getFieldType(Format.Field field) { - if(field == null) { - return null; - } - if(field.equals(NumberFormat.Field.SIGN)) { - return "sign"; - } - if(field.equals(NumberFormat.Field.INTEGER)) { - return "integer"; - } - if(field.equals(NumberFormat.Field.FRACTION)) { - return "fraction"; - } - if(field.equals(NumberFormat.Field.EXPONENT)) { - return "exponent"; - } - if(field.equals(NumberFormat.Field.EXPONENT_SIGN)) { - return "exponent_sign"; - } - if(field.equals(NumberFormat.Field.EXPONENT_SYMBOL)) { - return "exponent_symbol"; - } - if(field.equals(NumberFormat.Field.CURRENCY)) { - return "currency"; - } - if(field.equals(NumberFormat.Field.GROUPING_SEPARATOR)) { - return "grouping_separator"; - } - if(field.equals(NumberFormat.Field.DECIMAL_SEPARATOR)) { - return "decimal_separator"; - } - if(field.equals(NumberFormat.Field.PERCENT)) { - return "percent"; - } - if(field.equals(NumberFormat.Field.PERMILLE)) { - return "permille"; - } - return null; - } - - protected Format.Field getField(String type) { - if(type.equals("")) { - return null; - } - if(type.equals("sign")) { - return NumberFormat.Field.SIGN; - } - if(type.equals("integer")) { - return NumberFormat.Field.INTEGER; - } - if(type.equals("fraction")) { - return NumberFormat.Field.FRACTION; - } - if(type.equals("exponent")) { - return NumberFormat.Field.EXPONENT; - } - if(type.equals("exponent_sign")) { - return NumberFormat.Field.EXPONENT_SIGN; - } - if(type.equals("exponent_symbol")) { - return NumberFormat.Field.EXPONENT_SYMBOL; - } - if(type.equals("currency")) { - return NumberFormat.Field.CURRENCY; - } - if(type.equals("grouping_separator")) { - return NumberFormat.Field.GROUPING_SEPARATOR; - } - if(type.equals("decimal_separator")) { - return NumberFormat.Field.DECIMAL_SEPARATOR; - } - if(type.equals("percent")) { - return NumberFormat.Field.PERCENT; - } - if(type.equals("permille")) { - return NumberFormat.Field.PERMILLE; - } - return null; - } -} diff --git a/icu/src/main/java/com/ibm/icu4jni/text/NativeDecimalFormat.java b/icu/src/main/java/com/ibm/icu4jni/text/NativeDecimalFormat.java index ec07fbd..034491c 100644 --- a/icu/src/main/java/com/ibm/icu4jni/text/NativeDecimalFormat.java +++ b/icu/src/main/java/com/ibm/icu4jni/text/NativeDecimalFormat.java @@ -16,93 +16,599 @@ package com.ibm.icu4jni.text; +import com.ibm.icu4jni.util.LocaleData; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.AttributedCharacterIterator; +import java.text.AttributedString; +import java.text.DecimalFormatSymbols; import java.text.FieldPosition; +import java.text.Format; +import java.text.NumberFormat; import java.text.ParsePosition; +import java.util.Currency; +import java.util.Locale; + +public class NativeDecimalFormat { + /** + * Constants corresponding to the native type UNumberFormatSymbol, for getSymbol/setSymbol. + */ + private static final int UNUM_DECIMAL_SEPARATOR_SYMBOL = 0; + private static final int UNUM_GROUPING_SEPARATOR_SYMBOL = 1; + private static final int UNUM_PATTERN_SEPARATOR_SYMBOL = 2; + private static final int UNUM_PERCENT_SYMBOL = 3; + private static final int UNUM_ZERO_DIGIT_SYMBOL = 4; + private static final int UNUM_DIGIT_SYMBOL = 5; + private static final int UNUM_MINUS_SIGN_SYMBOL = 6; + private static final int UNUM_PLUS_SIGN_SYMBOL = 7; + private static final int UNUM_CURRENCY_SYMBOL = 8; + private static final int UNUM_INTL_CURRENCY_SYMBOL = 9; + private static final int UNUM_MONETARY_SEPARATOR_SYMBOL = 10; + private static final int UNUM_EXPONENTIAL_SYMBOL = 11; + private static final int UNUM_PERMILL_SYMBOL = 12; + private static final int UNUM_PAD_ESCAPE_SYMBOL = 13; + private static final int UNUM_INFINITY_SYMBOL = 14; + private static final int UNUM_NAN_SYMBOL = 15; + private static final int UNUM_SIGNIFICANT_DIGIT_SYMBOL = 16; + private static final int UNUM_MONETARY_GROUPING_SEPARATOR_SYMBOL = 17; + private static final int UNUM_FORMAT_SYMBOL_COUNT = 18; + + /** + * Constants corresponding to the native type UNumberFormatAttribute, for + * getAttribute/setAttribute. + */ + private static final int UNUM_PARSE_INT_ONLY = 0; + private static final int UNUM_GROUPING_USED = 1; + private static final int UNUM_DECIMAL_ALWAYS_SHOWN = 2; + private static final int UNUM_MAX_INTEGER_DIGITS = 3; + private static final int UNUM_MIN_INTEGER_DIGITS = 4; + private static final int UNUM_INTEGER_DIGITS = 5; + private static final int UNUM_MAX_FRACTION_DIGITS = 6; + private static final int UNUM_MIN_FRACTION_DIGITS = 7; + private static final int UNUM_FRACTION_DIGITS = 8; + private static final int UNUM_MULTIPLIER = 9; + private static final int UNUM_GROUPING_SIZE = 10; + private static final int UNUM_ROUNDING_MODE = 11; + private static final int UNUM_ROUNDING_INCREMENT = 12; + private static final int UNUM_FORMAT_WIDTH = 13; + private static final int UNUM_PADDING_POSITION = 14; + private static final int UNUM_SECONDARY_GROUPING_SIZE = 15; + private static final int UNUM_SIGNIFICANT_DIGITS_USED = 16; + private static final int UNUM_MIN_SIGNIFICANT_DIGITS = 17; + private static final int UNUM_MAX_SIGNIFICANT_DIGITS = 18; + private static final int UNUM_LENIENT_PARSE = 19; + + /** + * Constants corresponding to the native type UNumberFormatTextAttribute, for + * getTextAttribute/setTextAttribute. + */ + private static final int UNUM_POSITIVE_PREFIX = 0; + private static final int UNUM_POSITIVE_SUFFIX = 1; + private static final int UNUM_NEGATIVE_PREFIX = 2; + private static final int UNUM_NEGATIVE_SUFFIX = 3; + private static final int UNUM_PADDING_CHARACTER = 4; + private static final int UNUM_CURRENCY_CODE = 5; + private static final int UNUM_DEFAULT_RULESET = 6; + private static final int UNUM_PUBLIC_RULESETS = 7; + + /** + * The address of the ICU DecimalFormat* on the native heap. + */ + private final int addr; + + /** + * The last pattern we gave to ICU, so we can make repeated applications cheap. + * This helps in cases like String.format("%.2f,%.2f\n", x, y) where the DecimalFormat is + * reused. + */ + private String lastPattern; + + // TODO: store all these in DecimalFormat instead! + private boolean negPrefNull; + private boolean negSuffNull; + private boolean posPrefNull; + private boolean posSuffNull; + + /** + * Cache the BigDecimal form of the multiplier. This is null until we've + * formatted a BigDecimal (with a multiplier that is not 1), or the user has + * explicitly called {@link #setMultiplier(int)} with any multiplier. + */ + private BigDecimal multiplierBigDecimal = null; + + public NativeDecimalFormat(String pattern, Locale locale, DecimalFormatSymbols symbols) { + this.addr = openDecimalFormat(locale.toString(), pattern); + this.lastPattern = pattern; + setDecimalFormatSymbols(symbols); + } + + // Used to implement clone. + private NativeDecimalFormat(NativeDecimalFormat other) { + this.addr = cloneDecimalFormatImpl(other.addr); + this.lastPattern = other.lastPattern; + this.negPrefNull = other.negPrefNull; + this.negSuffNull = other.negSuffNull; + this.posPrefNull = other.posPrefNull; + this.posSuffNull = other.posSuffNull; + } + + // TODO: remove this and just have DecimalFormat.hashCode do the right thing itself. + @Override + public int hashCode() { + return this.getPositivePrefix().hashCode(); + } + + @Override + public Object clone() { + return new NativeDecimalFormat(this); + } + + @Override + protected void finalize() { + closeDecimalFormatImpl(this.addr); + } + + /** + * Note: this doesn't check that the underlying native DecimalFormat objects' configured + * native DecimalFormatSymbols objects are equal. It is assumed that the + * caller (DecimalFormat) will check the DecimalFormatSymbols objects + * instead, for performance. + * + * This is also unreasonably expensive, calling down to JNI multiple times. + * + * TODO: remove this and just have DecimalFormat.equals do the right thing itself. + */ + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof NativeDecimalFormat)) { + return false; + } + NativeDecimalFormat obj = (NativeDecimalFormat) object; + if (obj.addr == this.addr) { + return true; + } + return obj.toPattern().equals(this.toPattern()) && + obj.isDecimalSeparatorAlwaysShown() == this.isDecimalSeparatorAlwaysShown() && + obj.getGroupingSize() == this.getGroupingSize() && + obj.getMultiplier() == this.getMultiplier() && + obj.getNegativePrefix().equals(this.getNegativePrefix()) && + obj.getNegativeSuffix().equals(this.getNegativeSuffix()) && + obj.getPositivePrefix().equals(this.getPositivePrefix()) && + obj.getPositiveSuffix().equals(this.getPositiveSuffix()) && + obj.getMaximumIntegerDigits() == this.getMaximumIntegerDigits() && + obj.getMaximumFractionDigits() == this.getMaximumFractionDigits() && + obj.getMinimumIntegerDigits() == this.getMinimumIntegerDigits() && + obj.getMinimumFractionDigits() == this.getMinimumFractionDigits() && + obj.isGroupingUsed() == this.isGroupingUsed() && + obj.getCurrency() == this.getCurrency(); + } + + /** + * Copies the DecimalFormatSymbols settings into our native peer. + */ + public void setDecimalFormatSymbols(final DecimalFormatSymbols dfs) { + setSymbol(this.addr, UNUM_CURRENCY_SYMBOL, dfs.getCurrencySymbol()); + + setSymbol(this.addr, UNUM_DECIMAL_SEPARATOR_SYMBOL, dfs.getDecimalSeparator()); + setSymbol(this.addr, UNUM_DIGIT_SYMBOL, dfs.getDigit()); + + char groupingSeparator = dfs.getGroupingSeparator(); + setSymbol(this.addr, UNUM_GROUPING_SEPARATOR_SYMBOL, groupingSeparator); + setSymbol(this.addr, UNUM_MONETARY_GROUPING_SEPARATOR_SYMBOL, groupingSeparator); + + setSymbol(this.addr, UNUM_INFINITY_SYMBOL, dfs.getInfinity()); + setSymbol(this.addr, UNUM_INTL_CURRENCY_SYMBOL, dfs.getInternationalCurrencySymbol()); + setSymbol(this.addr, UNUM_MINUS_SIGN_SYMBOL, dfs.getMinusSign()); + setSymbol(this.addr, UNUM_MONETARY_SEPARATOR_SYMBOL, dfs.getMonetaryDecimalSeparator()); + setSymbol(this.addr, UNUM_NAN_SYMBOL, dfs.getNaN()); + setSymbol(this.addr, UNUM_PATTERN_SEPARATOR_SYMBOL, dfs.getPatternSeparator()); + setSymbol(this.addr, UNUM_PERCENT_SYMBOL, dfs.getPercent()); + setSymbol(this.addr, UNUM_PERMILL_SYMBOL, dfs.getPerMill()); + setSymbol(this.addr, UNUM_ZERO_DIGIT_SYMBOL, dfs.getZeroDigit()); + } -final class NativeDecimalFormat { - - enum UNumberFormatAttribute { - UNUM_PARSE_INT_ONLY, - UNUM_GROUPING_USED, - UNUM_DECIMAL_ALWAYS_SHOWN, - UNUM_MAX_INTEGER_DIGITS, - UNUM_MIN_INTEGER_DIGITS, - UNUM_INTEGER_DIGITS, - UNUM_MAX_FRACTION_DIGITS, - UNUM_MIN_FRACTION_DIGITS, - UNUM_FRACTION_DIGITS, - UNUM_MULTIPLIER, - UNUM_GROUPING_SIZE, - UNUM_ROUNDING_MODE, - UNUM_ROUNDING_INCREMENT, - UNUM_FORMAT_WIDTH, - UNUM_PADDING_POSITION, - UNUM_SECONDARY_GROUPING_SIZE, - UNUM_SIGNIFICANT_DIGITS_USED, - UNUM_MIN_SIGNIFICANT_DIGITS, - UNUM_MAX_SIGNIFICANT_DIGITS, - UNUM_LENIENT_PARSE - } - - enum UNumberFormatTextAttribute { - UNUM_POSITIVE_PREFIX, - UNUM_POSITIVE_SUFFIX, - UNUM_NEGATIVE_PREFIX, - UNUM_NEGATIVE_SUFFIX, - UNUM_PADDING_CHARACTER, - UNUM_CURRENCY_CODE, - UNUM_DEFAULT_RULESET, - UNUM_PUBLIC_RULESETS + private BigDecimal applyMultiplier(BigDecimal valBigDecimal) { + if (multiplierBigDecimal == null) { + multiplierBigDecimal = BigDecimal.valueOf(getMultiplier()); + } + // Get new value by multiplying multiplier. + return valBigDecimal.multiply(multiplierBigDecimal); } - static int openDecimalFormat(String locale, String pattern) { + public StringBuffer formatBigDecimal(BigDecimal value, StringBuffer buffer, FieldPosition field) { + if (buffer == null || field == null) { + throw new NullPointerException(); + } + if (getMultiplier() != 1) { + value = applyMultiplier(value); + } + StringBuilder val = new StringBuilder(); + val.append(value.unscaledValue().toString(10)); + int scale = value.scale(); + scale = makeScalePositive(scale, val); + String fieldType = getFieldType(field.getFieldAttribute()); + String result = format(this.addr, val.toString(), field, fieldType, null, scale); + return buffer.append(result); + } + + public StringBuffer formatBigInteger(BigInteger value, StringBuffer buffer, FieldPosition field) { + if (buffer == null || field == null) { + throw new NullPointerException(); + } + String fieldType = getFieldType(field.getFieldAttribute()); + String result = format(this.addr, value.toString(10), field, fieldType, null, 0); + return buffer.append(result); + } + + public StringBuffer format(long value, StringBuffer buffer, FieldPosition field) { + if (buffer == null || field == null) { + throw new NullPointerException(); + } + String fieldType = getFieldType(field.getFieldAttribute()); + buffer.append(format(this.addr, value, field, fieldType, null)); + return buffer; + } + + public StringBuffer format(double value, StringBuffer buffer, FieldPosition field) { + if (buffer == null || field == null) { + throw new NullPointerException(); + } + String fieldType = getFieldType(field.getFieldAttribute()); + buffer.append(format(this.addr, value, field, fieldType, null)); + return buffer; + } + + public void applyLocalizedPattern(String pattern) { + applyPattern(this.addr, true, pattern); + lastPattern = null; + } + + public void applyPattern(String pattern) { + if (lastPattern != null && pattern.equals(lastPattern)) { + return; + } + applyPattern(this.addr, false, pattern); + lastPattern = pattern; + } + + public AttributedCharacterIterator formatToCharacterIterator(Object object) { + if (!(object instanceof Number)) { + throw new IllegalArgumentException(); + } + Number number = (Number) object; + String text = null; + StringBuffer attributes = new StringBuffer(); + + if(number instanceof BigInteger) { + BigInteger valBigInteger = (BigInteger) number; + text = format(this.addr, valBigInteger.toString(10), null, null, attributes, 0); + } else if(number instanceof BigDecimal) { + BigDecimal valBigDecimal = (BigDecimal) number; + if (getMultiplier() != 1) { + valBigDecimal = applyMultiplier(valBigDecimal); + } + StringBuilder val = new StringBuilder(); + val.append(valBigDecimal.unscaledValue().toString(10)); + int scale = valBigDecimal.scale(); + scale = makeScalePositive(scale, val); + text = format(this.addr, val.toString(), null, null, attributes, scale); + } else if (number instanceof Double || number instanceof Float) { + double dv = number.doubleValue(); + text = format(this.addr, dv, null, null, attributes); + } else { + long lv = number.longValue(); + text = format(this.addr, lv, null, null, attributes); + } + + AttributedString as = new AttributedString(text); + + String[] attrs = attributes.toString().split(";"); + // add NumberFormat field attributes to the AttributedString + int size = attrs.length / 3; + if(size * 3 != attrs.length) { + return as.getIterator(); + } + for (int i = 0; i < size; i++) { + Format.Field attribute = getField(attrs[3*i]); + as.addAttribute(attribute, attribute, Integer.parseInt(attrs[3*i+1]), + Integer.parseInt(attrs[3*i+2])); + } + + // return the CharacterIterator from AttributedString + return as.getIterator(); + } + + private int makeScalePositive(int scale, StringBuilder val) { + if (scale < 0) { + scale = -scale; + for (int i = scale; i > 0; i--) { + val.append('0'); + } + scale = 0; + } + return scale; + } + + public String toLocalizedPattern() { + return toPatternImpl(this.addr, true); + } + + public String toPattern() { + return toPatternImpl(this.addr, false); + } + + public Number parse(String string, ParsePosition position) { + return parse(addr, string, position); + } + + // start getter and setter + + public int getMaximumFractionDigits() { + return getAttribute(this.addr, UNUM_MAX_FRACTION_DIGITS); + } + + public int getMaximumIntegerDigits() { + return getAttribute(this.addr, UNUM_MAX_INTEGER_DIGITS); + } + + public int getMinimumFractionDigits() { + return getAttribute(this.addr, UNUM_MIN_FRACTION_DIGITS); + } + + public int getMinimumIntegerDigits() { + return getAttribute(this.addr, UNUM_MIN_INTEGER_DIGITS); + } + + public Currency getCurrency() { + String curr = getSymbol(this.addr, UNUM_INTL_CURRENCY_SYMBOL); + if (curr.equals("") || curr.equals("\u00a4\u00a4")) { + return null; + } + return Currency.getInstance(curr); + } + + public int getGroupingSize() { + return getAttribute(this.addr, UNUM_GROUPING_SIZE); + } + + public int getMultiplier() { + return getAttribute(this.addr, UNUM_MULTIPLIER); + } + + public String getNegativePrefix() { + if (negPrefNull) { + return null; + } + return getTextAttribute(this.addr, UNUM_NEGATIVE_PREFIX); + } + + public String getNegativeSuffix() { + if (negSuffNull) { + return null; + } + return getTextAttribute(this.addr, UNUM_NEGATIVE_SUFFIX); + } + + public String getPositivePrefix() { + if (posPrefNull) { + return null; + } + return getTextAttribute(this.addr, UNUM_POSITIVE_PREFIX); + } + + public String getPositiveSuffix() { + if (posSuffNull) { + return null; + } + return getTextAttribute(this.addr, UNUM_POSITIVE_SUFFIX); + } + + public boolean isDecimalSeparatorAlwaysShown() { + return getAttribute(this.addr, UNUM_DECIMAL_ALWAYS_SHOWN) != 0; + } + + public boolean isParseIntegerOnly() { + return getAttribute(this.addr, UNUM_PARSE_INT_ONLY) != 0; + } + + public boolean isGroupingUsed() { + return getAttribute(this.addr, UNUM_GROUPING_USED) != 0; + } + + public void setDecimalSeparatorAlwaysShown(boolean value) { + int i = value ? -1 : 0; + setAttribute(this.addr, UNUM_DECIMAL_ALWAYS_SHOWN, i); + } + + public void setCurrency(Currency currency) { + setSymbol(this.addr, UNUM_CURRENCY_SYMBOL, currency.getSymbol()); + setSymbol(this.addr, UNUM_INTL_CURRENCY_SYMBOL, currency.getCurrencyCode()); + } + + public void setGroupingSize(int value) { + setAttribute(this.addr, UNUM_GROUPING_SIZE, value); + } + + public void setGroupingUsed(boolean value) { + int i = value ? -1 : 0; + setAttribute(this.addr, UNUM_GROUPING_USED, i); + } + + public void setMaximumFractionDigits(int value) { + setAttribute(this.addr, UNUM_MAX_FRACTION_DIGITS, value); + } + + public void setMaximumIntegerDigits(int value) { + setAttribute(this.addr, UNUM_MAX_INTEGER_DIGITS, value); + } + + public void setMinimumFractionDigits(int value) { + setAttribute(this.addr, UNUM_MIN_FRACTION_DIGITS, value); + } + + public void setMinimumIntegerDigits(int value) { + setAttribute(this.addr, UNUM_MIN_INTEGER_DIGITS, value); + } + + public void setMultiplier(int value) { + setAttribute(this.addr, UNUM_MULTIPLIER, value); + // Update the cached BigDecimal for multiplier. + multiplierBigDecimal = BigDecimal.valueOf(value); + } + + public void setNegativePrefix(String value) { + negPrefNull = value == null; + if (!negPrefNull) { + setTextAttribute(this.addr, UNUM_NEGATIVE_PREFIX, value); + } + } + + public void setNegativeSuffix(String value) { + negSuffNull = value == null; + if (!negSuffNull) { + setTextAttribute(this.addr, UNUM_NEGATIVE_SUFFIX, value); + } + } + + public void setPositivePrefix(String value) { + posPrefNull = value == null; + if (!posPrefNull) { + setTextAttribute(this.addr, UNUM_POSITIVE_PREFIX, value); + } + } + + public void setPositiveSuffix(String value) { + posSuffNull = value == null; + if (!posSuffNull) { + setTextAttribute(this.addr, UNUM_POSITIVE_SUFFIX, value); + } + } + + public void setParseIntegerOnly(boolean value) { + int i = value ? -1 : 0; + setAttribute(this.addr, UNUM_PARSE_INT_ONLY, i); + } + + static protected String getFieldType(Format.Field field) { + if(field == null) { + return null; + } + if(field.equals(NumberFormat.Field.SIGN)) { + return "sign"; + } + if(field.equals(NumberFormat.Field.INTEGER)) { + return "integer"; + } + if(field.equals(NumberFormat.Field.FRACTION)) { + return "fraction"; + } + if(field.equals(NumberFormat.Field.EXPONENT)) { + return "exponent"; + } + if(field.equals(NumberFormat.Field.EXPONENT_SIGN)) { + return "exponent_sign"; + } + if(field.equals(NumberFormat.Field.EXPONENT_SYMBOL)) { + return "exponent_symbol"; + } + if(field.equals(NumberFormat.Field.CURRENCY)) { + return "currency"; + } + if(field.equals(NumberFormat.Field.GROUPING_SEPARATOR)) { + return "grouping_separator"; + } + if(field.equals(NumberFormat.Field.DECIMAL_SEPARATOR)) { + return "decimal_separator"; + } + if(field.equals(NumberFormat.Field.PERCENT)) { + return "percent"; + } + if(field.equals(NumberFormat.Field.PERMILLE)) { + return "permille"; + } + return null; + } + + protected Format.Field getField(String type) { + if(type.equals("")) { + return null; + } + if(type.equals("sign")) { + return NumberFormat.Field.SIGN; + } + if(type.equals("integer")) { + return NumberFormat.Field.INTEGER; + } + if(type.equals("fraction")) { + return NumberFormat.Field.FRACTION; + } + if(type.equals("exponent")) { + return NumberFormat.Field.EXPONENT; + } + if(type.equals("exponent_sign")) { + return NumberFormat.Field.EXPONENT_SIGN; + } + if(type.equals("exponent_symbol")) { + return NumberFormat.Field.EXPONENT_SYMBOL; + } + if(type.equals("currency")) { + return NumberFormat.Field.CURRENCY; + } + if(type.equals("grouping_separator")) { + return NumberFormat.Field.GROUPING_SEPARATOR; + } + if(type.equals("decimal_separator")) { + return NumberFormat.Field.DECIMAL_SEPARATOR; + } + if(type.equals("percent")) { + return NumberFormat.Field.PERCENT; + } + if(type.equals("permille")) { + return NumberFormat.Field.PERMILLE; + } + return null; + } + + private static int openDecimalFormat(String locale, String pattern) { try { // FIXME: if we're about to override everything, should we just ask for the cheapest locale (presumably the root locale)? - return NativeDecimalFormat.openDecimalFormatImpl(locale, pattern); + return openDecimalFormatImpl(locale, pattern); } catch (NullPointerException npe) { throw npe; } catch (RuntimeException re) { throw new IllegalArgumentException("syntax error: " + re.getMessage() + ": " + pattern); } } - private static native int openDecimalFormatImpl(String locale, String pattern); - static native void closeDecimalFormatImpl(int addr); - - static native int cloneDecimalFormatImpl(int addr); - - static native void setSymbol(int addr, int symbol, String str); - static native void setSymbol(int addr, int symbol, char ch); - - // FIXME: do we need this any more? the Java-side object should be the canonical source. - static native String getSymbol(int addr, int symbol); - - static native void setAttribute(int addr, int symbol, int i); - - static native int getAttribute(int addr, int symbol); - - static native void setTextAttribute(int addr, int symbol, String str); - - static native String getTextAttribute(int addr, int symbol); - - static void applyPattern(int addr, boolean localized, String pattern) { + private static void applyPattern(int addr, boolean localized, String pattern) { try { - NativeDecimalFormat.applyPatternImpl(addr, localized, pattern); + applyPatternImpl(addr, localized, pattern); } catch (NullPointerException npe) { throw npe; } catch (RuntimeException re) { throw new IllegalArgumentException("syntax error: " + re.getMessage() + ": " + pattern); } } - private static native void applyPatternImpl(int addr, boolean localized, String pattern); - - static native String toPatternImpl(int addr, boolean localized); - - static native String format(int addr, long value, FieldPosition position, String fieldType, StringBuffer attributes); - - static native String format(int addr, double value, FieldPosition position, String fieldType, StringBuffer attributes); - static native String format(int addr, String value, FieldPosition position, String fieldType, StringBuffer attributes, int scale); - - static native Number parse(int addr, String string, ParsePosition position); + private static native void applyPatternImpl(int addr, boolean localized, String pattern); + private static native int cloneDecimalFormatImpl(int addr); + private static native void closeDecimalFormatImpl(int addr); + private static native String format(int addr, long value, FieldPosition position, String fieldType, StringBuffer attributes); + private static native String format(int addr, double value, FieldPosition position, String fieldType, StringBuffer attributes); + private static native String format(int addr, String value, FieldPosition position, String fieldType, StringBuffer attributes, int scale); + private static native int getAttribute(int addr, int symbol); + // FIXME: do we need getSymbol any more? the Java-side object should be the canonical source. + private static native String getSymbol(int addr, int symbol); + private static native String getTextAttribute(int addr, int symbol); + private static native int openDecimalFormatImpl(String locale, String pattern); + private static native Number parse(int addr, String string, ParsePosition position); + private static native void setSymbol(int addr, int symbol, String str); + private static native void setSymbol(int addr, int symbol, char ch); + private static native void setAttribute(int addr, int symbol, int i); + private static native void setTextAttribute(int addr, int symbol, String str); + private static native String toPatternImpl(int addr, boolean localized); } |