All Downloads are FREE. Search and download functionalities are using the official Maven repository.

at.spardat.enterprise.fmt.ABcdFmtDefault Maven / Gradle / Ivy

There is a newer version: 6.0.2
Show newest version
/*******************************************************************************
 * Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     s IT Solutions AT Spardat GmbH - initial API and implementation
 *******************************************************************************/

// @(#) $Id: ABcdFmtDefault.java 2582 2008-05-07 14:10:55Z webok $
package at.spardat.enterprise.fmt;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Locale;

import at.spardat.enterprise.util.BigDecimalHelper;
import at.spardat.enterprise.util.NumberUtil;

/**
 * This implementation allows to parse and format internationalized numbers. It's a performance
 * optimized implementation to support formatting and parsing of values coming from ABcds. 

* * The internal encoding is the canonical encoding defined in class ABcd, i.e., decimal * separator is the point, for example 100000.23. */ public class ABcdFmtDefault extends ABcdFmt { // keys are Strings denoting Locales, values are two character strings holding the separators. // the first character is the thousand separator, followed by the decimal separator. private static final HashMap seps_ = new HashMap(); // initialize the separators_ HashMap static { seps_.put ("cs", "\u00A0,"); seps_.put ("cs_CZ", "\u00A0,"); seps_.put ("de", ".,"); seps_.put ("de_AT", ".,"); seps_.put ("en", ",."); seps_.put ("en_GB", ",."); seps_.put ("en_US", ",."); seps_.put ("hr", ".,"); seps_.put ("hr_HR", ".,"); seps_.put ("hu", "\u00A0,"); seps_.put ("hu_HU", "\u00A0,"); seps_.put ("sk", "\u00A0,"); seps_.put ("sk_SK", "\u00A0,"); } // defaults if Locale cannot be found private static final String defaultSeps_ = ",."; // max digits before comma or -1 private int maxBeforeC_; // max digits after comma or -1 private int maxAfterC_; // thousands separation character private char tSep_; // decimal separation character private char dSep_; /** * Constructs an ABcdFmt. * * @see #set */ public ABcdFmtDefault (int maxBeforeC, int maxAfterC, int style, Locale l) { set (maxBeforeC, maxAfterC, style, l); } /** * Allows to set all parameters that determine the formatting process. * * @param maxBeforeC max number of digits before the comma or -1 if unrestricted. Must not be zero. * @param maxAfterC max number of digits after the comma or -1 if unrestricted * @param style may be DEFAULT, MANDATORY, NO_THOUS_SEPS, THOUS_SEPS, NO_NEG or ROUND_FRACTION. * Either NO_THOUS_SEPS or THOUS_SEPS may be specified. * @param l the Locale. Must not be null. * @exception IllegalArgumentException if maxBeforeC is zero */ public void set (int maxBeforeC, int maxAfterC, int style, Locale l) { if (maxBeforeC == 0) throw new IllegalArgumentException(); if (l == null) throw new IllegalArgumentException(); maxBeforeC_ = maxBeforeC; maxAfterC_ = maxAfterC; style_ = style; // compute separation characters from locale String sepChars = getSepsFor(l); tSep_ = sepChars.charAt(0); dSep_ = sepChars.charAt(1); } /** * Sets maximum number of digits after decimal separator. * * @param maxAfterC max number of digits after the comma or -1 if unrestricted */ public void setMaxAfterC (int maxAfterC_) { this.maxAfterC_ = maxAfterC_; } /** * Sets maximum number of digits before the decimal separator. * * @param maxBeforeC max number of digits before the comma or -1 if unrestricted. Must not be zero. */ public void setMaxBeforeC (int maxBeforeC_) { if (maxBeforeC_ == 0) throw new IllegalArgumentException(); this.maxBeforeC_ = maxBeforeC_; } /** * Returns maximum number of digits after decimal separator or -1, if number of * digits after decimal separator is unrestricted. */ public int getMaxAfterC () { return maxAfterC_; } /** * Return maximum number of digits before decimal separator or -1, if number of * digits before decimal separator is unrestricted. */ public int getMaxBeforeC () { return maxBeforeC_; } /** * Sets the style. * * @param style may be DEFAULT, MANDATORY, NO_THOUS_SEPS, THOUS_SEPS or NO_NEG. Either NO_THOUS_SEPS or THOUS_SEPS may be specified. */ public void setStyle (int style_) { this.style_ = style_; } /** * @see at.spardat.enterprise.fmt.IFmt#format(String) */ public String format (String internal) { char tSep = tSep_; if ((style_ & THOUS_SEPS) == 0) tSep = 0; String result = format (internal, tSep, dSep_, maxAfterC_); if (internal.length() > 0 && (style_ & SUPPRESS_ZERO) != 0) { /** * if the value is numeric zero, this converts to an empty string */ try { double d = Double.parseDouble(internal); if (d == 0.0) result = ""; } catch (NumberFormatException x) { result = ""; } } return result; } /** * @see at.spardat.enterprise.fmt.IFmt#isLegalExternalChar(char) */ public boolean isLegalExternalChar (char aChar) { // digits are allowed if (NumberUtil.isDigit(aChar)) return true; // decimal separators are allowed if maxAfterC is not zero if (aChar == dSep_ && maxAfterC_ != 0) return true; // thousand separators are allowed with MONEYs if (aChar == tSep_ && (style_ & THOUS_SEPS) != 0) return true; // minus sign if allowed if style NO_NEG is not set if (aChar == '-' && (style_ & NO_NEG) == 0) return true; // the remaining chars are not allowed return false; } /** * @see at.spardat.enterprise.fmt.IFmt#isLegalInternal(String) */ public boolean isLegalInternal (String v) { if (v == null || v.length() == 0) return true; NumberUtil.Metric m = NumberUtil.getMetric(v); if (m == null) return false; // check the limits if (maxBeforeC_ >= 0 && m.lenVorKomma_-m.lenZerosVK_ > maxBeforeC_) return false; if (maxAfterC_ >= 0 && m.lenNachKomma_ > maxAfterC_) return false; if ((style_ & NO_NEG) != 0 && m.lenSign_ == 1) return false; return true; } /** * @see at.spardat.enterprise.fmt.IFmt#isOneWay() */ public boolean isOneWay () { return false; } // an upper limit for maxLenOfExternal private static final int MAX_LEN = 100; /** * @see at.spardat.enterprise.fmt.IFmt#maxLenOfExternal() */ public int maxLenOfExternal () { if (maxBeforeC_ <= -1 || maxAfterC_ <= -1) return MAX_LEN; // first guess int maxLen = maxBeforeC_ + maxAfterC_; // decimal separator if (maxAfterC_ > 0) maxLen++; // sign if ((style_ & NO_NEG) == 0) maxLen++; // for MONEYs add space for thousands separation chars if ((style_ & THOUS_SEPS) != 0) maxLen += (maxBeforeC_-1)/3; return maxLen; } /** * @see at.spardat.enterprise.fmt.IFmt#parse(String) */ public String parse (String external) throws AParseException { /** * empty string is converted to zero if style SUPPRESS_ZERO is set */ if ((style_ & SUPPRESS_ZERO) != 0) { if (external == null || external.length() == 0) return "0"; } checkMandatory (external); // MANDATORY check on the input string if (external == null || external.length() == 0) return ""; StringBuffer internal = new StringBuffer (32); try { parse (external, internal, maxBeforeC_, maxAfterC_, tSep_, dSep_, NK_ERROR); } catch (AParseException ex) { /** * If style ROUND_FRACTION is set, we give it a second chance and * round the number of digits after the comma */ if ((style_ & ROUND_FRACTION) != 0 && maxAfterC_ != -1) { internal.setLength(0); parse (external, internal, maxBeforeC_, -1, tSep_, dSep_, NK_ERROR); /** * Parsing with relaxed max fraction length succeeded. Here, we extract * the number, round it and do an ordinary parse again. */ BigDecimal val = new BigDecimal (internal.toString()); val = val.setScale(maxAfterC_, BigDecimal.ROUND_HALF_UP); internal.setLength(0); parse (format (BigDecimalHelper.toPlainString(val)), internal, maxBeforeC_, maxAfterC_, tSep_, dSep_, NK_ERROR); } else { throw ex; } } // finally, if style NO_NEG is present, check that the number is not negative. if ((style_ & NO_NEG) != 0) { if (internal.length() > 0 && internal.charAt(0) == '-') { throw new FmtParseException ("ABcdNoNegative"); } } String toReturn = internal.toString(); /** * Mandatory check on the internal string again, because the external may consists of blanks only */ checkMandatory (toReturn); return toReturn; } // returns a sample Bcd format string for error messages private static String sampleString (char tSep, char decSep) { if (tSep != 0) return "100" + tSep + "000" + decSep + "15"; else return "100000" + decSep + "15"; } // if there are excess places after the decimal point, truncate public static final int NK_TRUNC = 2; // if there are more places after the decimal point, throw an exception public static final int NK_ERROR = 3; /** * This method parses an external representation of a ABcd and converts it to the canonic format. * * @param in the input string. May contain leading and trailing zeros. * @param result must be an empty StringBuffer which is going to hold the result. * The computed string in the canonic format, which may be safely assigned to an ABcd, if the precision * attributes maxVK and maxNK are drawn from it. An empty string is returned * if in is empty or just consists of blanks. * @param maxVK maximum number of digits in front of the decimal point. Specify -1, if no * restriction should be imposed. * @param maxNK maximum number of digits after the decimal point or -1, if no restriction * should be applied. * @param tSep A thousands separation character, which is generally ignored in the * part before the comma. May be 0, then in must not contain thousand * separation characters. * @param decSep The decimal separation character expected in the input. * @param excessNKMode either NK_TRUNC or NK_ERROR. Defines how to react if more * than maxNK places after the decimal point are present (after removing * trailing zeros). * @exception FmtParseException if in cannot be converted to the internal format. */ public static void parse (String in, StringBuffer result, int maxVK, int maxNK, char tSep, char decSep, int excessNKMode) { int inLen = in.length(); if (inLen == 0) return; int inIndex = 0; int lenSign = 0; int numVK = 0, numVKLeadingZ = 0; boolean lookForLeadingZ = true; int numNK = 0; char c; // skip leading space while (inIndex < inLen) { c = in.charAt(inIndex); if (c == ' ') inIndex++; else break; } // an optional sign if (inIndex < inLen) { c = in.charAt(inIndex); if (c == '-') { inIndex++; lenSign=1; result.append('-'); } else if (c == '+') { inIndex++; } } // sequence of digits or tSeps before the decimal-point while (inIndex < inLen) { c = in.charAt(inIndex); if (c == tSep) { inIndex++; } else if (NumberUtil.isDigit(c)) { numVK++; if (lookForLeadingZ) { if (c == '0') numVKLeadingZ++; else lookForLeadingZ = false; } inIndex++; result.append(c); } else break; } // optional decimal point if (inIndex < inLen && in.charAt(inIndex) == decSep) { inIndex++; result.append('.'); // sequence of digits after decimal-point while (inIndex < inLen) { c = in.charAt(inIndex); if (NumberUtil.isDigit(c)) { numNK++; result.append(c); inIndex++; } else break; } } // trailing space while (inIndex < inLen) { if (in.charAt(inIndex) == ' ') inIndex++; else break; } // are there characters remaining in the input-string? if (inIndex < inLen) { throw new FmtParseException ("ABcdNotFullyParsed", in.substring(inIndex), sampleString(tSep, decSep)); } // are there too many significant digits before the comma? if (maxVK != -1) { if (numVK - numVKLeadingZ > maxVK) { throw new FmtParseException ("ABcdToManyVKs2", String.valueOf(maxVK)); } } // the case where nothing is left in result if (result.length() == 0) return; // the case where no digit is left in result; but there must be some other char, // since we have come to this point. Either a decimal point or a sign. if (numVK + numNK == 0) { throw new FmtParseException ("ABcdNotValid", sampleString(tSep, decSep)); } // are there too many digits after the comma? if (maxNK != -1 && numNK > maxNK) { // first, remove trailing zeros while (result.length() > 0 && result.charAt(result.length()-1) == '0') { result.setLength(result.length()-1); numNK--; } // if still to long, throw an exception if NK_ERROR if (numNK > maxNK && excessNKMode == NK_ERROR) { throw new FmtParseException ("ABcdToManyNKs", String.valueOf(maxNK)); } // truncate until we have no more than maxNK digits while (numNK > maxNK) { result.setLength(result.length()-1); numNK--; } } } /** * Takes a canonic string as input and returns a formatted string. * * @param canonic canonic format string of a ABcd * @param tSep if not equal to zero, this character is inserted * at thousand separation position * @param decSep this character is used as decimal point character * in the result * @param numNachKomma if greater than or equal zero, the result will * have numNachKomma places after the decimal point. * If it is -1, the result consists of as many digits * after the comma as necessary. * @return the formatted string. It does not have leading zeros. */ public static String format (String canonic, char tSep, char decSep, int numNachKomma) { if (canonic == null) return ""; int cLen = canonic.length(); int cIndex = 0; if (cLen == 0) return ""; int cntSign = 0; int cntVorkomma = 0; int cntNachkomma = 0; int indexDecPoint = -1; StringBuffer result = new StringBuffer (canonic.length() * 2); // optionales Vorzeichen char c = canonic.charAt(cIndex); if (c == '-') { cntSign = 1; cIndex++; result.append('-'); } // Vorkommateil boolean lookForLeadingZeros = true; while (cIndex < cLen) { c = canonic.charAt(cIndex); if (NumberUtil.isDigit(c)) { if (lookForLeadingZeros) { // falls lookForLeadingZeros true, werden zeros ignoriert if (c != '0') { lookForLeadingZeros = false; result.append(c); cntVorkomma++; } } else { result.append(c); cntVorkomma++; } } else break; cIndex++; } // mindestens eine Vorkommastelle? if (cntVorkomma == 0) { result.append('0'); cntVorkomma++; } // Dezimalpunkt if (cIndex < cLen && canonic.charAt(cIndex) == '.') { result.append (decSep); cIndex++; indexDecPoint = result.length()-1; } // Nachkommateil while (cIndex < cLen) { c = canonic.charAt(cIndex); result.append(c); cntNachkomma++; cIndex++; } // auf numNachKomma richtigstellen if (numNachKomma != -1) { // sicherstellen, dass ein Komma da ist if (indexDecPoint == -1) { indexDecPoint = result.length(); result.append(decSep); } while (cntNachkomma < numNachKomma) { result.append('0'); cntNachkomma++; } while (cntNachkomma > numNachKomma) { result.setLength (result.length()-1); cntNachkomma--; } } else { // wenn ein Nachkommateil da ist if (indexDecPoint != -1) { // trailing zeros entfernen while (result.charAt(result.length()-1) == '0') { result.setLength (result.length()-1); cntNachkomma--; } } } // wenn ein Decimaltrennzeichen an letzter Stelle ?brig bleibt, dieses weg if (result.length()-1 == indexDecPoint) { result.setLength(result.length()-1); indexDecPoint = -1; } // Vorzeichen weg, wenn keine Ziffern ungleich 0 mehr vorhanden sind boolean nonZeroFound = false; if (cntSign == 1) { for (int i=result.length()-1; i>=0; i--) { c = result.charAt(i); if (NumberUtil.isDigit(c) && c != '0') { nonZeroFound = true; break; } } if (!nonZeroFound) { result.deleteCharAt(0); if (indexDecPoint != -1) indexDecPoint--; } } // Tausendertrennzeichen einfuegen if (tSep != 0 && cntVorkomma > 3) { int insertionIndex; if (indexDecPoint != -1) insertionIndex = indexDecPoint - 3; else insertionIndex = result.length() - 3; while (insertionIndex > 0 && NumberUtil.isDigit(result.charAt(insertionIndex-1))) { result.insert(insertionIndex, tSep); insertionIndex -= 3; } } return result.toString(); } /** * Returns two digit string containing the separation characters for a particular Locale. * * @param l the Locale * @return two character String whose first char is the thousands and whose second is the decimal sep char. */ private String getSepsFor (Locale l) { String locStr = l.toString(); String seps = (String) seps_.get(locStr); if (seps != null) return seps; int iUnder = locStr.lastIndexOf('_'); if (iUnder != -1) { locStr = locStr.substring(0, iUnder); seps = (String) seps_.get(locStr); if (seps != null) return seps; } iUnder = locStr.lastIndexOf('_'); if (iUnder != -1) { locStr = locStr.substring(0, iUnder); seps = (String) seps_.get(locStr); if (seps != null) return seps; } return defaultSeps_; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy