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

org.apache.myfaces.trinidad.convert.NumberConverter Maven / Gradle / Ivy

The newest version!
/*
 * 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.math.RoundingMode;
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 java.util.regex.Matcher;

import javax.el.ValueExpression;

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.buildtools.maven2.plugin.builder.annotation.JSFConverter;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
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 number grouping separator, decimal separator
 * is configured in trinidad-config.xml file,
 * it will be used during call to getAsObject() and
 * getAsString() for parsing and formatting. If it has not been set,
 * number grouping separator, decimal separator is
 * defaulted based on the locale.

* *

If currencyCode is set on the converter then it will be used. * Else uses the currencyCode set on trinidad-config.xml * 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}.

* *

Since Apache Trinidad is compatible only with JDK 1.4 and higher versions, * the currencyCode gets preference over currencySymbol * See RI's {@link javax.faces.convert.NumberConverter} for the way in which * currencyCode and currencySymbol gets preference for * different version of JDK. * *

The detail 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.

* *

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.

* * String convertNumberMessageDetail = "{0}" in "{1}" is not valid age. * * // Note that, the string can contain placeholders and it will be replaced * // appropriately as specified in the documentation for the corresponding * // message id. * * setMessageDetailConvertNumber(convertNumberMessageDetail); * * 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.

The methods used for customizing the detail message associated with each id * is given below:

*
    *
  1. {@link #CONVERT_PATTERN_MESSAGE_ID} - {@link #setMessageDetailConvertPattern(String)}
  2. *
  3. {@link #CONVERT_NUMBER_MESSAGE_ID} - {@link #setMessageDetailConvertNumber(String)}
  4. *
  5. {@link #CONVERT_CURRENCY_MESSAGE_ID} - {@link #setMessageDetailConvertCurrency(String)}
  6. *
  7. {@link #CONVERT_PERCENT_MESSAGE_ID} - {@link #setMessageDetailConvertPercent(String)}
  8. *
The custom messages can contain placeholders, which will be replaced with * values as specified in its corresponding message id. * *

* This NumberConverter is automatically registered under the standard * converter ID, and therefore will be used when the * <f:convertNumber> tag is used. * * @see org.apache.myfaces.trinidad.context.RequestContext * *

*/ @JSFConverter(configExcluded=true) public class NumberConverter extends javax.faces.convert.NumberConverter { /** *

The standard converter id for this converter.

*/ public static final String CONVERTER_ID = "org.apache.myfaces.trinidad.Number"; /** *

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 {0}, {1} and {4} * placeholders, which will be replaced with the input value, * label associated with the component and the pattern respectively.

*/ public static final String CONVERT_PATTERN_MESSAGE_ID = "org.apache.myfaces.trinidad.convert.NumberConverter.CONVERT_PATTERN"; /** *

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 {0} and * {1} placeholders, which will be replaced with * the input value and label associated with the component * respectively.

*/ public static final String CONVERT_NUMBER_MESSAGE_ID = "org.apache.myfaces.trinidad.convert.NumberConverter.CONVERT_NUMBER"; /** *

The message identifier of the {@link FacesMessage} * to be created if the input value is not a valid number when type * is set to 'currency'. The message format * string for this message may optionally include {0} and * {1} placeholders, which will be replaced with * the input value and label associated with the component * respectively.

*/ public static final String CONVERT_CURRENCY_MESSAGE_ID = "org.apache.myfaces.trinidad.convert.NumberConverter.CONVERT_CURRENCY"; /** *

The message identifier of the {@link FacesMessage} * to be created if the input value is not a valid number when type * is set to 'percent'. The message format * string for this message may optionally include {0} and * {1} placeholders, which will be replaced with * the input value and label associated with the component * respectively.

*/ 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 22.22.22 * and values of the form 22ABC, 22%ABC 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; if (isDisabled()) return value; value = value.trim(); if (value.length() < 1) return null; String pattern = getPattern(); String type = getType(); int typeIdx = _getType(pattern, type); 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; if(typeIdx == _CURRENCY_TYPE) { // Setup custom currency code/symbol if any _setCurrencyFormattingProperties(reqCtx, fmt); } _setFormatProperties(fmt, typeIdx, reqCtx); df.setParseBigDecimal(true); // TODO What does this do? DecimalFormatSymbols dfs = df.getDecimalFormatSymbols(); if("currency".equals(type)) { // Setup custom currency code/symbol if any _setCurrencyFormattingProperties(reqCtx, fmt); } // We change the grouping_separator b/c TRINIDAD-849 // source is this JDK bug: 4510618. boolean changed = false; if (dfs.getGroupingSeparator() == '\u00a0') { // In some locales, such as fr_FR, the grouping separator is '\u00a0', a // non-breaking space. However, users will normally enter a regular space // character into an input field, so in order for the input to be parsed // correctly, we set the grouping separator to a regular space character. dfs.setGroupingSeparator(' '); df.setDecimalFormatSymbols(dfs); // In the (rare) case that the user actually enters a non-breaking space, // we replace it with a regular space. This should be fine, since the // percent format for fr_FR is " %" (regular space followed by percent). value = value.replace('\u00a0', ' '); changed = true; } ParsePosition pp = new ParsePosition(0); Number num = (Number)fmt.parseObject(value, pp); // The following determines whether the percent/currency symbol was left off. if (num == null && (typeIdx == _CURRENCY_TYPE || typeIdx == _PERCENT_TYPE)) { // For parsing 'value' as a Number when the percent/currency symbol is left off. NumberFormat nfmt = NumberFormat.getNumberInstance(locale); DecimalFormat ndf = (DecimalFormat)nfmt; ndf.setParseBigDecimal(true); // TODO What does this do? DecimalFormatSymbols ndfs = null; if (changed) { ndfs = ndf.getDecimalFormatSymbols(); ndfs.setGroupingSeparator(' '); ndf.setDecimalFormatSymbols(ndfs); } // Assume the percent/currency symbol was left off, in which case we should // be able to parse 'value' as a Number. // An error occured, so the index of pp should still be 0. num = (Number)nfmt.parseObject(value, pp); if (typeIdx == _PERCENT_TYPE && num != null) num = num.doubleValue() / 100.0; } // Change it back, since we could have been handed a cached reference. This // may not be thread-safe, but it probably doesn't have to be. if (changed) { dfs.setGroupingSeparator('\u00a0'); df.setDecimalFormatSymbols(dfs); } if (pp.getIndex() != value.length()) { // According to the comments in // trinidad-api\src\main\xrts\org\apache\myfaces\trinidad\resource\MessageBundle.xrts, // the substitution parameters are supposed to be: // {0} the label that identifies the component // {1} value entered by the user Object label = ConverterUtils.getComponentLabel(component); Object[] params = null; if (typeIdx == _PATTERN_TYPE) { // We call this since the pattern may contain the generic currency sign, which we don't // want to display to the user. pattern = getLocalizedPattern(context, pattern, dfs); params = new Object[] {label, value, pattern}; } else if (typeIdx == _NUMBER_TYPE) { params = new Object[] {label, value}; } else if (typeIdx == _CURRENCY_TYPE) { params = new Object[] {label, value, fmt.format(_EXAMPLE_CURRENCY)}; } else if (typeIdx == _PERCENT_TYPE) { params = new Object[] {label, value, fmt.format(_EXAMPLE_PERCENT)}; } 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 null) * * @return a zero-length String if value is null, * 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 context or * component is null * @exception IllegalArgumentException if the value is not of * type other than {@link java.lang.Number}, {@link java.lang.String}. * value 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 (isDisabled()) return value.toString(); 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); int typeIndx = _getType(pattern, type); if(typeIndx == _CURRENCY_TYPE) { _setCurrencyFormattingProperties(reqCtx, formatter); } _setFormatProperties(formatter, typeIndx, reqCtx); 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); } /** *

Set the {@link ValueExpression} used to calculate the value for the * specified attribute if any.

* * @param name Name of the attribute for which to set a {@link ValueExpression} * @param expression The {@link ValueExpression} to set, or null * to remove any currently set {@link ValueExpression} * * @exception NullPointerException if name * is null * @exception IllegalArgumentException if name is not a valid * attribute of this converter */ public void setValueExpression(String name, ValueExpression expression) { ConverterUtils.setValueExpression(_facesBean, name, expression) ; } /** *

Return the {@link ValueExpression} used to calculate the value for the * specified attribute name, if any.

* * @param name Name of the attribute or property for which to retrieve a * {@link ValueExpression} * * @exception NullPointerException if name * is null * @exception IllegalArgumentException if name is not a valid * attribute of this converter */ public ValueExpression getValueExpression(String name) { return ConverterUtils.getValueExpression(_facesBean, name); } /** *

Set the {@link ValueBinding} used to calculate the value for the * specified attribute if any.

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

Return the {@link ValueBinding} used to calculate the value for the * specified attribute name, if any.

* * @param name Name of the attribute or property for which to retrieve a * {@link ValueBinding} * * @exception NullPointerException if name * is null * @exception IllegalArgumentException if name is not a valid * attribute of this converter * @deprecated */ 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 value cannot be converted to a number, * based on the pattern 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) * */ @JSFProperty public String getMessageDetailConvertPattern() { Object msg = _facesBean.getProperty(_CONVERT_PATTERN_MESSAGE_DETAIL_KEY); return ComponentUtils.resolveString(msg); } /** *

Custom error message to be used, for creating detail part of the {@link FacesMessage}, * when value cannot be converted to a number, * when type is set to 'number' and * pattern is null or not set.

* 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); } /** *

Return custom detail error message that was set for creating {@link FacesMessage}, * when conversion fails for values, when type is set to 'number' and * pattern is null or not set.

* @return Custom error message. * @see #setMessageDetailConvertNumber(String) */ @JSFProperty 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 value * cannot be converted to a number, when type is set to * 'currency' and pattern is null or not set.

* 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); } /** *

Return custom detail error message that was set for creating {@link FacesMessage}, * when conversion fails for values, when type is set to * 'currency' and pattern is null or not set.

* @return Custom error message. * @see #setMessageDetailConvertCurrency(String) */ @JSFProperty 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 value cannot be converted to a * number, when type is set to 'percent' and * pattern is null or not set.

* 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); } /** *

Return custom detail error message that was set for creating {@link FacesMessage}, * when conversion fails for values, when value cannot be converted to a * number, when type is set to 'percent' * and pattern is null or not set.

* @return Custom error message. * @see #setMessageDetailConvertPercent(String) */ @JSFProperty public String getMessageDetailConvertPercent() { Object msg = _facesBean.getProperty(_CONVERT_PERCENT_MESSAGE_DETAIL_KEY); return ComponentUtils.resolveString(msg); } /** *

Custom hintPattern message.

* Overrides default hint message * @param hintPattern Custom hint message. */ public void setHintPattern(String hintPattern) { _facesBean.setProperty(_HINT_PATTERN_KEY, hintPattern); } /** *

Return custom hintPattern message.

* @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); } @JSFProperty @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); } @JSFProperty @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); } @JSFProperty(defaultValue="true") @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)); } @JSFProperty(defaultValue="false") @Override public boolean isIntegerOnly() { Object isInt = _facesBean.getProperty(_INTEGER_ONLY_KEY); return ComponentUtils.resolveBoolean(isInt, false); } /** *

Set the Locale to be used when parsing numbers. * If set to null, the Locale stored in the * {@link javax.faces.component.UIViewRoot} for the current request * will be utilized.

* * @param locale The new Locale (or null) */ @Override public void setLocale(Locale locale) { _facesBean.setProperty(_LOCALE_KEY, locale); } /** *

Return the Locale that was set, returns null if it was not set, * while faces RI returns the Locale set on the view root if the locale * is null. * If this value is null, the Locale stored * in the {@link javax.faces.component.UIViewRoot} for the current request * will be utilized during parsing.

*/ @JSFProperty @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)); } @JSFProperty @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)); } @JSFProperty @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)); } @JSFProperty @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)); } @JSFProperty @Override public int getMinIntegerDigits() { Object value = _facesBean.getProperty(_MIN_INTEGER_DIGITS_KEY); return ComponentUtils.resolveInteger(value); } public void setRoundingMode (RoundingMode mode) { _facesBean.setProperty (_ROUNDING_MODE_KEY, mode); } @JSFProperty public RoundingMode getRoundingMode() { Object roundingMode = _facesBean.getProperty(_ROUNDING_MODE_KEY); return (roundingMode != null ? (RoundingMode) roundingMode : null); } /** * Sets the negative prefix on the converter. The negative prefix set using this method will override the negative * prefix pattern, if any, specified through {@link #setPattern(String)} or any other means. * @param negPrefix Prefix to be applied on negative number */ public void setNegativePrefix(String negPrefix) { _facesBean.setProperty(_NEGATIVE_PREFIX_KEY, negPrefix); } /** * Gets the negative prefix that is applied on the number. * Will return the prefix or null if no negative prefix is specified via {@link #setNegativePrefix(String)}. * Negative prefix specified using negative pattern string has no affect on this method. * @return The prefix as set using {@link #setNegativePrefix(String)} or null */ @JSFProperty public String getNegativePrefix() { Object negPrefix = _facesBean.getProperty(_NEGATIVE_PREFIX_KEY); return ComponentUtils.resolveString(negPrefix, true); } /** * Sets the negative suffix on the converter. The negative suffix set using this method will override the negative * suffix pattern, if any, specified through {@link #setPattern(String)} or any other means. * @param negSuffix Suffix to be applied on negative number */ public void setNegativeSuffix(String negSuffix) { _facesBean.setProperty(_NEGATIVE_SUFFIX_KEY, negSuffix); } /** * Gets the negative suffix that is applied on the number. * Will return the suffix or null if no negative suffix is specified via {@link #setNegativeSuffix(String)}. * Negative suffix specified using negative pattern string has no affect on this method. * @return The suffix as set using {@link #setNegativeSuffix(String)} or null */ @JSFProperty public String getNegativeSuffix() { Object negSuffix = _facesBean.getProperty(_NEGATIVE_SUFFIX_KEY); return ComponentUtils.resolveString(negSuffix, true); } @Override public void setPattern(String pattern) { _facesBean.setProperty(_PATTERN_KEY, pattern); } @JSFProperty @Override public String getPattern() { Object pattern = _facesBean.getProperty(_PATTERN_KEY); return ComponentUtils.resolveString(pattern, true); } /** * If pattern contains the generic currency sign, this method will replace it * with the localized currency symbol (if one exists). * @param context the FacesContext * @param pattern the pattern to be localized * @param dfs the DecimalFormatSymbols; if null, will be constructed from the context * @return */ public String getLocalizedPattern(FacesContext context, String pattern, DecimalFormatSymbols dfs) { if (pattern == null) return null; RequestContext reqCtx = RequestContext.getCurrentInstance(); String type = getType(); Locale locale = _getLocale(reqCtx, context); DecimalFormat df = (DecimalFormat) _getNumberFormat(pattern, type, locale, reqCtx); if (dfs == null) { dfs = df.getDecimalFormatSymbols(); } // If grouping and decimal separator have been customized then // show them in the hint so that it's less confusing for the user. char decSep = dfs.getDecimalSeparator(); char groupSep = dfs.getGroupingSeparator(); char[] patternArr = pattern.toCharArray(); for (int i = 0; i < patternArr.length; i++) { char c = patternArr[i]; if (c == '\u002E') patternArr[i] = decSep; else if (c == '\u002C') patternArr[i] = groupSep; } pattern = new String(patternArr); // If the pattern contains the generic currency sign, replace it with the localized // currency symbol (if one exists), so that when the pattern is displayed (such as in an error // message), it is more meaningful to the user. // If the pattern contains double international currency symbol, replace it with the international currency symbol. // For an explanation of this behavior, see section "Special Pattern Characters" at: // http://java.sun.com/javase/6/docs/api/java/text/DecimalFormat.html // The unicode for the international currency symbol is: \u00A4 // The xml hex is : ¤ int idx = pattern.indexOf('\u00A4'); if (idx == -1) return pattern; if (idx + 1 < pattern.length() && pattern.charAt(idx + 1) == '\u00A4') { // Matcher.quoteReplacement ensures that the replacement string is properly escaped. String symbol = dfs.getInternationalCurrencySymbol(); if (symbol.length() > 0) pattern = pattern.replaceAll(new String(new char[] {'\u00A4', '\u00A4'}), Matcher.quoteReplacement(symbol)); } else { // Matcher.quoteReplacement ensures that the replacement string is properly escaped. String symbol = dfs.getCurrencySymbol(); if (symbol.length() > 0) pattern = pattern.replaceAll(new String(new char[] {'\u00A4'}), Matcher.quoteReplacement(symbol)); } return pattern; } @Override public void setType(String type) { _facesBean.setProperty(_TYPE_KEY, type); } @JSFProperty(defaultValue="number") @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 + (isDisabled() ? 1 : 0); result = result * 37 + (isGroupingUsed() ? 1: 0); result = result * 37 + (isIntegerOnly()? 1: 0); result = result * 37 + (isTransient() ? 1: 0); result = result * 37 + _getHashValue(getNegativePrefix()); result = result * 37 + _getHashValue(getNegativeSuffix()); result = result * 37 + _getHashValue(getMessageDetailConvertPattern()); result = result * 37 + _getHashValue(getMessageDetailConvertNumber()); result = result * 37 + _getHashValue(getMessageDetailConvertCurrency()); result = result * 37 + _getHashValue(getMessageDetailConvertPercent()); return result; } /** *

Compares this NumberConverter with the specified Object for equality.

*/ @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() && isDisabled() == nConv.isDisabled() && 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(getNegativePrefix(), nConv.getNegativePrefix()) && ConverterUtils.equals(getNegativeSuffix(), nConv.getNegativeSuffix()) && 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; } /** *

Set the value to property disabled. Default value is false.

* @param isDisabled true if it's disabled, false otherwise. */ public void setDisabled(boolean isDisabled) { _facesBean.setProperty(_DISABLED_KEY, Boolean.valueOf(isDisabled)); } /** * Return whether it is disabled. * @return true if it's disabled and false if it's enabled. */ public boolean isDisabled() { Boolean disabled = (Boolean) _facesBean.getProperty(_DISABLED_KEY); return (disabled != null) ? disabled.booleanValue() : 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(); 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 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>(); 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 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(); _numberFormatHolder.put(key, nfMap); } // add this based on the type ('number','currency','percent') or // pattern1, pattern2.. patternN to the main holder nfMap.put(locale, (NumberFormat)format.clone()); } } } 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, int typeIdx, RequestContext reqCtx) { formatter.setGroupingUsed(isGroupingUsed()); if (isMaximumFractionDigitsSet()) { formatter.setMaximumFractionDigits(getMaxFractionDigits()); } if (isMaximumIntegerDigitsSet()) { formatter.setMaximumIntegerDigits(getMaxIntegerDigits()); } if (isMinimumFractionDigitsSet()) { formatter.setMinimumFractionDigits(getMinFractionDigits()); } if (isMinimumIntegerDigitsSet()) { formatter.setMinimumIntegerDigits(getMinIntegerDigits()); } if (formatter instanceof DecimalFormat) { DecimalFormat dFormatter = ((DecimalFormat)formatter); if (isNegativePrefixSet()) { String negativePrefix = ""; if (typeIdx == _CURRENCY_TYPE) { DecimalFormatSymbols symbols = dFormatter.getDecimalFormatSymbols(); negativePrefix = _replaceCurrencyFormattingCharacters(dFormatter.getNegativePrefix(), symbols.getCurrencySymbol(), getNegativePrefix(), true); } else { negativePrefix = getNegativePrefix(); } dFormatter.setNegativePrefix(negativePrefix); } if (isNegativeSuffixSet()) { String negativeSuffix = ""; if (typeIdx == _CURRENCY_TYPE) { DecimalFormatSymbols symbols = dFormatter.getDecimalFormatSymbols(); negativeSuffix = _replaceCurrencyFormattingCharacters(dFormatter.getNegativeSuffix(), symbols.getCurrencySymbol(), getNegativeSuffix(), false); } else if (typeIdx == _PERCENT_TYPE) { // If type="percent", retain the % symbol in the suffix negativeSuffix = dFormatter.getNegativeSuffix() + getNegativeSuffix(); } else { negativeSuffix = getNegativeSuffix(); } dFormatter.setNegativeSuffix(negativeSuffix); } } else { _LOG.warning("Failed to get hold of DecimalFormat \n" + "negative prefix, negative suffix will be defaulted based on locale " + reqCtx.getFormattingLocale().toString()); } RoundingMode rmode = _getRoundingMode (reqCtx); if (rmode != null) { formatter.setRoundingMode (rmode); } } /** * Replaces the locale specific formatting characters in the prefix/suffix with custom formatting, leaving the currency * symbol intact. * @param localePattern Locale specific currency prefix or suffix * @param localeDefaultCurSymb The currency symbol used in 'localePattern' * @param customPattern The replacement for the locale specific formatting characters * @param isPrefix true if localePattern is used as prefix, false if localePattern is a suffix * @return The customized prefix/suffix string */ private String _replaceCurrencyFormattingCharacters( String localePattern, String localeDefaultCurSymb, String customPattern, boolean isPrefix) { String newPattern = localePattern; if (customPattern != null) { // Spare the currency symbol while replacing the negative formatting characters int curSymblIdx = localePattern.trim().indexOf(localeDefaultCurSymb); if (curSymblIdx == -1) { // No currency symbol found in the localized currency prefix/suffix string // Replace the locale pattern entirely with the custom pattern newPattern = customPattern; } else { // if the localized currency prefix/suffix string only has the currency symbol if (localePattern.trim().equals(localeDefaultCurSymb)) { // In case of prefix formatting: put the custom prefix before the currency symbol. Ex: '(' + 'Rs.' // In case of suffix formatting: put the currency symbol before the custom prefix. Ex: 'Rs.' + ')' newPattern = (isPrefix)? customPattern + localeDefaultCurSymb : localeDefaultCurSymb + customPattern; } else if (curSymblIdx == 0) { // If currency code is located at the head of the localized prefix/suffix string // we can safely assume that the formatting chars follow the currency symbol in the pattern newPattern = localeDefaultCurSymb + customPattern; } else { // we can safely assume that the formatting chars come before the currency symbol in the prefix newPattern = customPattern + localeDefaultCurSymb; } } } return newPattern; } 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; } private RoundingMode _getRoundingMode (RequestContext context) { RoundingMode rmode = getRoundingMode (); if (rmode == null) { if (context == null) { _LOG.warning("NULL_REQUEST_CONTEXT_UNABLE_GET_ROUNDING_MODE"); } else { rmode = context.getRoundingMode(); } } return rmode; } // 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)); } /** * Return true if the maximum fraction digits have been set. If not set, return false; * @return true, if the maximum fraction digits have been set. */ public boolean isMaximumFractionDigitsSet() { return _facesBean.getProperty(_MAX_FRACTION_DIGITS_KEY) != null; } /** * Return true if the minimum fraction digits have been set. If not set, return false; * @return true, if the minimum fraction digits have been set. */ public boolean isMinimumFractionDigitsSet() { return _facesBean.getProperty(_MIN_FRACTION_DIGITS_KEY) != null; } /** * Return true if the maximum integer digits have been set. If not set, return false; * @return true, if the maximum integer digits have been set. */ public boolean isMaximumIntegerDigitsSet() { return _facesBean.getProperty(_MAX_INTEGER_DIGITS_KEY) != null; } /** * Return true if the minimum integer digits have been set. If not set, return false; * @return true, if the minimum integer digits have been set. */ public boolean isMinimumIntegerDigitsSet() { return _facesBean.getProperty(_MIN_INTEGER_DIGITS_KEY) != null; } /** * Return true if a non-null negative prefix has been set via {@link #setNegativePrefix(String)}. * If not set, return false; * @return true, if the negative prefix has been set. */ public boolean isNegativePrefixSet() { return _facesBean.getProperty(_NEGATIVE_PREFIX_KEY) != null; } /** * Return true if a non-null negative suffix has been set via {@link #setNegativeSuffix(String)}. * If not set, return false; * @return true, if the negative suffix has been set. */ public boolean isNegativeSuffixSet() { return _facesBean.getProperty(_NEGATIVE_SUFFIX_KEY) != null; } /** * Return true if a non-null rounding mode has been set via {@link #setRoundingMode(RoundingMode)}. * If not set, return false; * @return true, if the rounding mode has been set. */ public boolean isRoundingModeSet() { return _facesBean.getProperty(_ROUNDING_MODE_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 _ROUNDING_MODE_KEY = _TYPE.registerKey("roundingMode", RoundingMode.class); private static final PropertyKey _NEGATIVE_PREFIX_KEY = _TYPE.registerKey("negativePrefix", String.class); private static final PropertyKey _NEGATIVE_SUFFIX_KEY = _TYPE.registerKey("negativeSuffix", String.class); private static final PropertyKey _TYPE_KEY = _TYPE.registerKey("type", String.class, "numeric"); // Default is false private static final PropertyKey _DISABLED_KEY = _TYPE.registerKey("disabled", Boolean.class, Boolean.FALSE); 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> _numberFormatHolder = new HashMap>(); // 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 _patternFormatSymbolsHolder = new HashMap(); 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy