/*
* Copyright 2006 Robert Hanson
*
* 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 org.gwtwidgets.client.util;
/**
*
* - Title:
- Decimal Format
*
*
- Description:
- This is a simple number formatting/ parsing class. Besides the simple number formatting
* it also interprets shortcuts for thousand (k) million (m) and billion (b).
* This Number Format class was adapted from the public domain javascript class found at
* http://www.mredkj.com/javascript/nfdocs.html
*
*
* @author Jason Essington
* @version $Revision: 0.0 $
*/
public class NumberFormat
{
public static final String COMMA = ",";
public static final String PERIOD = ".";
public static final char DASH = '-';
public static final char LEFT_PAREN = '(';
public static final char RIGHT_PAREN = ')';
// k/m/b Shortcuts
public static final String THOUSAND = "k";
public static final String MILLION = "m";
public static final String BILLION = "b";
// currency position constants
public static final int CUR_POS_LEFT_OUTSIDE = 0;
public static final int CUR_POS_LEFT_INSIDE = 1;
public static final int CUR_POS_RIGHT_INSIDE = 2;
public static final int CUR_POS_RIGHT_OUTSIDE = 3;
// negative format constants
public static final int NEG_LEFT_DASH = 0;
public static final int NEG_RIGHT_DASH = 1;
public static final int NEG_PARENTHESIS = 2;
// constant to signal that fixed precision is not to be used
public static final int ARBITRARY_PRECISION = -1;
private String inputDecimalSeparator = PERIOD; // decimal character used on the original string
private boolean showGrouping = true;
private String groupingSeparator = COMMA; // thousands grouping character
private String decimalSeparator = PERIOD; // decimal point character
private boolean showCurrencySymbol = false;
private String currencySymbol = "$";
private int currencySymbolPosition = CUR_POS_LEFT_OUTSIDE;
private int negativeFormat = NEG_LEFT_DASH;
private boolean isNegativeRed = false; // wrap the output in html that will display red?
private int decimalPrecision = 0;
private boolean useFixedPrecision = false;
private boolean truncate = false; // truncate to decimalPrecision rather than rounding?
private boolean isPercentage = false; // should the result be displayed as a percentage?
private NumberFormat()
{
}
/**
* returns the default instance of NumberFormat
* @return
*/
public static NumberFormat getInstance ()
{
NumberFormat nf = new NumberFormat();
return nf;
}
/**
* Returns a currency instance of number format
* @return
*/
public static NumberFormat getCurrencyInstance ()
{
return getCurrencyInstance("$", true);
}
/**
* Returns a currency instance of number format that uses curSymbol as the currency symbol
* @param curSymbol
* @return
*/
public static NumberFormat getCurrencyInstance (String curSymbol)
{
return getCurrencyInstance(curSymbol, true);
}
/**
* Returns a currency instance of number format that uses curSymbol as the currency symbol
* and either commas or periods as the thousands separator.
* @param curSymbol Currency Symbol
* @param useCommas true, uses commas as the thousands separator, false uses periods
* @return
*/
public static NumberFormat getCurrencyInstance (String curSymbol, boolean useCommas)
{
NumberFormat nf = new NumberFormat();
nf.isCurrency(true);
nf.setCurrencySymbol(curSymbol);
if (!useCommas) {
nf.setDecimalSeparator(COMMA);
nf.setGroupingSeparator(PERIOD);
}
nf.setFixedPrecision(2);
return nf;
}
/**
* Returns an instance that formats numbers as integers.
* @return
*/
public static NumberFormat getIntegerInstance ()
{
NumberFormat nf = new NumberFormat();
nf.setShowGrouping(false);
nf.setFixedPrecision(0);
return nf;
}
public static NumberFormat getPercentInstance ()
{
NumberFormat nf = new NumberFormat();
nf.isPercentage(true);
nf.setFixedPrecision(2);
nf.setShowGrouping(false);
return nf;
}
public String format (String num)
{
return toFormatted(parse(num));
}
public double parse (String num)
{
return asNumber(num, inputDecimalSeparator);
}
/**
* Static routine that attempts to create a double out of the
* supplied text. This routine is a bit smarter than Double.parseDouble()
* @param num
* @return
*/
public static double parseDouble (String num, String decimalChar)
{
return asNumber(num, decimalChar);
}
public static double parseDouble (String num)
{
return parseDouble(num, PERIOD);
}
public void setInputDecimalSeparator (String val)
{
inputDecimalSeparator = val == null ? PERIOD : val;
}
public void setNegativeFormat (int format)
{
negativeFormat = format;
}
public void setNegativeRed (boolean isRed)
{
isNegativeRed = isRed;
}
public void setShowGrouping (boolean show)
{
showGrouping = show;
}
public void setDecimalSeparator (String separator)
{
decimalSeparator = separator;
}
public void setGroupingSeparator (String separator)
{
groupingSeparator = separator;
}
public void isCurrency (boolean isC)
{
showCurrencySymbol = isC;
}
public void setCurrencySymbol (String symbol)
{
currencySymbol = symbol;
}
public void setCurrencyPosition (int cp)
{
currencySymbolPosition = cp;
}
public void isPercentage (boolean pct)
{
isPercentage = pct;
}
/**
* Sets the number of fixed precision decimal places should be displayed.
* To use arbitrary precision, setFixedPrecision(NumberFormat.ARBITRARY_PRECISION)
* @param places
*/
public void setFixedPrecision (int places)
{
useFixedPrecision = places != ARBITRARY_PRECISION;
this.decimalPrecision = places < 0 ? 0 : places;
}
/**
* Causes the number to be truncated rather than rounded to its fixed precision.
* @param trunc
*/
public void setTruncate (boolean trunc)
{
truncate = trunc;
}
/**
*
* @param preSep raw number as text
* @param PERIOD incoming decimal point
* @param decimalSeparator outgoing decimal point
* @param groupingSeparator thousands separator
* @return
*/
private String addSeparators (String preSep)
{
String nStr = preSep;
int dpos = nStr.indexOf(PERIOD);
String nStrEnd = "";
if (dpos != -1) {
nStrEnd = decimalSeparator + nStr.substring(dpos + 1, nStr.length());
nStr = nStr.substring(0, dpos);
}
int l = nStr.length();
for (int i = l; i > 0; i--) {
nStrEnd = nStr.charAt(i - 1) + nStrEnd;
if (i != 1 && ((l - i + 1) % 3) == 0) nStrEnd = groupingSeparator + nStrEnd;
}
return nStrEnd;
}
protected String toFormatted(double num)
{
String nStr;
if (isPercentage) num = num * 100;
nStr = useFixedPrecision ? toFixed(Math.abs(getRounded(num)), decimalPrecision) : Double.toString(num);
nStr = showGrouping ? addSeparators(nStr) : nStr.replaceAll("\\" + PERIOD, decimalSeparator);
String c0 = "";
String n0 = "";
String c1 = "";
String n1 = "";
String n2 = "";
String c2 = "";
String n3 = "";
String c3 = "";
String negSignL = "" + ((negativeFormat == NEG_PARENTHESIS) ? LEFT_PAREN : DASH);
String negSignR = "" + ((negativeFormat == NEG_PARENTHESIS) ? RIGHT_PAREN : DASH);
if (currencySymbolPosition == CUR_POS_LEFT_OUTSIDE) {
if (num < 0) {
if (negativeFormat == NEG_LEFT_DASH || negativeFormat == NEG_PARENTHESIS)
n1 = negSignL;
if (negativeFormat == NEG_RIGHT_DASH || negativeFormat == NEG_PARENTHESIS)
n2 = negSignR;
}
if (showCurrencySymbol) c0 = currencySymbol;
}
else if (currencySymbolPosition == CUR_POS_LEFT_INSIDE) {
if (num < 0) {
if (negativeFormat == NEG_LEFT_DASH || negativeFormat == NEG_PARENTHESIS)
n0 = negSignL;
if (negativeFormat == NEG_RIGHT_DASH || negativeFormat == NEG_PARENTHESIS)
n3 = negSignR;
}
if (showCurrencySymbol) c1 = currencySymbol;
}
else if (currencySymbolPosition == CUR_POS_RIGHT_INSIDE) {
if (num < 0) {
if (negativeFormat == NEG_LEFT_DASH || negativeFormat == NEG_PARENTHESIS)
n0 = negSignL;
if (negativeFormat == NEG_RIGHT_DASH || negativeFormat == NEG_PARENTHESIS)
n3 = negSignR;
}
if (showCurrencySymbol) c2 = currencySymbol;
}
else if (currencySymbolPosition == CUR_POS_RIGHT_OUTSIDE) {
if (num < 0) {
if (negativeFormat == NEG_LEFT_DASH || negativeFormat == NEG_PARENTHESIS)
n1 = negSignL;
if (negativeFormat == NEG_RIGHT_DASH || negativeFormat == NEG_PARENTHESIS)
n2 = negSignR;
}
if (showCurrencySymbol) c3 = currencySymbol;
}
nStr = c0 + n0 + c1 + n1 + nStr + n2 + c2 + n3 + c3 + (isPercentage ? "%" : "");
if (isNegativeRed && num < 0) {
nStr = "" + nStr + "";
}
return nStr;
}
/**
* javascript only rounds to whole numbers, so we need to shift our decimal right,
* then round, then shift the decimal back left.
* @param val
* @return
*/
private double getRounded (double val)
{
double exp = Math.pow(10, decimalPrecision);
double rounded = val * exp;
if (truncate)
rounded = rounded >= 0 ? Math.floor(rounded) : Math.ceil(rounded);
else
rounded = Math.round(rounded);
return rounded / exp;
}
private static native String toFixed(double val, int places) /*-{
return val.toFixed(places);
}-*/;
private static double asNumber(String val, String inputDecimalValue)
{
String newVal = val;
boolean isPercentage = false;
// remove % if there is one
if (newVal.indexOf('%') != -1) {
newVal = newVal.replaceAll("\\%", "");
isPercentage = true;
}
// convert abbreviations for thousand, million and billion
newVal = newVal.toLowerCase().replaceAll(BILLION, "000000000");
newVal = newVal.replaceAll(MILLION, "000000");
newVal = newVal.replaceAll(THOUSAND, "000");
// remove any characters that are not digit, decimal separator, +, -, (, ), e, or E
String re = "[^\\" + inputDecimalValue + "\\d\\-\\+\\(\\)eE]";
newVal = newVal.replaceAll(re, "");
// ensure that the first decimal separator is a . and remove the rest.
int index = newVal.indexOf(inputDecimalValue);
if (index != -1) {
newVal = newVal.substring(0, index)
+ PERIOD
+ (newVal.substring(index + inputDecimalValue.length()).replaceAll("\\"
+ inputDecimalValue, ""));
}
// convert right dash and paren negatives to left dash negative
if (newVal.charAt(newVal.length() - 1) == DASH) {
newVal = newVal.substring(0, newVal.length() - 1);
newVal = DASH + newVal;
}
else if (newVal.charAt(0) == LEFT_PAREN
&& newVal.charAt(newVal.length() - 1) == RIGHT_PAREN) {
newVal = newVal.substring(1, newVal.length() - 1);
newVal = DASH + newVal;
}
Double parsed;
try {
parsed = new Double(newVal);
if (parsed.isInfinite() || parsed.isNaN()) parsed = new Double(0);
}
catch (NumberFormatException e) {
parsed = new Double(0);
}
return isPercentage ? parsed.doubleValue() / 100 : parsed.doubleValue();
}
}