Package Framework

Source Code of Framework.NullAwareNumberFormat

/*
Copyright (c) 2003-2009 ITerative Consulting Pty Ltd. All Rights Reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:

o Redistributions of source code must retain the above copyright notice, this list of conditions and
the following disclaimer.
 
o Redistributions in binary form must reproduce the above copyright notice, this list of conditions
and the following disclaimer in the documentation and/or other materials provided with the distribution.
   
o This jcTOOL Helper Class software, whether in binary or source form may not be used within,
or to derive, any other product without the specific prior written permission of the copyright holder

 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


*/
package Framework;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.AttributedCharacterIterator;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;

/**
* This class allows the formatting of numbers in a manner similar to the standard
* java number format. However, this class allows the parsing of numbers based on
* numericData as well as numbers, as well as supporting 4 different masks -- a
* positive mask, a negative mask, a zero mask and a null mask. If the negative mask
* isn't specified, the positive mask will be used. If the zero mask isn't specified,
* the positive mask will be used.
* @author Tim
*/
@SuppressWarnings("serial")
public class NullAwareNumberFormat extends NumberFormat {

  /**
   * The default value to display if the value is null and no template has been set
   * for the null template
   */
  protected static final String SYSTEM_DEFAULT_NULL_STRING = "N/A";
  private static final int cNOT_SET = -1;
    private String originalPattern;

    /**
     * Unfortunately, even though java supports a formatter with a positive format and a negative
     * format, this doesn't always work. For example, it assumes that the number of characters in
     * the mask after the decimal point are the same for positive and negative masks. Thus, a format
     * like<pre>
     * $#,##0.00;($#,##0)
     * </pre>
     * will confuse it. Thus, we need to store separate formatters for positive and negative values.
     */
  protected DecimalFormat posFormat;
  /**
   * The original positive format string
   */
  private String posFormatString;
  /**
   * The negative format mask, or null if none is specified.
   */
  protected DecimalFormat negFormat = null;
  /**
   * The original negative format string
   */
  private String negFormatString;
 
  /**
   * The zero format. This is stored as a string, since Java does something funny with
   * numeric masks. For example, if the zero mask is "ZERO", it will encode this as
   * ZERO0 when called with a value of 0 (ie it assumes is needs at least one digit)
   */
  protected String zeroFormat;
  /**
   * An iterator over the attributed characters in the zero format
   */
  protected AttributedCharacterIterator zeroFormatIterator;
  /**
   * The number of decimal places maximum in the zero format
   */
  protected int zeroFormatMaximumFractionalDigits = cNOT_SET;
 
  /**
   * The null format string
   */
  protected String nullFormat = SYSTEM_DEFAULT_NULL_STRING;
 
  /**
   * If the template allows a blank value which represents 0.  Eg: "#"
   *
   * CraigM:23/07/2008.
   */
  private boolean blankForZeroTemplate = false;
 
  /**
   * A template part to select. This is used to extract a particular part of the
   * passed format string so an appropriate positive, negative, zero or null format
   * can be created.
   */
    private enum TemplatePart {POSITIVE, NEGATIVE, ZERO, NULL};
   
    public NullAwareNumberFormat() {
      posFormat = new DecimalFormat();
  }
   
    public NullAwareNumberFormat(String pattern) {
      super();
      setTemplate(pattern);
    }
   
    public NullAwareNumberFormat(TextData pattern) {
      super();
      setTemplate(pattern);
    }

    public void setTemplate(TextData pattern) {
      setTemplate( pattern.toString() );
    }
   
    /**
     * This method attempts to turn parts of patterns that were valid in forte and
     * are not valid in java into their valid java equivalents. For example, 000# is
     * not valid in java, but is valid in Forte (and is equivalent to 000)
     * @param patternPart
     * @return
     */
    private String fixTemplate(String patternPart) {
      if (patternPart == null) {
        return null;
      }
      // Test for zeros followed by hashes (only)
      else if (patternPart.matches("^0+#+$")) {
        return patternPart.substring(0, patternPart.indexOf('#'));
      }
      else {
        return patternPart;
      }
    }
   
    public void setTemplate(String pattern) {
      if (pattern.equalsIgnoreCase("INTEGER")) {
        this.originalPattern = pattern = "#,##0;-#,##0";
      }
      else {
        this.originalPattern = pattern;
      }

      this.posFormatString = getFormatPatternPart(pattern, TemplatePart.POSITIVE);
      this.posFormat = new DecimalFormat(this.posFormatString);
     
      this.negFormatString = getFormatPatternPart(pattern, TemplatePart.NEGATIVE);
      if (this.negFormatString != null && this.negFormatString.length() != 0) {//PM:7 oct. 2008:performance
        // We need to give the negative format a dummy positive mask, otherwise it will return
        // it's parsed values as positive.
        this.negFormat = new DecimalFormat("'D'" + this.negFormatString + ";" + this.negFormatString);
      }
      this.zeroFormat = getFormatPatternPart(pattern, TemplatePart.ZERO);
      this.nullFormat = getFormatPatternPart(pattern, TemplatePart.NULL);

    // CraigM:23/07/2008 - If they put '#' as the positive part of the template and there's no zero mask,
      // use blank as the zero mask.
    // TF:05/02/2009:DET-74:Changed this to handle #;-# as well as other masks that are equivalent for 0, such as
    // #,### and ?,???. Note that without re-writing the standard decimal formatter, it is too hard to handle
    // unusual masks such as USD#,### which should give USD when passed 0, instead of USD0. This should not be an
    // issue, and if it is it can be corrected using a specific 0 mask.
    this.blankForZeroTemplate = zeroFormat == null && this.posFormatString.matches("^([#?],?)+$");

      // We need to get a character iterator for the zero part so we can use it if requested
      if (this.zeroFormat != null && zeroFormat.length()!=0) {//PM:7 oct. 2008:performance
        DecimalFormat temp = new DecimalFormat(this.zeroFormat);
        temp.setMultiplier(1);
        this.zeroFormatIterator = temp.formatToCharacterIterator(0);
        this.zeroFormatMaximumFractionalDigits = temp.getMaximumFractionDigits();
      }
      else {
        this.zeroFormatIterator = null;
        this.zeroFormatMaximumFractionalDigits = cNOT_SET;
      }
     
      // We have to set the multiplier of the posNegFormat to 1, even for percentage fields,
      // so that the behaviour matches Forte
      this.posFormat.setMultiplier(1);
      if (negFormat != null) {
        this.negFormat.setMultiplier(1);
      }
    }
   
    public TextData getTemplate() {
      return new TextData(originalPattern);
    }
   
    private String getFormatPatternPart(String wholeFormat, TemplatePart part){
      String[] parts = wholeFormat.split(";");
      if (part == TemplatePart.POSITIVE) {
        return fixTemplate(parts[0]);
      }
      else if (part == TemplatePart.NEGATIVE) {
        if (parts.length > 1) {
          return fixTemplate(parts[1]);
        }
        else {
          // Special case: if the pattern has a positive and a negative mask is just a
            // semi colon, it has a blank negative mask. Split returns the wrong result
          // here if there is no null mask or the null mask is also zero
          if (wholeFormat.matches(".*;;")) {
            return "";
          }
          else {
            // No negative statement, return null
            return null;
          }
        }
      }
      else if (part == TemplatePart.ZERO) {
        if (parts.length >=3) {
          return fixTemplate(parts[2]);
        }
        else {
          // Special case: if the pattern has a positive and a negative mask and then an
          // extra semi colon, it has a blank zero mask. Split returns the wrong result
          // here if there is no null mask or the null mask is also zero
          if (wholeFormat.matches(".*;.*;;")) {
            return "";
          }
          else {
            // No zero statement, return null
            return null;
          }
        }
      }
      else {
        // Want the null pattern
        if (parts.length >= 4) {
          return fixTemplate(parts[3]);
        }
        else {
          // Special case: if the pattern has a positive and a negative mask and a zero mask
          // and an extra semi colon, it has a blank null mask. Split returns the wrong result
          if (wholeFormat.matches(".*;.*;.*;;")) {
            return "";
          }
          else {
            return SYSTEM_DEFAULT_NULL_STRING;
          }
        }
      }
    }
   
  @Override
  public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
    StringBuffer _Result;
   
    if (number == 0.0 && zeroFormat != null) {
      _Result = toAppendTo.append(zeroFormat);
      // If the pattern wants a ".", but we don't have one.  Then put one in.  CraigM: 07/04/2008
      // TF:05/02/2009:DET-74:Fixed this to be specific to the format. Otherwise, strings like "$#,##0.00;($#,###)" will return the wrong result
      if (this.zeroFormat.indexOf('.') != -1 && _Result.indexOf(DoubleData.getDecimalSeparatorStr()) == -1) {
        _Result.append(DoubleData.getDecimalSeparator());
      }
    }
    else if (number < 0.0 && negFormat != null){
      _Result = negFormat.format(number, toAppendTo, pos);
      // If the pattern wants a ".", but we don't have one.  Then put one in.  CraigM: 07/04/2008
      // TF:05/02/2009:DET-74:Fixed this to be specific to the format. Otherwise, strings like "$#,##0.00;($#,###)" will return the wrong result
      if (this.negFormatString.indexOf('.') != -1 && _Result.indexOf(DoubleData.getDecimalSeparatorStr()) == -1) {
        _Result.append(DoubleData.getDecimalSeparator());
      }
    }
    else {
      _Result = posFormat.format(number, toAppendTo, pos);
      // If the pattern wants a ".", but we don't have one.  Then put one in.  CraigM: 07/04/2008
      // TF:05/02/2009:DET-74:Fixed this to be specific to the format. Otherwise, strings like "$#,##0.00;($#,###)" will return the wrong result
      if (this.posFormatString.indexOf('.') != -1 && _Result.indexOf(DoubleData.getDecimalSeparatorStr()) == -1) {
        _Result.append(DoubleData.getDecimalSeparator());
      }
    }

    return _Result;
  }

  @Override
  public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
    StringBuffer _Result;
   
    if (number == 0 && zeroFormat != null) {
      _Result = toAppendTo.append(zeroFormat);
      // If the pattern wants a ".", but we don't have one.  Then put one in.  CraigM: 07/04/2008
      // TF:05/02/2009:DET-74:Fixed this to be specific to the format. Otherwise, strings like "$#,##0.00;($#,###)" will return the wrong result
      if (this.zeroFormat.indexOf('.') != -1 && _Result.indexOf(DoubleData.getDecimalSeparatorStr()) == -1) {
        _Result.append(DoubleData.getDecimalSeparator());
      }
     
    }
    // CraigM:23/07/2008 - If the number is 0, and the pattern is '#' then we just want a blank string
    else if (number == 0 && this.isBlankForZeroTemplate()) {
      _Result = new StringBuffer();
    }
    else if (number < 0 && negFormat != null){
      _Result = negFormat.format(number, toAppendTo, pos);
      // If the pattern wants a ".", but we don't have one.  Then put one in.  CraigM: 07/04/2008
      // TF:05/02/2009:DET-74:Fixed this to be specific to the format. Otherwise, strings like "$#,##0.00;($#,###)" will return the wrong result
      if (this.negFormatString.indexOf('.') != -1 && _Result.indexOf(DoubleData.getDecimalSeparatorStr()) == -1) {
        _Result.append(DoubleData.getDecimalSeparator());
      }
     
    }
    else {
      _Result = posFormat.format(number, toAppendTo, pos);
      // If the pattern wants a ".", but we don't have one.  Then put one in.  CraigM: 07/04/2008
      // TF:05/02/2009:DET-74:Fixed this to be specific to the format. Otherwise, strings like "$#,##0.00;($#,###)" will return the wrong result
      if (this.posFormatString.indexOf('.') != -1 && _Result.indexOf(DoubleData.getDecimalSeparatorStr()) == -1) {
        _Result.append(DoubleData.getDecimalSeparator());
      }
    }
   
    return _Result;
  }

  /**
   * @return true if they have a template that allows blank as 0.  Ie: "#".
   *
   * CraigM:23/07/2008.
   */
  public boolean isBlankForZeroTemplate() {
    return this.blankForZeroTemplate;
  }

  public int getMaximumFractionDigits(Double value) {
    if (value == null) {
      // Null values don't have a maximum fractional digits
      return 0;
    }
    else {
      double dValue = value.doubleValue();
      return getMaximumFractionDigits(dValue);
    }
  }
 
  /**
   * Get the maximum number of fractional digits for a particular number
   * @param value
   * @return
   */
  public int getMaximumFractionDigits(double value) {
    if (value == 0 && this.zeroFormatMaximumFractionalDigits != cNOT_SET) {
      return this.zeroFormatMaximumFractionalDigits;
    }
    else if (value < 0.0 && negFormat != null){
      return negFormat.getMaximumFractionDigits();
    }
    else {
      return posFormat.getMaximumFractionDigits();
    }
  }
 
  @Override
  public StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) {
    if (number == null) {
      return toAppendTo.append(nullFormat);
    }
        if (number instanceof Long ||
          number instanceof Integer ||
          number instanceof Short ||
          number instanceof Byte ||
      (number instanceof BigInteger && ((BigInteger) number).bitLength() < 64)) {
         
      return format(((Number) number).longValue(), toAppendTo, pos);
    }
    else if (number instanceof BigDecimal) {
      return format(((BigDecimal) number).doubleValue(), toAppendTo, pos);//PM:31/07/2008:to prevent infinite recursion
    }
    else if (number instanceof BigInteger) {
      return format(((BigInteger) number).longValue(), toAppendTo, pos);//PM:31/07/2008:to prevent infinite recursion
    }
    else if (number instanceof Number) {
      return format(((Number) number).doubleValue(), toAppendTo, pos);
    }
    else if (number instanceof NumericData) {
      if (((NumericData)number).isNull()) {
        return format(null, toAppendTo, pos);
      }
      else if (number instanceof IntegerData) {
        return format(((IntegerData)number).intValue(), toAppendTo, pos);
      }
      else if (number instanceof DecimalData) {
        return format(((DecimalData)number).doubleValue(), toAppendTo, pos);
      }
      else if (number instanceof DoubleData) {
        return format(((DoubleData)number).doubleValue(), toAppendTo, pos);
      }
    }
    throw new IllegalArgumentException("Cannot format given Object as a Number");
  }

  @Override
  public Number parse(String source, ParsePosition parsePosition) {
    // We want to match the most specific mask. For example, if we have a positive mask of #,##0.00 and a
    // zero mask of 0.0 then we want 0.0 to match the zero mask, but 0.09 to match the positive mask
    int nullMatchEndIndex = -1;
    int zeroMatchEndIndex = -1;
    int negMatchEndIndex = -1;
    int posMatchEndIndex = -1;
    int startIndex = parsePosition.getIndex();
    Number negResult = null;
    Number posResult = null;
   
    if (source.regionMatches(parsePosition.getIndex(), this.nullFormat, 0, this.nullFormat.length())) {
      nullMatchEndIndex = startIndex + this.nullFormat.length();
    }
    if (zeroFormat != null && source.regionMatches(parsePosition.getIndex(), this.zeroFormat, 0, this.zeroFormat.length())) {
      zeroMatchEndIndex = startIndex + this.zeroFormat.length();
    }
    if (negFormat != null) {
      // See if we match a negative number first.
      negResult = negFormat.parse(source, parsePosition);
      if (negResult != null) {
        negMatchEndIndex = parsePosition.getIndex();
      }
      else {
        // Probably a positive number. Reset the error position first.
        parsePosition.setErrorIndex(-1);
      }
      parsePosition.setIndex(startIndex);
    }
    posResult = posFormat.parse(source, parsePosition);
    if (posResult != null) {
      posMatchEndIndex = parsePosition.getIndex();
    }
    else {
      // Probably a positive number. Reset the error position first.
      parsePosition.setErrorIndex(-1);
    }
   
    if (posMatchEndIndex > -1 && posMatchEndIndex >= negMatchEndIndex && posMatchEndIndex >= zeroMatchEndIndex && posMatchEndIndex >= nullMatchEndIndex) {
      // Use the positive mask
      parsePosition.setIndex(posMatchEndIndex);
      return posResult;
    }
    else if (negMatchEndIndex > -1 && negMatchEndIndex >= zeroMatchEndIndex && negMatchEndIndex >= nullMatchEndIndex) {
      // Use the negative mask
      parsePosition.setIndex(negMatchEndIndex);
      return negResult;
    }
    else if (zeroMatchEndIndex > -1 && zeroMatchEndIndex >= nullMatchEndIndex) {
      parsePosition.setIndex(zeroMatchEndIndex);
      return Long.valueOf(0);
    }
    else if (nullMatchEndIndex > -1) {
      parsePosition.setIndex(nullMatchEndIndex);
      return null;
    }
    else {
      // CraigM:23/07/2008 - Cater for a '#' mask and blank source
      if (source.length() == 0 && this.isBlankForZeroTemplate()) {
        return Long.valueOf(0);
      }

      // Nothing matches, must return an error.
      parsePosition.setErrorIndex(startIndex);
      return null;
    }
  }
 
  /**
   * Get the passed number as an AttributedCharacterIterator.
   */
  @Override
  public AttributedCharacterIterator formatToCharacterIterator(Object number) {
    if (number == null) {
      return super.formatToCharacterIterator(number);
    }
    if (zeroFormat == null && negFormat == null && number instanceof Number) {
      return posFormat.formatToCharacterIterator(number);
    }
   
    if (number instanceof Number) {
          if (zeroFormat != null && ((Number) number).doubleValue() == 0){
            return zeroFormatIterator;
          }
          else if (((Number) number).doubleValue() < 0 && negFormat != null) {
            return negFormat.formatToCharacterIterator(number);
          }
    }
    else if (number instanceof NumericData) {
      if (((NumericData)number).isNull()) {
        return super.formatToCharacterIterator(null);
      }
      else if (zeroFormat != null && ((NumericData)number).doubleValue() == 0) {
            return zeroFormatIterator;
          }
          else if (((NumericData) number).doubleValue() < 0 && negFormat != null) {
            return negFormat.formatToCharacterIterator(((NumericData)number).doubleValue());
          }
          else {
            return posFormat.formatToCharacterIterator(((NumericData)number).doubleValue());
          }
    }
    return posFormat.formatToCharacterIterator(number);
  }
 
  public static void main(String[] args) {
    NullAwareNumberFormat fnf = new NullAwareNumberFormat("$#,##0.00 CR;DB $#,##0.00;ZERO;nil");
    System.out.println(fnf.format(1234.57));
    System.out.println(fnf.format(-1234.57));
    System.out.println(fnf.format(0));
    System.out.println(fnf.format(null));
   
    try {
      System.out.println(fnf.parse("nil"));
      System.out.println(fnf.parse("ZERO"));
      System.out.println(fnf.parse("DB $1,234.56"));
      System.out.println(fnf.parse("$1,234.56 CR"));
    }
    catch (ParseException e) {
      e.printStackTrace();
    }
  }
}
TOP

Related Classes of Framework.NullAwareNumberFormat

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.