/* * Rapid Beans Framework: NumberFormat.java * * Copyright (C) 2009 Martin Bluemel * * Creation Date: 01/01/2000 * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; * either version 3 of the License, or (at your option) any later version. * 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 Lesser General Public License for more details. * You should have received a copies of the GNU Lesser General Public License and the * GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>. */ package org.rapidbeans.core.util; import java.math.BigDecimal; import org.rapidbeans.core.common.RapidBeansLocale; import org.rapidbeans.core.exception.UtilException; /** * Provides sophisticated localized formatting of numbers. * * @author Martin Bl�mel */ public class NumberFormat { // Format pattern string --------------------------- /** * A format pattern consists in following characters: */ public static final char PATTERN_FIX_DIGIT_CHAR = '0'; public static final char PATTERN_NON_NULL_DIGIT_CHAR = '#'; public static final char PATTERN_SEPARATOR_CHAR = ','; public static final char PATTERN_DECIMAL_CHAR = '.'; public static final char PATTERN_EXPONENT_CHAR = 'E'; public static final int CHECK_MODE_DECIMAL = 0; public static final int CHECK_MODE_FLOATING_POINT = 1; private static final int STATE_START = 0; private static final int STATE_AFTER_SIGN = 1; private static final int STATE_BEFORE_DECIMAL = 2; private static final int STATE_AFTER_DECIMAL_ONE = 3; private static final int STATE_AFTER_DECIMAL_N = 4; private static final int STATE_AFTER_DECIMAL_WSPACE = 5; /** * normalize is supposed to return a normalized string representation of a * decimal number with the following Syntax: checkMode:<br/> * * 1) checkMode = decimal:<br/> * <code><wspace>* [<sign char><wspace>*] <digit> {<digit>|<separator>}* <decimal> <digit>* * <wspace>* checkMode</code><br/> * * 2) maybe later extend for floating point strings<br/> * - eliminate leading and trailing whitespace characters<br/> * - eliminate positive sign '+'<br/> * - eliminate whitespace characters after sign<br/> * - eliminate separator characters<br/> * - eliminate leading zeros up to the last position before decimal<br/> * - eliminate trailing zeros up to the first position after decimal<br/> * * The input separator and the decimal char can be given language dependent. * The output is always language independent. */ public static String normalize(String sIn) throws UtilException { return normalize(sIn, CHECK_MODE_DECIMAL, PATTERN_SEPARATOR_CHAR, PATTERN_DECIMAL_CHAR); } public static String normalize(String sIn, int checkMode) throws UtilException { return normalize(sIn, checkMode, PATTERN_SEPARATOR_CHAR, PATTERN_DECIMAL_CHAR); } public static String normalize(String sIn, int checkMode, char separatorChar, char decimalChar) throws UtilException { int len = sIn.length(); char c; StringBuffer buf = new StringBuffer(); int i = 0; int state = STATE_START; if (checkMode == CHECK_MODE_DECIMAL) { for (i = 0; i < len; i++) { c = sIn.charAt(i); switch (state) { // --------------------------------------------------------------------- case STATE_START: // --------------------------------------------------------------------- // recognize the begin of the pre decimal part if (c >= '1' && c <= '9') { buf.append(c); state = STATE_BEFORE_DECIMAL; } // eliminate leading zeros except on the last position // before decimal else if (c == '0') { if (sIn.charAt(i + 1) == decimalChar) { buf.append(c); state = STATE_BEFORE_DECIMAL; } } // accept a leading decimal char but prepend a zero else if (c == decimalChar) { buf.append('0'); state = STATE_AFTER_DECIMAL_ONE; } // recognize a positive sign and filter it else if (c == '+') { state = STATE_AFTER_SIGN; } // recognize a negative sign else if (c == '-') { buf.append(c); state = STATE_AFTER_SIGN; } // eliminate leading whitespaces else if (c == ' ' || c == '\t' || c == '\n') { ; // filter leading whitespace } else { throw new UtilException("wrong char '" + c + "'"); } break; // --------------------------------------------------------------------- case STATE_AFTER_SIGN: // --------------------------------------------------------------------- // recognize the begin of the pre decimal part if (c >= '1' && c <= '9') { buf.append(c); state = STATE_BEFORE_DECIMAL; } // eliminate leading zeros except on the last position // before decimal else if (c == '0') { if (sIn.charAt(i + 1) == decimalChar) { buf.append(c); state = STATE_BEFORE_DECIMAL; } } // accept a leading decimal char but prepend a zero else if (c == decimalChar) { buf.append('0'); state = STATE_AFTER_DECIMAL_ONE; } // eliminate leading whitespaces else if (c == ' ' || c == '\t' || c == '\n') { ; // filter leading whitespace } else { throw new UtilException("wrong char '" + c + "'"); } break; // --------------------------------------------------------------------- case STATE_BEFORE_DECIMAL: // --------------------------------------------------------------------- if (c >= '0' && c <= '9') { buf.append(c); } else if (c == separatorChar) { ; // filter separators } else if (c == decimalChar) { buf.append(PATTERN_DECIMAL_CHAR); state = STATE_AFTER_DECIMAL_ONE; } else { throw new UtilException("wrong char '" + c + "'"); } break; // --------------------------------------------------------------------- case STATE_AFTER_DECIMAL_ONE: // --------------------------------------------------------------------- // only accept a digit here if (c >= '0' && c <= '9') { buf.append(c); state = STATE_AFTER_DECIMAL_N; } else { throw new UtilException("wrong char '" + c + "'"); } break; // --------------------------------------------------------------------- case STATE_AFTER_DECIMAL_N: // --------------------------------------------------------------------- if (c >= '0' && c <= '9') { buf.append(c); } // recognize the first trailing whitespace (filter it) else if (c == ' ' || c == '\t' || c == '\n') { state = STATE_AFTER_DECIMAL_WSPACE; } else { throw new UtilException("wrong char '" + c + "'"); } break; // --------------------------------------------------------------------- case STATE_AFTER_DECIMAL_WSPACE: // --------------------------------------------------------------------- // filter trailing whitespaces if (c == ' ' || c == '\t' || c == '\n') { ; } else { throw new UtilException("wrong char '" + c + "'"); } break; } } } String ret = buf.toString(); return ret; } public static String format(double d, String numberFormatString, char separatorChar, char decimalChar) { return format(new Double(d).toString(), numberFormatString, separatorChar, decimalChar); } /** * Formats a BigDecimal number according to the given number format string. * * @param d * the number a BigDecimal * @param locale * the locale * @param numberFormatString * the number format string * * @return the formatted number string */ public static String format(final BigDecimal d, final RapidBeansLocale locale, final String numberFormatString) { return format(d.toString(), numberFormatString, locale.getStringGui("number.format.char.separator").charAt(0), locale.getStringGui("number.format.char.decimal").charAt(0)); } /** * Formats an integer number according to the given number format string. * * @param i * the number * @param locale * the locale * @param numberFormatString * the number format string * * @return the formatted number string */ public static String format(final int i, final RapidBeansLocale locale, final String numberFormatString) { return format(Integer.toString(i), numberFormatString, locale.getStringGui("number.format.char.separator") .charAt(0), locale.getStringGui("number.format.char.decimal").charAt(0)); } /** * Formats a long integer number according to the given number format * string. * * @param l * the long integer number * @param locale * the locale * @param numberFormatString * the number format string * * @return the formatted number string */ public static String format(final long l, final RapidBeansLocale locale, final String numberFormatString) { return format(Long.toString(l), numberFormatString, locale.getStringGui("number.format.char.separator").charAt(0), locale.getStringGui("number.format.char.decimal").charAt(0)); } /** * Formats a number string according to the given number format string. * * @param sIn * the string containing the number * * @param numberFormatString * @param separatorChar * @param decimalChar * * @return the formatted number string */ public static String format(String sIn, String numberFormatString, char separatorChar, char decimalChar) { if (numberFormatString == null || numberFormatString.equals("")) { throw new UtilException("Illegal argument numberFormatString is empty"); } // create a buffer for the resulting String an initially fill it // with the format string StringBuffer buf = new StringBuffer(numberFormatString); // position one before the decimal sign or at the last digit int lenIn = sIn.length(); int posInStart, posFormatStart, posIn, posFormat; int posDecimalCharIn = sIn.indexOf(PATTERN_DECIMAL_CHAR); if (posDecimalCharIn == -1) { posInStart = lenIn - 1; } else { posInStart = posDecimalCharIn - 1; } final int lenBuf = numberFormatString.length(); boolean decimalFoundFormat = false; int posDecimalCharFormat = numberFormatString.indexOf(PATTERN_DECIMAL_CHAR); if (posDecimalCharFormat == -1) { posFormatStart = lenBuf - 1; } else { posFormatStart = posDecimalCharFormat - 1; decimalFoundFormat = true; } if (decimalFoundFormat) { buf.setCharAt(posDecimalCharFormat, decimalChar); } // fill the buffer before the decimal sign char c, cf; posIn = posInStart; posFormat = posFormatStart; while (posFormat >= 0) { if (posIn >= 0) { c = sIn.charAt(posIn); } else { c = ' '; } cf = numberFormatString.charAt(posFormat); if (cf == PATTERN_FIX_DIGIT_CHAR) { if (c == ' ') { c = '0'; } buf.setCharAt(posFormat, c); posIn--; } else if (cf == PATTERN_NON_NULL_DIGIT_CHAR) { buf.setCharAt(posFormat, c); posIn--; } else if (cf == PATTERN_SEPARATOR_CHAR) { buf.setCharAt(posFormat, separatorChar); } else { throw new UtilException("unexpected char'" + cf + "' at pos " + posFormat); } posFormat--; } // fill the buffer after the decimal sign if (decimalFoundFormat) { posIn = posInStart + 2; posFormat = posFormatStart + 2; while (posFormat < lenBuf) { if (posIn < lenIn) { c = sIn.charAt(posIn); } else { c = ' '; } cf = numberFormatString.charAt(posFormat); if (cf == PATTERN_FIX_DIGIT_CHAR) { if (c == ' ') { c = '0'; } buf.setCharAt(posFormat, c); posIn++; } else if (cf == PATTERN_NON_NULL_DIGIT_CHAR) { buf.setCharAt(posFormat, c); posIn++; } else if (cf == PATTERN_SEPARATOR_CHAR) { buf.setCharAt(posFormat, separatorChar); } else { throw new UtilException("unexpected char'" + cf + "' at pos " + posFormat); } posFormat++; } } // round String ret = buf.toString(); if (posIn < lenIn) { c = sIn.charAt(posIn); if (c >= '5' && c <= '9') { // round up char[] ca = ret.toCharArray(); boolean round = true; for (int pos = ca.length - 1; round && pos >= 0; pos--) { c = ca[pos]; if (c == '9') { ca[pos] = '0'; round = true; } else if (c >= '0' && c <= '8') { ca[pos] = (char) (ca[pos] + 1); round = false; } } ret = new String(ca); } } return ret; } }