Package org.apache.myfaces.trinidad.convert

Source Code of org.apache.myfaces.trinidad.convert.NumberConverter

/*
*  Licensed to the Apache Software Foundation (ASF) under one
*  or more contributor license agreements.  See the NOTICE file
*  distributed with this work for additional information
*  regarding copyright ownership.  The ASF licenses this file
*  to you 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.apache.myfaces.trinidad.convert;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParsePosition;

import java.util.Currency;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.ConverterException;

import javax.faces.el.ValueBinding;
import org.apache.myfaces.trinidad.bean.FacesBean;
import org.apache.myfaces.trinidad.bean.PropertyKey;
import org.apache.myfaces.trinidad.context.RequestContext;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.util.ComponentUtils;
import org.apache.myfaces.trinidad.util.MessageFactory;

/**
* This is an extension of the standard JSF {@link javax.faces.convert.NumberConverter}
* The converter provides all the standard functionality
* of the default NumberConverter and is strict while converting to object.
*
* JSF {@link javax.faces.convert.NumberConverter} is lenient and will convert
* values like 22.22.2 or 22ABC to valid number 22.22 and 22 respectively,
* here it would result in a conversion failure and would throw
* ConverterException.
*
* If <code>number grouping separator, decimal separator</code>
* is configured in <code>trinidad-config.xml</code> file,
* it will be used during call to <code>getAsObject()</code> and
* <code>getAsString()</code> for parsing and formatting. If it has not been set,
* <code>number grouping separator, decimal separator</code> is
* defaulted based on the locale.</p>
*
* <p>If <code>currencyCode</code> is set on the converter then it will be used.
* Else uses the <code>currencyCode</code> set on <code>trinidad-config.xml</code>
* file. If it is not set, then it is defaulted based on the locale. The
* value registered in trinidad-config.xml is obtained using
* api from {@link org.apache.myfaces.trinidad.context.RequestContext}.</p>
*
* <p>Since Apache Trinidad is compatible only with JDK 1.4 and higher versions,
* the <code>currencyCode</code> gets preference over <code>currencySymbol</code>
* See RI's {@link javax.faces.convert.NumberConverter} for the way in which
* <code>currencyCode</code> and <code>currencySymbol</code> gets preference for
* different version of JDK.
*
*<p>The <code>detail</code> part of the {@link FacesMessage} can be customized.
* For each message id there is a corresponding
* setter method, which provides for message customization. The customized
* messages can contain placeholders as specified in the documentation
* for its corresponding message id.</p>
*
* <p>Example: to customize the message for invalid input values, which will result
* in conversion error containing {@link #CONVERT_NUMBER_MESSAGE_ID}, the following,
* can be done.</p>
* <code>
* String convertNumberMessageDetail = "{0}" in "{1}" is not valid age.</code>
*
* //  Note that, the string can contain placeholders and it will be replaced
* //  appropriately as specified in the documentation for the corresponding
* //  message id.
*
* <code>setMessageDetailConvertNumber(convertNumberMessageDetail);</code>
*
* This way user can override detail part of the {@link FacesMessage} for
* different conversion errors that occur for wrong values, that arise
* during conversion.
*
* There is a one to one mapping for message customization which is as given below. <p>The methods used for customizing the detail message associated with each id
* is given below:</p>
* <ol><code>
* <li>{@link #CONVERT_PATTERN_MESSAGE_ID} - {@link #setMessageDetailConvertPattern(String)}</li>
* <li>{@link #CONVERT_NUMBER_MESSAGE_ID} {@link #setMessageDetailConvertNumber(String)}</li>
* <li>{@link #CONVERT_CURRENCY_MESSAGE_ID} - {@link #setMessageDetailConvertCurrency(String)}</li>
* <li>{@link #CONVERT_PERCENT_MESSAGE_ID} - {@link #setMessageDetailConvertPercent(String)}</li>
* </code></ol>The custom messages can contain placeholders, which will be replaced with
* values as specified in its corresponding message id.
*
* <p>
* This NumberConverter is automatically registered under the standard
* converter ID, and therefore will be used when the
* <code>&lt;f:convertNumber&gt;</code> tag is used.
*
* @see org.apache.myfaces.trinidad.context.RequestContext
*
* <p>
*/
public class NumberConverter extends javax.faces.convert.NumberConverter
{

  /**
   * <p>The standard converter id for this converter.</p>
   */
    public static final String CONVERTER_ID = "org.apache.myfaces.trinidad.Number";

  /**
   * <p>The message identifier of the {@link FacesMessage}
   * to be created if the input value does not match the specified pattern.
   * The message format string for this message may optionally
   * include <code>{0}</code>, <code>{1}</code> and <code>{4}</code>
   * placeholders, which will be replaced with the input value,
   * label associated with the component and the pattern respectively.</p>
   */
   public static final String CONVERT_PATTERN_MESSAGE_ID =
    "org.apache.myfaces.trinidad.convert.NumberConverter.CONVERT_PATTERN";

  /**
   * <p>The message identifier of the {@link FacesMessage}
   * to be created if the input value is not a valid number.  The message format
   * string for this message may optionally include <code>{0}</code> and
   * <code>{1}</code> placeholders, which will be replaced with
   * the input value and label associated with the component
   * respectively.</p>
   */
   public static final String CONVERT_NUMBER_MESSAGE_ID =
    "org.apache.myfaces.trinidad.convert.NumberConverter.CONVERT_NUMBER";

  /**
   * <p>The message identifier of the {@link FacesMessage}
   * to be created if the input value is not a valid number when <code>type</code>
   * is set to <code>'currency'</code>. The message format
   * string for this message may optionally include <code>{0}</code> and
   * <code>{1}</code> placeholders, which will be replaced with
   * the input value and label associated with the component
   * respectively.</p>
   */
   public static final String CONVERT_CURRENCY_MESSAGE_ID =
    "org.apache.myfaces.trinidad.convert.NumberConverter.CONVERT_CURRENCY";

   /**
   * <p>The message identifier of the {@link FacesMessage}
   * to be created if the input value is not a valid number when <code>type</code>
   * is set to <code>'percent'</code>. The message format
   * string for this message may optionally include <code>{0}</code> and
   * <code>{1}</code> placeholders, which will be replaced with
   * the input value and label associated with the component
   * respectively.</p>
   */
   public static final String CONVERT_PERCENT_MESSAGE_ID =
    "org.apache.myfaces.trinidad.convert.NumberConverter.CONVERT_PERCENT";


  //Converter interface implementation
  /**
   * Performs strict conversion of string to number.
   * Values having more than one decimal seprator like <code>22.22.22</code>
   * and values of the form <code>22ABC, 22%ABC</code> will result in
   * {@link javax.faces.convert.ConverterException}.
   */
  @Override
  public Object getAsObject(
    FacesContext context,
    UIComponent component,
    String value)
  {

    if ( null == context || null == component )
    {
      throw new NullPointerException(_LOG.getMessage(
        "NULL_FACESCONTEXT_OR_UICOMPONENT"));
    }

    if (null == value)
      return null;

    value = value.trim();
    if (value.length() < 1)
      return null;

    String pattern = getPattern();
    String type = getType();

    if (null == pattern && null == type)
    {
      throw new IllegalArgumentException(_LOG.getMessage(
        "EITHER_PATTERN_OR_TYPE_MUST_SPECIFIED"));
    }

    RequestContext reqCtx = RequestContext.getCurrentInstance();
    Locale locale = _getLocale(reqCtx, context);

    NumberFormat fmt = _getNumberFormat(pattern, type, locale, reqCtx);
   
    DecimalFormat df = (DecimalFormat)fmt;
    df.setParseBigDecimal(true);
    DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();

    // We change the grouping_separator b/c TRINIDAD-849
    // we parse a second-time, once the first run fails
    // source is this JDK bug: 4510618.
    boolean changed = false;
    if(dfs.getGroupingSeparator() == '\u00a0')
    {
      dfs.setGroupingSeparator(' ');
      df.setDecimalFormatSymbols(dfs);
      changed = true;
    }
    ParsePosition pp = new ParsePosition(0);
    Number num = (Number) fmt.parseObject(value,pp);
    if(changed)
    {
      dfs.setGroupingSeparator('\u00a0');
      df.setDecimalFormatSymbols(dfs);
    }
   
    if(num == null)
    {
      num = (Number) fmt.parseObject(value, pp);
    }

    if (pp.getIndex() != value.length())
    {
      Object label = ConverterUtils.getComponentLabel(component);
      Object[] params = null;
      if("currency".equals(type))
      {
        params = new Object[] {label, getPattern(), fmt.format(_EXAMPLE_CURRENCY)};
      }
      else if("percent".equals(type))
      {
        params = new Object[] {label, getPattern(), fmt.format(_EXAMPLE_PERCENT)};
      }
      else
      {
        params = new Object[] {label, getPattern()};
      }
       
       
      throw new ConverterException(
        getConvertMessage(context, component, value, params));
    }
    // if we set setParseIntegerOnly(isIntegerOnly()) - This may result in
    // the formatter stopping to parse after the first decimal point.
    // that is number of value 222.22 which is legitimate, hence our test would
    // fail. hence we did not do the following
    // fmt.setParseIntegerOnly(isIntegerOnly());
    // We allow the value to be totally parsed and if the user has set
    // to integer only. We will return the long value from the number object
    // we have in hand.
    if (isIntegerOnly())
      return Long.valueOf(num.longValue());

    return num;

  }
  /**
   *
   * @param context {@link FacesContext} for the request being processed
   * @param component {@link UIComponent} with which this model object
   *        value is associated.
   * @param value Model object value to be converted (may be <code>null</code>)
   *
   * @return a zero-length String if value is <code>null</code>,
   *         if the passed value is a String, it's returned unchanged,
   *         otherwise String representation for the number object based on the
   *         attributes set.
   * @exception ConverterException if conversion cannot be successfully
   *            performed
   * @exception NullPointerException if <code>context</code> or
   *            <code>component</code> is <code>null</code>
   * @exception IllegalArgumentException if the <code>value</code> is not of
   *            type other than {@link java.lang.Number}, {@link java.lang.String}.
   *            <code>value</code> can be null.
   */
  @Override
  public String getAsString(
    FacesContext context,
    UIComponent component,
    Object value)
  {
    if ( null == context || null == component )
    {
      throw new NullPointerException(_LOG.getMessage(
        "NULL_FACESCONTEXT_OR_UICOMPONENT"));
    }

    if(value == null)
     return "";

    if(value instanceof String)
      return (String)value;

    if (!(value instanceof Number))
      throw new IllegalArgumentException(_LOG.getMessage(
        "VALUE_NOT_JAVA_LANG_NUMBER_TYPE"));

    String pattern = getPattern();
    String type    = getType();

    if (null == pattern && null == type)
    {
      throw new IllegalArgumentException(_LOG.getMessage(
        "EITHER_PATTERN_OR_TYPE_MUST_SPECIFIED"));
    }


    RequestContext reqCtx = RequestContext.getCurrentInstance();
    Locale locale  = _getLocale(reqCtx, context);

    NumberFormat formatter = _getNumberFormat(pattern, type, locale, reqCtx);

    _setFormatProperties(formatter);

    if("currency".equals(type))
    {
      _setCurrencyFormattingProperties(reqCtx, formatter);
    }

    return formatter.format(value);
  }

  @Override
  public void restoreState(
    FacesContext context,
    Object state)
  {
    _facesBean.restoreState(context, state);
  }

  @Override
  public Object saveState(FacesContext context)
  {
    return _facesBean.saveState(context);
  }

  /**
   * <p>Set the {@link ValueBinding} used to calculate the value for the
   * specified attribute if any.</p>
   *
   * @param name Name of the attribute for which to set a {@link ValueBinding}
   * @param binding The {@link ValueBinding} to set, or <code>null</code>
   *  to remove any currently set {@link ValueBinding}
   *
   * @exception NullPointerException if <code>name</code>
   *  is <code>null</code>
   * @exception IllegalArgumentException if <code>name</code> is not a valid
   *            attribute of this converter
   */
  public void setValueBinding(String name, ValueBinding binding)
  {
    ConverterUtils.setValueBinding(_facesBean, name, binding) ;
  }

  /**
   * <p>Return the {@link ValueBinding} used to calculate the value for the
   * specified attribute name, if any.</p>
   *
   * @param name Name of the attribute or property for which to retrieve a
   *  {@link ValueBinding}
   *
   * @exception NullPointerException if <code>name</code>
   *  is <code>null</code>
   * @exception IllegalArgumentException if <code>name</code> is not a valid
   * attribute of this converter
   */
  public ValueBinding getValueBinding(String name)
  {
    return ConverterUtils.getValueBinding(_facesBean, name);
  }

  /**
   * Custom error message to be used, for creating detail part of the {@link FacesMessage},
   * message, when <code>value</code> cannot be converted to a number,
   * based on the <code>pattern</code> set.
   * Overrides detail message identified by message id {@link #CONVERT_PATTERN_MESSAGE_ID}
   * @param convertPatternMessageDetail Custom error message.
   */
  public void setMessageDetailConvertPattern(String convertPatternMessageDetail)
  {
    _facesBean.setProperty(_CONVERT_PATTERN_MESSAGE_DETAIL_KEY, convertPatternMessageDetail);
  }

  /**
   * Custom detail error message that was set for creation of {@link FacesMessage}
   * when conversion fails for values that does not match the pattern set.
   * @return Custom error message.
   * @see #setMessageDetailConvertPattern(String)
   *
   */
  public String getMessageDetailConvertPattern()
  {
    Object msg = _facesBean.getProperty(_CONVERT_PATTERN_MESSAGE_DETAIL_KEY);
    return ComponentUtils.resolveString(msg);
  }

  /**
   * <p>Custom error message to be used, for creating detail part of the {@link FacesMessage},
   * when <code>value</code> cannot be converted to a number,
   * when <code>type</code> is set to <code>'number'</code> and
   * <code>pattern</code> is null or not set.</p>
   * Overrides detail message identified by message id {@link #CONVERT_NUMBER_MESSAGE_ID}
   * @param convertNumberMessageDetail custom error message.
   */
  public void setMessageDetailConvertNumber(String convertNumberMessageDetail)
  {
    _facesBean.setProperty(_CONVERT_NUMBER_MESSAGE_DETAIL_KEY, convertNumberMessageDetail);
  }

  /**
   * <p>Return custom detail error message that was set for creating {@link FacesMessage},
   * when conversion fails for values, when <code>type</code> is set to <code>'number'</code> and
   * <code>pattern</code> is null or not set.</p>
   * @return Custom error message.
   * @see #setMessageDetailConvertNumber(String)
   */
  public String getMessageDetailConvertNumber()
  {
    Object msg = _facesBean.getProperty(_CONVERT_NUMBER_MESSAGE_DETAIL_KEY);
    return ComponentUtils.resolveString(msg);
  }

  /**
   * Custom error message to be used, for creating detail part of the
   * {@link FacesMessage},  when <code>value</code>
   * cannot be converted to a number, when <code>type</code> is set to
   * <code>'currency'</code> and <code>pattern</code> is null or not set.</p>
   * Overrides detail message identified by message id {@link #CONVERT_CURRENCY_MESSAGE_ID}.
   * @param convertCurrencyMessageDetail custom error message.
   *
   */
  public void setMessageDetailConvertCurrency(String convertCurrencyMessageDetail)
  {
    _facesBean.setProperty(_CONVERT_CURRENCY_MESSAGE_DETAIL_KEY,convertCurrencyMessageDetail);
  }

  /**
   * <p>Return custom detail error message that was set for creating {@link FacesMessage},
   * when conversion fails for values, when <code>type</code> is set to
   * <code>'currency'</code> and <code>pattern</code> is null or not set.</p>
   * @return Custom error message.
   * @see #setMessageDetailConvertCurrency(String)
   */
  public String getMessageDetailConvertCurrency()
  {
    Object msg = _facesBean.getProperty(_CONVERT_CURRENCY_MESSAGE_DETAIL_KEY);
    return ComponentUtils.resolveString(msg);
  }

  /**
   * Custom error message to be used, for creating detail part of the
   * {@link FacesMessage}, when <code>value</code> cannot be converted to a
   * number, when <code>type</code> is set to <code>'percent'</code> and
   * <code>pattern</code> is null or not set.</p>
   * Overrides detail message identified by message id {@link #CONVERT_PERCENT_MESSAGE_ID}
   * @param convertPercentMessageDetail custom error message.
   */
  public void setMessageDetailConvertPercent(String convertPercentMessageDetail)
  {
    _facesBean.setProperty(_CONVERT_PERCENT_MESSAGE_DETAIL_KEY, convertPercentMessageDetail);
  }

  /**
   * <p>Return custom detail error message that was set for creating {@link FacesMessage},
   * when conversion fails for values, when <code>value</code> cannot be converted to a
   * number, when <code>type</code> is set to <code>'percent'</code>
   * and <code>pattern</code> is null or not set.</p>
   * @return Custom error message.
   * @see #setMessageDetailConvertPercent(String)
   */
  public String getMessageDetailConvertPercent()
  {
    Object msg = _facesBean.getProperty(_CONVERT_PERCENT_MESSAGE_DETAIL_KEY);
    return ComponentUtils.resolveString(msg);
  }
 
  /**
   * <p>Custom hintPattern message.</p>
   * Overrides default hint message
   * @param hintPattern Custom hint message.
   */
  public void setHintPattern(String hintPattern)
  {
    _facesBean.setProperty(_HINT_PATTERN_KEY, hintPattern);
  }

  /**
   * <p>Return custom hintPattern message.</p>
   * @return Custom hint message.
   * @see  #setHintPattern(String)
   */
  public String getHintPattern()
  {
    Object obj = _facesBean.getProperty(_HINT_PATTERN_KEY);
    return ComponentUtils.resolveString(obj);
  }

 

  @Override
  public void setCurrencyCode(String currencyCode)
  {
    _facesBean.setProperty(_CURRENCY_CODE_KEY, currencyCode);
  }

  @Override
  public String getCurrencyCode()
  {
    Object currCode = _facesBean.getProperty(_CURRENCY_CODE_KEY);
    return ComponentUtils.resolveString(currCode);
  }

  @Override
  public void setCurrencySymbol(String currencySymbol)
  {
    _facesBean.setProperty(_CURRENCY_SYMBOL_KEY, currencySymbol);
  }

  @Override
  public String getCurrencySymbol()
  {
    Object currSymbol = _facesBean.getProperty(_CURRENCY_SYMBOL_KEY);
    return ComponentUtils.resolveString(currSymbol);
  }

  @Override
  public void setGroupingUsed(boolean groupingUsed)
  {
    Boolean grpUsed = _getBooleanValue(groupingUsed);
    _facesBean.setProperty(_GROUPING_USED_KEY, grpUsed);
  }

  @Override
  public  boolean isGroupingUsed()
  {
    Object grpUSed = _facesBean.getProperty(_GROUPING_USED_KEY);
    return ComponentUtils.resolveBoolean(grpUSed, true);
  }

  @Override
  public void setIntegerOnly(boolean integerOnly)
  {
    _facesBean.setProperty(_INTEGER_ONLY_KEY, _getBooleanValue(integerOnly));
  }

  @Override
  public boolean isIntegerOnly()
  {
    Object isInt = _facesBean.getProperty(_INTEGER_ONLY_KEY);
    return ComponentUtils.resolveBoolean(isInt, false);
  }

  /**
   * <p>Set the <code>Locale</code> to be used when parsing numbers.
   * If set to <code>null</code>, the <code>Locale</code> stored in the
   * {@link javax.faces.component.UIViewRoot} for the current request
   * will be utilized.</p>
   *
   * @param locale The new <code>Locale</code> (or <code>null</code>)
   */
  @Override
  public void setLocale(Locale locale)
  {
    _facesBean.setProperty(_LOCALE_KEY, locale);
  }
  /**
   * <p>Return the <code>Locale</code> that was set, returns null if it was not set,
   * while faces RI returns the <code>Locale</code> set on the view root if the locale
   * is null.
   * If this value is <code>null</code>, the <code>Locale</code> stored
   * in the {@link javax.faces.component.UIViewRoot} for the current request
   * will be utilized during parsing.</p>
   */
  @Override
  public Locale getLocale()
  {
    Object locale = _facesBean.getProperty(_LOCALE_KEY);
    return ComponentUtils.resolveLocale(locale);
  }

  // All these overrides are mainly to identify whether these were set or nor in
  // the first place

  @Override
  public void setMaxFractionDigits(int maxFractionDigits)
  {
    _facesBean.setProperty(_MAX_FRACTION_DIGITS_KEY, _getIntValue(maxFractionDigits));
  }

  @Override
  public int getMaxFractionDigits()
  {
    Object value = _facesBean.getProperty(_MAX_FRACTION_DIGITS_KEY);
    return ComponentUtils.resolveInteger(value);
  }

  @Override
  public void setMaxIntegerDigits(int maxIntegerDigits)
  {
    _facesBean.setProperty(_MAX_INTEGER_DIGITS_KEY, _getIntValue(maxIntegerDigits));
  }

  @Override
  public int getMaxIntegerDigits()
  {
    Object value = _facesBean.getProperty(_MAX_INTEGER_DIGITS_KEY);
    return ComponentUtils.resolveInteger(value);
  }

  @Override
  public void setMinFractionDigits(int minFractionDigits)
  {
    _facesBean.setProperty(_MIN_FRACTION_DIGITS_KEY, _getIntValue(minFractionDigits));
  }

  @Override
  public int getMinFractionDigits()
  {
    Object value = _facesBean.getProperty(_MIN_FRACTION_DIGITS_KEY);
    return ComponentUtils.resolveInteger(value);
  }

  @Override
  public void setMinIntegerDigits(int minIntegerDigits)
  {
    _facesBean.setProperty(_MIN_INTEGER_DIGITS_KEY, _getIntValue(minIntegerDigits));
  }

  @Override
  public int getMinIntegerDigits()
  {
    Object value = _facesBean.getProperty(_MIN_INTEGER_DIGITS_KEY);
    return ComponentUtils.resolveInteger(value);
  }

  @Override
  public void setPattern(String pattern)
  {
    _facesBean.setProperty(_PATTERN_KEY, pattern);
  }

  @Override
  public String getPattern()
  {
    Object pattern = _facesBean.getProperty(_PATTERN_KEY);
    return ComponentUtils.resolveString(pattern);
  }

  @Override
  public void setType(String type)
  {
    _facesBean.setProperty(_TYPE_KEY, type);
  }

  @Override
  public String getType()
  {
    Object type = _facesBean.getProperty(_TYPE_KEY);
    return ComponentUtils.resolveString(type, "number");
  }

  /**
   * Returns the hash code for this Converter.
   */
  @Override
  public int hashCode()
  {
    int result = 17;
    result = result * 37 + _getHashValue(getLocale());
    result = result * 37 + _getHashValue(getCurrencyCode());
    result = result * 37 + _getHashValue(getCurrencySymbol());
    result = result * 37 + _getHashValue(getType());
    result = result * 37 + _getHashValue(getPattern());
    result = result * 37 + getMaxFractionDigits();
    result = result * 37 + getMaxIntegerDigits();
    result = result * 37 + getMinFractionDigits();
    result = result * 37 + getMinIntegerDigits();
    result = result * 37 + (isGroupingUsed() ? 1: 0);
    result = result * 37 + (isIntegerOnly()? 1: 0);
    result = result * 37 + (isTransient() ? 1: 0);
    result = result * 37 + _getHashValue(getMessageDetailConvertPattern());
    result = result * 37 + _getHashValue(getMessageDetailConvertNumber());
    result = result * 37 + _getHashValue(getMessageDetailConvertCurrency());
    result = result * 37 + _getHashValue(getMessageDetailConvertPercent());
    return result;
  }

  /**
   * <p>Compares this NumberConverter with the specified Object for equality.</p>
   */
  @Override
  public boolean equals(Object numberConverter)
  {
    if (this == numberConverter)
      return true;

    if (numberConverter instanceof NumberConverter)
    {

      NumberConverter nConv = (NumberConverter) numberConverter;

      return
        getMaxFractionDigits() == nConv.getMaxFractionDigits()  &&
        getMaxIntegerDigits()  == nConv.getMaxIntegerDigits()   &&
        getMinFractionDigits() ==  nConv.getMinFractionDigits() &&
        getMinIntegerDigits()  ==  nConv.getMinIntegerDigits()  &&
        isTransient() == nConv.isTransient() &&
        isGroupingUsed() ==  nConv.isGroupingUsed() &&
        isIntegerOnly()  == nConv.isIntegerOnly()   &&
        ConverterUtils.equals(getType(), nConv.getType()) &&
        ConverterUtils.equals(getLocale(), nConv.getLocale()) &&
        ConverterUtils.equals(getCurrencyCode(), nConv.getCurrencyCode()) &&
        ConverterUtils.equals(getCurrencySymbol(), nConv.getCurrencySymbol()) &&
        ConverterUtils.equals(getPattern(), nConv.getPattern()) &&
        ConverterUtils.equals(getMessageDetailConvertPattern(),
                              nConv.getMessageDetailConvertPattern()) &&
        ConverterUtils.equals(getMessageDetailConvertNumber(),
                              nConv.getMessageDetailConvertNumber())  &&
        ConverterUtils.equals(getMessageDetailConvertCurrency(),
                              nConv.getMessageDetailConvertCurrency())&&
        ConverterUtils.equals(getMessageDetailConvertPercent(),
                              nConv.getMessageDetailConvertPercent());

    }
    return false;
  }

  private static int _getHashValue(Object obj)
  {
    return obj == null? 0 : obj.hashCode();
  }

  private static DecimalFormatSymbols _getCachedDecimalFormatSymbol(Locale locale)
  {
    synchronized(_SYMBOLS_LOCK)
    {

      Object dfs = _patternFormatSymbolsHolder.get(locale);

      // make sure to return a clone so that it can be mutated at the point
      // of use based on the setting in the RequestContext
      if (dfs != null)
        return (DecimalFormatSymbols) ((DecimalFormatSymbols) dfs).clone();
      else
        return null;
    }
  }

  private static void _cacheDecimalFormatSymbols(
    Locale locale,
    DecimalFormatSymbols symbols)
  {
    synchronized(_SYMBOLS_LOCK)
    {
      // -= Simon Lessard =- That if looks paranoid, the map get instanciated
      //                     during static initialization and is never set to
      //                     null
       if (_patternFormatSymbolsHolder == null)
        _patternFormatSymbolsHolder = new HashMap<Locale, DecimalFormatSymbols>();
       else
       // to clone here or at the point of creation??
      // FIXME: Is that line really supposed to go in the else????
      _patternFormatSymbolsHolder.put(locale, (DecimalFormatSymbols)symbols.clone());
    }
  }

  private static Boolean _getBooleanValue(boolean value)
  {
    return (value ? Boolean.TRUE : Boolean.FALSE);
  }

  private static Integer _getIntValue(int value)
  {
    return Integer.valueOf(value);
  }

  private NumberFormat _getCachedNumberFormat(
    String pattern,
    String type,
    Locale locale)
  {
    synchronized(_TYPE_LOCK)
    {
      // get the map for the appropriate type('number','currency', 'percent') or
      // based on diff patterns - pattern1.. pattern2 for diff locales.
      String key = ((pattern != null) ? pattern : type);
      Map<Locale, NumberFormat> nfMap = _numberFormatHolder.get(key);

      if (nfMap == null)
        return null;
      else
      {
        NumberFormat nf = nfMap.get(locale);
        if (nf != null)
          return (NumberFormat) nf.clone();
      }
      return null;
    }
  }

  private void _cacheNumberFormat(
    NumberFormat format,
    String pattern,
    String type,
    Locale locale)
  {
    synchronized(_TYPE_LOCK)
    {
      // -= Simon Lessard =- That if looks paranoid, the map get instanciated
      //                     during static initialization and is never set to
      //                     null
      if (_numberFormatHolder == null)
        _numberFormatHolder = new HashMap<String, Map<Locale, NumberFormat>>();

      else
      {
        // The key could have either been the type based on which formats are
        // stored or it can be based on the pattern also.
        String key = ((pattern != null) ? pattern : type);

        Map<Locale, NumberFormat> nfMap = _numberFormatHolder.get(key);

        // if we have not cached any NumberFormat for this type, then create a
        // map for that type and add to it based on the locale
        if (nfMap == null)
        {
          nfMap = new HashMap<Locale, NumberFormat>();
          nfMap.put(locale, (NumberFormat)format.clone());
        }
        // add this based on the type ('number','currency','percent') or
        // pattern1, pattern2.. patternN to the main holder
        _numberFormatHolder.put(key, nfMap);
      }
    }
  }

  private NumberFormat _getNumberFormat(
    String pattern,
    String type,
    Locale locale,
    RequestContext reqCtx
    )
  {
    NumberFormat nfmt;

    int formatType = _getType(pattern, type);

    nfmt = _getCachedNumberFormat(pattern, type, locale);

    if (nfmt == null)
    {
      nfmt = _getNumberFormatter(formatType, pattern, locale);

     _cacheNumberFormat(nfmt,pattern, type, locale);
    }

    if (nfmt instanceof DecimalFormat)
    {
      DecimalFormat dfmt = (DecimalFormat)nfmt;

      // what we get here is a shallow copy. cloned DFS
      DecimalFormatSymbols dfSymbols = dfmt.getDecimalFormatSymbols();

      _setUpDecimalSymbolFormatProperties(dfSymbols, reqCtx, locale);

      //since we get a shallow copy - setting it again after modification.
      ((DecimalFormat) nfmt).setDecimalFormatSymbols(dfSymbols);
    }
    else
    {
      if(_LOG.isWarning())
      {
        _LOG.warning("Failed to get hold of DecimalFormat for type: +" + type + "\n" +
                     "decimal separator,"         +
                     "number grouping separator," +
                     "currency code"              +
                     "will be defaulted based on locale " + locale.toString());
     }
    }
    return nfmt;
  }

  private void _setUpDecimalSymbolFormatProperties(
    DecimalFormatSymbols symbols,
    RequestContext reqCtx,
    Locale locale
    )
  {
    if (reqCtx != null)
    {
      char ch = (char) 0;

      if ((ch = reqCtx.getDecimalSeparator()) != (char)0)
        symbols.setDecimalSeparator(ch);

      if ((ch = reqCtx.getNumberGroupingSeparator()) != (char)0)
        symbols.setGroupingSeparator(ch);

    }
    else
    {
      if (_LOG.isWarning())
      {
        _LOG.warning("NULL_REQUESTCONTEXT", locale.toString());
        }
    }
  }


   // Configure the specified NumberFormat  based on the
   // formatting properties that have been set.
  private void _setFormatProperties(NumberFormat formatter) {

    formatter.setGroupingUsed(isGroupingUsed());

    if (isMaximumFractionDigitsSet())
    {
        formatter.setMaximumFractionDigits(getMaxFractionDigits());
    }

    if (isMaximumIntegerDigitsSet())
    {
      formatter.setMaximumIntegerDigits(getMaxIntegerDigits());
    }

    if (isMinimumFractionDigitsSet())
    {
      formatter.setMinimumFractionDigits(getMinFractionDigits());
    }

    if (isMinimumIntegerDigitsSet())
    {
      formatter.setMinimumIntegerDigits(getMinIntegerDigits());
    }
  }

  private void _setCurrencyInformation(
    RequestContext context,
    DecimalFormatSymbols symbols)
  {
    String currencyCode = _getCurrencyCode(context);

    // currencyCode is set we honour currency code.
    if (currencyCode != null)
    {
      symbols.setCurrency(Currency.getInstance(currencyCode));
      return;
    }

    if (getCurrencySymbol() != null)
    {
      symbols.setCurrencySymbol(getCurrencySymbol());

       // Loggin at level INFO - shows up by default - so use fine.
      _LOG.fine("Using currency symbol as currecny code evaluates to null");
    }
    // currency symbol will now default based on the locale.
  }

  private NumberFormat _getNumberFormatter(
    int formatType,
    String pattern,
    Locale locale)
  {
    NumberFormat nfmt;

    if(_PATTERN_TYPE == formatType)
    {
      DecimalFormatSymbols symbols = _getCachedDecimalFormatSymbol(locale);

      if (null == symbols)
      {
         symbols = new DecimalFormatSymbols(locale);
         // cache this - It is cloned while caching.
        _cacheDecimalFormatSymbols(locale, symbols);
      }
      nfmt = new DecimalFormat(pattern, symbols);
    }

    else if(_NUMBER_TYPE == formatType)
    {
       nfmt = NumberFormat.getNumberInstance(locale);
    }
    else if(_CURRENCY_TYPE == formatType)
    {
      nfmt = NumberFormat.getCurrencyInstance(locale);
    }
    else if(_PERCENT_TYPE == formatType)
    {
      nfmt = NumberFormat.getPercentInstance(locale);
    }
    else
    {
      // never expected to happen
      assert (formatType > _PATTERN_TYPE || formatType < _NUMBER_TYPE) : "invalid type" ;
      nfmt = null;
    }
    return nfmt;
  }

  private Object _getRawConvertCurrencyMessageDetail()
  {
    return _facesBean.getRawProperty(_CONVERT_CURRENCY_MESSAGE_DETAIL_KEY);
  }

  private Object _getRawConvertNumberMessageDetail()
  {
    return _facesBean.getRawProperty(_CONVERT_NUMBER_MESSAGE_DETAIL_KEY);
  }

  private Object _getRawConvertPatternMessageDetail()
  {
    return _facesBean.getRawProperty(_CONVERT_PATTERN_MESSAGE_DETAIL_KEY);
  }

  private Object _getRawConvertPercentMessageDetail()
  {
    return _facesBean.getRawProperty(_CONVERT_PERCENT_MESSAGE_DETAIL_KEY);
  }

  protected final FacesMessage getConvertMessage(
    FacesContext context,
    UIComponent component,
    String inputValue,
    Object[] params
    )
  {
    int type = _getType(getPattern(), getType());
    Object convMsgDet;
    String msgId;

    // Always check for pattern first.
    if (_PATTERN_TYPE == type)
    {
      convMsgDet = _getRawConvertPatternMessageDetail();
      msgId = CONVERT_PATTERN_MESSAGE_ID;
    }
    else if(_NUMBER_TYPE == type)
    {
      convMsgDet = _getRawConvertNumberMessageDetail();
      msgId = CONVERT_NUMBER_MESSAGE_ID;
    }
    else if(_CURRENCY_TYPE == type)
    {
      convMsgDet = _getRawConvertCurrencyMessageDetail();
      msgId = CONVERT_CURRENCY_MESSAGE_ID;
    }
    else if(_PERCENT_TYPE == type)
    {
      convMsgDet = _getRawConvertPercentMessageDetail();
      msgId = CONVERT_PERCENT_MESSAGE_ID;
    }
    else
    {
      throw new IllegalArgumentException("Invalid type: " + getType());
    }
   
    return MessageFactory.getMessage(context,
                                     msgId,
                                     convMsgDet,
                                     params,
                                     component);
  }

  private Locale _getLocale(RequestContext rc, FacesContext context)
  {
    Locale locale = getLocale();
    if (locale == null )
    {
      locale = rc.getFormattingLocale();
      if (locale == null)
      {
        locale = context.getViewRoot().getLocale();
      }
    }

    return locale;
  }

  private String _getCurrencyCode(
    RequestContext context
    )
  {
    String currencyCode = getCurrencyCode();
    if (currencyCode == null)
    {
      if (context != null)
      {
        currencyCode = context.getCurrencyCode();
      }
      else
      {
        _LOG.warning("NULL_REQUEST_CONTEXT_UNABLE_GET_CURRENCY_CODE");
      }
    }

    return currencyCode;
  }

  // applied only while formatting
  private void _setCurrencyFormattingProperties(
    RequestContext context,
    NumberFormat numberFormatter
   )
  {
    // Useless if... should be instanceof DecimalFormat
    // Potential ClassCastException before the change
    //if (numberFormatter instanceof NumberFormat)
    if (numberFormatter instanceof DecimalFormat)
    {
      DecimalFormat dfmt = (DecimalFormat)numberFormatter;
      DecimalFormatSymbols symbols = dfmt.getDecimalFormatSymbols();
      _setCurrencyInformation(context, symbols);
      dfmt.setDecimalFormatSymbols(symbols);
    }
    else
    //string cat at compile time.
      _LOG.warning("NUMBER_NOT_DECIMALFORMAT_IGNORE_CURRENCY");
    }
  }

  private static int _getType(String pattern, String type)
  {
    // check for pattern should be done first.
    if (pattern != null)
      return _PATTERN_TYPE;

    else if ("number".equals(type))
     return _NUMBER_TYPE;

    else if ("currency".equals(type))
     return _CURRENCY_TYPE;

    else if ("percent".equals(type))
     return _PERCENT_TYPE;
    else
      throw new IllegalArgumentException(_LOG.getMessage(
        "NOT_VALID_TYPE", type));
  }

  protected boolean isMaximumFractionDigitsSet()
  {
    return _facesBean.getProperty(_MAX_FRACTION_DIGITS_KEY) != null;
  }

  protected boolean isMinimumFractionDigitsSet()
  {
    return _facesBean.getProperty(_MIN_FRACTION_DIGITS_KEY) != null;
  }

  protected boolean isMaximumIntegerDigitsSet()
  {
    return _facesBean.getProperty(_MAX_INTEGER_DIGITS_KEY) != null;
  }

  protected boolean isMinimumIntegerDigitsSet()
  {
    return _facesBean.getProperty(_MIN_INTEGER_DIGITS_KEY) != null;
  }

  private static final FacesBean.Type _TYPE = new FacesBean.Type();

  private static final PropertyKey _CONVERT_CURRENCY_MESSAGE_DETAIL_KEY
   = _TYPE.registerKey("messageDetailConvertCurrency", String.class);

  private static final PropertyKey _CONVERT_NUMBER_MESSAGE_DETAIL_KEY
   = _TYPE.registerKey("messageDetailConvertNumber", String.class);

  private static final PropertyKey _CONVERT_PATTERN_MESSAGE_DETAIL_KEY
   = _TYPE.registerKey("messageDetailConvertPattern", String.class);

  private static final PropertyKey _CONVERT_PERCENT_MESSAGE_DETAIL_KEY
   = _TYPE.registerKey("messageDetailConvertPercent", String.class);
 
  private static final PropertyKey  _HINT_PATTERN_KEY =
    _TYPE.registerKey("hintPattern", String.class);

  private static final PropertyKey _CURRENCY_CODE_KEY
   = _TYPE.registerKey("currencyCode", String.class);

  private static final PropertyKey _CURRENCY_SYMBOL_KEY
   = _TYPE.registerKey("currencySymbol", String.class);

  private static final PropertyKey _GROUPING_USED_KEY
   = _TYPE.registerKey("groupingUsed", boolean.class, Boolean.TRUE);

  private static final PropertyKey _INTEGER_ONLY_KEY
   =  _TYPE.registerKey("integerOnly", boolean.class, Boolean.FALSE);

  private static final PropertyKey _LOCALE_KEY
   = _TYPE.registerKey("locale", Locale.class);

  private static final PropertyKey _MAX_FRACTION_DIGITS_KEY
   =  _TYPE.registerKey("maxFractionDigits", int.class);

  private static final PropertyKey _MAX_INTEGER_DIGITS_KEY
   = _TYPE.registerKey("maxIntegerDigits", int.class);

  private static final PropertyKey _MIN_FRACTION_DIGITS_KEY
   = _TYPE.registerKey("minFractionDigits", int.class);

  private static final PropertyKey _MIN_INTEGER_DIGITS_KEY
   = _TYPE.registerKey("minIntegerDigits", int.class);

  private static final PropertyKey _PATTERN_KEY
   = _TYPE.registerKey("pattern", String.class);

  private static final PropertyKey  _TYPE_KEY
   = _TYPE.registerKey("type", String.class, "numeric");

  private FacesBean _facesBean = ConverterUtils.getFacesBean(_TYPE);

  private static TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(NumberConverter.class);


  // This is a Map which will hold type and patterns as the key and then
  // hold corresponding  number formats based on the locale. The keys of this
  // map are 'number', 'percent', 'currency' and differernt patterns which the
  // user could have used during the entire application. Each key will
  // hold a Map as its value.
  // This Map (value part of the type) will hold locale as its key and Number
  // formats as its values.
  private static Map<String, Map<Locale, NumberFormat>> _numberFormatHolder =
    new HashMap<String, Map<Locale, NumberFormat>>();

  // This is map to hold DecimalFormatSymbols when the converter is used,
  // by specifying a pattern. When a pattern is specified we take care of
  // creating the DecimalFormatSymbols. We are caching decimal format symbols
  // based on the locale so that we can make use of it, when a new number
  // converters is instantiated and used based on pattern and not by type.
  private static Map<Locale, DecimalFormatSymbols> _patternFormatSymbolsHolder =
    new HashMap<Locale, DecimalFormatSymbols>();

  private static final Object _TYPE_LOCK = new Object();

  private static final Object _SYMBOLS_LOCK = new Object();

  private static final int _NUMBER_TYPE   = 1;

  private static final int _CURRENCY_TYPE = 2;

  private static final int _PERCENT_TYPE  = 3;

  private static final int _PATTERN_TYPE  = 4;
 
  private static final Number _EXAMPLE_PERCENT;

  private static final Number _EXAMPLE_CURRENCY;

  static
  {
    _EXAMPLE_PERCENT = 0.3423d;
    _EXAMPLE_CURRENCY = 10250;
  }
}
TOP

Related Classes of org.apache.myfaces.trinidad.convert.NumberConverter

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.