Package Framework

Source Code of Framework.NullAwareNumberFormat

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.