/*
*
*
* Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.j2me.global;
import com.sun.j2me.log.Logging;
import com.sun.j2me.log.LogChannels;
/**
* <code>NumberFormat</code> has features designed to make it possible to
* format numbers in any locale. It also supports different kinds of numbers,
* including integers (123), fixed-point numbers (123.4), percentages (12%),
* and currency amounts ($123). All of these can be localized. <p>
*
* To obtain a <code>NumberFormat</code> for a specific locale call one of
* <code>NumberFormat</code>'s factory methods, such as:
* <ul>
* <li> <code>getPercentageInstance(locale)</code>
* <li> <code>getIntegerInstance(locale)</code>
* <li> <code>getCurrencyInstance(locale)</code>
* <li> <code>getDecimalInstance(local)</code>
* </ul>
* <p>
*
* Usage: <pre>
* NumberFormat f = NumberFormat.getCurrencyInstance(loc);
* StringBuffer sb = f.format(new Double(123.45), new StringBuffer());
* </pre> <p>
*
* Or eventualy it's possible to change number of decimals displayed <pre>
* NumberFormat f = NumberFormat.getCurrencyInstance(loc);
* f.setMaximumFractionDigits(2);
* StringBuffer sb = f.format(new Double(123.45559), new StringBuffer());
* </pre>
*
*/
public class NumberFormat {
/**
* Class name.
*/
private static final String classname = NumberFormat.class.getName();
/**
* Upper limit on integer digits for a Java double.
*/
private final static int DOUBLE_INTEGER_DIGITS = 309;
/**
* Upper limit on fraction digits for a Java double.
*/
private final static int DOUBLE_FRACTION_DIGITS = 340;
/**
* Non localized percent sign.
*/
public final static char NONLOCALIZED_PERCENT_SIGN = '\u0025';
/**
* Unicode INFINITY character.
*/
public final static char UNICODE_INFINITY = '\u221e';
/**
* Styles of formatting j2se compatible.
*/
/**
* General number.
*/
public final static int NUMBERSTYLE = 0;
/**
* Currency style.
*/
public final static int CURRENCYSTYLE = 1;
/**
* Percent style.
*/
public final static int PERCENTSTYLE = 2;
/**
* Integer style.
*/
public final static int INTEGERSTYLE = 3;
/**
* Holds initialized instance of DecimalFormatSymbols which encapsulate
* locale dependent informations like currency symbol, percent symbol etc.
*/
private NumberFormatSymbols symbols;
/**
* Is this <code>NumberFormat</code> instance for currency formatting?
*/
private boolean isCurrencyFormat = false;
/**
* Is this <code>NumberFormat</code> instance of percentage formatting?
*/
private boolean isPercentageFormat = false;
/**
* Digit list does most of formatting work.
*/
private DigitList digitList = new DigitList();
/**
* Style of <code>NumberFormat</code>. Possible styles are:
* <ul>
* <li> {@link #NUMBERSTYLE}
* <li> {@link #CURRENCYSTYLE}
* <li> {@link #PERCENTSTYLE}
* <li> {@link #INTEGERSTYLE}
* </ul>
*/
private int style = NUMBERSTYLE;
/**
* Create <code>NumberFormat</code> with given number pattern and set of
* locale numeric symbols.
*
* @param style the style of <code>NumberFormat</code>
* <ul>
* <li> {@link #NUMBERSTYLE}
* <li> {@link #CURRENCYSTYLE}
* <li> {@link #PERCENTSTYLE}
* <li> {@link #INTEGERSTYLE}
* </ul>
*
* @param symbols NumberFormatSymbols identifying numbers formatting for
* given locale.
*/
public NumberFormat(int style, NumberFormatSymbols symbols) {
this.style = style;
this.symbols = symbols;
isCurrencyFormat = (style == CURRENCYSTYLE);
isPercentageFormat = (style == PERCENTSTYLE);
applySymbols();
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
classname + ": " +
"NumberFormat created\n" +
"style is " + style + "\n" +
"symbols is " + symbols);
}
}
/**
* Set maximal number of decimals to be displayed.
*
* @param count number of decimals to display
* @see #getMaximumFractionDigits
*/
public void setMaximumFractionDigits(int count) {
if (symbols != null &&
count <= DOUBLE_FRACTION_DIGITS &&
count >= 0 &&
style != INTEGERSTYLE) {
symbols.maximumFractionDigits[style] = count;
if (symbols.minimumFractionDigits[style] < count) {
symbols.minimumFractionDigits[style] = count;
}
}
}
/**
* How many decimals is used to display number.
*
* @return maximum number of decimals or <code>-1</code> if non-localized
* formatting is used.
* @see #setMaximumFractionDigits
*/
public int getMaximumFractionDigits() {
return (symbols != null) ?
symbols.maximumFractionDigits[style] :
-1;
}
/**
* Sets minimum number of decimals to be displayed.
*
* @param count minimum number of decimals to display
* @see #getMinimumFractionDigits
*/
public void setMinimumFractionDigits(int count) {
if (symbols != null &&
count >= 0 &&
style != INTEGERSTYLE) {
symbols.minimumFractionDigits[style] = count;
if (count > symbols.maximumFractionDigits[style]) {
symbols.maximumFractionDigits[style] = count;
}
}
}
/**
* Sets multiplier to different value than symbols for this locale do.
*
* @param multiplier new value for multiplier;
* @see #getMultiplier
*/
public void setMultiplier(int multiplier) {
if (symbols != null) {
symbols.multiplier[style] = multiplier;
}
}
/**
* Gets actual multilier used by this locale for this number style. Usually
* (1 or 100).
*
* @return the multiplier
* @see #setMultiplier
*/
public int getMultiplier() {
return (symbols != null) ? symbols.multiplier[style] : 1;
}
/**
* Sets if grouping is used.
*
* @param used <code>true</code> if grouping should be used
*/
public void setGroupingUsed(boolean used) {
if (symbols != null) {
symbols.groupingUsed = used;
}
}
/**
* Get minimum of decimals used to display number.
*
* @return minimum number of decimals or <code>-1</code> if non-localized
* formatting is used.
* @see #setMinimumFractionDigits
*/
public int getMinimumFractionDigits() {
return (symbols != null) ?
symbols.minimumFractionDigits[style] :
-1;
}
/**
* Sets minimum integer digits.
*
* @param count the count of digits
* @see #getMinimumIntegerDigits
*/
public void setMinimumIntegerDigits(int count) {
if (symbols != null && count > 0) {
symbols.minimumIntegerDigits[style] = count;
}
}
/**
* Gets minimum integer digits.
*
* @return number minimum of integer digits
* @see #setMinimumIntegerDigits
*/
public int getMinimumIntegerDigits() {
return (symbols != null) ?
symbols.minimumIntegerDigits[style] :
-1;
}
/**
* Sets currency symbol.
*
* @param symbol the currency symbol
* @return previously used currency symbol
*/
public String setCurrencySymbol(String symbol) {
String oldsymbol = null;
if (isCurrencyFormat) {
if (symbols != null) {
oldsymbol = symbols.currencySymbol;
if (!symbol.equals(symbols.currencySymbol)) {
symbols.currencySymbol = symbol;
symbols.suffixes[style] =
replSubStr(symbols.suffixes[style], oldsymbol,
symbol);
symbols.prefixes[style] =
replSubStr(symbols.prefixes[style], oldsymbol,
symbol);
symbols.negativeSuffix[style] =
replSubStr(symbols.negativeSuffix[style], oldsymbol,
symbol);
symbols.negativePrefix[style] =
replSubStr(symbols.negativePrefix[style], oldsymbol,
symbol);
symbols.positiveSuffix[style] =
replSubStr(symbols.positiveSuffix[style], oldsymbol,
symbol);
symbols.positivePrefix[style] =
replSubStr(symbols.positivePrefix[style], oldsymbol,
symbol);
}
}
}
return oldsymbol;
}
/**
* Replaces substring in the string onto new string.
*
* @param str the changed string
* @param oldVal the replaced substring
* @param newVal the replacing string
* @return changed string
*/
private String replSubStr(String str, String oldVal, String newVal) {
String res = str;
if (str.length() > 0) {
int pos = str.indexOf(oldVal);
if (pos >= 0) {
res = str.substring(0, pos);
res = res.concat(newVal);
res = res.concat(str.substring(pos + oldVal.length()));
return res;
}
}
return res;
}
/**
* Lookup table of supported currencies for appropriate symbol.
*
* @param currencyCode code ISO 4217.
* @return currency symbol or <code>null</code> if none was found.
*/
public String getCurrencySymbolForCode(String currencyCode) {
if (symbols != null && symbols.currencies != null){
for (int i = 0; i < symbols.currencies.length; i++) {
if (symbols.currencies[i].length>0 && symbols.currencies[i][0].equals(currencyCode))
if (symbols.currencies[i].length>1){
return symbols.currencies[i][1];
} else {
return null;
}
}
}
return null;
}
/**
* Check if some attributes of <code>NumberFormatSymbols</code> are
* undefined and replace them with default values.
*/
private void applySymbols() {
if (symbols != null) {
if (symbols.maximumIntegerDigits[style] == -1) {
symbols.maximumIntegerDigits[style] = DOUBLE_INTEGER_DIGITS;
}
if (symbols.maximumFractionDigits[style] == -1) {
symbols.maximumFractionDigits[style] = DOUBLE_FRACTION_DIGITS;
}
}
}
/**
* Method formats long.
*
* @param value long number to format
* @return formatted long number
*/
public String format(long value) {
return format(new Long(value));
}
/**
* Method formats double.
*
* @param value double value to format
* @return formatted double number
*/
public String format(double value) {
if (symbols != null) {
if (Double.isNaN(value)) {
return symbols.NaN;
}
if (Double.isInfinite(value)) {
String prefix = (value > 0.0) ? "" : symbols.negativePrefix[style];
String suffix = (value > 0.0) ? "" : symbols.negativeSuffix[style];
return prefix + symbols.infinity + suffix;
}
} else {
if (Double.isNaN(value)) {
return "NaN";
}
if (Double.isInfinite(value)) {
String prefix = (value > 0.0) ? "" : "-";
return prefix + UNICODE_INFINITY;
}
}
return format(new Double(value));
}
/**
* Method formats integer.
*
* @param value integer value to format
* @return formatted integer number
*/
public String format(int value) {
return format(new Long(value));
}
/**
* Method formats float.
*
* @param value float value to format
* @return formatted float number
*/
public String format(float value) {
return format((double)value);
}
/**
* Does formatting. Result is appended to parameter
* <code>StringBuffer appendTo</code>.
*
* @param o object to format
* @return buffer with appended formatted text
*/
protected String format(Object o) {
StringBuffer appendTo = new StringBuffer();
if (o == null) {
return "";
}
if (symbols != null) {
if (o instanceof Double) {
format(((Double) o).doubleValue(), appendTo);
}
if (o instanceof Long) {
format(((Long) o).longValue(), appendTo);
}
} else {
if (isPercentageFormat) {
if (o instanceof Double) {
appendTo.append(Double.toString(
((Double)o).doubleValue() * 100.0));
} else if (o instanceof Long) {
long value = ((Long) o).longValue();
appendTo.append(Long.toString(value));
if (value != 0) appendTo.append("00");
}
appendTo.append(NONLOCALIZED_PERCENT_SIGN);
} else {
return o.toString();
}
}
return appendTo.toString();
}
/**
* Formats double number.
*
* @param number the double number to formatt
* @param result formatted number
* @return buffer with appended formatted number
*/
private StringBuffer format(double number, StringBuffer result) {
if (Double.isNaN(number)) {
result.append(symbols.NaN);
return result;
}
boolean isNegative = (number < 0.0) ||
(number == 0.0 && 1 / number < 0.0);
if (isNegative) {
number = -number;
}
if (symbols.multiplier[style] != 1) {
number *= symbols.multiplier[style];
}
if (Double.isInfinite(number)) {
if (isNegative) {
result.append(symbols.negativePrefix[style]);
} else {
result.append(symbols.positivePrefix[style]);
}
result.append(symbols.infinity);
if (isNegative) {
result.append(symbols.negativeSuffix[style]);
} else {
result.append(symbols.positiveSuffix[style]);
}
return result;
}
digitList.set(number, symbols.maximumFractionDigits[style]);
result = subformat(result, isNegative, false);
return result;
}
/**
* Format a long to produce a string.
*
* @param number The long to format
* @param result where the text is to be appended
* @return The formatted number
*/
private StringBuffer format(long number, StringBuffer result) {
boolean isNegative = (number < 0);
if (isNegative) {
number = -number;
}
if (symbols.multiplier[style] != 1 &&
symbols.multiplier[style] != 0) {
boolean useDouble = false;
if (number < 0) {
// This can only happen if number == Long.MIN_VALUE
long cutoff = Long.MIN_VALUE / symbols.multiplier[style];
useDouble = (number < cutoff);
} else {
long cutoff = Long.MAX_VALUE / symbols.multiplier[style];
useDouble = (number > cutoff);
}
if (useDouble) {
double dnumber = (double) (isNegative ? -number : number);
return format(dnumber, result);
}
}
number *= symbols.multiplier[style];
synchronized (digitList) {
digitList.set(number, 0);
return subformat(result, isNegative, true);
}
}
/**
* Formats content of DigitList.
*
* @param result buffer to append formatted number to
* @param isNegative <code>true</code> if number is negative
* @param isInteger <code>true</code> if integer number will be formatted
* @return buffer with appended formatted number
*/
private StringBuffer subformat(StringBuffer result,
boolean isNegative, boolean isInteger) {
char zero = symbols.zeroDigit;
int zeroDelta = zero - '0';
// '0' is the DigitList representation of zero
char grouping = symbols.groupingSeparator;
char decimal = isCurrencyFormat ?
symbols.monetarySeparator :
symbols.decimalSeparator;
if (digitList.isZero()) {
digitList.decimalAt = 0;
// Normalize
}
int fieldStart = result.length();
if (isNegative) {
result.append(symbols.negativePrefix[style]);
} else {
result.append(symbols.positivePrefix[style]);
}
String prefix = symbols.prefixes[style];
result.append(prefix);
int count = symbols.minimumIntegerDigits[style];
int digitIndex = 0;
// Index into digitList.fDigits[]
if (digitList.decimalAt > 0 && count < digitList.decimalAt) {
count = digitList.decimalAt;
}
if (count > symbols.maximumIntegerDigits[style]) {
count = symbols.maximumIntegerDigits[style];
digitIndex = digitList.decimalAt - count;
}
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
classname + " :" +
"grouping used " + symbols.groupingUsed + "\n" +
"grouping separator \"" + grouping + "\"\n" +
"decimal separator \"" + decimal + "\"\n" +
"digit count " + count);
}
int sizeBeforeIntegerPart = result.length();
for (int i = count - 1; i >= 0; --i) {
if (i < digitList.decimalAt && digitIndex < digitList.count) {
// Output a real digit
result.append((char) (digitList.digits[digitIndex++] +
zeroDelta));
} else {
// Output a leading zero
result.append(zero);
}
// Output grouping separator if necessary. Don't output a
// grouping separator if i==0 though; that's at the end of
// the integer part.
if (symbols.groupingUsed && i > 0 &&
(symbols.groupingCount != 0) &&
(i % symbols.groupingCount == 0)) {
int gStart = result.length();
result.append(grouping);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR238,
classname + ": " +
"add grouping at " + (digitIndex-1));
}
}
}// for
boolean fractionPresent = (symbols.minimumFractionDigits[style] > 0) ||
(!isInteger && digitIndex < digitList.count);
if (!fractionPresent && result.length() == sizeBeforeIntegerPart) {
result.append(zero);
}
// Output the decimal separator if we always do so.
int sStart = result.length();
if (symbols.decimalSeparatorAlwaysShown || fractionPresent) {
result.append(decimal);
}
for (int i = 0; i < symbols.maximumFractionDigits[style]; ++i) {
if (i >= symbols.minimumFractionDigits[style] &&
(isInteger || digitIndex >= digitList.count)) {
break;
}
if (-1 - i > (digitList.decimalAt - 1)) {
result.append(zero);
continue;
}
if (!isInteger && digitIndex < digitList.count) {
result.append((char) (digitList.digits[digitIndex++] +
zeroDelta));
} else {
result.append(zero);
}
}
String suffix = symbols.suffixes[style];
result.append(suffix);
if (isNegative) {
result.append(symbols.negativeSuffix[style]);
} else {
result.append(symbols.positiveSuffix[style]);
}
return result;
}
}